def test_insertGlyph(self): font = Font(getTestFontPath()) glyph = Glyph() glyph.name = "NewGlyphTest" self.assertEqual(sorted(font.keys()), ["A", "B", "C"]) font.insertGlyph(glyph) self.assertEqual(sorted(font.keys()), ["A", "B", "C", "NewGlyphTest"])
def test_insertGlyph(self): font = Font(getTestFontPath()) glyph = Glyph() glyph.name = "NewGlyphTest" self.assertEqual(sorted(font.keys()), ["A", "B", "C"]) font.insertGlyph(glyph) self.assertEqual(sorted(font.keys()), ["A", "B", "C", "NewGlyphTest"])
def create_font(info_params, glyphs): font = Font() for info_key, info_value in info_params: setattr(font.info, info_key, info_value) for glyph in glyphs: font.insertGlyph(glyph) return font
def test_glyph_dispatcher_inserted(self): font = Font() font.newGlyph("A") glyph = font["A"] pen = glyph.getPointPen() pen.beginPath() pen.addPoint((0, 0), segmentType="line") pen.addPoint((0, 100), segmentType="line") pen.addPoint((100, 100), segmentType="line") pen.addPoint((100, 0), segmentType="line") pen.endPath() contour = glyph[0] component = Component() glyph.appendComponent(component) anchor = Anchor() glyph.appendAnchor(anchor) guideline = Guideline() glyph.appendGuideline(guideline) sourceGlyph = glyph newFont = Font() insertedGlyph = newFont.insertGlyph(sourceGlyph) contour = insertedGlyph[0] self.assertTrue(contour.getParent(), insertedGlyph) self.assertTrue(contour.dispatcher, newFont.dispatcher) component = insertedGlyph.components[0] self.assertTrue(component.getParent(), insertedGlyph) self.assertTrue(component.dispatcher, newFont.dispatcher) anchor = insertedGlyph.anchors[0] self.assertTrue(anchor.getParent(), insertedGlyph) self.assertTrue(anchor.dispatcher, newFont.dispatcher) guideline = insertedGlyph.guidelines[0] self.assertTrue(guideline.getParent(), insertedGlyph) self.assertTrue(guideline.dispatcher, newFont.dispatcher)
def combineGlyphs(path1, path2, destPath): """ Combines the glyphs of two UFOs and saves result to a new ufo. This only combines glyphs, so the first UFO path should be the one that you want all the metadata from. """ ufo1 = Font(path1) ufo2 = Font(path2) added_glyphs = [] for glyph in ufo2: if glyph.name not in ufo1: print "Inserting %s" % glyph.name ufo1.insertGlyph(glyph) added_glyphs.append(glyph.name) ufo1.save(destPath)
def test_glyph_dispatcher_inserted(self): font = Font() font.newGlyph("A") glyph = font["A"] pen = glyph.getPointPen() pen.beginPath() pen.addPoint((0, 0), segmentType="line") pen.addPoint((0, 100), segmentType="line") pen.addPoint((100, 100), segmentType="line") pen.addPoint((100, 0), segmentType="line") pen.endPath() contour = glyph[0] component = Component() glyph.appendComponent(component) anchor = Anchor() glyph.appendAnchor(anchor) guideline = Guideline() glyph.appendGuideline(guideline) sourceGlyph = glyph newFont = Font() insertedGlyph = newFont.insertGlyph(sourceGlyph) contour = insertedGlyph[0] self.assertTrue(contour.getParent(), insertedGlyph) self.assertTrue(contour.dispatcher, newFont.dispatcher) component = insertedGlyph.components[0] self.assertTrue(component.getParent(), insertedGlyph) self.assertTrue(component.dispatcher, newFont.dispatcher) anchor = insertedGlyph.anchors[0] self.assertTrue(anchor.getParent(), insertedGlyph) self.assertTrue(anchor.dispatcher, newFont.dispatcher) guideline = insertedGlyph.guidelines[0] self.assertTrue(guideline.getParent(), insertedGlyph) self.assertTrue(guideline.dispatcher, newFont.dispatcher)
def combineGlyphs(path1, path2, destPath): """ Combines the glyphs of two UFOs and saves result to a new ufo. This only combines glyphs, so the first UFO path should be the one that you want all the metadata from. """ ufo1 = Font(path1) ufo2 = Font(path2) added_glyphs = [] for glyph in ufo2: if glyph.name not in ufo1: print "Inserting %s" % glyph.name ufo1.insertGlyph(glyph) added_glyphs.append(glyph.name) else: print "Skipping %s" % glyph.name ufo1.save(destPath) print "Combined UFO saved at %s" % destPath
verbose=False) print("Reading DesignSpace...") doc.process(makeGlyphs=True, makeKerning=True, makeInfo=True) os.remove(tmpDesignSpace) # clean up # update the instances with the source fonts # print str(instances) + ' instances! ' for instance in instances: fileName = os.path.basename(instance["fileName"]) source_path = os.path.join(src_dir, fileName) instance_path = os.path.join(instance_dir, fileName) source_font = Font(source_path) instance_font = Font(instance_path) # insert the source glyphs in the instance font for glyph in source_font: instance_font.insertGlyph(glyph) master_path = os.path.join(master_dir, fileName) instance_font.save(master_path) designSpace = "sources/Amstelvar-NewSpaceNames/Italic/Amstelvar-Italic-010.designspace" sources = [ dict(path="master_ufo/Amstelvar-Italic.ufo", name="Amstelvar-Italic.ufo", location=dict(wght=400, wdth=100, opsz=14, GRAD=0), styleName="Regular", familyName=familyName, copyInfo=True), ## Main dict(path="master_ufo/Amstelvar-Italic-GRAD-1.ufo", name="Amstelvar-Italic-GRAD-1.ufo",
def merge(args): arabic = Font(args.arabicfile) to_ufo_propagate_font_anchors(None, arabic) latin = Font(args.latinfile) addPlaceHolders(arabic) unicodes = [] for glyph in arabic: unicodes.extend(glyph.unicodes) for name in latin.glyphOrder: if name in ("space", "nbspace", "CR", "NULL", ".notdef"): continue glyph = latin[name] assert glyph.name not in arabic, glyph.name assert glyph.unicodes not in unicodes, glyph.unicodes # Strip anchors from f_ ligatures, there are broken. # See https://github.com/googlei18n/glyphsLib/issues/313 if name.startswith("f_"): glyph.anchors = {} arabic.insertGlyph(glyph) # Copy kerning and groups. arabic.groups.update(latin.groups) arabic.kerning.update(latin.kerning) for attr in ("xHeight", "capHeight"): value = getattr(latin.info, attr) if value is not None: setattr(arabic.info, attr, getattr(latin.info, attr)) # Merge Arabic and Latin features, making sure languagesystem statements # come first. langsys = [] statements = [] for font in (arabic, latin): featurefile = UnicodeIO(tounicode(font.features.text)) fea = parser.Parser(featurefile, []).parse() langsys += [ s for s in fea.statements if isinstance(s, ast.LanguageSystemStatement) ] statements += [ s for s in fea.statements if not isinstance(s, ast.LanguageSystemStatement) ] # Drop GDEF table, we want to generate one based on final features. statements = [s for s in statements if not isinstance(s, ast.TableBlock)] # Make sure DFLT is the first. langsys = sorted(langsys, key=attrgetter("script")) fea.statements = langsys + statements arabic.features.text = fea.asFea() glyphOrder = arabic.glyphOrder + latin.glyphOrder # Make sure we have a fixed glyph order by using the original Arabic and # Latin glyph order, not whatever we end up with after adding glyphs. arabic.glyphOrder = sorted(arabic.glyphOrder, key=glyphOrder.index) # Set metadata arabic.info.versionMajor, arabic.info.versionMinor = map( int, args.version.split(".")) arabic.info.copyright = u"Copyright © 2015-%s The Reem Kufi Project Authors." % datetime.now( ).year return arabic
def merge(args): """Merges Arabic and Latin fonts together, and messages the combined font a bit. Returns the combined font.""" ufo = Font(args.arabicfile) to_ufo_propagate_font_anchors(None, ufo) latin = Font(args.latinfile) ufo.lib[POSTSCRIPT_NAMES] = {} # Save original glyph order, used below. glyphOrder = ufo.glyphOrder + latin.glyphOrder # Generate production glyph names for Arabic glyphs, in case it differs # from working names. This will be used by ufo2ft to set the final glyph # names in the font file. for glyph in ufo: if glyph.unicode is not None: if glyph.unicode < 0xffff: postName = "uni%04X" % glyph.unicode else: postName = "u%06X" % glyph.unicode if postName != glyph.name: ufo.lib[POSTSCRIPT_NAMES][glyph.name] = postName # Merge Arabic and Latin features, making sure languagesystem statements # come first. features = ufo.features langsys = [] statements = [] for font in (ufo, latin): featurefile = os.path.join(font.path, "features.fea") fea = parser.Parser(featurefile, font.glyphOrder).parse() langsys += [ s for s in fea.statements if isinstance(s, ast.LanguageSystemStatement) ] statements += [ s for s in fea.statements if not isinstance(s, ast.LanguageSystemStatement) ] # We will regenerate kern, mark and mkmk features, and aalt is useless. statements = [ s for s in statements if getattr(s, "name", None) not in ("aalt", "kern", "mark", "mkmk") ] # These will be regenerated as well statements = [ s for s in statements if not isinstance(s, ast.MarkClassDefinition) ] # Drop tables in fea, we don’t want them. statements = [s for s in statements if not isinstance(s, ast.TableBlock)] # Make sure DFLT is the first. langsys = sorted(langsys, key=attrgetter("script")) fea.statements = langsys + statements features.text = fea.asFea() features.text += generateStyleSets(ufo) # Source Sans Pro has different advance widths for space and NBSP # glyphs, fix it. latin["nbspace"].width = latin["space"].width # Set Latin production names ufo.lib[POSTSCRIPT_NAMES].update(latin.lib[POSTSCRIPT_NAMES]) # Copy Latin glyphs. for name in latin.glyphOrder: glyph = latin[name] # Remove anchors from spacing marks, otherwise ufo2ft will give them # mark glyph class which will cause HarfBuzz to zero their width. if glyph.unicode and unicodedata.category(unichr( glyph.unicode)) in ("Sk", "Lm"): for anchor in glyph.anchors: glyph.removeAnchor(anchor) # Add Arabic anchors to the dotted circle, we use an offset of 100 # units because the Latin anchors are too close to the glyph. offset = 100 if glyph.unicode == 0x25CC: for anchor in glyph.anchors: if anchor.name == "aboveLC": glyph.appendAnchor( dict(name="markAbove", x=anchor.x, y=anchor.y + offset)) glyph.appendAnchor( dict(name="hamzaAbove", x=anchor.x, y=anchor.y + offset)) if anchor.name == "belowLC": glyph.appendAnchor( dict(name="markBelow", x=anchor.x, y=anchor.y - offset)) glyph.appendAnchor( dict(name="hamzaBelow", x=anchor.x, y=anchor.y - offset)) # Break loudly if we have duplicated glyph in Latin and Arabic. # TODO should check duplicated Unicode values as well assert glyph.name not in ufo, glyph.name ufo.insertGlyph(glyph) # Copy kerning and groups. ufo.groups.update(latin.groups) ufo.kerning.update(latin.kerning) # We don’t set these in the Arabic font, so we just copy the Latin’s. for attr in ("xHeight", "capHeight"): value = getattr(latin.info, attr) if value is not None: setattr(ufo.info, attr, getattr(latin.info, attr)) # Make sure we don’t have glyphs with the same unicode value unicodes = [] for glyph in ufo: unicodes.extend(glyph.unicodes) duplicates = set([u for u in unicodes if unicodes.count(u) > 1]) assert len(duplicates) == 0, "Duplicate unicodes: %s " % ( ["%04X" % d for d in duplicates]) # Make sure we have a fixed glyph order by using the original Arabic and # Latin glyph order, not whatever we end up with after adding glyphs. ufo.glyphOrder = sorted(ufo.glyphOrder, key=glyphOrder.index) return ufo
scripts[script].append(lang) return OrderedDict((script, tuple(langs)) for script,langs in scripts.items()) if __name__ == '__main__': from defcon import Font font = Font(path=sys.argv[2]) for mergePath in sys.argv[3:]: merge = Font(path=mergePath) # merge glyphs for name in merge.keys(): glyph = merge[name] # had an error related to anchors, but I don't need anchors here glyph.anchors = [] font.insertGlyph(glyph, name=name) # merge groups font.groups.update(merge.groups) # merge kerning font.kerning.update(merge.kerning) # scripts = {'arab': ('dflt', 'ARA ', 'URD ', 'FAR '), 'latn': ('dflt', 'TRK ')} # scripts = scriptsFromFea(sys.argv[1]) # scripts = {'arab': ('dflt', ), 'latn': ('dflt', )} scripts = { 'arab': ('dflt', 'ARA ', 'URD ', 'FAR ') , 'latn': ('dflt', 'AZE', 'CRT', 'KAZ', 'MOL', 'ROM', 'TAT', 'TRK' ) }
def merge(args): """Merges Arabic and Latin fonts together, and messages the combined font a bit. Returns the combined font.""" ufo = Font(args.arabicfile) propagate_font_anchors(ufo) latin = Font(args.latinfile) # Parse the GlyphOrderAndAliasDB file for Unicode values and production # glyph names of the Latin glyphs. goadb = GOADBParser( os.path.dirname(args.latinfile) + "/../GlyphOrderAndAliasDB") ufo.lib[POSTSCRIPT_NAMES] = {} # Save original glyph order, used below. glyphOrder = ufo.glyphOrder + latin.glyphOrder # Generate production glyph names for Arabic glyphs, in case it differs # from working names. This will be used by ufo2ft to set the final glyph # names in the font file. for glyph in ufo: if glyph.unicode is not None: if glyph.unicode < 0xffff: postName = "uni%04X" % glyph.unicode else: postName = "u%06X" % glyph.unicode if postName != glyph.name: ufo.lib[POSTSCRIPT_NAMES][glyph.name] = postName # Populate the font’s feature text, we keep our main feature file out of # the UFO to share it between the fonts. features = ufo.features with open(args.feature_file) as feafile: fea = feafile.read() # Set Latin language system, ufo2ft will use it when generating kern # feature. features.text += fea.replace("#{languagesystems}", "languagesystem latn dflt;") features.text += generateStyleSets(ufo) for glyph in latin: if glyph.name in goadb.encodings: glyph.unicode = goadb.encodings[glyph.name] # Source Sans Pro has different advance widths for space and NBSP # glyphs, fix it. latin["nbspace"].width = latin["space"].width glyphs, components = collectGlyphs(latin, args.latin_subset) counter = Counter(components) uniqueComponents = set() for name in counter: if name not in glyphs: latin[name].unicode = None if counter[name] == 1: uniqueComponents.add(name) else: glyphs.add(name) # Set Latin production names ufo.lib[POSTSCRIPT_NAMES].update(goadb.names) # Copy Latin glyphs. for name in glyphs: glyph = latin[name] for component in glyph.components: if component.baseGlyph in uniqueComponents: glyph.decomposeComponent(component) # Remove anchors from spacing marks, otherwise ufo2ft will give them # mark glyph class which will cause HarfBuzz to zero their width. if glyph.unicode and unicodedata.category(unichr( glyph.unicode)) in ("Sk", "Lm"): for anchor in glyph.anchors: glyph.removeAnchor(anchor) # Add Arabic anchors to the dotted circle, we use an offset of 100 # units because the Latin anchors are too close to the glyph. offset = 100 if glyph.unicode == 0x25CC: for anchor in glyph.anchors: if anchor.name == "aboveLC": glyph.appendAnchor( dict(name="markAbove", x=anchor.x, y=anchor.y + offset)) glyph.appendAnchor( dict(name="hamzaAbove", x=anchor.x, y=anchor.y + offset)) if anchor.name == "belowLC": glyph.appendAnchor( dict(name="markBelow", x=anchor.x, y=anchor.y - offset)) glyph.appendAnchor( dict(name="hamzaBelow", x=anchor.x, y=anchor.y - offset)) # Break loudly if we have duplicated glyph in Latin and Arabic. # TODO should check duplicated Unicode values as well assert glyph.name not in ufo, glyph.name ufo.insertGlyph(glyph) # Copy kerning and groups. ufo.groups.update(latin.groups) ufo.kerning.update(latin.kerning) # We don’t set these in the Arabic font, so we just copy the Latin’s. for attr in ("xHeight", "capHeight"): value = getattr(latin.info, attr) if value is not None: setattr(ufo.info, attr, getattr(latin.info, attr)) # MutatorMath does not like multiple unicodes and will drop it entirely, # turning the glyph unencoded: # https://github.com/LettError/MutatorMath/issues/85 for glyph in ufo: assert not " " in glyph.unicodes # Make sure we don’t have glyphs with the same unicode value unicodes = [] for glyph in ufo: unicodes.extend(glyph.unicodes) duplicates = set([u for u in unicodes if unicodes.count(u) > 1]) assert len(duplicates) == 0, "Duplicate unicodes: %s " % ( ["%04X" % d for d in duplicates]) # Make sure we have a fixed glyph order by using the original Arabic and # Latin glyph order, not whatever we end up with after adding glyphs. ufo.glyphOrder = sorted(ufo.glyphOrder, key=glyphOrder.index) return ufo
class MinifyUFO(): def __init__(self, source, template=None, config=None): self.GDB = GlyphsLib(False, config) # buildFea set to false self.srcUFO = self.sourceFont(source) self.UFO = Font() self.layers = {} if template is None: template = os.path.join(os.path.dirname(__file__), 'database/template.ufo') self.templateUFO = Font(template) def sourceFont(self, source): if source is None: raise Exception('font-file argument missing') font_type = source.split('.')[-1].lower() if font_type == "ufo": src = Font(source) elif font_type in ["otf", "ttf", "woff", "woff2", "ttx", "pfa"]: import extractor src = Font() extractor.extractUFO(source, src) else: raise Exception('font-file in format ' + font_type + " is not supported") return src def build(self): self.copyFontInfo() self.createGlyphs() self.createAnchors() self.sortGlyphs() return self.UFO def getLayer(self, layer): layerName = "BG_layer_" + str(layer) if layerName not in self.layers.keys(): newLayer = self.UFO.layers.newLayer(layerName) newLayer.color = Color("0,1,0,1") self.layers.update({layerName: newLayer}) return self.layers[layerName] def copyFontInfo(self): data = self.srcUFO.getDataForSerialization() self.UFO.setDataFromSerialization({'info': data['info']}) def createGlyphs(self): self.UFO.newGlyph('.notdef') missing = '' for glf in self.GDB.Master2Search: glfUnicode = int(self.GDB.Master2Unicode[glf], 16) print('') layer = 0 stopAt = 'arAlef.fina.la' glfSrc = self.GDB.Master2Search[glf].split(',') if glf == stopAt: m = 1 log = (glf + ' ' * 50)[0:20] if glf in self.GDB.MAPPING: try: mgName = [self.GDB.MAPPING[glf]] glyph = Glyph() glyph.copyDataFromGlyph(self.srcUFO[mgName[0]]) glyph.name = glf if layer == 0: glyph.unicode = glfUnicode glyph.unicodes = [glfUnicode] else: glyph.unicode = None glyph.anchors = [] glyph.decomposeAllComponents() if layer > 0: currentLayer = self.getLayer(layer) currentLayer.insertGlyph(glyph) else: self.UFO.insertGlyph(glyph) layer += 1 # print(g + ' found :)' + ' L ' + str(layer)) gLog = log + (mgName[0] + ' ' * 50)[0:20] print(gLog + '[' + str(layer) + '] *') except: pass for g in glfSrc: mgName = None gCode = None gLog = log + (g + ' ' * 50)[0:20] try: try: gCode = self.GDB.Prod2Decimal[g] mgName = self.srcUFO.unicodeData[gCode] except: try: mgName = [g] except: pass glyph = Glyph() glyph.copyDataFromGlyph(self.srcUFO[mgName[0]]) glyph.name = glf if layer == 0: glyph.unicode = glfUnicode glyph.unicodes = [glfUnicode] else: glyph.unicode = None glyph.anchors = [] glyph.decomposeAllComponents() if layer > 0: currentLayer = self.getLayer(layer) currentLayer.insertGlyph(glyph) else: self.UFO.insertGlyph(glyph) layer += 1 print(gLog + '[' + str(layer) + ']') except: print(gLog + '[ ]') glyph = None if layer == 0: self.UFO.newGlyph(glf) missing += log + "\n" if len(missing) > 1: print('\n') print('=' * 60) print('Missing glyphes ' + str(len(missing.splitlines()))) print('=' * 60) print(missing) print('=' * 60) def createAnchors(self): factor = self.getFactorOfUPM() for g in self.UFO: try: sampleGlyph = self.templateUFO[g.name] g.anchors = copy.deepcopy(sampleGlyph.anchors) for point in g.anchors: point.x *= factor point.y *= factor for point in sampleGlyph.anchors: xVal = point.x * factor yVal = point.y * factor c = Contour() c.addPoint((xVal, yVal), name=point.name, segmentType="move") g.appendContour(c) except: pass def getFactorOfUPM(self): templateUPM = self.templateUFO.info.unitsPerEm sourceFontUPM = self.UFO.info.unitsPerEm if sourceFontUPM > templateUPM: factor = float(templateUPM) / float(sourceFontUPM) elif templateUPM > sourceFontUPM: factor = float(sourceFontUPM) /float(templateUPM) else: factor = 1 return factor def sortGlyphs(self): self.UFO.lib['public.glyphOrder'].sort()
def merge(args): """Merges Arabic and Latin fonts together, and messages the combined font a bit. Returns the combined font.""" ufo = Font(args.arabicfile) to_ufo_propagate_font_anchors(None, ufo) latin = Font(args.latinfile) ufo.lib[POSTSCRIPT_NAMES] = {} # Save original glyph order, used below. glyphOrder = ufo.glyphOrder + latin.glyphOrder # Generate production glyph names for Arabic glyphs, in case it differs # from working names. This will be used by ufo2ft to set the final glyph # names in the font file. for glyph in ufo: if glyph.unicode is not None: if glyph.unicode < 0xffff: postName = "uni%04X" % glyph.unicode else: postName = "u%06X" % glyph.unicode if postName != glyph.name: ufo.lib[POSTSCRIPT_NAMES][glyph.name] = postName # Merge Arabic and Latin features, making sure languagesystem statements # come first. features = ufo.features langsys = [] statements = [] for font in (ufo, latin): featurefile = os.path.join(font.path, "features.fea") fea = parser.Parser(featurefile, font.glyphOrder).parse() langsys += [s for s in fea.statements if isinstance(s, ast.LanguageSystemStatement)] statements += [s for s in fea.statements if not isinstance(s, ast.LanguageSystemStatement)] # We will regenerate kern, mark and mkmk features, and aalt is useless. statements = [s for s in statements if getattr(s, "name", None) not in ("aalt", "kern", "mark", "mkmk")] # These will be regenerated as well statements = [s for s in statements if not isinstance(s, ast.MarkClassDefinition)] # Drop tables in fea, we don’t want them. statements = [s for s in statements if not isinstance(s, ast.TableBlock)] # Make sure DFLT is the first. langsys = sorted(langsys, key=attrgetter("script")) fea.statements = langsys + statements features.text = fea.asFea() features.text += generateStyleSets(ufo) # Source Sans Pro has different advance widths for space and NBSP # glyphs, fix it. latin["nbspace"].width = latin["space"].width # Set Latin production names ufo.lib[POSTSCRIPT_NAMES].update(latin.lib[POSTSCRIPT_NAMES]) # Copy Latin glyphs. for name in latin.glyphOrder: glyph = latin[name] # Remove anchors from spacing marks, otherwise ufo2ft will give them # mark glyph class which will cause HarfBuzz to zero their width. if glyph.unicode and unicodedata.category(unichr(glyph.unicode)) in ("Sk", "Lm"): for anchor in glyph.anchors: glyph.removeAnchor(anchor) # Add Arabic anchors to the dotted circle, we use an offset of 100 # units because the Latin anchors are too close to the glyph. offset = 100 if glyph.unicode == 0x25CC: for anchor in glyph.anchors: if anchor.name == "aboveLC": glyph.appendAnchor(dict(name="markAbove", x=anchor.x, y=anchor.y + offset)) glyph.appendAnchor(dict(name="hamzaAbove", x=anchor.x, y=anchor.y + offset)) if anchor.name == "belowLC": glyph.appendAnchor(dict(name="markBelow", x=anchor.x, y=anchor.y - offset)) glyph.appendAnchor(dict(name="hamzaBelow", x=anchor.x, y=anchor.y - offset)) # Break loudly if we have duplicated glyph in Latin and Arabic. # TODO should check duplicated Unicode values as well assert glyph.name not in ufo, glyph.name ufo.insertGlyph(glyph) # Copy kerning and groups. ufo.groups.update(latin.groups) ufo.kerning.update(latin.kerning) # We don’t set these in the Arabic font, so we just copy the Latin’s. for attr in ("xHeight", "capHeight"): value = getattr(latin.info, attr) if value is not None: setattr(ufo.info, attr, getattr(latin.info, attr)) # Make sure we don’t have glyphs with the same unicode value unicodes = [] for glyph in ufo: unicodes.extend(glyph.unicodes) duplicates = set([u for u in unicodes if unicodes.count(u) > 1]) assert len(duplicates) == 0, "Duplicate unicodes: %s " % (["%04X" % d for d in duplicates]) # Make sure we have a fixed glyph order by using the original Arabic and # Latin glyph order, not whatever we end up with after adding glyphs. ufo.glyphOrder = sorted(ufo.glyphOrder, key=glyphOrder.index) return ufo
class MaxifyUFO(): def __init__(self, source, config=None): self.sourcesDir = os.sep.join(source.split(os.sep)[:-1]) self.GDB = GlyphsLib(True, config) self.srcUFO = Font(source) self.UFO = Font() self.transFields = [ "xScale", "xyScale", "yxScale", "yScale", "xOffset", "yOffset" ] def build(self): self.copyFontInfo() self.createGlyphs() self.removeOverlap() self.setFeatures() self.sortGlyphs() return self.UFO def copyFontInfo(self): data = self.srcUFO.getDataForSerialization() self.UFO.setDataFromSerialization({'info': data['info']}) def setFeatures(self): featuresText = self.GDB.fea.fea_main userFeaFile = self.sourcesDir + os.sep + "font.fea" if os.path.isfile(userFeaFile): featuresText += self.GDB.get_file_content(userFeaFile) self.UFO.features.text = featuresText def removeOverlap(self): """Removes overlap by combining overlapping contours. Not really necessary, but some font rendering systems need this.""" manager = BooleanOperationManager() for glyph in self.UFO: contours = list(glyph) glyph.clearContours() try: manager.union(contours, glyph.getPointPen()) except: m = 1 def createGlyphs(self): self.copyFundamentals() self.createWhiteSpacesGlyphs() subs = self.getSubsets() for gName, pgNames in subs.items(): gModz = {} if gName == 'uni062B.medi.yeh': m = 1 if gName in self.GDB.IRREGULAR.keys(): gModz = self.GDB.IRREGULAR[gName] try: baseGlyph = self.UFO[pgNames[0]] masterGlyph = self.srcUFO[pgNames[0]] if not gName in self.UFO.keys(): glyph = self.addGlyph(gName, baseGlyph) if len(pgNames) == 1: pass else: for partName in pgNames[1:]: pModz = [] if partName in gModz.keys(): pModz = gModz[partName] partGlyph = self.UFO[partName] masterPartGlyph = self.srcUFO[partName] # add parts like Dot, small V, Hamza on the Base Glyph partAnchors = [ a.name.replace("_", "", 1) for a in masterPartGlyph.anchors if a.name.startswith("_") ] baseAnchors = [ a.name for a in masterGlyph.anchors if not a.name.startswith("_") ] anchorName = set(baseAnchors).intersection(partAnchors) assert len(anchorName) > 0, (pgNames[0], partName, partAnchors, baseAnchors) anchorName = list(anchorName)[0] partAnchor = [ a for a in masterPartGlyph.anchors if a.name == "_" + anchorName ][0] baseAnchor = [ a for a in masterGlyph.anchors if a.name == anchorName ][0] xoff = baseAnchor.x - partAnchor.x yoff = baseAnchor.y - partAnchor.y self.addComponent(glyph, partName, xoff, yoff, pModz) self.updateAnchors(glyph, masterPartGlyph, xoff, yoff) del baseAnchor, baseAnchors, a, xoff, yoff, partGlyph, anchorName, partName except: pass def getSubsets(self): subs = {} for gName, pgNames in self.GDB.Prod2Comp.items(): rep = [] for el in pgNames: rep.append(self.getUniName(el)) subs[gName] = rep return subs def getUniName(self, name): if name in self.GDB.Master2Prod: return self.GDB.Master2Prod[name] else: return name def addGlyph(self, gName, baseGlyph): glyph = self.UFO.newGlyph(gName) if gName in self.GDB.Prod2Decimal: glyph.unicode = self.GDB.Prod2Decimal[gName] glyph.width = baseGlyph.width glyph.leftMargin = baseGlyph.leftMargin glyph.rightMargin = baseGlyph.rightMargin self.addComponent(glyph, baseGlyph.name) self.addAnchors(glyph, baseGlyph) return glyph def copyFundamentals(self): for g in self.srcUFO: if g.name in self.GDB.Master2Unicode: gName = self.GDB.Master2Prod[g.name] if gName == "uni0646.iso": m = 1 # print(gName) gUnicode = int(self.GDB.Master2Unicode[g.name], 16) g.name = gName g.unicode = gUnicode ng = copy.deepcopy(g) self.addAnchors(ng, g) self.UFO.insertGlyph(ng) def createWhiteSpacesGlyphs(self): spaceGlyph = self.UFO['space'] try: spaceWidth = self.GDB.CONFIGS['info']['spaceWidth'] spaceGlyph.width = int(spaceWidth) except: print('missing spaceWidth config value') space = spaceGlyph.width em = self.UFO.info.unitsPerEm # no-Break-space 0x00A0 width = int(space / 2) self.createSpaceGlyphe(spaceGlyph, 'nbsp', '00A0', width) # en space width = int(em / 2) self.createSpaceGlyphe(spaceGlyph, 'enspace', '2002', width) # em space self.createSpaceGlyphe(spaceGlyph, 'emspace', '2003', em) # thinspace 0x2009 width = int(space / 5) self.createSpaceGlyphe(spaceGlyph, 'thinspace', '2009', width) # hairspace 0x200A width = int(space / 7) self.createSpaceGlyphe(spaceGlyph, 'hairspace', '200A', width) # ZERO WIDTH SPACE 200B width = int(space / 7) self.createSpaceGlyphe(spaceGlyph, 'hairspace', '200B', 0) # ZWNJ 200C self.createSpaceGlyphe(spaceGlyph, 'zwnj', '200C', 0) # ZWJ 200D self.createSpaceGlyphe(spaceGlyph, 'zwj', '200D', 0) # lrm 200E self.createSpaceGlyphe(spaceGlyph, 'lrm', '200E', 0) # rlm 200F self.createSpaceGlyphe(spaceGlyph, 'rlm', '200F', 0) # ZERO WIDTH NO-BREAK SPACE FEFF width = int(space / 7) self.createSpaceGlyphe(spaceGlyph, 'zwnbsp', 'FEFF', 0) m = 1 def createSpaceGlyphe(self, spGlyph, name, unicode, width): glyph = copy.deepcopy(spGlyph) glyph.width = int(width) glyph.name = name glyph.unicodes = tuple() glyph.unicode = int(unicode, 16) self.UFO.insertGlyph(glyph) def addComponent(self, glyph, name, xoff=0, yoff=0, pModz=[]): component = glyph.instantiateComponent() component.baseGlyph = name component.move((xoff, yoff)) if len(pModz) > 0: trans = component.transformation dicts = [dict(zip(self.transFields, d)) for d in [trans]] ct = dicts[0] for prop in pModz: if prop in ("xOffset", "yOffset"): ct[prop] += float(pModz[prop]) else: ct[prop] = float(pModz[prop]) newProps = [] for pn in self.transFields: newProps.append(ct[pn]) component.transformation = tuple(newProps) glyph.appendComponent(component) def addAnchors(self, glyph, base): anchors = base.anchors glyph.clearAnchors() if glyph.name == 'uniE272': m = 1 markAnchors = [ 'markAbove', 'markBelow', 'markAbove_1', 'markAbove_2', 'markBelow_1', 'markBelow_2', '_markAbove', '_markBelow', '_markAbove_1', '_markAbove_2', '_markBelow_1', '_markBelow_2', '_hamzaAbove', 'hamzaAbove', '_hamzaBelow', 'hamzaBelow' # 'markAboveMark','markBelowMark','_markAboveMark','_markBelowMark' ] if len(anchors): for anchor in anchors: if anchor.name in markAnchors: anc = glyph.instantiateAnchor() anc.x = anchor.x anc.y = anchor.y anc.name = anchor.name glyph.appendAnchor(anc) c = Contour() c.addPoint((anchor.x, anchor.y), name=anchor.name, segmentType="move") glyph.appendContour(c) else: continue def updateAnchors(self, glyph, base, x, y): anchors = base.anchors if glyph.name == 'shaddaKasra': m = 1 if len(anchors): for anchor in anchors: if anchor.name == 'markAbove' or anchor.name == 'markAboveDot' or anchor.name == 'markAboveMark': for ganchor in glyph.anchors: if ganchor.name == "markAbove": ganchor.x = x + anchor.x ganchor.y = y + anchor.y if anchor.name == 'markAbove_2': for ganchor in glyph.anchors: if ganchor.name == "markAbove_2": ganchor.x = x + anchor.x ganchor.y = y + anchor.y if anchor.name == 'markBelow' or anchor.name == 'markBelowDot' or anchor.name == 'markBelowMark': if glyph.name in [ 'uni062C', 'uni062C.fina', 'uni062C.isol', 'uni0686.fina', 'uni0686.isol', 'uni0686' ]: continue for ganchor in glyph.anchors: if ganchor.name == "markBelow": ganchor.x = x + anchor.x ganchor.y = y + anchor.y # shadda with kasra or kasratan, will move above if anchor.name == '_markAboveAlt': for ganchor in glyph.anchors: if ganchor.name == "_markAbove": ganchor.x = x + anchor.x ganchor.y = y + anchor.y else: continue def sortGlyphs(self): self.UFO.lib['public.glyphOrder'].sort()
def merge(args): """Merges Arabic and Latin fonts together, and messages the combined font a bit. Returns the combined font.""" ufo = Font(args.arabicfile) latin = Font(args.latinfile) # Parse the GlyphOrderAndAliasDB file for Unicode values and production # glyph names of the Latin glyphs. goadb = GOADBParser(os.path.dirname(args.latinfile) + "/../GlyphOrderAndAliasDB") ufo.lib[POSTSCRIPT_NAMES] = {} # Generate production glyph names for Arabic glyphs, in case it differs # from working names. This will be used by ufo2ft to set the final glyph # names in the font file. for glyph in ufo: if glyph.unicode is not None: if glyph.unicode < 0xffff: postName = "uni%04X" % glyph.unicode else: postName = "u%06X" % glyph.unicode if postName != glyph.name: ufo.lib[POSTSCRIPT_NAMES][glyph.name] = postName # Populate the font’s feature text, we keep our main feature file out of # the UFO to share it between the fonts. features = ufo.features with open(args.feature_file) as feafile: fea = feafile.read() # Set Latin language system, ufo2ft will use it when generating kern # feature. features.text += fea.replace("#{languagesystems}", "languagesystem latn dflt;") features.text += generateStyleSets(ufo) for glyph in latin: if glyph.name in goadb.encodings: uni = goadb.encodings[glyph.name] # Source Sans Pro has different advance widths for space and NBSP # glyphs, so we drop the later, and map both Unicode characters to # the space glyph. if uni == 0x00A0: # NBSP continue glyph.unicode = uni if uni == 0x0020: # space glyph.unicodes = glyph.unicodes + [0x00A0] glyphs, components = collectGlyphs(latin, args.latin_subset) counter = Counter(components) uniqueComponents = set() for name in counter: if name not in glyphs: latin[name].unicode = None if counter[name] == 1: uniqueComponents.add(name) else: glyphs.add(name) # Set Latin production names ufo.lib[POSTSCRIPT_NAMES].update(goadb.names) # Copy Latin glyphs. for name in glyphs: glyph = latin[name] for component in glyph.components: if component.baseGlyph in uniqueComponents: glyph.decomposeComponent(component) # Remove anchors from spacing marks, otherwise ufo2ft will give them # mark glyph class which will cause HarfBuzz to zero their width. if glyph.unicode and unicodedata.category(unichr(glyph.unicode)) in ("Sk", "Lm"): for anchor in glyph.anchors: glyph.removeAnchor(anchor) # Add Arabic anchors to the dotted circle, we use an offset of 100 # units because the Latin anchors are too close to the glyph. offset = 100 if glyph.unicode == 0x25CC: for anchor in glyph.anchors: if anchor.name == "aboveLC": glyph.appendAnchor(dict(name="markAbove", x=anchor.x, y=anchor.y + offset)) glyph.appendAnchor(dict(name="hamzaAbove", x=anchor.x, y=anchor.y + offset)) if anchor.name == "belowLC": glyph.appendAnchor(dict(name="markBelow", x=anchor.x, y=anchor.y - offset)) glyph.appendAnchor(dict(name="hamzaBelow", x=anchor.x, y=anchor.y - offset)) # Break loudly if we have duplicated glyph in Latin and Arabic. # TODO should check duplicated Unicode values as well assert glyph.name not in ufo, glyph.name ufo.insertGlyph(glyph) # Copy kerning and groups. for group in latin.groups: ufo.groups[group] = latin.groups[group] for kern in latin.kerning: ufo.kerning[kern] = latin.kerning[kern] # We don’t set these in the Arabic font, so we just copy the Latin’s. for attr in ("xHeight", "capHeight"): value = getattr(latin.info, attr) if value is not None: setattr(ufo.info, attr, getattr(latin.info, attr)) return ufo
return OrderedDict( (script, tuple(langs)) for script, langs in scripts.items()) if __name__ == '__main__': from defcon import Font font = Font(path=sys.argv[2]) for mergePath in sys.argv[3:]: merge = Font(path=mergePath) # merge glyphs for name in merge.keys(): glyph = merge[name] # had an error related to anchors, but I don't need anchors here glyph.anchors = [] font.insertGlyph(glyph, name=name) # merge groups font.groups.update(merge.groups) # merge kerning font.kerning.update(merge.kerning) # scripts = {'arab': ('dflt', 'ARA ', 'URD ', 'FAR '), 'latn': ('dflt', 'TRK ')} # scripts = scriptsFromFea(sys.argv[1]) # scripts = {'arab': ('dflt', ), 'latn': ('dflt', )} scripts = { 'arab': ('dflt', 'ARA ', 'URD ', 'FAR '), 'latn': ('dflt', 'AZE', 'CRT', 'KAZ', 'MOL', 'ROM', 'TAT', 'TRK') } kfw = KernFeatureWriterWithHorizontalDirections(font, scripts) print(kfw.write())
def stub(r, rs): if rs.cmd == "rf": #if ufo_path not in rs.watch_soft_mods: os.system(f"robofont -p {stub.codepath}") ufo = Font(str(ufo_path)) if False: return [ DATPen().glyph(ufo[ch]).f(0).align(r), #DATPen().glyph(ufo[ch]).skeleton().s(hsl(0.5, s=1, l=0.3)) ] all_points = [[]] for g in rs.input_history.strokes(lambda g: g.action in ["down", "cmd"]): if g.action == "cmd": if g.position() == glfw.KEY_B: all_points.append([]) elif g.position() == glfw.KEY_C: try: all_points[-1][-1] = all_points[-1][-1][0:-1] all_points[-1][-1].append(all_points[-1][-1][0].offset(1, 1)) #return DATPen().rect(all_points[-1][-1][0].rect(30, 30)) all_points.append([]) except IndexError: pass continue if len(g) > 0: ps = [] for p in g: ps.append(p.position) all_points[-1].append(ps) else: #print(g.items) pass for i, points in enumerate(all_points): for j, path in enumerate(points): for k, pt in enumerate(path): #print(pt.round_to(100)) #npt = pt.round_to(100) #print(npt) all_points[i][j][k] = pt.round_to(10) dps = DATPenSet() for points in all_points: out = DATPenSet() dp = DATPen() for idx, path in enumerate(points): if len(path) < 3: for pidx, pt in enumerate(path): if idx == 0 and pidx == 0: dp.moveTo(pt) else: dp.lineTo(pt) else: #print(path) try: bpp = beziers.path.BezierPath.fromPoints([beziers.point.Point(p.x, p.y) for p in path], error=1000).smooth().addExtremes() cdp = DATPen.from_cbp([bpp]).s(0).sw(15).f(None) cdp.value = cdp.value[0:-1] mv, mvpts = cdp.value[0] if idx > 0: cdp.value[0] = ("lineTo", mvpts) dp.record(cdp) except IndexError: print(path) #dp = DATPen().f(0).record(out).connect() #dp.filter(lambda i, mv, ps: mv not in ["endPath"]) #dp = out.pen().connect() #try: # dp.value[-1] = ("closePath", []) #except IndexError: # pass dps += dp #dps += dp #dps += out.pen() if rs.keylayer == Keylayer.Default: return dps.pen().f(0).s(0).sw(5).color_phototype(r, blur=5, cut=150) else: #print(dps.pen().value[-1]) #print(len(dps.pen().value)) if rs.cmd == "save": for dp in dps: dp.value.append(("closePath", [])) #from pprint import pprint #pprint(dps.pen().value) g = dps.pen().to_glyph() ufo.insertGlyph(g, name=ch) ufo.save() # now the robofont script return [ dps.pen().f(0).s(0).sw(5).color_phototype(r, blur=5, cut=150), dps.f(None).s(hsl(0.9, s=1, l=0.8)).sw(3), dps.pen().skeleton() ]
def merge(args): arabic = Font(args.arabicfile) latin = Font(args.latinfile) addPlaceHolders(arabic) unicodes = parseSubset(args.latin_subset) for glyph in arabic: unicodes.extend(glyph.unicodes) latin_locl = "" for name in latin.glyphOrder: glyph = latin[name] if name in arabic: glyph.unicode = None glyph.name = name + ".latn" latin_locl += "sub %s by %s;" % (name, glyph.name) arabic.insertGlyph(glyph) for attr in ("xHeight", "capHeight"): value = getattr(latin.info, attr) if value is not None: setattr(arabic.info, attr, getattr(latin.info, attr)) arabic.features.text += latin.features.text if latin_locl: arabic.features.text += """ feature locl { lookupflag IgnoreMarks; script latn; %s } locl; """ % latin_locl for ch in [(ord(u'؟'), "question")]: arGlyph = arabic.newGlyph("uni%04X" % ch[0]) arGlyph.unicode = ch[0] enGlyph = arabic[ch[1]] component = Component() component.transformation = tuple(Transform().scale(-1, 1)) component.baseGlyph = enGlyph.name arGlyph.appendComponent(component) arGlyph.leftMargin = enGlyph.rightMargin arGlyph.rightMargin = enGlyph.leftMargin unicodes.append(arGlyph.unicode) arabic.lib[MADA_UNICODES] = unicodes # Set metadata arabic.info.versionMajor, arabic.info.versionMinor = map( int, args.version.split(".")) copyright = u"Copyright © 2015-%s The Reem Kufi Project Authors." % datetime.now( ).year arabic.info.copyright = copyright arabic.info.openTypeNameDesigner = u"Khaled Hosny" arabic.info.openTypeNameLicenseURL = u"http://scripts.sil.org/OFL" arabic.info.openTypeNameLicense = u"This Font Software is licensed under the SIL Open Font License, Version 1.1. This license is available with a FAQ at: http://scripts.sil.org/OFL" arabic.info.openTypeNameDescription = u"Reem Kufi is a Fatimid-style decorative Kufic typeface as seen in the historical mosques of Cairo. Reem Kufi is based on the Kufic designs of the great Arabic calligrapher Mohammed Abdul Qadir who revived this art in the 20th century and formalised its rules." arabic.info.openTypeNameSampleText = u"ريم على القاع بين البان و العلم أحل سفك دمي في الأشهر الحرم" return arabic