2016-01-13 17:52:30 +00:00
|
|
|
from __future__ import print_function, division, absolute_import
|
|
|
|
from fontTools import ttLib
|
|
|
|
from fontTools.ttLib.tables import otTables as ot
|
|
|
|
from fontTools.ttLib.tables.otBase import ValueRecord, valueRecordFormatDict
|
|
|
|
|
2016-01-14 17:10:45 +01:00
|
|
|
|
|
|
|
def buildCoverage(glyphs, glyphMap):
|
2016-01-22 19:27:33 +01:00
|
|
|
if not glyphs:
|
|
|
|
return None
|
2016-01-14 17:10:45 +01:00
|
|
|
self = ot.Coverage()
|
|
|
|
self.glyphs = sorted(glyphs, key=glyphMap.__getitem__)
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
2016-01-20 19:24:23 +01:00
|
|
|
LOOKUP_FLAG_RIGHT_TO_LEFT = 0x0001
|
|
|
|
LOOKUP_FLAG_IGNORE_BASE_GLYPHS = 0x0002
|
|
|
|
LOOKUP_FLAG_IGNORE_LIGATURES = 0x0004
|
|
|
|
LOOKUP_FLAG_IGNORE_MARKS = 0x0008
|
|
|
|
LOOKUP_FLAG_USE_MARK_FILTERING_SET = 0x0010
|
|
|
|
|
|
|
|
|
|
|
|
def buildLookup(subtables, flags=0, markFilterSet=None):
|
2016-01-22 19:38:20 +01:00
|
|
|
if subtables is None:
|
|
|
|
return None
|
|
|
|
subtables = [st for st in subtables if st is not None]
|
2016-01-20 19:24:23 +01:00
|
|
|
if not subtables:
|
|
|
|
return None
|
|
|
|
assert all(t.LookupType == subtables[0].LookupType for t in subtables), \
|
|
|
|
("all subtables must have the same LookupType; got %s" %
|
|
|
|
repr([t.LookupType for t in subtables]))
|
|
|
|
self = ot.Lookup()
|
|
|
|
self.LookupType = subtables[0].LookupType
|
|
|
|
self.LookupFlag = flags
|
|
|
|
self.SubTable = subtables
|
|
|
|
self.SubTableCount = len(subtables)
|
|
|
|
if markFilterSet is not None:
|
|
|
|
assert self.LookupFlag & LOOKUP_FLAG_USE_MARK_FILTERING_SET, \
|
|
|
|
("if markFilterSet is not None, flags must set "
|
|
|
|
"LOOKUP_FLAG_USE_MARK_FILTERING_SET; flags=0x%04x" % flags)
|
|
|
|
assert isinstance(markFilterSet, int), markFilterSet
|
|
|
|
self.MarkFilteringSet = markFilterSet
|
|
|
|
else:
|
|
|
|
assert (self.LookupFlag & LOOKUP_FLAG_USE_MARK_FILTERING_SET) == 0, \
|
|
|
|
("if markFilterSet is None, flags must not set "
|
|
|
|
"LOOKUP_FLAG_USE_MARK_FILTERING_SET; flags=0x%04x" % flags)
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
2016-01-13 17:52:30 +00:00
|
|
|
# GSUB
|
|
|
|
|
2016-01-14 11:46:25 +01:00
|
|
|
|
2016-01-22 19:32:45 +01:00
|
|
|
def buildSingleSubstSubtable(mapping):
|
2016-01-22 19:38:20 +01:00
|
|
|
if not mapping:
|
|
|
|
return None
|
2016-01-14 11:46:25 +01:00
|
|
|
self = ot.SingleSubst()
|
|
|
|
self.mapping = dict(mapping)
|
|
|
|
return self
|
|
|
|
|
2016-01-13 17:52:30 +00:00
|
|
|
|
2016-01-22 19:32:45 +01:00
|
|
|
def buildMultipleSubstSubtable(mapping):
|
2016-01-22 19:38:20 +01:00
|
|
|
if not mapping:
|
|
|
|
return None
|
2016-01-14 11:46:25 +01:00
|
|
|
self = ot.MultipleSubst()
|
|
|
|
self.mapping = dict(mapping)
|
|
|
|
return self
|
|
|
|
|
2016-01-13 17:52:30 +00:00
|
|
|
|
2016-01-22 19:32:45 +01:00
|
|
|
def buildAlternateSubstSubtable(mapping):
|
2016-01-22 19:38:20 +01:00
|
|
|
if not mapping:
|
|
|
|
return None
|
2016-01-14 11:46:25 +01:00
|
|
|
self = ot.AlternateSubst()
|
|
|
|
self.alternates = dict(mapping)
|
|
|
|
return self
|
|
|
|
|
2016-01-13 17:52:30 +00:00
|
|
|
|
2016-01-14 12:27:39 +00:00
|
|
|
def _getLigatureKey(components):
|
2016-01-14 11:46:25 +01:00
|
|
|
"""Computes a key for ordering ligatures in a GSUB Type-4 lookup.
|
2016-01-14 10:27:54 +01:00
|
|
|
|
2016-01-14 11:46:25 +01:00
|
|
|
When building the OpenType lookup, we need to make sure that
|
|
|
|
the longest sequence of components is listed first, so we
|
|
|
|
use the negative length as the primary key for sorting.
|
2016-01-22 19:32:45 +01:00
|
|
|
To make buildLigatureSubstSubtable() deterministic, we use the
|
2016-01-14 11:46:25 +01:00
|
|
|
component sequence as the secondary key.
|
|
|
|
|
|
|
|
For example, this will sort (f,f,f) < (f,f,i) < (f,f) < (f,i) < (f,l).
|
|
|
|
"""
|
|
|
|
return (-len(components), components)
|
2016-01-14 10:27:54 +01:00
|
|
|
|
2016-01-14 16:25:28 +01:00
|
|
|
|
2016-01-22 19:32:45 +01:00
|
|
|
def buildLigatureSubstSubtable(mapping):
|
2016-01-22 19:38:20 +01:00
|
|
|
if not mapping:
|
|
|
|
return None
|
2016-01-14 11:46:25 +01:00
|
|
|
self = ot.LigatureSubst()
|
|
|
|
# The following single line can replace the rest of this function
|
|
|
|
# with fontTools >= 3.1:
|
|
|
|
# self.ligatures = dict(mapping)
|
|
|
|
self.ligatures = {}
|
2016-01-14 12:27:39 +00:00
|
|
|
for components in sorted(mapping.keys(), key=_getLigatureKey):
|
2016-01-14 11:46:25 +01:00
|
|
|
ligature = ot.Ligature()
|
|
|
|
ligature.Component = components[1:]
|
|
|
|
ligature.CompCount = len(components)
|
|
|
|
ligature.LigGlyph = mapping[components]
|
|
|
|
firstGlyph = components[0]
|
|
|
|
self.ligatures.setdefault(firstGlyph, []).append(ligature)
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
2016-01-14 12:28:32 +00:00
|
|
|
# GPOS
|
|
|
|
|
|
|
|
|
2016-01-14 13:08:26 +01:00
|
|
|
def buildAnchor(x, y, point=None, deviceX=None, deviceY=None):
|
|
|
|
self = ot.Anchor()
|
|
|
|
self.XCoordinate, self.YCoordinate = x, y
|
|
|
|
self.Format = 1
|
|
|
|
if point is not None:
|
|
|
|
self.AnchorPoint = point
|
|
|
|
self.Format = 2
|
2016-01-14 14:59:10 +00:00
|
|
|
if deviceX is not None or deviceY is not None:
|
2016-01-21 15:23:41 +01:00
|
|
|
assert self.Format == 1, \
|
|
|
|
"Either point, or both of deviceX/deviceY, must be None."
|
2016-01-14 13:08:26 +01:00
|
|
|
self.XDeviceTable = deviceX
|
|
|
|
self.YDeviceTable = deviceY
|
|
|
|
self.Format = 3
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
2016-01-22 10:57:03 +01:00
|
|
|
def buildBaseArray(bases, numMarkClasses, glyphMap):
|
|
|
|
self = ot.BaseArray()
|
|
|
|
self.BaseCount = len(bases)
|
|
|
|
self.BaseRecord = []
|
|
|
|
for base in sorted(bases, key=glyphMap.__getitem__):
|
|
|
|
b = bases[base]
|
|
|
|
anchors = [b.get(markClass) for markClass in range(numMarkClasses)]
|
|
|
|
self.BaseRecord.append(buildBaseRecord(anchors))
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
2016-01-21 17:23:36 +01:00
|
|
|
def buildBaseRecord(anchors):
|
|
|
|
"""[otTables.Anchor, otTables.Anchor, ...] --> otTables.BaseRecord"""
|
|
|
|
self = ot.BaseRecord()
|
|
|
|
self.BaseAnchor = anchors
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
2016-01-21 15:16:55 +01:00
|
|
|
def buildComponentRecord(anchors):
|
|
|
|
"""[otTables.Anchor, otTables.Anchor, ...] --> otTables.ComponentRecord"""
|
2016-01-22 12:45:29 +01:00
|
|
|
if not anchors:
|
|
|
|
return None
|
2016-01-21 15:16:55 +01:00
|
|
|
self = ot.ComponentRecord()
|
|
|
|
self.LigatureAnchor = anchors
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
2016-01-22 19:38:20 +01:00
|
|
|
def buildCursivePosSubtable(attach, glyphMap):
|
2016-01-14 17:54:47 +01:00
|
|
|
"""{"alef": (entry, exit)} --> otTables.CursivePos"""
|
2016-01-22 19:39:54 +01:00
|
|
|
if not attach:
|
|
|
|
return None
|
2016-01-14 17:54:47 +01:00
|
|
|
self = ot.CursivePos()
|
|
|
|
self.Format = 1
|
|
|
|
self.Coverage = buildCoverage(attach.keys(), glyphMap)
|
|
|
|
self.EntryExitRecord = []
|
|
|
|
for glyph in self.Coverage.glyphs:
|
|
|
|
entryAnchor, exitAnchor = attach[glyph]
|
|
|
|
rec = ot.EntryExitRecord()
|
|
|
|
rec.EntryAnchor = entryAnchor
|
|
|
|
rec.ExitAnchor = exitAnchor
|
|
|
|
self.EntryExitRecord.append(rec)
|
2016-01-18 12:32:01 +01:00
|
|
|
self.EntryExitCount = len(self.EntryExitRecord)
|
2016-01-14 17:54:47 +01:00
|
|
|
return self
|
|
|
|
|
|
|
|
|
2016-01-14 11:46:25 +01:00
|
|
|
def buildDevice(device):
|
|
|
|
"""[(11, 22), (7, -7), ...] --> otTables.Device"""
|
2016-01-22 19:39:54 +01:00
|
|
|
if not device:
|
|
|
|
return None
|
2016-01-14 11:46:25 +01:00
|
|
|
self = ot.Device()
|
|
|
|
device = tuple(sorted(device))
|
|
|
|
self.StartSize = startSize = device[0][0]
|
|
|
|
self.EndSize = endSize = device[-1][0]
|
|
|
|
deviceDict = dict(device)
|
|
|
|
self.DeltaValue = deltaValues = [
|
|
|
|
deviceDict.get(size, 0)
|
|
|
|
for size in range(startSize, endSize + 1)]
|
|
|
|
maxDelta = max(deltaValues)
|
|
|
|
minDelta = min(deltaValues)
|
|
|
|
assert minDelta > -129 and maxDelta < 128
|
|
|
|
if minDelta > -3 and maxDelta < 2:
|
|
|
|
self.DeltaFormat = 1
|
|
|
|
elif minDelta > -9 and maxDelta < 8:
|
|
|
|
self.DeltaFormat = 2
|
|
|
|
else:
|
|
|
|
self.DeltaFormat = 3
|
|
|
|
return self
|
2016-01-14 16:25:28 +01:00
|
|
|
|
|
|
|
|
2016-01-22 14:12:53 +01:00
|
|
|
def buildLigatureArray(ligs, numMarkClasses, glyphMap):
|
|
|
|
self = ot.LigatureArray()
|
|
|
|
self.LigatureCount = len(ligs)
|
|
|
|
self.LigatureAttach = []
|
|
|
|
for lig in sorted(ligs, key=glyphMap.__getitem__):
|
|
|
|
anchors = []
|
|
|
|
for component in ligs[lig]:
|
|
|
|
anchors.append([component.get(mc) for mc in range(numMarkClasses)])
|
|
|
|
self.LigatureAttach.append(buildLigatureAttach(anchors))
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
2016-01-22 12:45:29 +01:00
|
|
|
def buildLigatureAttach(components):
|
|
|
|
"""[[Anchor, Anchor], [Anchor, Anchor, Anchor]] --> LigatureAttach"""
|
|
|
|
self = ot.LigatureAttach()
|
|
|
|
self.ComponentCount = len(components)
|
|
|
|
self.ComponentRecord = [buildComponentRecord(c) for c in components]
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
2016-01-21 16:23:55 +01:00
|
|
|
def buildMarkArray(marks, glyphMap):
|
|
|
|
"""{"acute": (markClass, otTables.Anchor)} --> otTables.MarkArray"""
|
|
|
|
self = ot.MarkArray()
|
|
|
|
self.MarkCount = len(marks)
|
|
|
|
self.MarkRecord = []
|
|
|
|
for mark in sorted(marks.keys(), key=glyphMap.__getitem__):
|
|
|
|
markClass, anchor = marks[mark]
|
|
|
|
markrec = buildMarkRecord(markClass, anchor)
|
|
|
|
self.MarkRecord.append(markrec)
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
2016-01-22 11:53:34 +01:00
|
|
|
def buildMarkBasePos(marks, bases, glyphMap):
|
|
|
|
"""Build a list of MarkBasePos subtables.
|
|
|
|
|
2016-01-22 14:50:17 +01:00
|
|
|
a1, a2, a3, a4, a5 = buildAnchor(500, 100), ...
|
2016-01-22 11:53:34 +01:00
|
|
|
marks = {"acute": (0, a1), "grave": (0, a1), "cedilla": (1, a2)}
|
|
|
|
bases = {"a": {0: a3, 1: a5}, "b": {0: a4, 1: a5}}
|
|
|
|
"""
|
|
|
|
# TODO: Consider emitting multiple subtables to save space.
|
|
|
|
# Partition the marks and bases into disjoint subsets, so that
|
|
|
|
# MarkBasePos rules would only access glyphs from a single
|
|
|
|
# subset. This would likely lead to smaller mark/base
|
|
|
|
# matrices, so we might be able to omit many of the empty
|
|
|
|
# anchor tables that we currently produce. Of course, this
|
|
|
|
# would only work if the MarkBasePos rules of real-world fonts
|
|
|
|
# allow partitioning into multiple subsets. We should find out
|
|
|
|
# whether this is the case; if so, implement the optimization.
|
|
|
|
# On the other hand, a very large number of subtables could
|
|
|
|
# slow down layout engines; so this would need profiling.
|
|
|
|
return [buildMarkBasePosSubtable(marks, bases, glyphMap)]
|
|
|
|
|
|
|
|
|
|
|
|
def buildMarkBasePosSubtable(marks, bases, glyphMap):
|
|
|
|
"""Build a single MarkBasePos subtable.
|
|
|
|
|
2016-01-22 14:50:17 +01:00
|
|
|
a1, a2, a3, a4, a5 = buildAnchor(500, 100), ...
|
2016-01-22 11:53:34 +01:00
|
|
|
marks = {"acute": (0, a1), "grave": (0, a1), "cedilla": (1, a2)}
|
|
|
|
bases = {"a": {0: a3, 1: a5}, "b": {0: a4, 1: a5}}
|
|
|
|
"""
|
|
|
|
self = ot.MarkBasePos()
|
|
|
|
self.Format = 1
|
|
|
|
self.MarkCoverage = buildCoverage(marks, glyphMap)
|
|
|
|
self.MarkArray = buildMarkArray(marks, glyphMap)
|
|
|
|
self.ClassCount = max([mc for mc, _ in marks.values()]) + 1
|
|
|
|
self.BaseCoverage = buildCoverage(bases, glyphMap)
|
|
|
|
self.BaseArray = buildBaseArray(bases, self.ClassCount, glyphMap)
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
2016-01-22 14:50:17 +01:00
|
|
|
def buildMarkLigPos(marks, ligs, glyphMap):
|
|
|
|
"""Build a list of MarkLigPos subtables.
|
|
|
|
|
|
|
|
a1, a2, a3, a4, a5 = buildAnchor(500, 100), ...
|
|
|
|
marks = {"acute": (0, a1), "grave": (0, a1), "cedilla": (1, a2)}
|
|
|
|
ligs = {"f_i": [{0: a3, 1: a5}, {0: a4, 1: a5}], "c_t": [{...}, {...}]}
|
|
|
|
"""
|
|
|
|
# TODO: Consider splitting into multiple subtables to save space,
|
|
|
|
# as with MarkBasePos, this would be a trade-off that would need
|
|
|
|
# profiling. And, depending on how typical fonts are structured,
|
|
|
|
# it might not be worth doing at all.
|
|
|
|
return [buildMarkLigPosSubtable(marks, ligs, glyphMap)]
|
|
|
|
|
|
|
|
|
|
|
|
def buildMarkLigPosSubtable(marks, ligs, glyphMap):
|
|
|
|
"""Build a single MarkLigPos subtable.
|
|
|
|
|
|
|
|
a1, a2, a3, a4, a5 = buildAnchor(500, 100), ...
|
|
|
|
marks = {"acute": (0, a1), "grave": (0, a1), "cedilla": (1, a2)}
|
|
|
|
ligs = {"f_i": [{0: a3, 1: a5}, {0: a4, 1: a5}], "c_t": [{...}, {...}]}
|
|
|
|
"""
|
|
|
|
self = ot.MarkLigPos()
|
|
|
|
self.Format = 1
|
|
|
|
self.MarkCoverage = buildCoverage(marks, glyphMap)
|
|
|
|
self.MarkArray = buildMarkArray(marks, glyphMap)
|
|
|
|
self.ClassCount = max([mc for mc, _ in marks.values()]) + 1
|
|
|
|
self.LigatureCoverage = buildCoverage(ligs, glyphMap)
|
|
|
|
self.LigatureArray = buildLigatureArray(ligs, self.ClassCount, glyphMap)
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
2016-01-21 11:29:32 +01:00
|
|
|
def buildMarkRecord(classID, anchor):
|
|
|
|
assert isinstance(classID, int)
|
|
|
|
assert isinstance(anchor, ot.Anchor)
|
|
|
|
self = ot.MarkRecord()
|
|
|
|
self.Class = classID
|
|
|
|
self.MarkAnchor = anchor
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
2016-01-21 13:05:36 +01:00
|
|
|
def buildMark2Record(anchors):
|
|
|
|
"""[otTables.Anchor, otTables.Anchor, ...] --> otTables.Mark2Record"""
|
|
|
|
self = ot.Mark2Record()
|
|
|
|
self.Mark2Anchor = anchors
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
2016-01-19 14:22:28 +01:00
|
|
|
def buildSinglePos(mapping, glyphMap):
|
|
|
|
"""{"glyph": ValueRecord} --> [otTables.SinglePos*]"""
|
|
|
|
result, handled = [], set()
|
|
|
|
# In SinglePos format 1, the covered glyphs all share the same ValueRecord.
|
|
|
|
# In format 2, each glyph has its own ValueRecord, but these records
|
|
|
|
# all have the same properties (eg., all have an X but no Y placement).
|
|
|
|
coverages, masks, values = {}, {}, {}
|
|
|
|
for glyph, value in mapping.items():
|
|
|
|
key = _getSinglePosValueKey(value)
|
|
|
|
coverages.setdefault(key, []).append(glyph)
|
|
|
|
masks.setdefault(key[0], []).append(key)
|
|
|
|
values[key] = value
|
|
|
|
|
|
|
|
# If a ValueRecord is shared between multiple glyphs, we generate
|
|
|
|
# a SinglePos format 1 subtable; that is the most compact form.
|
|
|
|
for key, glyphs in coverages.items():
|
|
|
|
if len(glyphs) > 1:
|
2016-01-19 15:38:33 +01:00
|
|
|
format1Mapping = {g: values[key] for g in glyphs}
|
|
|
|
result.append(buildSinglePosSubtable(format1Mapping, glyphMap))
|
2016-01-19 14:22:28 +01:00
|
|
|
handled.add(key)
|
|
|
|
|
|
|
|
# In the remaining ValueRecords, look for those whose valueFormat
|
|
|
|
# (the set of used properties) is shared between multiple records.
|
|
|
|
# These will get encoded in format 2.
|
|
|
|
for valueFormat, keys in masks.items():
|
|
|
|
f2 = [k for k in keys if k not in handled]
|
|
|
|
if len(f2) > 1:
|
|
|
|
format2Mapping = {coverages[k][0]: values[k] for k in f2}
|
2016-01-19 15:38:33 +01:00
|
|
|
result.append(buildSinglePosSubtable(format2Mapping, glyphMap))
|
2016-01-19 14:22:28 +01:00
|
|
|
handled.update(f2)
|
|
|
|
|
|
|
|
# The remaining ValueRecords are singletons in the sense that
|
|
|
|
# they are only used by a single glyph, and their valueFormat
|
|
|
|
# is unique as well. We encode these in format 1 again.
|
|
|
|
for key, glyphs in coverages.items():
|
|
|
|
if key not in handled:
|
2016-01-19 15:38:33 +01:00
|
|
|
assert len(glyphs) == 1, glyphs
|
|
|
|
st = buildSinglePosSubtable({glyphs[0]: values[key]}, glyphMap)
|
|
|
|
result.append(st)
|
2016-01-19 14:22:28 +01:00
|
|
|
|
|
|
|
# When the OpenType layout engine traverses the subtables, it will
|
|
|
|
# stop after the first matching subtable. Therefore, we sort the
|
|
|
|
# resulting subtables by decreasing coverage size; this increases
|
|
|
|
# the chance that the layout engine can do an early exit. (Of course,
|
|
|
|
# this would only be true if all glyphs were equally frequent, which
|
|
|
|
# is not really the case; but we do not know their distribution).
|
|
|
|
# If two subtables cover the same number of glyphs, we sort them
|
|
|
|
# by glyph ID so that our output is deterministic.
|
|
|
|
result.sort(key=lambda t: _getSinglePosTableKey(t, glyphMap))
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
2016-01-19 15:38:33 +01:00
|
|
|
def buildSinglePosSubtable(values, glyphMap):
|
|
|
|
"""{glyphName: otBase.ValueRecord} --> otTables.SinglePos"""
|
|
|
|
self = ot.SinglePos()
|
|
|
|
self.Coverage = buildCoverage(values.keys(), glyphMap)
|
|
|
|
valueRecords = [values[g] for g in self.Coverage.glyphs]
|
|
|
|
self.ValueFormat = 0
|
|
|
|
for v in valueRecords:
|
|
|
|
self.ValueFormat |= v.getFormat()
|
|
|
|
if all(v == valueRecords[0] for v in valueRecords):
|
|
|
|
self.Format = 1
|
2016-01-19 21:59:37 +01:00
|
|
|
if self.ValueFormat != 0:
|
|
|
|
self.Value = valueRecords[0]
|
2016-01-22 13:43:05 +01:00
|
|
|
else:
|
|
|
|
self.Value = None
|
2016-01-19 15:38:33 +01:00
|
|
|
else:
|
|
|
|
self.Format = 2
|
|
|
|
self.Value = valueRecords
|
|
|
|
self.ValueCount = len(self.Value)
|
|
|
|
return self
|
2016-01-19 14:22:28 +01:00
|
|
|
|
|
|
|
|
|
|
|
def _getSinglePosTableKey(subtable, glyphMap):
|
|
|
|
assert isinstance(subtable, ot.SinglePos), subtable
|
|
|
|
glyphs = subtable.Coverage.glyphs
|
|
|
|
return (-len(glyphs), glyphMap[glyphs[0]])
|
|
|
|
|
|
|
|
|
|
|
|
def _getSinglePosValueKey(valueRecord):
|
|
|
|
"""otBase.ValueRecord --> (2, ("YPlacement": 12))"""
|
|
|
|
assert isinstance(valueRecord, ValueRecord), valueRecord
|
|
|
|
valueFormat, result = 0, []
|
|
|
|
for name, value in valueRecord.__dict__.items():
|
|
|
|
if isinstance(value, ot.Device):
|
|
|
|
result.append((name, _makeDeviceTuple(value)))
|
|
|
|
else:
|
|
|
|
result.append((name, value))
|
|
|
|
valueFormat |= valueRecordFormatDict[name][0]
|
|
|
|
result.sort()
|
|
|
|
result.insert(0, valueFormat)
|
|
|
|
return tuple(result)
|
|
|
|
|
|
|
|
|
|
|
|
def _makeDeviceTuple(device):
|
|
|
|
"""otTables.Device --> tuple, for making device tables unique"""
|
|
|
|
return (device.DeltaFormat, device.StartSize, device.EndSize,
|
|
|
|
tuple(device.DeltaValue))
|
|
|
|
|
|
|
|
|
2016-01-14 16:25:28 +01:00
|
|
|
def buildValue(value):
|
|
|
|
self = ValueRecord()
|
|
|
|
for k, v in value.items():
|
|
|
|
setattr(self, k, v)
|
|
|
|
return self
|
2016-01-19 22:38:23 +01:00
|
|
|
|
|
|
|
|
|
|
|
# GDEF
|
|
|
|
|
|
|
|
def buildAttachList(attachPoints, glyphMap):
|
2016-01-19 23:25:47 +01:00
|
|
|
"""{"glyphName": [4, 23]} --> otTables.AttachList, or None"""
|
|
|
|
if not attachPoints:
|
|
|
|
return None
|
2016-01-19 22:38:23 +01:00
|
|
|
self = ot.AttachList()
|
|
|
|
self.Coverage = buildCoverage(attachPoints.keys(), glyphMap)
|
2016-01-19 23:25:47 +01:00
|
|
|
self.AttachPoint = [buildAttachPoint(attachPoints[g])
|
2016-01-19 22:38:23 +01:00
|
|
|
for g in self.Coverage.glyphs]
|
|
|
|
self.GlyphCount = len(self.AttachPoint)
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
2016-01-19 23:25:47 +01:00
|
|
|
def buildAttachPoint(points):
|
2016-01-19 22:38:23 +01:00
|
|
|
"""[4, 23, 41] --> otTables.AttachPoint"""
|
|
|
|
self = ot.AttachPoint()
|
|
|
|
self.PointIndex = sorted(points)
|
|
|
|
self.PointCount = len(points)
|
|
|
|
return self
|
2016-01-20 08:23:42 +01:00
|
|
|
|
|
|
|
|
|
|
|
def buildCaretValueForCoord(coord):
|
|
|
|
"""500 --> otTables.CaretValue, format 1"""
|
|
|
|
self = ot.CaretValue()
|
|
|
|
self.Format = 1
|
|
|
|
self.Coordinate = coord
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
|
|
|
def buildCaretValueForPoint(point):
|
|
|
|
"""4 --> otTables.CaretValue, format 2"""
|
|
|
|
self = ot.CaretValue()
|
|
|
|
self.Format = 2
|
|
|
|
self.CaretValuePoint = point
|
|
|
|
return self
|
2016-01-20 09:16:31 +01:00
|
|
|
|
|
|
|
|
2016-01-20 09:49:09 +01:00
|
|
|
def buildLigCaretList(coords, points, glyphMap):
|
|
|
|
"""{"f_f_i":[300,600]}, {"c_t":[28]} --> otTables.LigCaretList, or None"""
|
|
|
|
glyphs = set(coords.keys()) if coords else set()
|
|
|
|
if points:
|
|
|
|
glyphs.update(points.keys())
|
|
|
|
carets = {g: buildLigGlyph(coords.get(g), points.get(g)) for g in glyphs}
|
|
|
|
carets = {g: c for g, c in carets.items() if c is not None}
|
|
|
|
if not carets:
|
|
|
|
return None
|
|
|
|
self = ot.LigCaretList()
|
|
|
|
self.Coverage = buildCoverage(carets.keys(), glyphMap)
|
|
|
|
self.LigGlyph = [carets[g] for g in self.Coverage.glyphs]
|
|
|
|
self.LigGlyphCount = len(self.LigGlyph)
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
2016-01-20 09:16:31 +01:00
|
|
|
def buildLigGlyph(coords, points):
|
|
|
|
"""([500], [4]) --> otTables.LigGlyph; None for empty coords/points"""
|
|
|
|
carets = []
|
|
|
|
if coords:
|
|
|
|
carets.extend([buildCaretValueForCoord(c) for c in sorted(coords)])
|
|
|
|
if points:
|
|
|
|
carets.extend([buildCaretValueForPoint(p) for p in sorted(points)])
|
|
|
|
if not carets:
|
|
|
|
return None
|
|
|
|
self = ot.LigGlyph()
|
|
|
|
self.CaretCount = len(carets)
|
|
|
|
self.CaretValue = carets
|
|
|
|
return self
|
2016-01-20 11:28:33 +01:00
|
|
|
|
|
|
|
|
|
|
|
def buildMarkGlyphSetsDef(markSets, glyphMap):
|
|
|
|
"""[{"acute","grave"}, {"caron","grave"}] --> otTables.MarkGlyphSetsDef"""
|
|
|
|
if not markSets:
|
|
|
|
return None
|
|
|
|
self = ot.MarkGlyphSetsDef()
|
|
|
|
self.MarkSetTableFormat = 1
|
|
|
|
self.Coverage = [buildCoverage(m, glyphMap) for m in markSets]
|
|
|
|
self.MarkSetCount = len(self.Coverage)
|
|
|
|
return self
|