def run_ufolib_import_validation(self): """ ufoLib UFOReader.readLib method validates the lib.plist file :return: (list) list of test failure Result objects """ res = Result(self.testpath) ss = StdStreamer(self.ufopath) if file_exists(self.testpath) is False: res.test_failed = ( False ) # not a mandatory file in UFO spec, test passes if missing ss.stream_result(res) return self.test_fail_list try: # read lib.plist with ufoLib - the ufoLib library performs type validations on values on read ufolib_reader = UFOReader(self.ufopath, validate=True) ufolib_reader.readLib() res.test_failed = False ss.stream_result(res) except Exception as e: res.test_failed = True res.test_long_stdstream_string = ( self.testpath + " failed ufoLib import test with error: " + str(e) ) ss.stream_result(res) self.test_fail_list.append(res) return self.test_fail_list
def extractFontFromVFB( pathOrFile, destination, doGlyphs=True, doInfo=True, doKerning=True, doGroups=True, doFeatures=True, doLib=True, customFunctions=[], ): ufoPath = tempfile.mkdtemp(suffix=".ufo") cmds = [_ufo2vfbLocation, "-64", "-fo", pathOrFile, ufoPath] cmds = subprocess.list2cmdline(cmds) popen = subprocess.Popen(cmds, shell=True) popen.wait() try: # vfb2ufo writes ufo2, and has no update since 2015...so dont get to crazy here... # dont validate as vfb2ufo writes invalid ufos source = UFOReader(ufoPath, validate=False) if doInfo: source.readInfo(destination.info) if doKerning: kerning = source.readKerning() destination.kerning.update(kerning) if doGroups: groups = source.readGroups() destination.groups.update(groups) if doFeatures: features = source.readFeatures() destination.features.text = features if doLib: lib = source.readLib() destination.lib.update(lib) if doGlyphs: glyphSet = source.getGlyphSet() for glyphName in glyphSet.keys(): destination.newGlyph(glyphName) glyph = destination[glyphName] pointPen = glyph.getPointPen() glyphSet.readGlyph( glyphName=glyphName, glyphObject=glyph, pointPen=pointPen ) for function in customFunctions: function(source, destination) finally: shutil.rmtree(ufoPath)
def __attrs_post_init__(self) -> None: if self._path is not None: # if lazy argument is not set, default to lazy=True if path is provided if self._lazy is None: self._lazy = True reader = UFOReader(self._path, validate=self._validate) self.layers = LayerSet.read(reader, lazy=self._lazy) self.data = DataSet.read(reader, lazy=self._lazy) self.images = ImageSet.read(reader, lazy=self._lazy) self.info = Info.read(reader) self.features = Features(reader.readFeatures()) self.groups = reader.readGroups() self.kerning = reader.readKerning() self.lib = reader.readLib() self._fileStructure = reader.fileStructure if self._lazy: # keep the reader around so we can close it when done self._reader = reader
def read(cls, reader: UFOReader, lazy: bool = True) -> "Font": """Instantiates a Font object from a :class:`fontTools.ufoLib.UFOReader`. Args: path: The path to the UFO to load. lazy: If True, load glyphs, data files and images as they are accessed. If False, load everything up front. """ self = cls( layers=LayerSet.read(reader, lazy=lazy), data=DataSet.read(reader, lazy=lazy), images=ImageSet.read(reader, lazy=lazy), info=Info.read(reader), features=Features(reader.readFeatures()), groups=reader.readGroups(), kerning=reader.readKerning(), lib=reader.readLib(), lazy=lazy, ) self._fileStructure = reader.fileStructure if lazy: # keep the reader around so we can close it when done self._reader = reader return self
def main(args=None): from io import open options = parse_args(args) config = getConfig(options.config) svg_file = options.infile # Parse SVG to read the width, height attributes defined in it svgObj = parseSvg(svg_file) width = float(svgObj.attrib['width'].replace("px", " ")) height = float(svgObj.attrib['height'].replace("px", " ")) name = os.path.splitext(os.path.basename(svg_file))[0] ufo_font_path = config['font']['ufo'] # Get the font metadata from UFO reader = UFOReader(ufo_font_path) writer = UFOWriter(ufo_font_path) infoObject = InfoObject() reader.readInfo(infoObject) fontName = config['font']['name'] # Get the configuration for this svg try: svg_config = config['svgs'][name] except KeyError: print("\033[93mSkip: Configuration not found for svg : %r\033[0m" % name) return if 'unicode' in svg_config: unicodeVal = unicode_hex_list(svg_config['unicode']) else: unicodeVal = None glyphWidth = width + int(svg_config['left']) + int(svg_config['right']) if glyphWidth < 0: raise UFOLibError("Glyph %s has negative width." % name) contentsPlistPath = ufo_font_path + '/glyphs/contents.plist' try: with open(contentsPlistPath, "rb") as f: contentsPlist = load(f) except: raise UFOLibError("The file %s could not be read." % contentsPlistPath) glyph_name = svg_config['glyph_name'] # Replace all capital letters with a following '_' to avoid file name clash in Windows glyph_file_name = re.sub(r'([A-Z]){1}', lambda pat: pat.group(1) + '_', glyph_name) + '.glif' if glyph_name in contentsPlist: existing_glyph = True else: existing_glyph = False # Calculate the transformation to do transform = transform_list(config['font']['transform']) base = 0 if 'base' in svg_config: base = int(svg_config['base']) transform[4] += int(svg_config['left']) # X offset = left bearing transform[5] += height + base # Y offset glif = svg2glif(svg_file, name=svg_config['glyph_name'], width=glyphWidth, height=getattr(infoObject, 'unitsPerEm'), unicodes=unicodeVal, transform=transform, version=config['font']['version']) if options.outfile is None: output_file = ufo_font_path + '/glyphs/' + glyph_file_name else: output_file = options.outfile with open(output_file, 'w', encoding='utf-8') as f: f.write(glif) print( "\033[94m[%s]\033[0m \033[92mConvert\033[0m %s -> %s \033[92m✔️\033[0m" % (fontName, name, output_file)) # If this is a new glyph, add it to the UFO/glyphs/contents.plist if not existing_glyph: contentsPlist[glyph_name] = glyph_file_name writePlistAtomically(contentsPlist, contentsPlistPath) print( "\033[94m[%s]\033[0m \033[92mAdd\033[0m %s -> %s \033[92m✔️\033[0m" % (fontName, glyph_name, glyph_file_name)) lib_obj = reader.readLib() lib_obj['public.glyphOrder'].append(glyph_name) writer.writeLib(lib_obj)
class UFOFont(BaseFont): ufoState = None def resetCache(self): super().resetCache() del self.defaultVerticalAdvance del self.defaultVerticalOriginY del self.globalColorLayerMapping def _setupReaderAndGlyphSet(self): self.reader = UFOReader(self.fontPath, validate=False) self.glyphSet = self.reader.getGlyphSet() self.glyphSet.glyphClass = Glyph self.layerGlyphSets = {} async def load(self, outputWriter): if hasattr(self, "reader"): self._cachedGlyphs = {} return self._setupReaderAndGlyphSet() self.info = SimpleNamespace() self.reader.readInfo(self.info) self.lib = self.reader.readLib() self._cachedGlyphs = {} if self.ufoState is None: includedFeatureFiles = extractIncludedFeatureFiles( self.fontPath, self.reader) self.ufoState = UFOState( self.reader, self.glyphSet, getUnicodesAndAnchors=self._getUnicodesAndAnchors, includedFeatureFiles=includedFeatureFiles) fontData = await compileUFOToBytes(self.fontPath, outputWriter) f = io.BytesIO(fontData) self.ttFont = TTFont(f, lazy=True) self.shaper = self._getShaper(fontData) def updateFontPath(self, newFontPath): """This gets called when the source file was moved.""" super().updateFontPath(newFontPath) self._setupReaderAndGlyphSet() def getExternalFiles(self): return self.ufoState.includedFeatureFiles def canReloadWithChange(self, externalFilePath): if self.reader.fileStructure != UFOFileStructure.PACKAGE: # We can't (won't) partially reload .ufoz return False if externalFilePath: # Features need to be recompiled no matter what return False self.glyphSet.rebuildContents() self.ufoState = self.ufoState.newState() (needsFeaturesUpdate, needsGlyphUpdate, needsInfoUpdate, needsCmapUpdate, needsLibUpdate) = self.ufoState.getUpdateInfo() if needsFeaturesUpdate: return False if needsInfoUpdate: # font.info changed, all we care about is a possibly change unitsPerEm self.info = SimpleNamespace() self.reader.readInfo(self.info) if needsCmapUpdate: # The cmap changed. Let's update it in-place and only rebuild the shaper newCmap = { code: gn for gn, codes in self.ufoState.unicodes.items() for code in codes } fb = FontBuilder(font=self.ttFont) fb.setupCharacterMap(newCmap) f = io.BytesIO() self.ttFont.save(f, reorderTables=False) self.shaper = self._getShaper(f.getvalue()) if needsLibUpdate: self.lib = self.reader.readLib() # We don't explicitly track changes in layers, but they may be involved # in building layered color glyphs, so let's just always reset the cache. self.resetCache() return True def _getUnicodesAndAnchors(self): unicodes = defaultdict(list) for code, gn in self.ttFont.getBestCmap().items(): unicodes[gn].append(code) anchors = pickle.loads(self.ttFont["FGAx"].data) return unicodes, anchors def _getShaper(self, fontData): return HBShape(fontData, getHorizontalAdvance=self._getHorizontalAdvance, getVerticalAdvance=self._getVerticalAdvance, getVerticalOrigin=self._getVerticalOrigin, ttFont=self.ttFont) @cachedProperty def unitsPerEm(self): return self.info.unitsPerEm def _getGlyph(self, glyphName, layerName=None): glyph = self._cachedGlyphs.get((layerName, glyphName)) if glyph is None: if glyphName == ".notdef" and glyphName not in self.glyphSet: # We need a .notdef glyph, so let's make one. glyph = NotDefGlyph(self.info.unitsPerEm) self._addOutlinePathToGlyph(glyph) else: try: if layerName is None: glyph = self.glyphSet[glyphName] else: glyph = self.getLayerGlyphSet(layerName)[glyphName] self._addOutlinePathToGlyph(glyph) except Exception as e: # TODO: logging would be better but then capturing in mainWindow.py is harder print(f"Glyph '{glyphName}' could not be read: {e!r}", file=sys.stderr) glyph = self._getGlyph(".notdef") self._cachedGlyphs[(layerName, glyphName)] = glyph return glyph def _addOutlinePathToGlyph(self, glyph): if self.cocoa: pen = CocoaPen(self.glyphSet) glyph.draw(pen) glyph.outline = pen.path else: pen = RecordingPen() glyph.draw(pen) glyph.outline = pen def _getHorizontalAdvance(self, glyphName): glyph = self._getGlyph(glyphName) return glyph.width @cachedProperty def defaultVerticalAdvance(self): ascender = getattr(self.info, "ascender", None) descender = getattr(self.info, "descender", None) if ascender is None or descender is None: return self.info.unitsPerEm else: return ascender + abs(descender) @cachedProperty def defaultVerticalOriginY(self): ascender = getattr(self.info, "ascender", None) if ascender is None: return self.info.unitsPerEm # ??? else: return ascender def _getVerticalAdvance(self, glyphName): glyph = self._getGlyph(glyphName) vAdvance = glyph.height if vAdvance is None or vAdvance == 0: # XXX default vAdv == 0 -> bad UFO spec vAdvance = self.defaultVerticalAdvance return -abs(vAdvance) def _getVerticalOrigin(self, glyphName): glyph = self._getGlyph(glyphName) vOrgX = glyph.width / 2 lib = getattr(glyph, "lib", {}) vOrgY = lib.get("public.verticalOrigin") if vOrgY is None: vOrgY = self.defaultVerticalOriginY return True, vOrgX, vOrgY def _getGlyphDrawing(self, glyphName, colorLayers): glyph = self._getGlyph(glyphName) if colorLayers: colorLayerMapping = glyph.lib.get(COLOR_LAYER_MAPPING_KEY) if colorLayerMapping is None: colorLayerMapping = self.globalColorLayerMapping if colorLayerMapping is not None: layers = [] for layerName, colorID in colorLayerMapping: glyph = self._getGlyph(glyphName, layerName) if not isinstance(glyph, NotDefGlyph): layers.append((glyph.outline, colorID)) if layers: return GlyphDrawing(layers) return GlyphDrawing([(glyph.outline, None)]) @cachedProperty def colorPalettes(self): return self.lib.get(COLOR_PALETTES_KEY) @cachedProperty def globalColorLayerMapping(self): return self.lib.get(COLOR_LAYER_MAPPING_KEY) def getLayerGlyphSet(self, layerName): layerGlyphSet = self.layerGlyphSets.get(layerName) if layerGlyphSet is None: layerGlyphSet = self.reader.getGlyphSet(layerName) self.layerGlyphSets[layerName] = layerGlyphSet return layerGlyphSet
class UFOFontData: def __init__(self, path, log_only, write_to_default_layer): self._reader = UFOReader(path, validate=False) self.path = path self._glyphmap = None self._processed_layer_glyphmap = None self.newGlyphMap = {} self._fontInfo = None self._glyphsets = {} # If True, we are running in report mode and not doing any changes, so # we skip the hash map and process all glyphs. self.log_only = log_only # Used to store the hash of glyph data of already processed glyphs. If # the stored hash matches the calculated one, we skip the glyph. self._hashmap = None self.fontDict = None self.hashMapChanged = False # If True, then write data to the default layer self.writeToDefaultLayer = write_to_default_layer def getUnitsPerEm(self): return self.fontInfo.get("unitsPerEm", 1000) def getPSName(self): return self.fontInfo.get("postscriptFontName", "PSName-Undefined") @staticmethod def isCID(): return False def convertToBez(self, name, read_hints, round_coords, doAll=False): # We do not yet support reading hints, so read_hints is ignored. width, bez, skip = self._get_or_skip_glyph(name, round_coords, doAll) if skip: return None, width bezString = "\n".join(bez) bezString = "\n".join(["% " + name, "sc", bezString, "ed", ""]) return bezString, width def updateFromBez(self, bezData, name, width, mm_hint_info=None): # For UFO font, we don't use the width parameter: # it is carried over from the input glif file. layer = None if name in self.processedLayerGlyphMap: layer = PROCESSED_LAYER_NAME glyphset = self._get_glyphset(layer) glyph = BezGlyph(bezData) glyphset.readGlyph(name, glyph) if hasattr(glyph, 'width'): glyph.width = norm_float(glyph.width) self.newGlyphMap[name] = glyph # updateFromBez is called only if the glyph has been autohinted which # might also change its outline data. We need to update the edit status # in the hash map entry. I assume that convertToBez has been run # before, which will add an entry for this glyph. self.updateHashEntry(name) def save(self, path): if path is None: path = self.path if os.path.abspath(self.path) != os.path.abspath(path): # If user has specified a path other than the source font path, # then copy the entire UFO font, and operate on the copy. log.info("Copying from source UFO font to output UFO font before " "processing...") if os.path.exists(path): shutil.rmtree(path) shutil.copytree(self.path, path) writer = UFOWriter(path, self._reader.formatVersion, validate=False) layer = PROCESSED_LAYER_NAME if self.writeToDefaultLayer: layer = None # Write layer contents. layers = writer.layerContents.copy() if self.writeToDefaultLayer and PROCESSED_LAYER_NAME in layers: # Delete processed glyphs directory writer.deleteGlyphSet(PROCESSED_LAYER_NAME) # Remove entry from 'layercontents.plist' file del layers[PROCESSED_LAYER_NAME] elif self.processedLayerGlyphMap or not self.writeToDefaultLayer: layers[PROCESSED_LAYER_NAME] = PROCESSED_GLYPHS_DIRNAME writer.layerContents.update(layers) writer.writeLayerContents() # Write glyphs. glyphset = writer.getGlyphSet(layer, defaultLayer=layer is None) for name, glyph in self.newGlyphMap.items(): filename = self.glyphMap[name] if not self.writeToDefaultLayer and \ name in self.processedLayerGlyphMap: filename = self.processedLayerGlyphMap[name] # Recalculate glyph hashes if self.writeToDefaultLayer: self.recalcHashEntry(name, glyph) glyphset.contents[name] = filename glyphset.writeGlyph(name, glyph, glyph.drawPoints) glyphset.writeContents() # Write hashmap if self.hashMapChanged: self.writeHashMap(writer) @property def hashMap(self): if self._hashmap is None: try: data = self._reader.readData(HASHMAP_NAME) except UFOLibError: data = None if data: hashmap = ast.literal_eval(data.decode("utf-8")) else: hashmap = {HASHMAP_VERSION_NAME: HASHMAP_VERSION} version = (0, 0) if HASHMAP_VERSION_NAME in hashmap: version = hashmap[HASHMAP_VERSION_NAME] if version[0] > HASHMAP_VERSION[0]: raise FontParseError("Hash map version is newer than " "psautohint. Please update.") elif version[0] < HASHMAP_VERSION[0]: log.info("Updating hash map: was older version") hashmap = {HASHMAP_VERSION_NAME: HASHMAP_VERSION} self._hashmap = hashmap return self._hashmap def writeHashMap(self, writer): hashMap = self.hashMap if not hashMap: return # no glyphs were processed. data = ["{"] for gName in sorted(hashMap.keys()): data.append("'%s': %s," % (gName, hashMap[gName])) data.append("}") data.append("") data = "\n".join(data) writer.writeData(HASHMAP_NAME, data.encode("utf-8")) def updateHashEntry(self, glyphName): # srcHash has already been set: we are fixing the history list. # Get hash entry for glyph srcHash, historyList = self.hashMap[glyphName] self.hashMapChanged = True # If the program is not in the history list, add it. if AUTOHINT_NAME not in historyList: historyList.append(AUTOHINT_NAME) def recalcHashEntry(self, glyphName, glyph): hashBefore, historyList = self.hashMap[glyphName] hash_pen = HashPointPen(glyph) glyph.drawPoints(hash_pen) hashAfter = hash_pen.getHash() if hashAfter != hashBefore: self.hashMap[glyphName] = [tostr(hashAfter), historyList] self.hashMapChanged = True def checkSkipGlyph(self, glyphName, newSrcHash, doAll): skip = False if self.log_only: return skip srcHash = None historyList = [] # Get hash entry for glyph if glyphName in self.hashMap: srcHash, historyList = self.hashMap[glyphName] if srcHash == newSrcHash: if AUTOHINT_NAME in historyList: # The glyph has already been autohinted, and there have been no # changes since. skip = not doAll if not skip and AUTOHINT_NAME not in historyList: historyList.append(AUTOHINT_NAME) else: if CHECKOUTLINE_NAME in historyList: log.error( "Glyph '%s' has been edited. You must first " "run '%s' before running '%s'. Skipping.", glyphName, CHECKOUTLINE_NAME, AUTOHINT_NAME) skip = True # If the source hash has changed, we need to delete the processed # layer glyph. self.hashMapChanged = True self.hashMap[glyphName] = [tostr(newSrcHash), [AUTOHINT_NAME]] if glyphName in self.processedLayerGlyphMap: del self.processedLayerGlyphMap[glyphName] return skip def _get_glyphset(self, layer_name=None): if layer_name not in self._glyphsets: glyphset = None try: glyphset = self._reader.getGlyphSet(layer_name) except UFOLibError: pass self._glyphsets[layer_name] = glyphset return self._glyphsets[layer_name] @staticmethod def get_glyph_bez(glyph, round_coords): pen = BezPen(glyph.glyphSet, round_coords) glyph.draw(pen) if not hasattr(glyph, "width"): glyph.width = 0 return pen.bez def _get_or_skip_glyph(self, name, round_coords, doAll): # Get default glyph layer data, so we can check if the glyph # has been edited since this program was last run. # If the program name is in the history list, and the srcHash # matches the default glyph layer data, we can skip. glyphset = self._get_glyphset() glyph = glyphset[name] bez = self.get_glyph_bez(glyph, round_coords) # Hash is always from the default glyph layer. hash_pen = HashPointPen(glyph) glyph.drawPoints(hash_pen) skip = self.checkSkipGlyph(name, hash_pen.getHash(), doAll) # If there is a glyph in the processed layer, get the outline from it. if name in self.processedLayerGlyphMap: glyphset = self._get_glyphset(PROCESSED_LAYER_NAME) glyph = glyphset[name] bez = self.get_glyph_bez(glyph, round_coords) return glyph.width, bez, skip def getGlyphList(self): glyphOrder = self._reader.readLib().get(PUBLIC_GLYPH_ORDER, []) glyphList = list(self._get_glyphset().keys()) # Sort the returned glyph list by the glyph order as we depend in the # order for expanding glyph ranges. def key_fn(v): if v in glyphOrder: return glyphOrder.index(v) return len(glyphOrder) return sorted(glyphList, key=key_fn) @property def glyphMap(self): if self._glyphmap is None: glyphset = self._get_glyphset() self._glyphmap = glyphset.contents return self._glyphmap @property def processedLayerGlyphMap(self): if self._processed_layer_glyphmap is None: self._processed_layer_glyphmap = {} glyphset = self._get_glyphset(PROCESSED_LAYER_NAME) if glyphset is not None: self._processed_layer_glyphmap = glyphset.contents return self._processed_layer_glyphmap @property def fontInfo(self): if self._fontInfo is None: info = SimpleNamespace() self._reader.readInfo(info) self._fontInfo = vars(info) return self._fontInfo def getFontInfo(self, allow_no_blues, noFlex, vCounterGlyphs, hCounterGlyphs, fdIndex=0): if self.fontDict is not None: return self.fontDict fdDict = fdTools.FDDict() # should be 1 if the glyphs are ideographic, else 0. fdDict.LanguageGroup = self.fontInfo.get("languagegroup", "0") fdDict.OrigEmSqUnits = self.getUnitsPerEm() fdDict.FontName = self.getPSName() upm = self.getUnitsPerEm() low = min(-upm * 0.25, self.fontInfo.get("openTypeOS2WinDescent", 0) - 200) high = max(upm * 1.25, self.fontInfo.get("openTypeOS2WinAscent", 0) + 200) # Make a set of inactive alignment zones: zones outside of the font # bbox so as not to affect hinting. Used when src font has no # BlueValues or has invalid BlueValues. Some fonts have bad BBox # values, so I don't let this be smaller than -upm*0.25, upm*1.25. inactiveAlignmentValues = [low, low, high, high] blueValues = self.fontInfo.get("postscriptBlueValues", []) numBlueValues = len(blueValues) if numBlueValues < 4: if allow_no_blues: blueValues = inactiveAlignmentValues numBlueValues = len(blueValues) else: raise FontParseError( "Font must have at least four values in its " "BlueValues array for PSAutoHint to work!") blueValues.sort() # The first pair only is a bottom zone, where the first value is the # overshoot position; the rest are top zones, and second value of the # pair is the overshoot position. blueValues[0] = blueValues[0] - blueValues[1] for i in range(3, numBlueValues, 2): blueValues[i] = blueValues[i] - blueValues[i - 1] blueValues = [str(v) for v in blueValues] numBlueValues = min(numBlueValues, len(fdTools.kBlueValueKeys)) for i in range(numBlueValues): key = fdTools.kBlueValueKeys[i] value = blueValues[i] setattr(fdDict, key, value) otherBlues = self.fontInfo.get("postscriptOtherBlues", []) numBlueValues = len(otherBlues) if len(otherBlues) > 0: i = 0 numBlueValues = len(otherBlues) otherBlues.sort() for i in range(0, numBlueValues, 2): otherBlues[i] = otherBlues[i] - otherBlues[i + 1] otherBlues = [str(v) for v in otherBlues] numBlueValues = min(numBlueValues, len(fdTools.kOtherBlueValueKeys)) for i in range(numBlueValues): key = fdTools.kOtherBlueValueKeys[i] value = otherBlues[i] setattr(fdDict, key, value) vstems = self.fontInfo.get("postscriptStemSnapV", []) if not vstems: if allow_no_blues: # dummy value. Needs to be larger than any hint will likely be, # as the autohint program strips out any hint wider than twice # the largest global stem width. vstems = [fdDict.OrigEmSqUnits] else: raise FontParseError("Font does not have postscriptStemSnapV!") vstems.sort() if not vstems or (len(vstems) == 1 and vstems[0] < 1): # dummy value that will allow PyAC to run vstems = [fdDict.OrigEmSqUnits] log.warning("There is no value or 0 value for DominantV.") fdDict.DominantV = "[" + " ".join([str(v) for v in vstems]) + "]" hstems = self.fontInfo.get("postscriptStemSnapH", []) if not hstems: if allow_no_blues: # dummy value. Needs to be larger than any hint will likely be, # as the autohint program strips out any hint wider than twice # the largest global stem width. hstems = [fdDict.OrigEmSqUnits] else: raise FontParseError("Font does not have postscriptStemSnapH!") hstems.sort() if not hstems or (len(hstems) == 1 and hstems[0] < 1): # dummy value that will allow PyAC to run hstems = [fdDict.OrigEmSqUnits] log.warning("There is no value or 0 value for DominantH.") fdDict.DominantH = "[" + " ".join([str(v) for v in hstems]) + "]" if noFlex: fdDict.FlexOK = "false" else: fdDict.FlexOK = "true" # Add candidate lists for counter hints, if any. if vCounterGlyphs: temp = " ".join(vCounterGlyphs) fdDict.VCounterChars = "( %s )" % (temp) if hCounterGlyphs: temp = " ".join(hCounterGlyphs) fdDict.HCounterChars = "( %s )" % (temp) fdDict.BlueFuzz = self.fontInfo.get("postscriptBlueFuzz", 1) # postscriptBlueShift # postscriptBlueScale self.fontDict = fdDict return fdDict def getfdInfo(self, allow_no_blues, noFlex, vCounterGlyphs, hCounterGlyphs, glyphList, fdIndex=0): fdGlyphDict = None fdDict = self.getFontInfo(allow_no_blues, noFlex, vCounterGlyphs, hCounterGlyphs, fdIndex) fontDictList = [fdDict] # Check the fontinfo file, and add any other font dicts srcFontInfo = os.path.dirname(self.path) srcFontInfo = os.path.join(srcFontInfo, "fontinfo") maxX = self.getUnitsPerEm() * 2 maxY = maxX minY = -self.getUnitsPerEm() if os.path.exists(srcFontInfo): with open(srcFontInfo, "r", encoding="utf-8") as fi: fontInfoData = fi.read() fontInfoData = re.sub(r"#[^\r\n]+", "", fontInfoData) if "FDDict" in fontInfoData: fdGlyphDict, fontDictList, finalFDict = \ fdTools.parseFontInfoFile( fontDictList, fontInfoData, glyphList, maxY, minY, self.getPSName()) if finalFDict is None: # If a font dict was not explicitly specified for the # output font, use the first user-specified font dict. fdTools.mergeFDDicts(fontDictList[1:], self.fontDict) else: fdTools.mergeFDDicts([finalFDict], self.fontDict) return fdGlyphDict, fontDictList @staticmethod def close(): return