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

Fix OverflowErrors when pre-applying transform to radial gradient geometry #448

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
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
2 changes: 0 additions & 2 deletions src/nanoemoji/bitmap_tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,6 @@ def _cbdt_bitmapdata_offsets(
def _cbdt_bitmap_data(
config: FontConfig, metrics: BitmapMetrics, image_data: PNG
) -> CbdtBitmapFormat17:

bitmap_data = CbdtBitmapFormat17(b"", None)
bitmap_data.metrics = SmallGlyphMetrics()
bitmap_data.metrics.width, bitmap_data.metrics.height = image_data.size
Expand All @@ -171,7 +170,6 @@ def make_sbix_table(
ttfont: ttLib.TTFont,
color_glyphs: Sequence[ColorGlyph],
):

sbix = ttLib.newTable("sbix")
ttfont[sbix.tableTag] = sbix

Expand Down
1 change: 0 additions & 1 deletion src/nanoemoji/color_glyph.py
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,6 @@ def _painted_layers(
picosvg: SVG,
glyph_width: int,
) -> Tuple[Paint, ...]:

defs_seen = False
layers = []

Expand Down
1 change: 0 additions & 1 deletion src/nanoemoji/nanoemoji.py
Original file line number Diff line number Diff line change
Expand Up @@ -434,7 +434,6 @@ def write_compressed_bitmap_builds(


def write_fea_build(nw: NinjaWriter, font_config: FontConfig):

nw.build(
rel_build(_fea_file(font_config)),
"write_fea",
Expand Down
46 changes: 36 additions & 10 deletions src/nanoemoji/paint.py
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,15 @@ def check_overflows(self) -> "PaintRadialGradient":
)
return self

def _apply_uniform_transform(self, transform: Affine2D) -> "PaintRadialGradient":
sx, sy = transform.getscale()
assert almost_equal(abs(sx), abs(sy))
c0 = transform.map_point(self.c0)
c1 = transform.map_point(self.c1)
r0 = self.r0 * sx
r1 = self.r1 * sx
return dataclasses.replace(self, c0=c0, c1=c1, r0=r0, r1=r1)

def apply_transform(self, transform: Affine2D, check_overflows=True) -> Paint:
# if gradientUnits="objectBoundingBox" and the bbox is not square, or there's some
# gradientTransform, we may end up with a transformation that does not keep the
Expand All @@ -369,18 +378,35 @@ def apply_transform(self, transform: Affine2D, check_overflows=True) -> Paint:
# then encode any remaining non-uniform transformation as a COLRv1 transform
# that wraps the PaintRadialGradient (see further below).
uniform_transform, remaining_transform = _decompose_uniform_transform(transform)
gradient = self._apply_uniform_transform(uniform_transform)

c0 = uniform_transform.map_point(self.c0)
c1 = uniform_transform.map_point(self.c1)

sx, _ = uniform_transform.getscale()
r0 = self.r0 * sx
r1 = self.r1 * sx

# TODO handle degenerate cases, fallback to solid, w/e
gradient = dataclasses.replace(self, c0=c0, c1=c1, r0=r0, r1=r1)
if check_overflows:
gradient.check_overflows()
# If the scaled up gradient geometry doesn't fit the 16-bit integer limits,
# we try to scale it down by 1/10th until it does, while also scaling up the
# remaining_transform 10x to even things out.
try:
gradient.check_overflows()
except OverflowError as error:
onetenth = Affine2D.identity().scale(1 / 10)
tenfold = Affine2D.identity().scale(10)
# 10 attempts ought to be enough! (the last famous words)
for _ in range(10):
uniform_transform = Affine2D.compose_ltr(
(uniform_transform, onetenth)
)
remaining_transform = Affine2D.compose_ltr(
(tenfold, remaining_transform)
)
gradient = self._apply_uniform_transform(uniform_transform)
try:
gradient.check_overflows()
except OverflowError:
continue # try one more time
else:
error = None # success!
break
if error:
raise error
return transformed(remaining_transform, gradient)

def round(self, ndigits: int) -> "PaintRadialGradient":
Expand Down
1 change: 0 additions & 1 deletion src/nanoemoji/png.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@


class PNG(bytes):

# The first eight bytes of a PNG file always contain the following (decimal) values:
# 137 80 78 71 13 10 26 10
# https://www.w3.org/TR/PNG-Structure.html
Expand Down
2 changes: 1 addition & 1 deletion src/nanoemoji/svg_path.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ def curveTo(self, *points):

def qCurveTo(self, *points):
# handle TrueType quadratic splines with implicit on-curve mid-points
for (control_pt, end_pt) in pathops.decompose_quadratic_segment(points):
for control_pt, end_pt in pathops.decompose_quadratic_segment(points):
self.path.Q(*control_pt, *end_pt)

def closePath(self):
Expand Down