Some support for CFF2

This commit is contained in:
justvanrossum 2018-12-05 13:51:42 +01:00
parent 93cb09316a
commit 9d73327f3b
3 changed files with 411 additions and 0 deletions

View File

@ -491,6 +491,83 @@ class FontBuilder(object):
self.font["CFF "] = newTable("CFF ")
self.font["CFF "].cff = fontSet
def setupCFF2(self, charStringsDict, fdArrayList=None, regions=None):
from .cffLib import CFFFontSet, TopDictIndex, TopDict, CharStrings, \
GlobalSubrsIndex, PrivateDict, FDArrayIndex, FontDict
assert not self.isTTF
self.font.sfntVersion = "OTTO"
fontSet = CFFFontSet()
fontSet.major = 2
fontSet.minor = 0
cff2GetGlyphOrder = self.font.getGlyphOrder
fontSet.topDictIndex = TopDictIndex(None, cff2GetGlyphOrder, None)
globalSubrs = GlobalSubrsIndex()
fontSet.GlobalSubrs = globalSubrs
if fdArrayList is None:
fdArrayList = [{}]
fdSelect = None
fdArray = FDArrayIndex()
fdArray.strings = None
fdArray.GlobalSubrs = globalSubrs
for privateDict in fdArrayList:
fontDict = FontDict()
fontDict.setCFF2(True)
private = PrivateDict()
for key, value in privateDict.items():
setattr(private, key, value)
fontDict.Private = private
fdArray.append(fontDict)
topDict = TopDict()
topDict.cff2GetGlyphOrder = cff2GetGlyphOrder
topDict.FDArray = fdArray
scale = 1 / self.font["head"].unitsPerEm
topDict.FontMatrix = [scale, 0, 0, scale, 0, 0]
private = fdArray[0].Private
charStrings = CharStrings(None, None, globalSubrs, private, fdSelect, fdArray)
for glypnName, charString in charStringsDict.items():
charString.private = private
charString.globalSubrs = globalSubrs
charStrings[glypnName] = charString
topDict.CharStrings = charStrings
fontSet.topDictIndex.append(topDict)
self.font["CFF2"] = newTable("CFF2")
self.font["CFF2"].cff = fontSet
if regions:
self.setupCFF2Regions(regions)
def setupCFF2Regions(self, regions):
from .ttLib.tables import otTables as ot
from .varLib.builder import buildVarStore, buildVarRegionAxis, buildVarData
from .cffLib import VarStoreData
assert "fvar" in self.font, "fvar must to be set up first"
assert "CFF2" in self.font, "CFF2 must to be set up first"
axisTags = [a.axisTag for a in self.font["fvar"].axes]
varRegionList = ot.VarRegionList()
varRegionList.RegionAxisCount = len(regions)
varRegionList.Region = []
for regionDict in regions:
region = ot.VarRegion()
region.VarRegionAxis = []
for tag in axisTags:
axisSupport = regionDict.get(tag, (0, 0, 0))
region.VarRegionAxis.append(buildVarRegionAxis(axisSupport))
region.VarRegionAxisCount = len(region.VarRegionAxis)
varRegionList.Region.append(region)
varData = buildVarData(list(range(len(regions))), None, optimize=False)
varStore = buildVarStore(varRegionList, [varData])
vstore = VarStoreData(otVarStore=varStore)
self.font["CFF2"].cff.topDictIndex[0].VarStore = vstore
def setupGlyf(self, glyphs, calcGlyphBounds=True):
"""Create the `glyf` table from a dict, that maps glyph names
to `fontTools.ttLib.tables._g_l_y_f.Glyph` objects, for example

View File

@ -0,0 +1,276 @@
<?xml version="1.0" encoding="UTF-8"?>
<ttFont sfntVersion="OTTO" ttLibVersion="3.33">
<GlyphOrder>
<!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
<GlyphID id="0" name=".notdef"/>
<GlyphID id="1" name="glyph00001"/>
<GlyphID id="2" name="A"/>
<GlyphID id="3" name="a"/>
</GlyphOrder>
<head>
<!-- Most of this table will be recalculated by the compiler -->
<tableVersion value="1.0"/>
<fontRevision value="1.0"/>
<checkSumAdjustment value="0xed07360f"/>
<magicNumber value="0x5f0f3cf5"/>
<flags value="00000000 00000011"/>
<unitsPerEm value="1000"/>
<created value="Wed Dec 5 11:55:26 2018"/>
<modified value="Wed Dec 5 11:55:26 2018"/>
<xMin value="0"/>
<yMin value="0"/>
<xMax value="0"/>
<yMax value="0"/>
<macStyle value="00000000 00000000"/>
<lowestRecPPEM value="3"/>
<fontDirectionHint value="2"/>
<indexToLocFormat value="0"/>
<glyphDataFormat value="0"/>
</head>
<hhea>
<tableVersion value="0x00010000"/>
<ascent value="824"/>
<descent value="200"/>
<lineGap value="0"/>
<advanceWidthMax value="0"/>
<minLeftSideBearing value="0"/>
<minRightSideBearing value="0"/>
<xMaxExtent value="0"/>
<caretSlopeRise value="1"/>
<caretSlopeRun value="0"/>
<caretOffset value="0"/>
<reserved0 value="0"/>
<reserved1 value="0"/>
<reserved2 value="0"/>
<reserved3 value="0"/>
<metricDataFormat value="0"/>
<numberOfHMetrics value="4"/>
</hhea>
<maxp>
<tableVersion value="0x5000"/>
<numGlyphs value="4"/>
</maxp>
<OS_2>
<!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
will be recalculated by the compiler -->
<version value="3"/>
<xAvgCharWidth value="650"/>
<usWeightClass value="400"/>
<usWidthClass value="5"/>
<fsType value="00000000 00000100"/>
<ySubscriptXSize value="0"/>
<ySubscriptYSize value="0"/>
<ySubscriptXOffset value="0"/>
<ySubscriptYOffset value="0"/>
<ySuperscriptXSize value="0"/>
<ySuperscriptYSize value="0"/>
<ySuperscriptXOffset value="0"/>
<ySuperscriptYOffset value="0"/>
<yStrikeoutSize value="0"/>
<yStrikeoutPosition value="0"/>
<sFamilyClass value="0"/>
<panose>
<bFamilyType value="0"/>
<bSerifStyle value="0"/>
<bWeight value="0"/>
<bProportion value="0"/>
<bContrast value="0"/>
<bStrokeVariation value="0"/>
<bArmStyle value="0"/>
<bLetterForm value="0"/>
<bMidline value="0"/>
<bXHeight value="0"/>
</panose>
<ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
<ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
<ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
<ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
<achVendID value="????"/>
<fsSelection value="00000000 00000000"/>
<usFirstCharIndex value="65"/>
<usLastCharIndex value="97"/>
<sTypoAscender value="825"/>
<sTypoDescender value="200"/>
<sTypoLineGap value="0"/>
<usWinAscent value="824"/>
<usWinDescent value="200"/>
<ulCodePageRange1 value="00000000 00000000 00000000 00000000"/>
<ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
<sxHeight value="0"/>
<sCapHeight value="0"/>
<usDefaultChar value="0"/>
<usBreakChar value="32"/>
<usMaxContext value="2"/>
</OS_2>
<hmtx>
<mtx name=".notdef" width="600" lsb="0"/>
<mtx name="A" width="600" lsb="0"/>
<mtx name="a" width="800" lsb="0"/>
<mtx name="glyph00001" width="600" lsb="0"/>
</hmtx>
<cmap>
<tableVersion version="0"/>
<cmap_format_4 platformID="0" platEncID="3" language="0">
<map code="0x41" name="A"/><!-- LATIN CAPITAL LETTER A -->
<map code="0x61" name="a"/><!-- LATIN SMALL LETTER A -->
</cmap_format_4>
<cmap_format_4 platformID="3" platEncID="1" language="0">
<map code="0x41" name="A"/><!-- LATIN CAPITAL LETTER A -->
<map code="0x61" name="a"/><!-- LATIN SMALL LETTER A -->
</cmap_format_4>
</cmap>
<name>
<namerecord nameID="256" platformID="1" platEncID="0" langID="0x0" unicode="True">
Test Axis
</namerecord>
<namerecord nameID="257" platformID="1" platEncID="0" langID="0x0" unicode="True">
TotallyNormal
</namerecord>
<namerecord nameID="258" platformID="1" platEncID="0" langID="0x0" unicode="True">
TotallyTested
</namerecord>
<namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
HelloTestFont
</namerecord>
<namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
TotallyNormal
</namerecord>
<namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
HelloTestFont-TotallyNormal
</namerecord>
<namerecord nameID="256" platformID="3" platEncID="1" langID="0x409">
Test Axis
</namerecord>
<namerecord nameID="257" platformID="3" platEncID="1" langID="0x409">
TotallyNormal
</namerecord>
<namerecord nameID="258" platformID="3" platEncID="1" langID="0x409">
TotallyTested
</namerecord>
<namerecord nameID="1" platformID="3" platEncID="1" langID="0x413">
HalloTestFont
</namerecord>
<namerecord nameID="2" platformID="3" platEncID="1" langID="0x413">
TotaalNormaal
</namerecord>
</name>
<post>
<formatType value="3.0"/>
<italicAngle value="0.0"/>
<underlinePosition value="0"/>
<underlineThickness value="0"/>
<isFixedPitch value="0"/>
<minMemType42 value="0"/>
<maxMemType42 value="0"/>
<minMemType1 value="0"/>
<maxMemType1 value="0"/>
</post>
<CFF2>
<major value="2"/>
<minor value="0"/>
<CFFFont name="CFF2Font">
<FontMatrix value="0.001 0 0 0.001 0 0"/>
<FDArray>
<FontDict index="0">
<Private>
<BlueScale value="0.039625"/>
<BlueShift value="7"/>
<BlueFuzz value="1"/>
</Private>
</FontDict>
</FDArray>
<CharStrings>
<CharString name=".notdef">
100 100 rmoveto
900 vlineto
-67 67 66 -33 67 hhcurveto
67 66 33 67 67 hvcurveto
-900 vlineto
</CharString>
<CharString name="A">
100 100 rmoveto
900 vlineto
-67 67 66 -33 67 hhcurveto
67 66 33 67 67 hvcurveto
-900 vlineto
</CharString>
<CharString name="a">
200 200 -200 -200 2 blend
rmoveto
400 400 1 blend
hlineto
400 400 1 blend
vlineto
-400 -400 1 blend
hlineto
</CharString>
<CharString name="glyph00001">
100 100 rmoveto
900 vlineto
-67 67 66 -33 67 hhcurveto
67 66 33 67 67 hvcurveto
-900 vlineto
</CharString>
</CharStrings>
<VarStore Format="1">
<Format value="1"/>
<VarRegionList>
<!-- RegionAxisCount=1 -->
<!-- RegionCount=1 -->
<Region index="0">
<VarRegionAxis index="0">
<StartCoord value="0.0"/>
<PeakCoord value="1.0"/>
<EndCoord value="1.0"/>
</VarRegionAxis>
</Region>
</VarRegionList>
<!-- VarDataCount=1 -->
<VarData index="0">
<!-- ItemCount=0 -->
<NumShorts value="0"/>
<!-- VarRegionCount=1 -->
<VarRegionIndex index="0" value="0"/>
</VarData>
</VarStore>
</CFFFont>
<GlobalSubrs>
<!-- The 'index' attribute is only for humans; it is ignored when parsed. -->
</GlobalSubrs>
</CFF2>
<fvar>
<!-- Test Axis -->
<Axis>
<AxisTag>TEST</AxisTag>
<Flags>0x0</Flags>
<MinValue>0.0</MinValue>
<DefaultValue>0.0</DefaultValue>
<MaxValue>100.0</MaxValue>
<AxisNameID>256</AxisNameID>
</Axis>
<!-- TotallyNormal -->
<NamedInstance flags="0x0" subfamilyNameID="257">
<coord axis="TEST" value="0.0"/>
</NamedInstance>
<!-- TotallyTested -->
<NamedInstance flags="0x0" subfamilyNameID="258">
<coord axis="TEST" value="100.0"/>
</NamedInstance>
</fvar>
</ttFont>

View File

@ -10,6 +10,7 @@ from fontTools.pens.t2CharStringPen import T2CharStringPen
from fontTools.fontBuilder import FontBuilder
from fontTools.ttLib.tables.TupleVariation import TupleVariation
from fontTools.ttLib.tables._g_l_y_f import GlyphCoordinates
from fontTools.misc.psCharStrings import T2CharString
def getTestData(fileName, mode="r"):
@ -190,3 +191,60 @@ def test_build_var(tmpdir):
testData = strip_VariableItems(f.read())
refData = strip_VariableItems(getTestData("test_var.ttf.ttx"))
assert refData == testData
def test_build_cff2(tmpdir):
outPath = os.path.join(str(tmpdir), "test_var.otf")
fb = FontBuilder(1000, isTTF=False)
fb.setupGlyphOrder([".notdef", ".null", "A", "a"])
fb.setupCharacterMap({65: "A", 97: "a"})
advanceWidths = {".notdef": 600, "A": 600, "a": 800, ".null": 600}
familyName = "HelloTestFont"
styleName = "TotallyNormal"
nameStrings = dict(familyName=dict(en="HelloTestFont", nl="HalloTestFont"),
styleName=dict(en="TotallyNormal", nl="TotaalNormaal"))
nameStrings['psName'] = familyName + "-" + styleName
fb.setupNameTable(nameStrings)
axes = [
('TEST', 0, 0, 100, "Test Axis"),
]
instances = [
dict(location=dict(TEST=0), stylename="TotallyNormal"),
dict(location=dict(TEST=100), stylename="TotallyTested"),
]
fb.setupFvar(axes, instances)
pen = T2CharStringPen(None, None, CFF2=True)
drawTestGlyph(pen)
charString = pen.getCharString()
program = [
200, 200, -200, -200, 2, "blend", "rmoveto",
400, 400, 1, "blend", "hlineto",
400, 400, 1, "blend", "vlineto",
-400, -400, 1, "blend", "hlineto"
]
charStringVariable = T2CharString(program=program)
charStrings = {".notdef": charString, "A": charString, "a": charStringVariable, ".null": charString}
fb.setupCFF2(charStrings, [{}], regions=[{"TEST": (0, 1, 1)}])
metrics = {gn: (advanceWidth, 0) for gn, advanceWidth in advanceWidths.items()}
fb.setupHorizontalMetrics(metrics)
fb.setupHorizontalHeader(ascent=824, descent=200)
fb.setupOS2(sTypoAscender=825, sTypoDescender=200, usWinAscent=824, usWinDescent=200)
fb.setupPost()
fb.save(outPath)
f = TTFont(outPath)
f.saveXML(outPath + ".ttx")
with open(outPath + ".ttx") as f:
testData = strip_VariableItems(f.read())
refData = strip_VariableItems(getTestData("test_var.otf.ttx"))
assert refData == testData