mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Allow adding fonts in WOFF formats
Switch to using fontools to read font metadata instead of calibre code since it supports WOFF as well.
This commit is contained in:
parent
82522d710f
commit
bb4807cf99
@ -66,7 +66,7 @@ def pretty_opf(root):
|
||||
cat = 2
|
||||
elif mt.startswith('image/'):
|
||||
cat = 3
|
||||
elif ext in {'otf', 'ttf', 'woff'}:
|
||||
elif ext in {'otf', 'ttf', 'woff', 'woff2'}:
|
||||
cat = 4
|
||||
elif mt.startswith('audio/'):
|
||||
cat = 5
|
||||
|
@ -32,7 +32,7 @@ def get_category(name, mt):
|
||||
elif mt in OEB_DOCS:
|
||||
category = 'text'
|
||||
ext = name.rpartition('.')[-1].lower()
|
||||
if ext in {'ttf', 'otf', 'woff'}:
|
||||
if ext in {'ttf', 'otf', 'woff', 'woff2'}:
|
||||
# Probably wrong mimetype in the OPF
|
||||
category = 'font'
|
||||
elif ext == 'opf':
|
||||
|
@ -10,8 +10,8 @@ import shutil
|
||||
from qt.core import (
|
||||
QAbstractItemView, QDialog, QDialogButtonBox, QFont, QFontComboBox, QFontDatabase,
|
||||
QFontInfo, QFontMetrics, QGridLayout, QHBoxLayout, QIcon, QLabel, QLineEdit,
|
||||
QListView, QPen, QPushButton, QSize, QSizePolicy, QStringListModel, QStyle,
|
||||
QStyledItemDelegate, Qt, QToolButton, QVBoxLayout, QWidget, pyqtSignal,
|
||||
QListView, QPen, QPushButton, QRawFont, QSize, QSizePolicy, QStringListModel,
|
||||
QStyle, QStyledItemDelegate, Qt, QToolButton, QVBoxLayout, QWidget, pyqtSignal,
|
||||
)
|
||||
|
||||
from calibre.constants import config_dir
|
||||
@ -20,24 +20,21 @@ from calibre.utils.icu import lower as icu_lower
|
||||
|
||||
|
||||
def add_fonts(parent):
|
||||
from calibre.utils.fonts.metadata import FontMetadata
|
||||
files = choose_files(parent, 'add fonts to calibre',
|
||||
_('Select font files'), filters=[(_('TrueType/OpenType Fonts'),
|
||||
['ttf', 'otf'])], all_files=False)
|
||||
['ttf', 'otf', 'woff', 'woff2'])], all_files=False)
|
||||
if not files:
|
||||
return
|
||||
families = set()
|
||||
for f in files:
|
||||
try:
|
||||
with open(f, 'rb') as stream:
|
||||
fm = FontMetadata(stream)
|
||||
except:
|
||||
import traceback
|
||||
r = QRawFont()
|
||||
r.loadFromFile(f, 11.0, QFont.HintingPreference.PreferDefaultHinting)
|
||||
if r.isValid():
|
||||
families.add(r.familyName())
|
||||
else:
|
||||
error_dialog(parent, _('Corrupt font'),
|
||||
_('Failed to read metadata from the font file: %s')%
|
||||
f, det_msg=traceback.format_exc(), show=True)
|
||||
_('Failed to load font from the file: {}').format(f), show=True)
|
||||
return
|
||||
families.add(fm.font_family)
|
||||
families = sorted(families)
|
||||
|
||||
dest = os.path.join(config_dir, 'fonts')
|
||||
|
@ -503,7 +503,7 @@ class FileList(QTreeWidget, OpenWithHandler):
|
||||
elif mt in OEB_DOCS:
|
||||
category = 'text'
|
||||
ext = name.rpartition('.')[-1].lower()
|
||||
if ext in {'ttf', 'otf', 'woff'}:
|
||||
if ext in {'ttf', 'otf', 'woff', 'woff2'}:
|
||||
# Probably wrong mimetype in the OPF
|
||||
category = 'fonts'
|
||||
return category
|
||||
|
@ -243,7 +243,7 @@ class ManageFonts(Dialog):
|
||||
h.setContentsMargins(0, 0, 0, 0)
|
||||
self.install_fonts_button = b = QPushButton(_('&Install fonts'), self)
|
||||
h.addWidget(b), b.setIcon(QIcon.ic('plus.png'))
|
||||
b.setToolTip(textwrap.fill(_('Install fonts from .ttf/.otf files to make them available for embedding')))
|
||||
b.setToolTip(textwrap.fill(_('Install fonts from font files to make them available for embedding')))
|
||||
b.clicked.connect(self.install_fonts)
|
||||
l.addWidget(s), l.addLayout(h), h.addStretch(10), h.addWidget(self.bb)
|
||||
|
||||
|
@ -6,10 +6,9 @@ __copyright__ = '2012, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
from io import BytesIO
|
||||
from struct import calcsize, unpack, unpack_from
|
||||
from collections import namedtuple
|
||||
|
||||
from calibre.utils.fonts.utils import get_font_names2, get_font_characteristics
|
||||
from calibre.utils.fonts.utils import get_font_names_from_ttlib_names_table, get_font_characteristics
|
||||
|
||||
|
||||
class UnsupportedFont(ValueError):
|
||||
@ -25,18 +24,19 @@ FontNames = namedtuple('FontNames',
|
||||
class FontMetadata:
|
||||
|
||||
def __init__(self, bytes_or_stream):
|
||||
from fontTools.subset import load_font, Subsetter
|
||||
if not hasattr(bytes_or_stream, 'read'):
|
||||
bytes_or_stream = BytesIO(bytes_or_stream)
|
||||
f = bytes_or_stream
|
||||
f.seek(0)
|
||||
header = f.read(4)
|
||||
if header not in {b'\x00\x01\x00\x00', b'OTTO'}:
|
||||
raise UnsupportedFont('Not a supported sfnt variant')
|
||||
|
||||
self.is_otf = header == b'OTTO'
|
||||
self.read_table_metadata(f)
|
||||
self.read_names(f)
|
||||
self.read_characteristics(f)
|
||||
s = Subsetter()
|
||||
try:
|
||||
font = load_font(f, s.options, dontLoadGlyphNames=True)
|
||||
except Exception as e:
|
||||
raise UnsupportedFont(str(e)) from e
|
||||
self.is_otf = font.sfntVersion == 'OTTO'
|
||||
self._read_names(font)
|
||||
self._read_characteristics(font)
|
||||
|
||||
f.seek(0)
|
||||
self.font_family = self.names.family_name
|
||||
@ -60,41 +60,20 @@ class FontMetadata:
|
||||
else:
|
||||
self.font_style = 'normal'
|
||||
|
||||
def read_table_metadata(self, f):
|
||||
f.seek(4)
|
||||
num_tables = unpack(b'>H', f.read(2))[0]
|
||||
# Start of table record entries
|
||||
f.seek(4 + 4*2)
|
||||
table_record = b'>4s3L'
|
||||
sz = calcsize(table_record)
|
||||
self.tables = {}
|
||||
block = f.read(sz * num_tables)
|
||||
for i in range(num_tables):
|
||||
table_tag, table_checksum, table_offset, table_length = \
|
||||
unpack_from(table_record, block, i*sz)
|
||||
self.tables[table_tag.lower()] = (table_offset, table_length,
|
||||
table_checksum)
|
||||
|
||||
def read_names(self, f):
|
||||
if b'name' not in self.tables:
|
||||
def _read_names(self, font):
|
||||
try:
|
||||
name_table = font['name']
|
||||
except KeyError:
|
||||
raise UnsupportedFont('This font has no name table')
|
||||
toff, tlen = self.tables[b'name'][:2]
|
||||
f.seek(toff)
|
||||
table = f.read(tlen)
|
||||
if len(table) != tlen:
|
||||
raise UnsupportedFont('This font has a name table of incorrect length')
|
||||
vals = get_font_names2(table, raw_is_table=True)
|
||||
self.names = FontNames(*vals)
|
||||
self.names = FontNames(*get_font_names_from_ttlib_names_table(name_table))
|
||||
|
||||
def read_characteristics(self, f):
|
||||
if b'os/2' not in self.tables:
|
||||
def _read_characteristics(self, font):
|
||||
try:
|
||||
os2_table = font['OS/2']
|
||||
except KeyError:
|
||||
raise UnsupportedFont('This font has no OS/2 table')
|
||||
toff, tlen = self.tables[b'os/2'][:2]
|
||||
f.seek(toff)
|
||||
table = f.read(tlen)
|
||||
if len(table) != tlen:
|
||||
raise UnsupportedFont('This font has an OS/2 table of incorrect length')
|
||||
vals = get_font_characteristics(table, raw_is_table=True)
|
||||
|
||||
vals = get_font_characteristics(os2_table, raw_is_table=True)
|
||||
self.characteristics = FontCharacteristics(*vals)
|
||||
|
||||
def to_dict(self):
|
||||
|
@ -48,6 +48,32 @@ def get_table(raw, name):
|
||||
return None, None, None, None
|
||||
|
||||
|
||||
def get_font_characteristics_from_ttlib_os2_table(t, return_all=False):
|
||||
(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, selection, version) = (
|
||||
t.xAvgCharWidth, t.usWeightClass, t.usWidthClass, t.fsType,
|
||||
t.ySubscriptXSize, t.ySubscriptYSize, t.ySubscriptXOffset, t.ySubscriptYOffset,
|
||||
t.ySuperscriptXSize, t.ySuperscriptYSize, t.ySuperscriptXOffset, t.ySuperscriptYOffset,
|
||||
t.yStrikeoutSize, t.yStrikeoutPosition, t.sFamilyClass, t.fsSelection, t.version)
|
||||
is_italic = (selection & (1 << 0)) != 0
|
||||
is_bold = (selection & (1 << 5)) != 0
|
||||
is_regular = (selection & (1 << 6)) != 0
|
||||
is_wws = (selection & (1 << 8)) != 0
|
||||
is_oblique = (selection & (1 << 9)) != 0
|
||||
p = t.panose
|
||||
panose = (p.bFamilyType, p.bSerifStyle, p.bWeight, p.bProportion, p.bContrast, p.bStrokeVariation, p.bArmStyle, p.bLetterForm, p.bMidline, p.bXHeight)
|
||||
|
||||
if return_all:
|
||||
return (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, panose, selection, is_italic, is_bold, is_regular)
|
||||
|
||||
return weight, is_italic, is_bold, is_regular, fs_type, panose, width, is_oblique, is_wws, version
|
||||
|
||||
|
||||
def get_font_characteristics(raw, raw_is_table=False, return_all=False):
|
||||
'''
|
||||
Return (weight, is_italic, is_bold, is_regular, fs_type, panose, width,
|
||||
@ -55,6 +81,8 @@ def get_font_characteristics(raw, raw_is_table=False, return_all=False):
|
||||
values are taken from the OS/2 table of the font. See
|
||||
http://www.microsoft.com/typography/otspec/os2.htm for details
|
||||
'''
|
||||
if hasattr(raw, 'getUnicodeRanges'):
|
||||
return get_font_characteristics_from_ttlib_os2_table(raw, return_all)
|
||||
if raw_is_table:
|
||||
os2_table = raw
|
||||
else:
|
||||
@ -196,6 +224,29 @@ def _get_font_names(raw, raw_is_table=False):
|
||||
return records
|
||||
|
||||
|
||||
def get_font_name_records_from_ttlib_names_table(names_table):
|
||||
records = defaultdict(list)
|
||||
for rec in names_table.names:
|
||||
records[rec.nameID].append((rec.platformID, rec.platEncID, rec.langID, rec.string))
|
||||
return records
|
||||
|
||||
|
||||
def get_font_names_from_ttlib_names_table(names_table):
|
||||
records = get_font_name_records_from_ttlib_names_table(names_table)
|
||||
family_name = decode_name_record(records[1])
|
||||
subfamily_name = decode_name_record(records[2])
|
||||
full_name = decode_name_record(records[4])
|
||||
|
||||
preferred_family_name = decode_name_record(records[16])
|
||||
preferred_subfamily_name = decode_name_record(records[17])
|
||||
|
||||
wws_family_name = decode_name_record(records[21])
|
||||
wws_subfamily_name = decode_name_record(records[22])
|
||||
|
||||
return (family_name, subfamily_name, full_name, preferred_family_name,
|
||||
preferred_subfamily_name, wws_family_name, wws_subfamily_name)
|
||||
|
||||
|
||||
def get_font_names(raw, raw_is_table=False):
|
||||
records = _get_font_names(raw, raw_is_table)
|
||||
family_name = decode_name_record(records[1])
|
||||
|
Loading…
x
Reference in New Issue
Block a user