aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/fonttools/fontTools/otlLib/builder.py
diff options
context:
space:
mode:
authorAlexSm <alex@ydb.tech>2024-03-05 10:40:59 +0100
committerGitHub <noreply@github.com>2024-03-05 12:40:59 +0300
commit1ac13c847b5358faba44dbb638a828e24369467b (patch)
tree07672b4dd3604ad3dee540a02c6494cb7d10dc3d /contrib/python/fonttools/fontTools/otlLib/builder.py
parentffcca3e7f7958ddc6487b91d3df8c01054bd0638 (diff)
downloadydb-1ac13c847b5358faba44dbb638a828e24369467b.tar.gz
Library import 16 (#2433)
Co-authored-by: robot-piglet <robot-piglet@yandex-team.com> Co-authored-by: deshevoy <deshevoy@yandex-team.com> Co-authored-by: robot-contrib <robot-contrib@yandex-team.com> Co-authored-by: thegeorg <thegeorg@yandex-team.com> Co-authored-by: robot-ya-builder <robot-ya-builder@yandex-team.com> Co-authored-by: svidyuk <svidyuk@yandex-team.com> Co-authored-by: shadchin <shadchin@yandex-team.com> Co-authored-by: robot-ratatosk <robot-ratatosk@yandex-team.com> Co-authored-by: innokentii <innokentii@yandex-team.com> Co-authored-by: arkady-e1ppa <arkady-e1ppa@yandex-team.com> Co-authored-by: snermolaev <snermolaev@yandex-team.com> Co-authored-by: dimdim11 <dimdim11@yandex-team.com> Co-authored-by: kickbutt <kickbutt@yandex-team.com> Co-authored-by: abdullinsaid <abdullinsaid@yandex-team.com> Co-authored-by: korsunandrei <korsunandrei@yandex-team.com> Co-authored-by: petrk <petrk@yandex-team.com> Co-authored-by: miroslav2 <miroslav2@yandex-team.com> Co-authored-by: serjflint <serjflint@yandex-team.com> Co-authored-by: akhropov <akhropov@yandex-team.com> Co-authored-by: prettyboy <prettyboy@yandex-team.com> Co-authored-by: ilikepugs <ilikepugs@yandex-team.com> Co-authored-by: hiddenpath <hiddenpath@yandex-team.com> Co-authored-by: mikhnenko <mikhnenko@yandex-team.com> Co-authored-by: spreis <spreis@yandex-team.com> Co-authored-by: andreyshspb <andreyshspb@yandex-team.com> Co-authored-by: dimaandreev <dimaandreev@yandex-team.com> Co-authored-by: rashid <rashid@yandex-team.com> Co-authored-by: robot-ydb-importer <robot-ydb-importer@yandex-team.com> Co-authored-by: r-vetrov <r-vetrov@yandex-team.com> Co-authored-by: ypodlesov <ypodlesov@yandex-team.com> Co-authored-by: zaverden <zaverden@yandex-team.com> Co-authored-by: vpozdyayev <vpozdyayev@yandex-team.com> Co-authored-by: robot-cozmo <robot-cozmo@yandex-team.com> Co-authored-by: v-korovin <v-korovin@yandex-team.com> Co-authored-by: arikon <arikon@yandex-team.com> Co-authored-by: khoden <khoden@yandex-team.com> Co-authored-by: psydmm <psydmm@yandex-team.com> Co-authored-by: robot-javacom <robot-javacom@yandex-team.com> Co-authored-by: dtorilov <dtorilov@yandex-team.com> Co-authored-by: sennikovmv <sennikovmv@yandex-team.com> Co-authored-by: hcpp <hcpp@ydb.tech>
Diffstat (limited to 'contrib/python/fonttools/fontTools/otlLib/builder.py')
-rw-r--r--contrib/python/fonttools/fontTools/otlLib/builder.py346
1 files changed, 314 insertions, 32 deletions
diff --git a/contrib/python/fonttools/fontTools/otlLib/builder.py b/contrib/python/fonttools/fontTools/otlLib/builder.py
index 4b457f4d9f..70fd87ab57 100644
--- a/contrib/python/fonttools/fontTools/otlLib/builder.py
+++ b/contrib/python/fonttools/fontTools/otlLib/builder.py
@@ -1,11 +1,13 @@
from collections import namedtuple, OrderedDict
import os
from fontTools.misc.fixedTools import fixedToFloat
+from fontTools.misc.roundTools import otRound
from fontTools import ttLib
from fontTools.ttLib.tables import otTables as ot
from fontTools.ttLib.tables.otBase import (
ValueRecord,
valueRecordFormatDict,
+ OTLOffsetOverflowError,
OTTableWriter,
CountReference,
)
@@ -350,16 +352,14 @@ class ChainContextualBuilder(LookupBuilder):
return [x for x in ruleset if len(x.rules) > 0]
def getCompiledSize_(self, subtables):
- size = 0
- for st in subtables:
- w = OTTableWriter()
- w["LookupType"] = CountReference(
- {"LookupType": st.LookupType}, "LookupType"
- )
- # We need to make a copy here because compiling
- # modifies the subtable (finalizing formats etc.)
- copy.deepcopy(st).compile(w, self.font)
- size += len(w.getAllData())
+ if not subtables:
+ return 0
+ # We need to make a copy here because compiling
+ # modifies the subtable (finalizing formats etc.)
+ table = self.buildLookup_(copy.deepcopy(subtables))
+ w = OTTableWriter()
+ table.compile(w, self.font)
+ size = len(w.getAllData())
return size
def build(self):
@@ -410,22 +410,23 @@ class ChainContextualBuilder(LookupBuilder):
if not ruleset.hasAnyGlyphClasses:
candidates[1] = [self.buildFormat1Subtable(ruleset, chaining)]
+ candidates_by_size = []
for i in [1, 2, 3]:
if candidates[i]:
try:
- self.getCompiledSize_(candidates[i])
- except Exception as e:
+ size = self.getCompiledSize_(candidates[i])
+ except OTLOffsetOverflowError as e:
log.warning(
"Contextual format %i at %s overflowed (%s)"
% (i, str(self.location), e)
)
- candidates[i] = None
+ else:
+ candidates_by_size.append((size, candidates[i]))
- candidates = [x for x in candidates if x is not None]
- if not candidates:
+ if not candidates_by_size:
raise OpenTypeLibError("All candidates overflowed", self.location)
- winner = min(candidates, key=self.getCompiledSize_)
+ _min_size, winner = min(candidates_by_size, key=lambda x: x[0])
subtables.extend(winner)
# If we are not chaining, lookup type will be automatically fixed by
@@ -774,7 +775,10 @@ class ChainContextSubstBuilder(ChainContextualBuilder):
if lookup is not None:
alts = lookup.getAlternateGlyphs()
for glyph, replacements in alts.items():
- result.setdefault(glyph, set()).update(replacements)
+ alts_for_glyph = result.setdefault(glyph, [])
+ alts_for_glyph.extend(
+ g for g in replacements if g not in alts_for_glyph
+ )
return result
def find_chainable_single_subst(self, mapping):
@@ -1238,7 +1242,7 @@ class SingleSubstBuilder(LookupBuilder):
return self.buildLookup_(subtables)
def getAlternateGlyphs(self):
- return {glyph: set([repl]) for glyph, repl in self.mapping.items()}
+ return {glyph: [repl] for glyph, repl in self.mapping.items()}
def add_subtable_break(self, location):
self.mapping[(self.SUBTABLE_BREAK_, location)] = self.SUBTABLE_BREAK_
@@ -1567,19 +1571,6 @@ def buildAlternateSubstSubtable(mapping):
return self
-def _getLigatureKey(components):
- # Computes a key for ordering ligatures in a GSUB Type-4 lookup.
-
- # 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.
- # To make buildLigatureSubstSubtable() deterministic, we use the
- # 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)
-
-
def buildLigatureSubstSubtable(mapping):
"""Builds a ligature substitution (GSUB4) subtable.
@@ -1613,7 +1604,7 @@ def buildLigatureSubstSubtable(mapping):
# with fontTools >= 3.1:
# self.ligatures = dict(mapping)
self.ligatures = {}
- for components in sorted(mapping.keys(), key=_getLigatureKey):
+ for components in sorted(mapping.keys(), key=self._getLigatureSortKey):
ligature = ot.Ligature()
ligature.Component = components[1:]
ligature.CompCount = len(ligature.Component) + 1
@@ -2916,3 +2907,294 @@ def _addName(ttFont, value, minNameID=0, windows=True, mac=True):
return nameTable.addMultilingualName(
names, ttFont=ttFont, windows=windows, mac=mac, minNameID=minNameID
)
+
+
+def buildMathTable(
+ ttFont,
+ constants=None,
+ italicsCorrections=None,
+ topAccentAttachments=None,
+ extendedShapes=None,
+ mathKerns=None,
+ minConnectorOverlap=0,
+ vertGlyphVariants=None,
+ horizGlyphVariants=None,
+ vertGlyphAssembly=None,
+ horizGlyphAssembly=None,
+):
+ """
+ Add a 'MATH' table to 'ttFont'.
+
+ 'constants' is a dictionary of math constants. The keys are the constant
+ names from the MATH table specification (with capital first letter), and the
+ values are the constant values as numbers.
+
+ 'italicsCorrections' is a dictionary of italic corrections. The keys are the
+ glyph names, and the values are the italic corrections as numbers.
+
+ 'topAccentAttachments' is a dictionary of top accent attachments. The keys
+ are the glyph names, and the values are the top accent horizontal positions
+ as numbers.
+
+ 'extendedShapes' is a set of extended shape glyphs.
+
+ 'mathKerns' is a dictionary of math kerns. The keys are the glyph names, and
+ the values are dictionaries. The keys of these dictionaries are the side
+ names ('TopRight', 'TopLeft', 'BottomRight', 'BottomLeft'), and the values
+ are tuples of two lists. The first list contains the correction heights as
+ numbers, and the second list contains the kern values as numbers.
+
+ 'minConnectorOverlap' is the minimum connector overlap as a number.
+
+ 'vertGlyphVariants' is a dictionary of vertical glyph variants. The keys are
+ the glyph names, and the values are tuples of glyph name and full advance height.
+
+ 'horizGlyphVariants' is a dictionary of horizontal glyph variants. The keys
+ are the glyph names, and the values are tuples of glyph name and full
+ advance width.
+
+ 'vertGlyphAssembly' is a dictionary of vertical glyph assemblies. The keys
+ are the glyph names, and the values are tuples of assembly parts and italics
+ correction. The assembly parts are tuples of glyph name, flags, start
+ connector length, end connector length, and full advance height.
+
+ 'horizGlyphAssembly' is a dictionary of horizontal glyph assemblies. The
+ keys are the glyph names, and the values are tuples of assembly parts
+ and italics correction. The assembly parts are tuples of glyph name, flags,
+ start connector length, end connector length, and full advance width.
+
+ Where a number is expected, an integer or a float can be used. The floats
+ will be rounded.
+
+ Example::
+
+ constants = {
+ "ScriptPercentScaleDown": 70,
+ "ScriptScriptPercentScaleDown": 50,
+ "DelimitedSubFormulaMinHeight": 24,
+ "DisplayOperatorMinHeight": 60,
+ ...
+ }
+ italicsCorrections = {
+ "fitalic-math": 100,
+ "fbolditalic-math": 120,
+ ...
+ }
+ topAccentAttachments = {
+ "circumflexcomb": 500,
+ "acutecomb": 400,
+ "A": 300,
+ "B": 340,
+ ...
+ }
+ extendedShapes = {"parenleft", "parenright", ...}
+ mathKerns = {
+ "A": {
+ "TopRight": ([-50, -100], [10, 20, 30]),
+ "TopLeft": ([50, 100], [10, 20, 30]),
+ ...
+ },
+ ...
+ }
+ vertGlyphVariants = {
+ "parenleft": [("parenleft", 700), ("parenleft.size1", 1000), ...],
+ "parenright": [("parenright", 700), ("parenright.size1", 1000), ...],
+ ...
+ }
+ vertGlyphAssembly = {
+ "braceleft": [
+ (
+ ("braceleft.bottom", 0, 0, 200, 500),
+ ("braceleft.extender", 1, 200, 200, 200)),
+ ("braceleft.middle", 0, 100, 100, 700),
+ ("braceleft.extender", 1, 200, 200, 200),
+ ("braceleft.top", 0, 200, 0, 500),
+ ),
+ 100,
+ ],
+ ...
+ }
+ """
+ glyphMap = ttFont.getReverseGlyphMap()
+
+ ttFont["MATH"] = math = ttLib.newTable("MATH")
+ math.table = table = ot.MATH()
+ table.Version = 0x00010000
+ table.populateDefaults()
+
+ table.MathConstants = _buildMathConstants(constants)
+ table.MathGlyphInfo = _buildMathGlyphInfo(
+ glyphMap,
+ italicsCorrections,
+ topAccentAttachments,
+ extendedShapes,
+ mathKerns,
+ )
+ table.MathVariants = _buildMathVariants(
+ glyphMap,
+ minConnectorOverlap,
+ vertGlyphVariants,
+ horizGlyphVariants,
+ vertGlyphAssembly,
+ horizGlyphAssembly,
+ )
+
+
+def _buildMathConstants(constants):
+ if not constants:
+ return None
+
+ mathConstants = ot.MathConstants()
+ for conv in mathConstants.getConverters():
+ value = otRound(constants.get(conv.name, 0))
+ if conv.tableClass:
+ assert issubclass(conv.tableClass, ot.MathValueRecord)
+ value = _mathValueRecord(value)
+ setattr(mathConstants, conv.name, value)
+ return mathConstants
+
+
+def _buildMathGlyphInfo(
+ glyphMap,
+ italicsCorrections,
+ topAccentAttachments,
+ extendedShapes,
+ mathKerns,
+):
+ if not any([extendedShapes, italicsCorrections, topAccentAttachments, mathKerns]):
+ return None
+
+ info = ot.MathGlyphInfo()
+ info.populateDefaults()
+
+ if italicsCorrections:
+ coverage = buildCoverage(italicsCorrections.keys(), glyphMap)
+ info.MathItalicsCorrectionInfo = ot.MathItalicsCorrectionInfo()
+ info.MathItalicsCorrectionInfo.Coverage = coverage
+ info.MathItalicsCorrectionInfo.ItalicsCorrectionCount = len(coverage.glyphs)
+ info.MathItalicsCorrectionInfo.ItalicsCorrection = [
+ _mathValueRecord(italicsCorrections[n]) for n in coverage.glyphs
+ ]
+
+ if topAccentAttachments:
+ coverage = buildCoverage(topAccentAttachments.keys(), glyphMap)
+ info.MathTopAccentAttachment = ot.MathTopAccentAttachment()
+ info.MathTopAccentAttachment.TopAccentCoverage = coverage
+ info.MathTopAccentAttachment.TopAccentAttachmentCount = len(coverage.glyphs)
+ info.MathTopAccentAttachment.TopAccentAttachment = [
+ _mathValueRecord(topAccentAttachments[n]) for n in coverage.glyphs
+ ]
+
+ if extendedShapes:
+ info.ExtendedShapeCoverage = buildCoverage(extendedShapes, glyphMap)
+
+ if mathKerns:
+ coverage = buildCoverage(mathKerns.keys(), glyphMap)
+ info.MathKernInfo = ot.MathKernInfo()
+ info.MathKernInfo.MathKernCoverage = coverage
+ info.MathKernInfo.MathKernCount = len(coverage.glyphs)
+ info.MathKernInfo.MathKernInfoRecords = []
+ for glyph in coverage.glyphs:
+ record = ot.MathKernInfoRecord()
+ for side in {"TopRight", "TopLeft", "BottomRight", "BottomLeft"}:
+ if side in mathKerns[glyph]:
+ correctionHeights, kernValues = mathKerns[glyph][side]
+ assert len(correctionHeights) == len(kernValues) - 1
+ kern = ot.MathKern()
+ kern.HeightCount = len(correctionHeights)
+ kern.CorrectionHeight = [
+ _mathValueRecord(h) for h in correctionHeights
+ ]
+ kern.KernValue = [_mathValueRecord(v) for v in kernValues]
+ setattr(record, f"{side}MathKern", kern)
+ info.MathKernInfo.MathKernInfoRecords.append(record)
+
+ return info
+
+
+def _buildMathVariants(
+ glyphMap,
+ minConnectorOverlap,
+ vertGlyphVariants,
+ horizGlyphVariants,
+ vertGlyphAssembly,
+ horizGlyphAssembly,
+):
+ if not any(
+ [vertGlyphVariants, horizGlyphVariants, vertGlyphAssembly, horizGlyphAssembly]
+ ):
+ return None
+
+ variants = ot.MathVariants()
+ variants.populateDefaults()
+
+ variants.MinConnectorOverlap = minConnectorOverlap
+
+ if vertGlyphVariants or vertGlyphAssembly:
+ variants.VertGlyphCoverage, variants.VertGlyphConstruction = (
+ _buildMathGlyphConstruction(
+ glyphMap,
+ vertGlyphVariants,
+ vertGlyphAssembly,
+ )
+ )
+
+ if horizGlyphVariants or horizGlyphAssembly:
+ variants.HorizGlyphCoverage, variants.HorizGlyphConstruction = (
+ _buildMathGlyphConstruction(
+ glyphMap,
+ horizGlyphVariants,
+ horizGlyphAssembly,
+ )
+ )
+
+ return variants
+
+
+def _buildMathGlyphConstruction(glyphMap, variants, assemblies):
+ glyphs = set()
+ if variants:
+ glyphs.update(variants.keys())
+ if assemblies:
+ glyphs.update(assemblies.keys())
+ coverage = buildCoverage(glyphs, glyphMap)
+ constructions = []
+
+ for glyphName in coverage.glyphs:
+ construction = ot.MathGlyphConstruction()
+ construction.populateDefaults()
+
+ if variants and glyphName in variants:
+ construction.VariantCount = len(variants[glyphName])
+ construction.MathGlyphVariantRecord = []
+ for variantName, advance in variants[glyphName]:
+ record = ot.MathGlyphVariantRecord()
+ record.VariantGlyph = variantName
+ record.AdvanceMeasurement = otRound(advance)
+ construction.MathGlyphVariantRecord.append(record)
+
+ if assemblies and glyphName in assemblies:
+ parts, ic = assemblies[glyphName]
+ construction.GlyphAssembly = ot.GlyphAssembly()
+ construction.GlyphAssembly.ItalicsCorrection = _mathValueRecord(ic)
+ construction.GlyphAssembly.PartCount = len(parts)
+ construction.GlyphAssembly.PartRecords = []
+ for part in parts:
+ part_name, flags, start, end, advance = part
+ record = ot.GlyphPartRecord()
+ record.glyph = part_name
+ record.PartFlags = int(flags)
+ record.StartConnectorLength = otRound(start)
+ record.EndConnectorLength = otRound(end)
+ record.FullAdvance = otRound(advance)
+ construction.GlyphAssembly.PartRecords.append(record)
+
+ constructions.append(construction)
+
+ return coverage, constructions
+
+
+def _mathValueRecord(value):
+ value_record = ot.MathValueRecord()
+ value_record.Value = otRound(value)
+ return value_record