mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
PDF Output: Handle embedded fonts better on linux
This commit is contained in:
parent
65620d2a8b
commit
5a4d164347
@ -15,7 +15,6 @@ from calibre.customize.conversion import OutputFormatPlugin, \
|
||||
OptionRecommendation
|
||||
from calibre.ptempfile import TemporaryDirectory
|
||||
from calibre.constants import iswindows
|
||||
from calibre import walk
|
||||
|
||||
UNITS = [
|
||||
'millimeter',
|
||||
@ -138,6 +137,85 @@ class PDFOutput(OutputFormatPlugin):
|
||||
item = oeb.manifest.ids[cover_id]
|
||||
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):
|
||||
from calibre.ebooks.pdf.writer import PDFWriter
|
||||
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.get_cover_data()
|
||||
|
||||
if iswindows:
|
||||
self.remove_font_specification()
|
||||
else:
|
||||
self.handle_embedded_fonts()
|
||||
|
||||
with TemporaryDirectory('_pdf_out') as oeb_dir:
|
||||
from calibre.customize.ui import plugin_for_output_format
|
||||
oeb_output = plugin_for_output_format('oeb')
|
||||
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]
|
||||
opf = OPF(opfpath, os.path.dirname(opfpath))
|
||||
|
||||
|
@ -7,11 +7,11 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2012, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import os, sys
|
||||
import os, sys, atexit
|
||||
from itertools import product
|
||||
|
||||
from calibre import prints
|
||||
from calibre.constants import plugins
|
||||
from calibre import prints, isbytestring
|
||||
from calibre.constants import plugins, filesystem_encoding
|
||||
from calibre.utils.fonts.utils import (is_truetype_font, get_font_names,
|
||||
get_font_characteristics)
|
||||
|
||||
@ -97,6 +97,17 @@ class WinFonts(object):
|
||||
|
||||
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():
|
||||
w, err = plugins['winfonts']
|
||||
|
@ -161,21 +161,49 @@ static PyObject* font_data(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;
|
||||
int num;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "OO", &pyname, &private_font)) return NULL;
|
||||
|
||||
path = unicode_to_wchar(pyname);
|
||||
if (!PyArg_ParseTuple(args, "O", &name)) return NULL;
|
||||
path = unicode_to_wchar(name);
|
||||
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);
|
||||
|
||||
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
|
||||
PyMethodDef winfonts_methods[] = {
|
||||
{"enum_font_families", enum_font_families, METH_VARARGS,
|
||||
@ -189,8 +217,18 @@ PyMethodDef winfonts_methods[] = {
|
||||
},
|
||||
|
||||
{"add_font", add_font, METH_VARARGS,
|
||||
"add_font(filename, private)\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_font(data)\n\n"
|
||||
"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}
|
||||
|
Loading…
x
Reference in New Issue
Block a user