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',
|
||||
\'src/qtcurve/common', 'src/qtcurve',
|
||||
\'src/unrar',
|
||||
\'src/qt-harfbuzz/src',
|
||||
\'/usr/include/ImageMagick',
|
||||
\]
|
||||
let g:syntastic_c_include_dirs = g:syntastic_cpp_include_dirs
|
||||
|
@ -183,6 +183,13 @@ extensions = [
|
||||
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',
|
||||
['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
|
||||
@ -545,6 +552,9 @@ class Build(Command):
|
||||
VERSION = 1.0.0
|
||||
CONFIG += %s
|
||||
''')%(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('\\', '\\\\')
|
||||
open(ext.name+'.pro', 'wb').write(pro)
|
||||
qmc = [QMAKE, '-o', 'Makefile']
|
||||
|
@ -100,6 +100,7 @@ class Plugins(collections.Mapping):
|
||||
'freetype',
|
||||
'woff',
|
||||
'unrar',
|
||||
'qt_hack',
|
||||
]
|
||||
if iswindows:
|
||||
plugins.extend(['winutil', 'wpd', 'winfonts'])
|
||||
|
@ -7,15 +7,17 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2012, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import sys, traceback, unicodedata
|
||||
import sys, traceback
|
||||
from math import sqrt
|
||||
from collections import namedtuple
|
||||
from functools import wraps
|
||||
from functools import wraps, partial
|
||||
|
||||
import sip
|
||||
from PyQt4.Qt import (QPaintEngine, QPaintDevice, Qt, QApplication, QPainter,
|
||||
QTransform, QPainterPath, QTextOption, QTextLayout,
|
||||
QImage, QByteArray, QBuffer, qRgba)
|
||||
QTransform, QPainterPath, QImage, QByteArray, QBuffer,
|
||||
qRgba)
|
||||
|
||||
from calibre.constants import plugins
|
||||
from calibre.ebooks.pdf.render.serialize import (Color, PDFStream, Path)
|
||||
from calibre.ebooks.pdf.render.common import inch, A4
|
||||
from calibre.utils.fonts.sfnt.container import Sfnt
|
||||
@ -215,14 +217,15 @@ class PdfEngine(QPaintEngine):
|
||||
self.graphics_state = GraphicsState()
|
||||
self.errors_occurred = False
|
||||
self.errors, self.debug = errors, debug
|
||||
self.text_option = QTextOption()
|
||||
self.text_option.setWrapMode(QTextOption.NoWrap)
|
||||
self.fonts = {}
|
||||
i = QImage(1, 1, QImage.Format_ARGB32)
|
||||
i.fill(qRgba(0, 0, 0, 255))
|
||||
self.alpha_bit = i.constBits().asstring(4).find(b'\xff')
|
||||
self.current_page_num = 1
|
||||
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):
|
||||
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(),
|
||||
stroke=self.do_stroke, fill=self.do_fill)
|
||||
|
||||
def get_text_layout(self, text_item, text):
|
||||
tl = QTextLayout(text, text_item.font(), self.paintDevice())
|
||||
self.text_option.setTextDirection(Qt.RightToLeft if
|
||||
text_item.renderFlags() & text_item.RightToLeft else Qt.LeftToRight)
|
||||
tl.setTextOption(self.text_option)
|
||||
return tl
|
||||
|
||||
def update_glyph_map(self, text, indices, text_item, glyph_map):
|
||||
'''
|
||||
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())
|
||||
def create_sfnt(self, text_item):
|
||||
get_table = partial(self.qt_hack.get_sfnt_table, text_item)
|
||||
ans = Font(Sfnt(get_table))
|
||||
glyph_map = self.qt_hack.get_glyph_map(text_item)
|
||||
gm = {}
|
||||
for uc, glyph_id in enumerate(glyph_map):
|
||||
if glyph_id not in gm:
|
||||
gm[glyph_id] = unichr(uc)
|
||||
ans.full_glyph_map = gm
|
||||
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
|
||||
def drawTextItem(self, point, text_item):
|
||||
# super(PdfEngine, self).drawTextItem(point, text_item)
|
||||
self.graphics_state(self)
|
||||
text = type(u'')(text_item.text()).replace('\n', ' ')
|
||||
text = unicodedata.normalize('NFKC', text)
|
||||
tl = self.get_text_layout(text_item, text)
|
||||
tl.setPosition(point)
|
||||
tl.beginLayout()
|
||||
line = tl.createLine()
|
||||
if not line.isValid():
|
||||
tl.endLayout()
|
||||
gi = self.qt_hack.get_glyphs(point, text_item)
|
||||
if not gi.indices:
|
||||
sip.delete(gi)
|
||||
return
|
||||
line.setLineWidth(int(1e12))
|
||||
tl.endLayout()
|
||||
for run in tl.glyphRuns():
|
||||
rf = run.rawFont()
|
||||
name = hash(bytes(rf.fontTable('name')))
|
||||
name = hash(bytes(gi.name))
|
||||
if name not in self.fonts:
|
||||
self.fonts[name] = Font(Sfnt(rf))
|
||||
self.fonts[name] = self.create_sfnt(text_item)
|
||||
metrics = self.fonts[name]
|
||||
indices = run.glyphIndexes()
|
||||
text = self.update_glyph_map(text, indices, text_item, metrics.glyph_map)
|
||||
for glyph_id in gi.indices:
|
||||
try:
|
||||
metrics.glyph_map[glyph_id] = metrics.full_glyph_map[glyph_id]
|
||||
except (KeyError, ValueError):
|
||||
pass
|
||||
glyphs = []
|
||||
pdf_pos = point
|
||||
first_baseline = None
|
||||
for i, pos in enumerate(run.positions()):
|
||||
for i, pos in enumerate(gi.positions):
|
||||
if first_baseline is None:
|
||||
first_baseline = pos.y()
|
||||
glyph_pos = point + pos
|
||||
glyph_pos = 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
|
||||
|
||||
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
|
||||
def drawPolygon(self, points, mode):
|
||||
@ -645,12 +598,12 @@ if __name__ == '__main__':
|
||||
# f.setUnderline(True)
|
||||
# f.setOverline(True)
|
||||
# f.setStrikeOut(True)
|
||||
f.setFamily('DejaVu Sans')
|
||||
f.setFamily('Calibri')
|
||||
p.setFont(f)
|
||||
# p.setPen(QColor(0, 0, 255))
|
||||
# p.scale(2, 2)
|
||||
# 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:
|
||||
p.end()
|
||||
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,
|
||||
}
|
||||
|
||||
def __init__(self, raw_or_qrawfont):
|
||||
def __init__(self, raw_or_get_table):
|
||||
self.tables = {}
|
||||
if isinstance(raw_or_qrawfont, bytes):
|
||||
raw = raw_or_qrawfont
|
||||
if isinstance(raw_or_get_table, bytes):
|
||||
raw = raw_or_get_table
|
||||
self.sfnt_version = raw[:4]
|
||||
if self.sfnt_version not in {b'\x00\x01\x00\x00', b'OTTO', b'true',
|
||||
b'type1'}:
|
||||
@ -62,7 +62,7 @@ class Sfnt(object):
|
||||
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'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:
|
||||
self.tables[table_tag] = self.TABLE_MAP.get(
|
||||
table_tag, UnknownTable)(table)
|
||||
|
Loading…
x
Reference in New Issue
Block a user