Skip to content

Commit 3200366

Browse files
committed
fix(svg): gradient number errors read in the reader's house style
SvgGradients coordinate/radius/stop parsing now goes through one number() helper that throws '<field> must be a number, got <value>' with the cause chained, matching the rest of the beta SVG reader instead of leaking the raw JDK NumberFormatException. Adds a negative test + CHANGELOG entry.
1 parent 41e2db0 commit 3200366

3 files changed

Lines changed: 47 additions & 6 deletions

File tree

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,13 @@ Entries land here as they merge.
223223
`ListMarker.defaultForDepth(int)` and
224224
`ListMarker.normalizeItemText(String, boolean)` (`@since 1.8.0`) — and the
225225
fixed-layout pipeline and the DOCX export both call them.
226+
- **SVG gradient number errors read in the reader's house style.** A
227+
non-numeric gradient coordinate, radius, or stop value (e.g. `x1="abc"`,
228+
`r="x%"`, `offset="?"`) leaked the raw JDK `NumberFormatException`
229+
("For input string: …") as the reason. `SvgGradients` now parses through one
230+
shared helper that throws `"<field> must be a number, got '…'"` with the
231+
cause chained — matching the rest of the beta SVG reader, where the
232+
per-element wrapper already names the referencing element.
226233

227234
### Documentation
228235

src/main/java/com/demcha/compose/document/svg/SvgGradients.java

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -216,10 +216,10 @@ private static double coordinate(String value, double defaultFraction,
216216
return userSpace ? defaultFraction * viewportSize : defaultFraction;
217217
}
218218
if (v.endsWith("%")) {
219-
double fraction = Double.parseDouble(v.substring(0, v.length() - 1)) / 100.0;
219+
double fraction = number(v.substring(0, v.length() - 1), "gradient coordinate") / 100.0;
220220
return userSpace ? fraction * viewportSize : fraction;
221221
}
222-
return Double.parseDouble(v);
222+
return number(v, "gradient coordinate");
223223
}
224224

225225
private static double length(Element gradient, String attr, double defaultFraction,
@@ -231,10 +231,10 @@ private static double length(Element gradient, String attr, double defaultFracti
231231
return userSpace ? defaultFraction * diagonal : defaultFraction;
232232
}
233233
if (v.endsWith("%")) {
234-
double fraction = Double.parseDouble(v.substring(0, v.length() - 1)) / 100.0;
234+
double fraction = number(v.substring(0, v.length() - 1), "gradient radius") / 100.0;
235235
return userSpace ? fraction * diagonal : fraction;
236236
}
237-
return Double.parseDouble(v);
237+
return number(v, "gradient radius");
238238
}
239239

240240
/**
@@ -321,9 +321,24 @@ private static double fraction(String value, double defaultValue) {
321321
}
322322
String v = value.trim();
323323
if (v.endsWith("%")) {
324-
return Double.parseDouble(v.substring(0, v.length() - 1)) / 100.0;
324+
return number(v.substring(0, v.length() - 1), "gradient stop value") / 100.0;
325+
}
326+
return number(v, "gradient stop value");
327+
}
328+
329+
/**
330+
* Parses a gradient numeric value, naming what failed and the offending
331+
* input instead of leaking the raw {@link NumberFormatException} message
332+
* ("For input string: …"). The cause is chained, and the referencing
333+
* element is added by the reader's per-element error wrapper.
334+
*/
335+
private static double number(String value, String what) {
336+
try {
337+
return Double.parseDouble(value);
338+
} catch (NumberFormatException e) {
339+
throw new IllegalArgumentException(
340+
what + " must be a number, got '" + value + "'", e);
325341
}
326-
return Double.parseDouble(v);
327342
}
328343

329344
private static String attrOrStyle(Element element, String property) {

src/test/java/com/demcha/compose/document/svg/SvgIconTest.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,25 @@ void gradientCornersWithoutAPdfAnalogueFailLoudly() {
364364
.hasMessageContaining("focal");
365365
}
366366

367+
@Test
368+
void malformedGradientNumberSaysWhatExpectedANumber() {
369+
// A non-numeric gradient coordinate must read in the reader's house
370+
// style (named + reason) instead of leaking a bare JDK parse message.
371+
assertThatThrownBy(() -> SvgIcon.parse("""
372+
<svg viewBox="0 0 10 10">
373+
<defs>
374+
<linearGradient id="g" gradientUnits="userSpaceOnUse" x1="abc" y1="0" x2="10" y2="10">
375+
<stop offset="0" stop-color="#000"/><stop offset="1" stop-color="#fff"/>
376+
</linearGradient>
377+
</defs>
378+
<path d="M0 0 H10 V10 Z" fill="url(#g)"/>
379+
</svg>
380+
"""))
381+
.isInstanceOf(IllegalArgumentException.class)
382+
.hasMessageContaining("must be a number")
383+
.hasMessageContaining("abc");
384+
}
385+
367386
@Test
368387
void nodeFormPackagesLayersAtTheRequestedWidth() {
369388
SvgIcon icon = SvgIcon.parse("""

0 commit comments

Comments
 (0)