feat(svg): honour the opacity family and warn on what the reader approximates - #596
Merged
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
The beta SVG reader read
opacity,fill-opacityandstroke-opacityfrom nowhere: a translucent logo landed on the page fully opaque, anopacity="0"guide layer an exporter left behind painted at full strength, and the only fix was editing the SVG by hand. The reader's silence had the same shape elsewhere —fill-rule="evenodd"filled with non-zero winding without a word (a donut whose hole is cut by a same-direction subpath comes out solid), amask/filter/patternvanished with no signal, and the DOCX export dropped a paragraph's inline SVG icons while the block-level drop path warned. Three parse edges (#zzzhex, non-numericrgb()channels, unit-only lengths) leaked the rawNumberFormatExceptionpast the reader's name-the-field-and-element error convention. This is the "hardens against real-world exporter output" work the surface has carried@Betafor since 1.8.0.What changed
SvgIconReaderhonours the opacity family.fill-opacity/stroke-opacityride the inheritedPaintstate and an explicit value replaces the inherited one (SVG property inheritance);opacitycomposites — each carrier multiplies into an accumulated group product, the standard per-layer approximation of SVG's offscreen group compositing. The product multiplies into each layer's flat paint alpha viaSvgStyles.multiplyAlpha, on top of any alpha the colour already carries from 8-digit hex orrgba(). A slot whose product reaches zero is treated likefill="none". A partial opacity cannot reach a gradient slot — theDocumentPaint.Stopcontract refuses translucent stops because shadings carry no alpha — so the gradient paints opaque and the degradation is warned once per icon.SkipTallygrew a degraded-features channel.fill-rule="evenodd"renders as before (non-zero) but warns once, noted at layer-emit time so the tally never names a layer that was dropped as degenerate. Amask="url(#id)"/filter="url(#id)"attribute warns even when the definition sits in<defs>(which the walk never visits) — the referencing attribute is the only place the divergence is observable. Element kinds outside the reader's vocabulary (mask,pattern,marker,<style>CSS,<a>wrappers…) join the skip tally that previously only counted text / image /use; a blank icon's "no drawable geometry" error names them.defs/title/desc/ gradients /clipPath/symbolstay silent — they hold no direct ink.SvgStyleskeeps the error house style at the edges. Malformed hex digits, non-numericrgb()channels /rgba()alpha, and unit-only lengths now throw contextualIllegalArgumentExceptions with the JDK cause chained;opacity="NaN"is refused rather than silently hiding the subtree (NaNfails every> 0check). An out-of-vocabularyfill-rulevalue is refused like any other bad presentation value — previously the attribute was unread, so this is a new loud edge, recorded in the CHANGELOG.DocxSemanticBackend.warnDroppedInlineRuns— the inline mirror of the existing block-levelwarnUnsupported: image / shape / SVG runs (emoji lower to SVG runs) vanish from a paragraph by contract and now say so once per export per kind, deduplicated through the samewarnedNodeKindsset that resets inexport().SvgGradientsclaimed focal radials andstop-opacityare "loudly refused" — both degrade deliberately (centred-radial approximation, opaque stops, alpha-only overlays dropped).SvgIconstill listed clip paths as out of scope and the tally warning said "no clips" —clip-path:url(#id)has rendered since 1.9.0.DocumentPaint.Stopsaid the reader "refuses"stop-opacity. All three now state what actually happens.emoji-clip-path.pdfandemoji-shortcodes.pdfare the only committed renders whose source SVGs carry element opacity (401 of the 1595 bundled emoji glyphs do); the visible delta is small (~100 px at 2× raster) because most glyph transparency rides gradients, which are unchanged. Template CV icons and the showcase art use onlyfill-rule="evenodd", which renders identically and merely warns — no other preview moves.Verification
Full reactor gate
./mvnw -B -ntp clean verify -pl :graph-compose-core,…,:graph-compose-coverage -am→ BUILD SUCCESS, all coverage checks met, committed layout snapshots and visual baselines untouched. +31 tests:SvgIconOpacityAndWarningsTest(22 — opacity inheritance vs composition,style=""carriage, zero-hides-slot for fill/stroke/whole layer, hex-alpha multiplication, gradient fill/stroke opaque-plus-warn, NaN refusal, evenodd warn-once and quiet nonzero/inherit, mask-in-defs andstyle="filter:…"warns, unknown-element tally incl. the empty-icon error, structure-only silence),SvgStylesTest+5 (hex/rgb NFE wrapping, channel clamping, unit-only length, opacity grammar),SvgPathTest+2 (scientific-notation numbers, non-zero arcx-axis-rotationendpoint),DocxInlineRunDropWarningTest(2 — one warn per export namingInlineSvgRunwith paragraph text preserved, text-only export stays quiet). Existing 40-caseSvgIconTestand the qa render suites stay green. No public API signature change — behaviour and messages only.Lane: canonical (
document.svg) + render-docx warn seam — no engine/layout surface touched.Notes
PathNodehas no winding-rule slot;DocumentPaint.Stoprejects alpha) — each is a separate feature if ever wanted, not part of this hardening.