Пример #1
0
    def test_curveTo_no_points(self):
        quadpen = Cu2QuPen(DummyPen(), MAX_ERR)
        quadpen.moveTo((0, 0))

        with self.assertRaisesRegex(AssertionError,
                                    "illegal curve segment point count: 0"):
            quadpen.curveTo()
Пример #2
0
    def test_addComponent(self):
        pen = DummyPen()
        quadpen = Cu2QuPen(pen, MAX_ERR)
        quadpen.addComponent("a", (1, 2, 3, 4, 5.0, 6.0))

        # components are passed through without changes
        self.assertEqual(
            str(pen).splitlines(), [
                "pen.addComponent('a', (1, 2, 3, 4, 5.0, 6.0))",
            ])
Пример #3
0
    def test_curveTo_3_points(self):
        pen = DummyPen()
        quadpen = Cu2QuPen(pen, MAX_ERR)
        quadpen.moveTo((0, 0))
        quadpen.curveTo((1, 1), (2, 2), (3, 3))

        self.assertEqual(
            str(pen).splitlines(), [
                "pen.moveTo((0, 0))",
                "pen.qCurveTo((0.75, 0.75), (2.25, 2.25), (3, 3))",
            ])
Пример #4
0
    def test_curveTo_1_point(self):
        pen = DummyPen()
        quadpen = Cu2QuPen(pen, MAX_ERR)
        quadpen.moveTo((0, 0))
        quadpen.curveTo((1, 1))

        self.assertEqual(
            str(pen).splitlines(), [
                "pen.moveTo((0, 0))",
                "pen.lineTo((1, 1))",
            ])
Пример #5
0
    def test_qCurveTo_more_than_1_point(self):
        pen = DummyPen()
        quadpen = Cu2QuPen(pen, MAX_ERR)
        quadpen.moveTo((0, 0))
        quadpen.qCurveTo((1, 1), (2, 2))

        self.assertEqual(
            str(pen).splitlines(), [
                "pen.moveTo((0, 0))",
                "pen.qCurveTo((1, 1), (2, 2))",
            ])
Пример #6
0
def glyphs_to_quadratic(
        glyphs, max_err=MAX_ERR, reverse_direction=REVERSE_DIRECTION):
    quadGlyphs = {}
    for gname in glyphs.keys():
        glyph = glyphs[gname]
        ttPen = TTGlyphPen(glyphs)
        cu2quPen = Cu2QuPen(ttPen, max_err,
                            reverse_direction=reverse_direction)
        glyph.draw(cu2quPen)
        quadGlyphs[gname] = ttPen.glyph()
    return quadGlyphs
Пример #7
0
    def test_curveTo_more_than_3_points(self):
        # a 'SuperBezier' as described in fontTools.basePen.AbstractPen
        pen = DummyPen()
        quadpen = Cu2QuPen(pen, MAX_ERR)
        quadpen.moveTo((0, 0))
        quadpen.curveTo((1, 1), (2, 2), (3, 3), (4, 4))

        self.assertEqual(
            str(pen).splitlines(), [
                "pen.moveTo((0, 0))",
                "pen.qCurveTo((0.75, 0.75), (1.625, 1.625), (2, 2))",
                "pen.qCurveTo((2.375, 2.375), (3.25, 3.25), (4, 4))",
            ])
Пример #8
0
    def test__check_contour_closed(self):
        msg = "closePath or endPath is required"
        quadpen = Cu2QuPen(DummyPen(), MAX_ERR)
        quadpen.moveTo((0, 0))

        with self.assertRaisesRegex(AssertionError, msg):
            quadpen.moveTo((1, 1))
        with self.assertRaisesRegex(AssertionError, msg):
            quadpen.addComponent("a", (1, 0, 0, 1, 0, 0))

        # it works if contour is closed
        quadpen.closePath()
        quadpen.moveTo((1, 1))
        quadpen.endPath()
        quadpen.addComponent("a", (1, 0, 0, 1, 0, 0))
Пример #9
0
    def test__check_contour_is_open(self):
        msg = "moveTo is required"
        quadpen = Cu2QuPen(DummyPen(), MAX_ERR)

        with self.assertRaisesRegex(AssertionError, msg):
            quadpen.lineTo((0, 0))
        with self.assertRaisesRegex(AssertionError, msg):
            quadpen.qCurveTo((0, 0), (1, 1))
        with self.assertRaisesRegex(AssertionError, msg):
            quadpen.curveTo((0, 0), (1, 1), (2, 2))
        with self.assertRaisesRegex(AssertionError, msg):
            quadpen.closePath()
        with self.assertRaisesRegex(AssertionError, msg):
            quadpen.endPath()

        quadpen.moveTo((0, 0))  # now it works
        quadpen.lineTo((1, 1))
        quadpen.qCurveTo((2, 2), (3, 3))
        quadpen.curveTo((4, 4), (5, 5), (6, 6))
        quadpen.closePath()
