Example #1
0
def getControlBounds(drawable: Drawable,
                     layer: GlyphSet | None) -> BoundingBox | None:
    pen = ControlBoundsPen(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)
Example #2
0
def getControlBounds(drawable: Drawable, layer: Any) -> Optional[BoundingBox]:
    # XXX: layer should behave like a mapping of glyph names to Glyph objects, but
    # cyclic imports...
    pen = ControlBoundsPen(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)
Example #3
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)))
Example #4
0
 def _get_controlPointBounds(self):
     from fontTools.pens.boundsPen import ControlBoundsPen
     if self._controlPointBoundsCache is None:
         pen = ControlBoundsPen(None)
         self.draw(pen)
         self._controlPointBoundsCache = pen.bounds
     return self._controlPointBoundsCache
Example #5
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
    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] = (_roundInt(height),
                               int(math.ceil(verticalOrigin) - math.ceil(top)))
Example #7
0
def _transformed_glyph_bounds(
        ufo: ufoLib2.Font, glyph_name: str,
        transform: Affine2D) -> Optional[Tuple[float, float, float, float]]:
    glyph = ufo[glyph_name]
    pen = bounds_pen = ControlBoundsPen(ufo)
    if not transform.almost_equals(Affine2D.identity()):
        pen = TransformPen(bounds_pen, transform)
    glyph.draw(pen)
    return bounds_pen.bounds
Example #8
0
 def glyph(self, glyph_name):
     if isinstance(self.wrapped, fontTools.ttLib.TTFont):
         return self.glyphset[glyph_name]
     elif isinstance(self.wrapped, fontTools.t1Lib.T1Font):
         glyph = self.glyphset[glyph_name]
         if not hasattr(glyph, "width"):
             glyph.draw(ControlBoundsPen(self.glyphset))
         glyph.lsb = 0
         return glyph
Example #9
0
def extractOpenTypeGlyphs(source, destination):
    # grab the cmap
    cmap = source["cmap"]
    vmtx = source.get("vmtx")
    vorg = source.get("VORG")
    preferred = [(3, 10, 12), (3, 10, 4), (3, 1, 12), (3, 1, 4), (0, 3, 12),
                 (0, 3, 4), (3, 0, 12), (3, 0, 4), (1, 0, 12), (1, 0, 4)]
    found = {}
    for table in cmap.tables:
        found[table.platformID, table.platEncID, table.format] = table
        table = None
    for key in preferred:
        if key not in found:
            continue
        table = found[key]
        break
    reversedMapping = {}
    if table is not None:
        for uniValue, glyphName in table.cmap.items():
            reversedMapping[glyphName] = uniValue
    # grab the glyphs
    glyphSet = source.getGlyphSet()
    for glyphName in glyphSet.keys():
        sourceGlyph = glyphSet[glyphName]
        # make the new glyph
        destination.newGlyph(glyphName)
        destinationGlyph = destination[glyphName]
        # outlines
        pen = destinationGlyph.getPen()
        sourceGlyph.draw(pen)
        # width
        destinationGlyph.width = sourceGlyph.width
        # height and vertical origin
        if vmtx is not None and glyphName in vmtx.metrics:
            destinationGlyph.height = vmtx[glyphName][0]
            if vorg is not None:
                if glyphName in vorg.VOriginRecords:
                    destinationGlyph.verticalOrigin = vorg[glyphName]
                else:
                    destinationGlyph.verticalOrigin = vorg.defaultVertOriginY
            else:
                tsb = vmtx[glyphName][1]
                bounds_pen = ControlBoundsPen(glyphSet)
                sourceGlyph.draw(bounds_pen)
                if bounds_pen.bounds is None:
                    continue
                xMin, yMin, xMax, yMax = bounds_pen.bounds
                destinationGlyph.verticalOrigin = tsb + yMax
        # unicode
        destinationGlyph.unicode = reversedMapping.get(glyphName)
