def test_fromXML(self): cs = T2CharString() for name, attrs, content in parseXML([ '<CharString name="period">' ' 338.4 142.8 rmoveto', ' 28 0 21.9 9 15.8 18 15.8 18 7.9 20.79959 0 23.6 rrcurveto', ' endchar' '</CharString>' ]): cs.fromXML(name, attrs, content) expected_program = [ 338.3999939, 142.8000031, 'rmoveto', 28, 0, 21.8999939, 9, 15.8000031, 18, 15.8000031, 18, 7.8999939, 20.7995911, 0, 23.6000061, 'rrcurveto', 'endchar' ] self.assertEqual(len(cs.program), len(expected_program)) for arg, expected_arg in zip(cs.program, expected_program): if isinstance(arg, str): self.assertIsInstance(expected_arg, str) self.assertEqual(arg, expected_arg) else: self.assertNotIsInstance(expected_arg, str) self.assertAlmostEqual(arg, expected_arg)
def getCharString(self, private=None, globalSubrs=None, var_model=None, optimize=True): commands = self._commands commands = self.reorder_blend_args(commands) if optimize: commands = specializeCommands(commands, generalizeFirst=False, maxstack=maxStackLimit) program = self.mergeCommandsToProgram(commands, var_model=var_model, round_func=self.roundNumber) charString = T2CharString(program=program, private=private, globalSubrs=globalSubrs) return charString
def test_build_cff2(tmpdir): outPath = os.path.join(str(tmpdir), "test_var.otf") fb, advanceWidths, nameStrings = _setupFontBuilder(False, 1000) 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) _verifyOutput(outPath)
def buildMMCFFTables(baseFont, bcDictList, cff2GlyphList, numMasters, varModel): pd = baseFont.privateDict opCodeDict = buildOpcodeDict(privateDictOperators) for key in bcDictList.keys(): operator, argType = opCodeDict[key] valList = bcDictList[key] if argType == 'delta': # If all masters are the same for each entry in the list, then # save a non-blend version; else save the blend version needsBlend = False for blendList in valList: if pointsDiffer(blendList): needsBlend = True break dataList = [] if needsBlend: blendCount = 0 for blendList in valList: dataList.append(blendList) else: for blendList in valList: dataList.append(blendList[0]) else: if pointsDiffer(valList): dataList = valList else: dataList = valList[0] pd.rawDict[key] = dataList # Now update all the charstrings. fontGlyphList = baseFont.ttFont.getGlyphOrder() for glyphName in fontGlyphList: gid = baseFont.charStrings.charStrings[glyphName] t2CharString = baseFont.charStringIndex[gid] cff2GlyphData = cff2GlyphList[glyphName] t2CharString.decompile() newProgram = [] for op, pointList in cff2GlyphData.opList: blendStack = appendBlendOp(op, pointList, varModel.deltaWeights) newProgram.extend(blendStack) t2CharString = T2CharString(private=t2CharString.private, globalSubrs=t2CharString.globalSubrs) baseFont.charStringIndex[gid] = t2CharString t2CharString.program = newProgram t2CharString.compile(isCFF2=True) baseFont.privateDict.defaultWidthX = 0 baseFont.privateDict.nominalWidthX = 0
def addGlyphToCFF(glyphName=None, program=None, private=None, globalSubrs=None, charStringsIndex=None, topDict=None, charStrings=None): charString = T2CharString(program=program, private=private, globalSubrs=globalSubrs) charStringsIndex.append(charString) glyphID = len(topDict.charset) charStrings.charStrings[glyphName] = glyphID topDict.charset.append(glyphName)
def getCharString( self, private=None, globalSubrs=None, var_model=None, optimize=True): commands = self._commands commands = self.reorder_blend_args(commands, partial (var_model.getDeltas, round=self.round)) if optimize: commands = specializeCommands( commands, generalizeFirst=False, maxstack=maxStackLimit) program = commandsToProgram(commands) charString = T2CharString( program=program, private=private, globalSubrs=globalSubrs) return charString
def addCFFGlyph(self, glyphName=None, program=None, private=None, globalSubrs=None, charStringsIndex=None, topDict=None, charStrings=None): from fontTools.misc.psCharStrings import T2CharString charString = T2CharString(program=program, private=private, globalSubrs=globalSubrs) charStringsIndex.append(charString) glyphID = len(topDict.charset) charStrings.charStrings[glyphName] = glyphID topDict.charset.append(glyphName)
def getCharString(self, private=None, globalSubrs=None, optimize=True): commands = self._commands if optimize: maxstack = 48 if not self._CFF2 else 513 commands = specializeCommands(commands, generalizeFirst=False, maxstack=maxstack) program = commandsToProgram(commands) if self._width is not None: assert not self._CFF2, "CFF2 does not allow encoding glyph width in CharString." program.insert(0, otRound(self._width)) if not self._CFF2: program.append('endchar') charString = T2CharString( program=program, private=private, globalSubrs=globalSubrs) return charString
def getCharStrings(self, num_masters, private=None, globalSubrs=None, default_idx=0): """ A command looks like: [op_name, [ [source 0 arglist for op], [source 1 arglist for op], ... [source n arglist for op], I am not optimizing this there, as that will be done when the CFF2 Charstring is created in fontTools.varLib.build(). If I did, I would have to rearrange the arguments to: [ [arg 0 for source 0 ... arg 0 for source n] [arg 1 for source 0 ... arg 1 for source n] ... [arg M for source 0 ... arg M for source n] ] before calling specialize. """ t2List = [] merged_commands = self._commands for i in range(num_masters): commands = [] for op in merged_commands: source_op = [op[0], op[1][i]] commands.append(source_op) program = commandsToProgram(commands) if self._width is not None: assert not self._CFF2, ( "CFF2 does not allow encoding glyph width in CharString.") program.insert(0, otRound(self._width)) if not self._CFF2: program.append('endchar') charString = T2CharString(program=program, private=private, globalSubrs=globalSubrs) t2List.append(charString) # if default_idx is not 0, we need to move it to the right index. if default_idx: default_font_cs = t2List.pop(0) t2List.insert(default_idx, default_font_cs) return t2List
def _setupFontBuilderCFF2(fb): assert 'fvar' in fb.font, 'Must run _setupFontBuilderFvar() first.' 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)}]) return fb
def stringToT2CharString(cls, string): return T2CharString(program=stringToProgram(string), private=PrivateDict())
'specific language, permissions and limitations governing your ' 'use of this Font Software.') LICENSE_URL = 'http://scripts.sil.org/OFL' FSTYPE = 0 # Installable embedding UPM = 2048 EMOJI_H_ADV = 2550 EMOJI_V_ADV = 2500 EMOJI_SIZE = 2400 # ASCENT + abs(DESCENT) ABOVE_BASELINE = 0.7451 # ASCENT / EMOJI_H_ADV ASCENT = 1900 DESCENT = -500 UNDERLINE_POSITION = -1244 UNDERLINE_THICKNESS = 131 SPACE_CHARSTRING = T2CharString(program=[EMOJI_H_ADV, 'endchar']) RE_UNICODE = re.compile(r'^u[0-9a-f]{4,5}$', re.IGNORECASE) RE_REVISION = re.compile(r'^[0-9]{1,3}\.[0-9]{3}$') # The value of the viewBox attribute is a list of four numbers # min-x, min-y, width and height, separated by whitespace and/or a comma RE_VIEWBOX = re.compile( r"(<svg.+?)(\s*viewBox=[\"|\']([-\d,. ]+)[\"|\'])(.+?>)", re.DOTALL) VALID_1STCHARS = tuple('_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz') VALID_CHARS = VALID_1STCHARS + tuple('.0123456789') TAG_LAT_LETTR = ('e0061 e0062 e0063 e0064 e0065 e0066 e0067 e0068 e0069 e006a ' 'e006b e006c e006d e006e e006f e0070 e0071 e0072 e0073 e0074 ' 'e0075 e0076 e0077 e0078 e0079 e007a e007f').split()
def getCharString(self, private=None, globalSubrs=None): program = self._program + ["endchar"] charString = T2CharString(program=program, private=private, globalSubrs=globalSubrs) return charString
def buildMMCFFTables(baseFont, privateDictList, cff2GlyphList, varModel): # Build the blended PrivateDicts. opCodeDict = buildOpcodeDict(privateDictOperators) blendedPD = privateDictList[0] if baseFont.isCID: numFDArray = len(baseFont.topDict.FDArray) else: numFDArray = 1 for fdIndex in range(numFDArray): if baseFont.isCID: pd = baseFont.topDict.FDArray[fdIndex] blendedPD = privateDictList[fdIndex] else: pd = baseFont.topDict.Private blendedPD = privateDictList[0] for key in blendedPD.keys(): argType = opCodeDict[key][1] valList = blendedPD[key] if argType == 'delta': # If all masters are the same for each entry in the list, then # save a non-blend version; else save the blend version needsBlend = False for blendList in valList: if pointsDiffer(blendList): needsBlend = True break dataList = [] if needsBlend: prevBlendList = [0] * len(valList[0]) for blendList in valList: # convert blend list from absolute values to relative # values from the previous blend list. relBlendList = [(val - prevBlendList[i]) for i, val in enumerate(blendList)] prevBlendList = blendList deltas = varModel.getDeltas(relBlendList) # For PrivateDict BlueValues, the default font # values are absolute, not relative. deltas[0] = blendList[0] dataList.append(deltas) else: for blendList in valList: dataList.append(blendList[0]) else: if pointsDiffer(valList): dataList = varModel.getDeltas(valList) else: dataList = valList[0] pd.rawDict[key] = dataList # Now update all the charstrings. fontGlyphList = baseFont.ttFont.getGlyphOrder() for glyphName in fontGlyphList: gid = baseFont.charStrings.charStrings[glyphName] t2CharString = baseFont.charStringIndex[gid] cff2GlyphData = cff2GlyphList[glyphName] t2CharString.decompile() newProgram = [] for op, pointList in cff2GlyphData.opList: blendStack = appendBlendOp(op, pointList, varModel) newProgram.extend(blendStack) t2CharString = T2CharString(private=t2CharString.private, globalSubrs=t2CharString.globalSubrs) baseFont.charStringIndex[gid] = t2CharString t2CharString.program = newProgram t2CharString.private.defaultWidthX = 0 t2CharString.private.nominalWidthX = 0 t2CharString.compile(isCFF2=True)
def build(instance, opts): font = instance.parent source = font.masters[0] fea, marks = makeFeatures(instance, source) glyphOrder = [] advanceWidths = {} characterMap = {} charStrings = {} source.blueValues = [] source.otherBlues = [] for zone in sorted(source.alignmentZones): pos = zone.position size = zone.size vals = sorted((pos, pos + size)) if pos == 0 or size >= 0: source.blueValues.extend(vals) else: source.otherBlues.extend(vals) fontinfo = f""" FontName {instance.fontName} OrigEmSqUnits {font.upm} DominantV {source.verticalStems} DominantH {source.horizontalStems} BaselineOvershoot {source.blueValues[0]} BaselineYCoord {source.blueValues[1]} LcHeight {source.blueValues[2]} LcOvershoot {source.blueValues[3] - source.blueValues[2]} CapHeight {source.blueValues[4]} CapOvershoot {source.blueValues[5] - source.blueValues[4]} AscenderHeight {source.blueValues[6]} AscenderOvershoot {source.blueValues[7] - source.blueValues[6]} Baseline5 {source.otherBlues[1]} Baseline5Overshoot {source.otherBlues[0] - source.otherBlues[1]} FlexOK true BlueFuzz 1 """ layerSet = {g.name: g.layers[source.id] for g in font.glyphs} for glyph in font.glyphs: if not glyph.export: continue name = glyph.name glyphOrder.append(name) for code in glyph.unicodes: characterMap[int(code, 16)] = name layer = glyph.layers[source.id] width = 0 if name in marks else layer.width # Draw glyph and remove overlaps. path = Path() layer.draw(DecomposePathPen(path, layerSet=layerSet)) path.simplify(fix_winding=True, keep_starting_points=True) # Autohint. pen = BezPen(None, True) path.draw(pen) bez = "\n".join(["% " + name, "sc", *pen.bez, "ed", ""]) hinted = hint_bez_glyph(fontinfo, bez) program = [width] + convertBezToT2(hinted) # Build CharString. charStrings[name] = T2CharString(program=program) advanceWidths[name] = width # Make sure .notdef is glyph index 0. glyphOrder.pop(glyphOrder.index(".notdef")) glyphOrder.insert(0, ".notdef") version = float(opts.version) vendor = font.customParameters["vendorID"] names = { "copyright": font.copyright, "familyName": instance.familyName, "styleName": instance.name, "uniqueFontIdentifier": f"{version:.03f};{vendor};{instance.fontName}", "fullName": instance.fullName, "version": f"Version {version:.03f}", "psName": instance.fontName, "manufacturer": font.manufacturer, "designer": font.designer, "description": font.customParameters["description"], "vendorURL": font.manufacturerURL, "designerURL": font.designerURL, "licenseDescription": font.customParameters["license"], "licenseInfoURL": font.customParameters["licenseURL"], "sampleText": font.customParameters["sampleText"], } date = int(font.date.timestamp()) - epoch_diff fb = FontBuilder(font.upm, isTTF=False) fb.updateHead(fontRevision=version, created=date, modified=date) fb.setupGlyphOrder(glyphOrder) fb.setupCharacterMap(characterMap) fb.setupNameTable(names, mac=False) fb.setupHorizontalHeader( ascent=source.ascender, descent=source.descender, lineGap=source.customParameters["typoLineGap"], ) privateDict = { "BlueValues": source.blueValues, "OtherBlues": source.otherBlues, "StemSnapH": source.horizontalStems, "StemSnapV": source.verticalStems, "StdHW": source.horizontalStems[0], "StdVW": source.verticalStems[0], } fontInfo = { "FullName": names["fullName"], "Notice": names["copyright"].replace("©", "\(c\)"), "version": f"{version:07.03f}", "Weight": instance.name, } fb.setupCFF(names["psName"], fontInfo, charStrings, privateDict) metrics = {} for i, (name, width) in enumerate(advanceWidths.items()): bounds = charStrings[name].calcBounds(None) or [0] metrics[name] = (width, bounds[0]) fb.setupHorizontalMetrics(metrics) codePages = [ CODEPAGE_RANGES[v] for v in font.customParameters["codePageRanges"] ] fb.setupOS2( version=4, sTypoAscender=source.ascender, sTypoDescender=source.descender, sTypoLineGap=source.customParameters["typoLineGap"], usWinAscent=source.ascender, usWinDescent=-source.descender, sxHeight=source.xHeight, sCapHeight=source.capHeight, achVendID=vendor, fsType=calcBits(font.customParameters["fsType"], 0, 16), fsSelection=calcFsSelection(instance), ulUnicodeRange1=calcBits(font.customParameters["unicodeRanges"], 0, 32), ulCodePageRange1=calcBits(codePages, 0, 32), ) ut = int(source.customParameters["underlineThickness"]) up = int(source.customParameters["underlinePosition"]) fb.setupPost(underlineThickness=ut, underlinePosition=up + ut // 2) meta = newTable("meta") meta.data = {"dlng": "Arab", "slng": "Arab"} fb.font["meta"] = meta fb.addOpenTypeFeatures(fea) return fb.font