Start work on replacing the use of fontconfig in windows

This commit is contained in:
Kovid Goyal 2012-09-30 17:13:49 +05:30
parent b4d4133c6d
commit c6c878462e
7 changed files with 376 additions and 165 deletions

View File

@ -191,6 +191,12 @@ if iswindows:
# needs_ddk=True, # needs_ddk=True,
cflags=['/X'] cflags=['/X']
), ),
Extension('winfonts',
['calibre/utils/fonts/winfonts.cpp'],
libraries=['Gdi32', 'User32'],
cflags=['/X']
),
]) ])
if isosx: if isosx:

View File

@ -91,7 +91,7 @@ class Plugins(collections.Mapping):
'speedup', 'speedup',
] ]
if iswindows: if iswindows:
plugins.extend(['winutil', 'wpd']) plugins.extend(['winutil', 'wpd', 'winfonts'])
if isosx: if isosx:
plugins.append('usbobserver') plugins.append('usbobserver')
if islinux or isosx: if islinux or isosx:

View File

@ -151,14 +151,28 @@ class PDFOutput(OutputFormatPlugin):
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: if iswindows:
from calibre.utils.fonts.utils import remove_embed_restriction
# On windows Qt generates an image based PDF if the html uses # On windows Qt generates an image based PDF if the html uses
# embedded fonts. See https://launchpad.net/bugs/1053906 # embedded fonts. See https://launchpad.net/bugs/1053906
for f in walk(oeb_dir): for f in walk(oeb_dir):
if f.rpartition('.')[-1].lower() in {'ttf', 'otf'}: if f.rpartition('.')[-1].lower() in {'ttf', 'otf'}:
self.log.warn('Found embedded font %s, removing it, as ' fixed = False
'embedded fonts on windows are not supported by ' with open(f, 'r+b') as s:
'the PDF Output plugin'%os.path.basename(f)) raw = s.read()
os.remove(f) 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

@ -1,45 +0,0 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai
from __future__ import (unicode_literals, division, absolute_import,
print_function)
__license__ = 'GPL v3'
__copyright__ = '2012, Kovid Goyal <kovid at kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import sys, struct
class UnsupportedFont(ValueError):
pass
def remove_embed_restriction(raw):
sfnt_version = raw[:4]
if sfnt_version not in {b'\x00\x01\x00\x00', b'OTTO'}:
raise UnsupportedFont('Not a supported font, sfnt_version: %r'%sfnt_version)
num_tables = struct.unpack_from(b'>H', raw, 4)[0]
# Find OS/2 table
offset = 4 + 4*2 # Start of the Table record entries
os2_table_offset = None
for i in xrange(num_tables):
table_tag = raw[offset:offset+4]
offset += 16 # Size of a table record
if table_tag == b'OS/2':
os2_table_offset = struct.unpack_from(b'>I', raw, offset+8)[0]
break
if os2_table_offset is None:
raise UnsupportedFont('Not a supported font, has no OS/2 table')
version, = struct.unpack_from(b'>H', raw, os2_table_offset)
fs_type_offset = os2_table_offset + struct.calcsize(b'>HhHH')
fs_type = struct.unpack_from(b'>H', raw, fs_type_offset)[0]
if fs_type == 0:
return raw
return raw[:fs_type_offset] + struct.pack(b'>H', 0) + raw[fs_type_offset+2:]
if __name__ == '__main__':
remove_embed_restriction(open(sys.argv[-1], 'rb').read())

View File

