Skip to content
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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,13 @@ Entries land here as they merge.
`ListMarker.defaultForDepth(int)` and
`ListMarker.normalizeItemText(String, boolean)` (`@since 1.8.0`) — and the
fixed-layout pipeline and the DOCX export both call them.
- **SVG gradient number errors read in the reader's house style.** A
non-numeric gradient coordinate, radius, or stop value (e.g. `x1="abc"`,
`r="x%"`, `offset="?"`) leaked the raw JDK `NumberFormatException`
("For input string: …") as the reason. `SvgGradients` now parses through one
shared helper that throws `"<field> must be a number, got '…'"` with the
cause chained — matching the rest of the beta SVG reader, where the
per-element wrapper already names the referencing element.

### Documentation

Expand Down
27 changes: 21 additions & 6 deletions src/main/java/com/demcha/compose/document/svg/SvgGradients.java
Original file line number Diff line number Diff line change
Expand Up @@ -216,10 +216,10 @@ private static double coordinate(String value, double defaultFraction,
return userSpace ? defaultFraction * viewportSize : defaultFraction;
}
if (v.endsWith("%")) {
double fraction = Double.parseDouble(v.substring(0, v.length() - 1)) / 100.0;
double fraction = number(v.substring(0, v.length() - 1), "gradient coordinate") / 100.0;
return userSpace ? fraction * viewportSize : fraction;
}
return Double.parseDouble(v);
return number(v, "gradient coordinate");
}

private static double length(Element gradient, String attr, double defaultFraction,
Expand All @@ -231,10 +231,10 @@ private static double length(Element gradient, String attr, double defaultFracti
return userSpace ? defaultFraction * diagonal : defaultFraction;
}
if (v.endsWith("%")) {
double fraction = Double.parseDouble(v.substring(0, v.length() - 1)) / 100.0;
double fraction = number(v.substring(0, v.length() - 1), "gradient radius") / 100.0;
return userSpace ? fraction * diagonal : fraction;
}
return Double.parseDouble(v);
return number(v, "gradient radius");
}

/**
Expand Down Expand Up @@ -321,9 +321,24 @@ private static double fraction(String value, double defaultValue) {
}
String v = value.trim();
if (v.endsWith("%")) {
return Double.parseDouble(v.substring(0, v.length() - 1)) / 100.0;
return number(v.substring(0, v.length() - 1), "gradient stop value") / 100.0;
}
return number(v, "gradient stop value");
}

/**
* Parses a gradient numeric value, naming what failed and the offending
* input instead of leaking the raw {@link NumberFormatException} message
* ("For input string: …"). The cause is chained, and the referencing
* element is added by the reader's per-element error wrapper.
*/
private static double number(String value, String what) {
try {
return Double.parseDouble(value);
} catch (NumberFormatException e) {
throw new IllegalArgumentException(
what + " must be a number, got '" + value + "'", e);
}
return Double.parseDouble(v);
}

private static String attrOrStyle(Element element, String property) {
Expand Down
19 changes: 19 additions & 0 deletions src/test/java/com/demcha/compose/document/svg/SvgIconTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,25 @@ void gradientCornersWithoutAPdfAnalogueFailLoudly() {
.hasMessageContaining("focal");
}

@Test
void malformedGradientNumberSaysWhatExpectedANumber() {
// A non-numeric gradient coordinate must read in the reader's house
// style (named + reason) instead of leaking a bare JDK parse message.
assertThatThrownBy(() -> SvgIcon.parse("""
<svg viewBox="0 0 10 10">
<defs>
<linearGradient id="g" gradientUnits="userSpaceOnUse" x1="abc" y1="0" x2="10" y2="10">
<stop offset="0" stop-color="#000"/><stop offset="1" stop-color="#fff"/>
</linearGradient>
</defs>
<path d="M0 0 H10 V10 Z" fill="url(#g)"/>
</svg>
"""))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("must be a number")
.hasMessageContaining("abc");
}

@Test
void nodeFormPackagesLayersAtTheRequestedWidth() {
SvgIcon icon = SvgIcon.parse("""
Expand Down