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
55 changes: 55 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,61 @@ follow semantic versioning; release dates are ISO 8601.

## v2.2.1 — Planned

### Fixed

- **The SVG reader honours the opacity family.** `opacity`, `fill-opacity` and
`stroke-opacity` — attribute or `style=""`, number or percentage, with SVG's
inheritance for the paint slots and composition for group `opacity` — now
multiply into each layer's flat paint alpha, on top of any alpha the colour
already carries from an 8-digit hex or `rgba()`. They were read from nowhere
before: a translucent logo landed on the page fully opaque, and the only fix
was editing the SVG. A slot whose product reaches zero is treated like
`fill="none"`, so an `opacity="0"` guide layer no longer paints at all. A
*partial* opacity cannot reach a gradient slot — the `DocumentPaint` contract
refuses translucent stops because shadings carry no alpha — so a gradient
under `fill-opacity="0.5"` still paints opaque, now with a one-line warning
naming the approximation. Group opacity is the per-layer approximation of SVG's
offscreen compositing — overlapping siblings inside one translucent group
darken where a browser would flatten them first — which is the same trade
every lightweight icon reader makes.

- **What the SVG reader cannot honour now says so.** `fill-rule="evenodd"` was
read from nowhere and filled with non-zero winding — a donut whose hole is cut
by a same-direction subpath came out solid, silently; it still renders the
same way, but the icon now logs one warning naming the approximation (a
`fill-rule` value outside nonzero / evenodd / inherit, previously unread, is
now refused like any other bad presentation value). A `mask="url(#id)"` or
`filter="url(#id)"` attribute — whose definition sits in `<defs>`, where the
walk never looks — paints unmasked / unfiltered as before, but the
referencing attribute is now read and warned about, because that attribute is
the only place the divergence is observable. Element kinds outside the
reader's vocabulary (`mask`, `pattern`, `marker`, `<style>` CSS, `<a>`
wrappers…) joined the skip tally that previously only counted
text / image / `use` — one warning per kind, and a blank icon's "no drawable
geometry" error names them. The DOCX export gained the inline mirror of its
block-level drop warning: image / shape / SVG runs (emoji included) vanish
from a paragraph by contract, and now say so once per export instead of only
the block path warning.

- **SVG reader errors keep their house style at the edges.** A malformed hex
colour (`#zzz`), a non-numeric `rgb()` channel or `rgba()` alpha, and a
unit-only length (`stroke-width="px"`) leaked the raw
`NumberFormatException` ("For input string: …") past the reader's
name-the-field-and-the-element convention; each now reports what was being
parsed and the offending input, with the JDK detail chained as the cause.

### Documentation

- **The SVG Javadoc stopped describing a younger reader.** `SvgGradients` claimed
focal radials and `stop-opacity` are "loudly refused" — both degrade
deliberately (centred-radial approximation, opaque stops, alpha-only overlay
layers dropped) and the class doc now says what actually happens. `SvgIcon`
still listed clip paths as out of scope and the skip-tally warning said "no
clips" — `clip-path:url(#id)` has rendered since 1.9.0, so the supported list
gained it (with its innermost-wins nesting rule) and the out-of-scope list,
the warning, and the empty-icon error text shrank to what is actually
dropped.

### Build

