def _drawDefaultNotdef(self, pen): width = round(self.unitsPerEm * 0.5) stroke = round(self.unitsPerEm * 0.05) ascender = self.ascender descender = self.descender xMin = stroke xMax = width - stroke yMax = ascender yMin = descender pen.moveTo((xMin, yMin)) pen.lineTo((xMax, yMin)) pen.lineTo((xMax, yMax)) pen.lineTo((xMin, yMax)) pen.lineTo((xMin, yMin)) pen.closePath() xMin += stroke xMax -= stroke yMax -= stroke yMin += stroke pen.moveTo((xMin, yMin)) pen.lineTo((xMin, yMax)) pen.lineTo((xMax, yMax)) pen.lineTo((xMax, yMin)) pen.lineTo((xMin, yMin)) pen.closePath()
def setupTable_post(self): """ Make the post table. **This should not be called externally.** Subclasses may override or supplement this method to handle the table creation in a different way if desired. """ self.otf["post"] = post = newTable("post") font = self.ufo post.formatType = 3.0 # italic angle italicAngle = getAttrWithFallback(font.info, "italicAngle") post.italicAngle = italicAngle # underline underlinePosition = getAttrWithFallback(font.info, "postscriptUnderlinePosition") post.underlinePosition = round(underlinePosition) underlineThickness = getAttrWithFallback( font.info, "postscriptUnderlineThickness") post.underlineThickness = round(underlineThickness) # determine if the font has a fixed width widths = set([glyph.width for glyph in self.allGlyphs.values()]) post.isFixedPitch = getAttrWithFallback(font.info, "postscriptIsFixedPitch") # misc post.minMemType42 = 0 post.maxMemType42 = 0 post.minMemType1 = 0 post.maxMemType1 = 0
def setupTable_hhea(self): """ Make the hhea table. This assumes that the hmtx table was made first. **This should not be called externally.** Subclasses may override or supplement this method to handle the table creation in a different way if desired. """ self.otf["hhea"] = hhea = newTable("hhea") hmtx = self.otf["hmtx"] font = self.ufo hhea.tableVersion = 0x00010000 # vertical metrics hhea.ascent = round( getAttrWithFallback(font.info, "openTypeHheaAscender")) hhea.descent = round( getAttrWithFallback(font.info, "openTypeHheaDescender")) hhea.lineGap = round( getAttrWithFallback(font.info, "openTypeHheaLineGap")) # horizontal metrics widths = [] lefts = [] rights = [] extents = [] for glyphName in self.allGlyphs: width, left = hmtx[glyphName] widths.append(width) bounds = self.glyphBoundingBoxes[glyphName] if bounds is None: continue right = width - left - (bounds.xMax - bounds.xMin) lefts.append(left) rights.append(right) # equation from the hhea spec for calculating xMaxExtent: # Max(lsb + (xMax - xMin)) extent = left + (bounds.xMax - bounds.xMin) extents.append(extent) hhea.advanceWidthMax = max(widths) hhea.minLeftSideBearing = min(lefts) hhea.minRightSideBearing = min(rights) hhea.xMaxExtent = max(extents) # misc hhea.caretSlopeRise = getAttrWithFallback( font.info, "openTypeHheaCaretSlopeRise") hhea.caretSlopeRun = getAttrWithFallback(font.info, "openTypeHheaCaretSlopeRun") hhea.caretOffset = round( getAttrWithFallback(font.info, "openTypeHheaCaretOffset")) hhea.reserved0 = 0 hhea.reserved1 = 0 hhea.reserved2 = 0 hhea.reserved3 = 0 hhea.metricDataFormat = 0 # glyph count hhea.numberOfHMetrics = len(self.allGlyphs)
def setupTable_vhea(self): """ Make the vhea table. This assumes that the head and vmtx tables were made first. **This should not be called externally.** Subclasses may override or supplement this method to handle the table creation in a different way if desired. """ self.otf["vhea"] = vhea = newTable("vhea") font = self.ufo head = self.otf["head"] vmtx = self.otf["vmtx"] vhea.tableVersion = 0x00011000 # horizontal metrics vhea.ascent = round( getAttrWithFallback(font.info, "openTypeVheaVertTypoAscender")) vhea.descent = round( getAttrWithFallback(font.info, "openTypeVheaVertTypoDescender")) vhea.lineGap = round( getAttrWithFallback(font.info, "openTypeVheaVertTypoLineGap")) # vertical metrics heights = [] tops = [] bottoms = [] for glyphName in self.allGlyphs: height, top = vmtx[glyphName] heights.append(height) bounds = self.glyphBoundingBoxes[glyphName] if bounds is None: continue bottom = height - top - (bounds.yMax - bounds.yMin) tops.append(top) bottoms.append(bottom) vhea.advanceHeightMax = max(heights) vhea.minTopSideBearing = max(tops) vhea.minBottomSideBearing = max(bottoms) vhea.yMaxExtent = vhea.minTopSideBearing - (head.yMax - head.yMin) # misc vhea.caretSlopeRise = getAttrWithFallback( font.info, "openTypeVheaCaretSlopeRise") vhea.caretSlopeRun = getAttrWithFallback(font.info, "openTypeVheaCaretSlopeRun") vhea.caretOffset = getAttrWithFallback(font.info, "openTypeVheaCaretOffset") vhea.reserved0 = 0 vhea.reserved1 = 0 vhea.reserved2 = 0 vhea.reserved3 = 0 vhea.reserved4 = 0 vhea.metricDataFormat = 0 # glyph count vhea.numberOfVMetrics = len(self.allGlyphs)
def addPoint(self, pt, segmentType=None, smooth=False, name=None, identifier=None, **kwargs): if segmentType is None: pt_type = "" else: pt_type = segmentType[0] self.data.append("%s%s%s" % (pt_type, repr(norm_float(round( pt[0], 9))), repr(norm_float(round(pt[1], 9)))))
def get_origin_height(self, font, origin): if origin is self.Origin.BASELINE: return 0 elif origin is self.Origin.CAP_HEIGHT: return font.info.capHeight elif origin is self.Origin.HALF_CAP_HEIGHT: return round(font.info.capHeight / 2) elif origin is self.Origin.X_HEIGHT: return font.info.xHeight elif origin is self.Origin.HALF_X_HEIGHT: return round(font.info.xHeight / 2) else: raise AssertionError(origin)
def __init__(self, font, glyphOrder=None, convertCubics=True, cubicConversionError=2): self.ufo = font self.convertCubics = convertCubics self.cubicConversionError = cubicConversionError self.log = [] # make any missing glyphs and store them locally missingRequiredGlyphs = self.makeMissingRequiredGlyphs() # make a dict of all glyphs self.allGlyphs = {} for glyph in font: self.allGlyphs[glyph.name] = glyph self.allGlyphs.update(missingRequiredGlyphs) # store the glyph order if glyphOrder is None: if hasattr(font, 'glyphOrder'): glyphOrder = font.glyphOrder else: glyphOrder = sorted(self.allGlyphs.keys()) self.glyphOrder = self.makeOfficialGlyphOrder(glyphOrder) # make a reusable bounding box self.fontBoundingBox = tuple( round(i) for i in self.makeFontBoundingBox()) # make a reusable character mapping self.unicodeToGlyphNameMapping = self.makeUnicodeToGlyphNameMapping()
def _getVerticalOrigin(glyph): height = glyph.height if (hasattr(glyph, "verticalOrigin") and glyph.verticalOrigin is not None): verticalOrigin = glyph.verticalOrigin else: verticalOrigin = height return round(verticalOrigin)
def setupTable_hmtx(self): """ Make the hmtx table. **This should not be called externally.** Subclasses may override or supplement this method to handle the table creation in a different way if desired. """ self.otf["hmtx"] = hmtx = newTable("hmtx") hmtx.metrics = {} for glyphName, glyph in self.allGlyphs.items(): width = glyph.width if width < 0: raise ValueError("The width should not be negative: '%s'" % (glyphName)) left = 0 if len(glyph) or len(glyph.components): # lsb should be consistent with glyf xMin, which is just # minimum x for coordinate data pen = ControlBoundsPen(self.ufo, ignoreSinglePoints=True) glyph.draw(pen) left = 0 if pen.bounds is None else pen.bounds[0] # take floor of lsb/xMin, as fontTools does with min bounds hmtx[glyphName] = (round(width), int(math.floor(left)))
def setupTable_vmtx(self): """ Make the vmtx table. **This should not be called externally.** Subclasses may override or supplement this method to handle the table creation in a different way if desired. """ self.otf["vmtx"] = vmtx = newTable("vmtx") vmtx.metrics = {} for glyphName, glyph in self.allGlyphs.items(): height = glyph.height verticalOrigin = _getVerticalOrigin(glyph) top = 0 if len(glyph) or len(glyph.components): # tsb should be consistent with glyf yMax, which is just # maximum y for coordinate data pen = ControlBoundsPen(self.ufo, ignoreSinglePoints=True) glyph.draw(pen) if pen.bounds is not None: top = pen.bounds[3] # take ceil of tsb/yMax, as fontTools does with max bounds vmtx[glyphName] = (round(height), int(math.ceil(verticalOrigin) - math.ceil(top)))
def makeGlyphsBoundingBoxes(self): """ Make bounding boxes for all the glyphs, and return a dictionary of BoundingBox(xMin, xMax, yMin, yMax) namedtuples keyed by glyph names. The bounding box of empty glyphs (without contours or components) is set to None. Float values are rounded to integers using the built-in round(). **This should not be called externally.** Subclasses may override this method to handle the bounds creation in a different way if desired. """ def getControlPointBounds(glyph): pen.init() glyph.draw(pen) return pen.bounds glyphBoxes = {} pen = ControlBoundsPen(self.allGlyphs) for glyphName, glyph in self.allGlyphs.items(): bounds = None if glyph or glyph.components: bounds = getControlPointBounds(glyph) if bounds: bounds = BoundingBox(*(round(v) for v in bounds)) glyphBoxes[glyphName] = bounds return glyphBoxes
def build_gdef(ufo): """Build a table GDEF statement for ligature carets.""" bases, ligatures, marks, carets = set(), set(), set(), {} for glyph in ufo: has_attaching_anchor = False for anchor in glyph.anchors: name = anchor.get('name') if name and not name.startswith('_'): has_attaching_anchor = True if name and name.startswith('caret_') and 'x' in anchor: carets.setdefault(glyph.name, []).append(round(anchor['x'])) glyphinfo = glyphsLib.glyphdata.get_glyph(glyph.name) if glyphinfo: category, subCategory = glyphinfo.category, glyphinfo.subCategory else: category, subCategory = None, None # Glyphs.app assigns glyph classes like this: # # * Base: any glyph that has an attaching anchor # (such as "top"; "_top" does not count) and is neither # classified as Ligature nor Mark using the definitions below; # # * Ligature: if subCategory is "Ligature" and the glyph has # at least one attaching anchor; # # * Mark: if category is "Mark" and subCategory is either # "Nonspacing" or "Spacing Combining"; # # * Compound: never assigned by Glyphs.app. # # https://github.com/googlei18n/glyphsLib/issues/85 # https://github.com/googlei18n/glyphsLib/pull/100#issuecomment-275430289 if subCategory == 'Ligature' and has_attaching_anchor: ligatures.add(glyph.name) elif category == 'Mark' and (subCategory == 'Nonspacing' or subCategory == 'Spacing Combining'): marks.add(glyph.name) elif has_attaching_anchor: bases.add(glyph.name) if not any((bases, ligatures, marks, carets)): return None lines = ['table GDEF {', ' # automatic'] glyphOrder = ufo.lib[PUBLIC_PREFIX + 'glyphOrder'] glyphIndex = lambda glyph: glyphOrder.index(glyph) fmt = lambda g: ('[%s]' % ' '.join(sorted(g, key=glyphIndex))) if g else '' lines.extend([ ' GlyphClassDef', ' %s, # Base' % fmt(bases), ' %s, # Liga' % fmt(ligatures), ' %s, # Mark' % fmt(marks), ' ;' ]) for glyph, caretPos in sorted(carets.items()): lines.append(' LigatureCaretByPos %s %s;' % (glyph, ' '.join(unicode(p) for p in sorted(caretPos)))) lines.append('} GDEF;') return '\n'.join(lines)
def addComponent(self, baseGlyphName, transformation, identifier=None, **kwargs): self.data.append("base:%s" % baseGlyphName) for i, v in enumerate(transformation): if transformation[i] != self.DEFAULT_TRANSFORM[i]: self.data.append(str(round(v, 9))) self.data.append("w%s" % self.width) glyph = self.glyphset[baseGlyphName] glyph.drawPoints(self)
def makeMissingRequiredGlyphs(font, glyphSet): """ Add .notdef to the glyph set if it is not present. **This should not be called externally.** Subclasses may override this method to handle the glyph creation in a different way if desired. """ if ".notdef" in glyphSet: return unitsPerEm = round(getAttrWithFallback(font.info, "unitsPerEm")) ascender = round(getAttrWithFallback(font.info, "ascender")) descender = round(getAttrWithFallback(font.info, "descender")) defaultWidth = round(unitsPerEm * 0.5) glyphSet[".notdef"] = StubGlyph(name=".notdef", width=defaultWidth, unitsPerEm=unitsPerEm, ascender=ascender, descender=descender)
def _to_glyphs_color(color): # If the color matches one of Glyphs's predefined colors, return that # index. for index, glyphs_color in enumerate(GLYPHS_COLORS): if str(color) == glyphs_color: return index # Otherwise, make a Glyphs-formatted RGBA color list: [u8, u8, u8, u8]. # Glyphs up to version 2.5.1 always set the alpha channel to 1. It should # round-trip the actual value in later versions. # https://github.com/googlefonts/glyphsLib/pull/363#issuecomment-390418497 return [round(component * 255) for component in tuple(color)]
def addComponent(self, baseGlyphName, transformation, identifier=None, **kwargs): self.data.append("base:%s" % baseGlyphName) for v in transformation: self.data.append(str(norm_float(round(v, 9)))) self.data.append("w%s" % self.width) glyph = self.glyphset[baseGlyphName] glyph.drawPoints(self)
def setupTable_vhea(self): """ Make the vhea table. **This should not be called externally.** Subclasses may override or supplement this method to handle the table creation in a different way if desired. """ self.otf["vhea"] = vhea = newTable("vhea") font = self.ufo head = self.otf["head"] vhea.tableVersion = 0x00011000 # horizontal metrics vhea.ascent = round( getAttrWithFallback(font.info, "openTypeVheaVertTypoAscender")) vhea.descent = round( getAttrWithFallback(font.info, "openTypeVheaVertTypoDescender")) vhea.lineGap = round( getAttrWithFallback(font.info, "openTypeVheaVertTypoLineGap")) # vertical metrics heights = [] tops = [] bottoms = [] for glyph in self.allGlyphs.values(): top = glyph.topMargin bottom = glyph.rightMargin if top is None: top = 0 if bottom is None: bottom = 0 heights.append(glyph.height) tops.append(top) bottoms.append(bottom) vhea.advanceHeightMax = round(max(heights)) vhea.minTopSideBearing = round(max(tops)) vhea.minBottomSideBearing = round(max(bottoms)) vhea.yMaxExtent = round(vhea.minTopSideBearing - (head.yMax - head.yMin)) # misc vhea.caretSlopeRise = getAttrWithFallback( font.info, "openTypeVheaCaretSlopeRise") vhea.caretSlopeRun = getAttrWithFallback(font.info, "openTypeVheaCaretSlopeRun") vhea.caretOffset = getAttrWithFallback(font.info, "openTypeVheaCaretOffset") vhea.reserved0 = 0 vhea.reserved1 = 0 vhea.reserved2 = 0 vhea.reserved3 = 0 vhea.reserved4 = 0 vhea.metricDataFormat = 0 # glyph count vhea.numberOfVMetrics = len(self.allGlyphs)
def setupTable_vmtx(self): """ Make the vmtx table. **This should not be called externally.** Subclasses may override or supplement this method to handle the table creation in a different way if desired. """ self.otf["vmtx"] = vmtx = newTable("vmtx") vmtx.metrics = {} for glyphName, glyph in self.allGlyphs.items(): height = glyph.height verticalOrigin = _getVerticalOrigin(glyph) bounds = self.glyphBoundingBoxes[glyphName] top = bounds.yMax if bounds else 0 vmtx[glyphName] = (round(height), verticalOrigin - top)
def setupTable_hmtx(self): """ Make the hmtx table. **This should not be called externally.** Subclasses may override or supplement this method to handle the table creation in a different way if desired. """ self.otf["hmtx"] = hmtx = newTable("hmtx") hmtx.metrics = {} for glyphName, glyph in self.allGlyphs.items(): width = glyph.width if width < 0: raise ValueError("The width should not be negative: '%s'" % (glyphName)) bounds = self.glyphBoundingBoxes[glyphName] left = bounds.xMin if bounds else 0 hmtx[glyphName] = (round(width), left)
def getCharStringForGlyph(self, glyph, private, globalSubrs): """ Get a Type2CharString for the *glyph* **This should not be called externally.** Subclasses may override this method to handle the charstring creation in a different way if desired. """ width = glyph.width # subtract the nominal width postscriptNominalWidthX = getAttrWithFallback( self.ufo.info, "postscriptNominalWidthX") if postscriptNominalWidthX: width = width - postscriptNominalWidthX # round width = round(width) pen = T2CharStringPen(width, self.allGlyphs) glyph.draw(pen) charString = pen.getCharString(private, globalSubrs) return charString
def clientInitData(self): txFont = self.parentFont.clientFont try: glyph_set = txFont.getGlyphSet() except KeyError: glyph_set = None 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(glyph_set) 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(glyph_set) charstring.draw(pen) self.xAdvance = charstring.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 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 _build_gdef(ufo): """Build a GDEF table statement (GlyphClassDef and LigatureCaretByPos). Building GlyphClassDef requires anchor propagation or user care to work as expected, as Glyphs.app also looks at anchors for classification: * Base: any glyph that has an attaching anchor (such as "top"; "_top" does not count) and is neither classified as Ligature nor Mark using the definitions below; * Ligature: if subCategory is "Ligature" and the glyph has at least one attaching anchor; * Mark: if category is "Mark" and subCategory is either "Nonspacing" or "Spacing Combining"; * Compound: never assigned by Glyphs.app. See: * https://github.com/googlei18n/glyphsLib/issues/85 * https://github.com/googlei18n/glyphsLib/pull/100#issuecomment-275430289 """ from glyphsLib import glyphdata bases, ligatures, marks, carets = set(), set(), set(), {} category_key = GLYPHLIB_PREFIX + "category" subCategory_key = GLYPHLIB_PREFIX + "subCategory" for glyph in ufo: has_attaching_anchor = False for anchor in glyph.anchors: name = anchor.name if name and not name.startswith("_"): has_attaching_anchor = True if name and name.startswith("caret_") and "x" in anchor: carets.setdefault(glyph.name, []).append(round(anchor["x"])) # First check glyph.lib for category/subCategory overrides. Otherwise, # use global values from GlyphData. glyphinfo = glyphdata.get_glyph(glyph.name) category = glyph.lib.get(category_key) or glyphinfo.category subCategory = glyph.lib.get(subCategory_key) or glyphinfo.subCategory if subCategory == "Ligature" and has_attaching_anchor: ligatures.add(glyph.name) elif category == "Mark" and (subCategory == "Nonspacing" or subCategory == "Spacing Combining"): marks.add(glyph.name) elif has_attaching_anchor: bases.add(glyph.name) if not any((bases, ligatures, marks, carets)): return None def fmt(g): return ("[%s]" % " ".join(sorted(g, key=ufo.glyphOrder.index))) if g else "" lines = [ "table GDEF {", " # automatic", " GlyphClassDef", " %s, # Base" % fmt(bases), " %s, # Liga" % fmt(ligatures), " %s, # Mark" % fmt(marks), " ;", ] for glyph, caretPos in sorted(carets.items()): lines.append(" LigatureCaretByPos %s %s;" % (glyph, " ".join(unicode(p) for p in sorted(caretPos)))) lines.append("} GDEF;") return "\n".join(lines)
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 setupTable_CFF(self): """Make the CFF table.""" self.otf["CFF "] = cff = newTable("CFF ") cff = cff.cff # set up the basics cff.major = 1 cff.minor = 0 cff.hdrSize = 4 cff.offSize = 4 cff.fontNames = [] strings = IndexedStrings() cff.strings = strings private = PrivateDict(strings=strings) private.rawDict.update(private.defaults) globalSubrs = GlobalSubrsIndex(private=private) topDict = TopDict(GlobalSubrs=globalSubrs, strings=strings) topDict.Private = private charStrings = topDict.CharStrings = CharStrings( file=None, charset=None, globalSubrs=globalSubrs, private=private, fdSelect=None, fdArray=None) charStrings.charStringsAreIndexed = True topDict.charset = [] charStringsIndex = charStrings.charStringsIndex = SubrsIndex( private=private, globalSubrs=globalSubrs) cff.topDictIndex = topDictIndex = TopDictIndex() topDictIndex.append(topDict) topDictIndex.strings = strings cff.GlobalSubrs = globalSubrs # populate naming data info = self.ufo.info psName = getAttrWithFallback(info, "postscriptFontName") cff.fontNames.append(psName) topDict = cff.topDictIndex[0] topDict.version = "%d.%d" % (getAttrWithFallback( info, "versionMajor"), getAttrWithFallback(info, "versionMinor")) trademark = getAttrWithFallback(info, "trademark") if trademark: trademark = normalizeStringForPostscript( trademark.replace("\u00A9", "Copyright")) if trademark != self.ufo.info.trademark: self.log.append( "[Warning] The trademark was normalized for storage in the CFF table and consequently some characters were dropped: '%s'" % trademark) if trademark is None: trademark = "" topDict.Notice = trademark copyright = getAttrWithFallback(info, "copyright") if copyright: copyright = normalizeStringForPostscript( copyright.replace("\u00A9", "Copyright")) if copyright != self.ufo.info.copyright: self.log.append( "[Warning] The copyright was normalized for storage in the CFF table and consequently some characters were dropped: '%s'" % copyright) if copyright is None: copyright = "" topDict.Copyright = copyright topDict.FullName = getAttrWithFallback(info, "postscriptFullName") topDict.FamilyName = getAttrWithFallback( info, "openTypeNamePreferredFamilyName") topDict.Weight = getAttrWithFallback(info, "postscriptWeightName") topDict.FontName = psName # populate various numbers topDict.isFixedPitch = getAttrWithFallback(info, "postscriptIsFixedPitch") topDict.ItalicAngle = getAttrWithFallback(info, "italicAngle") underlinePosition = getAttrWithFallback(info, "postscriptUnderlinePosition") topDict.UnderlinePosition = round(underlinePosition) underlineThickness = getAttrWithFallback( info, "postscriptUnderlineThickness") topDict.UnderlineThickness = round(underlineThickness) # populate font matrix unitsPerEm = round(getAttrWithFallback(info, "unitsPerEm")) topDict.FontMatrix = [1.0 / unitsPerEm, 0, 0, 1.0 / unitsPerEm, 0, 0] # populate the width values defaultWidthX = round( getAttrWithFallback(info, "postscriptDefaultWidthX")) if defaultWidthX: private.rawDict["defaultWidthX"] = defaultWidthX nominalWidthX = round( getAttrWithFallback(info, "postscriptNominalWidthX")) if nominalWidthX: private.rawDict["nominalWidthX"] = nominalWidthX # populate hint data blueFuzz = round(getAttrWithFallback(info, "postscriptBlueFuzz")) blueShift = round(getAttrWithFallback(info, "postscriptBlueShift")) blueScale = getAttrWithFallback(info, "postscriptBlueScale") forceBold = getAttrWithFallback(info, "postscriptForceBold") blueValues = getAttrWithFallback(info, "postscriptBlueValues") if isinstance(blueValues, list): blueValues = [round(i) for i in blueValues] otherBlues = getAttrWithFallback(info, "postscriptOtherBlues") if isinstance(otherBlues, list): otherBlues = [round(i) for i in otherBlues] familyBlues = getAttrWithFallback(info, "postscriptFamilyBlues") if isinstance(familyBlues, list): familyBlues = [round(i) for i in familyBlues] familyOtherBlues = getAttrWithFallback(info, "postscriptFamilyOtherBlues") if isinstance(familyOtherBlues, list): familyOtherBlues = [round(i) for i in familyOtherBlues] stemSnapH = getAttrWithFallback(info, "postscriptStemSnapH") if isinstance(stemSnapH, list): stemSnapH = [round(i) for i in stemSnapH] stemSnapV = getAttrWithFallback(info, "postscriptStemSnapV") if isinstance(stemSnapV, list): stemSnapV = [round(i) for i in stemSnapV] # only write the blues data if some blues are defined. if (blueValues or otherBlues): private.rawDict["BlueFuzz"] = blueFuzz private.rawDict["BlueShift"] = blueShift private.rawDict["BlueScale"] = blueScale private.rawDict["ForceBold"] = forceBold private.rawDict["BlueValues"] = blueValues private.rawDict["OtherBlues"] = otherBlues private.rawDict["FamilyBlues"] = familyBlues private.rawDict["FamilyOtherBlues"] = familyOtherBlues # only write the stems if both are defined. if (stemSnapH and stemSnapV): private.rawDict["StemSnapH"] = stemSnapH private.rawDict["StdHW"] = stemSnapH[0] private.rawDict["StemSnapV"] = stemSnapV private.rawDict["StdVW"] = stemSnapV[0] # populate glyphs for glyphName in self.glyphOrder: glyph = self.allGlyphs[glyphName] unicodes = glyph.unicodes charString = self.getCharStringForGlyph(glyph, private, globalSubrs) # add to the font if glyphName in charStrings: # XXX a glyph already has this name. should we choke? glyphID = charStrings.charStrings[glyphName] charStringsIndex.items[glyphID] = charString else: charStringsIndex.append(charString) glyphID = len(topDict.charset) charStrings.charStrings[glyphName] = glyphID topDict.charset.append(glyphName) topDict.FontBBox = self.fontBoundingBox # write the glyph order self.otf.setGlyphOrder(self.glyphOrder)
def _point(self, point): if self.round_coords: return " ".join("%d" % round(pt) for pt in point) return " ".join("%3f" % pt for pt in point)
def __init__(self, glyph): self.glyphset = getattr(glyph, "glyphSet", None) self.width = norm_float(round(getattr(glyph, "width", 0), 9)) self.data = ["w%s" % self.width]
def setupTable_hhea(self): """ Make the hhea table. **This should not be called externally.** Subclasses may override or supplement this method to handle the table creation in a different way if desired. """ self.otf["hhea"] = hhea = newTable("hhea") font = self.ufo hhea.tableVersion = 0x00010000 # vertical metrics hhea.ascent = round( getAttrWithFallback(font.info, "openTypeHheaAscender")) hhea.descent = round( getAttrWithFallback(font.info, "openTypeHheaDescender")) hhea.lineGap = round( getAttrWithFallback(font.info, "openTypeHheaLineGap")) # horizontal metrics widths = [] lefts = [] rights = [] extents = [] for glyph in self.allGlyphs.values(): left = glyph.leftMargin right = glyph.rightMargin if left is None: left = 0 if right is None: right = 0 widths.append(glyph.width) lefts.append(left) rights.append(right) # robofab if hasattr(glyph, "box"): bounds = glyph.box # others else: bounds = glyph.bounds if bounds is not None: xMin, yMin, xMax, yMax = bounds else: xMin = 0 xMax = 0 extent = left + ( xMax - xMin ) # equation from spec for calculating xMaxExtent: Max(lsb + (xMax - xMin)) extents.append(extent) hhea.advanceWidthMax = round(max(widths)) hhea.minLeftSideBearing = round(min(lefts)) hhea.minRightSideBearing = round(min(rights)) hhea.xMaxExtent = round(max(extents)) # misc hhea.caretSlopeRise = getAttrWithFallback( font.info, "openTypeHheaCaretSlopeRise") hhea.caretSlopeRun = getAttrWithFallback(font.info, "openTypeHheaCaretSlopeRun") hhea.caretOffset = round( getAttrWithFallback(font.info, "openTypeHheaCaretOffset")) hhea.reserved0 = 0 hhea.reserved1 = 0 hhea.reserved2 = 0 hhea.reserved3 = 0 hhea.metricDataFormat = 0 # glyph count hhea.numberOfHMetrics = len(self.allGlyphs)
def setupTable_OS2(self): """ Make the OS/2 table. **This should not be called externally.** Subclasses may override or supplement this method to handle the table creation in a different way if desired. """ self.otf["OS/2"] = os2 = newTable("OS/2") font = self.ufo os2.version = 0x0004 # average glyph width widths = [ glyph.width for glyph in self.allGlyphs.values() if glyph.width > 0 ] os2.xAvgCharWidth = round(sum(widths) / len(widths)) # weight and width classes os2.usWeightClass = getAttrWithFallback(font.info, "openTypeOS2WeightClass") os2.usWidthClass = getAttrWithFallback(font.info, "openTypeOS2WidthClass") # embedding os2.fsType = intListToNum( getAttrWithFallback(font.info, "openTypeOS2Type"), 0, 16) # subscript, superscript, strikeout values, taken from AFDKO: # FDK/Tools/Programs/makeotf/makeotf_lib/source/hotconv/hot.c unitsPerEm = getAttrWithFallback(font.info, "unitsPerEm") italicAngle = getAttrWithFallback(font.info, "italicAngle") xHeight = getAttrWithFallback(font.info, "xHeight") def adjustOffset(offset, angle): """Adjust Y offset based on italic angle, to get X offset.""" return offset * math.tan(math.radians(-angle)) if angle else 0 v = getAttrWithFallback(font.info, "openTypeOS2SubscriptXSize") if v is None: v = unitsPerEm * 0.65 os2.ySubscriptXSize = round(v) v = getAttrWithFallback(font.info, "openTypeOS2SubscriptYSize") if v is None: v = unitsPerEm * 0.6 os2.ySubscriptYSize = round(v) v = getAttrWithFallback(font.info, "openTypeOS2SubscriptYOffset") if v is None: v = unitsPerEm * 0.075 os2.ySubscriptYOffset = round(v) v = getAttrWithFallback(font.info, "openTypeOS2SubscriptXOffset") if v is None: v = adjustOffset(-os2.ySubscriptYOffset, italicAngle) os2.ySubscriptXOffset = round(v) v = getAttrWithFallback(font.info, "openTypeOS2SuperscriptXSize") if v is None: v = os2.ySubscriptXSize os2.ySuperscriptXSize = round(v) v = getAttrWithFallback(font.info, "openTypeOS2SuperscriptYSize") if v is None: v = os2.ySubscriptYSize os2.ySuperscriptYSize = round(v) v = getAttrWithFallback(font.info, "openTypeOS2SuperscriptYOffset") if v is None: v = unitsPerEm * 0.35 os2.ySuperscriptYOffset = round(v) v = getAttrWithFallback(font.info, "openTypeOS2SuperscriptXOffset") if v is None: v = adjustOffset(os2.ySuperscriptYOffset, italicAngle) os2.ySuperscriptXOffset = round(v) v = getAttrWithFallback(font.info, "openTypeOS2StrikeoutSize") if v is None: v = getAttrWithFallback(font.info, "postscriptUnderlineThickness") os2.yStrikeoutSize = round(v) v = getAttrWithFallback(font.info, "openTypeOS2StrikeoutPosition") if v is None: v = xHeight * 0.6 if xHeight else unitsPerEm * 0.22 os2.yStrikeoutPosition = round(v) # family class ibmFontClass, ibmFontSubclass = getAttrWithFallback( font.info, "openTypeOS2FamilyClass") os2.sFamilyClass = (ibmFontClass << 8) + ibmFontSubclass # panose data = getAttrWithFallback(font.info, "openTypeOS2Panose") panose = Panose() panose.bFamilyType = data[0] panose.bSerifStyle = data[1] panose.bWeight = data[2] panose.bProportion = data[3] panose.bContrast = data[4] panose.bStrokeVariation = data[5] panose.bArmStyle = data[6] panose.bLetterForm = data[7] panose.bMidline = data[8] panose.bXHeight = data[9] os2.panose = panose # Unicode ranges uniRanges = getAttrWithFallback(font.info, "openTypeOS2UnicodeRanges") os2.ulUnicodeRange1 = intListToNum(uniRanges, 0, 32) os2.ulUnicodeRange2 = intListToNum(uniRanges, 32, 32) os2.ulUnicodeRange3 = intListToNum(uniRanges, 64, 32) os2.ulUnicodeRange4 = intListToNum(uniRanges, 96, 32) # codepage ranges codepageRanges = getAttrWithFallback(font.info, "openTypeOS2CodePageRanges") os2.ulCodePageRange1 = intListToNum(codepageRanges, 0, 32) os2.ulCodePageRange2 = intListToNum(codepageRanges, 32, 32) # vendor id os2.achVendID = tounicode(getAttrWithFallback(font.info, "openTypeOS2VendorID"), encoding="ascii", errors="ignore") # vertical metrics os2.sxHeight = round(getAttrWithFallback(font.info, "xHeight")) os2.sCapHeight = round(getAttrWithFallback(font.info, "capHeight")) os2.sTypoAscender = round( getAttrWithFallback(font.info, "openTypeOS2TypoAscender")) os2.sTypoDescender = round( getAttrWithFallback(font.info, "openTypeOS2TypoDescender")) os2.sTypoLineGap = round( getAttrWithFallback(font.info, "openTypeOS2TypoLineGap")) os2.usWinAscent = round( getAttrWithFallback(font.info, "openTypeOS2WinAscent")) os2.usWinDescent = round( getAttrWithFallback(font.info, "openTypeOS2WinDescent")) # style mapping selection = list(getAttrWithFallback(font.info, "openTypeOS2Selection")) styleMapStyleName = getAttrWithFallback(font.info, "styleMapStyleName") if styleMapStyleName == "regular": selection.append(6) elif styleMapStyleName == "bold": selection.append(5) elif styleMapStyleName == "italic": selection.append(0) elif styleMapStyleName == "bold italic": selection += [0, 5] os2.fsSelection = intListToNum(selection, 0, 16) # characetr indexes unicodes = [ i for i in self.unicodeToGlyphNameMapping.keys() if i is not None ] if unicodes: minIndex = min(unicodes) maxIndex = max(unicodes) else: # the font may have *no* unicode values (it really happens!) so # there needs to be a fallback. use 0xFFFF, as AFDKO does: # FDK/Tools/Programs/makeotf/makeotf_lib/source/hotconv/map.c minIndex = 0xFFFF maxIndex = 0xFFFF if maxIndex > 0xFFFF: # the spec says that 0xFFFF should be used # as the max if the max exceeds 0xFFFF maxIndex = 0xFFFF os2.fsFirstCharIndex = minIndex os2.fsLastCharIndex = maxIndex os2.usBreakChar = 32 os2.usDefaultChar = 0 # maximum contextual lookup length os2.usMaxContex = 0
def setupTable_head(self): """ Make the head table. **This should not be called externally.** Subclasses may override or supplement this method to handle the table creation in a different way if desired. """ self.otf["head"] = head = newTable("head") font = self.ufo head.checkSumAdjustment = 0 head.tableVersion = 1.0 head.magicNumber = 0x5F0F3CF5 # version numbers # limit minor version to 3 digits as recommended in OpenType spec: # https://www.microsoft.com/typography/otspec/recom.htm versionMajor = getAttrWithFallback(font.info, "versionMajor") versionMinor = getAttrWithFallback(font.info, "versionMinor") fullFontRevision = float("%d.%03d" % (versionMajor, versionMinor)) head.fontRevision = round(fullFontRevision, 3) if head.fontRevision != fullFontRevision: logger.warning( "Minor version in %s has too many digits and won't fit into " "the head table's fontRevision field; rounded to %s.", fullFontRevision, head.fontRevision) # upm head.unitsPerEm = getAttrWithFallback(font.info, "unitsPerEm") # times head.created = dateStringToTimeValue( getAttrWithFallback(font.info, "openTypeHeadCreated")) - mac_epoch_diff head.modified = dateStringToTimeValue( dateStringForNow()) - mac_epoch_diff # bounding box xMin, yMin, xMax, yMax = self.fontBoundingBox head.xMin = xMin head.yMin = yMin head.xMax = xMax head.yMax = yMax # style mapping styleMapStyleName = getAttrWithFallback(font.info, "styleMapStyleName") macStyle = [] if styleMapStyleName == "bold": macStyle = [0] elif styleMapStyleName == "bold italic": macStyle = [0, 1] elif styleMapStyleName == "italic": macStyle = [1] head.macStyle = intListToNum(macStyle, 0, 16) # misc head.flags = intListToNum( getAttrWithFallback(font.info, "openTypeHeadFlags"), 0, 16) head.lowestRecPPEM = round( getAttrWithFallback(font.info, "openTypeHeadLowestRecPPEM")) head.fontDirectionHint = 2 head.indexToLocFormat = 0 head.glyphDataFormat = 0
def toInt(value, else_callback): rounded = round(value) if tolerance >= 0.5 or abs(rounded - value) <= tolerance: return rounded else: return int(else_callback(value))
def scalePoint(p, resolution): return round(p[0] * resolution), round(p[1] * resolution)