Пример #10
0
    def test_ignore_single_points(self):
        pen = DummyPen()
        try:
            logging.captureWarnings(True)
            with CapturingLogHandler("py.warnings", level="WARNING") as log:
                quadpen = Cu2QuPen(pen, MAX_ERR, ignore_single_points=True)
        finally:
            logging.captureWarnings(False)
        quadpen.moveTo((0, 0))
        quadpen.endPath()
        quadpen.moveTo((1, 1))
        quadpen.closePath()

        self.assertGreaterEqual(len(log.records), 1)
        if sys.version_info < (3, 11):
            self.assertIn("ignore_single_points is deprecated",
                          log.records[0].args[0])
        else:
            self.assertIn("ignore_single_points is deprecated",
                          log.records[0].msg)

        # single-point contours were ignored, so the pen commands are empty
        self.assertFalse(pen.commands)

        # redraw without ignoring single points
        quadpen.ignore_single_points = False
        quadpen.moveTo((0, 0))
        quadpen.endPath()
        quadpen.moveTo((1, 1))
        quadpen.closePath()

        self.assertTrue(pen.commands)
        self.assertEqual(
            str(pen).splitlines(), [
                "pen.moveTo((0, 0))", "pen.endPath()", "pen.moveTo((1, 1))",
                "pen.closePath()"
            ])
Пример #11
0
def _save_ttfont(bbf):
    fb = FontBuilder(bbf.info.unitsPerEm, isTTF=True)
    fb.setupGlyphOrder(bbf.lib.glyphOrder)
    bbf._build_maps()
    fb.setupCharacterMap(bbf._unicodemap)
    glyf = {}
    metrics = {}
    for i in [1, 2]:
        for g in bbf:
            if i == 1 and g.components:
                continue
            if i == 2 and not g.components:
                continue
            pen = Cu2QuPen(TTGlyphPen(glyf), bbf.info.unitsPerEm / 1000)
            try:
                g.draw(pen)
                glyf[g.name] = pen.pen.glyph()
            except Exception as e:
                pen = Cu2QuPen(TTGlyphPen(glyf), bbf.info.unitsPerEm / 1000)
                glyf[g.name] = pen.pen.glyph()
                pass
            bounds = g.bounds
            xMin = 0
            if bounds:
                xMin = bounds[0]
            metrics[g.name] = (g.width, xMin)
    versionMajor = bbf.info.versionMajor or 1
    versionMinor = bbf.info.versionMinor or 0
    fb.updateHead(
        fontRevision=versionMajor + versionMinor / 10**len(str(versionMinor)),
        created=_ufo_date_to_opentime(bbf.info.openTypeHeadCreated),
        lowestRecPPEM=bbf.info.openTypeHeadLowestRecPPEM
        or fb.font["head"].lowestRecPPEM,
    )
    fb.setupGlyf(glyf)
    fb.setupHorizontalMetrics(metrics)
    fb.setupHorizontalHeader(
        ascent=int(bbf.info.openTypeHheaAscender or bbf.info.ascender or 0),
        descent=int(bbf.info.openTypeHheaDescender or bbf.info.descender or 0),
    )
    os2 = {
        "sTypoAscender":
        bbf.info.openTypeOS2TypoAscender or bbf.info.ascender or 0,
        "sTypoDescender":
        bbf.info.openTypeOS2TypoDescender or bbf.info.descender or 0,
        "sxHeight":
        bbf.info.xHeight,
        "sCapHeight":
        bbf.info.capHeight,
    }
    for k in [
            "usWidthClass",
            "usWeightClass",
            "sTypoLineGap",
            "usWinAscent",
            "usWinDescent",
            "ySubscriptXSize",
            "ySubscriptYSize",
            "ySubscriptXOffset",
            "ySubscriptYOffset",
            "ySuperscriptXSize",
            "ySuperscriptYSize",
            "ySuperscriptXOffset",
            "ySuperscriptYOffset",
            "yStrikeoutSize",
            "yStrikeoutPosition",
    ]:
        infokey = k
        while not infokey[0].isupper():
            infokey = infokey[1:]
        infokey = "openTypeOS2" + infokey
        if hasattr(bbf.info, infokey):
            if getattr(bbf.info, infokey):
                os2[k] = getattr(bbf.info, infokey)
    fb.setupOS2(**os2)
    # Name tables
    nameStrings = dict(
        familyName=dict(en=bbf.info.familyName),
        styleName=dict(en=bbf.info.styleName),
        fullName=f"{bbf.info.familyName} {bbf.info.styleName}",
        psName=bbf.info.postscriptFontName,
        version="Version " +
        str(bbf.info.versionMajor +
            bbf.info.versionMinor / 10**len(str(bbf.info.versionMinor))),
    )
    if not nameStrings["psName"]:
        del nameStrings["psName"]
    fb.setupNameTable(nameStrings)
    # Kerning
    # Features

    fb.setupPost(
        underlinePosition=(bbf.info.postscriptUnderlinePosition or 0),
        underlineThickness=(bbf.info.postscriptUnderlineThickness or 0),
    )
    return fb.font
