mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Fix text rendering by using private Qt APIs
This commit is contained in:
parent
c1fa9da1cf
commit
4eae90e5c2
@ -12,6 +12,7 @@ let g:syntastic_cpp_include_dirs = [
|
|||||||
\'/usr/include/fontconfig',
|
\'/usr/include/fontconfig',
|
||||||
\'src/qtcurve/common', 'src/qtcurve',
|
\'src/qtcurve/common', 'src/qtcurve',
|
||||||
\'src/unrar',
|
\'src/unrar',
|
||||||
|
\'src/qt-harfbuzz/src',
|
||||||
\'/usr/include/ImageMagick',
|
\'/usr/include/ImageMagick',
|
||||||
\]
|
\]
|
||||||
let g:syntastic_c_include_dirs = g:syntastic_cpp_include_dirs
|
let g:syntastic_c_include_dirs = g:syntastic_cpp_include_dirs
|
||||||
|
@ -183,6 +183,13 @@ extensions = [
|
|||||||
sip_files = ['calibre/gui2/progress_indicator/QProgressIndicator.sip']
|
sip_files = ['calibre/gui2/progress_indicator/QProgressIndicator.sip']
|
||||||
),
|
),
|
||||||
|
|
||||||
|
Extension('qt_hack',
|
||||||
|
['calibre/ebooks/pdf/render/qt_hack.cpp'],
|
||||||
|
inc_dirs = ['calibre/ebooks/pdf/render', 'qt-harfbuzz/src'],
|
||||||
|
headers = ['calibre/ebooks/pdf/render/qt_hack.h'],
|
||||||
|
sip_files = ['calibre/ebooks/pdf/render/qt_hack.sip']
|
||||||
|
),
|
||||||
|
|
||||||
Extension('unrar',
|
Extension('unrar',
|
||||||
['unrar/%s.cpp'%(x.partition('.')[0]) for x in '''
|
['unrar/%s.cpp'%(x.partition('.')[0]) for x in '''
|
||||||
rar.o strlist.o strfn.o pathfn.o savepos.o smallfn.o global.o file.o
|
rar.o strlist.o strfn.o pathfn.o savepos.o smallfn.o global.o file.o
|
||||||
@ -545,6 +552,9 @@ class Build(Command):
|
|||||||
VERSION = 1.0.0
|
VERSION = 1.0.0
|
||||||
CONFIG += %s
|
CONFIG += %s
|
||||||
''')%(ext.name, ' '.join(ext.headers), ' '.join(ext.sources), archs)
|
''')%(ext.name, ' '.join(ext.headers), ' '.join(ext.sources), archs)
|
||||||
|
if ext.inc_dirs:
|
||||||
|
idir = ' '.join(ext.inc_dirs)
|
||||||
|
pro += 'INCLUDEPATH = %s\n'%idir
|
||||||
pro = pro.replace('\\', '\\\\')
|
pro = pro.replace('\\', '\\\\')
|
||||||
open(ext.name+'.pro', 'wb').write(pro)
|
open(ext.name+'.pro', 'wb').write(pro)
|
||||||
qmc = [QMAKE, '-o', 'Makefile']
|
qmc = [QMAKE, '-o', 'Makefile']
|
||||||
|
@ -100,6 +100,7 @@ class Plugins(collections.Mapping):
|
|||||||
'freetype',
|
'freetype',
|
||||||
'woff',
|
'woff',
|
||||||
'unrar',
|
'unrar',
|
||||||
|
'qt_hack',
|
||||||
]
|
]
|
||||||
if iswindows:
|
if iswindows:
|
||||||
plugins.extend(['winutil', 'wpd', 'winfonts'])
|
plugins.extend(['winutil', 'wpd', 'winfonts'])
|
||||||
|
@ -7,15 +7,17 @@ __license__ = 'GPL v3'
|
|||||||
__copyright__ = '2012, Kovid Goyal <kovid at kovidgoyal.net>'
|
__copyright__ = '2012, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
import sys, traceback, unicodedata
|
import sys, traceback
|
||||||
from math import sqrt
|
from math import sqrt
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
from functools import wraps
|
from functools import wraps, partial
|
||||||
|
|
||||||
|
import sip
|
||||||
from PyQt4.Qt import (QPaintEngine, QPaintDevice, Qt, QApplication, QPainter,
|
from PyQt4.Qt import (QPaintEngine, QPaintDevice, Qt, QApplication, QPainter,
|
||||||
QTransform, QPainterPath, QTextOption, QTextLayout,
|
QTransform, QPainterPath, QImage, QByteArray, QBuffer,
|
||||||
QImage, QByteArray, QBuffer, qRgba)
|
qRgba)
|
||||||
|
|
||||||
|
from calibre.constants import plugins
|
||||||
from calibre.ebooks.pdf.render.serialize import (Color, PDFStream, Path)
|
from calibre.ebooks.pdf.render.serialize import (Color, PDFStream, Path)
|
||||||
from calibre.ebooks.pdf.render.common import inch, A4
|
from calibre.ebooks.pdf.render.common import inch, A4
|
||||||
from calibre.utils.fonts.sfnt.container import Sfnt
|
from calibre.utils.fonts.sfnt.container import Sfnt
|
||||||
@ -215,14 +217,15 @@ class PdfEngine(QPaintEngine):
|
|||||||
self.graphics_state = GraphicsState()
|
self.graphics_state = GraphicsState()
|
||||||
self.errors_occurred = False
|
self.errors_occurred = False
|
||||||
self.errors, self.debug = errors, debug
|
self.errors, self.debug = errors, debug
|
||||||
self.text_option = QTextOption()
|
|
||||||
self.text_option.setWrapMode(QTextOption.NoWrap)
|
|
||||||
self.fonts = {}
|
self.fonts = {}
|
||||||
i = QImage(1, 1, QImage.Format_ARGB32)
|
i = QImage(1, 1, QImage.Format_ARGB32)
|
||||||
i.fill(qRgba(0, 0, 0, 255))
|
i.fill(qRgba(0, 0, 0, 255))
|
||||||
self.alpha_bit = i.constBits().asstring(4).find(b'\xff')
|
self.alpha_bit = i.constBits().asstring(4).find(b'\xff')
|
||||||
self.current_page_num = 1
|
self.current_page_num = 1
|
||||||
self.current_page_inited = False
|
self.current_page_inited = False
|
||||||
|
self.qt_hack, err = plugins['qt_hack']
|
||||||
|
if err:
|
||||||
|
raise RuntimeError('Failed to load qt_hack with err: %s'%err)
|
||||||
|
|
||||||
def init_page(self):
|
def init_page(self):
|
||||||
self.pdf.transform(self.pdf_system)
|
self.pdf.transform(self.pdf_system)
|
||||||
@ -421,98 +424,48 @@ class PdfEngine(QPaintEngine):
|
|||||||
self.pdf.draw_rect(bl.x(), bl.y(), rect.width(), rect.height(),
|
self.pdf.draw_rect(bl.x(), bl.y(), rect.width(), rect.height(),
|
||||||
stroke=self.do_stroke, fill=self.do_fill)
|
stroke=self.do_stroke, fill=self.do_fill)
|
||||||
|
|
||||||
def get_text_layout(self, text_item, text):
|
def create_sfnt(self, text_item):
|
||||||
tl = QTextLayout(text, text_item.font(), self.paintDevice())
|
get_table = partial(self.qt_hack.get_sfnt_table, text_item)
|
||||||
self.text_option.setTextDirection(Qt.RightToLeft if
|
ans = Font(Sfnt(get_table))
|
||||||
text_item.renderFlags() & text_item.RightToLeft else Qt.LeftToRight)
|
glyph_map = self.qt_hack.get_glyph_map(text_item)
|
||||||
tl.setTextOption(self.text_option)
|
gm = {}
|
||||||
return tl
|
for uc, glyph_id in enumerate(glyph_map):
|
||||||
|
if glyph_id not in gm:
|
||||||
def update_glyph_map(self, text, indices, text_item, glyph_map):
|
gm[glyph_id] = unichr(uc)
|
||||||
'''
|
ans.full_glyph_map = gm
|
||||||
Map glyphs back to the unicode text they represent.
|
|
||||||
'''
|
|
||||||
pos = 0
|
|
||||||
tl = self.get_text_layout(text_item, '')
|
|
||||||
indices = list(indices)
|
|
||||||
|
|
||||||
def get_glyphs(string):
|
|
||||||
tl.setText(string)
|
|
||||||
tl.beginLayout()
|
|
||||||
line = tl.createLine()
|
|
||||||
if not line.isValid():
|
|
||||||
tl.endLayout()
|
|
||||||
return []
|
|
||||||
line.setLineWidth(int(1e12))
|
|
||||||
tl.endLayout()
|
|
||||||
ans = []
|
|
||||||
for run in tl.glyphRuns():
|
|
||||||
ans.extend(run.glyphIndexes())
|
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
ipos = 0
|
|
||||||
while ipos < len(indices):
|
|
||||||
if indices[ipos] in glyph_map:
|
|
||||||
t = glyph_map[indices[ipos]]
|
|
||||||
if t == text[pos:pos+len(t)]:
|
|
||||||
pos += len(t)
|
|
||||||
ipos += 1
|
|
||||||
continue
|
|
||||||
|
|
||||||
found = False
|
|
||||||
for l in xrange(1, 10):
|
|
||||||
string = text[pos:pos+l]
|
|
||||||
g = get_glyphs(string)
|
|
||||||
if g and g[0] == indices[ipos]:
|
|
||||||
found = True
|
|
||||||
glyph_map[g[0]] = string
|
|
||||||
break
|
|
||||||
if not found:
|
|
||||||
self.debug(
|
|
||||||
'Failed to find glyph->unicode mapping for text: %s'%text)
|
|
||||||
break
|
|
||||||
ipos += 1
|
|
||||||
pos += l
|
|
||||||
|
|
||||||
return text[pos:]
|
|
||||||
|
|
||||||
@store_error
|
@store_error
|
||||||
def drawTextItem(self, point, text_item):
|
def drawTextItem(self, point, text_item):
|
||||||
# super(PdfEngine, self).drawTextItem(point, text_item)
|
# super(PdfEngine, self).drawTextItem(point, text_item)
|
||||||
self.graphics_state(self)
|
self.graphics_state(self)
|
||||||
text = type(u'')(text_item.text()).replace('\n', ' ')
|
gi = self.qt_hack.get_glyphs(point, text_item)
|
||||||
text = unicodedata.normalize('NFKC', text)
|
if not gi.indices:
|
||||||
tl = self.get_text_layout(text_item, text)
|
sip.delete(gi)
|
||||||
tl.setPosition(point)
|
|
||||||
tl.beginLayout()
|
|
||||||
line = tl.createLine()
|
|
||||||
if not line.isValid():
|
|
||||||
tl.endLayout()
|
|
||||||
return
|
return
|
||||||
line.setLineWidth(int(1e12))
|
name = hash(bytes(gi.name))
|
||||||
tl.endLayout()
|
|
||||||
for run in tl.glyphRuns():
|
|
||||||
rf = run.rawFont()
|
|
||||||
name = hash(bytes(rf.fontTable('name')))
|
|
||||||
if name not in self.fonts:
|
if name not in self.fonts:
|
||||||
self.fonts[name] = Font(Sfnt(rf))
|
self.fonts[name] = self.create_sfnt(text_item)
|
||||||
metrics = self.fonts[name]
|
metrics = self.fonts[name]
|
||||||
indices = run.glyphIndexes()
|
for glyph_id in gi.indices:
|
||||||
text = self.update_glyph_map(text, indices, text_item, metrics.glyph_map)
|
try:
|
||||||
|
metrics.glyph_map[glyph_id] = metrics.full_glyph_map[glyph_id]
|
||||||
|
except (KeyError, ValueError):
|
||||||
|
pass
|
||||||
glyphs = []
|
glyphs = []
|
||||||
pdf_pos = point
|
pdf_pos = point
|
||||||
first_baseline = None
|
first_baseline = None
|
||||||
for i, pos in enumerate(run.positions()):
|
for i, pos in enumerate(gi.positions):
|
||||||
if first_baseline is None:
|
if first_baseline is None:
|
||||||
first_baseline = pos.y()
|
first_baseline = pos.y()
|
||||||
glyph_pos = point + pos
|
glyph_pos = pos
|
||||||
delta = glyph_pos - pdf_pos
|
delta = glyph_pos - pdf_pos
|
||||||
glyphs.append((delta.x(), pos.y()-first_baseline, indices[i]))
|
glyphs.append((delta.x(), pos.y()-first_baseline, gi.indices[i]))
|
||||||
pdf_pos = glyph_pos
|
pdf_pos = glyph_pos
|
||||||
|
|
||||||
self.pdf.draw_glyph_run([1, 0, 0, -1, point.x(),
|
self.pdf.draw_glyph_run([1, 0, 0, -1, point.x(),
|
||||||
point.y()], rf.pixelSize(), metrics, glyphs)
|
point.y()], gi.size, metrics, glyphs)
|
||||||
|
sip.delete(gi)
|
||||||
|
|
||||||
@store_error
|
@store_error
|
||||||
def drawPolygon(self, points, mode):
|
def drawPolygon(self, points, mode):
|
||||||
@ -645,12 +598,12 @@ if __name__ == '__main__':
|
|||||||
# f.setUnderline(True)
|
# f.setUnderline(True)
|
||||||
# f.setOverline(True)
|
# f.setOverline(True)
|
||||||
# f.setStrikeOut(True)
|
# f.setStrikeOut(True)
|
||||||
f.setFamily('DejaVu Sans')
|
f.setFamily('Calibri')
|
||||||
p.setFont(f)
|
p.setFont(f)
|
||||||
# p.setPen(QColor(0, 0, 255))
|
# p.setPen(QColor(0, 0, 255))
|
||||||
# p.scale(2, 2)
|
# p.scale(2, 2)
|
||||||
# p.rotate(45)
|
# p.rotate(45)
|
||||||
p.drawText(QPoint(0, 300), 'Some—text not By’s ū --- Д AV ff ff')
|
p.drawText(QPoint(300, 300), 'Some—text not By’s ū --- Д AV ff ff')
|
||||||
finally:
|
finally:
|
||||||
p.end()
|
p.end()
|
||||||
if dev.engine.errors_occurred:
|
if dev.engine.errors_occurred:
|
||||||
|
66
src/calibre/ebooks/pdf/render/qt_hack.cpp
Normal file
66
src/calibre/ebooks/pdf/render/qt_hack.cpp
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* qt_hack.cpp
|
||||||
|
* Copyright (C) 2012 Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
*
|
||||||
|
* Distributed under terms of the GPL3 license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "qt_hack.h"
|
||||||
|
|
||||||
|
#include <QtEndian>
|
||||||
|
|
||||||
|
#include "private/qtextengine_p.h"
|
||||||
|
#include "private/qfontengine_p.h"
|
||||||
|
|
||||||
|
GlyphInfo* get_glyphs(QPointF &p, const QTextItem &text_item) {
|
||||||
|
QTextItemInt ti = static_cast<const QTextItemInt &>(text_item);
|
||||||
|
QFontEngine *fe = ti.fontEngine;
|
||||||
|
qreal size = ti.fontEngine->fontDef.pixelSize;
|
||||||
|
#ifdef Q_WS_WIN
|
||||||
|
if (ti.fontEngine->type() == QFontEngine::Win) {
|
||||||
|
QFontEngineWin *fe = static_cast<QFontEngineWin *>(ti.fontEngine);
|
||||||
|
size = fe->tm.tmHeight;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
QVarLengthArray<glyph_t> glyphs;
|
||||||
|
QVarLengthArray<QFixedPoint> positions;
|
||||||
|
QTransform m = QTransform::fromTranslate(p.x(), p.y());
|
||||||
|
fe->getGlyphPositions(ti.glyphs, m, ti.flags, glyphs, positions);
|
||||||
|
QVector<QPointF> points = QVector<QPointF>(positions.count());
|
||||||
|
for (int i = 0; i < positions.count(); i++) {
|
||||||
|
points[i].setX(positions[i].x.toReal());
|
||||||
|
points[i].setY(positions[i].y.toReal());
|
||||||
|
}
|
||||||
|
|
||||||
|
QVector<unsigned int> indices = QVector<unsigned int>(glyphs.count());
|
||||||
|
for (int i = 0; i < glyphs.count(); i++)
|
||||||
|
indices[i] = (unsigned int)glyphs[i];
|
||||||
|
|
||||||
|
const quint32 *tag = reinterpret_cast<const quint32 *>("name");
|
||||||
|
|
||||||
|
return new GlyphInfo(fe->getSfntTable(qToBigEndian(*tag)), size, points, indices);
|
||||||
|
}
|
||||||
|
|
||||||
|
GlyphInfo::GlyphInfo(const QByteArray& name, qreal size, const QVector<QPointF> &positions, const QVector<unsigned int> &indices) :name(name), positions(positions), size(size), indices(indices) {
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray get_sfnt_table(const QTextItem &text_item, const char* tag_name) {
|
||||||
|
QTextItemInt ti = static_cast<const QTextItemInt &>(text_item);
|
||||||
|
const quint32 *tag = reinterpret_cast<const quint32 *>(tag_name);
|
||||||
|
return ti.fontEngine->getSfntTable(qToBigEndian(*tag));
|
||||||
|
}
|
||||||
|
|
||||||
|
QVector<unsigned int>* get_glyph_map(const QTextItem &text_item) {
|
||||||
|
QTextItemInt ti = static_cast<const QTextItemInt &>(text_item);
|
||||||
|
QVector<unsigned int> *ans = new QVector<unsigned int>(0x10000);
|
||||||
|
QGlyphLayoutArray<10> glyphs;
|
||||||
|
int nglyphs = 10;
|
||||||
|
|
||||||
|
for (uint uc = 0; uc < 0x10000; ++uc) {
|
||||||
|
QChar ch(uc);
|
||||||
|
ti.fontEngine->stringToCMap(&ch, 1, &glyphs, &nglyphs, QTextEngine::GlyphIndicesOnly);
|
||||||
|
(*ans)[uc] = glyphs.glyphs[0];
|
||||||
|
}
|
||||||
|
return ans;
|
||||||
|
}
|
||||||
|
|
34
src/calibre/ebooks/pdf/render/qt_hack.h
Normal file
34
src/calibre/ebooks/pdf/render/qt_hack.h
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* qt_hack.h
|
||||||
|
* Copyright (C) 2012 Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
*
|
||||||
|
* Distributed under terms of the GPL3 license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QGlyphRun>
|
||||||
|
#include <QTextItem>
|
||||||
|
#include <QPointF>
|
||||||
|
|
||||||
|
|
||||||
|
class GlyphInfo {
|
||||||
|
public:
|
||||||
|
QByteArray name;
|
||||||
|
QVector<QPointF> positions;
|
||||||
|
qreal size;
|
||||||
|
QVector<unsigned int> indices;
|
||||||
|
|
||||||
|
GlyphInfo(const QByteArray &name, qreal size, const QVector<QPointF> &positions, const QVector<unsigned int> &indices);
|
||||||
|
|
||||||
|
private:
|
||||||
|
GlyphInfo(const GlyphInfo&);
|
||||||
|
GlyphInfo &operator=(const GlyphInfo&);
|
||||||
|
};
|
||||||
|
|
||||||
|
GlyphInfo* get_glyphs(QPointF &p, const QTextItem &text_item);
|
||||||
|
|
||||||
|
QByteArray get_sfnt_table(const QTextItem &text_item, const char* tag_name);
|
||||||
|
|
||||||
|
QVector<unsigned int>* get_glyph_map(const QTextItem &text_item);
|
||||||
|
|
28
src/calibre/ebooks/pdf/render/qt_hack.sip
Normal file
28
src/calibre/ebooks/pdf/render/qt_hack.sip
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
//Define the SIP wrapper to the qt_hack code
|
||||||
|
//Author - Kovid Goyal <kovid@kovidgoyal.net>
|
||||||
|
|
||||||
|
%Module(name=qt_hack, version=1)
|
||||||
|
|
||||||
|
%Import QtCore/QtCoremod.sip
|
||||||
|
%Import QtGui/QtGuimod.sip
|
||||||
|
|
||||||
|
class GlyphInfo {
|
||||||
|
%TypeHeaderCode
|
||||||
|
#include <qt_hack.h>
|
||||||
|
%End
|
||||||
|
public:
|
||||||
|
QByteArray name;
|
||||||
|
qreal size;
|
||||||
|
QVector<QPointF> &positions;
|
||||||
|
QVector<unsigned int> indices;
|
||||||
|
GlyphInfo(const QByteArray &name, qreal size, const QVector<QPointF> &positions, const QVector<unsigned int> &indices);
|
||||||
|
private:
|
||||||
|
GlyphInfo(const GlyphInfo& g);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
GlyphInfo* get_glyphs(QPointF &p, const QTextItem &text_item);
|
||||||
|
|
||||||
|
QByteArray get_sfnt_table(const QTextItem &text_item, const char* tag_name);
|
||||||
|
|
||||||
|
QVector<unsigned int>* get_glyph_map(const QTextItem &text_item);
|
@ -44,10 +44,10 @@ class Sfnt(object):
|
|||||||
b'post' : PostTable,
|
b'post' : PostTable,
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, raw_or_qrawfont):
|
def __init__(self, raw_or_get_table):
|
||||||
self.tables = {}
|
self.tables = {}
|
||||||
if isinstance(raw_or_qrawfont, bytes):
|
if isinstance(raw_or_get_table, bytes):
|
||||||
raw = raw_or_qrawfont
|
raw = raw_or_get_table
|
||||||
self.sfnt_version = raw[:4]
|
self.sfnt_version = raw[:4]
|
||||||
if self.sfnt_version not in {b'\x00\x01\x00\x00', b'OTTO', b'true',
|
if self.sfnt_version not in {b'\x00\x01\x00\x00', b'OTTO', b'true',
|
||||||
b'type1'}:
|
b'type1'}:
|
||||||
@ -62,7 +62,7 @@ class Sfnt(object):
|
|||||||
b'VORG', b'EBDT', b'EBLC', b'EBSC', b'BASE', b'GSUB', b'GPOS',
|
b'VORG', b'EBDT', b'EBLC', b'EBSC', b'BASE', b'GSUB', b'GPOS',
|
||||||
b'GDEF', b'JSTF', b'gasp', b'hdmx', b'kern', b'LTSH', b'PCLT',
|
b'GDEF', b'JSTF', b'gasp', b'hdmx', b'kern', b'LTSH', b'PCLT',
|
||||||
b'VDMX', b'vhea', b'vmtx', b'MATH'}:
|
b'VDMX', b'vhea', b'vmtx', b'MATH'}:
|
||||||
table = bytes(raw_or_qrawfont.fontTable(table_tag))
|
table = bytes(raw_or_get_table(table_tag))
|
||||||
if table:
|
if table:
|
||||||
self.tables[table_tag] = self.TABLE_MAP.get(
|
self.tables[table_tag] = self.TABLE_MAP.get(
|
||||||
table_tag, UnknownTable)(table)
|
table_tag, UnknownTable)(table)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user