- **The weekly benchmark run builds the modules it measures.** The JMH workflow
Expand Down
Binary file modified assets/readme/examples/emoji-clip-path.pdf
Binary file not shown.
Binary file modified assets/readme/examples/emoji-shortcodes.pdf
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -227,8 +227,11 @@ private static void requireFinite(double value, String what) {
* radial shadings, which carry no alpha channel, so a translucent stop
* would silently render opaque. Rather than render wrong, a stop with
* alpha below 255 is rejected at construction — flatten transparency into
* the stop colour, or apply opacity to the whole shape instead. This
* mirrors the SVG reader, which already refuses {@code stop-opacity}.
* the stop colour, or apply opacity to the whole shape instead. The SVG
* reader honours the same constraint from its side: {@code stop-opacity}
* and a partial element opacity on a gradient-painted shape paint opaque
* (warned once per icon), so no translucent stop ever reaches this
* constructor.
*
* @param offset position along the gradient axis in [0,1]
* @param color fully-opaque colour at this offset
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,14 @@
* One level of {@code href} / {@code xlink:href} indirection supplies
* stops for the split-definition style Inkscape and Figma emit.</p>
*
* <p>Out of PDF reach and loudly refused: focal radials ({@code fx} /
* {@code fy}), {@code spreadMethod} other than pad, and translucent stops
* ({@code stop-opacity}).</p>
* <p>Loudly refused because PDF shadings cannot express it:
* {@code spreadMethod} other than pad. Approximated instead of refused,
* matching how real-world icons use them: focal radials ({@code fx} /
* {@code fy}) collapse to a plain radial about the centre, and translucent
* stops ({@code stop-opacity}) paint opaque — except a same-colour
* translucent gradient (a pure alpha overlay), which
* {@link #isAlphaOnlyOverlay} lets the reader drop entirely rather than
* blot out the art beneath it.</p>
*/
final class SvgGradients {

Expand Down
25 changes: 19 additions & 6 deletions core/src/main/java/com/demcha/compose/document/svg/SvgIcon.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,18 @@
* {@code rotate} / {@code matrix} — affine maps are exact on Bézier control
* points), per-element {@code fill} / {@code stroke} / {@code stroke-width}
* styling with SVG's inheritance and defaults (missing {@code fill} paints
* black, {@code fill="none"} skips the fill), and {@code linearGradient} /
* {@code radialGradient} paints referenced via {@code url(#id)} — on fills
* and strokes alike, rendered as native PDF shadings.
* black, {@code fill="none"} skips the fill), the opacity family
* ({@code opacity} / {@code fill-opacity} / {@code stroke-opacity},
* multiplied down the tree into each layer's paint alpha — the per-layer
* approximation of SVG's offscreen group compositing; zero hides the slot,
* and a partial opacity cannot reach a gradient slot because shadings carry
* no alpha — the gradient paints opaque, with a one-line warning),
* {@code clip-path:url(#id)} clipping (a clip on an element or group clips
* it and its descendants; nested clips are not intersected — the innermost
* wins, and a clip that cannot be resolved paints unclipped), and
* {@code linearGradient} / {@code radialGradient} paints referenced via
* {@code url(#id)} — on fills and strokes alike, rendered as native PDF
* shadings.
*
* <p>Each layer is one {@link SvgPath} with its resolved paint, in document
* order — render them back-to-front. {@link #node(double)} packages the
Expand All @@ -38,11 +47,15 @@
* requested width with the icon's own aspect ratio.</p>
*
* <p>Out of scope (deliberately, this is an icon reader, not a browser):
* CSS stylesheets and classes, text, masks, clip paths, filters,
* CSS stylesheets and classes, text, masks, filters,
* {@code <use>} references, nested {@code <svg>} viewBoxes (inner frames
* recurse but their coordinates stay in the outer space), animations, and
* the gradient corners that have no PDF analogue (focal points,
* {@code spreadMethod} other than pad, stop opacity). A gradient's
* the gradient corners that have no PDF analogue ({@code spreadMethod}
* other than pad is refused; focal points collapse to a centred radial and
* translucent stops paint opaque). Fills always use non-zero winding —
* {@code fill-rule="evenodd"} renders, with a one-line warning, as if it
* were {@code nonzero}. Dropped elements and approximated features are
* each warned once per icon rather than silently ignored. A gradient's
* {@code href} / {@code xlink:href} indirection inherits only the referenced
* {@code <stop>} list — not its geometry attributes ({@code x1}/{@code y1}/
* {@code x2}/{@code y2}, {@code cx}/{@code cy}/{@code r}, {@code gradientUnits},
Expand Down
Loading
Loading