def normalize_flag_aspect(svg, viewbox_size, width, height, right_margin, top_margin): apply_viewbox_preserve_aspect_ratio(svg) current_box = _bbox(tuple(s.bounding_box() for s in svg.shapes())) # Try to keep overall proportions for the flags that are considerably # narrower or wider than the standard aspect ratio. aspect = current_box.w / current_box.h aspect /= STD_ASPECT aspect = sqrt(aspect) # Discount the effect if 0.9 <= aspect <= 1.1: aspect = 1.0 else: print("Non-standard aspect ratio:", aspect) xmin = _x_aspect(right_margin, aspect, viewbox_size) ymin = _y_aspect(top_margin, aspect, viewbox_size) xmax = _x_aspect(right_margin + width, aspect, viewbox_size) ymax = _y_aspect(top_margin + height, aspect, viewbox_size) new_box = Rect(xmin, ymin, xmax - xmin, ymax - ymin) affine = Affine2D.rect_to_rect(current_box, new_box) _picosvg_transform(svg, affine) square_viewbox = Rect(0, 0, viewbox_size, viewbox_size) svg.svg_root.attrib["viewBox"] = " ".join(ntos(v) for v in square_viewbox) for attr_name in ("width", "height"): if attr_name in svg.svg_root.attrib: del svg.svg_root.attrib[attr_name]
def apply_viewbox_preserve_aspect_ratio(svg): """If viewport != viewBox apply the resulting transform and remove viewBox. Takes 'preserveAspectRatio' into account. E.g. The Qatar flag (QA.svg) needs this treatment. """ svg_root = svg.svg_root width = svg_root.attrib.get("width") height = svg_root.attrib.get("height") if width is not None and height is not None and "viewBox" in svg_root.attrib: # ignore absolute length units; we're only interested in the relative size # of viewport vs viewbox here width = SVG_UNITS_RE.sub("", width) height = SVG_UNITS_RE.sub("", height) viewport = Rect(0, 0, float(width), float(height)) viewbox = svg.view_box() if viewport != viewbox: transform = Affine2D.rect_to_rect( viewbox, viewport, svg_root.attrib.get("preserveAspectRatio", "xMidYMid"), ) _picosvg_transform(svg, transform) del svg_root.attrib["viewBox"] if "preserveAspectRatio" in svg_root.attrib: del svg_root.attrib["preserveAspectRatio"]
def _get_gradient_transform(grad_el, shape_bbox, view_box, upem) -> Affine2D: transform = map_viewbox_to_font_emsquare(view_box, upem) gradient_units = grad_el.attrib.get("gradientUnits", "objectBoundingBox") if gradient_units == "objectBoundingBox": bbox_space = Rect(0, 0, 1, 1) bbox_transform = Affine2D.rect_to_rect(bbox_space, shape_bbox) transform = Affine2D.product(bbox_transform, transform) if "gradientTransform" in grad_el.attrib: gradient_transform = Affine2D.fromstring( grad_el.attrib["gradientTransform"]) transform = Affine2D.product(gradient_transform, transform) return transform
def _get_gradient_transform( config: FontConfig, grad_el: etree.Element, shape_bbox: Rect, view_box: Rect, glyph_width: int, ) -> Affine2D: transform = map_viewbox_to_font_space( view_box, config.ascender, config.descender, glyph_width, config.transform ) gradient_units = grad_el.attrib.get("gradientUnits", "objectBoundingBox") if gradient_units == "objectBoundingBox": bbox_space = Rect(0, 0, 1, 1) bbox_transform = Affine2D.rect_to_rect(bbox_space, shape_bbox) transform = Affine2D.compose_ltr((bbox_transform, transform)) if "gradientTransform" in grad_el.attrib: gradient_transform = Affine2D.fromstring(grad_el.attrib["gradientTransform"]) transform = Affine2D.compose_ltr((gradient_transform, transform)) return transform
def _unnest_svg(self, svg: etree.Element, parent_width: float, parent_height: float) -> Tuple[etree.Element, ...]: x = float(svg.attrib.get("x", 0)) y = float(svg.attrib.get("y", 0)) width = float(svg.attrib.get("width", parent_width)) height = float(svg.attrib.get("height", parent_height)) viewport = viewbox = Rect(x, y, width, height) if "viewBox" in svg.attrib: viewbox = parse_view_box(svg.attrib["viewBox"]) # first recurse to un-nest any nested nested SVGs self._swap_elements((el, self._unnest_svg(el, viewbox.w, viewbox.h)) for el in self._iter_nested_svgs(svg)) g = etree.Element(f"{{{svgns()}}}g") g.extend(svg) if viewport != viewbox: preserve_aspect_ratio = svg.attrib.get("preserveAspectRatio", "xMidYMid") transform = Affine2D.rect_to_rect(viewbox, viewport, preserve_aspect_ratio) else: transform = Affine2D.identity().translate(x, y) if "transform" in svg.attrib: transform = Affine2D.compose_ltr( (transform, Affine2D.fromstring(svg.attrib["transform"]))) if transform != Affine2D.identity(): g.attrib["transform"] = transform.tostring() # TODO Define a viewport-sized clipPath once transform+clip-path issue is fixed # https://github.com/googlefonts/picosvg/issues/200 return (g, )
def _build_transformation(self, rect): return Affine2D.rect_to_rect(rect, Rect(0, 0, _SYMBOL_SIZE, _SYMBOL_SIZE)).translate( 0, _SYMBOL_DY_MULTIPLE * rect.h)