class HeadTable(UnknownTable): created = DateTimeProperty('_created') modified = DateTimeProperty('_modified') version_number = FixedProperty('_version_number') font_revision = FixedProperty('_font_revision') def __init__(self, *args, **kwargs): super(HeadTable, self).__init__(*args, **kwargs) field_types = ('_version_number', 'l', '_font_revision', 'l', 'checksum_adjustment', 'L', 'magic_number', 'L', 'flags', 'H', 'units_per_em', 'H', '_created', 'q', '_modified', 'q', 'x_min', 'h', 'y_min', 'h', 'x_max', 'h', 'y_max', 'h', 'mac_style', 'H', 'lowest_rec_ppem', 'H', 'font_direction_hint', 'h', 'index_to_loc_format', 'h', 'glyph_data_format', 'h') self._fmt = ('>%s' % (''.join(field_types[1::2]))).encode('ascii') self._fields = field_types[0::2] for f, val in zip(self._fields, unpack_from(self._fmt, self.raw)): setattr(self, f, val) def update(self): vals = [getattr(self, f) for f in self._fields] self.raw = pack(self._fmt, *vals)
class PostTable(UnknownTable): version_number = FixedProperty('_version') italic_angle = FixedProperty('_italic_angle') def read_data(self): if hasattr(self, 'underline_position'): return (self._version, self._italic_angle, self.underline_position, self.underline_thickness) = unpack_from(b'>llhh', self.raw)
class VerticalHeader(UnknownTable): version_number = FixedProperty('_version_number') def read_data(self, vmtx): if hasattr(self, 'ascender'): return field_types = ( '_version_number', 'l', 'ascender', 'h', 'descender', 'h', 'line_gap', 'h', 'advance_height_max', 'H', 'min_top_side_bearing', 'h', 'min_bottom_side_bearing', 'h', 'y_max_extent', 'h', 'caret_slope_rise', 'h', 'caret_slop_run', 'h', 'caret_offset', 'h', 'r1', 'h', 'r2', 'h', 'r3', 'h', 'r4', 'h', 'metric_data_format', 'h', 'number_of_v_metrics', 'H', ) self._fmt = ('>%s' % (''.join(field_types[1::2]))).encode('ascii') self._fields = field_types[0::2] for f, val in zip(self._fields, unpack_from(self._fmt, self.raw)): setattr(self, f, val) raw = vmtx.raw num = self.number_of_h_metrics if len(raw) < 4 * num: raise UnsupportedFont('The vmtx table has insufficient data') long_hor_metric = raw[:4 * num] long_hor_metric = raw[:4 * num] a = read_array(long_hor_metric) self.advance_heights = a[0::2] a = read_array(long_hor_metric, 'h') self.top_side_bearings = a[1::2]
class MaxpTable(UnknownTable): version = FixedProperty('_version') def __init__(self, *args, **kwargs): super(MaxpTable, self).__init__(*args, **kwargs) self._fmt = b'>lH' self._version, self.num_glyphs = unpack_from(self._fmt, self.raw) self.fields = ('_version', 'num_glyphs') if self.version > 1.0: raise UnsupportedFont( 'This font has a maxp table with version: %s' % self.version) if self.version == 1.0: self.fields = ('_version', 'num_glyphs', 'max_points', 'max_contours', 'max_composite_points', 'max_composite_contours', 'max_zones', 'max_twilight_points', 'max_storage', 'max_function_defs', 'max_instruction_defs', 'max_stack_elements', 'max_size_of_instructions', 'max_component_elements', 'max_component_depth') self._fmt = b'>lH' + b'H' * (len(self.fields) - 2) vals = unpack_from(self._fmt, self.raw) for f, val in zip(self.fields, vals): setattr(self, f, val) def update(self): vals = [getattr(self, f) for f in self._fields] self.raw = pack(self._fmt, *vals)
class GSUBTable(UnknownTable): version = FixedProperty('_version') def decompile(self): (self._version, self.scriptlist_offset, self.featurelist_offset, self.lookuplist_offset) = unpack_from(b'>L3H', self.raw) if self._version != 0x10000: raise UnsupportedFont('The GSUB table has unknown version: 0x%x'% self._version) self.script_list_table = ScriptListTable(self.raw, self.scriptlist_offset) # self.script_list_table.dump() self.feature_list_table = FeatureListTable(self.raw, self.featurelist_offset) # self.feature_list_table.dump() self.lookup_list_table = LookupListTable(self.raw, self.lookuplist_offset) def all_substitutions(self, glyph_ids): ans = set() glyph_ids = frozenset(glyph_ids) for lookup_table in self.lookup_list_table: for subtable in lookup_table: gids = subtable.all_substitutions(glyph_ids) ans |= gids return ans
class HorizontalHeader(UnknownTable): version_number = FixedProperty('_version_number') def read_data(self, hmtx): if hasattr(self, 'ascender'): return field_types = ( '_version_number', 'l', 'ascender', 'h', 'descender', 'h', 'line_gap', 'h', 'advance_width_max', 'H', 'min_left_size_bearing', 'h', 'min_right_side_bearing', 'h', 'x_max_extent', 'h', 'caret_slope_rise', 'h', 'caret_slop_run', 'h', 'caret_offset', 'h', 'r1', 'h', 'r2', 'h', 'r3', 'h', 'r4', 'h', 'metric_data_format', 'h', 'number_of_h_metrics', 'H', ) self._fmt = ('>%s' % (''.join(field_types[1::2]))).encode('ascii') self._fields = field_types[0::2] for f, val in izip(self._fields, unpack_from(self._fmt, self.raw)): setattr(self, f, val) raw = hmtx.raw num = self.number_of_h_metrics if len(raw) < 4 * num: raise UnsupportedFont('The hmtx table has insufficient data') long_hor_metric = raw[:4 * num] fmt = '>%dH' % (2 * num) entries = unpack_from(fmt.encode('ascii'), long_hor_metric) self.advance_widths = entries[0::2] fmt = '>%dh' % (2 * num) entries = unpack_from(fmt.encode('ascii'), long_hor_metric) self.left_side_bearings = entries[1::2]
class HorizontalHeader(UnknownTable): version_number = FixedProperty('_version_number') field_types = ( '_version_number' , 'l', 'ascender', 'h', 'descender', 'h', 'line_gap', 'h', 'advance_width_max', 'H', 'min_left_side_bearing', 'h', 'min_right_side_bearing', 'h', 'x_max_extent', 'h', 'caret_slope_rise', 'h', 'caret_slop_run', 'h', 'caret_offset', 'h', 'r1', 'h', 'r2', 'h', 'r3', 'h', 'r4', 'h', 'metric_data_format', 'h', 'number_of_h_metrics', 'H', ) def read_data(self, hmtx, num_glyphs): self._fmt = ('>%s'%(''.join(self.field_types[1::2]))).encode('ascii') self._fields = self.field_types[0::2] for f, val in zip(self._fields, unpack_from(self._fmt, self.raw)): setattr(self, f, val) self.advance_widths, self.left_side_bearings = read_metrics(hmtx.raw, self.number_of_h_metrics, num_glyphs, 'hmtx') def metrics_for(self, glyph_id): lsb = self.left_side_bearings[glyph_id] if glyph_id >= len(self.advance_widths): glyph_id = -1 return self.advance_widths[glyph_id], lsb def update(self, metrics_map, mtx_table): aw, b = update_metrics_table(metrics_map, mtx_table) self.advance_widths = aw self.left_side_bearings = b self.number_of_h_metrics = len(metrics_map) self.advance_width_max = max(aw or (0,)) self.min_left_side_bearing = min(b or (0,)) data = (getattr(self, x) for x in self._fields) self.raw = pack('>' + ''.join(self.field_types[1::2]), *data)
class KernTable(UnknownTable): version = FixedProperty('_version') def __init__(self, *args, **kwargs): super(KernTable, self).__init__(*args, **kwargs) self._version, self.num_tables = unpack_from(b'>HH', self.raw) if self._version == 1 and len(self.raw) >= 8: self._version, self.num_tables = unpack_from(b'>LL', self.raw) self.headerfmt = b'>HH' if self._version == 0 else b'>LL' def restrict_to_glyphs(self, glyph_ids): if self._version not in {0, 0x10000}: raise UnsupportedFont('kern table has version: %x' % self._version) offset = 4 if (self._version == 0) else 8 tables = [] for i in range(self.num_tables): if self._version == 0: version, length, coverage = unpack_from( b'>3H', self.raw, offset) table_format = version else: length, coverage = unpack_from(b'>LH', self.raw, offset) table_format = coverage & 0xff raw = self.raw[offset:offset + length] if table_format == 0: raw = self.restrict_format_0(raw, glyph_ids) if not raw: continue tables.append(raw) offset += length self.raw = pack(self.headerfmt, self._version, len(tables)) + b''.join(tables) def restrict_format_0(self, raw, glyph_ids): if self._version == 0: version, length, coverage, npairs = unpack_from(b'>4H', raw) headerfmt = b'>3H' else: length, coverage, tuple_index, npairs = unpack_from(b'>L3H', raw) headerfmt = b'>L2H' offset = calcsize(headerfmt + b'4H') entries = [] entrysz = calcsize(b'>2Hh') for i in range(npairs): try: left, right, value = unpack_from(b'>2Hh', raw, offset) except struct_error: offset = len(raw) break # Buggy kern table if left in glyph_ids and right in glyph_ids: entries.append(pack(b'>2Hh', left, right, value)) offset += entrysz if offset != len(raw): raise UnsupportedFont('This font has extra data at the end of' ' a Format 0 kern subtable') npairs = len(entries) if npairs == 0: return b'' entry_selector = max_power_of_two(npairs) search_range = (2**entry_selector) * 6 range_shift = (npairs - (2**entry_selector)) * 6 entries = b''.join(entries) length = calcsize(headerfmt + b'4H') + len(entries) if self._version == 0: header = pack(headerfmt, version, length, coverage) else: header = pack(headerfmt, length, coverage, tuple_index) return header + pack(b'>4H', npairs, search_range, entry_selector, range_shift) + entries