Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Made text elements optionally allowed #289

Merged
merged 1 commit into from
Feb 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions src/picosvg/picosvg.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@

flags.DEFINE_bool("clip_to_viewbox", False, "Whether to clip content outside viewbox")
flags.DEFINE_string("output_file", "-", "Output SVG file ('-' means stdout)")
flags.DEFINE_bool(
"allow_text",
False,
"Whether to allow text elements. Note that they will not be converted to paths, just pass through to the output.",
)


def _run(argv):
Expand All @@ -40,9 +45,9 @@ def _run(argv):
input_file = None

if input_file:
svg = SVG.parse(input_file).topicosvg()
svg = SVG.parse(input_file).topicosvg(allow_text=FLAGS.allow_text)
else:
svg = SVG.fromstring(sys.stdin.read()).topicosvg()
svg = SVG.fromstring(sys.stdin.read()).topicosvg(allow_text=FLAGS.allow_text)

if FLAGS.clip_to_viewbox:
svg.clip_to_viewbox(inplace=True)
Expand Down
53 changes: 29 additions & 24 deletions src/picosvg/svg.py
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,9 @@ def __init__(self, svg_root):
self.svg_root = svg_root
self.elements = []

def _clone(self) -> "SVG":
return SVG(svg_root=copy.deepcopy(self.svg_root))

def _elements(self) -> List[Tuple[etree.Element, Tuple[SVGShape, ...]]]:
if self.elements:
return self.elements
Expand Down Expand Up @@ -403,7 +406,7 @@ def shapes(self):
def absolute(self, inplace=False):
"""Converts all basic shapes to their equivalent path."""
if not inplace:
svg = SVG(copy.deepcopy(self.svg_root))
svg = self._clone()
svg.absolute(inplace=True)
return svg

Expand All @@ -415,7 +418,7 @@ def absolute(self, inplace=False):
def shapes_to_paths(self, inplace=False):
"""Converts all basic shapes to their equivalent path."""
if not inplace:
svg = SVG(copy.deepcopy(self.svg_root))
svg = self._clone()
svg.shapes_to_paths(inplace=True)
return svg

Expand All @@ -426,7 +429,7 @@ def shapes_to_paths(self, inplace=False):

def expand_shorthand(self, inplace=False):
if not inplace:
svg = SVG(copy.deepcopy(self.svg_root))
svg = self._clone()
svg.expand_shorthand(inplace=True)
return svg

Expand All @@ -444,7 +447,7 @@ def _apply_styles(self, el: etree.Element):
def apply_style_attributes(self, inplace=False):
"""Converts inlined CSS "style" attributes to equivalent SVG attributes."""
if not inplace:
svg = SVG(copy.deepcopy(self.svg_root))
svg = self._clone()
svg.apply_style_attributes(inplace=True)
return svg

Expand Down Expand Up @@ -546,7 +549,7 @@ def resolve_use(self, inplace=False):

https://www.w3.org/TR/SVG11/struct.html#UseElement"""
if not inplace:
svg = SVG(copy.deepcopy(self.svg_root))
svg = self._clone()
svg.resolve_use(inplace=True)
return svg

Expand Down Expand Up @@ -793,7 +796,7 @@ def _simplify(self):

def simplify(self, inplace=False):
if not inplace:
svg = SVG(copy.deepcopy(self.svg_root))
svg = self._clone()
svg.simplify(inplace=True)
return svg

Expand Down Expand Up @@ -838,7 +841,7 @@ def _stroke(self, shape):

def clip_to_viewbox(self, inplace=False):
if not inplace:
svg = SVG(copy.deepcopy(self.svg_root))
svg = self._clone()
svg.clip_to_viewbox(inplace=True)
return svg

Expand Down Expand Up @@ -892,7 +895,7 @@ def clip_to_viewbox(self, inplace=False):

def evenodd_to_nonzero_winding(self, inplace=False):
if not inplace:
svg = SVG(copy.deepcopy(self.svg_root))
svg = self._clone()
svg.evenodd_to_nonzero_winding(inplace=True)
return svg

Expand All @@ -905,7 +908,7 @@ def evenodd_to_nonzero_winding(self, inplace=False):

def round_floats(self, ndigits: int, inplace=False):
if not inplace:
svg = SVG(copy.deepcopy(self.svg_root))
svg = self._clone()
svg.round_floats(ndigits, inplace=True)
return svg

Expand All @@ -915,7 +918,7 @@ def round_floats(self, ndigits: int, inplace=False):

def remove_empty_subpaths(self, inplace=False):
if not inplace:
svg = SVG(copy.deepcopy(self.svg_root))
svg = self._clone()
svg.remove_empty_subpaths(inplace=True)
return svg

Expand All @@ -927,7 +930,7 @@ def remove_empty_subpaths(self, inplace=False):

def remove_unpainted_shapes(self, inplace=False):
if not inplace:
svg = SVG(copy.deepcopy(self.svg_root))
svg = self._clone()
svg.remove_unpainted_shapes(inplace=True)
return svg

Expand All @@ -947,7 +950,7 @@ def remove_unpainted_shapes(self, inplace=False):

def remove_nonsvg_content(self, inplace=False):
if not inplace:
svg = SVG(copy.deepcopy(self.svg_root))
svg = self._clone()
svg.remove_nonsvg_content(inplace=True)
return svg