Example #10
0
def glyphControlPointBoundsRepresentationFactory(glyph):
    # base glyph
    pen = ControlBoundsPen(glyph.getParent())
    glyph.draw(pen)
    bounds = pen.bounds
    # components
    for component in glyph.components:
        b = component.controlPointBounds
        if b is not None:
            if bounds is None:
                bounds = b
            else:
                bounds = unionRect(bounds, b)
    return bounds
Example #11
0
def extractOpenTypeGlyphs(source, destination, layerName="public.default"):
    # grab the cmap
    vmtx = source.get("vmtx")
    vorg = source.get("VORG")
    is_ttf = "glyf" in source
    reversedMapping = source.get("cmap").buildReversed()
    # add layers
    if layerName != destination.defaultLayerName:
        destination.newLayer(layerName)
    # grab the glyphs
    glyphSet = source.getGlyphSet()
    for glyphName in glyphSet.keys():
        sourceGlyph = glyphSet[glyphName]
        # make the new glyph
        destinationLayer = destination.getLayer(layerName)
        destinationLayer.newGlyph(glyphName)
        destinationGlyph = destinationLayer[glyphName]
        # outlines
        if is_ttf:
            pen = destinationGlyph.getPointPen()
            sourceGlyph.drawPoints(pen)
        else:
            pen = destinationGlyph.getPen()
            sourceGlyph.draw(pen)
        # width
        destinationGlyph.width = sourceGlyph.width
        # height and vertical origin
        if vmtx is not None and glyphName in vmtx.metrics:
            destinationGlyph.height = vmtx[glyphName][0]
            if vorg is not None:
                if glyphName in vorg.VOriginRecords:
                    destinationGlyph.verticalOrigin = vorg[glyphName]
                else:
                    destinationGlyph.verticalOrigin = vorg.defaultVertOriginY
            else:
                tsb = vmtx[glyphName][1]
                bounds_pen = ControlBoundsPen(glyphSet)
                sourceGlyph.draw(bounds_pen)
                if bounds_pen.bounds is None:
                    continue
                xMin, yMin, xMax, yMax = bounds_pen.bounds
                destinationGlyph.verticalOrigin = tsb + yMax
        # unicodes
        destinationGlyph.unicodes = reversedMapping.get(glyphName, [])
Example #12
0
def estimateCOLRv1BoundingBoxes(vcFont, ttFont, neutralOnly):
    from fontTools.pens.ttGlyphPen import TTGlyphPointPen
    from fontTools.pens.boundsPen import ControlBoundsPen

    locations = [{}]
    if not neutralOnly:
        for axis in ttFont["fvar"].axes:
            if axis.flags & 0x0001:
                # hidden axis
                continue

            values = {0}
            if axis.minValue < axis.defaultValue:
                values.add(-1)
            if axis.defaultValue < axis.maxValue:
                values.add(1)
            locations = [
                dictUpdate(loc, axis.axisTag, v) for loc in locations
                for v in sorted(values)
            ]
    glyfTable = ttFont["glyf"]
    gvarTable = ttFont["gvar"]
    hmtxTable = ttFont["hmtx"]
    # TODO: fix tsb if we have "vmtx"
    for glyphName in sorted(vcFont.keys()):
        glyph = vcFont[glyphName]
        if not glyph.components or not glyph.outline.isEmpty():
            continue

        # calculate the bounding box that would fit on all locations
        bpen = ControlBoundsPen(None)
        for loc in locations:
            vcFont.drawGlyph(bpen, glyphName, loc)
        gvarTable.variations.pop(glyphName, None)
        pen = TTGlyphPointPen(None)
        if bpen.bounds is not None:
            bounds = intRect(bpen.bounds)
            for pt in [bounds[:2], bounds[2:]]:
                pen.beginPath()
                pen.addPoint(pt, segmentType="line")
                pen.endPath()
        glyfTable[glyphName] = pen.glyph()
        adv, lsb = hmtxTable.metrics[glyphName]
        hmtxTable.metrics[glyphName] = adv, bounds[0]
