diff --git a/ocpsvg/hlr.py b/ocpsvg/hlr.py index b12df3f..2d4b514 100644 --- a/ocpsvg/hlr.py +++ b/ocpsvg/hlr.py @@ -24,7 +24,7 @@ from OCP.TopoDS import TopoDS, TopoDS_Edge, TopoDS_Shape from .ocp import bounding_box, topoDS_iterator -from .svg import edge_to_svg_path, format_svg +from .svg import edge_to_svg_path, float_formatter, format_svg_path HlrEdgeTypeName = Literal["undefined", "isoline", "sewn", "smooth", "sharp", "outline"] @@ -170,7 +170,7 @@ def to_svg( css_style: Optional[CssStyle] = None, background: Optional[bool] = None, tolerance: float = 1e-6, - float_format: str = "g", + decimals: Optional[int] = None, ) -> ET.ElementTree: (x0, y0, x1, y1), scale, (W, H) = _viewbox_and_scale( self.bounds(), @@ -180,8 +180,7 @@ def to_svg( ) ty = -(y1 + y0) / scale - def fmt(v: float): - return v.__format__(float_format) + fmt = float_formatter(decimals) svg = ET.Element("svg", xmlns="http://www.w3.org/2000/svg") svg.attrib["viewBox"] = f"{fmt(x0)} {fmt(y0)} {fmt(x1-x0)} {fmt(y1-y0)}" @@ -224,9 +223,9 @@ def fmt(v: float): attrs = { "id": f"e{i}", - "d": format_svg( + "d": format_svg_path( edge_to_svg_path(edge.projected_edge, tolerance=tolerance), - float_format=float_format, + decimals=decimals, ), "class": " ".join(classnames), } diff --git a/ocpsvg/svg.py b/ocpsvg/svg.py index 77d4e1d..52a9457 100755 --- a/ocpsvg/svg.py +++ b/ocpsvg/svg.py @@ -1,5 +1,6 @@ import logging import pathlib +import warnings from itertools import chain from math import degrees, pi from typing import ( @@ -58,7 +59,7 @@ "edge_to_svg_path", "curve_to_svg_path", "SvgPathCommand", - "format_svg", + "format_svg_path", ] @@ -78,7 +79,29 @@ ] +class float_formatter: + """float formatter with given precision and no trailing zeroes.""" + + def __init__(self, decimals: Optional[int] = None): + self.format_spec = f".{decimals or 12}f" + + def __call__(self, v: float): + return v.__format__(self.format_spec).rstrip("0").rstrip(".") + + +def format_svg_path( + path: Iterable[SvgPathCommand], *, decimals: Optional[int] = None +) -> str: + ff = float_formatter(decimals) + return " ".join(f"{cmd[0]}{','.join(map(ff, cmd[1:]))}" for cmd in path) + + def format_svg(path: Iterable[SvgPathCommand], float_format: str = "f") -> str: + warnings.warn( + "`format_svg` is deprecated, use `format_svg_path` instead.", + FutureWarning, + stacklevel=2, + ) return " ".join( f"{cmd[0]}{','.join(arg.__format__(float_format) for arg in cmd[1:])}" for cmd in path @@ -808,7 +831,7 @@ def _path_from_SvgPathLike(path: SvgPathLike) -> svgelements.Path: return path if not isinstance(path, str): - path = format_svg(path) + path = format_svg_path(path) try: return svgelements.Path(str(path)) diff --git a/tests/test_svg.py b/tests/test_svg.py index 56ae101..7d682e9 100755 --- a/tests/test_svg.py +++ b/tests/test_svg.py @@ -31,7 +31,7 @@ from ocpsvg.svg import ( ColorAndLabel, SvgPathCommand, - _SegmentInPath, + _SegmentInPath, # type: ignore private usage bezier_to_svg_path, edge_to_svg_path, edges_from_svg_path, @@ -39,6 +39,7 @@ faces_from_svg_path, find_shapes_svg_in_document, format_svg, + format_svg_path, import_svg_document, polyline_to_svg_path, svg_element_to_path, @@ -52,7 +53,7 @@ class SvgPath(list[SvgPathCommand]): def __str__(self) -> str: - return format_svg(self) + return format_svg_path(self) def wire_from_curves(*curves: Geom_Curve): @@ -821,7 +822,7 @@ def test_fix_closing_lines_doc(): for e, _ in find_shapes_svg_in_document(StringIO(svg_src)): path = svg_element_to_path(e) assert path - for segment in path: + for segment in path: # type: ignore assert not isinstance(segment, svgelements.Line) @@ -985,3 +986,18 @@ def split_floats(text: str): yield end return list(split_floats(str(path))) + + +def test_format_svg_path(): + assert format_svg_path([("M", 1, 2), ("L", 3.45, 6.78)]) == "M1,2 L3.45,6.78" + + +def test_format_svg_path_number_format(): + path: list[SvgPathCommand] = [("M", 1.0, 2.0), ("L", 3e-8, 4.56789123456789)] + assert format_svg_path(path, decimals=2) == "M1,2 L0,4.57" + assert format_svg_path(path, decimals=8) == "M1,2 L0.00000003,4.56789123" + + +def test_format_svg_deprecated(): + with pytest.warns(FutureWarning): + format_svg([("M", 1, 2), ("L", 3.45, 6.78)])