feat(svm): fill with the gradients, hatches and transparency a metafile asks for - #789
Merged
Conversation
This was referenced Aug 30, 2026
andiwand
force-pushed
the
feat/svm-primitives
branch
from
August 30, 2026 17:55
8edc2ad to
eec0439
Compare
andiwand
force-pushed
the
feat/svm-fills
branch
from
August 30, 2026 18:08
2099e4d to
1ad4577
Compare
…le asks for `GRADIENT`, `GRADIENTEX`, `HATCH` and `TRANSPARENT` were skipped. All four map onto svg declaratively - a `<linearGradient>` or `<radialGradient>`, a `<pattern>` of lines, an `opacity` - so nothing is rasterised. None of them occurs in the 1125 metafiles harvested from the fixtures, so LibreOffice was the oracle: a metafile written by hand for the purpose, converted with `--convert-to svg`, and the two exports compared. That found three things a reading of the format alone did not: **A colour inside an object is not a colour.** An action's own colour is a plain `uint32` that `SvmReader::ReadColor` reads; a gradient's two ends and a hatch's lines are not. `GenericTypeSerializer::readColor` reads a `uint16` name id first, and only the user id (`0x8000`) carries three 16-bit channels behind it - anything else indexes a palette of 31. Reading four bytes there desynchronises the stream, and the action-length check then throws the whole image away. LibreOffice's export of the hand-written file said the gradient was black, which is what a wrongly written colour looks like from the other side. **An axial ramp has its colours the other way round.** `DrawLinearGradient` swaps them: the end colour is at both ends of the axis, the start colour in the middle. **A radial ramp ends in the middle.** `DrawComplexGradient` fills with the start colour and shrinks rings inwards towards the end colour. The linear ramp's vector is `Gradient::GetBoundRect`'s: the bounds grown so that turning still covers them, from the top of that to the bottom, turned about the centre. For the same action LibreOffice writes (450,100) to (750,400), and so do we. `SQUARE` and `RECT` shrink a rectangle rather than an ellipse, which svg has no gradient for; they come out as the ellipse they are closest to. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CmCr22NW6wPQKiQidk96bq
andiwand
force-pushed
the
feat/svm-fills
branch
from
August 30, 2026 18:21
1ad4577 to
f760ca0
Compare
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.
🤖 Generated with Claude Code
Stage 7 of #772, stacked on #788 → #787 → #786 → #785 → #784 → #779 —
review those first; this branch's base is
feat/svm-primitives.GRADIENT,GRADIENTEX,HATCHandTRANSPARENTwere skipped. All four maponto svg declaratively — a
<linearGradient>or<radialGradient>, a<pattern>of lines, anopacity— so nothing is rasterised.LibreOffice as the oracle, and what it caught
None of these actions occurs in the 1125 metafiles harvested from the
fixtures, so there was nothing to check against. A metafile was written by hand
instead, converted with
soffice --convert-to svg, and the two exportscompared. That found three things reading the format alone did not:
1. A colour inside an object is not a colour. An action's own colour
(
LINECOLOR,FILLCOLOR, …) is a plainuint32thatSvmReader::ReadColorreads. A gradient's two ends and a hatch's lines are not:
GenericTypeSerializer::readColorreads auint16name id first, and onlythe user id (
0x8000) carries three 16-bit channels behind it — anything elseindexes a palette of 31 colours. Reading four bytes there desynchronises the
stream, and the action-length check then throws away the whole image.
LibreOffice's export of the hand-written file said the gradient was black,
which is what a wrongly written colour looks like from the other side; the
same mistake reading would have cost every metafile with a gradient in it.
2. An axial ramp has its colours the other way round.
DrawLinearGradientswaps them (vcl/source/outdev/gradient.cxx): the endcolour is at both ends of the axis and the start colour in the middle.
LibreOffice's own export agrees —
stop 0 = blue, stop 0.5 = redfor ared→blue axial gradient.
3. A radial ramp ends in the middle.
DrawComplexGradientfills with thestart colour and shrinks rings inwards to the end colour, so in svg terms the
centre stop is the end colour.
The geometry
The linear ramp's vector is
Gradient::GetBoundRect's: the bounds grown sothat turning still covers them, from the top of that to the bottom, turned
about the centre —
(sin, cos)of the angle, becausePolygon::Rotateturnscounter-clockwise. For the same action LibreOffice writes (450,100)→(750,400),
and so do we.
SQUAREandRECTshrink a rectangle rather than an ellipse, which svg has nogradient for; they come out as the ellipse they are closest to, and that is
written down in
svm/PLAN.md.A hatch becomes a tiling
<pattern>of one, two or three line sets, turnedby
patternTransform— LibreOffice instead lays every line out individuallyinside a pattern the size of the shape, which does not scale.
Verification
6 new tests (40 in the svm suite), asserting the gradient vectors and stop
order against the numbers LibreOffice produced. The reference output does not
move at all — none of these actions is in the corpus — so there is no
regeneration and no pin bump in this branch.