• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1from fontTools.misc import sstruct
2from fontTools.misc.fixedTools import floatToFixedToStr, strToFixedToFloat
3from fontTools.misc.textTools import safeEval, num2binary, binary2num
4from fontTools.misc.timeTools import (
5    timestampFromString,
6    timestampToString,
7    timestampNow,
8)
9from fontTools.misc.timeTools import epoch_diff as mac_epoch_diff  # For backward compat
10from fontTools.misc.arrayTools import intRect, unionRect
11from . import DefaultTable
12import logging
13
14
15log = logging.getLogger(__name__)
16
17headFormat = """
18		>	# big endian
19		tableVersion:       16.16F
20		fontRevision:       16.16F
21		checkSumAdjustment: I
22		magicNumber:        I
23		flags:              H
24		unitsPerEm:         H
25		created:            Q
26		modified:           Q
27		xMin:               h
28		yMin:               h
29		xMax:               h
30		yMax:               h
31		macStyle:           H
32		lowestRecPPEM:      H
33		fontDirectionHint:  h
34		indexToLocFormat:   h
35		glyphDataFormat:    h
36"""
37
38
39class table__h_e_a_d(DefaultTable.DefaultTable):
40    dependencies = ["maxp", "loca", "CFF ", "CFF2"]
41
42    def decompile(self, data, ttFont):
43        dummy, rest = sstruct.unpack2(headFormat, data, self)
44        if rest:
45            # this is quite illegal, but there seem to be fonts out there that do this
46            log.warning("extra bytes at the end of 'head' table")
47            assert rest == b"\0\0"
48
49        # For timestamp fields, ignore the top four bytes.  Some fonts have
50        # bogus values there.  Since till 2038 those bytes only can be zero,
51        # ignore them.
52        #
53        # https://github.com/fonttools/fonttools/issues/99#issuecomment-66776810
54        for stamp in "created", "modified":
55            value = getattr(self, stamp)
56            if value > 0xFFFFFFFF:
57                log.warning("'%s' timestamp out of range; ignoring top bytes", stamp)
58                value &= 0xFFFFFFFF
59                setattr(self, stamp, value)
60            if value < 0x7C259DC0:  # January 1, 1970 00:00:00
61                log.warning(
62                    "'%s' timestamp seems very low; regarding as unix timestamp", stamp
63                )
64                value += 0x7C259DC0
65                setattr(self, stamp, value)
66
67    def compile(self, ttFont):
68        if ttFont.recalcBBoxes:
69            # For TT-flavored fonts, xMin, yMin, xMax and yMax are set in table__m_a_x_p.recalc().
70            if "CFF " in ttFont:
71                topDict = ttFont["CFF "].cff.topDictIndex[0]
72                self.xMin, self.yMin, self.xMax, self.yMax = intRect(topDict.FontBBox)
73            elif "CFF2" in ttFont:
74                topDict = ttFont["CFF2"].cff.topDictIndex[0]
75                charStrings = topDict.CharStrings
76                fontBBox = None
77                for charString in charStrings.values():
78                    bounds = charString.calcBounds(charStrings)
79                    if bounds is not None:
80                        if fontBBox is not None:
81                            fontBBox = unionRect(fontBBox, bounds)
82                        else:
83                            fontBBox = bounds
84                if fontBBox is not None:
85                    self.xMin, self.yMin, self.xMax, self.yMax = intRect(fontBBox)
86        if ttFont.recalcTimestamp:
87            self.modified = timestampNow()
88        data = sstruct.pack(headFormat, self)
89        return data
90
91    def toXML(self, writer, ttFont):
92        writer.comment("Most of this table will be recalculated by the compiler")
93        writer.newline()
94        _, names, fixes = sstruct.getformat(headFormat)
95        for name in names:
96            value = getattr(self, name)
97            if name in fixes:
98                value = floatToFixedToStr(value, precisionBits=fixes[name])
99            elif name in ("created", "modified"):
100                value = timestampToString(value)
101            elif name in ("magicNumber", "checkSumAdjustment"):
102                if value < 0:
103                    value = value + 0x100000000
104                value = hex(value)
105                if value[-1:] == "L":
106                    value = value[:-1]
107            elif name in ("macStyle", "flags"):
108                value = num2binary(value, 16)
109            writer.simpletag(name, value=value)
110            writer.newline()
111
112    def fromXML(self, name, attrs, content, ttFont):
113        value = attrs["value"]
114        fixes = sstruct.getformat(headFormat)[2]
115        if name in fixes:
116            value = strToFixedToFloat(value, precisionBits=fixes[name])
117        elif name in ("created", "modified"):
118            value = timestampFromString(value)
119        elif name in ("macStyle", "flags"):
120            value = binary2num(value)
121        else:
122            value = safeEval(value)
123        setattr(self, name, value)
124