Ejemplo n.º 1
0
    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)
Ejemplo n.º 2
0
	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
Ejemplo n.º 3
0
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)
Ejemplo n.º 4
0
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
Ejemplo n.º 5
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)
Ejemplo n.º 6
0
	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
Ejemplo n.º 7
0
 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
Ejemplo n.º 9
0
    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
Ejemplo n.º 10
0
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
Ejemplo n.º 11
0
 def stringToT2CharString(cls, string):
     return T2CharString(program=stringToProgram(string),
                         private=PrivateDict())
Ejemplo n.º 12
0
           '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()
Ejemplo n.º 13
0
 def getCharString(self, private=None, globalSubrs=None):
     program = self._program + ["endchar"]
     charString = T2CharString(program=program,
                               private=private,
                               globalSubrs=globalSubrs)
     return charString
Ejemplo n.º 14
0
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)
Ejemplo n.º 15
0
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