@ -0,0 +1,90 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai
from __future__ import (unicode_literals, division, absolute_import,
print_function)
__license__ = 'GPL v3'
__copyright__ = '2012, Kovid Goyal <kovid at kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import sys, struct
class UnsupportedFont(ValueError):
pass
def is_truetype_font(raw):
sfnt_version = raw[:4]
return (sfnt_version in {b'\x00\x01\x00\x00', b'OTTO'}, sfnt_version)
def get_font_characteristics(raw):
num_tables = struct.unpack_from(b'>H', raw, 4)[0]
# Find OS/2 table
offset = 4 + 4*2 # Start of the Table record entries
os2_table_offset = None
for i in xrange(num_tables):
table_tag = raw[offset:offset+4]
if table_tag == b'OS/2':
os2_table_offset = struct.unpack_from(b'>I', raw, offset+8)[0]
break
offset += 16 # Size of a table record
if os2_table_offset is None:
raise UnsupportedFont('Not a supported font, has no OS/2 table')
common_fields = b'>HhHHHhhhhhhhhhhh'
(version, char_width, weight, width, fs_type, subscript_x_size,
subscript_y_size, subscript_x_offset, subscript_y_offset,
superscript_x_size, superscript_y_size, superscript_x_offset,
superscript_y_offset, strikeout_size, strikeout_position,
family_class) = struct.unpack_from(common_fields,
raw, os2_table_offset)
offset = os2_table_offset + struct.calcsize(common_fields)
panose = struct.unpack_from(b'>'+b'B'*10, raw, offset)
panose
offset += 10
(range1,) = struct.unpack_from(b'>L', raw, offset)
offset += struct.calcsize(b'>L')
if version > 0:
range2, range3, range4 = struct.unpack_from(b'>LLL', raw, offset)
offset += struct.calcsize(b'>LLL')
vendor_id = raw[offset:offset+4]
vendor_id
offset += 4
selection, = struct.unpack_from(b'>H', raw, offset)
is_italic = (selection & 0b1) != 0
is_bold = (selection & 0b100000) != 0
is_regular = (selection & 0b1000000) != 0
return weight, is_italic, is_bold, is_regular
def remove_embed_restriction(raw):
sfnt_version = raw[:4]
if sfnt_version not in {b'\x00\x01\x00\x00', b'OTTO'}:
raise UnsupportedFont('Not a supported font, sfnt_version: %r'%sfnt_version)
num_tables = struct.unpack_from(b'>H', raw, 4)[0]
# Find OS/2 table
offset = 4 + 4*2 # Start of the Table record entries
os2_table_offset = None
for i in xrange(num_tables):
table_tag = raw[offset:offset+4]
if table_tag == b'OS/2':
os2_table_offset = struct.unpack_from(b'>I', raw, offset+8)[0]
break
offset += 16 # Size of a table record
if os2_table_offset is None:
raise UnsupportedFont('Not a supported font, has no OS/2 table')
version, = struct.unpack_from(b'>H', raw, os2_table_offset)
fs_type_offset = os2_table_offset + struct.calcsize(b'>HhHH')
fs_type = struct.unpack_from(b'>H', raw, fs_type_offset)[0]
if fs_type == 0:
return raw
return raw[:fs_type_offset] + struct.pack(b'>H', 0) + raw[fs_type_offset+2:]
if __name__ == '__main__':
raw = remove_embed_restriction(open(sys.argv[-1], 'rb').read())

View File

