clone

a browser-first diagram editor with live svg previews, local drafts and encrypted, accountless sharing

main
diagramzip / renderers / python / blockdiag-family / src / blockdiag / imagedraw / pdf.py
pdf.py 8.0K
1# -*- coding: utf-8 -*-2# Copyright 2011 Takeshi KOMIYA3#4# Licensed under the Apache License, Version 2.0 (the "License");5# you may not use this file except in compliance with the License.6# You may obtain a copy of the License at7#8# http://www.apache.org/licenses/LICENSE-2.09#10# Unless required by applicable law or agreed to in writing, software11# distributed under the License is distributed on an "AS IS" BASIS,12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.13# See the License for the specific language governing permissions and14# limitations under the License.1516import math17import re18import sys1920from reportlab.lib.utils import ImageReader21from reportlab.pdfbase import pdfmetrics22from reportlab.pdfbase.ttfonts import TTFont23from reportlab.pdfgen import canvas2425from blockdiag.imagedraw import base26from blockdiag.imagedraw.utils import memoize27from blockdiag.utils import Box, Size, images28from blockdiag.utils.fontmap import parse_fontpath293031class PDFImageDraw(base.ImageDraw):32 baseline_text_rendering = True3334 def __init__(self, filename, **kwargs):35 self.size = None36 self.filename = filename37 self.canvas = None38 self.fonts = {}3940 self.set_canvas_size(Size(1, 1)) # This line make textsize() workable4142 def set_canvas_size(self, size):43 if self.filename is None:44 self.canvas = canvas.Canvas(sys.stdout.buffer, pagesize=size)45 else:46 self.canvas = canvas.Canvas(self.filename, pagesize=size)4748 self.size = size4950 def set_font(self, font):51 if font.path is None:52 msg = "Could not detect fonts, use --font option\n"53 raise RuntimeError(msg)5455 if font.path not in self.fonts:56 path, index = parse_fontpath(font.path)57 if index:58 ttfont = TTFont(font.path, path, subfontIndex=index)59 else:60 ttfont = TTFont(font.path, path)61 pdfmetrics.registerFont(ttfont)6263 self.fonts[font.path] = ttfont6465 self.canvas.setFont(font.path, font.size)6667 def set_render_params(self, **kwargs):68 self.set_stroke_color(kwargs.get('outline'))69 self.set_fill_color(kwargs.get('fill', 'none'))70 self.set_style(kwargs.get('style'), kwargs.get('thick'))7172 params = {}73 if kwargs.get('fill', 'none') == 'none':74 params['fill'] = 075 else:76 params['fill'] = 17778 if kwargs.get('outline') is None:79 params['stroke'] = 080 else:81 params['stroke'] = 18283 return params8485 def set_style(self, style, thick):86 if thick is None:87 thick = 18889 if style == 'dotted':90 self.canvas.setDash([2 * thick, 2 * thick])91 elif style == 'dashed':92 self.canvas.setDash([4 * thick, 4 * thick])93 elif style == 'none':94 self.canvas.setDash([0, 65535 * thick])95 elif re.search(r'^\d+(,\d+)*$', style or ""):96 self.canvas.setDash([int(n) * thick for n in style.split(',')])97 else:98 self.canvas.setDash()99100 def set_stroke_color(self, color="black"):101 if isinstance(color, str):102 self.canvas.setStrokeColor(color)103 elif color:104 rgb = (color[0] / 256.0, color[1] / 256.0, color[2] / 256.0)105 self.canvas.setStrokeColorRGB(*rgb)106 else:107 self.set_stroke_color()108109 def set_fill_color(self, color="white"):110 if isinstance(color, str):111 if color != 'none':112 self.canvas.setFillColor(color)113 elif color:114 rgb = (color[0] / 256.0, color[1] / 256.0, color[2] / 256.0)115 self.canvas.setFillColorRGB(*rgb)116 else:117 self.set_fill_color()118119 def path(self, pd, **kwargs):120 params = self.set_render_params(**kwargs)121 self.canvas.drawPath(pd, **params)122123 def rectangle(self, box, **kwargs):124 x = box[0]125 y = self.size[1] - box[3]126 width = box[2] - box[0]127 height = box[3] - box[1]128129 if 'thick' in kwargs and kwargs['thick'] is not None:130 self.canvas.setLineWidth(kwargs['thick'])131132 params = self.set_render_params(**kwargs)133 self.canvas.rect(x, y, width, height, **params)134135 if 'thick' in kwargs:136 self.canvas.setLineWidth(1)137138 @memoize139 def textlinesize(self, string, font):140 self.set_font(font)141 width = self.canvas.stringWidth(string, font.path, font.size)142 return Size(int(math.ceil(width)), font.size)143144 def text(self, xy, string, font, **kwargs):145 self.set_font(font)146 self.set_fill_color(kwargs.get('fill'))147 self.canvas.drawString(xy[0], self.size[1] - xy[1], string)148149 def textarea(self, box, string, font, **kwargs):150 self.canvas.saveState()151152 if 'rotate' in kwargs and kwargs['rotate'] != 0:153 angle = 360 - int(kwargs['rotate']) % 360154 self.canvas.rotate(angle)155156 if angle == 90:157 box = Box(-box.y2, box.x1, -box.y1, box.x1 + box.width)158 box = box.shift(x=self.size.height, y=self.size.height)159 elif angle == 180:160 box = Box(-box.x2, -box.y2, -box.x1, -box.y2 + box.height)161 box = box.shift(y=self.size.height * 2)162 elif angle == 270:163 box = Box(box.y1, -box.x2, box.y2, -box.x1)164 box = box.shift(x=-self.size.height, y=self.size.height)165166 self.set_font(font)167 lines = self.textfolder(box, string, font, **kwargs)168169 if kwargs.get('outline'):170 outline = kwargs.get('outline')171 self.rectangle(lines.outlinebox, fill='white', outline=outline)172173 rendered = False174 for string, xy in lines.lines:175 self.text(xy, string, font, **kwargs)176 rendered = True177 self.canvas.restoreState()178179 if not rendered and font.size > 0:180 font.size = int(font.size * 0.8)181 self.textarea(box, string, font, **kwargs)182183 def line(self, xy, **kwargs):184 self.set_stroke_color(kwargs.get('fill', 'none'))185 self.set_style(kwargs.get('style'), kwargs.get('thick'))186187 if 'thick' in kwargs and kwargs['thick'] is not None:188 self.canvas.setLineWidth(kwargs['thick'])189190 p1 = xy[0]191 y = self.size[1]192 for p2 in xy[1:]:193 self.canvas.line(p1.x, y - p1.y, p2.x, y - p2.y)194 p1 = p2195196 if 'thick' in kwargs:197 self.canvas.setLineWidth(1)198199 def arc(self, xy, start, end, **kwargs):200 start, end = 360 - end, 360 - start201 r = (360 + end - start) % 360202203 self.set_render_params(**kwargs)204 y = self.size[1]205 self.canvas.arc(xy[0], y - xy[3], xy[2], y - xy[1], start, r)206207 def ellipse(self, xy, **kwargs):208 params = self.set_render_params(**kwargs)209 y = self.size[1]210 self.canvas.ellipse(xy[0], y - xy[3], xy[2], y - xy[1], **params)211212 def polygon(self, xy, **kwargs):213 pd = self.canvas.beginPath()214 y = self.size[1]215 pd.moveTo(xy[0][0], y - xy[0][1])216 for p in xy[1:]:217 pd.lineTo(p[0], y - p[1])218219 params = self.set_render_params(**kwargs)220 self.canvas.drawPath(pd, **params)221222 def image(self, box, url):223 try:224 image = images.open(url, mode='pillow')225 if image.mode not in ('RGBA', 'L', 'RGB', 'CYMYK'):226 # convert to format that reportlab can recognize227 image = image.convert('RGBA')228229 y = self.size[1] - box[3]230 data = ImageReader(image)231 self.canvas.drawImage(data, box.x1, y, box.width, box.height,232 mask='auto', preserveAspectRatio=True)233 except IOError:234 pass235236 def save(self, filename, size, _format):237 # Ignore size and format parameter; compatibility for ImageDrawEx.238239 self.canvas.showPage()240 self.canvas.save()241242243def setup(self):244 from blockdiag.imagedraw import install_imagedrawer245 install_imagedrawer('pdf', PDFImageDraw)