class SVGEngine: """ Renders SVG data on Qt paint devices. """ def __init__(self, *, render_hints=QPainter.Antialiasing) -> None: self._data = b"" # Current XML SVG binary data. self._renderer = QSvgRenderer() # Qt SVG renderer. self._render_hints = render_hints # SVG rendering quality hints (such as anti-aliasing). def loads(self, data: QtSVGData) -> None: """ Load the renderer with SVG data. """ if isinstance(data, str): data = data.encode('utf-8') self._data = data self._renderer.load(data) def load(self, filename: str) -> None: """ Load SVG data directly from disk. """ with open(filename) as fp: data = fp.read() self.loads(data) def viewbox_size(self) -> QSize: """ If no valid viewbox is defined, 100x100 is a typical default. """ size = self._renderer.viewBox().size() if size.isEmpty(): size = QSize(100, 100) return size def render(self, target: QPaintDevice, bounds: QRectF = None) -> None: """ Render the current SVG data on <target> with optional <bounds> on the area. """ args = () if bounds is None else (bounds, ) with QPainter(target) as p: p.setRenderHints(self._render_hints) self._renderer.render(p, *args) def render_fit(self, target: QPaintDevice) -> None: """ Render the current SVG data on <target>, centered at maximum scale while preserving aspect ratio. """ width = target.width() height = target.height() v_size = self.viewbox_size() vw = v_size.width() vh = v_size.height() scale = min(width / vw, height / vh) rw = vw * scale rh = vh * scale rx = (width - rw) / 2 ry = (height - rh) / 2 bounds = QRectF(rx, ry, rw, rh) self.render(target, bounds) def dumps(self) -> str: """ Return the current SVG data as a string. """ return self._data.decode('utf-8') def dump(self, filename: str) -> None: """ Save the current SVG data directly to disk. """ with open(filename, 'wb') as fp: fp.write(self._data)
def _draw_plate(self, background_path, font_path, target_template, symbols): if background_path.suffix == '.svg': renderer = QSvgRenderer(str(background_path)) assert renderer.isValid() width = target_template['width'] w, h = renderer.viewBox().width(), renderer.viewBox().height() aspect_ratio = w / h w, h = width, width / aspect_ratio image = QImage(w, h, QImage.Format_ARGB32) image.fill(Qt.transparent) # workaround for clean image painter = QPainter(image) painter.setRenderHint(QPainter.Antialiasing, True) painter.setRenderHint(QPainter.TextAntialiasing, True) painter.setRenderHint(QPainter.SmoothPixmapTransform, True) renderer.render(painter) else: image = QPixmap(str(background_path)) assert not image.isNull() width = target_template['width'] w, h = image.width(), image.height() aspect_ratio = w / h w, h = width, width / aspect_ratio image.scaled(w, h, Qt.KeepAspectRatio) image = image.toImage() painter = QPainter(image) font = get_font(font_path) pen = QPen(QColor.fromRgb(int(target_template['font_color'], base=16)), 2, Qt.SolidLine) painter.setPen(pen) for _, sym in symbols.items(): rect = QRect(sym.x, sym.y, sym.w, sym.h) font.setPointSizeF(sym.font_size) painter.setFont(font) painter.drawText(rect, Qt.AlignCenter, sym.sym) painter.end() return qimage_to_nparr(image)
def _render(self, data: XMLIconData) -> QIcon: """ Create a template image, render the XML data in place, and convert it to an icon. Use the viewbox dimensions as pixel sizes. """ svg = QSvgRenderer(data) viewbox = svg.viewBox().size() im = QImage(viewbox, QImage.Format_ARGB32) im.fill(self._bg_color) with QPainter(im) as p: p.setRenderHints(self._render_hints) svg.render(p) return QIcon(QPixmap.fromImage(im))
def _render(self, svg_data: str) -> QImage: """ Create a new raster image with the current background color and render an SVG image to it. Pixel dimensions will fit the viewbox at maximum scale. """ svg = QSvgRenderer(svg_data.encode("utf-8")) v_size = svg.viewBox().size() vw = v_size.width() vh = v_size.height() scale = min(self._w_max / vw, self._h_max / vh) w = round(vw * scale) h = round(vh * scale) im = QImage(w, h, QImage.Format_ARGB32) im.fill(self._bg_color) with QPainter(im) as p: p.setRenderHints(QPainter.Antialiasing) svg.render(p, QRectF(0, 0, w, h)) return im