def draw(self, pen): if self.transform: pen = TransformPen(pen, self.transform) for path in self.paths: current_pos = None for s in path: if current_pos != s.start: if current_pos is not None: pen.closePath() pen.moveTo((s.start.real, s.start.imag)) if isinstance(s, Line): pen.lineTo((s.end.real, s.end.imag)) elif isinstance(s, CubicBezier): pen.curveTo( (s.control1.real, s.control1.imag), (s.control2.real, s.control2.imag), (s.end.real, s.end.imag)) elif isinstance(s, QuadraticBezier): pen.qCurveTo( (s.control.real, s.control.imag), (s.end.real, s.end.imag)) #else: # TODO convert Arc segments to bezier? #raise NotImplementedError(s) current_pos = s.end pen.closePath()
def _colr_ufo(colr_version, ufo, color_glyphs): # Sort colors so the index into colors == index into CPAL palette. # We only store opaque colors in CPAL for CORLv1, as 'transparency' is # encoded separately. colors = sorted( set(c if colr_version == 0 else c.opaque() for c in chain.from_iterable(g.colors() for g in color_glyphs))) logging.debug("colors %s", colors) # KISS; use a single global palette ufo.lib[ufo2ft.constants.COLOR_PALETTES_KEY] = [[ c.to_ufo_color() for c in colors ]] # We created glyph_name on the default layer for the base glyph # Now create glyph_name on layers 0..N-1 for the colored layers for color_glyph in color_glyphs: # For COLRv0, paint is just the palette index # For COLRv1, it's a data structure describing paint layer_to_paint = [] svg_units_to_font_units = color_glyph.transform_for_font_space() for idx, (paint, path) in enumerate(color_glyph.as_painted_layers()): glyph_layer = _layer(ufo, idx) if colr_version == 0: # COLRv0: draw using the first available color on the glyph_layer # Results for gradients will be suboptimal :) color = next(paint.colors()) layer_to_paint.append((glyph_layer.name, colors.index(color))) elif colr_version == 1: # COLRv0: fill in gradient paint structures layer_to_paint.append( (glyph_layer.name, paint.to_ufo_paint(colors))) else: raise ValueError(f"Unsupported COLR version: {colr_version}") # we've got a colored layer, put a glyph on it glyph = glyph_layer.newGlyph(color_glyph.glyph_name) glyph.width = ufo.info.unitsPerEm pen = TransformPen(glyph.getPen(), svg_units_to_font_units) skia_path(path, color_glyph.nsvg.tolerance).draw(pen) # each base glyph contains a list of (layer.name, paint info) in z-order base_glyph = ufo.get(color_glyph.glyph_name) base_glyph.lib[ ufo2ft.constants.COLOR_LAYER_MAPPING_KEY] = layer_to_paint # apparently on Mac (but not Linux) Chrome and Firefox end up relying on the # extents of the base layer to determine where the glyph might paint. If you # leave the base blank the COLR glyph never renders. pen = base_glyph.getPen() pen.moveTo((0, 0)) pen.lineTo((ufo.info.unitsPerEm, ufo.info.unitsPerEm)) pen.endPath()
def trianglePath(x, y, size, angle): thirdSize = size / 3 pen = QtPen({}) tPen = TransformPen(pen, Identity.rotate(angle)) tPen.moveTo((-thirdSize, size)) tPen.lineTo((-thirdSize, -size)) tPen.lineTo((2 * thirdSize, 0)) tPen.closePath() return pen.path.translated(x, y)
def trianglePath(x, y, size, angle): thirdSize = size / 3 pen = QtPen({}) tPen = TransformPen( pen, Identity.rotate(angle)) tPen.moveTo((-thirdSize, size)) tPen.lineTo((-thirdSize, -size)) tPen.lineTo((2 * thirdSize, 0)) tPen.closePath() return pen.path.translated(x, y)
def drawAICBOutlines(data, pen, fitInside=(None, None, None, None), fixedScale=None): """ Draw outline data from an eps. Returns True if the data was drawn, False if not. data = the EPS data (string) pen = a drawing pen fitInside = the maximum size that the outline can be drawn into. the function will transform the eps outlines to fit within this box. if you don't want to transform the data, send it a box of (None, None, None, None). it is also possible to transform based on only horizontal OR vertical parameters. Simply send a box formatted like: (None, -250, None, 750) to base the transform on vertical dimensions or like: (-1000, None, 1000, None) to base the transform on horizontal dimensions. fixedScale = a set scale factor for transforming the outline. If the resulting scaled outline is larger than the fit rect, the outline will be centered on those parameters. """ from fontTools.pens.transformPen import TransformPen from fontTools.misc.arrayTools import calcBounds data = '\n'.join(data.splitlines()) ## ## get the point data # FL follows the EPSF3.0 spec, but AI seems # to be using a different spec. AI puts the outline data # in layers. we can get around this by iterating over all lines # in the data and drawing points as we find them. contours = [] previousOnCurve = None for line in data.splitlines(): movetoMatch = moveto_RE.match(line) if movetoMatch: contours.append([]) x = float(movetoMatch.group(1)) y = float(movetoMatch.group(2)) contours[-1].append(('move', [(x, y)])) previousOnCurve = (x, y) continue linetoMatch = lineto_RE.match(line) if linetoMatch: x = float(linetoMatch.group(1)) y = float(linetoMatch.group(2)) contours[-1].append(('line', [(x, y)])) previousOnCurve = (x, y) continue startCurveToMatch = startCurveTo_RE.match(line) if startCurveToMatch: x1 = float(startCurveToMatch.group(1)) y1 = float(startCurveToMatch.group(2)) x2 = float(startCurveToMatch.group(3)) y2 = float(startCurveToMatch.group(4)) contours[-1].append(('curve', [previousOnCurve, (x1, y1), (x2, y2)])) previousOnCurve = (x2, y2) continue endCurveToMatch = endCurveTo_RE.match(line) if endCurveToMatch: x1 = float(endCurveToMatch.group(1)) y1 = float(endCurveToMatch.group(2)) x2 = float(endCurveToMatch.group(3)) y2 = float(endCurveToMatch.group(4)) contours[-1].append(('curve', [(x1, y1), (x2, y2), (x2, y2)])) previousOnCurve = (x2, y2) continue curvetoMatch = curveto_RE.match(line) if curvetoMatch: x1 = float(curvetoMatch.group(1)) y1 = float(curvetoMatch.group(2)) x2 = float(curvetoMatch.group(3)) y2 = float(curvetoMatch.group(4)) x3 = float(curvetoMatch.group(5)) y3 = float(curvetoMatch.group(6)) contours[-1].append(('curve', [(x1, y1), (x2, y2), (x3, y3)])) previousOnCurve = (x3, y3) continue # no outline data. give up. if not contours: return False ## get the bounding box boundingBox = boundingBox_RE.findall(data) if boundingBox: # rudely assume that there is only one EPS level bounding box # (the spec says that it should be that way) boundingBox = [ int(i.split('.')[0]) # FL writes floats in the bounding box for i in boundingBox[0].split(' ') ] # the EPS does not have a bounding box # or the EPS has a stated box of (0, 0, 0, 0) # (which AI seems to do for open paths!) # so, we get the bounds from a points array if not boundingBox or boundingBox == [0, 0, 0, 0]: points = [] for contour in contours: for tp, pts in contour: points.extend(pts) boundingBox = calcBounds(points) ## ## determine if the outlines need to be transformed ## and set up the transformation pen. transform = _getRectTransform(fitInside, boundingBox, fixedScale) transformPen = TransformPen(pen, transform) ## ## finally, draw the points for contour in contours: haveClosedPath = False if len(contour) > 1: # filter out overlapping points at the # start and the end of the contour start = contour[0] end = contour[-1] if end[0] == 'line': startPoints = start[1] endPoints = end[1] if start[0] == end[0]: contour = contour[:-1] haveClosedPath = True for tp, pts in contour: if tp == 'move': transformPen.moveTo(pts[0]) elif tp == 'line': transformPen.lineTo(pts[0]) elif tp == 'curve': pt1, pt2, pt3 = pts transformPen.curveTo(pt1, pt2, pt3) transformPen.closePath() # XXX #if haveClosedPath: # transformPen.closePath() #else: # transformPen.endPath() return True
def drawAICBOutlines(data, pen, fitInside=(None, None, None, None), fixedScale=None): """ Draw outline data from an eps. Returns True if the data was drawn, False if not. data = the EPS data (string) pen = a drawing pen fitInside = the maximum size that the outline can be drawn into. the function will transform the eps outlines to fit within this box. if you don't want to transform the data, send it a box of (None, None, None, None). it is also possible to transform based on only horizontal OR vertical parameters. Simply send a box formatted like: (None, -250, None, 750) to base the transform on vertical dimensions or like: (-1000, None, 1000, None) to base the transform on horizontal dimensions. fixedScale = a set scale factor for transforming the outline. If the resulting scaled outline is larger than the fit rect, the outline will be centered on those parameters. """ from fontTools.pens.transformPen import TransformPen from fontTools.misc.arrayTools import calcBounds data = '\n'.join(data.splitlines()) ## ## get the point data # FL follows the EPSF3.0 spec, but AI seems # to be using a different spec. AI puts the outline data # in layers. we can get around this by iterating over all lines # in the data and drawing points as we find them. contours = [] previousOnCurve = None for line in data.splitlines(): movetoMatch = moveto_RE.match(line) if movetoMatch: contours.append([]) x = float(movetoMatch.group(1)) y = float(movetoMatch.group(2)) contours[-1].append(('move', [(x, y)])) previousOnCurve = (x, y) continue linetoMatch = lineto_RE.match(line) if linetoMatch: x = float(linetoMatch.group(1)) y = float(linetoMatch.group(2)) contours[-1].append(('line', [(x, y)])) previousOnCurve = (x, y) continue startCurveToMatch = startCurveTo_RE.match(line) if startCurveToMatch: x1 = float(startCurveToMatch.group(1)) y1 = float(startCurveToMatch.group(2)) x2 = float(startCurveToMatch.group(3)) y2 = float(startCurveToMatch.group(4)) contours[-1].append( ('curve', [previousOnCurve, (x1, y1), (x2, y2)])) previousOnCurve = (x2, y2) continue endCurveToMatch = endCurveTo_RE.match(line) if endCurveToMatch: x1 = float(endCurveToMatch.group(1)) y1 = float(endCurveToMatch.group(2)) x2 = float(endCurveToMatch.group(3)) y2 = float(endCurveToMatch.group(4)) contours[-1].append(('curve', [(x1, y1), (x2, y2), (x2, y2)])) previousOnCurve = (x2, y2) continue curvetoMatch = curveto_RE.match(line) if curvetoMatch: x1 = float(curvetoMatch.group(1)) y1 = float(curvetoMatch.group(2)) x2 = float(curvetoMatch.group(3)) y2 = float(curvetoMatch.group(4)) x3 = float(curvetoMatch.group(5)) y3 = float(curvetoMatch.group(6)) contours[-1].append(('curve', [(x1, y1), (x2, y2), (x3, y3)])) previousOnCurve = (x3, y3) continue # no outline data. give up. if not contours: return False ## get the bounding box boundingBox = boundingBox_RE.findall(data) if boundingBox: # rudely assume that there is only one EPS level bounding box # (the spec says that it should be that way) boundingBox = [ int(i.split('.')[0]) # FL writes floats in the bounding box for i in boundingBox[0].split(' ') ] # the EPS does not have a bounding box # or the EPS has a stated box of (0, 0, 0, 0) # (which AI seems to do for open paths!) # so, we get the bounds from a points array if not boundingBox or boundingBox == [0, 0, 0, 0]: points = [] for contour in contours: for tp, pts in contour: points.extend(pts) boundingBox = calcBounds(points) ## ## determine if the outlines need to be transformed ## and set up the transformation pen. transform = _getRectTransform(fitInside, boundingBox, fixedScale) transformPen = TransformPen(pen, transform) ## ## finally, draw the points for contour in contours: haveClosedPath = False if len(contour) > 1: # filter out overlapping points at the # start and the end of the contour start = contour[0] end = contour[-1] if end[0] == 'line': startPoints = start[1] endPoints = end[1] if start[0] == end[0]: contour = contour[:-1] haveClosedPath = True for tp, pts in contour: if tp == 'move': transformPen.moveTo(pts[0]) elif tp == 'line': transformPen.lineTo(pts[0]) elif tp == 'curve': pt1, pt2, pt3 = pts transformPen.curveTo(pt1, pt2, pt3) transformPen.closePath() # XXX #if haveClosedPath: # transformPen.closePath() #else: # transformPen.endPath() return True