def interpolate_cff2_metrics(varfont, topDict, glyphOrder, loc): """Unlike TrueType glyphs, neither advance width nor bounding box info is stored in a CFF2 charstring. The width data exists only in the hmtx and HVAR tables. Since LSB data cannot be interpolated reliably from the master LSB values in the hmtx table, we traverse the charstring to determine the actual bound box. """ charstrings = topDict.CharStrings boundsPen = BoundsPen(glyphOrder) hmtx = varfont['hmtx'] hvar_table = None if 'HVAR' in varfont: hvar_table = varfont['HVAR'].table fvar = varfont['fvar'] varStoreInstancer = VarStoreInstancer(hvar_table.VarStore, fvar.axes, loc) for gid, gname in enumerate(glyphOrder): entry = list(hmtx[gname]) # get width delta. if hvar_table: if hvar_table.AdvWidthMap: width_idx = hvar_table.AdvWidthMap.mapping[gname] else: width_idx = gid width_delta = otRound(varStoreInstancer[width_idx]) else: width_delta = 0 # get LSB. boundsPen.init() charstring = charstrings[gname] charstring.draw(boundsPen) if boundsPen.bounds is None: # Happens with non-marking glyphs lsb_delta = 0 else: lsb = boundsPen.bounds[0] lsb_delta = entry[1] - lsb if lsb_delta or width_delta: if width_delta: entry[0] += width_delta if lsb_delta: entry[1] = lsb hmtx[gname] = tuple(entry)
def _get_box(self): from fontTools.pens.boundsPen import BoundsPen bP = BoundsPen(None) self.draw(bP) return bP.bounds
def _getGlyphBounds(self, glyphName): glyph = self.glyphSet[glyphName] pen = BoundsPen(self.glyphSet) glyph.draw(pen) return pen.bounds
def calcBounds(self, glyphSet): boundsPen = BoundsPen(glyphSet) self.draw(boundsPen) return boundsPen.bounds
def componentBoundsRepresentationFactory(obj): pen = BoundsPen(obj.layer) obj.draw(pen) return pen.bounds
def calcBounds(self): boundsPen = BoundsPen(None) self.draw(boundsPen) return boundsPen.bounds
def clientInitData(self): txFont = self.parentFont.clientFont if not hasattr(txFont, 'vmetrics'): try: txFont.vmetrics = txFont['vmtx'].metrics except KeyError: txFont.vmetrics = None try: txFont.vorg = txFont['VORG'] except KeyError: txFont.vorg = None fTopDict = txFont['CFF '].cff.topDictIndex[0] self.isCID = self.parentFont.isCID charstring = fTopDict.CharStrings[self.name] # Get the list of points pen = FontPDFPen(None) drawCharString(charstring, pen) self.hintTable = charstring.hintTable self.hhints = charstring.hhints self.vhints = charstring.vhints self.numMT = pen.numMT self.numLT = pen.numLT self.numCT = pen.numCT self.numPaths = pen.numPaths self.pathList = pen.pathList for path in self.pathList: lenPath = len(path) path[-1].next = path[0] path[0].last = path[-1] if lenPath > 1: path[0].next = path[1] path[-1].last = path[-2] for i in list(range(lenPath)[1:-1]): pt = path[i] pt.next = path[i + 1] pt.last = path[i - 1] assert len(self.pathList) == self.numPaths, ( "Path lengths don't match %s %s" % (len(self.pathList), self.numPaths)) # get the bbox and width. pen = BoundsPen(None) charstring.draw(pen) self.xAdvance = charstring.width self.BBox = pen.bounds if not self.BBox: self.BBox = [0, 0, 0, 0] self.yOrigin = self.parentFont.emSquare + self.parentFont.getBaseLine() if txFont.vorg: try: self.yOrigin = txFont.vorg[self.name] except KeyError: if txFont.vmetrics: try: mtx = txFont.vmetrics[self.name] self.yOrigin = mtx[1] + self.BBox[3] except KeyError: pass haveVMTX = 0 if txFont.vmetrics: try: mtx = txFont.vmetrics[self.name] self.yAdvance = mtx[0] self.tsb = mtx[1] haveVMTX = 1 except KeyError: pass if not haveVMTX: self.yAdvance = self.parentFont.getEmSquare() self.tsb = (self.yOrigin - self.BBox[3] + self.parentFont.getBaseLine()) # Get the fdIndex, so we can later determine # which set of blue values to use. self.fdIndex = 0 if hasattr(fTopDict, "ROS"): gid = fTopDict.CharStrings.charStrings[self.name] self.fdIndex = fTopDict.FDSelect[gid]
def getGlyphBox(glyph): pen = BoundsPen(glyph.layer) glyph.draw(pen) return pen.bounds
def test_quadraticCurve(self): pen = BoundsPen(None) pen.moveTo((0, 0)) pen.qCurveTo((6, 6), (10, 0)) self.assertEqual("0 0 10 3", bounds_(pen))
def test_curve(self): pen = BoundsPen(None) pen.moveTo((0, 0)) pen.curveTo((20, 10), (90, 40), (0, 0)) self.assertEqual("0 0 45 20", bounds_(pen))
def bounds(self): """Returns the bounding box of the path.""" pen = BoundsPen(self) self.draw(pen) return pen.bounds
def getGlyphBox(glyph): pen = BoundsPen(glyph.getParent()) glyph.draw(pen) return pen.bounds
def _get_bounds(self): from fontTools.pens.boundsPen import BoundsPen pen = BoundsPen(None) self.draw(pen) return pen.bounds
def com_google_fonts_check_iso15008_intercharacter_spacing(font, ttFont): """Check if spacing between characters is adequate for display use""" width = stem_width(ttFont) # Because an l can have a curly tail, we don't want the *glyph* sidebearings; # we want the sidebearings measured using a line at Y=x-height. l_intersections = xheight_intersections(ttFont, "l") if width is None or len(l_intersections) < 2: yield FAIL, Message("no-stem-width", "Could not determine stem width") return l_lsb = l_intersections[0].point.x l_advance = ttFont["hmtx"]["l"][0] l_rsb = l_advance - l_intersections[-1].point.x l_l = l_rsb + pair_kerning(font, "l", "l") + l_lsb if l_l is None: yield FAIL,\ Message('glyph-not-present', "There was no 'l' glyph in the font," " so the spacing could not be tested") return if 1.5 <= (l_l / width) <= 2.4: yield PASS, "Distance between vertical strokes was adequate" else: yield FAIL,\ Message('bad-vertical-vertical-spacing', f"The space between vertical strokes ({l_l})" f" does not conform to the expected" f" range of {width * 1.5}-{width * 2.4}") # For v, however, a simple LSB/RSB is adequate. glyphset = ttFont.getGlyphSet() h_glyph = glyphset["v"] pen = BoundsPen(glyphset) h_glyph._glyph.draw(pen, ttFont.get("glyf")) (xMin, yMin, xMax, yMax) = pen.bounds v_advance = ttFont["hmtx"]["v"][0] v_lsb = xMin v_rsb = v_advance - (v_lsb + xMax - xMin) l_v = l_rsb + pair_kerning(font, "l", "v") + v_lsb if l_v is None: yield FAIL,\ Message('glyph-not-present', "There was no 'v' glyph in the font," " so the spacing could not be tested") return if (l_v / width) > 0.85: yield PASS, "Distance between vertical and diagonal strokes was adequate" else: yield FAIL,\ Message('bad-vertical-diagonal-spacing', f"The space between vertical and diagonal strokes ({l_v})" f" was less than the expected" f" value of {width * 0.85}") v_v = v_rsb + pair_kerning(font, "v", "v") + v_lsb if v_v > 0: yield PASS, "Distance between diagonal strokes was adequate" else: yield FAIL,\ Message('bad-diagonal-diagonal-spacing', "Diagonal strokes (vv) were touching")
def getFontBounds(font): gs = font.getGlyphSet() pen = BoundsPen(gs) for g in gs.keys(): gs[g].draw(pen) return pen.bounds
def clientInitData(self): self.isTT = 1 self.isCID = 0 txFont = self.parentFont.clientFont glyphSet = txFont.getGlyphSet(preferCFF=1) clientGlyph = glyphSet[self.name] # Get the list of points pen = FontPDFPen(None) clientGlyph.draw(pen) if not hasattr(txFont, 'vmetrics'): try: txFont.vmetrics = txFont['vmtx'].metrics except KeyError: txFont.vmetrics = None try: txFont.vorg = txFont['VORG'] except KeyError: txFont.vorg = None self.hhints = [] self.vhints =[] self.numMT = pen.numMT self.numLT = pen.numLT self.numCT = pen.numCT self.numPaths = pen.numPaths self.pathList = pen.pathList for path in self.pathList : lenPath = len(path) path[-1].next = path[0] path[0].last = path[-1] if lenPath > 1: path[0].next = path[1] path[-1].last = path[-2] for i in range(lenPath)[1:-1]: pt = path[i] pt.next = path[i+1] pt.last = path[i-1] assert len(self.pathList) == self.numPaths, " Path lengths don't match %s %s" % (len(self.pathList) , self.numPaths) # get the bbox and width. pen = BoundsPen(None) clientGlyph.draw(pen) self.xAdvance = clientGlyph.width glyph_bounds = pen.bounds if not glyph_bounds: self.BBox = [0, 0, 0, 0] else: self.BBox = [round(item) for item in glyph_bounds] self.yOrigin = self.parentFont.emSquare + self.parentFont.getBaseLine() if txFont.vorg: try: self.yOrigin = txFont.vorg[self.name] except KeyError: if txFont.vmetrics: try: mtx = txFont.vmetrics[self.name] self.yOrigin = mtx[1] + self.BBox[3] except KeyError: pass haveVMTX = 0 if txFont.vmetrics: try: mtx = txFont.vmetrics[self.name] self.yAdvance = mtx[0] self.tsb = mtx[1] haveVMTX =1 except KeyError: pass if not haveVMTX: self.yAdvance = self.parentFont.getEmSquare() self.tsb = self.yOrigin - self.BBox[3] + self.parentFont.getBaseLine() # Get the fdIndex, so we can laterdetermine which set of blue values to use. self.fdIndex = 0 return
def test_draw(self): pen = BoundsPen(None) draw_(pen) self.assertEqual("-55 0 58 100", bounds_(pen))
def get_glyph_cleaned_extents(ttglyph, glyf_set): pen = BoundsPen(glyf_set, ignoreSinglePoints=True) ttglyph.draw(pen) if not pen.bounds: return None, None return pen.bounds[1], pen.bounds[3]
def test_empty(self): pen = BoundsPen(None) self.assertEqual(None, pen.bounds)
def gen_glyphs(ttfont, out): math = ttfont['MATH'].table cmap = ttfont['cmap'].getcmap(3, 10).cmap glyphs = ttfont.getGlyphSet() metrics = { name: { "usv": codepoint, "xmin": 0, "ymin": 0, "xmax": 0, "ymax": 0, "attachment": 0, "italics": 0, "advance": 0, "lsb": 0, } for codepoint, name in cmap.items() } # Gather bounding box information pen = BoundsPen(None) for glyph in cmap.values(): bbox = (0, 0, 0, 0) glyphs.get(glyph).draw(pen) if pen.bounds is not None: (xmin, ymin, xmax, ymax) = pen.bounds bbox = (int(xmin), int(ymin), int(xmax), int(ymax)) metrics[glyph]['xmin'] = bbox[0] metrics[glyph]['ymin'] = bbox[1] metrics[glyph]['xmax'] = bbox[2] metrics[glyph]['ymax'] = bbox[3] pen.bounds = None pen._start = None # Gather accent attachment accent_table = math.MathGlyphInfo.MathTopAccentAttachment accent_coverage = accent_table.TopAccentCoverage.glyphs for glyph in accent_coverage: value = accent_table \ .TopAccentAttachment[accent_coverage.index(glyph)] \ .Value metrics[glyph]["attachment"] = value # Gather italics offsets italics_table = math.MathGlyphInfo.MathItalicsCorrectionInfo italics_coverage = italics_table.Coverage.glyphs for glyph in italics_coverage: value = italics_table \ .ItalicsCorrection[italics_coverage.index(glyph)] \ .Value metrics[glyph]["italics"] = value # Gather advance and left side bearing hmtx = ttfont['hmtx'].metrics for glyph in cmap.values(): (advance, lsb) = hmtx[glyph] metrics[glyph]["advance"] = advance metrics[glyph]["lsb"] = lsb # Insert shim shim = [] cmap = ttfont['cmap'].getcmap(3, 10).cmap for (new, old) in SHIM: if old in cmap: name = cmap[old] data = deepcopy(metrics[name]) shim.append((new, data)) else: print("Ignoring shim for", hex(old)) template = Template(filename="tools/mako/glyphs.mako.rs") with open(out + "glyphs.rs", 'w') as file: file.write(template.render(glyphs=metrics, shim=shim))
def _get_bounds(self): if self._bounds is None: pen = BoundsPen(None) self.draw(pen) self._bounds = pen.bounds return self._bounds
def contourBoundsRepresentationFactory(obj): pen = BoundsPen(None) obj.draw(pen) return pen.bounds
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 _get_bounds(self): pen = BoundsPen(None) self.draw(pen) return pen.bounds
def glyphBoundsRepresentationFactory(glyph): pen = BoundsPen(glyph.layer) glyph.draw(pen) return pen.bounds