diff --git a/src/nanoemoji/color_glyph.py b/src/nanoemoji/color_glyph.py index 96a72f72..4a042c48 100644 --- a/src/nanoemoji/color_glyph.py +++ b/src/nanoemoji/color_glyph.py @@ -49,7 +49,7 @@ import pathops -def _scale_viewbox_to_font_metrics( +def scale_viewbox_to_font_metrics( view_box: Rect, ascender: int, descender: int, width: int ): assert descender <= 0 @@ -71,7 +71,7 @@ def map_viewbox_to_font_space( ) -> Affine2D: return Affine2D.compose_ltr( [ - _scale_viewbox_to_font_metrics(view_box, ascender, descender, width), + scale_viewbox_to_font_metrics(view_box, ascender, descender, width), # flip y axis and shift so things are in the right place Affine2D(1, 0, 0, -1, 0, ascender), user_transform, @@ -85,7 +85,7 @@ def map_viewbox_to_otsvg_space( ) -> Affine2D: return Affine2D.compose_ltr( [ - _scale_viewbox_to_font_metrics(view_box, ascender, descender, width), + scale_viewbox_to_font_metrics(view_box, ascender, descender, width), # shift things in the [+x,-y] quadrant where OT-SVG expects them Affine2D(1, 0, 0, 1, 0, -ascender), user_transform, diff --git a/src/nanoemoji/parts.py b/src/nanoemoji/parts.py index f65a88d7..fd27c025 100644 --- a/src/nanoemoji/parts.py +++ b/src/nanoemoji/parts.py @@ -24,6 +24,7 @@ from functools import lru_cache, partial, reduce import json from nanoemoji.config import FontConfig +from nanoemoji.color_glyph import scale_viewbox_to_font_metrics from pathlib import Path from picosvg.geometric_types import Rect from picosvg.svg import SVG @@ -149,7 +150,10 @@ def add(self, source: PathSource): ) elif isinstance(source, SVG): source.checkpicosvg() - transform = Affine2D.rect_to_rect(source.view_box(), self.view_box) + source_box = source.view_box() + transform = scale_viewbox_to_font_metrics( + self.view_box, source_box.h, 0, source_box.w + ) shapes = tuple(s.as_path() for s in source.shapes()) else: raise ValueError(f"Unknown part source: {type(source)}") diff --git a/tests/parts_test.py b/tests/parts_test.py index c6ff204b..74833b0c 100644 --- a/tests/parts_test.py +++ b/tests/parts_test.py @@ -227,3 +227,30 @@ def test_scaled_merge_arcs_to_cubics(): "MCCCCZ", "MCCCCZ", ), f"Path damaged\nnorm {norm}\npaths {paths}" + + +def _start_at_origin(path): + cmd, args = next(iter(path)) + assert cmd == "M" + x, y = args + return path.move(-x, -y) + + +# SVGs with varied width that contains squares should push squares +# into the part store, not get mangled into rectangles. +def test_squares_stay_squares(): + parts = ReusableParts(view_box=Rect(0, 0, 10, 10)) + + parts.add(SVG.parse(locate_test_file("square_vbox_narrow.svg"))) + parts.add(SVG.parse(locate_test_file("square_vbox_square.svg"))) + parts.add(SVG.parse(locate_test_file("square_vbox_narrow.svg"))) + + # Every square should have normalized the same + assert len(parts.shape_sets) == 1, parts.to_json() + + paths = only(parts.shape_sets.values()) + + paths = [_start_at_origin(SVGPath(d=p)).relative(inplace=True) for p in paths] + assert {p.d for p in paths} == { + "M0,0 l3,0 l0,3 l-3,0 l0,-3 z" + }, "The square should remain 3x3; converted to relative and starting at 0,0 they should be identical"