Expand Down Expand Up @@ -1001,7 +1004,7 @@ def remove_processing_instructions(self, inplace=False):

def remove_comments(self, inplace=False):
if not inplace:
svg = SVG(copy.deepcopy(self.svg_root))
svg = self._clone()
svg.remove_comments(inplace=True)
return svg

Expand All @@ -1016,7 +1019,7 @@ def remove_anonymous_symbols(self, inplace=False):
# No id makes a symbol useless
# https://github.com/googlefonts/picosvg/issues/46
if not inplace:
svg = SVG(copy.deepcopy(self.svg_root))
svg = self._clone()
svg.remove_anonymous_symbols(inplace=True)
return svg

Expand All @@ -1029,7 +1032,7 @@ def remove_anonymous_symbols(self, inplace=False):

def remove_title_meta_desc(self, inplace=False):
if not inplace:
svg = SVG(copy.deepcopy(self.svg_root))
svg = self._clone()
svg.remove_title_meta_desc(inplace=True)
return svg

Expand All @@ -1043,7 +1046,7 @@ def remove_title_meta_desc(self, inplace=False):

def set_attributes(self, name_values, xpath="/svg:svg", inplace=False):
if not inplace:
svg = SVG(copy.deepcopy(self.svg_root))
svg = self._clone()
svg.set_attributes(name_values, xpath=xpath, inplace=True)
return svg

Expand All @@ -1058,7 +1061,7 @@ def set_attributes(self, name_values, xpath="/svg:svg", inplace=False):
def remove_attributes(self, names, xpath="/svg:svg", inplace=False):
"""Drop things like viewBox, width, height that set size of overall svg"""
if not inplace:
svg = SVG(copy.deepcopy(self.svg_root))
svg = self._clone()
svg.remove_attributes(names, xpath=xpath, inplace=True)
return svg

Expand All @@ -1072,7 +1075,7 @@ def remove_attributes(self, names, xpath="/svg:svg", inplace=False):
def normalize_opacity(self, inplace=False):
"""Merge '{fill,stroke}_opacity' with generic 'opacity' when possible."""
if not inplace:
svg = SVG(copy.deepcopy(self.svg_root))
svg = self._clone()
svg.normalize_opacity(inplace=True)
return svg

Expand Down Expand Up @@ -1167,7 +1170,7 @@ def resolve_nested_svgs(self, inplace=False):
- https://www.sarasoueidan.com/blog/nesting-svgs/
"""
if not inplace:
svg = SVG(copy.deepcopy(self.svg_root))
svg = self._clone()
svg.resolve_nested_svgs(inplace=True)
return svg

Expand Down Expand Up @@ -1273,7 +1276,7 @@ def _remove_orphaned_gradients(self):
if grad.attrib.get("id") not in used_gradient_ids:
_safe_remove(grad)

def checkpicosvg(self):
def checkpicosvg(self, allow_text=False):
"""Check for nano violations, return xpaths to bad elements.

If result sequence empty then this is a valid picosvg.
Expand All @@ -1290,6 +1293,8 @@ def checkpicosvg(self):
r"^/svg\[0\]/defs\[0\]/(linear|radial)Gradient\[\d+\](/stop\[\d+\])?$",
r"^/svg\[0\](/(path|g)\[\d+\])+$",
}
if allow_text:
path_allowlist.add(r"^/svg\[0\](/text\[\d+\])+$")
paths_required = {
"/svg[0]",
"/svg[0]/defs[0]",
Expand Down Expand Up @@ -1323,10 +1328,10 @@ def checkpicosvg(self):

return tuple(errors)

def topicosvg(self, *, ndigits=3, inplace=False):
def topicosvg(self, *, ndigits=3, inplace=False, allow_text=False):
if not inplace:
svg = SVG(copy.deepcopy(self.svg_root))
svg.topicosvg(ndigits=ndigits, inplace=True)
svg = self._clone()
svg.topicosvg(ndigits=ndigits, inplace=True, allow_text=allow_text)
return svg

self._update_etree()
Expand Down Expand Up @@ -1358,7 +1363,7 @@ def topicosvg(self, *, ndigits=3, inplace=False):
self.remove_empty_subpaths(inplace=True)
self.remove_unpainted_shapes(inplace=True)

nano_violations = self.checkpicosvg()
nano_violations = self.checkpicosvg(allow_text=allow_text)
if nano_violations:
raise ValueError(
"Unable to convert to picosvg: " + ",".join(nano_violations)
Expand Down
10 changes: 10 additions & 0 deletions tests/svg_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -672,3 +672,13 @@ def test_remove_processing_instructions():
assert "xpacket" in xpacket_svg.tostring()
pico_svg = xpacket_svg.remove_processing_instructions()
assert "xpacket" not in pico_svg.tostring()


def test_allow_text():
text_svg = load_test_svg("text-before.svg")
with pytest.raises(
ValueError,
match=r"Unable to convert to picosvg: BadElement: /svg\[0\]/text\[0\]",
):
text_svg.topicosvg()
assert "text" in text_svg.topicosvg(allow_text=True).tostring()
5 changes: 5 additions & 0 deletions tests/text-before.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.