def italicizeGlyph(f, g, angle=10, stemWidth=185, meanYCenter=-825, narrowAmount=1): unic = g.unicode #save unicode glyph = f[g.name] slope = np.tanh(math.pi * angle / 180) # determine how far on the x axis the glyph should slide # to compensate for the slant. # meanYCenter: # -600 is a magic number that assumes a 2048 unit em square, # and -825 for a 2816 unit em square. (UPM*0.29296875) m = Transform(1, 0, slope, 1, 0, 0) xoffset, junk = m.transformPoint((0, meanYCenter)) m = Transform(narrowAmount, 0, slope, 1, xoffset, 0) if len(glyph) > 0: g2 = italicize(f[g.name], angle, xoffset=xoffset, stemWidth=stemWidth) f.insertGlyph(g2, g.name) transformFLGlyphMembers(f[g.name], m) if unic > 0xFFFF: #restore unicode g.unicode = unic
def getGlyph(self, glyph, skew, rotation, addComponents=False): skew = radians(skew) rotation = radians(-rotation) dest = glyph.copy() if not addComponents: for component in dest.components: pointPen = DecomposePointPen(glyph.getParent(), dest.getPointPen(), component.transformation) component.drawPoints(pointPen) dest.removeComponent(component) for contour in list(dest): if contour.open: dest.removeContour(contour) if skew == 0 and rotation == 0: return dest for contour in dest: for bPoint in contour.bPoints: bcpIn = bPoint.bcpIn bcpOut = bPoint.bcpOut if bcpIn == (0, 0): continue if bcpOut == (0, 0): continue if bcpIn[0] == bcpOut[0] and bcpIn[1] != bcpOut[1]: bPoint.anchorLabels = ["extremePoint"] if rotation and bcpIn[0] != bcpOut[0] and bcpIn[1] == bcpOut[1]: bPoint.anchorLabels = ["extremePoint"] cx, cy = 0, 0 box = glyph.box if box: cx = box[0] + (box[2] - box[0]) * .5 cy = box[1] + (box[3] - box[1]) * .5 t = Transform() t = t.skew(skew) t = t.translate(cx, cy).rotate(rotation).translate(-cx, -cy) # RF3 if version >= "3.0": dest.transformBy(tuple(t)) # RF1 else: dest.transform(t) dest.extremePoints(round=0) for contour in dest: for point in contour.points: if "extremePoint" in point.labels: point.selected = True point.smooth = True else: point.selected = False dest.removeSelection() dest.round() return dest
def getKernel(radius, amplitude=1, depth=50, angle=0): """ >>> a = getKernel(5) >>> a[(0,0)] 0.03662899097662087 >>> a[(2,0)] 0.02371649522786113 """ t = Transform() t = t.rotate(angle) lines = getCircle(0, 0, radius) sigma = (radius+1) / math.sqrt(2*math.log(depth)) grid = {} total = 0 for y, (xMin, xMax) in lines.items(): for x in range(xMin, xMax+1, 1): g = xyGaussian(x,y, amplitude, 0, 0, sigma, sigma) grid[(x,y)]=grid.get((x,y), 0)+g total += g # scale the amplitude based on the total grid = {k: float(v)/total for k, v in grid.items()} # rotate the grid grid = grid new = {} for k, v in grid.items(): k_ = t.transformPoint(k) new[k_] = v grid = new return grid
def test_reverseTransform(self): t = Transform(2, 0, 0, 3, 1, 6) reverse_t = t.reverseTransform((4, 3, 2, 1, 5, 6)) assert reverse_t == Transform(8, 6, 6, 3, 21, 15) t = Transform(4, 3, 2, 1, 5, 6) reverse_t = t.transform((2, 0, 0, 3, 1, 6)) assert reverse_t == Transform(8, 6, 6, 3, 21, 15)
def transformationAtCenter(matrix, centerPoint): """Helper function for rotate(), scale() and skew() to apply a transformation with a specified center point. >>> transformationAtCenter((2, 0, 0, 2, 0, 0), (0, 0)) (2, 0, 0, 2, 0, 0) >>> transformationAtCenter((2, 0, 0, 2, 0, 0), (100, 200)) (2, 0, 0, 2, -100, -200) >>> transformationAtCenter((-2, 0, 0, 2, 0, 0), (100, 200)) (-2, 0, 0, 2, 300, -200) >>> t = Transform(*transformationAtCenter((0, 1, 1, 0, 0, 0), (100, 200))) >>> t.transformPoint((100, 200)) (100, 200) >>> t.transformPoint((0, 0)) (-100, 100) """ if centerPoint == (0, 0): return matrix t = Transform() cx, cy = centerPoint t = t.translate(cx, cy) t = t.transform(matrix) t = t.translate(-cx, -cy) return tuple(t)
def GlyphConstructionBuilder(construction, font): # create a construction glyph destination = ConstructionGlyph(font) # test if the input is a proper string try: construction = str(construction) except: return destination # parse the note destination.note, construction = parseNote(construction) # check if there is a = sing if glyphNameSplit not in construction: return destination # remove all spaces and tabs construction = removeSpacesAndTabs(construction) # escape math formulas inside a ` ` construction = forceEscapingMathOperations(construction) # extract the name destination.name, construction = parseGlyphName(construction) # extract glyph attributes glyphAttributes, construction = parseGlyphattributes(construction, font) # split into base glyphs, ligatures baseGlyphs = construction.split(baseGlyphSplit) advanceWidth = 0 # start for baseGlyph in baseGlyphs: # split into mark glyphs markGlyphs = baseGlyph.split(markGlyphSplit) baseGlyph = None baseMarkGlyph = None baseTransformMatrix = [1, 0, 0, 1, 0, 0] markTransformMap = {} advanceHeight = 0 for markGlyph in markGlyphs: markGlyph = reEscapeMathOperations(markGlyph) component, transformMatrix = parsePositions(baseMarkGlyph, markGlyph, font, markTransformMap, advanceWidth, advanceHeight) destination.addComponent(component, transformMatrix) markTransformMap[component] = transformMatrix baseMarkGlyph = component if baseGlyph is None: baseGlyph = component baseTransformMatrix = transformMatrix if baseGlyph in font: width = font[baseGlyph].width t = Transform(*baseTransformMatrix) width, y = t.transformPoint((width-advanceWidth, 0)) advanceWidth += width destination.width = advanceWidth for key, value in glyphAttributes.items(): setattr(destination, key, value) return destination
def makeSVGShape(glyph, name=None, width=None, opacity=None): attrs = { 'id': 'mathShape', 'title': "None", 'xmlns': "http://www.w3.org/2000/svg", 'xmlns:xlink': "http://www.w3.org/1999/xlink", 'xml:space': 'preserve', 'style': "fill-rule:nonzero;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;", } # try to get the bounds from the bounds layer. # if that does not work, get it from the glyph itself. bounds = None try: boundsGlyph = glyph.getLayer('bounds') if boundsGlyph is not None: bounds = boundsGlyph.box # print 'using bounds from bounds layer' except: pass # print 'using bounds from glyph' if bounds is None: boundsPen = BoundsPen({}) glyph.draw(boundsPen) bounds = boundsPen.bounds xOffset = 0 yOffset = 0 attrs['id'] = name if width is None: attrs['width'] = "100%" else: attrs['width'] = width if name is not None: attrs['name'] = name else: attrs['name'] = glyph.name if opacity is not None: attrs['fill-opacity'] = "%3.3f" % opacity t = Transform() # print bounds, -(bounds[3]-bounds[1]) t = t.scale(1, -1) t = t.translate(0, -bounds[3]) vb = (0, 0, glyph.width, bounds[3] - bounds[1]) attrs['viewBox'] = "%3.3f %3.3f %3.3f %3.3f" % (vb[0], vb[1], vb[2], vb[3]) attrs['enable-background'] = attrs['viewBox'] sPen = MathImageSVGPathPen({}) tPen = TransformPen(sPen, t) glyph.draw(tPen) path = "<path d=\"%s\"/>" % (sPen.getCommands()) tag = "<svg %s>%s</svg>" % (" ".join( ["%s=\"%s\"" % (k, v) for k, v in attrs.items()]), path) return vb, tag
def _get_controlPointBounds(self): from fontTools.misc.transform import Transform # storage glyphRects = {} componentReferences = {} # scan loaded glyphs for glyphName, glyph in self._glyphs.items(): if glyphName in self._scheduledForDeletion: continue glyphRect = glyph.controlPointBounds if glyphRect: glyphRects[glyphName] = glyphRect # scan glyphs that have not been loaded if self._glyphSet is not None: for glyphName, fileName in self._glyphSet.contents.items(): if glyphName in self._glyphs or glyphName in self._scheduledForDeletion: continue glif = self._glyphSet.getGLIF(glyphName) points, components = _fetchControlPointBoundsData(glif) if points: glyphRects[glyphName] = calcBounds(points) for base, transformation in components: xScale, xyScale, yxScale, yScale, xOffset, yOffset = transformation if glyphName not in componentReferences: componentReferences[glyphName] = [] componentReferences[glyphName].append((base, xScale, xyScale, yxScale, yScale, xOffset, yOffset)) # get the transformed component bounding boxes and update the glyphs for glyphName, components in componentReferences.items(): glyphRect = glyphRects.get(glyphName, (None, None, None, None)) # XXX this doesn't handle nested components for base, xScale, xyScale, yxScale, yScale, xOffset, yOffset in components: # base glyph doesn't exist if base not in glyphRects: continue baseRect = glyphRects[base] # base glyph has no points if None in baseRect: continue # transform the base rect transform = Transform(xx=xScale, xy=xyScale, yx=yxScale, yy=yScale, dx=xOffset, dy=yOffset) xMin, yMin, xMax, yMax = baseRect (xMin, yMin), (xMax, yMax) = transform.transformPoints([(xMin, yMin), (xMax, yMax)]) componentRect = (xMin, yMin, xMax, yMax) # update the glyph rect if None in glyphRect: glyphRect = componentRect else: glyphRect = unionRect(glyphRect, componentRect) # store the updated rect glyphRects[glyphName] = glyphRect # work out the unified rect fontRect = None for glyphRect in glyphRects.values(): if fontRect is None: fontRect = glyphRect elif glyphRect is not None: fontRect = unionRect(fontRect, glyphRect) # done return fontRect
def skew(self, x=0, y=0, point=None): t = Transform() if not point: point = self.bounds().point("C") # maybe should be getFrame()? t = t.translate(point.x, point.y) t = t.skew(x, y) t = t.translate(-point.x, -point.y) return self.transform(t)
def rotate(self, degrees, point=None): t = Transform() if not point: point = self.point("C") t = t.translate(point.x, point.y) t = t.rotate(math.radians(degrees)) t = t.translate(-point.x, -point.y) return self.transform(t)
def skew(self, x=0, y=0, unalign=True): t = Transform() if unalign != False: point = self.bounds().point("SW") # maybe should be getFrame()? t = t.translate(point.x, point.y) t = t.skew(x, y) if unalign != False: t = t.translate(-point.x, -point.y) return self.transform(t)
def rotate(self, degrees, point=None): """Rotate this shape by a degree (in 360-scale, counterclockwise).""" t = Transform() if not point: point = self.bounds().point("C") # maybe should be getFrame()? t = t.translate(point.x, point.y) t = t.rotate(math.radians(degrees)) t = t.translate(-point.x, -point.y) return self.transform(t, transformFrame=False)
def transform(self, transform, center=(0, 0)): cx, cy = center t = Transform() t = t.translate(cx, cy) t = t.transform(transform) t = t.translate(-cx, -cy) matrix = skia.Matrix() matrix.setAffine(t) self.path.transform(matrix)
def scale(self, scaleX, scaleY=None, point=None): """Scale this shape by a percentage amount (1-scale).""" t = Transform() if point != False: point = self.bounds().point("C") if point == None else point # maybe should be getFrame()? t = t.translate(point.x, point.y) t = t.scale(scaleX, scaleY or scaleX) if point != False: t = t.translate(-point.x, -point.y) return self.transform(t)
def adjust_anchors(anchor_data, ufo, component): """Adjust anchors to which a mark component may have been attached.""" glyph = ufo[component.baseGlyph] t = Transform(*component.transformation) for anchor in glyph.anchors: # only adjust if this anchor has data and the component also contains # the associated mark anchor (e.g. "_top" for "top") if (anchor.name in anchor_data and any(a.name == '_' + anchor.name for a in glyph.anchors)): anchor_data[anchor.name] = t.transformPoint((anchor.x, anchor.y))
def rotate(self, degrees, point=None): if Transform: t = Transform() if not point: point = self.mid t = t.translate(point.x, point.y) t = t.rotate(math.radians(degrees)) t = t.translate(-point.x, -point.y) return self.transform(t) else: raise Exception("fontTools not installed")
def makeSVGShape(glyph, name=None, width=None, opacity=None): attrs = { 'id': 'mathShape', 'title': "None", 'xmlns': "http://www.w3.org/2000/svg", 'xmlns:xlink' : "http://www.w3.org/1999/xlink", 'xml:space':'preserve', 'style': "fill-rule:nonzero;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;", } # try to get the bounds from the bounds layer. # if that does not work, get it from the glyph itself. bounds = None try: boundsGlyph = glyph.getLayer('bounds') if boundsGlyph is not None: bounds = boundsGlyph.box # print 'using bounds from bounds layer' except: pass # print 'using bounds from glyph' if bounds is None: boundsPen = BoundsPen({}) glyph.draw(boundsPen) bounds = boundsPen.bounds xOffset = 0 yOffset = 0 attrs['id']= name; if width is None: attrs['width'] = "100%" else: attrs['width'] = width if name is not None: attrs['name'] = name else: attrs['name'] = glyph.name if opacity is not None: attrs['fill-opacity'] = "%3.3f"%opacity t = Transform() # print bounds, -(bounds[3]-bounds[1]) t = t.scale(1,-1) t = t.translate(0, -bounds[3]) vb = (0, 0, glyph.width, bounds[3]-bounds[1]) attrs['viewBox'] = "%3.3f %3.3f %3.3f %3.3f"%(vb[0],vb[1],vb[2],vb[3]) attrs['enable-background'] = attrs['viewBox'] sPen = MathImageSVGPathPen({}) tPen = TransformPen(sPen, t) glyph.draw(tPen) path = "<path d=\"%s\"/>"%(sPen.getCommands()) tag = "<svg %s>%s</svg>"%(" ".join(["%s=\"%s\""%(k,v) for k, v in attrs.items()]), path) return vb, tag
def _adjust_anchors(anchor_data, glyphSet, component): """ Adjust base anchors to which a mark component may have been attached, by moving the base anchor attached to a mark anchor to the position of the mark component's base anchor. """ glyph = glyphSet[component.baseGlyph] t = Transform(*component.transformation) for anchor in glyph.anchors: # only adjust if this anchor has data and the component also contains # the associated mark anchor (e.g. "_top" for "top") if anchor.name in anchor_data and any(a.name == "_" + anchor.name for a in glyph.anchors): anchor_data[anchor.name] = t.transformPoint((anchor.x, anchor.y))
def filter(self, glyph): if not glyph.components: return False ufo2ft.util.deepCopyContours(self.context.glyphSet, glyph, glyph, Transform()) glyph.clearComponents() return True
def SelectedComponentsQPainterPathFactory(glyph): pen = OnlyComponentsQtPen(glyph.layer) pointPen = PointToSegmentPen(pen) selectedPen = OnlyComponentsQtPen(glyph.layer) selectedPointPen = PointToSegmentPen(selectedPen) originPts = [] for component in glyph.components: if component.selected: component.drawPoints(selectedPointPen) t = Transform(*component.transformation) originPts.append(t.transformPoint((0, 0))) else: component.drawPoints(pointPen) pen.path.setFillRule(Qt.WindingFill) selectedPen.path.setFillRule(Qt.WindingFill) return (pen.path, selectedPen.path, originPts)
def propogateAnchors(layer): for component in layer.components: clayer = component.layer or component.component.layers[0] propogateAnchors(clayer) for anchor in clayer.anchors: names = [a.name for a in layer.anchors] name = anchor.name if name.startswith("_") or name in names: continue x, y = anchor.position.x, anchor.position.y if component.transform != DEFAULT_TRANSFORM: t = Transform(*component.transform.value) x, y = t.transformPoint((x, y)) new = GSAnchor(name) new.position.x, new.position.y = (x, y) layer.anchors[name] = new
def condenseFont(font, scale=.8, stemWidth=185): f = font.copy() xscale = scale CAPS = ("A B C.cn D.cn E F G.cn H I J K L M N O.cn P Q.cn R S T U.cn V W X " "Y Z one two three four five six seven eight nine zero").split() LC = ("a.cn b.cn c.cn d.cn e.cn f g.cn h i j k l m n o.cn p.cn q.cn r s t " "u v w x y z").split() # for g in [f[name] for name in LC]: for g in f: if len(g) > 0: # print g.name if g.name in lessCondensed: scale = xscale * 1.1 if g.name in uncondensed: continue if g.name in moreCondensed: scale = xscale * .90 # g2 = condenseGlyph(g, xscale) # g.clear() # g2.drawPoints(g.getPointPen()) m = Transform(xscale, 0, 0, 1, 20, 0) g.transform(m) transformFLGlyphMembers(g, m, transformAnchors=False) if g.width != 0: g.width += 40 return f
def build_font(srcs, metadata, filename): ascent = 880 descent = 120 upem = ascent + descent scale = upem / 360.0 transform = Transform(scale, 0, 0, -scale, 0, ascent) glyphs = collect_glyphs(srcs, transform=transform) builder = FontBuilder(1000, isTTF=False) builder.setupGlyphOrder([glyph.name for glyph in glyphs]) builder.setupCharacterMap({0: ".notdef"}) psname = metadata["psName"] builder.setupCFF(psname, {"FullName": psname}, {glyph.name: glyph.charstring for glyph in glyphs}, {}) builder.setupHorizontalMetrics( {glyph.name: glyph.get_hmetrics() for glyph in glyphs}) builder.setupHorizontalHeader(ascent=ascent, descent=-descent) builder.setupNameTable({}) builder.setupOS2() builder.setupPost() builder.setupVerticalMetrics( {glyph.name: glyph.get_vmetrics(ascent=ascent) for glyph in glyphs}) builder.setupVerticalOrigins({}, ascent) builder.setupVerticalHeader(ascent=ascent, descent=-descent) builder.save(filename)
class Component(object): baseGlyph = attr.ib(type=str) transformation = attr.ib( default=Identity, converter=lambda t: t if isinstance(t, Transform) else Transform(*t), type=Transform, ) identifier = attr.ib(default=None, type=Optional[str]) # ----------- # Pen methods # ----------- def draw(self, pen): pointPen = PointToSegmentPen(pen) self.drawPoints(pointPen) def drawPoints(self, pointPen): try: pointPen.addComponent( self.baseGlyph, self.transformation, identifier=self.identifier ) except TypeError: pointPen.addComponent(self.baseGlyph, self.transformation) warnings.warn( "The addComponent method needs an identifier kwarg. " "The component's identifier value has been discarded.", UserWarning, )
def decompose_glyphs(self, ufos): """Move components of UFOs' glyphs to their outlines.""" for ufo in ufos: print('>> Decomposing glyphs for ' + self._font_name(ufo)) for glyph in ufo: self._deep_copy_contours(ufo, glyph, glyph, Transform()) glyph.clearComponents()
def _getBounds(self, boundsAttr): layer = self.layer if layer is None: return None if self.baseGlyph not in layer: return None glyph = layer[self.baseGlyph] bounds = getattr(glyph, boundsAttr) if bounds is None: return None if self.transformation == _defaultTransformation: return bounds xMin, yMin, xMax, yMax = bounds t = Transform(*self.transformation) points = [(xMin, yMin), (xMax, yMax)] (xMin, yMin), (xMax, yMax) = t.transformPoints(points) return xMin, yMin, xMax, yMax
def __init__(self, outPen, transformation): if not hasattr(transformation, "transformPoint"): from fontTools.misc.transform import Transform transformation = Transform(*transformation) self._transformation = transformation self._transformPoint = transformation.transformPoint self._outPen = outPen self._stack = []
def addComponent(self, baseGlyph, transformation, identifier=None, **kwargs): if baseGlyph in self.modified: # multiply the component's transformation matrix with the inverse # of the filter's transformation matrix to compensate for the # transformation already applied to the base glyph transformation = Transform(*transformation).transform(self._inverted) super().addComponent(baseGlyph, transformation, identifier=identifier, **kwargs)
def _adjust_anchors(anchor_data, ufo, parent, component): """Adjust anchors to which a mark component may have been attached.""" glyph = ufo[component.baseGlyph] t = Transform(*component.transformation) _componentAnchor = _componentAnchorFromLib(parent, component) for anchor in glyph.anchors: # adjust either if component is attached to a specific named anchor # (e.g. top_2 for a ligature glyph) # rather than to the standard anchors (top/bottom) if _componentAnchor and _componentAnchor in anchor_data: anchor_data[_componentAnchor] = t.transformPoint( (anchor.x, anchor.y)) # ... or this anchor has data and the component also contains # the associated mark anchor (e.g. "_top" for "top") ... elif anchor.name in anchor_data and any(a.name == "_" + anchor.name for a in glyph.anchors): anchor_data[anchor.name] = t.transformPoint((anchor.x, anchor.y))
def get_anchor_data(anchor_data, ufo, components, anchor_name): """Get data for an anchor from a list of components.""" anchors = [] for component in components: for anchor in ufo[component.baseGlyph].anchors: if anchor.name == anchor_name: anchors.append((anchor, component)) break if len(anchors) > 1: for i, (anchor, component) in enumerate(anchors): t = Transform(*component.transformation) name = '%s_%d' % (anchor.name, i + 1) anchor_data[name] = t.transformPoint((anchor.x, anchor.y)) elif anchors: anchor, component = anchors[0] t = Transform(*component.transformation) anchor_data[anchor.name] = t.transformPoint((anchor.x, anchor.y))
def addComponent(self, glyphName, transformation): self._outPen.addComponent( glyphName, Transform( *transformation[:4], self.roundFunc(transformation[4]), self.roundFunc(transformation[5]), ), )
def translate(self, x, y=None, transformFrame=True): """Translate this shape by `x` and `y` (pixel values).""" if y is None: y = x img = self.img() if img: img["rect"] = img["rect"].offset(x, y) return self.transform(Transform(1, 0, 0, 1, x, y), transformFrame=transformFrame)
def _flattenComponent(glyphSet, component): """Returns a list of tuples (baseGlyph, transform) of nested component.""" glyph = glyphSet[component.baseGlyph] # Any contour will cause components to be decomposed if not glyph.components or len(glyph) > 0: transformation = Transform(*component.transformation) return [(component.baseGlyph, transformation)] all_flattened_components = [] for nested in glyph.components: flattened_components = _flattenComponent(glyphSet, nested) for i, (name, tr) in enumerate(flattened_components): flat_tr = Transform(*component.transformation) flat_tr = flat_tr.translate(tr.dx, tr.dy) flat_tr = flat_tr.transform((tr.xx, tr.xy, tr.yx, tr.yy, 0, 0)) flattened_components[i] = (name, flat_tr) all_flattened_components.extend(flattened_components) return all_flattened_components
def decompose_glyphs(self, ufos, glyph_filter=lambda g: True): """Move components of UFOs' glyphs to their outlines.""" for ufo in ufos: logger.info('Decomposing glyphs for ' + self._font_name(ufo)) for glyph in ufo: if not glyph.components or not glyph_filter(glyph): continue self._deep_copy_contours(ufo, glyph, glyph, Transform()) glyph.clearComponents()
def getGlyph(self, glyph, skew, rotation, addComponents=False): skew = radians(skew) rotation = radians(-rotation) dest = glyph.copy() if skew == 0 and rotation == 0: return dest for contour in dest: for bPoint in contour.bPoints: bcpIn = bPoint.bcpIn bcpOut = bPoint.bcpOut if bcpIn == (0, 0): continue if bcpOut == (0, 0): continue if bcpIn[0] == bcpOut[0] and bcpIn[1] != bcpOut[1]: bPoint.anchorLabels = ["extremePoint"] if rotation and bcpIn[0] != bcpOut[0] and bcpIn[1] == bcpOut[1]: bPoint.anchorLabels = ["extremePoint"] cx, cy = 0, 0 box = dest.box if box: cx = box[0] + (box[2] - box[0]) * .5 cy = box[1] + (box[3] - box[1]) * .5 t = Transform() t = t.skew(skew) t = t.translate(cx, cy).rotate(rotation).translate(-cx, -cy) dest.transform(t) dest.extremePoints(round=0) for contour in dest: for point in contour.points: if "extremePoint" in point.labels: point.selected = True point.smooth = True else: point.selected = False dest.removeSelection() dest.round() return dest
def addComponent(self, baseGlyphName, transformation, **kwargs): self._outPen.addComponent( baseGlyphName, Transform( *transformation[:4], self.roundFunc(transformation[4]), self.roundFunc(transformation[5]), ), **kwargs, )
def _getBounds(self, boundsAttr): glyph = self.getParent() if glyph is None: return None font = glyph.getParent() if font is None: return None if self.baseGlyph not in font: return None glyph = font[self.baseGlyph] bounds = getattr(glyph, boundsAttr) if bounds is None: return None if self.transformation == _defaultTransformation: return bounds xMin, yMin, xMax, yMax = bounds t = Transform(*self.transformation) points = [(xMin, yMin), (xMax, yMax)] (xMin, yMin), (xMax, yMax) = t.transformPoints(points) return xMin, yMin, xMax, yMax
def propagate_anchors(ufo): """Copy anchors from parent glyphs' components to the parent.""" from fontTools.misc.transform import Transform def get_anchor(glyph, name): return next((a for a in glyph.anchors if a.name == name), None) for parent in ufo: added_here = {} # don't propagate anchors for mark glyphs if any(a.name.startswith('_') for a in parent.anchors): continue # try to get anchors from base (first) components glyph = parent transformation = Transform() while glyph.components: component = glyph.components[0] glyph = ufo[component.baseGlyph] transformation = transformation.transform(component.transformation) for anchor in glyph.anchors: if get_anchor(parent, anchor.name) is None: added_here[anchor.name] = transformation.transformPoint( (anchor.x, anchor.y)) # adjust anchors to which a mark has been attached for component in parent.components[1:]: glyph = ufo[component.baseGlyph] transformation = Transform(*component.transformation) for anchor in glyph.anchors: if (anchor.name in added_here and get_anchor(glyph, '_' + anchor.name) is not None): added_here[anchor.name] = transformation.transformPoint( (anchor.x, anchor.y)) for name, (x, y) in added_here.items(): anchor_dict = {'name': name, 'x': x, 'y': y} parent.appendAnchor(glyph.anchorClass(anchorDict=anchor_dict))
def italicizeGlyph(f, g, angle=10, stemWidth=185): unic = g.unicode #save unicode glyph = f[g.name] slope = np.tanh(math.pi * angle / 180) # determine how far on the x axis the glyph should slide # to compensate for the slant. -600 is a magic number # that assumes a 2048 unit em square MEAN_YCENTER = -600 m = Transform(1, 0, slope, 1, 0, 0) xoffset, junk = m.transformPoint((0, MEAN_YCENTER)) m = Transform(.97, 0, slope, 1, xoffset, 0) if len(glyph) > 0: g2 = italicize(f[g.name], angle, xoffset=xoffset, stemWidth=stemWidth) f.insertGlyph(g2, g.name) transformFLGlyphMembers(f[g.name], m) if unic > 0xFFFF: #restore unicode g.unicode = unic
def test_rotate(self): t = Transform() assert t.rotate(math.pi / 2) == Transform(0, 1, -1, 0, 0, 0) t = Transform() assert t.rotate(-math.pi / 2) == Transform(0, -1, 1, 0, 0, 0) t = Transform() assert tuple(t.rotate(math.radians(30))) == pytest.approx( tuple(Transform(0.866025, 0.5, -0.5, 0.866025, 0, 0)))
def test_toPS(self): t = Transform().scale(2, 3).translate(4, 5) assert t.toPS() == '[2 0 0 3 8 15]'
def test_transformPoints(self): t = Transform(2, 0, 0, 3, 0, 0) assert t.transformPoints( [(0, 0), (0, 100), (100, 100), (100, 0)] ) == [(0, 0), (0, 300), (200, 300), (200, 0)]
def GlyphConstructionBuilder(construction, font): # create a construction glyph destination = ConstructionGlyph(font) # test if the input is a proper string try: construction = str(construction) except Exception: return destination # parse decomposing shouldDecompose, construction = parseShouldDecompose(construction) # parse the note destination.note, construction = parseNote(construction) # check if there is a = sing if glyphNameSplit not in construction: return destination # remove all spaces and tabs construction = removeSpacesAndTabs(construction) # escape math formulas inside a ` ` construction = forceEscapingMathOperations(construction) # extract the name destination.name, construction = parseGlyphName(construction) # extract glyph attributes glyphAttributes, construction = parseGlyphattributes(construction, font) # split into base glyphs, ligatures baseGlyphs = construction.split(baseGlyphSplit) advanceWidth = 0 previousBaseGlyph = None # start for baseGlyph in baseGlyphs: applyKerning, baseGlyph = parseApplyKerning(baseGlyph) # split into mark glyphs markGlyphs = baseGlyph.split(markGlyphSplit) baseGlyph = None baseMarkGlyph = None baseTransformMatrix = [1, 0, 0, 1, 0, 0] markTransformMap = {} advanceHeight = 0 for markGlyph in markGlyphs: markGlyph = reEscapeMathOperations(markGlyph) component, transformMatrix = parsePositions(baseMarkGlyph, markGlyph, font, markTransformMap, advanceWidth, advanceHeight) baseMarkGlyph = component if baseGlyph is None: baseGlyph = component if applyKerning: kern = kernValueForGlyphPair(font, (previousBaseGlyph, baseGlyph)) if kern: t = Transform(*transformMatrix).translate(kern, 0) transformMatrix = t[:] baseTransformMatrix = transformMatrix destination.addComponent(component, transformMatrix) markTransformMap[component] = transformMatrix if baseGlyph in font: width = font[baseGlyph].width t = Transform(*baseTransformMatrix) width, y = t.transformPoint((width - advanceWidth, 0)) advanceWidth += width previousBaseGlyph = baseGlyph destination.width = advanceWidth for key, value in glyphAttributes.items(): setattr(destination, key, value) if shouldDecompose: destination._shouldDecompose = True return destination
def test_scale(self): t = Transform() assert t.scale(5) == Transform(5, 0, 0, 5, 0, 0) assert t.scale(5, 6) == Transform(5, 0, 0, 6, 0, 0)
def test_inverse(self): t = Transform().translate(2, 3).scale(4, 5) assert t.transformPoint((10, 20)) == (42, 103) it = t.inverse() assert it.transformPoint((42, 103)) == (10.0, 20.0) assert Transform().inverse() == Transform()
def transform_bbox(bbox, matrix): t = Transform(*matrix) ll_x, ll_y = t.transformPoint((bbox[0], bbox[1])) tr_x, tr_y = t.transformPoint((bbox[2], bbox[3])) return normRect((ll_x, ll_y, tr_x, tr_y))
def test_examples(self): t = Transform() assert repr(t) == "<Transform [1 0 0 1 0 0]>" assert t.scale(2) == Transform(2, 0, 0, 2, 0, 0) assert t.scale(2.5, 5.5) == Transform(2.5, 0, 0, 5.5, 0, 0) assert t.scale(2, 3).transformPoint((100, 100)) == (200, 300)
def test_translate(self): t = Transform() assert t.translate(20, 30) == Transform(1, 0, 0, 1, 20, 30)
def test_transform(self): t = Transform(2, 0, 0, 3, 1, 6) assert t.transform((4, 3, 2, 1, 5, 6)) == Transform(8, 9, 4, 3, 11, 24)
def parsePositions(baseGlyph, markGlyph, font, markTransformMap, advanceWidth, advanceHeight): xx, xy, yx, yy, x, y = 1, 0, 0, 1, advanceWidth, advanceHeight baseGlyphX = baseGlyphY = baseGlyph markFixedX = markFixedY = False flipX = flipY = False if positionSplit in markGlyph: markGlyph, position = markGlyph.split(positionSplit) if positionXYSplit in position: positions = position.split(positionXYSplit) if len(positions) == 6: xx, xy, yx, yy, positionX, positionY = positions xx = float(xx) xy = float(xy) yx = float(yx) yy = float(yy) elif len(positions) == 2: positionX, positionY = positions else: raise GlyphBuilderError("mark positions should have 6 or 2 options") else: positionX = positionY = position if positionBaseSplit in positionX: baseGlyphX, positionX = positionX.split(positionBaseSplit) if positionBaseSplit in positionY: baseGlyphY, positionY = positionY.split(positionBaseSplit) if flipMarkGlyphSplit in positionX: flipY = True positionX = positionX.replace(flipMarkGlyphSplit, "") if flipMarkGlyphSplit in positionY: flipX = True positionY = positionY.replace(flipMarkGlyphSplit, "") if positionX and positionY: baseX = baseY = 0 markX = markY = 0 if markGlyph not in font: if glyphSuffixSplit in markGlyph: markGlyph = markGlyph.split(glyphSuffixSplit)[0] markPoint1, markAngle1, markFixedX = parsePosition(markGlyph, font, positionX, direction="x", prefix="_") markPoint2, markAngle2, markFixedY = parsePosition(markGlyph, font, positionY, direction="y", prefix="_") intersection = _intersectAngles(markPoint1, markAngle1, markPoint2, markAngle2) if intersection is not None: markX, markY = intersection if baseGlyphX in font and baseGlyphY in font: basePoint1, baseAngle1, _ = parsePosition(baseGlyphX, font, positionX, direction="x", isBase=True) basePoint2, baseAngle2, _ = parsePosition(baseGlyphY, font, positionY, direction="y", isBase=True) intersection = _intersectAngles(basePoint1, baseAngle1, basePoint2, baseAngle2) if intersection is not None: baseX, baseY = intersection # calculate the offset if not markFixedX: x += baseX - markX else: x += markX if not markFixedY: y += baseY - markY else: y += markY if not markFixedX: baseTransform = markTransformMap.get(baseGlyphX) if baseTransform: x += baseTransform[4] - advanceWidth if not markFixedY: baseTransform = markTransformMap.get(baseGlyphY) if baseTransform: y += baseTransform[5] - advanceHeight transformMatrix = (xx, xy, yx, yy, x, y) if flipX: bounds = font[markGlyph].bounds if bounds: minx, miny, maxx, maxy = bounds bt = Transform(*transformMatrix) minx, miny = bt.transformPoint((minx, miny)) maxx, maxy = bt.transformPoint((maxx, maxy)) t = Transform() t = t.translate(0, miny) t = t.scale(1, -1) t = t.translate(0, -maxy) t = t.transform(bt) transformMatrix = t[:] if flipY: bounds = font[markGlyph].bounds if bounds: minx, miny, maxx, maxy = bounds bt = Transform(*transformMatrix) minx, miny = bt.transformPoint((minx, miny)) maxx, maxy = bt.transformPoint((maxx, maxy)) t = Transform() t = t.translate(minx, 0) t = t.scale(-1, 1) t = t.translate(-maxx, 0) t = t.transform(bt) transformMatrix = t[:] return markGlyph, transformMatrix