def glyph_bounds(self, glyph) -> Tuple[int]: glyph = self.glyph_name(glyph) ttglyphset = self.ttglyphset ttglyph = ttglyphset[glyph] bounds = BoundsPen(ttglyphset) ttglyph.draw(bounds) return bounds.bounds
def getBounds(drawable: Drawable, layer: GlyphSet | None) -> BoundingBox | None: pen = BoundsPen(layer) # raise 'KeyError' when a referenced component is missing from glyph set pen.skipMissingComponents = False drawable.draw(pen) return None if pen.bounds is None else BoundingBox(*pen.bounds)
def getBounds(drawable: Drawable, layer: Any) -> Optional[BoundingBox]: # XXX: layer should behave like a mapping of glyph names to Glyph objects, but # cyclic imports... pen = BoundsPen(layer) # raise 'KeyError' when a referenced component is missing from glyph set pen.skipMissingComponents = False drawable.draw(pen) return None if pen.bounds is None else BoundingBox(*pen.bounds)
def _get_bounds(self): """ Subclasses may override this method. """ from fontTools.pens.boundsPen import BoundsPen pen = BoundsPen(self.layer) self.draw(pen) return pen.bounds
def test_compileVariable_filters(self, designspace, compileFunc): filters = [TransformationsFilter(OffsetY=10)] varfont = compileFunc(designspace, filters=filters) ufo = designspace.sources[0].font pen1 = BoundsPen(ufo) glyph = ufo["a"] glyph.draw(pen1) glyphSet = varfont.getGlyphSet() tt_glyph = glyphSet["a"] pen2 = BoundsPen(glyphSet) tt_glyph.draw(pen2) assert pen1.bounds[0] == pen2.bounds[0] assert pen1.bounds[1] + 10 == pen2.bounds[1] assert pen1.bounds[2] == pen2.bounds[2] assert pen1.bounds[3] + 10 == pen2.bounds[3]
def test_compile_filters(self, compileFunc, FontClass): ufo = FontClass(getpath("LayerFont-Regular.ufo")) filters = [TransformationsFilter(OffsetY=10)] ttf = compileFunc(ufo, filters=filters) pen1 = BoundsPen(ufo) glyph = ufo["a"] glyph.draw(pen1) glyphSet = ttf.getGlyphSet() tt_glyph = glyphSet["a"] pen2 = BoundsPen(glyphSet) tt_glyph.draw(pen2) assert pen1.bounds[0] == pen2.bounds[0] assert pen1.bounds[1] + 10 == pen2.bounds[1] assert pen1.bounds[2] == pen2.bounds[2] assert pen1.bounds[3] + 10 == pen2.bounds[3]
def bounds(self): """Calculate the bounds of this shape; mostly for internal use.""" try: cbp = BoundsPen(None) self.replay(cbp) mnx, mny, mxx, mxy = cbp.bounds return Rect((mnx, mny, mxx - mnx, mxy - mny)) except: return Rect(0, 0, 0, 0)
def makeSVGShape(glyph, name=None, width=None, opacity=None): attrs = { 'id': 'mathShape', 'title': "None", 'xmlns': "http://www.w3.org/2000/svg", 'xmlns:xlink': "http://www.w3.org/1999/xlink", 'xml:space': 'preserve', 'style': "fill-rule:nonzero;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;", } # try to get the bounds from the bounds layer. # if that does not work, get it from the glyph itself. bounds = None try: boundsGlyph = glyph.getLayer('bounds') if boundsGlyph is not None: bounds = boundsGlyph.box except: pass if bounds is None: boundsPen = BoundsPen({}) glyph.draw(boundsPen) bounds = boundsPen.bounds xOffset = 0 yOffset = 0 attrs['id'] = name if width is None: attrs['width'] = "100%" else: attrs['width'] = width if name is not None: attrs['name'] = name else: attrs['name'] = glyph.name if opacity is not None: attrs['fill-opacity'] = "%3.3f" % opacity t = Transform() t = t.scale(1, -1) t = t.translate(0, -bounds[3]) vb = (0, 0, glyph.width, bounds[3] - bounds[1]) attrs['viewBox'] = "%3.3f %3.3f %3.3f %3.3f" % (vb[0], vb[1], vb[2], vb[3]) attrs['enable-background'] = attrs['viewBox'] sPen = MathImageSVGPathPen({}, optimise=False, lineAsCurve=True) tPen = TransformPen(sPen, t) glyph.draw(tPen) path = "<path d=\"%s\"/>" % (sPen.getCommands()) tag = "<svg %s>%s</svg>" % (" ".join( ["%s=\"%s\"" % (k, v) for k, v in attrs.items()]), path) return vb, tag
def test_compileInterpolatableTTFs(self, FontClass): ufos = [ FontClass(getpath("NestedComponents-Regular.ufo")), FontClass(getpath("NestedComponents-Bold.ufo")), ] filters = [TransformationsFilter(OffsetY=10)] ttfs = compileInterpolatableTTFs(ufos, filters=filters) for i, ttf in enumerate(ttfs): glyph = ufos[i]["a"] pen1 = BoundsPen(ufos[i]) glyph.draw(pen1) glyphSet = ttf.getGlyphSet() tt_glyph = glyphSet["uni0061"] pen2 = BoundsPen(glyphSet) tt_glyph.draw(pen2) assert pen1.bounds[0] == pen2.bounds[0] assert pen1.bounds[1] + 10 == pen2.bounds[1] assert pen1.bounds[2] == pen2.bounds[2] assert pen1.bounds[3] + 10 == pen2.bounds[3]
def com_google_fonts_check_iso15008_interline_spacing(ttFont): """Check if spacing between lines is adequate for display use""" glyphset = ttFont.getGlyphSet() if "h" not in glyphset or "g" not in glyphset: yield FAIL,\ Message('glyph-not-present', "There was no 'g'/'h' glyph in the font," " so the spacing could not be tested") return h_glyph = glyphset["h"] pen = BoundsPen(glyphset) h_glyph._glyph.draw(pen, ttFont.get("glyf")) (_, _, _, h_yMax) = pen.bounds g_glyph = glyphset["g"] pen = BoundsPen(glyphset) g_glyph._glyph.draw(pen, ttFont.get("glyf")) (_, g_yMin, _, _) = pen.bounds linegap = ( (g_yMin - ttFont["OS/2"].sTypoDescender) + ttFont["OS/2"].sTypoLineGap + (ttFont["OS/2"].sTypoAscender - h_yMax) ) width = stem_width(ttFont) if width is None: yield FAIL,\ Message('no-stem-width', "Could not determine stem width") return if linegap < width: yield FAIL,\ Message('bad-interline-spacing', f"The interline space {linegap} should" f" be more than the stem width {width}") return yield PASS, "Amount of interline space was adequate"
def _get_cp_metrics(font, cp): # returns metrics for nominal glyph for cp, or None if cp not in font cmap = font_data.get_cmap(font) if cp not in cmap: return None glyphs = font.getGlyphSet() g = glyphs[cmap[cp]] pen = BoundsPen(glyphs) g.draw(pen) if not pen.bounds: return None xmin, ymin, xmax, ymax = pen.bounds return GMetrics( xmin, g.width - xmax, xmax - xmin, g.width, (ymin + ymax) / 2)
def glyphBoundsRepresentationFactory(glyph): # base glyph pen = BoundsPen(glyph.getParent()) glyph.draw(pen) bounds = pen.bounds # components for component in glyph.components: b = component.bounds if b is not None: if bounds is None: bounds = b else: bounds = unionRect(bounds, b) return bounds
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 com_google_fonts_check_iso15008_proportions(ttFont): """Check if 0.65 => (H width / H height) => 0.80""" glyphset = ttFont.getGlyphSet() if "H" not in glyphset: yield FAIL,\ Message('glyph-not-present', "There was no 'H' glyph in the font," " so the proportions could not be tested") h_glyph = glyphset["H"] pen = BoundsPen(glyphset) h_glyph._glyph.draw(pen, ttFont.get("glyf")) (xMin, yMin, xMax, yMax) = pen.bounds proportion = (xMax - xMin) / (yMax - yMin) if 0.65 <= proportion <= 0.80: yield PASS, "the letter H is not too narrow or too wide" else: yield FAIL,\ Message('invalid-proportion', f"The proportion of H width to H height ({proportion})" f"does not conform to the expected range of 0.65-0.80")
def com_google_fonts_check_iso15008_interword_spacing(font, ttFont): """Check if spacing between words is adequate for display use""" l_intersections = xheight_intersections(ttFont, "l") if len(l_intersections) < 2: yield FAIL,\ Message('glyph-not-present', "There was no 'l' glyph in the font," " so the spacing could not be tested") return l_advance = ttFont["hmtx"]["l"][0] l_rsb = l_advance - l_intersections[-1].point.x glyphset = ttFont.getGlyphSet() h_glyph = glyphset["m"] pen = BoundsPen(glyphset) h_glyph._glyph.draw(pen, ttFont.get("glyf")) (xMin, yMin, xMax, yMax) = pen.bounds m_advance = ttFont["hmtx"]["m"][0] m_lsb = xMin m_rsb = m_advance - (m_lsb + xMax - xMin) n_lsb = ttFont["hmtx"]["n"][1] l_m = l_rsb + pair_kerning(font, "l", "m") + m_lsb space_width = ttFont["hmtx"]["space"][0] # Add spacing caused by normal sidebearings space_width += m_rsb + n_lsb if 2.50 <= space_width / l_m <= 3.0: yield PASS, "Advance width of interword space was adequate" else: yield FAIL,\ Message('bad-interword-spacing', f"The interword space ({space_width}) was" f" outside the recommended range ({l_m*2.5}-{l_m*3.0})")
def getBounds(self, layer=None): pen = BoundsPen(layer) pen.skipMissingComponents = False self.draw(pen) return None if pen.bounds is None else BoundingBox(*pen.bounds)
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 _get_bounds(self): pen = BoundsPen(None) self.draw(pen) return pen.bounds
def calcBounds(self, glyphSet): boundsPen = BoundsPen(glyphSet) self.draw(boundsPen) return boundsPen.bounds
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 contourBoundsRepresentationFactory(obj): pen = BoundsPen(None) obj.draw(pen) return pen.bounds
def getFontBounds(font): gs = font.getGlyphSet() pen = BoundsPen(gs) for g in gs.keys(): gs[g].draw(pen) return pen.bounds
def _get_bounds(self): if self._bounds is None: pen = BoundsPen(None) self.draw(pen) self._bounds = pen.bounds return self._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): 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 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 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