Пример #12
0
def build(instance, isTTF, version):
    font = instance.parent
    source = font.masters[0]

    fea, marks = makeFeatures(instance, source)

    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
    """

    characterMap = {}
    glyphs = {}
    metrics = {}
    layerSet = {g.name: g.layers[source.id] for g in font.glyphs}

    if isTTF:
        from fontTools.pens.cu2quPen import Cu2QuPen
        from fontTools.pens.recordingPen import RecordingPen

        for glyph in font.glyphs:
            layer = glyph.layers[source.id]
            pen = RecordingPen()
            layer.draw(pen)
            layer.paths = []
            layer.components = []
            pen.replay(Cu2QuPen(layer.getPen(), 1.0, reverse_direction=True))

    for glyph in font.glyphs:
        if not glyph.export and not isTTF:
            continue
        name = glyph.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

        pen = BoundsPen(layerSet)
        layer.draw(pen)
        metrics[name] = (width, pen.bounds[0] if pen.bounds else 0)

        if isTTF:
            from fontTools.pens.ttGlyphPen import TTGlyphPen

            pen = TTGlyphPen(layerSet)
            if layer.paths:
                # Decompose and remove overlaps.
                path = Path()
                layer.draw(DecomposePathPen(path, layerSet))
                path.simplify(fix_winding=True, keep_starting_points=True)
                path.draw(pen)
            else:
                # Composite-only glyph, no need to decompose.
                layer.draw(FlattenComponentsPen(pen, layerSet))
            glyphs[name] = pen.glyph()
        else:
            from fontTools.pens.t2CharStringPen import T2CharStringPen

            # Draw glyph and remove overlaps.
            path = Path()
            layer.draw(DecomposePathPen(path, layerSet))
            path.simplify(fix_winding=True, keep_starting_points=True)

            # Build CharString.
            pen = T2CharStringPen(width, None)
            path.draw(pen)
            glyphs[name] = pen.getCharString()

    vendor = font.customParameters["vendorID"]
    names = {
        "copyright": font.copyright,
        "familyName": instance.familyName,
        "styleName": instance.name,
        "uniqueFontIdentifier": f"{version};{vendor};{instance.fontName}",
        "fullName": instance.fullName,
        "version": f"Version {version}",
        "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=isTTF)
    fb.updateHead(fontRevision=version, created=date, modified=date)
    fb.setupGlyphOrder(font.glyphOrder)
    fb.setupCharacterMap(characterMap)
    fb.setupNameTable(names, mac=False)
    fb.setupHorizontalHeader(
        ascent=source.ascender,
        descent=source.descender,
        lineGap=source.customParameters["typoLineGap"],
    )

    if isTTF:
        fb.setupGlyf(glyphs)
    else:
        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}",
            "Weight": instance.name,
        }

        fb.setupCFF(names["psName"], fontInfo, glyphs, privateDict)

    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),
    )

    underlineThickness = int(source.customParameters["underlineThickness"])
    underlinePosition = int(source.customParameters["underlinePosition"])
    fb.setupPost(
        keepGlyphNames=False,
        underlineThickness=underlineThickness,
        underlinePosition=underlinePosition + underlineThickness // 2,
    )

    fb.font["meta"] = meta = newTable("meta")
    meta.data = {"dlng": "Arab", "slng": "Arab"}

    fb.addOpenTypeFeatures(fea)

    if isTTF:
        from fontTools.ttLib.tables import ttProgram

        fb.setupDummyDSIG()

        fb.font["gasp"] = gasp = newTable("gasp")
        gasp.gaspRange = {0xFFFF: 15}

        fb.font["prep"] = prep = newTable("prep")
        prep.program = ttProgram.Program()
        assembly = ["PUSHW[]", "511", "SCANCTRL[]", "PUSHB[]", "4", "SCANTYPE[]"]
        prep.program.fromAssembly(assembly)
    else:
        from cffsubr import subroutinize

        subroutinize(fb.font)

    return fb.font