def calculate_pathop(pen1, pen2, operation): if USE_SKIA_PATHOPS: p1 = Path() pen1.replay(p1.getPen()) if operation == BooleanOp.Simplify: # ignore pen2 p1.simplify(fix_winding=True, keep_starting_points=True) d0 = RecordingPen() p1.draw(d0) return d0.value if pen2: p2 = Path() pen2.replay(p2.getPen()) builder = OpBuilder(fix_winding=True, keep_starting_points=True) builder.add(p1, PathOp.UNION) if pen2: builder.add(p2, BooleanOp.Skia(operation)) result = builder.resolve() d0 = RecordingPen() result.draw(d0) return d0.value else: bg2 = BooleanGlyph() if pen2: pen2.replay(bg2.getPen()) bg = BooleanGlyph() pen1.replay(bg.getPen()) bg = bg._booleanMath(BooleanOp.BooleanGlyphMethod(operation), bg2) dp = RecordingPen() bg.draw(dp) return dp.value
def removeOverlap(self): from pathops import Path path = Path() self.drawToPen(path.getPen()) path.simplify( fix_winding=True, keep_starting_points=False, ) resultPath = BezierPath() path.draw(resultPath) self.path = resultPath.path
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
def test_strip_collinear_moveTo(): # https://github.com/fonttools/skia-pathops/issues/12 path = Path() path.moveTo( bits2float(0x440b8000), # 558 bits2float(0x0), # 0 ) path.lineTo( bits2float(0x44098000), # 550 bits2float(0x0), # 0 ) path.lineTo( bits2float(0x440c247f), # 560.57 bits2float(0x41daf87e), # 27.3713 ) path.lineTo( bits2float(0x440e247f), # 568.57 bits2float(0x41daf87e), # 27.3713 ) path.close() path.moveTo( bits2float(0x440b0000), # 556 bits2float(0x40e00000), # 7 ) path.lineTo( bits2float(0x440a4000), # 553 bits2float(0x0), # 0 ) path.lineTo( bits2float(0x44049c26), # 530.44 bits2float(0x0), # 0 ) path.lineTo( bits2float(0x44052891), # 532.634 bits2float(0x40e00000), # 7 ) path.close() path.simplify() expected = Path() expected.moveTo( bits2float(0x440b8000), # 558 bits2float(0x0), # 0 ) expected.lineTo( bits2float(0x440e247f), # 568.57 bits2float(0x41daf87e), # 27.3713 ) expected.lineTo( bits2float(0x440c247f), # 560.57 bits2float(0x41daf87e), # 27.3713 ) expected.lineTo( bits2float(0x440a2d02), # 552.703 bits2float(0x40e00000), # 7 ) expected.lineTo( bits2float(0x44052891), # 532.634 bits2float(0x40e00000), # 7 ) expected.lineTo( bits2float(0x44049c26), # 530.44 bits2float(0x0), # 0 ) # expected.lineTo( # bits2float(0x44098000), # 550 # bits2float(0x0), # 0 # ) expected.close() assert list(path) == list(expected)
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
def build(instance, opts): font = instance.parent master = font.masters[0] fea, marks = makeFeatures(instance, master) glyphOrder = [] advanceWidths = {} characterMap = {} charStrings = {} for glyph in font.glyphs: if not glyph.export: continue name = glyph.name glyphOrder.append(name) if glyph.unicode: characterMap[int(glyph.unicode, 16)] = name layer = getLayer(glyph, instance) width = 0 if name in marks else layer.width path = Path() draw(layer, instance, path.getPen()) path.simplify(fix_winding=True, keep_starting_points=True) pen = T2CharStringPen(width, None) path.draw(pen) charStrings[name] = pen.getCharString(optimize=False) advanceWidths[name] = width # XXX glyphOrder.pop(glyphOrder.index(".notdef")) glyphOrder.pop(glyphOrder.index("space")) glyphOrder.insert(0, ".notdef") glyphOrder.insert(1, "space") 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=master.ascender, descent=master.descender, lineGap=master.customParameters["typoLineGap"]) privateDict = { "BlueValues": [], "OtherBlues": [], "StemSnapH": master.horizontalStems, "StemSnapV": master.verticalStems, "StdHW": master.horizontalStems[0], "StdVW": master.verticalStems[0], } for zone in sorted(master.alignmentZones): pos = zone.position size = zone.size vals = privateDict["BlueValues"] if pos == 0 or size >= 0 else privateDict["OtherBlues"] vals.extend(sorted((pos, pos + size))) 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=master.ascender, sTypoDescender=master.descender, sTypoLineGap=master.customParameters["typoLineGap"], usWinAscent=master.ascender, usWinDescent=-master.descender, sxHeight=master.xHeight, sCapHeight=master.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(master.customParameters["underlineThickness"]) up = int(master.customParameters["underlinePosition"]) fb.setupPost(underlineThickness=ut, underlinePosition=up + ut//2) fb.addOpenTypeFeatures(fea) cidinfo = f""" FontName ({names["psName"]}) FamilyName ({names["familyName"]}) Weight ({fontInfo["Weight"]}) version ({fontInfo["version"]}) Notice ({fontInfo["Notice"]}) Registry Adobe Ordering Identity Supplement 0 """ cidmap = f"mergefonts {instance.fontName}\n" \ + "\n".join([f"{i} {n}" for i, n in enumerate(glyphOrder)]) return fb.font, cidinfo, cidmap