Example #13
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.

        Check that the float values are within the range of the specified
        self.roundTolerance, and if so use the rounded value; else take the
        floor or ceiling to ensure that the bounding box encloses the original
        values.
        """
        def getControlPointBounds(glyph):
            pen.init()
            glyph.draw(pen)
            return pen.bounds

        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))

        tolerance = self.roundTolerance
        glyphBoxes = {}
        pen = ControlBoundsPen(self.allGlyphs)
        for glyphName, glyph in self.allGlyphs.items():
            bounds = None
            if glyph or glyph.components:
                bounds = getControlPointBounds(glyph)
                if bounds:
                    rounded = []
                    for value in bounds[:2]:
                        rounded.append(toInt(value, math.floor))
                    for value in bounds[2:]:
                        rounded.append(toInt(value, math.ceil))
                    bounds = BoundingBox(*rounded)
            glyphBoxes[glyphName] = bounds
        return glyphBoxes
Example #14
0
 def test_ignoreSinglePoint(self):
     pen = ControlBoundsPen(None, ignoreSinglePoints=True)
     pen.moveTo((0, 10))
     self.assertEqual(None, pen.bounds)
Example #15
0
 def test_singlePoint(self):
     pen = ControlBoundsPen(None)
     pen.moveTo((-5, 10))
     self.assertEqual("-5 10 -5 10", bounds_(pen))
Example #16
0
 def test_quadraticCurve(self):
     pen = ControlBoundsPen(None)
     pen.moveTo((0, 0))
     pen.qCurveTo((6, 6), (10, 0))
     self.assertEqual("0 0 10 6", bounds_(pen))
Example #17
0
 def test_singlePoint(self):
     pen = ControlBoundsPen(None)
     pen.moveTo((-5, 10))
     self.assertEqual("-5 10 -5 10", bounds_(pen))
Example #18
0
 def test_curve(self):
     pen = ControlBoundsPen(None)
     pen.moveTo((0, 0))
     pen.curveTo((20, 10), (90, 40), (0, 0))
     self.assertEqual("0 0 90 40", bounds_(pen))
def contourControlPointBoundsRepresentationFactory(obj):
    pen = ControlBoundsPen(None)
    obj.draw(pen)
    return pen.bounds
Example #20
0
 def _get_controlPointBounds(self):
     pen = ControlBoundsPen(None)
     self.draw(pen)
     return pen.bounds
Example #21
0
 def test_ignoreSinglePoint(self):
     pen = ControlBoundsPen(None, ignoreSinglePoints=True)
     pen.moveTo((0, 10))
     self.assertEqual(None, pen.bounds)
Example #22
0
 def test_curve(self):
     pen = ControlBoundsPen(None)
     pen.moveTo((0, 0))
     pen.curveTo((20, 10), (90, 40), (0, 0))
     self.assertEqual("0 0 90 40", bounds_(pen))
def componentPointBoundsRepresentationFactory(obj):
    pen = ControlBoundsPen(obj.layer)
    obj.draw(pen)
    return pen.bounds
Example #24
0
 def test_quadraticCurve(self):
     pen = ControlBoundsPen(None)
     pen.moveTo((0, 0))
     pen.qCurveTo((6, 6), (10, 0))
     self.assertEqual("0 0 10 6", bounds_(pen))
Example #25
0
 def test_draw(self):
     pen = ControlBoundsPen(None)
     draw_(pen)
     self.assertEqual("-55 0 60 100", bounds_(pen))
Example #26
0
 def test_empty(self):
     pen = ControlBoundsPen(None)
     self.assertEqual(None, pen.bounds)
Example #27
0
 def getControlBounds(self, layer=None):
     pen = ControlBoundsPen(layer)
     # raise 'KeyError' when a referenced component is missing from glyph set
     pen.skipMissingComponents = False
     self.draw(pen)
     return None if pen.bounds is None else BoundingBox(*pen.bounds)
Example #28
0
def glyphControlPointBoundsRepresentationFactory(glyph):
    pen = ControlBoundsPen(glyph.layer)
    glyph.draw(pen)
    return pen.bounds