def img_save(): img_path = PATH.rsplit('.', 1)[0] font = TTFont( PATH) # it would work just as well with fontTools.t1Lib.T1Font glyf = font['glyf'] if not os.path.exists(img_path): os.makedirs(img_path) for glyphName in glyf.keys(): gs = font.getGlyphSet() pen = ReportLabPen(gs, Path(fillColor=colors.black, strokeWidth=1)) g = gs[glyphName] g.draw(pen) # 调整图片大小 w, h = 800, 800 g = Group(pen.path) g.translate(10, 200) g.scale(0.3, 0.3) d = Drawing(w, h) d.add(g) image = renderPM.drawToPIL(d) little_image = image.resize((180, 180)) fromImage = Image.new('RGBA', (360, 360), color=(255, 255, 255)) fromImage.paste(little_image, (0, 0)) # fromImage.show() glyphName = glyphName.replace('uni', '&#x') imageFile = img_path + "/%s.png" % glyphName fromImage.save(imageFile) break
def ttf_ocr(font, key=None): gs = font.getGlyphSet() keys = [key for key in gs.keys() if key.startswith("uni")] if key is None else [key] c = [] for i, key in enumerate(keys): if key not in gs: logger.info("No this key: %s" % key) c.append("") continue pen = ReportLabPen(gs, Path(fillColor=colors.black, strokeWidth=0.01)) g = gs[key] g.draw(pen) w, h = 50, 50 g = Group(pen.path) g.translate(10, 10) g.scale(0.02, 0.02) d = Drawing(w, h) d.add(g) renderPM.drawToFile(d, png_file.name, fmt="PNG") result = os.popen("tesseract %s stdout -l chi_sim -psm 5" % png_file.name).read().strip().decode("utf-8", "ignore") if len(result) != 1: result = os.popen("tesseract %s stdout -l chi_sim -psm 8" % png_file.name).read().strip().decode("utf-8", "ignore") logger.info("key: %s, result: %s" % (key, result)) c.append(result) if key is not None: return c[0] return c
def __init__(self,width=224,height=124,*args,**kw): Drawing.__init__(self,width,height,*args,**kw) points = [122.0, 87.0, 122.0, 88.0, 123.0, 88.0, 123.0, 89.0, 124.0, 89.0, 124.0, 90.0, 126.0, 90.0, 126.0, 89.0, 128.0, 88.0, 128.0, 89.0, 129.0, 89.0, 129.0, 91.0, 128.0, 91.0, 128.0, 92.0, 130.0, 99.0, 130.0, 100.0, 129.0, 100.0, 126.0, 103.0, 125.0, 103.0, 125.0, 104.0, 126.0, 106.0, 130.0, 87.0, 129.0, 87.0, 129.0, 86.0, 126.0, 86.0, 126.0, 87.0] grp = Group(Polygon(points, fillColor=toColor('red'))) grp.scale(1, -1) grp.translate(0, -124) self.add(grp)
def _rawDraw(self): _text = self._text self._text = _text or '' self.computeSize() self._text = _text g = Group() g.translate(self.x + self.dx, self.y + self.dy) g.rotate(self.angle) x = self._left # paint box behind text just in case they # fill it if self.boxFillColor or (self.boxStrokeColor and self.boxStrokeWidth): g.add(Rect( self._left-self.leftPadding, self._bottom-self.bottomPadding, self._width, self._height, strokeColor=self.boxStrokeColor, strokeWidth=self.boxStrokeWidth, fillColor=self.boxFillColor) ) g1 = Group() g1.translate(x,self._top-self._eheight) g1.add(self._ddf(self._obj)) g.add(g1) return g
def ttf_to_image(self): """ 将ttf字体文件的字体绘制在Image对象上 :return: """ glyphset = self.font.getGlyphSet() size = (BASE_BACKGOUND_WIDTH * FONT_NUMS_PER_LINE, ceil(len(self.glyphnames) / FONT_NUMS_PER_LINE) * BASE_BACKGOUND_HEIGHT) # 背景图片尺寸 image = Image.new("RGB", size=size, color=(255, 255, 255)) # 初始化背景图片 name_list, image_dict = [], {} for index, glyphname in enumerate(self.glyphnames): if glyphname[0] in ['.', 'g'] or glyphname in self.ignore_names: # 跳过'.notdef', '.null' continue g = glyphset[glyphname] pen = ReportLabPen(self.glyphnames, Path(fillColor=colors.black, strokeWidth=1)) g.draw(pen) # w, h = g.width, g.width w, h = g.width if g.width > 1000 else 1000, g.width if g.width > 1000 else 1000 g = Group(pen.path) g.translate(0, 200) d = Drawing(w, h) d.add(g) im = renderPM.drawToPIL(d, dpi=72).resize((FONT_WIDTH, FONT_HEIGHT)) box = ( (index % FONT_NUMS_PER_LINE) * BASE_BACKGOUND_WIDTH, (index // FONT_NUMS_PER_LINE) * BASE_BACKGOUND_HEIGHT) image.paste(im, box=box) name_list.append(glyphname) image_dict[glyphname] = im return image, name_list, image_dict
def convertImage(self, node): getAttr = node.getAttribute x, y, width, height = map(getAttr, ('x', 'y', "width", "height")) x, y, width, height = map(self.attrConverter.convertLength, (x, y, width, height)) xlink_href = node.attrib.get('{http://www.w3.org/1999/xlink}href') magic_re = r"^data:image/(jpeg|png);base64" match = re.match(magic_re, xlink_href) if match: img_format = match.groups()[0] image_data = base64.decodestring(xlink_href[(match.span(0)[1] + 1):].encode('ascii')) _, path = tempfile.mkstemp(suffix='.%s' % img_format) with open(path, 'wb') as fh: fh.write(image_data) img = Image(int(x), int(y + height), int(width), int(height), path) # this needs to be removed later, not here... # if exists(path): os.remove(path) else: xlink_href = os.path.join(os.path.dirname(self.svg_source_file), xlink_href) img = Image(int(x), int(y + height), int(width), int(height), xlink_href) try: # this will catch invalid image PDFImage(xlink_href, 0, 0) except IOError: logger.error("Unable to read the image %s. Skipping..." % img.path) return None group = Group(img) group.translate(0, (x + height) * 2) group.scale(1, -1) return group
def renderSvg(self, node, outermost=False): getAttr = node.getAttribute _saved_preserve_space = self.shape_converter.preserve_space self.shape_converter.preserve_space = getAttr("{%s}space" % XML_NS) == 'preserve' group = Group() for child in node.getchildren(): self.renderNode(child, group) self.shape_converter.preserve_space = _saved_preserve_space if not outermost: x, y = map(getAttr, ("x", "y")) x, y = map(self.attrConverter.convertLength, (x, y)) if x or y: group.translate(x or 0, y or 0) view_box = self.get_box(node) if view_box: x_scale, y_scale = 1, 1 width, height = map(getAttr, ("width", "height")) width, height = map(self.attrConverter.convertLength, (width, height)) if view_box.height != height: y_scale = height / view_box.height if view_box.width != width: x_scale = width / view_box.width group.scale(x_scale, y_scale) return group
def makeCircularString(x, y, radius, angle, text, fontName, fontSize, inside=0, G=None,textAnchor='start'): '''make a group with circular text in it''' if not G: G = Group() angle %= 360 pi180 = pi/180 phi = angle*pi180 width = stringWidth(text, fontName, fontSize) sig = inside and -1 or 1 hsig = sig*0.5 sig90 = sig*90 if textAnchor!='start': if textAnchor=='middle': phi += sig*(0.5*width)/radius elif textAnchor=='end': phi += sig*float(width)/radius elif textAnchor=='numeric': phi += sig*float(numericXShift(textAnchor,text,width,fontName,fontSize,None))/radius for letter in text: width = stringWidth(letter, fontName, fontSize) beta = float(width)/radius h = Group() h.add(String(0, 0, letter, fontName=fontName,fontSize=fontSize,textAnchor="start")) h.translate(x+cos(phi)*radius,y+sin(phi)*radius) #translate to radius and angle h.rotate((phi-hsig*beta)/pi180-sig90) # rotate as needed G.add(h) #add to main group phi -= sig*beta #increment return G
def convertText(self, node): attrConv = self.attrConverter x, y = map(node.getAttribute, ('x', 'y')) x, y = map(attrConv.convertLength, (x, y)) xml_space = node.getAttribute("{%s}space" % XML_NS) if xml_space: preserve_space = xml_space == 'preserve' else: preserve_space = self.preserve_space gr = Group() frag_lengths = [] dx0, dy0 = 0, 0 x1, y1 = 0, 0 ff = attrConv.findAttr(node, "font-family") or "Helvetica" ff = attrConv.convertFontFamily(ff) fs = attrConv.findAttr(node, "font-size") or "12" fs = attrConv.convertLength(fs) for c in itertools.chain([node], node.getchildren()): has_x = False dx, dy = 0, 0 baseLineShift = 0 if node_name(c) == 'text': text = self.clean_text(c.text, preserve_space) if not text: continue elif node_name(c) == 'tspan': text = self.clean_text(c.text, preserve_space) if not text: continue x1, y1, dx, dy = [c.attrib.get(name, '') for name in ("x", "y", "dx", "dy")] has_x = x1 != '' x1, y1, dx, dy = map(attrConv.convertLength, (x1, y1, dx, dy)) dx0 = dx0 + dx dy0 = dy0 + dy baseLineShift = c.attrib.get("baseline-shift", '0') if baseLineShift in ("sub", "super", "baseline"): baseLineShift = {"sub":-fs/2, "super":fs/2, "baseline":0}[baseLineShift] else: baseLineShift = attrConv.convertLength(baseLineShift, fs) else: continue frag_lengths.append(stringWidth(text, ff, fs)) new_x = x1 if has_x else sum(frag_lengths[:-1]) shape = String(x + new_x, y - y1 - dy0 + baseLineShift, text) self.applyStyleOnShape(shape, node) if node_name(c) == 'tspan': self.applyStyleOnShape(shape, c) gr.add(shape) gr.scale(1, -1) gr.translate(0, -2*y) return gr
def _rawDraw(self): _text = self._text self._text = _text or "" self.computeSize() self._text = _text g = Group() g.translate(self.x + self.dx, self.y + self.dy) g.rotate(self.angle) y = self._top - self._leading * self._baselineRatio textAnchor = self._getTextAnchor() if textAnchor == "start": x = self._left elif textAnchor == "middle": x = self._left + self._ewidth * 0.5 else: x = self._right # paint box behind text just in case they # fill it if self.boxFillColor or (self.boxStrokeColor and self.boxStrokeWidth): g.add( Rect( self._left - self.leftPadding, self._bottom - self.bottomPadding, self._width, self._height, strokeColor=self.boxStrokeColor, strokeWidth=self.boxStrokeWidth, fillColor=self.boxFillColor, ) ) fillColor, fontName, fontSize = self.fillColor, self.fontName, self.fontSize strokeColor, strokeWidth, leading = self.strokeColor, self.strokeWidth, self._leading svgAttrs = getattr(self, "_svgAttrs", {}) if strokeColor: for line in self._lines: s = _text2Path(line, x, y, fontName, fontSize, textAnchor) s.fillColor = fillColor s.strokeColor = strokeColor s.strokeWidth = strokeWidth g.add(s) y -= leading else: for line in self._lines: s = String(x, y, line, _svgAttrs=svgAttrs) s.textAnchor = textAnchor s.fontName = fontName s.fontSize = fontSize s.fillColor = fillColor g.add(s) y -= leading return g
def draw(self): _text = self._text self._text = _text or '' self.computeSize() self._text = _text g = Group() g.translate(self.x + self.dx, self.y + self.dy) g.rotate(self.angle) y = self._top - self.fontSize textAnchor = self._getTextAnchor() if textAnchor == 'start': x = self._left elif textAnchor == 'middle': x = self._left + self._ewidth * 0.5 else: x = self._right # paint box behind text just in case they # fill it if self.boxFillColor or (self.boxStrokeColor and self.boxStrokeWidth): g.add( Rect( self._left - self.leftPadding, self._bottom - self.bottomPadding, self._width, self._height, strokeColor=self.boxStrokeColor, strokeWidth=self.boxStrokeWidth, fillColor=self.boxFillColor)) fillColor, fontName, fontSize = self.fillColor, self.fontName, self.fontSize strokeColor, strokeWidth, leading = self.strokeColor, self.strokeWidth, ( self.leading or 1.2 * fontSize) if strokeColor: for line in self._lines: s = _text2Path(line, x, y, fontName, fontSize, textAnchor) s.fillColor = fillColor s.strokeColor = strokeColor s.strokeWidth = strokeWidth g.add(s) y = y - leading else: for line in self._lines: s = String(x, y, line) s.textAnchor = textAnchor s.fontName = fontName s.fontSize = fontSize s.fillColor = fillColor g.add(s) y = y - leading return g
def _rawDraw(self): _text = self._text self._text = _text or '' self.computeSize() self._text = _text g = Group() g.translate(self.x + self.dx, self.y + self.dy) g.rotate(self.angle) y = self._top - self._leading * self._baselineRatio textAnchor = self._getTextAnchor() if textAnchor == 'start': x = self._left elif textAnchor == 'middle': x = self._left + self._ewidth * 0.5 else: x = self._right # paint box behind text just in case they # fill it if self.boxFillColor or (self.boxStrokeColor and self.boxStrokeWidth): g.add( Rect(self._left - self.leftPadding, self._bottom - self.bottomPadding, self._width, self._height, strokeColor=self.boxStrokeColor, strokeWidth=self.boxStrokeWidth, fillColor=self.boxFillColor)) fillColor, fontName, fontSize = self.fillColor, self.fontName, self.fontSize strokeColor, strokeWidth, leading = self.strokeColor, self.strokeWidth, self._leading svgAttrs = getattr(self, '_svgAttrs', {}) if strokeColor: for line in self._lines: s = _text2Path(line, x, y, fontName, fontSize, textAnchor) s.fillColor = fillColor s.strokeColor = strokeColor s.strokeWidth = strokeWidth g.add(s) y -= leading else: for line in self._lines: s = String(x, y, line, _svgAttrs=svgAttrs) s.textAnchor = textAnchor s.fontName = fontName s.fontSize = fontSize s.fillColor = fillColor g.add(s) y -= leading return g
def group_demo(): drawing = Drawing(width=400, height=200) radius = 25 circles = Group(Circle(50, 40, radius, fillColor=colors.blue), Circle(75, 40, radius, fillColor=colors.red), Circle(100, 40, radius, fillColor=colors.green), Circle(125, 40, radius, fillColor=colors.yellow), String(75, 5, 'Circles')) drawing.add(circles) more_circles = Group(circles) more_circles.translate(75, 55) more_circles.rotate(35) drawing.add(more_circles) drawing.save(formats=['pdf'], outDir='.', fnRoot='group_demo')
def one_to_image(self, glyph_name): glyphset = self.font.getGlyphSet() try: glyph = glyphset[glyph_name] except KeyError as e: raise KeyError("{} dont't in {}".format(glyph_name, self.font_file_name)) pen = ReportLabPen(self.glyphnames, Path(fillColor=colors.black, strokeWidth=1)) glyph.draw(pen) w, h = glyph.width if glyph.width > 1000 else 1000, glyph.width if glyph.width > 1000 else 1000 d = Drawing(w, h) g = Group(pen.path) g.translate(0, 150) d.add(g) im = renderPM.drawToPIL(d, dpi=72).resize((FONT_WIDTH, FONT_HEIGHT)) return im
def draw_one(self, unicode, font_io): ttf_font = TTFont(font_io) glyphSet = ttf_font.getGlyphSet() if unicode not in glyphSet: return None glyph = glyphSet[unicode] pen = reportLabPen.ReportLabPen( glyphSet, Path(fillColor=colors.black, strokeWidth=1)) glyph.draw(pen) w, h = glyph.width, glyph._glyph.yMax - glyph._glyph.yMin yOffset = -glyph._glyph.yMin * \ self.font_scale + h * (1 - self.font_scale) / 2 glyph = Group(pen.path) glyph.translate(w * (1 - self.font_scale) / 2, yOffset) glyph.scale(self.font_scale, self.font_scale) draw = Drawing(w, h) draw.add(glyph) PIL_image = renderPM.drawToPIL(draw) return PIL_image
def ttfToImage(fontName, imagePath, fmt="png"): font = TTFont(fontName) gs = font.getGlyphSet() glyphNames = font.getGlyphNames() for i in glyphNames: if i[0] == '.': #Ìø¹ý'.notdef', '.null' continue g = gs[i] pen = ReportLabPen(gs, Path(fillColor=colors.red, strokeWidth=5)) g.draw(pen) w, h = g.width, g.width g = Group(pen.path) g.translate(0, 200) d = Drawing(w, h) d.add(g) imageFile = imagePath + "/" + i + ".png" renderPM.drawToFile(d, imageFile, fmt) print(i)
def _draw(self): """ :param font_path: 下载的web 字体所在目录 :param image_path: 转换为image后所在目录 :return:返回font 对应的编码列表 """ import os import shutil shutil.rmtree(os.path.join(os.path.abspath('.'), "web_font", "image")) # 能删除该文件夹和文件夹下所有文件 os.mkdir(os.path.join(os.path.abspath('.'), "web_font", "image")) from fontTools.ttLib import TTFont from reportlab.lib import colors font = TTFont( os.path.join(os.path.abspath('.'), "web_font", "stonefont.woff") ) # it would work just as well with fontTools.t1Lib.T1Font gs = font.getGlyphSet() w, h = 40, 40 keys = gs.keys()[2:] for glyphName in keys: pen = FontPen(gs, Path(fillColor=colors.red, strokeWidth=0.01)) imageFile = "%s.png" % glyphName g = gs[glyphName] g.draw(pen) from reportlab.graphics import renderPM from reportlab.graphics.shapes import Group, Drawing g = Group(pen.path) g.translate(10, 13) g.scale(0.02, 0.02) d = Drawing(w, h) d.add(g) renderPM.drawToFile(d, os.path.join(os.path.abspath('.'), "web_font", "image", imageFile), fmt="PNG", dpi=100) return keys
def add_vert_line(self, x, y, color, label): i = self.x_to_i(x) j = self.y_to_j(y) self.flowable.add( Line( i, self.j_plot_offset, i, j, strokeColor=color, strokeWidth=0.75)) if not label: return group = Group( String( 0, 0, label, fontSize=self.font_size, fontName='Helvetica', textAnchor='start', fillColor=color)) group.translate(i + 1, j + 2) group.rotate(90) self.flowable.add(group)
def ttfToImage(fontName, imagePath, fmt="png"): font = TTFont(fontName) gs = font.getGlyphSet() glyphNames = font.getGlyphNames() m_dict = font.getBestCmap() unicode_list = [] gs_key=[] for key, value in m_dict.items(): unicode_list.append(key) gs_key.append(value) char_list = [chr(ch_unicode) for ch_unicode in unicode_list] #2500-29000 FZSONG_ZhongHuaSongPlane00_2021120120211201171438.ttf # 275 FZSONG_ZhongHuaSongPlane02_2021120120211201171459.ttf for i in range(len(char_list)): if fontName=='FZSONG_ZhongHuaSongPlane00_2021120120211201171438.ttf' and (i<2500 or i>29000): continue if fontName=='FZSONG_ZhongHuaSongPlane02_2021120120211201171459.ttf' and (i<275 or i>100000000): continue thechr=char_list[i] print(thechr) gs_ind=gs_key[i] if gs_ind[0] == '.': # 跳过'.notdef', '.null' continue g = gs[gs_ind] pen = ReportLabPen(gs, Path(fillColor=colors.black, strokeWidth=5)) g.draw(pen) w, h = g.width, g.width # w = 4048 # h = 4048 g = Group(pen.path) g.translate(0, 170) d = Drawing(w, h) d.add(g) imageFile = imagePath + "/" + thechr + ".png" renderPM.drawToFile(d, imageFile, fmt)
def process(O, grid_ij, xy_max, label, data, label_font_size=12): if (reportlab is None): return from reportlab.graphics.shapes import Group, String lp = O.line_plot(xy_max, data=data, label_font_size=label_font_size) gr = Group(lp) i,j = grid_ij assert 0 <= i < O.grid[0] assert 0 <= j < O.grid[1] i = O.grid[0] - 1 - i tx, ty = O.margin + j * (O.page_size[0] - 2 * O.margin) / O.grid[1] \ - j * O.more_narrow_shift, \ O.margin + i * (O.page_size[1] - 2 * O.margin - O.top_label_space) / O.grid[0] gr.translate(tx, ty) O.top_group.add(gr) O.top_group.add(String( tx+lp.x+lp.width*0.5, ty+lp.y+lp.height*1.05, label, fontSize=label_font_size, textAnchor="middle")) if (i == 0 and j == 0): O.top_group.add(String( tx+lp.x+lp.width*0.5, ty+lp.y-lp.height*0.3, u"RMSD start (\u00C5)", fontSize=label_font_size, textAnchor="middle")) gr = Group(String( 0, 0, u"RMSD final (\u00C5)", fontSize=label_font_size, textAnchor="middle")) gr.rotate(90) gr.translate( ty+lp.y+lp.height*0.5, -(tx+lp.x-lp.width*0.15)) O.top_group.add(gr)
def process(O, grid_ij, xy_max, label, data, label_font_size=12): if (reportlab is None): return from reportlab.graphics.shapes import Group, String lp = O.line_plot(xy_max, data=data, label_font_size=label_font_size) gr = Group(lp) i, j = grid_ij assert 0 <= i < O.grid[0] assert 0 <= j < O.grid[1] i = O.grid[0] - 1 - i tx, ty = O.margin + j * (O.page_size[0] - 2 * O.margin) / O.grid[1] \ - j * O.more_narrow_shift, \ O.margin + i * (O.page_size[1] - 2 * O.margin - O.top_label_space) / O.grid[0] gr.translate(tx, ty) O.top_group.add(gr) O.top_group.add( String(tx + lp.x + lp.width * 0.5, ty + lp.y + lp.height * 1.05, label, fontSize=label_font_size, textAnchor="middle")) if (i == 0 and j == 0): O.top_group.add( String(tx + lp.x + lp.width * 0.5, ty + lp.y - lp.height * 0.3, u"RMSD start (\u00C5)", fontSize=label_font_size, textAnchor="middle")) gr = Group( String(0, 0, u"RMSD final (\u00C5)", fontSize=label_font_size, textAnchor="middle")) gr.rotate(90) gr.translate(ty + lp.y + lp.height * 0.5, -(tx + lp.x - lp.width * 0.15)) O.top_group.add(gr)
def draw_all(self, font_io): ttf_font = TTFont(font_io) glyphSet = ttf_font.getGlyphSet() font_image_dict = {} for glyphName in glyphSet.keys(): if (not glyphName.startswith('uni')): continue pen = reportLabPen.ReportLabPen( glyphSet, Path(fillColor=colors.black, strokeWidth=1)) glyph = glyphSet[glyphName] glyph.draw(pen) w, h = glyph.width, glyph._glyph.yMax - glyph._glyph.yMin yOffset = -glyph._glyph.yMin * \ self.font_scale + h * (1 - self.font_scale) / 2 glyph = Group(pen.path) glyph.translate(w * (1 - self.font_scale) / 2, yOffset) glyph.scale(self.font_scale, self.font_scale) draw = Drawing(w, h) draw.add(glyph) PIL_image = renderPM.drawToPIL(draw) font_image_dict[glyphName] = PIL_image return font_image_dict
def convertImage(self, node): if not haveImages: logger.warning( "Unable to handle embedded images. Maybe the pillow library is missing?" ) return None getAttr = node.getAttribute x, y, width, height = map(getAttr, ('x', 'y', "width", "height")) x, y, width, height = map(self.attrConverter.convertLength, (x, y, width, height)) image = node._resolved_target is_raster = isinstance(image, str) # A path to a file if is_raster: image = Image(int(x), int(y + height), int(width), int(height), image) group = Group(image) if is_raster: group.translate(0, (y + height) * 2) group.scale(1, -1) return group
def draw(self, dx=5, dy=300, auto_width=500, auto_height=500): """ 绘图操作 :param dx: 图片绘图位置 :param dy: 图片绘图位置 :param auto_width: 自适应图片宽度(调节字体) :param auto_height: 自适应图片高度(调节字体) :return: """ def reverse_dict(data: dict): out = {} for k, v in data.items(): out[str(v)] = str(k) return out font = TTFont(self.fontName) img_dict = font['cmap'].tables[2].ttFont.tables['cmap'].tables[1].cmap img_dict = reverse_dict(img_dict) gs = font.getGlyphSet() glyphNames = font.getGlyphNames() for i in glyphNames: name = self.key(img_dict.get(i)) if name == None: continue g = gs[i] pen = ReportLabPen(gs, Path(fillColor=colors.black, strokeWidth=10)) g.draw(pen) w, h = g.width, g.width g = Group(pen.path) g.translate(dx, dy) d = Drawing(w + auto_width, h + auto_height) d.add(g) imageFile = self.imagePath + "/" + str(name) + "." + self.fmt renderPM.drawToFile(d, imageFile, self.fmt)
glyphName = sys.argv[2] if (len(sys.argv) > 3): imageFile = sys.argv[3] else: imageFile = "%s.png" % glyphName font = TTFont(path) # it would work just as well with fontTools.t1Lib.T1Font gs = font.getGlyphSet() pen = ReportLabPen(gs, Path(fillColor=colors.black, strokeWidth=0)) #pen = ReportLabPen(gs, Path(fillColor=colors.red, strokeWidth=5)) g = gs[glyphName] g.draw(pen) w, h = g.width, 1000 from reportlab.graphics import renderPM from reportlab.graphics.shapes import Group, Drawing, scale # Everything is wrapped in a group to allow transformations. g = Group(pen.path) g.translate(10, 10) #g.scale(0.9, 0.9) #g.scale(0.3, 0.3) g.scale(0.8, 0.8) d = Drawing(w, h) d.add(g) from reportlab.lib.colors import white from reportlab.lib.colors import green renderPM.drawToFile(d, imageFile, fmt="TIFF", configPIL={'transparent':white})
class Renderer: LINK = '{http://www.w3.org/1999/xlink}href' SVG_NS = '{http://www.w3.org/2000/svg}' SVG_ROOT = SVG_NS + 'svg' SVG_A = SVG_NS + 'a' SVG_G = SVG_NS + 'g' SVG_TITLE = SVG_NS + 'title' SVG_DESC = SVG_NS + 'desc' SVG_DEFS = SVG_NS + 'defs' SVG_SYMBOL = SVG_NS + 'symbol' SVG_USE = SVG_NS + 'use' SVG_RECT = SVG_NS + 'rect' SVG_CIRCLE = SVG_NS + 'circle' SVG_ELLIPSE = SVG_NS + 'ellipse' SVG_LINE = SVG_NS + 'line' SVG_POLYLINE = SVG_NS + 'polyline' SVG_POLYGON = SVG_NS + 'polygon' SVG_PATH = SVG_NS + 'path' SVG_TEXT = SVG_NS + 'text' SVG_TSPAN = SVG_NS + 'tspan' SVG_IMAGE = SVG_NS + 'image' SVG_NODES = frozenset(( SVG_ROOT, SVG_A, SVG_G, SVG_TITLE, SVG_DESC, SVG_DEFS, SVG_SYMBOL, SVG_USE, SVG_RECT, SVG_CIRCLE, SVG_ELLIPSE, SVG_LINE, SVG_POLYLINE, SVG_POLYGON, SVG_PATH, SVG_TEXT, SVG_TSPAN, SVG_IMAGE )) SKIP_NODES = frozenset((SVG_TITLE, SVG_DESC, SVG_DEFS, SVG_SYMBOL)) PATH_NODES = frozenset((SVG_RECT, SVG_CIRCLE, SVG_ELLIPSE, SVG_LINE, SVG_POLYLINE, SVG_POLYGON, SVG_PATH, SVG_TEXT)) def __init__(self, filename): self.filename = filename self.level = 0 self.styles = {} self.mainGroup = Group() self.drawing = None self.root = None def render(self, node, parent=None): if parent is None: parent = self.mainGroup # ignore if display = none display = node.get('display') if display == "none": return if node.tag == self.SVG_ROOT: self.level += 1 if not self.drawing is None: raise SVGError('drawing already created!') self.root = node # default styles style = { 'color':'none', 'fill':'none', 'stroke':'none', 'font-family':'Helvetica', 'font-size':'12' } self.styles[self.level] = style # iterate children for child in node: self.render(child, self.mainGroup) # create drawing width = node.get('width', '100%') height = node.get('height', '100%') if node.get("viewBox"): try: minx, miny, width, height = node.get("viewBox").split() except ValueError: raise SVGError("viewBox values not valid") if width.endswith('%') and height.endswith('%'): # handle relative size wscale = parseLength(width) / 100. hscale = parseLength(height) / 100. xL,yL,xH,yH = self.mainGroup.getBounds() self.drawing = Drawing(xH*wscale + xL, yH*hscale + yL) else: self.drawing = Drawing(parseLength(width), parseLength(height)) height = self.drawing.height self.mainGroup.scale(1, -1) self.mainGroup.translate(0, -height) self.drawing.add(self.mainGroup) self.level -= 1 return self.drawing elif node.tag in (self.SVG_G, self.SVG_A): self.level += 1 # set this levels style style = self.styles[self.level - 1].copy() style = self.nodeStyle(node, style) self.styles[self.level] = style group = Group() # iterate children for child in node: self.render(child, group) parent.add(group) transforms = node.get('transform') if transforms: for op in parseTransform.iterparse(transforms): self.applyTransformOnGroup(group, op) self.level -= 1 elif node.tag == self.SVG_USE: self.level += 1 # set this levels style style = self.styles[self.level - 1].copy() style = self.nodeStyle(node, style) self.styles[self.level] = style group = Group() # link id link_id = node.get(self.LINK).lstrip('#') # find linked node in defs or symbol section target = None for defs in self.root.getiterator(self.SVG_DEFS): for element in defs: if element.get('id') == link_id: target = element break if target is None: for defs in self.root.getiterator(self.SVG_SYMBOL): for element in defs: if element.get('id') == link_id: target = element break if target is None: msg = "Could not find use node '%s'" % link_id raise SVGError(msg) self.render(target, group) parent.add(group) # apply transform transforms = node.get('transform') if transforms: for op in parseTransform.iterparse(transforms): self.applyTransformOnGroup(group, op) # apply 'x' and 'y' attribute as translation of defs object if node.get('x') or node.get('y'): dx = parseLength(node.get('x','0')) dy = parseLength(node.get('y','0')) self.applyTransformOnGroup(group, ('translate', (dx,dy))) self.level -= 1 elif node.tag == self.SVG_LINE: # get coordinates x1 = parseLength(node.get('x1', '0')) y1 = parseLength(node.get('y1', '0')) x2 = parseLength(node.get('x2', '0')) y2 = parseLength(node.get('y2', '0')) shape = Line(x1, y1, x2, y2) self.addShape(parent, node, shape) elif node.tag == self.SVG_RECT: # get coordinates x = parseLength(node.get('x', '0')) y = parseLength(node.get('y', '0')) width = parseLength(node.get('width')) height = parseLength(node.get('height')) rx = parseLength(node.get('rx', '0')) ry = parseLength(node.get('ry', '0')) shape = Rect(x, y, width, height, rx=rx, ry=ry) self.addShape(parent, node, shape) elif node.tag == self.SVG_CIRCLE: cx = parseLength(node.get('cx', '0')) cy = parseLength(node.get('cy', '0')) r = parseLength(node.get('r')) if r > 0.: shape = Circle(cx, cy, r) self.addShape(parent, node, shape) elif node.tag == self.SVG_ELLIPSE: cx = parseLength(node.get('cx', '0')) cy = parseLength(node.get('cy', '0')) rx = parseLength(node.get('rx')) ry = parseLength(node.get('ry')) if rx > 0. and ry > 0.: shape = Ellipse(cx, cy, rx, ry) self.addShape(parent, node, shape) elif node.tag == self.SVG_POLYLINE: # convert points points = node.get('points').strip() if len(points) == 0: return points = list(map(parseLength, re.split('[ ,]+', points))) # Need to use two shapes, because standard RLG polylines # do not support filling... group = Group() shape = Polygon(points) self.applyStyleToShape(shape, node) shape.strokeColor = None group.add(shape) shape = PolyLine(points) self.applyStyleToShape(shape, node) group.add(shape) self.addShape(parent, node, group) elif node.tag == self.SVG_POLYGON: # convert points points = node.get('points').strip() if len(points) == 0: return points = list(map(parseLength, re.split('[ ,]+', points))) shape = Polygon(points) self.addShape(parent, node, shape) elif node.tag == self.SVG_IMAGE: x = parseLength(node.get('x', '0')) y = parseLength(node.get('y', '0')) width = parseLength(node.get('width', '0')) height = parseLength(node.get('height', '0')) # link id link_id = node.get(self.LINK) filename = os.path.join(os.path.dirname(self.filename), link_id) shape = Image(x, y, width, height, filename) self.addShape(parent, node, shape) elif node.tag == self.SVG_TEXT: # Todo: # - rotation not handled # - baseshift not handled # - embedded span node not handled # def parsePos(node, subnode, name, default='0'): values = subnode.get(name) if values is None: if node is not None: values = node.get(name, default) else: values = default return list(map(parseLength, values.split())) def getPos(values, i, default=None): if i >= len(values): if default is None: return values[-1] else: return default else: return values[i] def handleText(node, subnode, text): # get position variables xs = parsePos(node, subnode, 'x') dxs = parsePos(node, subnode, 'dx') ys = parsePos(node, subnode,'y') dys = parsePos(node, subnode,'dy') if sum(map(len, (xs,ys,dxs,dys))) == 4: # single value shape = String(xs[0] + dxs[0], -ys[0] - dys[0], text) self.applyStyleToShape(shape, subnode) group.add(shape) else: # multiple values for i, c in enumerate(text): x = getPos(xs, i) dx = getPos(dxs, i, 0) y = getPos(ys, i) dy = getPos(dys, i, 0) shape = String(x + dx, -y -dy, c) self.applyStyleToShape(shape, subnode) group.add(shape) if node.text and node.text.strip(): group = Group() handleText(None, node, node.text.strip()) group.scale(1, -1) self.addShape(parent, node, group) if len(node) > 0: group = Group() self.level += 1 # set this levels style style = self.styles[self.level - 1].copy() nodestylestyle = self.nodeStyle(node, style) self.styles[self.level] = nodestylestyle for subnode in node: if subnode.tag == self.SVG_TSPAN: handleText(node, subnode, subnode.text.strip()) self.level -= 1 group.scale(1, -1) self.addShape(parent, node, group) elif node.tag == self.SVG_PATH: def convertQuadratic(Q0, Q1, Q2): C1 = (Q0[0] + 2./3*(Q1[0] - Q0[0]), Q0[1] + 2./3*(Q1[1] - Q0[1])) C2 = (C1[0] + 1./3*(Q2[0] - Q0[0]), C1[1] + 1./3*(Q2[1] - Q0[1])) C3 = Q2 return C1[0], C1[1], C2[0], C2[1], C3[0], C3[1] def prevCtrl(lastOp, lastArgs, currentX, currentY): # fetch last controll point if lastOp in 'CScsQqTt': x, y = lastArgs[-2] # mirror about current point return currentX + (currentX-x), currentY + (currentY-y) else: # defaults to current point return currentX, currentY # store sub paths in 'paths' list shape = Path() # keep track of current point and path start point startX, startY = 0., 0. currentX, currentY = 0., 0. # keep track of last operation lastOp = None lastArgs = None # avoid empty path data data = node.get('d') if data is None or len(data) == 0: return for op, args in parsePath.iterparse(data): if op == 'z' or op == 'Z': # close path or subpath shape.closePath() # next sub path starts at begining of current path currentX, currentY = startX, startY elif op == 'M': # moveto absolute if lastOp is not None and lastOp not in ('z', 'Z'): # close sub path shape.closePath() x, y = args[0] shape.moveTo(x, y) startX, startY = x, y # multiple moveto arge result in line for x, y in args[1:]: shape.lineTo(x, y) currentX, currentY = x, y elif op == 'm': if lastOp is not None and lastOp not in ('z', 'Z'): # close sub path shape.closePath() # moveto relative rx, ry = args[0] x, y = currentX + rx, currentY + ry shape.moveTo(x, y) startX, startY = x, y currentX, currentY = x, y # multiple moveto arge result in line for rx, ry in args[1:]: x, y = currentX + rx, currentY + ry shape.lineTo(x, y) currentX, currentY = x, y elif op == 'L': # lineto absolute for x, y in args: shape.lineTo(x, y) currentX, currentY = x, y elif op == 'l': # lineto relative for rx, ry in args: x, y = currentX + rx, currentY + ry shape.lineTo(x, y) currentX, currentY = x, y elif op == 'V': # vertical line absolute for y in args: shape.lineTo(currentX, y) currentY = y elif op == 'v': # vertical line relative for ry in args: y = currentY + ry shape.lineTo(currentX, y) currentY = y elif op == 'H': # horisontal line absolute for x in args: shape.lineTo(x, currentY) currentX = x elif op == 'h': # horisontal line relative for rx in args: x = currentX + rx shape.lineTo(x, currentY) currentX = x elif op == 'C': # cubic bezier absolute for p1, p2, p3 in zip(*([iter(args)] * 3)): shape.curveTo(*(p1 + p2 + p3)) currentX, currentY = p3 elif op == 'c': # cubic bezier relative for pnts in zip(*([iter(args)] * 3)): (x1, y1), (x2, y2), (x3, y3) = pnts pnts = tuple((p[0] + currentX, p[1] + currentY) for p in pnts) shape.curveTo(*(pnts[0] + pnts[1] + pnts[2])) currentX, currentY = pnts[2] lastOp = op lastArgs = pnts continue elif op == 'S': # shorthand cubic bezier absolute for p2, p3 in zip(*([iter(args)] * 2)): x1, y1 = prevCtrl(lastOp, lastArgs, currentX, currentY) x2, y2 = p2 x3, y3 = p3 shape.curveTo(x1, y1, x2, y2, x3, y3) lastOp = op lastArgs = (x2, y2), (x3, y3) currentX, currentY = x3, y3 continue elif op == 's': # shorthand cubic bezier relative for p2, p3 in zip(*([iter(args)] * 2)): x1, y1 = prevCtrl(lastOp, lastArgs, currentX, currentY) x2, y2 = p2 x2, y2 = x2 + currentX, y2 + currentY x3, y3 = p3 x3, y3 = x3 + currentX, y3 + currentY shape.curveTo(x1, y1, x2, y2, x3, y3) currentX, currentY = x3, y3 lastOp = op lastArgs = (x1, y1), (x2, y2), (x3, y3) continue elif op == 'Q': # quadratic bezier absolute for p2, p3 in zip(*([iter(args)] * 2)): x1, y1 = currentX, currentY x2, y2 = p2 x3, y3 = p3 ctrls = convertQuadratic((x1, y1), (x2, y2), (x3, y3)) shape.curveTo(*ctrls) currentX, currentY = x3, y3 lastOp = op lastArgs = (x2, y2), (x3, y3) continue elif op == 'q': # quadratic bezier relative for p2, p3 in zip(*([iter(args)] * 2)): x1, y1 = currentX, currentY x2, y2 = p2 x2, y2 = x2 + currentX, y2 + currentY x3, y3 = p3 x3, y3 = x3 + currentX, y3 + currentY ctrls = convertQuadratic((x1, y1), (x2, y2), (x3, y3)) shape.curveTo(*ctrls) currentX, currentY = x3, y3 lastOp = op lastArgs = (x2, y2), (x3, y3) continue elif op == 'T': # shorthand quadratic bezier absolute for i in range(len(args)): x1, y1 = currentX, currentY x2, y2 = prevCtrl(lastOp, lastArgs, currentX, currentY) x3, y3 = args[i] ctrls = convertQuadratic((x1, y1), (x2, y2), (x3, y3)) shape.curveTo(*ctrls) currentX, currentY = x3, y3 lastOp = op lastArgs = (x2, y2), (x3, y3) continue elif op == 't': # shorthand quadratic bezier relative for i in range(len(args)): x1, y1 = currentX, currentY x2, y2 = prevCtrl(lastOp, lastArgs, currentX, currentY) x3, y3 = args[i] x3, y3 = x3 + currentX, y3 + currentY ctrls = convertQuadratic((x1, y1), (x2, y2), (x3, y3)) shape.curveTo(*ctrls) currentX, currentY = x3, y3 lastOp = op lastArgs = (x2, y2), (x3, y3) continue elif op == 'A' or op == 'a': # elliptic arc missing continue lastOp = op lastArgs = args # check if fill applies to path fill = None if node.get('fill'): # inline style fill = node.get('fill') else: # try local style if node.get('style'): style = parseStyle.parse(node.get('style')) if 'fill' in style: fill = style['fill'] # try global style if fill is None: style = self.styles[self.level] if 'fill' in style: fill = style['fill'] else: fill = 'none' # hack because RLG has no "semi-closed" paths... if lastOp == 'z' or lastOp == 'Z' or fill == 'none': self.addShape(parent, node, shape) else: group = Group() strokeshape = shape.copy() self.addShape(group, node, strokeshape, fill='none') shape.closePath() self.addShape(group, node, shape, stroke='none') self.addShape(parent, node, group) elif node.tag == self.SVG_TITLE or node.tag == self.SVG_DESC: # Skip non-graphics elements return def addShape(self, parent, node, shape, **kwargs): self.applyStyleToShape(shape, node, **kwargs) transform = node.get('transform') if transform: # transform can only be applied to a group if isinstance(node, Group): group = node else: group = Group() for op in parseTransform.iterparse(transform): self.applyTransformOnGroup(group, op) if not isinstance(node, Group): group.add(shape) parent.add(group) else: parent.add(shape) return shape def nodeStyle(self, node, style=None): if style is None: style = {} # update with local style if node.get('style'): localstyle = parseStyle.parse(node.get('style')) for name, value in localstyle.items(): style[name] = value # update with inline style for name in STYLE_NAMES: value = node.get(name) if value: style[name] = value return style def applyStyleToShape(self, shape, node, **kwargs): # fetch global styles for this level globalstyle = self.styles[self.level] # update with node style localstyle = self.nodeStyle(node) for name in STYLES: # avoid text styles if not a String subclass if not isinstance(shape, String) and name in STYLES_FONT: continue value = None if name in kwargs: value = kwargs[name] elif name in localstyle: value = localstyle[name] elif name in globalstyle: value = globalstyle[name] else: continue # handle 'currentColor' if value == 'currentColor': if 'color' in localstyle: value = localstyle['color'] elif 'color' in globalstyle: value = globalstyle['color'] # fetch shape attribute name and converter attrname, converter = STYLES[name] # Sett attribute of shape if hasattr(shape, attrname): setattr(shape, attrname, converter(value)) # defaults if isinstance(shape, String): if shape.fillColor is None: shape.fillColor = colors.black elif isinstance(shape, (Line, PolyLine)): if shape.strokeColor is None: shape.strokeColor = colors.black elif isinstance(shape, (Rect, Circle, Ellipse, Polygon)): if shape.fillColor is None and shape.strokeColor is None: shape.strokeColor = colors.black def applyTransformOnGroup(self, group, transform): op, args = transform if op == 'scale': group.scale(*args) elif op == 'translate': group.translate(*args) elif op == 'rotate': if len(args) == 2: angle, center = args if center: cx, cy = center group.translate(cx, cy) group.rotate(angle) group.translate(-cx, -cy) else: group.rotate(angle) elif op == "skewX": angle = args[0] group.skew(angle, 0) elif op == "skewY": angle = args[0] group.skew(0, angle) elif op == "matrix": group.transform = mmult(group.transform, args)
from fontTools.ttLib import TTFont from reportlab.lib import colors path = sys.argv[1] glyphName = sys.argv[2] if (len(sys.argv) > 3): imageFile = sys.argv[3] else: imageFile = "%s.png" % glyphName font = TTFont(path) # it would work just as well with fontTools.t1Lib.T1Font gs = font.getGlyphSet() pen = ReportLabPen(gs, Path(fillColor=colors.red, strokeWidth=5)) g = gs[glyphName] g.draw(pen) w, h = g.width, 1000 from reportlab.graphics import renderPM from reportlab.graphics.shapes import Group, Drawing, scale # Everything is wrapped in a group to allow transformations. g = Group(pen.path) g.translate(0, 200) g.scale(0.3, 0.3) d = Drawing(w, h) d.add(g) renderPM.drawToFile(d, imageFile, fmt="PNG")
class Renderer: LINK = '{http://www.w3.org/1999/xlink}href' SVG_NS = '{http://www.w3.org/2000/svg}' SVG_ROOT = SVG_NS + 'svg' SVG_A = SVG_NS + 'a' SVG_G = SVG_NS + 'g' SVG_TITLE = SVG_NS + 'title' SVG_DESC = SVG_NS + 'desc' SVG_DEFS = SVG_NS + 'defs' SVG_SYMBOL = SVG_NS + 'symbol' SVG_USE = SVG_NS + 'use' SVG_RECT = SVG_NS + 'rect' SVG_CIRCLE = SVG_NS + 'circle' SVG_ELLIPSE = SVG_NS + 'ellipse' SVG_LINE = SVG_NS + 'line' SVG_POLYLINE = SVG_NS + 'polyline' SVG_POLYGON = SVG_NS + 'polygon' SVG_PATH = SVG_NS + 'path' SVG_TEXT = SVG_NS + 'text' SVG_TSPAN = SVG_NS + 'tspan' SVG_IMAGE = SVG_NS + 'image' SVG_NODES = frozenset( (SVG_ROOT, SVG_A, SVG_G, SVG_TITLE, SVG_DESC, SVG_DEFS, SVG_SYMBOL, SVG_USE, SVG_RECT, SVG_CIRCLE, SVG_ELLIPSE, SVG_LINE, SVG_POLYLINE, SVG_POLYGON, SVG_PATH, SVG_TEXT, SVG_TSPAN, SVG_IMAGE)) SKIP_NODES = frozenset((SVG_TITLE, SVG_DESC, SVG_DEFS, SVG_SYMBOL)) PATH_NODES = frozenset((SVG_RECT, SVG_CIRCLE, SVG_ELLIPSE, SVG_LINE, SVG_POLYLINE, SVG_POLYGON, SVG_PATH, SVG_TEXT)) def __init__(self, filename): self.filename = filename self.level = 0 self.styles = {} self.mainGroup = Group() self.drawing = None self.root = None def render(self, node, parent=None): if parent is None: parent = self.mainGroup # ignore if display = none display = node.get('display') if display == "none": return if node.tag == self.SVG_ROOT: self.level += 1 if not self.drawing is None: raise SVGError('drawing already created!') self.root = node # default styles style = { 'color': 'none', 'fill': 'none', 'stroke': 'none', 'font-family': 'Helvetica', 'font-size': '12' } self.styles[self.level] = style # iterate children for child in node: self.render(child, self.mainGroup) # create drawing width = node.get('width', '100%') height = node.get('height', '100%') if node.get("viewBox"): try: minx, miny, width, height = node.get("viewBox").split() except ValueError: raise SVGError("viewBox values not valid") if width.endswith('%') and height.endswith('%'): # handle relative size wscale = parseLength(width) / 100. hscale = parseLength(height) / 100. xL, yL, xH, yH = self.mainGroup.getBounds() self.drawing = Drawing(xH * wscale + xL, yH * hscale + yL) else: self.drawing = Drawing(parseLength(width), parseLength(height)) height = self.drawing.height self.mainGroup.scale(1, -1) self.mainGroup.translate(0, -height) self.drawing.add(self.mainGroup) self.level -= 1 return self.drawing elif node.tag in (self.SVG_G, self.SVG_A): self.level += 1 # set this levels style style = self.styles[self.level - 1].copy() style = self.nodeStyle(node, style) self.styles[self.level] = style group = Group() # iterate children for child in node: self.render(child, group) parent.add(group) transforms = node.get('transform') if transforms: for op in parseTransform.iterparse(transforms): self.applyTransformOnGroup(group, op) self.level -= 1 elif node.tag == self.SVG_USE: self.level += 1 # set this levels style style = self.styles[self.level - 1].copy() style = self.nodeStyle(node, style) self.styles[self.level] = style group = Group() # link id link_id = node.get(self.LINK).lstrip('#') # find linked node in defs or symbol section target = None for defs in self.root.getiterator(self.SVG_DEFS): for element in defs: if element.get('id') == link_id: target = element break if target is None: for defs in self.root.getiterator(self.SVG_SYMBOL): for element in defs: if element.get('id') == link_id: target = element break if target is None: msg = "Could not find use node '%s'" % link_id raise SVGError(msg) self.render(target, group) parent.add(group) # apply transform transforms = node.get('transform') if transforms: for op in parseTransform.iterparse(transforms): self.applyTransformOnGroup(group, op) # apply 'x' and 'y' attribute as translation of defs object if node.get('x') or node.get('y'): dx = parseLength(node.get('x', '0')) dy = parseLength(node.get('y', '0')) self.applyTransformOnGroup(group, ('translate', (dx, dy))) self.level -= 1 elif node.tag == self.SVG_LINE: # get coordinates x1 = parseLength(node.get('x1', '0')) y1 = parseLength(node.get('y1', '0')) x2 = parseLength(node.get('x2', '0')) y2 = parseLength(node.get('y2', '0')) shape = Line(x1, y1, x2, y2) self.addShape(parent, node, shape) elif node.tag == self.SVG_RECT: # get coordinates x = parseLength(node.get('x', '0')) y = parseLength(node.get('y', '0')) width = parseLength(node.get('width')) height = parseLength(node.get('height')) rx = parseLength(node.get('rx', '0')) ry = parseLength(node.get('ry', '0')) shape = Rect(x, y, width, height, rx=rx, ry=ry) self.addShape(parent, node, shape) elif node.tag == self.SVG_CIRCLE: cx = parseLength(node.get('cx', '0')) cy = parseLength(node.get('cy', '0')) r = parseLength(node.get('r')) if r > 0.: shape = Circle(cx, cy, r) self.addShape(parent, node, shape) elif node.tag == self.SVG_ELLIPSE: cx = parseLength(node.get('cx', '0')) cy = parseLength(node.get('cy', '0')) rx = parseLength(node.get('rx')) ry = parseLength(node.get('ry')) if rx > 0. and ry > 0.: shape = Ellipse(cx, cy, rx, ry) self.addShape(parent, node, shape) elif node.tag == self.SVG_POLYLINE: # convert points points = node.get('points').strip() if len(points) == 0: return points = list(map(parseLength, re.split('[ ,]+', points))) # Need to use two shapes, because standard RLG polylines # do not support filling... group = Group() shape = Polygon(points) self.applyStyleToShape(shape, node) shape.strokeColor = None group.add(shape) shape = PolyLine(points) self.applyStyleToShape(shape, node) group.add(shape) self.addShape(parent, node, group) elif node.tag == self.SVG_POLYGON: # convert points points = node.get('points').strip() if len(points) == 0: return points = list(map(parseLength, re.split('[ ,]+', points))) shape = Polygon(points) self.addShape(parent, node, shape) elif node.tag == self.SVG_IMAGE: x = parseLength(node.get('x', '0')) y = parseLength(node.get('y', '0')) width = parseLength(node.get('width', '0')) height = parseLength(node.get('height', '0')) # link id link_id = node.get(self.LINK) filename = os.path.join(os.path.dirname(self.filename), link_id) shape = Image(x, y, width, height, filename) self.addShape(parent, node, shape) elif node.tag == self.SVG_TEXT: # Todo: # - rotation not handled # - baseshift not handled # - embedded span node not handled # def parsePos(node, subnode, name, default='0'): values = subnode.get(name) if values is None: if node is not None: values = node.get(name, default) else: values = default return list(map(parseLength, values.split())) def getPos(values, i, default=None): if i >= len(values): if default is None: return values[-1] else: return default else: return values[i] def handleText(node, subnode, text): # get position variables xs = parsePos(node, subnode, 'x') dxs = parsePos(node, subnode, 'dx') ys = parsePos(node, subnode, 'y') dys = parsePos(node, subnode, 'dy') if sum(map(len, (xs, ys, dxs, dys))) == 4: # single value shape = String(xs[0] + dxs[0], -ys[0] - dys[0], text) self.applyStyleToShape(shape, subnode) group.add(shape) else: # multiple values for i, c in enumerate(text): x = getPos(xs, i) dx = getPos(dxs, i, 0) y = getPos(ys, i) dy = getPos(dys, i, 0) shape = String(x + dx, -y - dy, c) self.applyStyleToShape(shape, subnode) group.add(shape) if node.text and node.text.strip(): group = Group() handleText(None, node, node.text.strip()) group.scale(1, -1) self.addShape(parent, node, group) if len(node) > 0: group = Group() self.level += 1 # set this levels style style = self.styles[self.level - 1].copy() nodestylestyle = self.nodeStyle(node, style) self.styles[self.level] = nodestylestyle for subnode in node: if subnode.tag == self.SVG_TSPAN: handleText(node, subnode, subnode.text.strip()) self.level -= 1 group.scale(1, -1) self.addShape(parent, node, group) elif node.tag == self.SVG_PATH: def convertQuadratic(Q0, Q1, Q2): C1 = (Q0[0] + 2. / 3 * (Q1[0] - Q0[0]), Q0[1] + 2. / 3 * (Q1[1] - Q0[1])) C2 = (C1[0] + 1. / 3 * (Q2[0] - Q0[0]), C1[1] + 1. / 3 * (Q2[1] - Q0[1])) C3 = Q2 return C1[0], C1[1], C2[0], C2[1], C3[0], C3[1] def prevCtrl(lastOp, lastArgs, currentX, currentY): # fetch last controll point if lastOp in 'CScsQqTt': x, y = lastArgs[-2] # mirror about current point return currentX + (currentX - x), currentY + (currentY - y) else: # defaults to current point return currentX, currentY # store sub paths in 'paths' list shape = Path() # keep track of current point and path start point startX, startY = 0., 0. currentX, currentY = 0., 0. # keep track of last operation lastOp = None lastArgs = None # avoid empty path data data = node.get('d') if data is None or len(data) == 0: return for op, args in parsePath.iterparse(data): if op == 'z' or op == 'Z': # close path or subpath shape.closePath() # next sub path starts at begining of current path currentX, currentY = startX, startY elif op == 'M': # moveto absolute if lastOp is not None and lastOp not in ('z', 'Z'): # close sub path shape.closePath() x, y = args[0] shape.moveTo(x, y) startX, startY = x, y # multiple moveto arge result in line for x, y in args[1:]: shape.lineTo(x, y) currentX, currentY = x, y elif op == 'm': if lastOp is not None and lastOp not in ('z', 'Z'): # close sub path shape.closePath() # moveto relative rx, ry = args[0] x, y = currentX + rx, currentY + ry shape.moveTo(x, y) startX, startY = x, y currentX, currentY = x, y # multiple moveto arge result in line for rx, ry in args[1:]: x, y = currentX + rx, currentY + ry shape.lineTo(x, y) currentX, currentY = x, y elif op == 'L': # lineto absolute for x, y in args: shape.lineTo(x, y) currentX, currentY = x, y elif op == 'l': # lineto relative for rx, ry in args: x, y = currentX + rx, currentY + ry shape.lineTo(x, y) currentX, currentY = x, y elif op == 'V': # vertical line absolute for y in args: shape.lineTo(currentX, y) currentY = y elif op == 'v': # vertical line relative for ry in args: y = currentY + ry shape.lineTo(currentX, y) currentY = y elif op == 'H': # horisontal line absolute for x in args: shape.lineTo(x, currentY) currentX = x elif op == 'h': # horisontal line relative for rx in args: x = currentX + rx shape.lineTo(x, currentY) currentX = x elif op == 'C': # cubic bezier absolute for p1, p2, p3 in zip(*([iter(args)] * 3)): shape.curveTo(*(p1 + p2 + p3)) currentX, currentY = p3 elif op == 'c': # cubic bezier relative for pnts in zip(*([iter(args)] * 3)): (x1, y1), (x2, y2), (x3, y3) = pnts pnts = tuple( (p[0] + currentX, p[1] + currentY) for p in pnts) shape.curveTo(*(pnts[0] + pnts[1] + pnts[2])) currentX, currentY = pnts[2] lastOp = op lastArgs = pnts continue elif op == 'S': # shorthand cubic bezier absolute for p2, p3 in zip(*([iter(args)] * 2)): x1, y1 = prevCtrl(lastOp, lastArgs, currentX, currentY) x2, y2 = p2 x3, y3 = p3 shape.curveTo(x1, y1, x2, y2, x3, y3) lastOp = op lastArgs = (x2, y2), (x3, y3) currentX, currentY = x3, y3 continue elif op == 's': # shorthand cubic bezier relative for p2, p3 in zip(*([iter(args)] * 2)): x1, y1 = prevCtrl(lastOp, lastArgs, currentX, currentY) x2, y2 = p2 x2, y2 = x2 + currentX, y2 + currentY x3, y3 = p3 x3, y3 = x3 + currentX, y3 + currentY shape.curveTo(x1, y1, x2, y2, x3, y3) currentX, currentY = x3, y3 lastOp = op lastArgs = (x1, y1), (x2, y2), (x3, y3) continue elif op == 'Q': # quadratic bezier absolute for p2, p3 in zip(*([iter(args)] * 2)): x1, y1 = currentX, currentY x2, y2 = p2 x3, y3 = p3 ctrls = convertQuadratic((x1, y1), (x2, y2), (x3, y3)) shape.curveTo(*ctrls) currentX, currentY = x3, y3 lastOp = op lastArgs = (x2, y2), (x3, y3) continue elif op == 'q': # quadratic bezier relative for p2, p3 in zip(*([iter(args)] * 2)): x1, y1 = currentX, currentY x2, y2 = p2 x2, y2 = x2 + currentX, y2 + currentY x3, y3 = p3 x3, y3 = x3 + currentX, y3 + currentY ctrls = convertQuadratic((x1, y1), (x2, y2), (x3, y3)) shape.curveTo(*ctrls) currentX, currentY = x3, y3 lastOp = op lastArgs = (x2, y2), (x3, y3) continue elif op == 'T': # shorthand quadratic bezier absolute for i in range(len(args)): x1, y1 = currentX, currentY x2, y2 = prevCtrl(lastOp, lastArgs, currentX, currentY) x3, y3 = args[i] ctrls = convertQuadratic((x1, y1), (x2, y2), (x3, y3)) shape.curveTo(*ctrls) currentX, currentY = x3, y3 lastOp = op lastArgs = (x2, y2), (x3, y3) continue elif op == 't': # shorthand quadratic bezier relative for i in range(len(args)): x1, y1 = currentX, currentY x2, y2 = prevCtrl(lastOp, lastArgs, currentX, currentY) x3, y3 = args[i] x3, y3 = x3 + currentX, y3 + currentY ctrls = convertQuadratic((x1, y1), (x2, y2), (x3, y3)) shape.curveTo(*ctrls) currentX, currentY = x3, y3 lastOp = op lastArgs = (x2, y2), (x3, y3) continue elif op == 'A' or op == 'a': # elliptic arc missing continue lastOp = op lastArgs = args # check if fill applies to path fill = None if node.get('fill'): # inline style fill = node.get('fill') else: # try local style if node.get('style'): style = parseStyle.parse(node.get('style')) if 'fill' in style: fill = style['fill'] # try global style if fill is None: style = self.styles[self.level] if 'fill' in style: fill = style['fill'] else: fill = 'none' # hack because RLG has no "semi-closed" paths... if lastOp == 'z' or lastOp == 'Z' or fill == 'none': self.addShape(parent, node, shape) else: group = Group() strokeshape = shape.copy() self.addShape(group, node, strokeshape, fill='none') shape.closePath() self.addShape(group, node, shape, stroke='none') self.addShape(parent, node, group) elif node.tag == self.SVG_TITLE or node.tag == self.SVG_DESC: # Skip non-graphics elements return def addShape(self, parent, node, shape, **kwargs): self.applyStyleToShape(shape, node, **kwargs) transform = node.get('transform') if transform: # transform can only be applied to a group if isinstance(node, Group): group = node else: group = Group() for op in parseTransform.iterparse(transform): self.applyTransformOnGroup(group, op) if not isinstance(node, Group): group.add(shape) parent.add(group) else: parent.add(shape) return shape def nodeStyle(self, node, style=None): if style is None: style = {} # update with local style if node.get('style'): localstyle = parseStyle.parse(node.get('style')) for name, value in localstyle.items(): style[name] = value # update with inline style for name in STYLE_NAMES: value = node.get(name) if value: style[name] = value return style def applyStyleToShape(self, shape, node, **kwargs): # fetch global styles for this level globalstyle = self.styles[self.level] # update with node style localstyle = self.nodeStyle(node) for name in STYLES: # avoid text styles if not a String subclass if not isinstance(shape, String) and name in STYLES_FONT: continue value = None if name in kwargs: value = kwargs[name] elif name in localstyle: value = localstyle[name] elif name in globalstyle: value = globalstyle[name] else: continue # handle 'currentColor' if value == 'currentColor': if 'color' in localstyle: value = localstyle['color'] elif 'color' in globalstyle: value = globalstyle['color'] # fetch shape attribute name and converter attrname, converter = STYLES[name] # Sett attribute of shape if hasattr(shape, attrname): setattr(shape, attrname, converter(value)) # defaults if isinstance(shape, (String, Rect)): if shape.fillColor is None: shape.fillColor = colors.black elif isinstance(shape, (Line, PolyLine)): if shape.strokeColor is None: shape.strokeColor = colors.black elif isinstance(shape, (Circle, Ellipse, Polygon)): if shape.fillColor is None and shape.strokeColor is None: shape.strokeColor = colors.black def applyTransformOnGroup(self, group, transform): op, args = transform if op == 'scale': group.scale(*args) elif op == 'translate': group.translate(*args) elif op == 'rotate': if len(args) == 2: angle, center = args if center: cx, cy = center group.translate(cx, cy) group.rotate(angle) group.translate(-cx, -cy) else: group.rotate(angle) elif op == "skewX": angle = args[0] group.skew(angle, 0) elif op == "skewY": angle = args[0] group.skew(0, angle) elif op == "matrix": group.transform = mmult(group.transform, args)
btmfront = Group( Rect(0, 0, bottom_width, bottom_thickness, strokeWidth=0.02, fillColor=colors.white)) btmtop = Group( Rect(0, 0, bottom_width, bottom_depth, strokeWidth=0.02, fillColor=colors.white)) btmtop.translate(0, bottom_thickness) btmtop.shift(0, bottom_thickness) btmtop.skew(iso_angle, 0) btmright = Group( Rect(0, 0, bottom_depth, bottom_thickness, strokeWidth=0.02, fillColor=colors.white)) btmright.translate(bottom_width, 0) btmright.shift(bottom_width, 0) btmright.skew(0, 90 - iso_angle) d.add(btmfront) d.add(btmtop)
from reportlab.lib import colors path = sys.argv[1] glyphName = sys.argv[2] if (len(sys.argv) > 3): imageFile = sys.argv[3] else: imageFile = "%s.png" % glyphName font = TTFont( path ) # it would work just as well with fontemon_blender_addon.fontTools.t1Lib.T1Font gs = font.getGlyphSet() pen = ReportLabPen(gs, Path(fillColor=colors.red, strokeWidth=5)) g = gs[glyphName] g.draw(pen) w, h = g.width, 1000 from reportlab.graphics import renderPM from reportlab.graphics.shapes import Group, Drawing, scale # Everything is wrapped in a group to allow transformations. g = Group(pen.path) g.translate(0, 200) g.scale(0.3, 0.3) d = Drawing(w, h) d.add(g) renderPM.drawToFile(d, imageFile, fmt="PNG")
def draw(self): # general widget bits g = Group() x = self.x y = self.y scale = self.scale stemThickness = self.stemThickness * scale stemLength = self.stemLength * scale headProjection = self.headProjection * scale headLength = self.headLength * scale headSweep = self.headSweep * scale w = stemLength + headLength h = 2 * headProjection + stemThickness # shift to the boxAnchor boxAnchor = self.boxAnchor if self.right: if boxAnchor in ('sw', 'w', 'nw'): dy = -h elif boxAnchor in ('s', 'c', 'n'): dy = -h * 0.5 else: dy = 0 if boxAnchor in ('w', 'c', 'e'): dx = -w * 0.5 elif boxAnchor in ('nw', 'n', 'ne'): dx = -w else: dx = 0 points = [ dx, dy + headProjection + stemThickness, dx + stemLength, dy + headProjection + stemThickness, dx + stemLength + headSweep, dy + 2 * headProjection + stemThickness, dx + stemLength + headLength, dy + 0.5 * stemThickness + headProjection, dx + stemLength + headSweep, dy, dx + stemLength, dy + headProjection, dx, dy + headProjection, ] else: w, h = h, w if boxAnchor in ('nw', 'n', 'ne'): dy = -h elif boxAnchor in ('w', 'c', 'e'): dy = -h * 0.5 else: dy = 0 if boxAnchor in ('ne', 'e', 'se'): dx = -w elif boxAnchor in ('n', 'c', 's'): dx = -w * 0.5 else: dx = 0 points = [ dx + headProjection, dy, #sw dx + headProjection + stemThickness, dy, #se dx + headProjection + stemThickness, dy + stemLength, dx + w, dy + stemLength + headSweep, dx + headProjection + 0.5 * stemThickness, dy + h, dx, dy + stemLength + headSweep, dx + headProjection, dy + stemLength, ] g.add( Polygon( points=points, fillColor=self.fillColor, strokeColor=self.strokeColor, strokeWidth=self.strokeWidth, )) g.translate(x, y) g.rotate(self.angle) return g