Exemple #1
0
 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()
Exemple #2
0
    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
Exemple #3
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)
Exemple #4
0
    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)
Exemple #5
0
 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)))))
Exemple #6
0
 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)
Exemple #7
0
 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()
Exemple #8
0
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)
Exemple #9
0
    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)))
Exemple #10
0
    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)))
Exemple #11
0
    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
Exemple #12
0
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)
Exemple #13
0
    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)
Exemple #14
0
    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)
Exemple #15
0
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)]
Exemple #16
0
    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)
Exemple #17
0
    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)
Exemple #18
0
    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)
Exemple #19
0
    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)
Exemple #20
0
    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
Exemple #21
0
    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]
Exemple #22
0
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)
Exemple #23
0
	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
Exemple #24
0
    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)
Exemple #25
0
 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)
Exemple #26
0
 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]
Exemple #27
0
    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)
Exemple #28
0
    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
Exemple #29
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
Exemple #30
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))
Exemple #31
0
def scalePoint(p, resolution):
    return round(p[0] * resolution), round(p[1] * resolution)