@ -0,0 +1,112 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai
from __future__ import (unicode_literals, division, absolute_import,
print_function)
__license__ = 'GPL v3'
__copyright__ = '2012, Kovid Goyal <kovid at kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import os, sys
from itertools import product
from calibre import prints
from calibre.constants import plugins
from calibre.utils.fonts.utils import (is_truetype_font,
get_font_characteristics)
class WinFonts(object):
def __init__(self, winfonts):
self.w = winfonts
def font_families(self):
names = set()
for font in self.w.enum_font_families():
if (
font['is_truetype'] and
# Fonts with names starting with @ are designed for
# vertical text
not font['name'].startswith('@')
):
names.add(font['name'])
return sorted(names)
def get_normalized_name(self, is_italic, weight):
if is_italic:
ft = 'bi' if weight == self.w.FW_BOLD else 'italic'
else:
ft = 'bold' if weight == self.w.FW_BOLD else 'normal'
return ft
def fonts_for_family(self, family, normalize=True):
family = type(u'')(family)
ans = {}
for weight, is_italic in product( (self.w.FW_NORMAL, self.w.FW_BOLD), (False, True) ):
try:
data = self.w.font_data(family, is_italic, weight)
except Exception as e:
prints('Failed to get font data for font: %s [%s] with error: %s'%
(family, self.get_normalized_name(is_italic, weight), e))
continue
ok, sig = is_truetype_font(data)
if not ok:
prints('Not a supported font, sfnt_version: %r'%sig)
continue
ext = 'otf' if sig == b'OTTO' else 'ttf'
try:
weight, is_italic, is_bold, is_regular = get_font_characteristics(data)
except Exception as e:
prints('Failed to get font characteristic for font: %s [%s]'
' with error: %s'%(family,
self.get_normalized_name(is_italic, weight), e))
continue
if normalize:
ft = {(True, True):'bi', (True, False):'italic', (False,
True):'bold', (False, False):'normal'}[(is_italic,
is_bold)]
else:
ft = (1 if is_italic else 0, weight//10)
ans[ft] = (ext, data)
return ans
def load_winfonts():
w, err = plugins['winfonts']
if w is None:
raise RuntimeError('Failed to load the winfonts module: %s'%err)
return WinFonts(w)
def test_ttf_reading():
for f in sys.argv[1:]:
raw = open(f).read()
print (os.path.basename(f))
get_font_characteristics(raw)
print()
if __name__ == '__main__':
base = os.path.abspath(__file__)
d = os.path.dirname
pluginsd = os.path.join(d(d(d(base))), 'plugins')
if os.path.exists(os.path.join(pluginsd, 'winfonts.pyd')):
sys.path.insert(0, pluginsd)
import winfonts
w = WinFonts(winfonts)
else:
w = load_winfonts()
print (w.w)
families = w.font_families()
print (families)
for family in families:
print (family + ':')
for font, data in w.fonts_for_family(family).iteritems():
print (' ', font, data[0], len(data[1]))
print ()

View File

@ -1,168 +1,202 @@
/* /*
:mod:`fontconfig` -- Pythonic interface to Windows font api :mod:`winfont` -- Pythonic interface to Windows font api
============================================================ ============================================================
.. module:: fontconfig .. module:: winfonts
:platform: All :platform: All
:synopsis: Pythonic interface to the fontconfig library :synopsis: Pythonic interface to the windows font routines
.. moduleauthor:: Kovid Goyal <kovid@kovidgoyal.net> Copyright 2009 .. moduleauthor:: Kovid Goyal <kovid@kovidgoyal.net> Copyright 2009
*/ */
#define _UNICODE
#define UNICODE #define UNICODE
#define PY_SSIZE_T_CLEAN
#include <Windows.h> #include <Windows.h>
#include <strsafe.h> #include <Strsafe.h>
#include <vector> #include <Python.h>
#include <new>
using namespace std;
vector<BYTE> *get_font_data(HDC hdc) {
DWORD sz;
vector<BYTE> *data;
sz = GetFontData(hdc, 0, 0, NULL, 0);
data = new vector<BYTE>(sz);
if (GetFontData(hdc, 0, 0, &((*data)[0]), sz) == GDI_ERROR) {
delete data; data = NULL;
}
return data;
// Utils {{{
static wchar_t* unicode_to_wchar(PyObject *o) {
wchar_t *buf;
Py_ssize_t len;
if (o == NULL) return NULL;
if (!PyUnicode_Check(o)) {PyErr_Format(PyExc_TypeError, "The python object must be a unicode object"); return NULL;}
len = PyUnicode_GET_SIZE(o);
buf = (wchar_t *)calloc(len+2, sizeof(wchar_t));
if (buf == NULL) { PyErr_NoMemory(); return NULL; }
len = PyUnicode_AsWideChar((PyUnicodeObject*)o, buf, len);
if (len == -1) { free(buf); PyErr_Format(PyExc_TypeError, "Invalid python unicode object."); return NULL; }
return buf;
} }
BOOL is_font_embeddable(ENUMLOGFONTEX *lpelfe) { static PyObject* wchar_to_unicode(const wchar_t *o) {
HDC hdc; PyObject *ans;
HFONT font; if (o == NULL) return NULL;
HFONT old_font = NULL; ans = PyUnicode_FromWideChar(o, wcslen(o));
UINT sz; if (ans == NULL) PyErr_NoMemory();
size_t i;
LPOUTLINETEXTMETRICW metrics;
BOOL ans = TRUE;
hdc = GetDC(NULL);
font = CreateFontIndirect(&lpelfe->elfLogFont);
if (font != NULL) {
old_font = SelectObject(hdc, font);
sz = GetOutlineTextMetrics(hdc, 0, NULL);
metrics = new OUTLINETEXTMETRICW[sz];
if ( GetOutlineTextMetrics(hdc, sz, metrics) != 0) {
for ( i = 0; i < sz; i++) {
if (metrics[i].otmfsType & 0x01) {
wprintf_s(L"Not embeddable: %s\n", lpelfe->elfLogFont.lfFaceName);
ans = FALSE; break;
}
}
} else ans = FALSE;
delete[] metrics;
DeleteObject(font);
SelectObject(hdc, old_font);
} else ans = FALSE;
ReleaseDC(NULL, hdc);
return ans; return ans;
} }
int CALLBACK find_families_callback ( // }}}
ENUMLOGFONTEX *lpelfe, /* pointer to logical-font data */
NEWTEXTMETRICEX *lpntme, /* pointer to physical-font data */
int FontType, /* type of font */
LPARAM lParam /* a combo box HWND */
) {
size_t i;
LPWSTR tmp;
vector<LPWSTR> *families = (vector<LPWSTR>*)lParam;
if (FontType & TRUETYPE_FONTTYPE) { // Enumerate font families {{{
for (i = 0; i < families->size(); i++) { struct EnumData {
if (lstrcmp(families->at(i), lpelfe->elfLogFont.lfFaceName) == 0) HDC hdc;
return 1; PyObject *families;
} };
tmp = new WCHAR[LF_FACESIZE];
swprintf_s(tmp, LF_FACESIZE, L"%s", lpelfe->elfLogFont.lfFaceName);
families->push_back(tmp); static PyObject* logfont_to_dict(const ENUMLOGFONTEX *lf, const TEXTMETRIC *tm, DWORD font_type, HDC hdc) {
} PyObject *name, *full_name, *style, *script;
LOGFONT f = lf->elfLogFont;
name = wchar_to_unicode(f.lfFaceName);
full_name = wchar_to_unicode(lf->elfFullName);
style = wchar_to_unicode(lf->elfStyle);
script = wchar_to_unicode(lf->elfScript);
return Py_BuildValue("{s:N, s:N, s:N, s:N, s:O, s:O, s:O, s:O, s:l}",
"name", name,
"full_name", full_name,
"style", style,
"script", script,
"is_truetype", (font_type & TRUETYPE_FONTTYPE) ? Py_True : Py_False,
"is_italic", (tm->tmItalic != 0) ? Py_True : Py_False,
"is_underlined", (tm->tmUnderlined != 0) ? Py_True : Py_False,
"is_strikeout", (tm->tmStruckOut != 0) ? Py_True : Py_False,
"weight", tm->tmWeight
);
}
static int CALLBACK find_families_callback(const ENUMLOGFONTEX *lpelfe, const TEXTMETRIC *lpntme, DWORD font_type, LPARAM lParam) {
struct EnumData *enum_data = reinterpret_cast<struct EnumData*>(lParam);
PyObject *font = logfont_to_dict(lpelfe, lpntme, font_type, enum_data->hdc);
if (font == NULL) return 0;
PyList_Append(enum_data->families, font);
return 1; return 1;
} }
static PyObject* enum_font_families(PyObject *self, PyObject *args) {
vector<LPWSTR>* find_font_families(void) {
LOGFONTW logfont; LOGFONTW logfont;
HDC hdc; HDC hdc;
vector<LPWSTR> *families; PyObject *families;
struct EnumData enum_data;
families = new vector<LPWSTR>(); families = PyList_New(0);
if (families == NULL) return PyErr_NoMemory();
SecureZeroMemory(&logfont, sizeof(logfont)); SecureZeroMemory(&logfont, sizeof(logfont));
logfont.lfCharSet = DEFAULT_CHARSET; logfont.lfCharSet = DEFAULT_CHARSET;
logfont.lfPitchAndFamily = VARIABLE_PITCH | FF_DONTCARE; logfont.lfFaceName[0] = L'\0';
StringCchCopyW(logfont.lfFaceName, 2, L"\0");
hdc = GetDC(NULL); hdc = GetDC(NULL);
EnumFontFamiliesExW(hdc, &logfont, (FONTENUMPROC)find_families_callback, enum_data.hdc = hdc;
(LPARAM)(families), 0); enum_data.families = families;
EnumFontFamiliesExW(hdc, &logfont, (FONTENUMPROC)find_families_callback,
(LPARAM)(&enum_data), 0);
ReleaseDC(NULL, hdc); ReleaseDC(NULL, hdc);
return families; return families;
} }
inline void free_families_vector(vector<LPWSTR> *v) { // }}}
for (size_t i = 0; i < v->size(); i++) delete[] v->at(i);
delete v; static PyObject* font_data(PyObject *self, PyObject *args) {
PyObject *ans = NULL, *italic, *pyname;
LOGFONTW lf;
HDC hdc;
LONG weight;
LPWSTR family = NULL;
HGDIOBJ old_font = NULL;
HFONT hf;
DWORD sz;
char *buf;
SecureZeroMemory(&lf, sizeof(lf));
if (!PyArg_ParseTuple(args, "OOl", &pyname, &italic, &weight)) return NULL;
family = unicode_to_wchar(pyname);
if (family == NULL) { Py_DECREF(ans); return NULL; }
StringCchCopyW(lf.lfFaceName, LF_FACESIZE, family);
free(family);
lf.lfItalic = (PyObject_IsTrue(italic)) ? 1 : 0;
lf.lfWeight = weight;
lf.lfOutPrecision = OUT_TT_ONLY_PRECIS;
hdc = GetDC(NULL);
if ( (hf = CreateFontIndirect(&lf)) != NULL) {
if ( (old_font = SelectObject(hdc, hf)) != NULL ) {
sz = GetFontData(hdc, 0, 0, NULL, 0);
if (sz != GDI_ERROR) {
buf = (char*)calloc(sz, sizeof(char));
if (buf != NULL) {
if (GetFontData(hdc, 0, 0, buf, sz) != GDI_ERROR) {
ans = PyBytes_FromStringAndSize(buf, sz);
if (ans == NULL) PyErr_NoMemory();
} else PyErr_SetString(PyExc_ValueError, "GDI Error");
free(buf);
} else PyErr_NoMemory();
} else PyErr_SetString(PyExc_ValueError, "GDI Error");
SelectObject(hdc, old_font);
} else PyErr_SetFromWindowsErr(0);
DeleteObject(hf);
} else PyErr_SetFromWindowsErr(0);
ReleaseDC(NULL, hdc);
return ans;
} }
#ifdef TEST
int main(int argc, char **argv) {
vector<LPWSTR> *all_families;
size_t i;
all_families = find_font_families();
for (i = 0; i < all_families->size(); i++)
wprintf_s(L"%s\n", all_families->at(i));
free_families_vector(all_families);
HDC hdc = GetDC(NULL);
HFONT font = CreateFont(72,0,0,0,0,0,0,0,0,0,0,0,0,L"Verdana");
HFONT old_font = SelectObject(hdc, font);
vector<BYTE> *data = get_font_data(hdc);
DeleteObject(font);
SelectObject(hdc, old_font);
ReleaseDC(NULL, hdc);
if (data != NULL) printf("\nyay: %d\n", data->size());
delete data;
return 0;
}
#else
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#
static static
PyMethodDef fontconfig_methods[] = { PyMethodDef winfonts_methods[] = {
{"find_font_families", fontconfig_find_font_families, METH_VARARGS, {"enum_font_families", enum_font_families, METH_VARARGS,
"find_font_families(allowed_extensions)\n\n" "enum_font_families()\n\n"
"Find all font families on the system for fonts of the specified types. If no " "Enumerate all regular (not italic/bold/etc. variants) font families on the system. Note there will be multiple entries for every family (corresponding to each charset of the font)."
"types are specified all font families are returned."
}, },
{"font_data", font_data, METH_VARARGS,
"font_data(family_name, italic, weight)\n\n"
"Return the raw font data for the specified font."
},
{NULL, NULL, 0, NULL} {NULL, NULL, 0, NULL}
}; };
extern "C" {
PyMODINIT_FUNC PyMODINIT_FUNC
initfontconfig(void) { initwinfonts(void) {
PyObject *m; PyObject *m;
m = Py_InitModule3( m = Py_InitModule3(
"fontconfig", fontconfig_methods, "winfonts", winfonts_methods,
"Find fonts." "Windows font API"
); );
if (m == NULL) return; if (m == NULL) return;
}
PyModule_AddIntMacro(m, FW_DONTCARE);
PyModule_AddIntMacro(m, FW_THIN);
PyModule_AddIntMacro(m, FW_EXTRALIGHT);
PyModule_AddIntMacro(m, FW_ULTRALIGHT);
PyModule_AddIntMacro(m, FW_LIGHT);
PyModule_AddIntMacro(m, FW_NORMAL);
PyModule_AddIntMacro(m, FW_REGULAR);
PyModule_AddIntMacro(m, FW_MEDIUM);
PyModule_AddIntMacro(m, FW_SEMIBOLD);
PyModule_AddIntMacro(m, FW_DEMIBOLD);
PyModule_AddIntMacro(m, FW_BOLD);
PyModule_AddIntMacro(m, FW_EXTRABOLD);
PyModule_AddIntMacro(m, FW_ULTRABOLD);
PyModule_AddIntMacro(m, FW_HEAVY);
PyModule_AddIntMacro(m, FW_BLACK);
} }
#endif