SickGear/lib/hachoir/parser/misc/ttf.py

809 lines
28 KiB
Python
Raw Permalink Normal View History

"""
TrueType Font parser.
Documents:
- "The OpenType Specification"
https://docs.microsoft.com/en-us/typography/opentype/spec/
- "An Introduction to TrueType Fonts: A look inside the TTF format"
written by "NRSI: Computers & Writing Systems"
http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&item_id=IWS-Chapter08
Author: Victor Stinner
Creation date: 2007-02-08
"""
from hachoir.parser import Parser
from hachoir.field import (
FieldSet,
ParserError,
UInt8,
UInt16,
UInt24,
UInt32,
Int16,
Bit,
Bits,
PaddingBits,
NullBytes,
String,
RawBytes,
Bytes,
Enum,
TimestampMac32,
GenericVector,
PascalString8,
)
from hachoir.core.endian import BIG_ENDIAN
from hachoir.core.text_handler import textHandler, hexadecimal, filesizeHandler
MAX_NAME_COUNT = 300
MIN_NB_TABLE = 3
MAX_NB_TABLE = 30
DIRECTION_NAME = {
0: "Mixed directional",
1: "Left to right",
2: "Left to right + neutrals",
-1: "Right to left",
-2: "Right to left + neutrals",
}
NAMEID_NAME = {
0: "Copyright notice",
1: "Font family name",
2: "Font subfamily name",
3: "Unique font identifier",
4: "Full font name",
5: "Version string",
6: "Postscript name",
7: "Trademark",
8: "Manufacturer name",
9: "Designer",
10: "Description",
11: "URL Vendor",
12: "URL Designer",
13: "License Description",
14: "License info URL",
16: "Preferred Family",
17: "Preferred Subfamily",
18: "Compatible Full",
19: "Sample text",
20: "PostScript CID findfont name",
}
PLATFORM_NAME = {
0: "Unicode",
1: "Macintosh",
2: "ISO",
3: "Microsoft",
4: "Custom",
}
CHARSET_MAP = {
# (platform, encoding) => charset
0: {3: "UTF-16-BE"},
1: {0: "MacRoman"},
3: {1: "UTF-16-BE"},
}
PERMISSIONS = {
0: "Installable embedding",
2: "Restricted License embedding",
4: "Preview & Print embedding",
8: "Editable embedding",
}
FWORD = Int16
UFWORD = UInt16
class Tag(String):
def __init__(self, parent, name, description=None):
String.__init__(self, parent, name, 4, description)
class Version16Dot16(FieldSet):
static_size = 32
def createFields(self):
yield UInt16(self, "major")
yield UInt16(self, "minor")
def createValue(self):
return float("%u.%x" % (self["major"].value, self["minor"].value))
class Fixed(FieldSet):
def createFields(self):
yield UInt16(self, "int_part")
yield UInt16(self, "float_part")
def createValue(self):
return self["int_part"].value + float(self["float_part"].value) / 65536
class Tuple(FieldSet):
def __init__(self, parent, name, axisCount):
super().__init__(parent, name, description="Tuple Record")
self.axisCount = axisCount
def createFields(self):
for _ in range(self.axisCount):
yield (Fixed(self, "coordinate[]"))
class F2DOT14(FieldSet):
static_size = 16
def createFields(self):
yield Int16(self, "int_part")
def createValue(self):
return self["int_part"].value / 16384
class TableHeader(FieldSet):
def createFields(self):
yield Tag(self, "tag")
yield textHandler(UInt32(self, "checksum"), hexadecimal)
yield UInt32(self, "offset")
yield filesizeHandler(UInt32(self, "size"))
def createDescription(self):
return "Table entry: %s (%s)" % (self["tag"].display, self["size"].display)
class NameHeader(FieldSet):
def createFields(self):
yield Enum(UInt16(self, "platformID"), PLATFORM_NAME)
yield UInt16(self, "encodingID")
yield UInt16(self, "languageID")
yield Enum(UInt16(self, "nameID"), NAMEID_NAME)
yield UInt16(self, "length")
yield UInt16(self, "offset")
def getCharset(self):
platform = self["platformID"].value
encoding = self["encodingID"].value
try:
return CHARSET_MAP[platform][encoding]
except KeyError:
self.warning("TTF: Unknown charset (%s,%s)" % (platform, encoding))
return "ISO-8859-1"
def createDescription(self):
platform = self["platformID"].display
name = self["nameID"].display
return "Name record: %s (%s)" % (name, platform)
def parseFontHeader(self):
yield UInt16(self, "maj_ver", "Major version")
yield UInt16(self, "min_ver", "Minor version")
yield UInt16(self, "font_maj_ver", "Font major version")
yield UInt16(self, "font_min_ver", "Font minor version")
yield textHandler(UInt32(self, "checksum"), hexadecimal)
yield Bytes(self, "magic", 4, r"Magic string (\x5F\x0F\x3C\xF5)")
if self["magic"].value != b"\x5F\x0F\x3C\xF5":
raise ParserError("TTF: invalid magic of font header")
# Flags
yield Bit(self, "y0", "Baseline at y=0")
yield Bit(self, "x0", "Left sidebearing point at x=0")
yield Bit(self, "instr_point", "Instructions may depend on point size")
yield Bit(self, "ppem", "Force PPEM to integer values for all")
yield Bit(self, "instr_width", "Instructions may alter advance width")
yield Bit(self, "vertical", "e laid out vertically?")
yield PaddingBits(self, "reserved[]", 1)
yield Bit(self, "linguistic", "Requires layout for correct linguistic rendering?")
yield Bit(self, "gx", "Metamorphosis effects?")
yield Bit(self, "strong", "Contains strong right-to-left glyphs?")
yield Bit(self, "indic", "contains Indic-style rearrangement effects?")
yield Bit(self, "lossless", "Data is lossless (Agfa MicroType compression)")
yield Bit(self, "converted", "Font converted (produce compatible metrics)")
yield Bit(self, "cleartype", "Optimised for ClearType")
yield Bits(self, "adobe", 2, "(used by Adobe)")
yield UInt16(self, "unit_per_em", "Units per em")
if not (16 <= self["unit_per_em"].value <= 16384):
raise ParserError("TTF: Invalid unit/em value")
yield UInt32(self, "created_high")
yield TimestampMac32(self, "created")
yield UInt32(self, "modified_high")
yield TimestampMac32(self, "modified")
yield UInt16(self, "xmin")
yield UInt16(self, "ymin")
yield UInt16(self, "xmax")
yield UInt16(self, "ymax")
# Mac style
yield Bit(self, "bold")
yield Bit(self, "italic")
yield Bit(self, "underline")
yield Bit(self, "outline")
yield Bit(self, "shadow")
yield Bit(self, "condensed", "(narrow)")
yield Bit(self, "expanded")
yield PaddingBits(self, "reserved[]", 9)
yield UInt16(self, "lowest", "Smallest readable size in pixels")
yield Enum(UInt16(self, "font_dir", "Font direction hint"), DIRECTION_NAME)
yield Enum(UInt16(self, "ofst_format"), {0: "short offsets", 1: "long"})
yield UInt16(self, "glyph_format", "(=0)")
class AxisValueMap(FieldSet):
static_size = 32
def createFields(self):
yield F2DOT14(self, "fromCoordinate")
yield F2DOT14(self, "toCoordinate")
class SegmentMaps(FieldSet):
def createFields(self):
yield UInt16(
self, "positionMapCount", "The number of correspondence pairs for this axis"
)
for _ in range(self["positionMapCount"].value):
yield (AxisValueMap(self, "axisValueMaps[]"))
def parseAvar(self):
yield UInt16(self, "majorVersion", "Major version")
yield UInt16(self, "minorVersion", "Minor version")
yield PaddingBits(self, "reserved[]", 16)
yield UInt16(self, "axisCount", "The number of variation axes for this font")
for _ in range(self["axisCount"].value):
yield (SegmentMaps(self, "segmentMaps[]"))
class VariationAxisRecord(FieldSet):
def createFields(self):
yield Tag(self, "axisTag", "Tag identifying the design variation for the axis")
yield Fixed(self, "minValue", "The minimum coordinate value for the axis")
yield Fixed(self, "defaultValue", "The default coordinate value for the axis")
yield Fixed(self, "maxValue", "The maximum coordinate value for the axis")
yield PaddingBits(self, "reservedFlags", 15)
yield Bit(
self, "hidden", "The axis should not be exposed directly in user interfaces"
)
yield UInt16(
self,
"axisNameID",
"The name ID for entries in the 'name' table that provide a display name for this axis",
)
class InstanceRecord(FieldSet):
def __init__(self, parent, name, axisCount, hasPSNameID=False):
super().__init__(parent, name, description="Instance record")
self.axisCount = axisCount
self.hasPSNameID = hasPSNameID
def createFields(self):
yield UInt16(
self, "subfamilyNameID", "Name ID for subfamily names for this instance"
)
yield PaddingBits(self, "reservedFlags", 16)
yield Tuple(self, "coordinates", axisCount=self.axisCount)
if self.hasPSNameID:
yield UInt16(
self,
"postScriptNameID",
"Name ID for PostScript names for this instance",
)
def parseFvar(self):
yield UInt16(self, "majorVersion", "Major version")
yield UInt16(self, "minorVersion", "Minor version")
yield UInt16(
self, "axisArrayOffset", "Offset to the start of the VariationAxisRecord array."
)
yield PaddingBits(self, "reserved[]", 16)
yield UInt16(self, "axisCount", "The number of variation axes for this font")
yield UInt16(self, "axisSize", "The size in bytes of each VariationAxisRecord")
yield UInt16(self, "instanceCount", "The number of named instances for this font")
yield UInt16(self, "instanceSize", "The size in bytes of each InstanceRecord")
if self["axisArrayOffset"].value > 16:
yield PaddingBits(self, "padding", 8 * (self["axisArrayOffset"].value - 16))
for _ in range(self["axisCount"].value):
yield (VariationAxisRecord(self, "axes[]"))
for _ in range(self["instanceCount"].value):
yield (
InstanceRecord(
self,
"instances[]",
axisCount=self["axisCount"].value,
hasPSNameID=(
self["instanceSize"].value == (2 * self["axisCount"].value + 6)
),
)
)
class EncodingRecord(FieldSet):
static_size = 64
def createFields(self):
yield Enum(UInt16(self, "platformID"), PLATFORM_NAME)
yield UInt16(self, "encodingID")
self.offset = UInt32(self, "subtableOffset")
yield self.offset
class CmapTable0(FieldSet):
def createFields(self):
yield UInt16(self, "format", "Table format")
yield UInt16(self, "length", "Length in bytes")
yield UInt16(self, "language", "Language ID")
yield GenericVector(self, "mapping", 256, UInt8)
class CmapTable4(FieldSet):
def createFields(self):
yield UInt16(self, "format", "Table format")
yield UInt16(self, "length", "Length in bytes")
yield UInt16(self, "language", "Language ID")
yield UInt16(self, "segCountX2", "Twice the number of segments")
segments = self["segCountX2"].value // 2
yield UInt16(self, "searchRange")
yield UInt16(self, "entrySelector")
yield UInt16(self, "rangeShift")
yield GenericVector(self, "endCode", segments, UInt16)
yield PaddingBits(self, "reserved[]", 16)
yield GenericVector(self, "startCode", segments, UInt16)
yield GenericVector(self, "idDelta", segments, Int16)
yield GenericVector(self, "idRangeOffsets", segments, UInt16)
remainder = (self["length"].value - (self.current_size / 8)) / 2
if remainder:
yield GenericVector(self, "glyphIdArray", remainder, UInt16)
class CmapTable6(FieldSet):
def createFields(self):
yield UInt16(self, "format", "Table format")
yield UInt16(self, "length", "Length in bytes")
yield UInt16(self, "language", "Language ID")
yield UInt16(self, "firstCode", "First character code of subrange")
yield UInt16(self, "entryCount", "Number of character codes in subrange")
yield GenericVector(self, "glyphIdArray", self["entryCount"].value, UInt16)
class SequentialMapGroup(FieldSet):
def createFields(self):
yield UInt32(self, "startCharCode", "First character code in this group")
yield UInt32(self, "endCharCode", "First character code in this group")
yield UInt32(
self,
"startGlyphID",
"Glyph index corresponding to the starting character code",
)
class CmapTable12(FieldSet):
def createFields(self):
yield UInt16(self, "format", "Table format")
yield PaddingBits(self, "reserved[]", 16)
yield UInt32(self, "length", "Length in bytes")
yield UInt32(self, "language", "Language ID")
yield UInt32(self, "numGroups", "Number of groupings which follow")
for i in range(self["numGroups"].value):
yield SequentialMapGroup(self, "mapgroup[]")
class VariationSelector(FieldSet):
def createFields(self):
yield UInt24(self, "varSelector", "Variation selector")
yield UInt32(self, "defaultUVSOffset", "Offset to default UVS table")
yield UInt32(self, "nonDefaultUVSOffset", "Offset to non-default UVS table")
class CmapTable14(FieldSet):
def createFields(self):
yield UInt16(self, "format", "Table format")
yield UInt32(self, "length", "Length in bytes")
yield UInt32(
self, "numVarSelectorRecords", "Number of variation selector records"
)
for i in range(self["numVarSelectorRecords"].value):
yield VariationSelector(self, "variationSelector[]")
def parseCmap(self):
yield UInt16(self, "version")
numTables = UInt16(self, "numTables", "Number of encoding tables")
yield numTables
encodingRecords = []
for index in range(numTables.value):
entry = EncodingRecord(self, "encodingRecords[]")
yield entry
encodingRecords.append(entry)
encodingRecords.sort(key=lambda field: field["subtableOffset"].value)
last = None
for er in encodingRecords:
offset = er["subtableOffset"].value
if last and last == offset:
continue
last = offset
# Add padding if any
padding = self.seekByte(offset, relative=True, null=False)
if padding:
yield padding
format = UInt16(self, "format").value
if format == 0:
yield CmapTable0(self, "cmap table format 0")
elif format == 4:
yield CmapTable4(self, "cmap table format 4")
elif format == 6:
yield CmapTable6(self, "cmap table format 6")
elif format == 12:
yield CmapTable12(self, "cmap table format 12")
elif format == 14:
yield CmapTable14(self, "cmap table format 14")
class SignatureRecord(FieldSet):
def createFields(self):
yield UInt16(self, "format", "Table format")
yield UInt16(self, "length", "Length of signature")
yield UInt16(self, "signatureBlockOffset", "Offset to signature block")
class SignatureBlock(FieldSet):
def createFields(self):
yield PaddingBits(self, "reserved[]", 32)
yield UInt32(
self,
"length",
"Length (in bytes) of the PKCS#7 packet in the signature field",
)
yield String(self, "signature", self["length"].value, "Signature block")
def parseDSIG(self):
yield UInt32(self, "version")
yield UInt16(self, "numSignatures", "Number of signatures in the table")
yield Bit(self, "flag", "Cannot be resigned")
yield PaddingBits(self, "reserved[]", 7)
entries = []
for i in range(self["numSignatures"].value):
record = SignatureRecord(self, "signatureRecords[]")
entries.append(record)
yield record
entries.sort(key=lambda field: field["signatureBlockOffset"].value)
last = None
for entry in entries:
offset = entry["signatureBlockOffset"].value
if last and last == offset:
continue
last = offset
# Add padding if any
padding = self.seekByte(offset, relative=True, null=False)
if padding:
yield padding
padding = (self.size - self.current_size) // 8
if padding:
yield NullBytes(self, "padding_end", padding)
def parseNames(self):
# Read header
yield UInt16(self, "format")
if self["format"].value != 0:
raise ParserError("TTF (names): Invalid format (%u)" % self["format"].value)
yield UInt16(self, "count")
yield UInt16(self, "offset")
if MAX_NAME_COUNT < self["count"].value:
raise ParserError("Invalid number of names (%s)" % self["count"].value)
# Read name index
entries = []
for index in range(self["count"].value):
entry = NameHeader(self, "header[]")
yield entry
entries.append(entry)
# Sort names by their offset
entries.sort(key=lambda field: field["offset"].value)
# Read name value
last = None
for entry in entries:
# Skip duplicates values
new = (entry["offset"].value, entry["length"].value)
if last and last == new:
self.warning("Skip duplicate %s %s" % (entry.name, new))
continue
last = (entry["offset"].value, entry["length"].value)
# Skip negative offset
offset = entry["offset"].value + self["offset"].value
if offset < self.current_size // 8:
self.warning("Skip value %s (negative offset)" % entry.name)
continue
# Add padding if any
padding = self.seekByte(offset, relative=True, null=True)
if padding:
yield padding
# Read value
size = entry["length"].value
if size:
yield String(
self, "value[]", size, entry.description, charset=entry.getCharset()
)
padding = (self.size - self.current_size) // 8
if padding:
yield NullBytes(self, "padding_end", padding)
def parseMaxp(self):
# Read header
yield Version16Dot16(self, "format", "format version")
yield UInt16(self, "numGlyphs", "Number of glyphs")
if self["format"].value >= 1:
yield UInt16(self, "maxPoints", "Maximum points in a non-composite glyph")
yield UInt16(self, "maxContours", "Maximum contours in a non-composite glyph")
yield UInt16(self, "maxCompositePoints", "Maximum points in a composite glyph")
yield UInt16(
self, "maxCompositeContours", "Maximum contours in a composite glyph"
)
yield UInt16(self, "maxZones", "Do instructions use the twilight zone?")
yield UInt16(self, "maxTwilightPoints", "Maximum points used in Z0")
yield UInt16(self, "maxStorage", "Number of Storage Area locations")
yield UInt16(self, "maxFunctionDefs", "Number of function definitions")
yield UInt16(self, "maxInstructionDefs", "Number of instruction definitions")
yield UInt16(self, "maxStackElements", "Maximum stack depth")
yield UInt16(
self, "maxSizeOfInstructions", "Maximum byte count for glyph instructions"
)
yield UInt16(
self,
"maxComponentElements",
"Maximum number of components at glyph top level",
)
yield UInt16(self, "maxComponentDepth", "Maximum level of recursion")
def parseHhea(self):
yield UInt16(self, "majorVersion", "Major version")
yield UInt16(self, "minorVersion", "Minor version")
yield FWORD(self, "ascender", "Typographic ascent")
yield FWORD(self, "descender", "Typographic descent")
yield FWORD(self, "lineGap", "Typographic linegap")
yield UFWORD(self, "advanceWidthMax", "Maximum advance width")
yield FWORD(self, "minLeftSideBearing", "Minimum left sidebearing value")
yield FWORD(self, "minRightSideBearing", "Minimum right sidebearing value")
yield FWORD(self, "xMaxExtent", "Maximum X extent")
yield Int16(self, "caretSlopeRise", "Caret slope rise")
yield Int16(self, "caretSlopeRun", "Caret slope run")
yield Int16(self, "caretOffset", "Caret offset")
yield GenericVector(self, "reserved", 4, Int16)
yield Int16(self, "metricDataFormat", "Metric data format")
yield UInt16(self, "numberOfHMetrics", "Number of horizontal metrics")
class fsType(FieldSet):
def createFields(self):
yield Enum(Bits(self, "usage_permissions", 4), PERMISSIONS)
yield PaddingBits(self, "reserved[]", 4)
yield Bit(self, "no_subsetting", "Font may not be subsetted prior to embedding")
yield Bit(
self,
"bitmap_embedding",
"Only bitmaps contained in the font may be embedded",
)
yield PaddingBits(self, "reserved[]", 6)
def parseOS2(self):
yield UInt16(self, "version", "Table version")
yield Int16(self, "xAvgCharWidth")
yield UInt16(self, "usWeightClass")
yield UInt16(self, "usWidthClass")
yield fsType(self, "fsType")
yield Int16(self, "ySubscriptXSize")
yield Int16(self, "ySubscriptYSize")
yield Int16(self, "ySubscriptXOffset")
yield Int16(self, "ySubscriptYOffset")
yield Int16(self, "ySuperscriptXSize")
yield Int16(self, "ySuperscriptYSize")
yield Int16(self, "ySuperscriptXOffset")
yield Int16(self, "ySuperscriptYOffset")
yield Int16(self, "yStrikeoutSize")
yield Int16(self, "yStrikeoutPosition")
yield Int16(self, "sFamilyClass")
yield GenericVector(self, "panose", 10, UInt8)
yield UInt32(self, "ulUnicodeRange1")
yield UInt32(self, "ulUnicodeRange2")
yield UInt32(self, "ulUnicodeRange3")
yield UInt32(self, "ulUnicodeRange4")
yield Tag(self, "achVendID", "Vendor ID")
yield UInt16(self, "fsSelection")
yield UInt16(self, "usFirstCharIndex")
yield UInt16(self, "usLastCharIndex")
yield Int16(self, "sTypoAscender")
yield Int16(self, "sTypoDescender")
yield Int16(self, "sTypoLineGap")
yield UInt16(self, "usWinAscent")
yield UInt16(self, "usWinDescent")
if self["version"].value >= 1:
yield UInt32(self, "ulCodePageRange1")
yield UInt32(self, "ulCodePageRange2")
if self["version"].value >= 2:
yield Int16(self, "sxHeight")
yield Int16(self, "sCapHeight")
yield UInt16(self, "usDefaultChar")
yield UInt16(self, "usBreakChar")
yield UInt16(self, "usMaxContext")
if self["version"].value >= 5:
yield UInt16(self, "usLowerOpticalPointSize")
yield UInt16(self, "usUpperOpticalPointSize")
def parsePost(self):
yield Version16Dot16(self, "version", "Table version")
yield Fixed(
self,
"italicAngle",
"Italic angle in counter-clockwise degrees from the vertical.",
)
yield FWORD(self, "underlinePosition", "Top of underline to baseline")
yield FWORD(self, "underlineThickness", "Suggested underline thickness")
yield UInt32(self, "isFixedPitch", "Is the font fixed pitch?")
yield UInt32(self, "minMemType42", "Minimum memory usage (OpenType)")
yield UInt32(self, "maxMemType42", "Maximum memory usage (OpenType)")
yield UInt32(self, "minMemType1", "Minimum memory usage (Type 1)")
yield UInt32(self, "maxMemType1", "Maximum memory usage (Type 1)")
if self["version"].value == 2.0:
yield UInt16(self, "numGlyphs")
indices = GenericVector(
self,
"Array of indices into the string data",
self["numGlyphs"].value,
UInt16,
"glyphNameIndex",
)
yield indices
for gid, index in enumerate(indices):
if index.value >= 258:
yield PascalString8(self, "glyphname[%i]" % gid)
elif self["version"].value == 2.0:
yield UInt16(self, "numGlyphs")
indices = GenericVector(
self,
"Difference between graphic index and standard order of glyph",
self["numGlyphs"].value,
UInt16,
"offset",
)
yield indices
# This is work-in-progress until I work out good ways to do random-access on offsets
parseScriptList = (
parseFeatureList
) = parseLookupList = parseFeatureVariationsTable = lambda x: None
def parseGSUB(self):
yield UInt16(self, "majorVersion", "Major version")
yield UInt16(self, "minorVersion", "Minor version")
SUBTABLES = [
("script list", parseScriptList),
("feature list", parseFeatureList),
("lookup list", parseLookupList),
]
offsets = []
for description, parser in SUBTABLES:
name = description.title().replace(" ", "")
offset = UInt16(
self, name[0].lower() + name[1:], "Offset to %s table" % description
)
yield offset
offsets.append((offset.value, parser))
if self["min_ver"].value == 1:
offset = UInt32(
self, "featureVariationsOffset", "Offset to feature variations table"
)
offsets.append((offset.value, parseFeatureVariationsTable))
offsets.sort(key=lambda field: field[0])
padding = self.seekByte(offsets[0][0], null=True)
if padding:
yield padding
lastOffset, first_parser = offsets[0]
for offset, parser in offsets[1:]:
# yield parser(self)
yield RawBytes(self, "content", offset - lastOffset)
lastOffset = offset
class Table(FieldSet):
TAG_INFO = {
"DSIG": ("DSIG", "Digital Signature", parseDSIG),
"GSUB": ("GSUB", "Glyph Substitutions", parseGSUB),
"avar": ("avar", "Axis variation table", parseAvar),
"cmap": ("cmap", "Character to Glyph Index Mapping", parseCmap),
"fvar": ("fvar", "Font variations table", parseFvar),
"head": ("header", "Font header", parseFontHeader),
"hhea": ("hhea", "Horizontal Header", parseHhea),
"maxp": ("maxp", "Maximum Profile", parseMaxp),
"name": ("names", "Names", parseNames),
"OS/2": ("OS_2", "OS/2 and Windows Metrics", parseOS2),
"post": ("post", "PostScript", parsePost),
}
def __init__(self, parent, name, table, **kw):
FieldSet.__init__(self, parent, name, **kw)
self.table = table
tag = table["tag"].value
if tag in self.TAG_INFO:
self._name, self._description, self.parser = self.TAG_INFO[tag]
else:
self.parser = None
def createFields(self):
if self.parser:
yield from self.parser(self)
else:
yield RawBytes(self, "content", self.size // 8)
def createDescription(self):
return "Table %s (%s)" % (self.table["tag"].value, self.table.path)
class TrueTypeFontFile(Parser):
endian = BIG_ENDIAN
PARSER_TAGS = {
"id": "ttf",
"category": "misc",
"file_ext": ("ttf",),
"min_size": 10 * 8, # FIXME
"description": "TrueType font",
}
def validate(self):
if self["maj_ver"].value == 1 and self["min_ver"].value == 0:
pass
elif self["maj_ver"].value == 0x4F54 and self["min_ver"].value == 0x544F:
pass
else:
return "Invalid version (%u.%u)" % (
self["maj_ver"].value,
self["min_ver"].value,
)
if not (MIN_NB_TABLE <= self["nb_table"].value <= MAX_NB_TABLE):
return "Invalid number of table (%u)" % self["nb_table"].value
return True
def createFields(self):
yield UInt16(self, "maj_ver", "Major version")
yield UInt16(self, "min_ver", "Minor version")
yield UInt16(self, "nb_table")
yield UInt16(self, "search_range")
yield UInt16(self, "entry_selector")
yield UInt16(self, "range_shift")
tables = []
for index in range(self["nb_table"].value):
table = TableHeader(self, "table_hdr[]")
yield table
tables.append(table)
tables.sort(key=lambda field: field["offset"].value)
for table in tables:
padding = self.seekByte(table["offset"].value, null=True)
if padding:
yield padding
size = table["size"].value
if size:
yield Table(self, "table[]", table, size=size * 8)
padding = self.seekBit(self.size, null=True)
if padding:
yield padding