Commit Graph

74 Commits

Author SHA1 Message Date
Brandon Thomas 8ee082f257 Render: 3-D shapes' faces are lit even without a bevel
A shape with scene3d and sp3d but no bevelT was drawn in its flat fill
colour. PowerPoint lights the front face from the light rig whether or
not there is a bevel, so a plain 3-D rotated rectangle is lighter than
its fill. Model the face with the same threePt approximation as the
bevel (no rim), for solid fills.

Wild 3-D slides: s8 6.35 -> 4.26, s9 19.74 -> 17.68. Corpus unchanged
except lo tdf114848 s4 2.13 -> 1.94.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 08:16:41 -04:00
Brandon Thomas 238157d98e Render: 3-D cameras turn shapes (affine approximation)
A shape seen through a 3-D camera preset (isometric, perspective...) or
turned with a:camera/a:rot drew flat and front-on. PowerPoint turns the
shape's plane in space and projects it. Fitted black-box to its exports
(a picture with coloured corner markers under every preset and several
rotations; every fit within half a point on a 100 x 75 pt shape): points
about the centre are turned Rz(-rev) Rx(lat) Ry(lon), perspective cameras
then dividing by D / (D - z) with D about 5-16 shape sizes; a:rot turns
the other way about the horizontal and vertical axes. DeckCraft now draws
the shape under the affine transform that best fits its turned corners:
exact for orthographic cameras, within 2% of the size for perspective
ones (an affine map can't foreshorten). Oblique cameras leave a flat
shape as it is; depth (extrusion) is not drawn.

Wild synthetic set: camera presets 15.02 -> 6.35, 3-D picture styles
16.00 -> 7.72, extruded cameras 21.41 -> 19.74.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 08:16:41 -04:00
Brandon Thomas ee71879f80 Render: pictures reflect their pixels
A reflection on a picture (PowerPoint's "reflected" picture styles) drew
nothing: the reflection painted the shape's fill, and a picture has none.
It now mirrors the picture itself, clipped to its outline, under the same
fade.

Wild synthetic set: picture effects slide 16.77 -> 16.00 (the rest is
3-D camera rotation, not modelled).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 08:16:41 -04:00
Brandon Thomas 7cb62d5197 Pptx/Render: picture fills with rotWithShape=0 stay upright
A shape's picture fill marked not to rotate with the shape turned and
flipped with it (rotWithShape was neither kept nor written: always 1).
PowerPoint keeps such a picture upright, stretched (or tiled) over the
box the rotated shape covers on the slide and clipped to the shape. Now
read, written (PictureFill::upright) and drawn so.

Wild synthetic set: picture fills on rotated/flipped shapes 12.89 ->
3.87.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 08:16:41 -04:00
Brandon Thomas 9238a915a2 Render: presets PowerPoint mitres by default get sharp corners; miter limit 8
An outline naming no join was always rounded. PowerPoint rounds most
presets but mitres some by default: classified per preset by comparing
its export (8 pt outline, no join) with round- and miter-joined renders:
the plus, right/up/down block arrows (not the left one), chevrons, stars
from five points up, the equation shapes, curved ribbons and the action
buttons. Miter joins also take PowerPoint's default limit of 8 widths
(kurbo's default was 4), so sharp star points no longer bevel off.

Wild synthetic set: joins slide 0.43 -> 0.21, outline-on-pattern slide
3.95 -> 3.76, preset galleries slightly better.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 08:16:41 -04:00
Brandon Thomas 2e68e54eec Render: gradients with rotWithShape=0 keep to the slide's axes
A gradient fill marked not to rotate with its shape turned and flipped
with it anyway. PowerPoint lays such a gradient out upright, over the box
the rotated or flipped shape covers on the slide (a 90 degree gradient on
a quarter-turned triangle still runs top to bottom). Linear and radial
gradients now do; rect and outline path gradients still follow the shape.

Wild synthetic set: gradients on rotated/flipped shapes 5.48 -> 1.34,
gradient angles slide 6.75 -> 1.95.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 08:16:41 -04:00
Brandon Thomas 239e41bdf6 Render: a picture's shadow covers the whole picture
The shadow of a picture is the picture recoloured to the shadow colour,
but drawing the picture left its pixel-space transform set, so the
recolouring rectangle (sized in points) covered only part of a picture
whose bitmap is bigger than it is shown: a 400 px photo at 240 pt cast a
shadow of its top-left corner only. The shape's transform is restored
before recolouring.

Wild synthetic set: framed pictures with shadows 7.67 -> 5.95, rotated
picture with a shadow 10.67 -> 10.07.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 08:16:41 -04:00
Brandon Thomas cef6e1a8c8 Render: table borders draw their dashes, compound bands and diagonals
Explicit cell borders were stroked as plain solid lines (no dash, compound
or cap), and diagonal borders (lnTlToBr, lnBlToTr) were read but never
drawn. Both now go through the shape line stroker; diagonals are drawn
under the cell edges.

Wild synthetic set: border styles slide 5.67 -> 2.26, thick merged-cell
borders 1.20 -> 0.69.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 08:16:41 -04:00
Brandon Thomas 9d4541b8b4 Render: bevelled shapes lit like PowerPoint's default 3-D light (approximation)
A shape with a top bevel seen through a 3-D scene drew flat. Fitted to
PowerPoint's export of the bevel presets on a solid fill with the default
three-point light rig and matte material, DeckCraft now lights the face
and a band as wide as the bevel inside the outline as surfaces: Lambert
light from the upper left (azimuth 240, 45 degrees up), c * (0.453 +
0.731 * n.L) + 14.7 per channel, the band's normal tilting outwards by
the profile's slope (quarter circle by default, straight for angle).
Within about 15 grey levels RMS of PowerPoint on the circle bevel; other
profiles use the circle's, pictures get light and dark veils, and other
light rigs and materials (and anything without a scene) stay flat.

Wild synthetic set: bevel presets slide 5.20 -> 2.48, materials/rigs
13.25 -> 12.73.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 08:16:41 -04:00
Brandon Thomas 46dfb23dc5 Render: outline (shape) path gradients from an off-shape focus or a focus box
A "shape" path gradient with its focus off the shape (fillToRect on a
corner of an ellipse's box) filled solid with its last colour: the
position came from the nearest crossing of the ray from the focus with
the outline, and rays from outside cross it twice or miss. The contours
are the outline scaled about the focus, which a point first reaches at
the ray's farthest crossing; that is now used. A focus rectangle (rather
than a point) is the first colour throughout, with the colour running from
its edge to the outline, as PowerPoint draws it.

Wild synthetic set: shape path gradients 18.95 -> 3.54.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 08:16:41 -04:00
Brandon Thomas 675d20417e Pictures: black and white, inverted alpha, and PowerPoint's grey weights
- a:biLevel (Color > Black and White) was ignored. It is now read, written
  and drawn: white where the luminance reaches the threshold, else black
  (observed thresholds at 30/50/70% on colour swatches).
- a:alphaInv was ignored; an opaque picture with it is invisible in
  PowerPoint. Now read, written and drawn.
- Grayscale, duotone and saturation weighed channels 0.299/0.587/0.114;
  PowerPoint's export greys pure red to 54 and green to 182: Rec. 709
  weights (0.2126/0.7152/0.0722).

Wild synthetic set: recolour slide 7.60 -> 1.15, picture corrections
13.36 -> 7.36.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 08:16:41 -04:00
Brandon Thomas bf877411fb Render: picture brightness with contrast as PowerPoint combines them
Brightness was added after the contrast stretch. Measured on PowerPoint's
export of grey ramps (5 x 5 grid of -80..80% brightness and contrast):
contrast scales about mid-grey (x(1+c) below zero, /(1-c) above) and
brightness adds b * 255 scaled by (1 + cf) / 2, half before the stretch
and half after; +40% brightness with +40% contrast maps black to 54, not
17. DeckCraft now matches the 25 ramps within 5 grey levels.

Wild synthetic set: brightness/contrast grid 15.93 -> 13.36, recolour
slide 8.74 -> 7.60.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 08:16:41 -04:00
Brandon Thomas 3b04dd14be Render: pictures with opposite corrections no longer share decoded pixels
The decoded-picture cache keyed adjustments with an xor-multiply mix in
which brightness b, contrast c and -b, -c collide (the sign bits cancel),
so of two copies of a picture, one +40% bright and contrast and one -40%,
both showed whichever was decoded first. The key is now a real hash.

Wild synthetic set: brightness/contrast grid 27.86 -> 15.93.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 08:16:41 -04:00
Brandon Thomas 772abb2154 Render: pattern fills blend neighbouring cells between whole-pixel sizes
Pattern tiles were sampled nearest-cell, exact when a pattern cell
(0.75 pt) is a whole number of output pixels (a 960-wide export of a
720 pt slide) but harsh otherwise: at 960 px for a 960 pt slide (0.75 px
per cell) PowerPoint shows greys where a pixel straddles two cells, and
DeckCraft showed only the two colours, in different places. The tile is
now sampled bilinearly. Fitted against PowerPoint's export of all 54
presets at 0.75 px per cell: mean error 0.067 (bilinear) against 0.082
(nearest) and 0.095 (area average); still not exact (narVert reads
255/128/0 in PowerPoint, 213/128/42 here). Unchanged at whole-pixel sizes.

Wild synthetic set: patterned backgrounds 21.15 -> 11.31 and
18.00 -> 9.75, pattern galleries 22.50 -> 19.44 and 13.96 -> 11.64.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 08:16:41 -04:00
Brandon Thomas 84e6c12543 Render: a group's outer shadow is cast by its children
An outer shadow set on a group (grpSpPr effectLst) was ignored. PowerPoint
casts it from everything in the group: the children are now drawn once
more under the shadow's offset, scale and skew (anchored on the group's
box), recoloured to the shadow colour and blurred, below the group.

Wild synthetic set: combined-effects slide (shadowed group) 2.00 -> 0.98.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 08:16:41 -04:00
Brandon Thomas aafce0d2ef Render: gradient lines draw their gradient, heads included
A line filled with a gradient drew in its first stop's colour only. As
PowerPoint draws it, the gradient is laid out over the box the stroke
covers (the outline's bounds grown by half the line width, before the
line is cut back under its arrowheads), so a horizontal line's 90 degree
gradient runs across its width; arrowheads take the gradient where they
sit.

Wild synthetic set: gradient lines slide 8.43 -> 0.40; picture with a
gradient border 8.54 -> 7.67.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 08:16:41 -04:00
Brandon Thomas 4773e4ee0f Render: tiled pictures take their size from the resolution they record
A tiled picture fill (including a blipFill with neither stretch nor tile,
which PowerPoint tiles) assumed 96 dpi. PowerPoint sizes a picture by the
resolution its file records: a 32 px tile saved at 90 dpi is 25.6 pt, not
24. The PNG pHYs chunk and JPEG JFIF density are now read (rounded to
whole dpi) and scale the tile.

Corpus lo/tdf128596 (tiled checkmark texture): 8.79 -> 0.52 mean diff
(it had drifted from 7.36 when tiles started snapping to whole pixels).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 08:16:41 -04:00
Brandon Thomas c2b622a797 Render: tiled picture fills flip, align and size like PowerPoint's
- flip x/y/xy (every other tile mirrored) was ignored: tiles now repeat
  reflected across the flip axes.
- algn was ignored: the first tile now sits on the corner, edge middle or
  centre of the box it names, before the tx/ty offset.
- The tile size came from the decoded (possibly downsampled) bitmap; it is
  now the picture's own pixel size (96 dpi) times sx/sy, rounded to whole
  output pixels as PowerPoint draws it.
- A scale of zero or less drew tiles at 100%; PowerPoint draws nothing.
- Tiles are one repeating image paint instead of one draw per tile (no
  200-tile cap, constant cost).

Wild synthetic set: tiled background flipped/aligned 82.92 -> 1.80, tile
variants 51.15 -> 8.82.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 08:16:41 -04:00
Brandon Thomas 0cbcbebf68 Render: a 3-D picture's border stays centred on its edge
Follow-up to drawing picture borders outside the picture: PowerPoint does
that for flat pictures only. A picture seen through a 3-D scene with a
bevel (a common picture style) gets its border centred on the edge like
any shape: observed with an 18 pt border, which then covers 9 pt of the
picture. The corpus deck 60810 (white-framed bevelled screenshots) showed
the screenshots' own edges once the border moved out; it now matches
PowerPoint again.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 08:16:41 -04:00
Brandon Thomas 0c5ddb5dea Gradients: read, write and draw a:tileRect
Gradient tile rectangles were dropped on import. PowerPoint's "from
corner" radial presets depend on them: fillToRect puts the focus on a
corner and tileRect stretches the gradient's box a whole size past it, so
the circle is centred on that corner and reaches the far one. Without it
(after the two-point radial change) such fills washed out. A linear
gradient now spans its tile rectangle; a radial one ends on the circle
around it, with the focus still placed in the shape's own box (fitted to
PowerPoint's export of the corpus deck tdf114848: mean diff over the
filled box 0.77). Round-trips through pptx.

Wild synthetic set: tile-rect gradients 16.74 -> 2.48.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 08:16:41 -04:00
Brandon Thomas 9a7e7e133e Geom/Render: brackets and braces fill; arrowheads follow open paths, not brackets
- Left/right brackets and braces drew only their curve: a filled bracket
  showed no fill. PowerPoint fills them out to the straight side they open
  to (the fill path is the curve closed), outlining only the curve.
- Arrowheads were drawn on any shape with nothing filled (brackets among
  them) but not on open freeforms that had a fill. PowerPoint puts them on
  lines, arcs and open freeforms whether filled or not, never on brackets.

Wild synthetic set: preset galleries 9.44 -> 4.65, 9.55 -> 4.94,
9.60 -> 5.38; extreme-adjust galleries 10.7 -> 6.6.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 08:16:41 -04:00
Brandon Thomas 47dfaaca61 Render: shapes filled with their group's fill (a:grpFill) show it
A shape inside a group whose fill is grpFill drew nothing (the fill was
read but the renderer skipped it). PowerPoint fills it with the group's
fill laid out over the whole group: a gradient or pattern runs on across
the children rather than restarting in each. Groups without a fill of
their own pass their parent's on.

Wild synthetic set: group fills slide 27.33 -> 7.88.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 08:16:41 -04:00
Brandon Thomas 7d642e12c9 Render: a picture's border lies outside the picture
DeckCraft centred a picture's outline on its edge, covering half the
border's width of the picture. PowerPoint draws picture borders wholly
outside: an 18 pt white photo frame leaves the whole photo showing and
reaches 18 pt out (measured on its export; a 2 pt border on a cropped
picture covers 18-20 pt for a picture starting at 20). Pictures now stroke
their border twice as wide, clipped to outside the outline, keeping dash
lengths; the border's shadow is cast from there too. Compound borders
stay centred.

Wild synthetic set: picture frames 13.12 -> 8.54, cropped/flipped pictures
with borders 5.79 -> 2.57, crop-to-shape 8.03 -> 5.25.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 08:16:41 -04:00
Brandon Thomas 862e2175a7 Render: shadows are cast by what is drawn: no-fill shapes, see-through fills, lines
An outer shadow was always the shape's filled silhouette at the shadow's
opacity: an outline-only shape cast a solid block, a 40% fill cast a full
shadow that darkened it from behind, and lines (and outlines) cast none.
PowerPoint casts the shadow from the drawn shape: the filled part as
see-through as its fill (nothing for no fill) plus the outline, which is
now stroked into the shadow layer with the line's width, dash and opacity.

Wild synthetic set: combined effects slide (no-fill frame, 40% fill,
shadows) 21.88 -> 2.34.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 08:16:41 -04:00
Brandon Thomas b3a980dcb5 Render: perspective shadows scale and skew about their alignment point
A shadow's sx/sy/kx/ky were applied about the shape's centre with the
skew unscaled, so a perspective shadow (algn=b, sy 23%, kx -20 deg) floated
in the middle of the shape instead of lying along its bottom edge. As
PowerPoint draws it, the shadow is skewed then scaled about the point of
the box its algn names (b: middle of the bottom edge, tl: top-left...).

Wild synthetic set: shadow alignment/scale/skew slide 5.57 -> 0.41,
picture frames 13.71 -> 12.71.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 08:16:41 -04:00
Brandon Thomas 0796325a1f Render: radial (circle path) gradients end on the circle around the whole box
An off-centre radial gradient ("from corner", or any fillToRect focus
other than the middle) spread from its focus out to the farthest corner,
so the near side stayed light. Fitted to PowerPoint's export: the first
stop is a point at the focus and the last is the circle through the box's
corners (centre of the box, half its diagonal), with the circles between
moving from one to the other (a two-point radial). A corner focus thus
reaches its last colour at its own corner too. Centred gradients are
unchanged.

Wild synthetic set: off-centre radial background 20.66 -> 1.22, radial
focus grid 6.34 -> 5.69.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 08:16:41 -04:00
Brandon Thomas 90a9933d46 Render: arrowheads as PowerPoint draws them: the line stops inside, one shape, 2 pt minimum
- A wide line poked its corners (and square caps) out past a triangle,
  stealth or open arrowhead: the line now stops under the head (at a
  triangle's base, inside a stealth notch, where an open arrow's legs meet).
- A see-through line darkened where it overlapped its heads; PowerPoint
  draws line and heads as one shape, so they are drawn opaque into a
  faded layer.
- Heads on lines thinner than 2 pt were too small: PowerPoint sizes them
  as for a 2 pt line at least (measured 6 pt for a medium head on 0.25 to
  2 pt lines).
- An open arrow's point sat a miter past the line's end, with flat-ended
  legs; PowerPoint puts the point's outside on the end and rounds the legs.

Wild synthetic set: open arrows by width 6.85 -> 3.78, 8 pt open arrows
3.84 -> 1.89, triangles by width 3.62 -> 2.17, stealth by width 3.45 -> 2.38.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 08:16:41 -04:00
Brandon Thomas 2fca333cac Render: dashes with round caps keep their length; square caps only at the line's ends
A round-capped dashed line (PowerPoint's "Round Dot" is sysDot with round
caps) drew as a solid line: each dash grew by its caps and closed the
one-width gaps. PowerPoint keeps the caps inside each dash, so dots stay
separate circles. Square-capped dashes end flat in PowerPoint; only the
line's own start (and its end, when it ends in a dash) is squared off.

Wild synthetic set: 12 pt round-cap dashes 12.69 -> 7.05, 12 pt square-cap
dashes 11.76 -> 6.12, custom dashes 2.12 -> 0.10.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 08:16:41 -04:00
Brandon Thomas 39adfcd62a Render: custom geometry sub-paths fill even-odd, so inner sub-paths cut holes
A freeform whose path holds a sub-path inside another (an icon, outlined
text, a frame drawn as two rectangles) showed the inner one filled when
both wound the same way: DeckCraft filled custom paths non-zero.
PowerPoint fills every custom path alternately (even-odd), so the inner
sub-path is a hole whichever way it winds.

Wild synthetic set: holes slide 7.60 -> 2.84 mean diff.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 08:16:41 -04:00
Brandon Thomas 41d839d931 Text: East Asian vertical text (eaVert) stands ideographs and kana upright
eaVert text was laid out exactly like vert: the whole text turned a quarter turn, so Japanese and
Chinese lay on their side. PowerPoint draws ideographs, kana, Hangul, full-width forms, CJK
punctuation, circled numbers and emoji upright, each turned back about the centre of its em box,
while Latin letters and digits stay on their side ("英字123も": 123 sideways). It keeps the glyphs'
horizontal forms (no vert alternates: "、" "「" are simply turned), which DeckCraft now matches.

Glyph runs carry an `upright` flag; the renderer turns each such glyph about its em box centre
(0.38 em above the baseline). w01-vertical s3 (eaVert) 9.70 -> 8.98, s6/s7 (eaVert rotated,
flipped, bulleted) 1.58 -> 1.45, 2.30 -> 1.76.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 06:48:43 -04:00
Brandon Thomas 08bc46c0ea Text: underline styles (double, heavy, dotted, dashed, wavy) at the font's underline position
Every underline was a solid line 0.06 em thick at 0.12 em below the baseline, whatever a:rPr/@u
asked for. Underlines now sit at the font's own underline position and thickness (post table),
where PowerPoint draws them (Arial 96 pt: 10 px below the baseline, 7 px thick), and each style
is drawn as measured on PowerPoint's export: dbl two lines 0.55 thick spanning one thickness above
to the single line's bottom; heavy from one thickness above to 0.55 below; dotted dots and gaps one
thickness long; dashes three long with gaps of two; wavy a thin zigzag every 3.9 thicknesses. The
"words" style underlines spaces too, as PowerPoint draws it. Dashes and waves are new Deco line
kinds the renderer fills or strokes (warped text draws their boxes).

Underline slides: w14-xml s10 11.49 -> 10.43, s11 6.64 -> 4.06; w06-charfmt s3 9.66 -> 9.51,
s4 7.63 -> 7.44.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 06:48:43 -04:00
Brandon Thomas d5e30557eb
Transitions like PowerPoint: measured frame by frame and retuned (#366)
Every transition and Effect Option was built in PowerPoint (COM), exported as
video and compared frame by frame with DeckCraft at the same progress
(mean abs diff over 150 variants 35.7 -> 15.0, 0-255).

- anim: transition_frame(kind, option, t, aspect) with measured curves
  (smoothstep fades, ease-in-out moves, Cover/Uncover/Pan curves), directions
  and option meanings, soft edges as per-corner opacity, 3-D effects as
  perspective-projected cards, Dynamic Content moving slide contents over
  cross-fading backgrounds (Source::OldBack/NewBack/OldContent/NewContent);
  layer_mesh subdivides perspective quads so textures stay straight.
- New options: Page Curl Single, Glitter Hexagons, Shred In/Out, Zoom and
  Rotate; PowerPoint-style Effect Options labels.
- render: composite_transition (CPU twin of the show), render_slide_part,
  render_transition; deckcraft-cli render-transition FILE --slide N --t T.
- show: per-vertex opacity meshes, slide parts rendered once per transition.
- pptx: PowerPoint's elements and defaults (p:strips diagonal wipes, p14:warp
  and p:newsflash for Zoom, p:zoom for Shape In/Out, glitter pattern, shred
  rectangle/dir, prstTrans invX per preset, pageCurlSingle, defaults for
  randomBar/doors/window/pan/zoom); PowerPoint reopens DeckCraft's files with
  the same effects.
- Tests: measured curves, probes of which slide shows where at t, option
  coverage, per-frame triangle budget, PowerPoint XML and defaults round trip.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 05:31:13 -04:00
Brandon Thomas 735e16d93c
Charts: text at the sizes the file sets, as in PowerPoint (#358)
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 04:30:41 -04:00
Магда М.Г. ce1c7b7759 Charts: axis number formats, automatic major unit, hidden axes
Value axes showed raw numbers ("800000") with a fixed ~5-interval scale and
fixed label gutters, deleted axes were still drawn, and data labels ignored
their number format, so Russian business charts read "516600000" where
PowerPoint shows "516 600 000 ₽".

- New deckcraft_model::numfmt: Excel number format codes (sections for
  positive/negative/zero, quoted and escaped literals, _x padding, *x fill,
  [$sym-lcid] currency, [colour]/conditions dropped, 0/#/? placeholders,
  grouping with ',' or the locale's literal space, trailing-comma scaling,
  %, General) with digit grouping and decimal mark from the chart's
  language (c:lang: ru-RU -> "800 000", "12,5%").
- Reading: each value axis' numFmt, c:delete, scaling min/max and
  majorUnit, for the primary and a combo chart's secondary axis; data
  labels' numFmt (series, then group, else the source data's formatCode);
  the chart's c:lang. The primary value axis is the one on the left
  (bottom) and the secondary the one on the right/top or crossing at max,
  whatever the plot groups' order.
- Scale (Chart::axis_scale): fixed bounds and unit where given, else the
  data plus 5% headroom rounded out to the smallest 1-2-5 unit that leaves
  as many intervals as the axis has room for labels, as PowerPoint does
  (0-800 000 by 100 000, 0-7 000 by 1 000 on the sample deck).
- Rendering: labels in the axis format, gutters sized to the longest label,
  deleted axes draw no labels and take no room, data labels in their own
  format.
2026-10-13 04:20:52 -04:00
Магда М.Г. 5c82c08847 Charts: draw combo charts with columns, lines and a secondary axis
A combo chart (columns plus lines, often with the lines on a second value
axis) was read with each series' type but drawn as lines only, on one
scale, so a spend-and-clicks chart showed the clicks as a flat line along
the bottom and no columns.

- Reading: a plot group whose value axis isn't the first group's is on the
  secondary axis (Series::secondary_axis, now set).
- Model: Chart::series_kind, on_secondary and axis_range (per axis; a combo
  stacks only its stacked series).
- Rendering: bar-like series are drawn as bars (clustered or stacked among
  themselves), the others as lines/areas over them, each against its own
  axis; the secondary axis' labels go on the right. Line series use their
  line's colour and width (where PowerPoint keeps them), also in the legend.
  Long category labels wrap onto two lines.
2026-10-13 04:20:52 -04:00
MarekSpodBiedry b585027de2 Chart legend: honour legendPos and overlay in the renderer (#262) 2026-10-13 04:20:52 -04:00
Brandon Thomas a68da9f51d
Effects: fill overlays (a:fillOverlay) blend over the shape's fill, and round-trip (#342)
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-12 23:50:02 -04:00
Brandon Thomas 10d3333344
Render: text in a scaled group keeps its point size, as in PowerPoint (#340)
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-12 23:27:47 -04:00
Brandon Thomas bb6f0dc88a
Render: pictures cast their shadow from their pixels, as in PowerPoint (#338)
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-12 22:46:32 -04:00
NetterSilver dc8a884389 Render: a group's child-coordinate scale no longer scales line widths (#233)
Outlines, dashes, arrowheads and ink strokes were drawn under the full
transform, including the mapping of the group's child coordinate space onto its
box. A group whose children use small legacy child coordinates (scale ~1600)
therefore stroked 0.75 pt lines about 1200 pt wide and covered the slide. Inside
a group the outline is now carried through that mapping and stroked under the
slide-level view, so the line width, dash lengths and arrowhead sizes stay in
points, also for groups scaled unevenly.
2026-10-12 22:44:18 -04:00
Brandon Thomas e28851d2f1
Pictures: WMF/EMF text, pattern and hatch brushes draw; ActiveX previews show (#336)
* ActiveX controls show their pictures

A slide's p:controls are read: each control with a p:pic (the image PowerPoint shows for it,
often a WMF) becomes a picture above the slide's shapes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Metafiles draw their text, pattern and hatch brushes

deckcraft-metafile (still kurbo + thiserror only, so it builds for wasm):
- Text: WMF EXTTEXTOUT/TEXTOUT and EMF EXTTEXTOUTW/EXTTEXTOUTA/SMALLTEXTOUT with the
  selected font (CREATEFONTINDIRECT, EXTCREATEFONTINDIRECTW: face, signed LOGFONT height,
  width, weight, italic, underline, strike-out, escapement, charset), text and background
  colour, background mode, TA_* alignment and TA_UPDATECP, the dx array (ETO_PDY pairs too),
  ETO_OPAQUE fills and ETO_CLIPPED clips. 8-bit strings decode through their charset
  (Windows-1252/1250/1251; symbol fonts keep their codes at U+F000; double-byte sets keep
  their advances). The neutral Item::Text carries a text-space-to-picture transform, so the
  mapping and escapement are folded in and glyphs are never mirrored.
- Brushes: WMF DIBCREATEPATTERNBRUSH, EMF CREATEDIBPATTERNBRUSHPT and CREATEMONOBRUSH, and
  BS_HATCHED, as Item::Pattern tiles in 96-dpi pixels; WMF PATBLT. The XOR / AND-pattern /
  XOR see-through shadow of old PowerPoint WMFs draws its pattern in the XOR colour.
- MFCOMMENT escapes with an embedded EMF are ignored: PowerPoint draws the WMF's records.
- Pattern pixels and text count against the byte budget; strings are capped.

deckcraft-render draws text through deckcraft-fonts with the face slide text would use
(document_face), symbol fonts mapped as bullets map them (deckcraft_text::symbol_char, now
public, with Webdings), per-character fallback, the file's advances, GDI cell metrics for
top/bottom alignment, underline/strike-out and synthetic bold/italic. Tiles below 1.5 output
pixels per cell are drawn as their average colour.

On the 24 corpus slides that show a metafile, mean abs diff against PowerPoint goes from
28.31 to 5.06 (with the OLE/ActiveX preview commits).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-12 22:40:56 -04:00
Brandon Thomas f94f3c12d1
Canvas: objects off the slide show and can be grabbed around it, as in PowerPoint (#270) (#329)
PowerPoint's Normal view draws shapes, pictures and text that sit partly
or wholly off the slide on the area around it, at full strength, with
their effects, master and layout graphics included; the slide show,
exports and thumbnails clip to the slide. DeckCraft clipped everything to
the slide in the editor too.

- render: `slide_extent`/`layout_extent` estimate how far a slide's
  drawing reaches (boxes, outlines, shadow, glow, reflection);
  `render_slide_drawn` also reports what a render actually drew (text
  spilling out of its box, table rows that grew); `pasteboard_region`
  picks the pixels the editor renders: the slide plus what reaches off
  it and is in view, rounded to 256 px tiles so scrolling rarely
  re-renders. A slide with nothing off it renders exactly as before.
- ui-egui: the canvas texture covers that region (slide and master
  views); it re-renders only when the slide, zoom or the region changes,
  and a second time only if text drew beyond the estimate.
- Hit testing, selection, marquee and moves already worked in slide
  space; tests now cover them off the slide.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-12 21:47:05 -04:00
Brandon Thomas 75bfd1da3e
Embedded objects draw their preview picture, including old VML previews (#328)
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-12 21:40:45 -04:00
Brandon Thomas 8159db8ba5
Render tests: the arch top bound allows Linux's fallback font (#323)
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-12 19:06:46 -04:00
Brandon Thomas f8d54d7af4
Pictures: WMF and EMF draw, with the metafile reader ported from WordCraft (#317)
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-12 18:27:19 -04:00
Brandon Thomas dedb20890b
Text: WordArt transforms (prstTxWarp) draw, and their adjusts round-trip (#312)
* Text warps: draw WordArt prstTxWarp presets, round-trip their adjusts

All 40 DrawingML text-warp presets now render instead of drawing flat text.
A warp is a list of bands over the shape's text rectangle (insets ignored):
fill bands stretch the ink of their lines between a top and a bottom guide
(positions by arc length, one horizontal scale for the whole text), follow
bands (arch, circle, button) set lines at natural size along an ellipse arc,
lines stacking outwards on parallel curves, with no wrapping. Lines are
shared over multi-band presets in order (ceil(n/k) per band).

The guide curves were fitted to PowerPoint renders of synthetic decks of
"I" bars (black-box observation, defaults and changed adjusts, 1-3 lines);
most presets match within about a point on a 420 x 200 pt box.

- geom: warp module (guides, presets, band mapping) with unit tests
- render: glyph outlines and underlines flattened, subdivided and mapped
- text: follow warps lay out without wrapping
- model/pptx: BodyProps.warp_adj read from and written to prstTxWarp/avLst

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Text warps: bands with fewer lines keep the fuller band's text height

Multi-band fill warps (Deflate-Inflate, Button Pour) map a box as tall as
the tallest band's ink onto every band, centred on the band's own ink, as
PowerPoint does: a band holding one line next to a band of two keeps the
line at the same size instead of stretching it over the whole band.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-12 18:12:58 -04:00
Brandon Thomas a520dd4d1d
Render: shadows, inner shadows and glows spread like PowerPoint's (#305)
* Render: shadows, inner shadows and glows spread like PowerPoint's

Measured on PowerPoint's export of probe shapes:
- outer and inner shadows are blurred by a third of the blur radius (was
  half), so their profile matches pixel for pixel;
- an inner shadow falls inside along the edges its direction points to (45
  degrees: bottom and right); DeckCraft put it on the opposite edges;
- a glow of radius r is half strength about r/2 out and gone by r (was a
  wider, softer halo).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Render tests: probe the outer shadow where the narrower blur still reaches

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-12 18:09:46 -04:00
Brandon Thomas 1679836540
Tables: the table's own fill (a:tblPr fill) is drawn behind its cells (#294)
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-12 18:05:12 -04:00
Brandon Thomas d8407c11f9
Tables: styles the file defines are read, drawn and kept (#292)
* Tables: styles the file defines are read, drawn and kept

PowerPoint writes the definition of every table style a deck uses into
ppt/tableStyles.xml. DeckCraft mapped a handful of GUIDs to its own looks
and drew any other style as Medium Style 2, so themed and light styles came
out in the wrong colours, and dropped the definitions on save.

The reader now keeps each definition (whole table, bands, first/last
rows and columns, corner cells, table background; fills, theme fill and
line references, borders, text colour and bold) on the presentation, and
tables using one keep its GUID. The renderer applies the parts that cover
each cell in the spec's order, a part's edge lines on its region's edges
and its inside lines within it, and paints fills as real fills (gradients
and see-through colours). The writer puts the definitions back as read.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Tables: DeckCraft's own table styles still map back to their ids

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Fix a brace lost in the rebase

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-12 18:03:19 -04:00
Brandon Thomas 880b974cff
Render: soft edges fade to the shape's outline (#289)
The soft-edge mask was the bounding box shrunk and blurred, so ovals and
other non-rectangular shapes kept hard edges where the box was wider, and
a rectangle's fade began outside it. The mask is now the outline itself,
shrunk by 0.85 r (a stroke punched out of the fill) and blurred by r/3:
transparent at the outline, half opaque 0.85 r inside, as measured on
PowerPoint's export of a 12 pt soft edge.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-12 18:00:38 -04:00