Some support for CFF2
This commit is contained in:
parent
93cb09316a
commit
9d73327f3b
@ -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
|
||||
|
276
Tests/fontBuilder/data/test_var.otf.ttx
Normal file
276
Tests/fontBuilder/data/test_var.otf.ttx
Normal 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>
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user