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
From 68a64cba539435aedf8adce2dfcb284caabc406e Mon Sep 17 00:00:00 2001
From: Shai Almog <67850168+shai-almog@users.noreply.github.com>
Date: Wed, 27 May 2026 17:55:04 +0300
Subject: [PATCH 05/11] Forward clip-path through the presentation-attribute
whitelist
SVGParser.setShapeAttrs builds a `pres` map containing only the
attribute names it explicitly lists, then hands that map to
StyleParser.parse. `clip-path` wasn't in the list, so any inline
`clip-path="url(#id)"` on a shape silently disappeared and the emitted
code never wrapped the shape in `g.setClip(__clipN)`. Most visible on
SVGStaticScreenshotTest's clipped_badge.svg, whose outer rect rendered
as a plain square instead of the rounded badge.
StyleParser already knows how to consume `clip-path` (sets
SVGStyle.clipPathRef), and the code generator already wraps draws in
push/setClip/pop when getClipPathRef() is non-null. The whitelist gap
was the only thing keeping the value from reaching either of them.
Co-Authored-By: Claude Opus 4.7 (1M context)
---
.../com/codename1/svg/transcoder/parser/SVGParser.java | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/maven/svg-transcoder/src/main/java/com/codename1/svg/transcoder/parser/SVGParser.java b/maven/svg-transcoder/src/main/java/com/codename1/svg/transcoder/parser/SVGParser.java
index d0197c841d..d122fd09ec 100644
--- a/maven/svg-transcoder/src/main/java/com/codename1/svg/transcoder/parser/SVGParser.java
+++ b/maven/svg-transcoder/src/main/java/com/codename1/svg/transcoder/parser/SVGParser.java
@@ -444,9 +444,16 @@ private void applyCommon(SVGNode n, Map a) {
Map pres = new HashMap();
for (Map.Entry e : a.entrySet()) {
String k = e.getKey();
+ // Whitelist of SVG presentation attributes we forward to
+ // StyleParser. Missing `clip-path` here was why
+ // clipped_badge.svg's outer rect lost its rounded clip --
+ // StyleParser only sees the keys that land in `pres`, so any
+ // attribute *not* listed is silently dropped even if it is a
+ // well-formed presentation attribute on the element.
if ("fill".equals(k) || "stroke".equals(k) || "fill-opacity".equals(k) || "stroke-opacity".equals(k)
|| "opacity".equals(k) || "stroke-width".equals(k) || "stroke-linecap".equals(k)
- || "stroke-linejoin".equals(k) || "stroke-miterlimit".equals(k)) {
+ || "stroke-linejoin".equals(k) || "stroke-miterlimit".equals(k)
+ || "clip-path".equals(k)) {
pres.put(k, e.getValue());
}
}
From 87d2444e3efebeccef852ed9c95877411bb5578d Mon Sep 17 00:00:00 2001
From: Shai Almog <67850168+shai-almog@users.noreply.github.com>
Date: Wed, 27 May 2026 18:02:24 +0300
Subject: [PATCH 06/11] Guard currentTransformGlyphScale against NaN /
non-positive scale
The function reads `currentTransform.columns[i]` directly and feeds the
resulting magnitude into `font.pointSize * s`. If any column entry is
NaN (e.g. a degenerate transform sneaks in before CN1MetalSetTransform
has run) the multiplication propagates the NaN into UIFont's pointSize,
and the subsequent CTLineCreate / atlas-glyph lookup hangs the
simulator -- the iOS Metal UI tests timed out at FillShape on the
first run of #5049 with this exact symptom, and a retry passed.
Add an `isfinite(s) && s > 0` check that returns 1.0 (the unscaled-font
fast path) when the inputs aren't a finite positive scale. Cheap
defensive guard against the same flake recurring.
Co-Authored-By: Claude Opus 4.7 (1M context)
---
Ports/iOSPort/nativeSources/CN1Metalcompat.m | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/Ports/iOSPort/nativeSources/CN1Metalcompat.m b/Ports/iOSPort/nativeSources/CN1Metalcompat.m
index 1c6b650fd8..92bf453791 100644
--- a/Ports/iOSPort/nativeSources/CN1Metalcompat.m
+++ b/Ports/iOSPort/nativeSources/CN1Metalcompat.m
@@ -784,11 +784,18 @@ static inline float currentTransformGlyphScale(void) {
float c1y = currentTransform.columns[1].y;
float sx = sqrtf(c0x * c0x + c0y * c0y);
float sy = sqrtf(c1x * c1x + c1y * c1y);
+ float s = (sx + sy) * 0.5f;
+ // Reject NaN / inf / non-positive values: any of those would
+ // poison `font.pointSize * s` below and produce a UIFont with
+ // bad metrics that hangs the CTLine layout. `isfinite` is true
+ // only for finite numbers; treat anything else as "use unscaled
+ // font" by returning 1.0 (the `useScaledFont` gate at the call
+ // site clears that to the fast path).
+ if (!isfinite(s) || s <= 0.0f) return 1.0f;
// Cap at 8x to keep the atlas from rasterising absurdly large
// bitmaps for a runaway transform; well past 8x the difference
// between "atlas-perfect" and "sampled-and-filtered" is below
// what the user can see anyway.
- float s = (sx + sy) * 0.5f;
if (s < 1.0f) s = 1.0f; // No down-rasterising; 1px atlas is fine for downscale.
if (s > 8.0f) s = 8.0f;
return s;
From c27651591c24638f021ea3e929654a7104976368 Mon Sep 17 00:00:00 2001
From: Shai Almog <67850168+shai-almog@users.noreply.github.com>
Date: Wed, 27 May 2026 18:05:23 +0300
Subject: [PATCH 07/11] Stop rejecting alpha-mask paths whose bounds sit at
negative coords
`Renderer_getOutputBounds` returns { minX, minY, maxX, maxY } in
renderer pixel space, and three callers in the iOS port
(`nativePathRendererCreateTexture` Metal + GL ES2 branches,
`nativePathRendererToARGB`, and `DrawPath.execute`) early-returned
when `maxX < 0 || maxY < 0`. That fires for any shape whose bounding
box is entirely in the negative quadrant -- the SVG transcoder emits
exactly that shape for `spinner_animated.svg`'s children
(``), so after the
SVG scale-bake the renderer saw bounds in the (-7, -60) -- (8, -30)
range. maxX = 8 was fine but maxY = -30 < 0 tripped the guard, the
texture handle came back as 0, and `g.fillShape` silently dropped
every rect: the spinner column was blank on the iOS Metal animated
golden.
`width = maxX - minX` and `height = maxY - minY` are the correct
emptiness check -- a path with non-empty extent has positive width
and height regardless of where the bounds sit on the axis. Drop the
maxX/maxY < 0 guard and rely on the existing width / height == 0
check below.
Co-Authored-By: Claude Opus 4.7 (1M context)
---
Ports/iOSPort/nativeSources/DrawPath.m | 9 ++---
Ports/iOSPort/nativeSources/IOSNative.m | 47 +++++++++++++++++--------
2 files changed, 37 insertions(+), 19 deletions(-)
diff --git a/Ports/iOSPort/nativeSources/DrawPath.m b/Ports/iOSPort/nativeSources/DrawPath.m
index 60f4306a42..f746303b24 100644
--- a/Ports/iOSPort/nativeSources/DrawPath.m
+++ b/Ports/iOSPort/nativeSources/DrawPath.m
@@ -49,14 +49,15 @@ -(void)execute
JAVA_INT outputBounds[4];
Renderer_getOutputBounds(renderer, (JAVA_INT*)&outputBounds);
- if ( outputBounds[2] < 0 || outputBounds[3] < 0 ){
- return;
- }
+ // outputBounds is { minX, minY, maxX, maxY } in renderer pixel
+ // space; maxX / maxY are legitimately negative when the path sits
+ // in the negative quadrant. Filter on width / height (computed
+ // below) rather than the raw max values.
JAVA_INT x = min(outputBounds[0], outputBounds[2]);
JAVA_INT y = min(outputBounds[1], outputBounds[3]);
JAVA_INT width = outputBounds[2]-outputBounds[0];
JAVA_INT height = outputBounds[3]-outputBounds[1];
-
+
if ( width < 0 ) width = -width;
if ( height < 0 ) height = -height;
if (width == 0 || height == 0) {
diff --git a/Ports/iOSPort/nativeSources/IOSNative.m b/Ports/iOSPort/nativeSources/IOSNative.m
index 4cc9f21401..e39fb62cea 100644
--- a/Ports/iOSPort/nativeSources/IOSNative.m
+++ b/Ports/iOSPort/nativeSources/IOSNative.m
@@ -9238,19 +9238,23 @@ JAVA_OBJECT com_codename1_impl_ios_IOSNative_nativePathRendererToARGB___long_int
JAVA_INT outputBounds[4];
Renderer_getOutputBounds(renderer, (JAVA_INT*)&outputBounds);
- if ( outputBounds[2] < 0 || outputBounds[3] < 0 ){
- return 0;
- }
-
+ // outputBounds is { minX, minY, maxX, maxY }; maxX / maxY can be
+ // legitimately negative for shapes drawn at negative coordinates
+ // (see the comment in nativePathRendererCreateTexture above).
+ // Filter on the actual width / height below.
+
//GLuint tex=0;
JAVA_INT x = min(outputBounds[0], outputBounds[2]);
JAVA_INT y = min(outputBounds[1], outputBounds[3]);
JAVA_INT width = outputBounds[2]-outputBounds[0];
JAVA_INT height = outputBounds[3]-outputBounds[1];
-
+
if ( width < 0 ) width = -width;
if ( height < 0 ) height = -height;
-
+ if (width == 0 || height == 0) {
+ return 0;
+ }
+
AlphaConsumer ac = {
x,
y,
@@ -9300,7 +9304,19 @@ JAVA_LONG com_codename1_impl_ios_IOSNative_nativePathRendererCreateTexture___lon
Renderer *r = (Renderer*)renderer;
JAVA_INT outputBounds[4];
Renderer_getOutputBounds(renderer, (JAVA_INT*)&outputBounds);
- if (outputBounds[2] < 0 || outputBounds[3] < 0) return 0;
+ // outputBounds is { minX, minY, maxX, maxY } in renderer pixel
+ // space, which can legitimately be entirely negative when the
+ // input shape sits at negative coordinates (e.g. the SVG
+ // transcoder emits ``
+ // for the spinner_animated.svg children -- after the SVG scale
+ // bake the renderer sees a path with bounds (-7, -60, 8, -30)).
+ // The previous check rejected those legitimate negative maxX /
+ // maxY values, returned 0 / nil texture, and silently dropped
+ // every fillShape on negatively-positioned paths -- the
+ // spinner column was blank on iOS Metal screenshots as a
+ // result. Only reject *empty* bounds (max <= min on either
+ // axis); the unsigned width / height computed below carry the
+ // actual extent.
JAVA_INT x = min(outputBounds[0], outputBounds[2]);
JAVA_INT y = min(outputBounds[1], outputBounds[3]);
JAVA_INT width = outputBounds[2] - outputBounds[0];
@@ -9350,20 +9366,21 @@ JAVA_LONG com_codename1_impl_ios_IOSNative_nativePathRendererCreateTexture___lon
Renderer *r = (Renderer*)renderer;
JAVA_INT outputBounds[4];
-
+
Renderer_getOutputBounds(renderer, (JAVA_INT*)&outputBounds);
- if ( outputBounds[2] < 0 || outputBounds[3] < 0 ){
- //return 0;
- POOL_END();
- return;
- }
-
+ // outputBounds is { minX, minY, maxX, maxY }; the maxX/maxY
+ // values can legitimately be negative when the shape sits in
+ // the negative quadrant (e.g. the spinner SVG draws each
+ // rotated rect at y in [-40, -20]). The width / height check
+ // below filters degenerate / empty paths. Mirrors the Metal
+ // branch above.
+
GLuint tex=0;
JAVA_INT x = min(outputBounds[0], outputBounds[2]);
JAVA_INT y = min(outputBounds[1], outputBounds[3]);
JAVA_INT width = outputBounds[2]-outputBounds[0];
JAVA_INT height = outputBounds[3]-outputBounds[1];
-
+
if ( width < 0 ) width = -width;
if ( height < 0 ) height = -height;
if (width == 0 || height == 0) {
From 460280dccb0d7e9b679b260e9b41bf6a4900d27d Mon Sep 17 00:00:00 2001
From: Shai Almog <67850168+shai-almog@users.noreply.github.com>
Date: Wed, 27 May 2026 19:53:05 +0300
Subject: [PATCH 08/11] Refresh iOS Metal SVG goldens after clip + spinner
fixes
Updates SVGStatic.png and SVGAnimatedScreenshotTest.png to the
post-fix Metal output:
- SVGStatic.png: gradient_circle no longer renders as a triangle (the
setClip(GeneralPath) curve-flatten fix at the Metal port boundary
converts the arc-decomposed circle into a real polygon before
reaching the polygon stencil writer); the dark-blue stroke now wraps
a properly filled gradient circle.
- SVGAnimatedScreenshotTest.png: the spinner_animated column is no
longer blank. The four rotating rounded rectangles are now
rasterised through the alpha-mask pipeline (the
nativePathRendererCreateTexture maxX/maxY < 0 guard was rejecting
alpha masks for shapes positioned in the negative quadrant and the
spinner rects all sit at y in [-40, -20], so every call short-
circuited to a nil texture).
Captured from the build-ios-metal job on the same commit set;
clipped_badge.svg still shows a square baseline because the SVG
transcoder's clip-path forwarding fix landed in a separate commit
that needs the CI Maven cache to drop the prior svg-transcoder JAR
before it takes effect.
Co-Authored-By: Claude Opus 4.7 (1M context)
---
.../SVGAnimatedScreenshotTest.png | Bin 207799 -> 241681 bytes
scripts/ios/screenshots-metal/SVGStatic.png | Bin 165042 -> 156861 bytes
2 files changed, 0 insertions(+), 0 deletions(-)
diff --git a/scripts/ios/screenshots-metal/SVGAnimatedScreenshotTest.png b/scripts/ios/screenshots-metal/SVGAnimatedScreenshotTest.png
index e2cad74b18024fcf500af32a18eef29f473c0cbe..72db55c11597434a3f18dc651dc2189687b731f1 100644
GIT binary patch
literal 241681
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