Skip to content

Commit

Permalink
Merge pull request #222 from googlefonts/gradient-radius-percent-of-d…
Browse files Browse the repository at this point in the history
…iagonal

parse radialGradient radius as percentage of viewport diagonal, not width
  • Loading branch information
anthrotype authored May 20, 2021
2 parents 9d00851 + 75131aa commit ef65249
Show file tree
Hide file tree
Showing 2 changed files with 125 additions and 3 deletions.
8 changes: 6 additions & 2 deletions src/picosvg/svg_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import copy
import dataclasses
from itertools import zip_longest
import math
import re
from picosvg.geometric_types import Point, Rect
from picosvg.svg_meta import (
Expand Down Expand Up @@ -836,18 +837,21 @@ class SVGRadialGradient:
def from_element(el, view_box) -> "SVGRadialGradient":
self = SVGRadialGradient()
width, height = _parse_common_gradient_parts(self, el, view_box)
# lengths are calculated as percentages of the "normalized diagonal" of the
# SVG viewport. See formula at https://www.w3.org/TR/SVG2/coords.html#Units
diagonal = math.hypot(width, height) / math.sqrt(2)

self.cx = number_or_percentage(el.attrib.get("cx", "50%"), width)
self.cy = number_or_percentage(el.attrib.get("cy", "50%"), height)
self.r = number_or_percentage(el.attrib.get("r", "50%"), width)
self.r = number_or_percentage(el.attrib.get("r", "50%"), diagonal)

raw_fx = el.attrib.get("fx")
self.fx = number_or_percentage(raw_fx, width) if raw_fx is not None else self.cx
raw_fy = el.attrib.get("fy")
self.fy = (
number_or_percentage(raw_fy, height) if raw_fy is not None else self.cy
)
self.fr = number_or_percentage(el.attrib.get("fr", "0%"), width)
self.fr = number_or_percentage(el.attrib.get("fr", "0%"), diagonal)
return self


Expand Down
120 changes: 119 additions & 1 deletion tests/svg_types_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,16 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import math
import pytest
from picosvg.geometric_types import Rect
from picosvg.svg_transform import Affine2D
from picosvg.svg_types import SVGPath, SVGRect, Rect
from picosvg.svg_types import (
SVGPath,
SVGRect,
SVGLinearGradient,
SVGRadialGradient,
)
from svg_test_helpers import *


Expand Down Expand Up @@ -335,3 +342,114 @@ def test_round_multiple(path: str, multiple_of: float, expected_result: str):
)
def test_normalize_opacity(shape, expected):
assert shape.normalize_opacity() == expected


@pytest.mark.parametrize(
"el, view_box, expected",
[
# default linearGradient
(
etree.Element("linearGradient"),
Rect(0, 0, 10, 10),
SVGLinearGradient(
x1=0.0,
y1=0.0,
x2=1.0,
y2=0.0,
gradientUnits=Rect(0, 0, 1, 1),
),
),
# default radialGradient
(
etree.Element("radialGradient"),
Rect(0, 0, 10, 10),
SVGRadialGradient(
cx=0.5,
cy=0.5,
r=0.5,
fx=0.5,
fy=0.5,
fr=0.0,
gradientUnits=Rect(0, 0, 1, 1),
),
),
# radialGradient with gradientUnits="userSpaceOnUse" on square viewport
(
etree.Element("radialGradient", {"gradientUnits": "userSpaceOnUse"}),
Rect(0, 0, 10, 10),
SVGRadialGradient(
cx=5.0,
cy=5.0,
r=5.0,
fx=5.0,
fy=5.0,
fr=0.0,
gradientUnits=Rect(0, 0, 10, 10),
),
),
# userSpaceOnUse & nonsquare viewport, default 'r' is 50% of normalized diagonal
(
etree.Element("radialGradient", {"gradientUnits": "userSpaceOnUse"}),
Rect(0, 0, 10, 5),
SVGRadialGradient(
cx=5.0,
cy=2.5,
r=0.5 * math.hypot(10, 5) / math.sqrt(2), # 3.952847
fx=5.0,
fy=2.5,
fr=0.0,
gradientUnits=Rect(0, 0, 10, 5),
),
),
# fx/fy default to cx/cy when not explicitly set
(
etree.Element("radialGradient", {"cx": "20%", "cy": "40%"}),
Rect(0, 0, 10, 5),
SVGRadialGradient(
cx=0.2,
cy=0.4,
r=0.5,
fx=0.2,
fy=0.4,
fr=0.0,
gradientUnits=Rect(0, 0, 1, 1),
),
),
# fx/fy explicitly set
(
etree.Element("radialGradient", {"fx": "20%", "fy": "40%"}),
Rect(0, 0, 10, 5),
SVGRadialGradient(
cx=0.5,
cy=0.5,
r=0.5,
fx=0.2,
fy=0.4,
fr=0.0,
gradientUnits=Rect(0, 0, 1, 1),
),
),
# linearGradient with gradientTransform and spreadMethod
(
etree.Element(
"linearGradient",
{
"gradientTransform": "matrix(1, 0.3, 0, 1, 0, 0)",
"spreadMethod": "reflect",
},
),
Rect(0, 0, 10, 10),
SVGLinearGradient(
x1=0.0,
y1=0.0,
x2=1.0,
y2=0.0,
gradientTransform=Affine2D(1, 0.3, 0, 1, 0, 0),
spreadMethod="reflect",
),
),
],
)
def test_gradient_from_element(el, view_box, expected):
klass = type(expected)
assert klass.from_element(el, view_box) == expected

0 comments on commit ef65249

Please sign in to comment.