PDF Output: Handle embedded fonts better on linux

This commit is contained in:
Kovid Goyal 2012-10-02 13:36:16 +05:30
parent 65620d2a8b
commit 5a4d164347
3 changed files with 144 additions and 40 deletions

View File

@ -15,7 +15,6 @@ from calibre.customize.conversion import OutputFormatPlugin, \
OptionRecommendation OptionRecommendation
from calibre.ptempfile import TemporaryDirectory from calibre.ptempfile import TemporaryDirectory
from calibre.constants import iswindows from calibre.constants import iswindows
from calibre import walk
UNITS = [ UNITS = [
'millimeter', 'millimeter',
@ -138,6 +137,85 @@ class PDFOutput(OutputFormatPlugin):
item = oeb.manifest.ids[cover_id] item = oeb.manifest.ids[cover_id]
self.cover_data = item.data self.cover_data = item.data
def handle_embedded_fonts(self):
'''
Because of QtWebKit's inability to handle embedded fonts correctly, we
remove the embedded fonts and make them available system wide instead.
If you ever move to Qt WebKit 2.3+ then this will be unnecessary.
'''
from calibre.ebooks.oeb.base import urlnormalize
from calibre.gui2 import must_use_qt
from calibre.utils.fonts.utils import get_font_names, remove_embed_restriction
from PyQt4.Qt import QFontDatabase, QByteArray
# First find all @font-face rules and remove them, adding the embedded
# fonts to Qt
family_map = {}
for item in list(self.oeb.manifest):
if not hasattr(item.data, 'cssRules'): continue
remove = set()
for i, rule in enumerate(item.data.cssRules):
if rule.type == rule.FONT_FACE_RULE:
remove.add(i)
try:
s = rule.style
src = s.getProperty('src').propertyValue[0].uri
font_family = s.getProperty('font-family').propertyValue[0].value
except:
continue
path = item.abshref(src)
ff = self.oeb.manifest.hrefs.get(urlnormalize(path), None)
if ff is None:
continue
raw = ff.data
self.oeb.manifest.remove(ff)
try:
raw = remove_embed_restriction(raw)
except:
continue
must_use_qt()
QFontDatabase.addApplicationFontFromData(QByteArray(raw))
try:
family_name = get_font_names(raw)[0]
except:
family_name = None
if family_name:
family_map[icu_lower(font_family)] = family_name
for i in sorted(remove, reverse=True):
item.data.cssRules.pop(i)
# Now map the font family name specified in the css to the actual
# family name of the embedded font (they may be different in general).
for item in self.oeb.manifest:
if not hasattr(item.data, 'cssRules'): continue
for i, rule in enumerate(item.data.cssRules):
if rule.type != rule.STYLE_RULE: continue
ff = rule.style.getProperty('font-family')
if ff is None: continue
val = ff.propertyValue
for i in xrange(val.length):
k = icu_lower(val[i].value)
if k in family_map:
val[i].value = family_map[k]
def remove_font_specification(self):
# Qt produces image based pdfs on windows when non-generic fonts are specified
# This might change in Qt WebKit 2.3+ you will have to test.
for item in self.oeb.manifest:
if not hasattr(item.data, 'cssRules'): continue
for i, rule in enumerate(item.data.cssRules):
if rule.type != rule.STYLE_RULE: continue
ff = rule.style.getProperty('font-family')
if ff is None: continue
val = ff.propertyValue
for i in xrange(val.length):
k = icu_lower(val[i].value)
if k not in {'serif', 'sans', 'sans-serif', 'sansserif',
'monospace', 'cursive', 'fantasy'}:
val[i].value = ''
def convert_text(self, oeb_book): def convert_text(self, oeb_book):
from calibre.ebooks.pdf.writer import PDFWriter from calibre.ebooks.pdf.writer import PDFWriter
from calibre.ebooks.metadata.opf2 import OPF from calibre.ebooks.metadata.opf2 import OPF
@ -145,39 +223,16 @@ class PDFOutput(OutputFormatPlugin):
self.log.debug('Serializing oeb input to disk for processing...') self.log.debug('Serializing oeb input to disk for processing...')
self.get_cover_data() self.get_cover_data()
if iswindows:
self.remove_font_specification()
else:
self.handle_embedded_fonts()
with TemporaryDirectory('_pdf_out') as oeb_dir: with TemporaryDirectory('_pdf_out') as oeb_dir:
from calibre.customize.ui import plugin_for_output_format from calibre.customize.ui import plugin_for_output_format
oeb_output = plugin_for_output_format('oeb') oeb_output = plugin_for_output_format('oeb')
oeb_output.convert(oeb_book, oeb_dir, self.input_plugin, self.opts, self.log) oeb_output.convert(oeb_book, oeb_dir, self.input_plugin, self.opts, self.log)
if iswindows:
# from calibre.utils.fonts.utils import remove_embed_restriction
# On windows Qt generates an image based PDF if the html uses
# embedded fonts. See https://launchpad.net/bugs/1053906
for f in walk(oeb_dir):
if f.rpartition('.')[-1].lower() in {'ttf', 'otf'}:
os.remove(f)
# It's not the font embedding restriction that causes
# this, even after removing the restriction, Qt still
# generates an image based document. Theoretically, it
# fixed = False
# with open(f, 'r+b') as s:
# raw = s.read()
# try:
# raw = remove_embed_restriction(raw)
# except:
# self.log.exception('Failed to remove embedding'
# ' restriction from font %s, ignoring it'%
# os.path.basename(f))
# else:
# s.seek(0)
# s.truncate()
# s.write(raw)
# fixed = True
# if not fixed:
# os.remove(f)
opfpath = glob.glob(os.path.join(oeb_dir, '*.opf'))[0] opfpath = glob.glob(os.path.join(oeb_dir, '*.opf'))[0]
opf = OPF(opfpath, os.path.dirname(opfpath)) opf = OPF(opfpath, os.path.dirname(opfpath))

View File

@ -7,11 +7,11 @@ __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 os, sys import os, sys, atexit
from itertools import product from itertools import product
from calibre import prints from calibre import prints, isbytestring
from calibre.constants import plugins from calibre.constants import plugins, filesystem_encoding
from calibre.utils.fonts.utils import (is_truetype_font, get_font_names, from calibre.utils.fonts.utils import (is_truetype_font, get_font_names,
get_font_characteristics) get_font_characteristics)
@ -97,6 +97,17 @@ class WinFonts(object):
return ans return ans
def add_system_font(self, path):
if isbytestring(path):
path = path.decode(filesystem_encoding)
path = os.path.abspath(path)
ret = self.w.add_system_font(path)
if ret > 0:
atexit.register(self.remove_system_font, path)
return ret
def remove_system_font(self, path):
return self.w.remove_system_font(path)
def load_winfonts(): def load_winfonts():
w, err = plugins['winfonts'] w, err = plugins['winfonts']

View File

@ -161,21 +161,49 @@ static PyObject* font_data(PyObject *self, PyObject *args) {
// }}} // }}}
static PyObject* add_font(PyObject *self, PyObject *args) { static PyObject* add_font(PyObject *self, PyObject *args) {
PyObject *pyname, *private_font; char *data;
Py_ssize_t sz;
DWORD num = 0;
if (!PyArg_ParseTuple(args, "s#", &data, &sz)) return NULL;
AddFontMemResourceEx(data, sz, NULL, &num);
return Py_BuildValue("k", num);
}
static PyObject* add_system_font(PyObject *self, PyObject *args) {
PyObject *name;
LPWSTR path; LPWSTR path;
int num; int num;
if (!PyArg_ParseTuple(args, "OO", &pyname, &private_font)) return NULL; if (!PyArg_ParseTuple(args, "O", &name)) return NULL;
path = unicode_to_wchar(name);
path = unicode_to_wchar(pyname);
if (path == NULL) return NULL; if (path == NULL) return NULL;
num = AddFontResourceEx(path, (PyObject_IsTrue(private_font)) ? FR_PRIVATE : 0, 0); num = AddFontResource(path);
if (num > 0)
SendMessage(HWND_BROADCAST, WM_FONTCHANGE, 0, 0);
free(path); free(path);
return Py_BuildValue("i", num); return Py_BuildValue("i", num);
} }
static PyObject* remove_system_font(PyObject *self, PyObject *args) {
PyObject *name, *ok = Py_False;
LPWSTR path;
if (!PyArg_ParseTuple(args, "O", &name)) return NULL;
path = unicode_to_wchar(name);
if (path == NULL) return NULL;
if (RemoveFontResource(path)) {
SendMessage(HWND_BROADCAST, WM_FONTCHANGE, 0, 0);
ok = Py_True;
}
free(path);
return Py_BuildValue("O", ok);
}
static static
PyMethodDef winfonts_methods[] = { PyMethodDef winfonts_methods[] = {
{"enum_font_families", enum_font_families, METH_VARARGS, {"enum_font_families", enum_font_families, METH_VARARGS,
@ -189,8 +217,18 @@ PyMethodDef winfonts_methods[] = {
}, },
{"add_font", add_font, METH_VARARGS, {"add_font", add_font, METH_VARARGS,
"add_font(filename, private)\n\n" "add_font(data)\n\n"
"Add the font(s) in filename to windows. If private is True, the font will only be available to this process and will not be installed system wide. Reeturns the number of fonts added." "Add the font(s) in the data (bytestring) to windows. Added fonts are always private. Returns the number of fonts added."
},
{"add_system_font", add_system_font, METH_VARARGS,
"add_system_font(data)\n\n"
"Add the font(s) in the specified file to the system font tables."
},
{"remove_system_font", remove_system_font, METH_VARARGS,
"remove_system_font(data)\n\n"
"Remove the font(s) in the specified file from the system font tables."
}, },
{NULL, NULL, 0, NULL} {NULL, NULL, 0, NULL}