123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308 |
- from fontTools import ttLib
- from fontTools.ttLib.standardGlyphOrder import standardGlyphOrder
- from fontTools.misc import sstruct
- from fontTools.misc.textTools import bytechr, byteord, tobytes, tostr, safeEval, readHex
- from . import DefaultTable
- import sys
- import struct
- import array
- import logging
- log = logging.getLogger(__name__)
- postFormat = """
- >
- formatType: 16.16F
- italicAngle: 16.16F # italic angle in degrees
- underlinePosition: h
- underlineThickness: h
- isFixedPitch: L
- minMemType42: L # minimum memory if TrueType font is downloaded
- maxMemType42: L # maximum memory if TrueType font is downloaded
- minMemType1: L # minimum memory if Type1 font is downloaded
- maxMemType1: L # maximum memory if Type1 font is downloaded
- """
- postFormatSize = sstruct.calcsize(postFormat)
- class table__p_o_s_t(DefaultTable.DefaultTable):
- def decompile(self, data, ttFont):
- sstruct.unpack(postFormat, data[:postFormatSize], self)
- data = data[postFormatSize:]
- if self.formatType == 1.0:
- self.decode_format_1_0(data, ttFont)
- elif self.formatType == 2.0:
- self.decode_format_2_0(data, ttFont)
- elif self.formatType == 3.0:
- self.decode_format_3_0(data, ttFont)
- elif self.formatType == 4.0:
- self.decode_format_4_0(data, ttFont)
- else:
- # supported format
- raise ttLib.TTLibError(
- "'post' table format %f not supported" % self.formatType
- )
- def compile(self, ttFont):
- data = sstruct.pack(postFormat, self)
- if self.formatType == 1.0:
- pass # we're done
- elif self.formatType == 2.0:
- data = data + self.encode_format_2_0(ttFont)
- elif self.formatType == 3.0:
- pass # we're done
- elif self.formatType == 4.0:
- data = data + self.encode_format_4_0(ttFont)
- else:
- # supported format
- raise ttLib.TTLibError(
- "'post' table format %f not supported" % self.formatType
- )
- return data
- def getGlyphOrder(self):
- """This function will get called by a ttLib.TTFont instance.
- Do not call this function yourself, use TTFont().getGlyphOrder()
- or its relatives instead!
- """
- if not hasattr(self, "glyphOrder"):
- raise ttLib.TTLibError("illegal use of getGlyphOrder()")
- glyphOrder = self.glyphOrder
- del self.glyphOrder
- return glyphOrder
- def decode_format_1_0(self, data, ttFont):
- self.glyphOrder = standardGlyphOrder[: ttFont["maxp"].numGlyphs]
- def decode_format_2_0(self, data, ttFont):
- (numGlyphs,) = struct.unpack(">H", data[:2])
- numGlyphs = int(numGlyphs)
- if numGlyphs > ttFont["maxp"].numGlyphs:
- # Assume the numGlyphs field is bogus, so sync with maxp.
- # I've seen this in one font, and if the assumption is
- # wrong elsewhere, well, so be it: it's hard enough to
- # work around _one_ non-conforming post format...
- numGlyphs = ttFont["maxp"].numGlyphs
- data = data[2:]
- indices = array.array("H")
- indices.frombytes(data[: 2 * numGlyphs])
- if sys.byteorder != "big":
- indices.byteswap()
- data = data[2 * numGlyphs :]
- maxIndex = max(indices)
- self.extraNames = extraNames = unpackPStrings(data, maxIndex - 257)
- self.glyphOrder = glyphOrder = [""] * int(ttFont["maxp"].numGlyphs)
- for glyphID in range(numGlyphs):
- index = indices[glyphID]
- if index > 257:
- try:
- name = extraNames[index - 258]
- except IndexError:
- name = ""
- else:
- # fetch names from standard list
- name = standardGlyphOrder[index]
- glyphOrder[glyphID] = name
- self.build_psNameMapping(ttFont)
- def build_psNameMapping(self, ttFont):
- mapping = {}
- allNames = {}
- for i in range(ttFont["maxp"].numGlyphs):
- glyphName = psName = self.glyphOrder[i]
- if glyphName == "":
- glyphName = "glyph%.5d" % i
- if glyphName in allNames:
- # make up a new glyphName that's unique
- n = allNames[glyphName]
- while (glyphName + "#" + str(n)) in allNames:
- n += 1
- allNames[glyphName] = n + 1
- glyphName = glyphName + "#" + str(n)
- self.glyphOrder[i] = glyphName
- allNames[glyphName] = 1
- if glyphName != psName:
- mapping[glyphName] = psName
- self.mapping = mapping
- def decode_format_3_0(self, data, ttFont):
- # Setting self.glyphOrder to None will cause the TTFont object
- # try and construct glyph names from a Unicode cmap table.
- self.glyphOrder = None
- def decode_format_4_0(self, data, ttFont):
- from fontTools import agl
- numGlyphs = ttFont["maxp"].numGlyphs
- indices = array.array("H")
- indices.frombytes(data)
- if sys.byteorder != "big":
- indices.byteswap()
- # In some older fonts, the size of the post table doesn't match
- # the number of glyphs. Sometimes it's bigger, sometimes smaller.
- self.glyphOrder = glyphOrder = [""] * int(numGlyphs)
- for i in range(min(len(indices), numGlyphs)):
- if indices[i] == 0xFFFF:
- self.glyphOrder[i] = ""
- elif indices[i] in agl.UV2AGL:
- self.glyphOrder[i] = agl.UV2AGL[indices[i]]
- else:
- self.glyphOrder[i] = "uni%04X" % indices[i]
- self.build_psNameMapping(ttFont)
- def encode_format_2_0(self, ttFont):
- numGlyphs = ttFont["maxp"].numGlyphs
- glyphOrder = ttFont.getGlyphOrder()
- assert len(glyphOrder) == numGlyphs
- indices = array.array("H")
- extraDict = {}
- extraNames = self.extraNames = [
- n for n in self.extraNames if n not in standardGlyphOrder
- ]
- for i in range(len(extraNames)):
- extraDict[extraNames[i]] = i
- for glyphID in range(numGlyphs):
- glyphName = glyphOrder[glyphID]
- if glyphName in self.mapping:
- psName = self.mapping[glyphName]
- else:
- psName = glyphName
- if psName in extraDict:
- index = 258 + extraDict[psName]
- elif psName in standardGlyphOrder:
- index = standardGlyphOrder.index(psName)
- else:
- index = 258 + len(extraNames)
- extraDict[psName] = len(extraNames)
- extraNames.append(psName)
- indices.append(index)
- if sys.byteorder != "big":
- indices.byteswap()
- return (
- struct.pack(">H", numGlyphs) + indices.tobytes() + packPStrings(extraNames)
- )
- def encode_format_4_0(self, ttFont):
- from fontTools import agl
- numGlyphs = ttFont["maxp"].numGlyphs
- glyphOrder = ttFont.getGlyphOrder()
- assert len(glyphOrder) == numGlyphs
- indices = array.array("H")
- for glyphID in glyphOrder:
- glyphID = glyphID.split("#")[0]
- if glyphID in agl.AGL2UV:
- indices.append(agl.AGL2UV[glyphID])
- elif len(glyphID) == 7 and glyphID[:3] == "uni":
- indices.append(int(glyphID[3:], 16))
- else:
- indices.append(0xFFFF)
- if sys.byteorder != "big":
- indices.byteswap()
- return indices.tobytes()
- def toXML(self, writer, ttFont):
- formatstring, names, fixes = sstruct.getformat(postFormat)
- for name in names:
- value = getattr(self, name)
- writer.simpletag(name, value=value)
- writer.newline()
- if hasattr(self, "mapping"):
- writer.begintag("psNames")
- writer.newline()
- writer.comment(
- "This file uses unique glyph names based on the information\n"
- "found in the 'post' table. Since these names might not be unique,\n"
- "we have to invent artificial names in case of clashes. In order to\n"
- "be able to retain the original information, we need a name to\n"
- "ps name mapping for those cases where they differ. That's what\n"
- "you see below.\n"
- )
- writer.newline()
- items = sorted(self.mapping.items())
- for name, psName in items:
- writer.simpletag("psName", name=name, psName=psName)
- writer.newline()
- writer.endtag("psNames")
- writer.newline()
- if hasattr(self, "extraNames"):
- writer.begintag("extraNames")
- writer.newline()
- writer.comment(
- "following are the name that are not taken from the standard Mac glyph order"
- )
- writer.newline()
- for name in self.extraNames:
- writer.simpletag("psName", name=name)
- writer.newline()
- writer.endtag("extraNames")
- writer.newline()
- if hasattr(self, "data"):
- writer.begintag("hexdata")
- writer.newline()
- writer.dumphex(self.data)
- writer.endtag("hexdata")
- writer.newline()
- def fromXML(self, name, attrs, content, ttFont):
- if name not in ("psNames", "extraNames", "hexdata"):
- setattr(self, name, safeEval(attrs["value"]))
- elif name == "psNames":
- self.mapping = {}
- for element in content:
- if not isinstance(element, tuple):
- continue
- name, attrs, content = element
- if name == "psName":
- self.mapping[attrs["name"]] = attrs["psName"]
- elif name == "extraNames":
- self.extraNames = []
- for element in content:
- if not isinstance(element, tuple):
- continue
- name, attrs, content = element
- if name == "psName":
- self.extraNames.append(attrs["name"])
- else:
- self.data = readHex(content)
- def unpackPStrings(data, n):
- # extract n Pascal strings from data.
- # if there is not enough data, use ""
- strings = []
- index = 0
- dataLen = len(data)
- for _ in range(n):
- if dataLen <= index:
- length = 0
- else:
- length = byteord(data[index])
- index += 1
- if dataLen <= index + length - 1:
- name = ""
- else:
- name = tostr(data[index : index + length], encoding="latin1")
- strings.append(name)
- index += length
- if index < dataLen:
- log.warning("%d extra bytes in post.stringData array", dataLen - index)
- elif dataLen < index:
- log.warning("not enough data in post.stringData array")
- return strings
- def packPStrings(strings):
- data = b""
- for s in strings:
- data = data + bytechr(len(s)) + tobytes(s, encoding="latin1")
- return data
|