# # Copyright (C) 2017-2020 Dimitar Toshkov Zhekov # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by the Free # Software Foundation; either version 2 of the License, or (at your option) # any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License # for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # import struct import codecs import math from datetime import datetime, timezone from itertools import groupby from enum import IntEnum, unique from collections import OrderedDict import fnutil import fncli import bdf import bdfexp import otb1get # -- Table -- class Table: def __init__(self, name): self.data = bytearray(0) self.table_name = name def check_size(self, size): if size != self.size: raise Exception('internal error: %s size = %d instead of %d' % (self.table_name, self.size, size)) def checksum(self): cksum = 0 data = self.data + self.padding for offset in range(0, self.size, 4): cksum += struct.unpack('>L', data[offset : offset + 4])[0] return cksum & 0xFFFFFFFF def pack(self, format, value, name): try: return struct.pack(format, value) except struct.error as ex: raise Exception('%s.%s: %s' % (self.table_name, name, str(ex))) @property def size(self): return len(self.data) @property def padding(self): return bytes(((self.size + 1) & 3) ^ 1) def rewrite_uint32(self, value, offset): self.data[offset : offset + 4] = struct.pack('>L', value) def write(self, data): self.data += data def write_int8(self, value, name): self.data += self.pack('b', value, name) def write_uint8(self, value, name): self.data += self.pack('B', value, name) def write_int16(self, value, name): self.data += self.pack('>h', value, name) def write_uint16(self, value, name): self.data += self.pack('>H', value, name) def write_uint32(self, value, name): self.data += self.pack('>L', value, name) def write_uint64(self, value, name): self.data += self.pack('>Q', value, name) def write_fixed(self, value, name): self.data += self.pack('>l', round(value * 65536), name) def write_table(self, table): self.data += table.data # -- Params -- EM_SIZE_MIN = 64 EM_SIZE_MAX = 16384 EM_SIZE_DEFAULT = 1024 class Params(fncli.Params): # pylint: disable=too-many-instance-attributes def __init__(self): fncli.Params.__init__(self) self.created = datetime.now(timezone.utc) self.modified = self.created self.dir_hint = 0 self.em_size = EM_SIZE_DEFAULT self.line_gap = 0 self.low_ppem = 0 self.encoding = 'utf_8' self.w_lang_id = 0x0409 self.x_max_extent = True self.single_loca = False self.post_names = False # -- Options -- class Options(fncli.Options): def __init__(self, need_args, help_text, version_text): fncli.Options.__init__(self, need_args + ['-d', '-e', '-g', '-l', '-E', '-W'], help_text, version_text) def parse(self, name, value, params): if name == '-d': params.dir_hint = fnutil.parse_dec('DIR-HINT', value, -2, 2) elif name == '-e': params.em_size = fnutil.parse_dec('EM-SIZE', value, EM_SIZE_MIN, EM_SIZE_MAX) elif name == '-g': params.line_gap = fnutil.parse_dec('LINE-GAP', value, 0, EM_SIZE_MAX << 1) elif name == '-l': params.low_ppem = fnutil.parse_dec('LOW-PPEM', value, 1, bdf.DPARSE_LIMIT) elif name == '-E': params.encoding = value elif name == '-W': params.w_lang_id = fnutil.parse_hex('WLANG-ID', value, 0, 0x7FFF) elif name == '-X': params.x_max_extent = False elif name == '-L': params.single_loca = True elif name == '-P': params.post_names = True else: self.fallback(name, params) # -- Font -- class Font(bdfexp.Font): def __init__(self, params): bdfexp.Font.__init__(self) self.params = params self.em_ascender = 0 self.em_descender = 0 self.em_max_width = 0 self.mac_style = 0 self.line_size = 0 @property def bmp_only(self): return self.max_code <= fnutil.UNICODE_BMP_MAX @property def created(self): return Font.sfntime(self.params.created) def decode(self, data): return codecs.decode(data, self.params.encoding) def em_scale(self, value, divisor=0): return round(value * self.params.em_size / (divisor or self.bbx.height)) def em_scale_width(self, base): return self.em_scale(base.bbx.width) @property def italic_angle(self): value = self.props.get('ITALIC_ANGLE') # must be integer return fnutil.parse_dec('ITALIC_ANGLE', value, -45, 45) if value else -11.5 if self.italic else 0 @property def max_code(self): return self.chars[-1].code @property def min_code(self): return self.chars[0].code @property def modified(self): return Font.sfntime(self.params.modified) def prepare(self): self.chars.sort(key=lambda c: c.code) self.chars = [next(elem[1]) for elem in groupby(self.chars, key=lambda c: c.code)] self.props.set('CHARS', len(self.chars)) self.em_ascender = self.em_scale(self.px_ascender) self.em_descender = self.em_ascender - self.params.em_size self.em_max_width = self.em_scale_width(self) self.mac_style = int(self.bold) + (int(self.italic) << 1) self.line_size = self.em_scale(round(self.bbx.height / 17) or 1) def _read(self, input): bdfexp.Font._read(self, input) self.prepare() return self @staticmethod def read(input, params): # pylint: disable=arguments-differ return Font(params)._read(input) # pylint: disable=protected-access @staticmethod def sfntime(stamp): return math.floor((stamp - datetime(1904, 1, 1, tzinfo=timezone.utc)).total_seconds()) @property def underline_position(self): return round((self.em_descender + self.line_size) / 2) @property def x_max_extent(self): return self.em_max_width if self.params.x_max_extent else 0 # -- BDAT -- BDAT_HEADER_SIZE = 4 BDAT_METRIC_SIZE = 5 class BDAT(Table): def __init__(self, font): Table.__init__(self, 'EBDT') # header self.write_fixed(2, 'version') # format 1 data for char in font.chars: self.write_uint8(font.bbx.height, 'height') self.write_uint8(char.bbx.width, 'width') self.write_int8(0, 'bearingX') self.write_int8(font.px_ascender, 'bearingY') self.write_uint8(char.bbx.width, 'advance') self.write(char.data) # imageData @staticmethod def get_char_size(char): return BDAT_METRIC_SIZE + len(char.data) # -- BLOC -- BLOC_TABLE_SIZE_OFFSET = 12 BLOC_PREFIX_SIZE = 0x38 # header 0x08 + 1 bitmapSizeTable * 0x30 BLOC_INDEX_ARRAY_SIZE = 8 # 1 index record * 0x08 class BLOC(Table): def __init__(self, font): Table.__init__(self, 'EBLC') # header self.write_fixed(2, 'version') self.write_uint32(1, 'numSizes') # bitmapSizeTable self.write_uint32(BLOC_PREFIX_SIZE, 'indexSubTableArrayOffset') self.write_uint32(0, 'indexTableSize') # adjusted later self.write_uint32(1, 'numberOfIndexSubTables') self.write_uint32(0, 'colorRef') # hori self.write_int8(font.px_ascender, 'hori ascender') self.write_int8(font.px_descender, 'hori descender') self.write_uint8(font.bbx.width, 'hori widthMax') self.write_int8(1, 'hori caretSlopeNumerator') self.write_int8(0, 'hori caretSlopeDenominator') self.write_int8(0, 'hori caretOffset') self.write_int8(0, 'hori minOriginSB') self.write_int8(0, 'hori minAdvanceSB') self.write_int8(font.px_ascender, 'hori maxBeforeBL') self.write_int8(font.px_descender, 'hori minAfterBL') self.write_int16(0, 'hori padd') # vert self.write_int8(0, 'vert ascender') self.write_int8(0, 'vert descender') self.write_uint8(0, 'vert widthMax') self.write_int8(0, 'vert caretSlopeNumerator') self.write_int8(0, 'vert caretSlopeDenominator') self.write_int8(0, 'vert caretOffset') self.write_int8(0, 'vert minOriginSB') self.write_int8(0, 'vert minAdvanceSB') self.write_int8(0, 'vert maxBeforeBL') self.write_int8(0, 'vert minAfterBL') self.write_int16(0, 'vert padd') # (bitmapSizeTable) self.write_uint16(0, 'startGlyphIndex') self.write_uint16(len(font.chars) - 1, 'endGlyphIndex') self.write_uint8(font.bbx.height, 'ppemX') self.write_uint8(font.bbx.height, 'ppemY') self.write_uint8(1, 'bitDepth') self.write_uint8(1, 'flags') # small metrics are horizontal # indexSubTableArray self.write_uint16(0, 'firstGlyphIndex') self.write_uint16(len(font.chars) - 1, 'lastGlyphIndex') self.write_uint32(BLOC_INDEX_ARRAY_SIZE, 'additionalOffsetToIndexSubtable') # indexSubtableHeader self.write_uint16(1 if font.proportional else 2, 'indexFormat') self.write_uint16(1, 'imageFormat') # BDAT -> small metrics, byte-aligned self.write_uint32(BDAT_HEADER_SIZE, 'imageDataOffset') # indexSubtable data if font.proportional: offset = 0 for char in font.chars: self.write_uint32(offset, 'offsetArray[]') offset += BDAT.get_char_size(char) self.write_uint32(offset, 'offsetArray[]') else: self.write_uint32(BDAT.get_char_size(font.chars[0]), 'imageSize') self.write_uint8(font.bbx.height, 'height') self.write_uint8(font.bbx.width, 'width') self.write_int8(0, 'horiBearingX') self.write_int8(font.px_ascender, 'horiBearingY') self.write_uint8(font.bbx.width, 'horiAdvance') self.write_int8(-(font.bbx.width >> 1), 'vertBearingX') self.write_int8(0, 'vertBearingY') self.write_uint8(font.bbx.height, 'vertAdvance') # adjust self.rewrite_uint32(self.size - BLOC_PREFIX_SIZE, BLOC_TABLE_SIZE_OFFSET) # -- OS/2 -- OS_2_TABLE_SIZE = 96 class OS_2(Table): # pylint: disable=invalid-name def __init__(self, font): Table.__init__(self, 'OS/2') # Version 4 x_avg_char_width = font.em_scale(font.avg_width) # otb1get.x_avg_char_width(font) ul_char_ranges = otb1get.ul_char_ranges(font) ul_code_pages = otb1get.ul_code_pages(font) if font.bmp_only else [0, 0] # mostly from FontForge script_xsize = font.em_scale(30, 100) script_ysize = font.em_scale(40, 100) subscript_yoff = script_ysize >> 1 xfactor = math.tan(font.italic_angle * math.pi / 180) subscript_xoff = 0 # stub, no overlapping characters yet superscript_yoff = font.em_ascender - script_ysize superscript_xoff = -round(xfactor * superscript_yoff) # write self.write_uint16(4, 'version') self.write_int16(x_avg_char_width, 'xAvgCharWidth') self.write_uint16(700 if font.bold else 400, 'usWeightClass') self.write_uint16(5, 'usWidthClass') # medium self.write_int16(0, 'fsType') self.write_int16(script_xsize, 'ySubscriptXSize') self.write_int16(script_ysize, 'ySubscriptYSize') self.write_int16(subscript_xoff, 'ySubscriptXOffset') self.write_int16(subscript_yoff, 'ySubscriptYOffset') self.write_int16(script_xsize, 'ySuperscriptXSize') self.write_int16(script_ysize, 'ySuperscriptYSize') self.write_int16(superscript_xoff, 'ySuperscriptXOffset') self.write_int16(superscript_yoff, 'ySuperscriptYOffset') self.write_int16(font.line_size, 'yStrikeoutSize') self.write_int16(font.em_scale(25, 100), 'yStrikeoutPosition') self.write_int16(0, 'sFamilyClass') # no classification self.write_uint8(2, 'bFamilyType') # text and display self.write_uint8(0, 'bSerifStyle') # any self.write_uint8(8 if font.bold else 6, 'bWeight') self.write_uint8(3 if font.proportional else 9, 'bProportion') self.write_uint8(0, 'bContrast') self.write_uint8(0, 'bStrokeVariation') self.write_uint8(0, 'bArmStyle') self.write_uint8(0, 'bLetterform') self.write_uint8(0, 'bMidline') self.write_uint8(0, 'bXHeight') self.write_uint32(ul_char_ranges[0], 'ulCharRange1') self.write_uint32(ul_char_ranges[1], 'ulCharRange2') self.write_uint32(ul_char_ranges[2], 'ulCharRange3') self.write_uint32(ul_char_ranges[3], 'ulCharRange4') self.write_uint32(0x586F7334, 'achVendID') # 'Xos4' self.write_uint16(OS_2.fs_selection(font), 'fsSelection') self.write_uint16(min(font.min_code, fnutil.UNICODE_BMP_MAX), 'firstChar') self.write_uint16(min(font.max_code, fnutil.UNICODE_BMP_MAX), 'lastChar') self.write_int16(font.em_ascender, 'sTypoAscender') self.write_int16(font.em_descender, 'sTypoDescender') self.write_int16(font.params.line_gap, 'sTypoLineGap') self.write_uint16(font.em_ascender, 'usWinAscent') self.write_uint16(-font.em_descender, 'usWinDescent') self.write_uint32(ul_code_pages[0], 'ulCodePageRange1') self.write_uint32(ul_code_pages[1], 'ulCodePageRange2') self.write_int16(font.em_scale(font.px_ascender * 0.6), 'sxHeight') # stub self.write_int16(font.em_scale(font.px_ascender * 0.8), 'sCapHeight') # stub self.write_uint16(OS_2.default_char(font), 'usDefaultChar') self.write_uint16(OS_2.break_char(font), 'usBreakChar') self.write_uint16(1, 'usMaxContext') # check self.check_size(OS_2_TABLE_SIZE) @staticmethod def break_char(font): return 0x20 if next((char for char in font.chars if char.code == 0x20), None) else font.min_code @staticmethod def default_char(font): if font.default_code != -1 and font.default_code <= fnutil.UNICODE_BMP_MAX: return font.default_code return 0 if font.min_code == 0 else font.max_code @staticmethod def fs_selection(font): fs_selection = int(font.bold) * 5 + int(font.italic) return fs_selection if fs_selection != 0 else 0x40 if font.xlfd[bdf.XLFD.SLANT] == 'R' else 0 # -- cmap -- CMAP_4_PREFIX_SIZE = 12 CMAP_4_FORMAT_SIZE = 16 CMAP_4_SEGMENT_SIZE = 8 CMAP_12_PREFIX_SIZE = 20 CMAP_12_FORMAT_SIZE = 16 CMAP_12_GROUP_SIZE = 12 class CMapRange: def __init__(self, glyph_index=0, start_code=0, final_code=-2): self.glyph_index = glyph_index self.start_code = start_code self.final_code = final_code @property def id_delta(self): return (self.glyph_index - self.start_code) & 0xFFFF class CMAP(Table): def __init__(self, font): Table.__init__(self, 'cmap') # make ranges ranges = [] range = CMapRange() index = -1 for char in font.chars: index += 1 code = char.code if code == range.final_code + 1: range.final_code += 1 else: range = CMapRange(index, code, code) ranges.append(range) # write if font.bmp_only: if font.max_code < 0xFFFF: ranges.append(CMapRange(0, 0xFFFF, 0xFFFF)) self.write_format_4(ranges) else: self.write_format_12(ranges) def write_format_4(self, ranges): # index self.write_uint16(0, 'version') self.write_uint16(1, 'numberSubtables') # encoding subtables index self.write_uint16(3, 'platformID') # Microsoft self.write_uint16(1, 'platformSpecificID') # Unicode BMP (UCS-2) self.write_uint32(CMAP_4_PREFIX_SIZE, 'offset') # for Unicode BMP (UCS-2) # cmap format 4 seg_count = len(ranges) subtable_size = CMAP_4_FORMAT_SIZE + seg_count * CMAP_4_SEGMENT_SIZE search_range = 2 << math.floor(math.log2(seg_count)) self.write_uint16(4, 'format') self.write_uint16(subtable_size, 'length') self.write_uint16(0, 'language') # none/independent self.write_uint16(seg_count * 2, 'segCountX2') self.write_uint16(search_range, 'searchRange') self.write_uint16(int(math.log2(search_range / 2)), 'entrySelector') self.write_uint16((seg_count * 2) - search_range, 'rangeShift') for range in ranges: self.write_uint16(range.final_code, 'endCode') self.write_uint16(0, 'reservedPad') for range in ranges: self.write_uint16(range.start_code, 'startCode') for range in ranges: self.write_uint16(range.id_delta, 'idDelta') for _ in ranges: self.write_uint16(0, 'idRangeOffset') # check self.check_size(CMAP_4_PREFIX_SIZE + subtable_size) def write_format_12(self, ranges): # index self.write_uint16(0, 'version') self.write_uint16(2, 'numberSubtables') # encoding subtables self.write_uint16(0, 'platformID') # Unicode self.write_uint16(4, 'platformSpecificID') # Unicode 2.0+ full range self.write_uint32(CMAP_12_PREFIX_SIZE, 'offset') # for Unicode 2.0+ full range self.write_uint16(3, 'platformID') # Microsoft self.write_uint16(10, 'platformSpecificID') # Unicode UCS-4 self.write_uint32(CMAP_12_PREFIX_SIZE, 'offset') # for Unicode UCS-4 # cmap format 12 subtable_size = CMAP_12_FORMAT_SIZE + len(ranges) * CMAP_12_GROUP_SIZE self.write_fixed(12, 'format') self.write_uint32(subtable_size, 'length') self.write_uint32(0, 'language') # none/independent self.write_uint32(len(ranges), 'nGroups') for range in ranges: self.write_uint32(range.start_code, 'startCharCode') self.write_uint32(range.final_code, 'endCharCode') self.write_uint32(range.glyph_index, 'startGlyphID') # check self.check_size(CMAP_12_PREFIX_SIZE + subtable_size) # -- glyf -- class GLYF(Table): def __init__(self, _font): Table.__init__(self, 'glyf') # -- head -- HEAD_TABLE_SIZE = 54 HEAD_CHECKSUM_OFFSET = 8 class HEAD(Table): def __init__(self, font): Table.__init__(self, 'head') self.write_fixed(1, 'version') self.write_fixed(1, 'fontRevision') self.write_uint32(0, 'checksumAdjustment') # adjusted later self.write_uint32(0x5F0F3CF5, 'magicNumber') self.write_uint16(HEAD.flags(font), 'flags') self.write_uint16(font.params.em_size, 'unitsPerEm') self.write_uint64(font.created, 'created') self.write_uint64(font.modified, 'modified') self.write_int16(0, 'xMin') self.write_int16(font.em_descender, 'yMin') self.write_int16(font.em_max_width, 'xMax') self.write_int16(font.em_ascender, 'yMax') self.write_uint16(font.mac_style, 'macStyle') self.write_uint16(font.params.low_ppem or font.bbx.height, 'lowestRecPPEM') self.write_int16(font.params.dir_hint, 'fontDirectionHint') self.write_int16(0, 'indexToLocFormat') # short self.write_int16(0, 'glyphDataFormat') # current # check self.check_size(HEAD_TABLE_SIZE) @staticmethod def flags(font): return 0x20B if otb1get.contains_rtl(font) else 0x0B # y0 base, x0 lsb, scale int # -- hhea -- HHEA_TABLE_SIZE = 36 class HHEA(Table): def __init__(self, font): Table.__init__(self, 'hhea') self.write_fixed(1, 'version') self.write_int16(font.em_ascender, 'ascender') self.write_int16(font.em_descender, 'descender') self.write_int16(font.params.line_gap, 'lineGap') self.write_uint16(font.em_max_width, 'advanceWidthMax') self.write_int16(0, 'minLeftSideBearing') self.write_int16(0, 'minRightSideBearing') self.write_int16(font.x_max_extent, 'xMaxExtent') self.write_int16(100 if font.italic else 1, 'caretSlopeRise') self.write_int16(20 if font.italic else 0, 'caretSlopeRun') self.write_int16(0, 'caretOffset') self.write_int16(0, 'reserved') self.write_int16(0, 'reserved') self.write_int16(0, 'reserved') self.write_int16(0, 'reserved') self.write_int16(0, 'metricDataFormat') # current self.write_uint16(len(font.chars), 'numOfLongHorMetrics') # check self.check_size(HHEA_TABLE_SIZE) # -- hmtx -- class HMTX(Table): def __init__(self, font): Table.__init__(self, 'hmtx') for char in font.chars: self.write_uint16(font.em_scale_width(char), 'advanceWidth') self.write_int16(0, 'leftSideBearing') # -- loca -- class LOCA(Table): def __init__(self, font): Table.__init__(self, 'loca') if not font.params.single_loca: for _ in font.chars: self.write_uint16(0, 'offset') self.write_uint16(0, 'offset') # -- maxp -- MAXP_TABLE_SIZE = 32 class MAXP(Table): def __init__(self, font): Table.__init__(self, 'maxp') self.write_fixed(1, 'version') self.write_uint16(len(font.chars), 'numGlyphs') self.write_uint16(0, 'maxPoints') self.write_uint16(0, 'maxContours') self.write_uint16(0, 'maxComponentPoints') self.write_uint16(0, 'maxComponentContours') self.write_uint16(2, 'maxZones') self.write_uint16(0, 'maxTwilightPoints') self.write_uint16(1, 'maxStorage') self.write_uint16(1, 'maxFunctionDefs') self.write_uint16(0, 'maxInstructionDefs') self.write_uint16(64, 'maxStackElements') self.write_uint16(0, 'maxSizeOfInstructions') self.write_uint16(0, 'maxComponentElements') self.write_uint16(0, 'maxComponentDepth') # check self.check_size(MAXP_TABLE_SIZE) # -- name -- @unique # pylint: disable=invalid-name class NAME_ID(IntEnum): # pylint: disable=invalid-name COPYRIGHT = 0 FONT_FAMILY = 1 FONT_SUBFAMILY = 2 UNIQUE_SUBFAMILY = 3 FULL_FONT_NAME = 4 LICENSE = 14 NAME_HEADER_SIZE = 6 NAME_RECORD_SIZE = 12 class NAME(Table): def __init__(self, font): Table.__init__(self, 'name') # compute names names = OrderedDict() copyright = font.props.get('COPYRIGHT') if copyright is not None: names[NAME_ID.COPYRIGHT] = fnutil.unquote(copyright) family = font.xlfd[bdf.XLFD.FAMILY_NAME] style = [b'Regular', b'Bold', b'Italic', b'Bold Italic'][font.mac_style] names[NAME_ID.FONT_FAMILY] = family names[NAME_ID.FONT_SUBFAMILY] = style names[NAME_ID.UNIQUE_SUBFAMILY] = b'%s %s bitmap height %d' % (family, style, font.bbx.height) names[NAME_ID.FULL_FONT_NAME] = b'%s %s' % (family, style) license = font.props.get('LICENSE') notice = font.props.get('NOTICE') if license is None and notice is not None and b'license' in notice.lower(): license = notice if license is not None: names[NAME_ID.LICENSE] = fnutil.unquote(license) # header count = len(names) * (1 + 1) # Unicode + Microsoft string_offset = NAME_HEADER_SIZE + NAME_RECORD_SIZE * count self.write_uint16(0, 'format') self.write_uint16(count, 'count') self.write_uint16(string_offset, 'stringOffset') # name records / create values values = Table('name') for [name_id, bstr] in names.items(): s = font.decode(bstr) value = codecs.encode(s, 'utf_16_be') bmp = font.bmp_only and len(value) == len(s) * 2 # Unicode self.write_uint16(0, 'platformID') # Unicode self.write_uint16(3 if bmp else 4, 'platformSpecificID') self.write_uint16(0, 'languageID') # none self.write_uint16(name_id, 'nameID') self.write_uint16(len(value), 'length') # in bytes self.write_uint16(values.size, 'offset') # Microsoft self.write_uint16(3, 'platformID') # Microsoft self.write_uint16(1 if bmp else 10, 'platformSpecificID') self.write_uint16(font.params.w_lang_id, 'languageID') self.write_uint16(name_id, 'nameID') self.write_uint16(len(value), 'length') # in bytes self.write_uint16(values.size, 'offset') # value values.write(value) # write values self.write_table(values) # check self.check_size(string_offset + values.size) # -- post -- POST_TABLE_SIZE = 32 class POST(Table): def __init__(self, font): Table.__init__(self, 'post') self.write_fixed(2 if font.params.post_names else 3, 'format') self.write_fixed(font.italic_angle, 'italicAngle') self.write_int16(font.underline_position, 'underlinePosition') self.write_int16(font.line_size, 'underlineThickness') self.write_uint32(0 if font.proportional else 1, 'isFixedPitch') self.write_uint32(0, 'minMemType42') self.write_uint32(0, 'maxMemType42') self.write_uint32(0, 'minMemType1') self.write_uint32(0, 'maxMemType1') # names if font.params.post_names: self.write_uint16(len(font.chars), 'numberOfGlyphs') post_names = otb1get.post_mac_names() post_mac_count = len(post_names) for name in [char.props['STARTCHAR'] for char in font.chars]: if name in post_names: self.write_uint16(post_names.index(name), 'glyphNameIndex') else: self.write_uint16(len(post_names), 'glyphNameIndex') post_names.append(name) for name in post_names[post_mac_count:]: self.write_uint8(len(name), 'glyphNameLength') self.write(name) # check else: self.check_size(POST_TABLE_SIZE) # -- SFNT -- SFNT_HEADER_SIZE = 12 SFNT_RECORD_SIZE = 16 SFNT_SUBTABLES = (BDAT, BLOC, OS_2, CMAP, GLYF, HEAD, HHEA, HMTX, LOCA, MAXP, NAME, POST) class SFNT(Table): def __init__(self, font): Table.__init__(self, 'SFNT') # create tables tables = [] for ctor in SFNT_SUBTABLES: tables.append(ctor(font)) # header num_tables = len(tables) entry_selector = math.floor(math.log2(num_tables)) search_range = 16 << entry_selector content_offset = SFNT_HEADER_SIZE + num_tables * SFNT_RECORD_SIZE offset = content_offset content = Table('SFNT') head_checksum_offset = -1 self.write_fixed(1, 'sfntVersion') self.write_uint16(num_tables, 'numTables') self.write_uint16(search_range, 'searchRange') self.write_uint16(entry_selector, 'entrySelector') self.write_uint16(num_tables * 16 - search_range, 'rangeShift') # table records / create content for table in tables: self.write(bytes(table.table_name, 'ascii')) self.write_uint32(table.checksum(), 'checkSum') self.write_uint32(offset, 'offset') self.write_uint32(len(table.data), 'length') # create content if table.table_name == 'head': head_checksum_offset = offset + HEAD_CHECKSUM_OFFSET padded_data = table.data + table.padding content.write(padded_data) offset += len(padded_data) # write content self.write_table(content) # check self.check_size(content_offset + len(content.data)) # adjust if head_checksum_offset != -1: self.rewrite_uint32((0xB1B0AFBA - self.checksum()) & 0xFFFFFFFF, head_checksum_offset)