Commit Graph

41 Commits

Author SHA1 Message Date
Brandon Thomas 061e2c8ad5 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:14:08 -04:00
Brandon Thomas 8dafbcb8c2 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:14:08 -04:00
Brandon Thomas ac8c40c2eb 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:14:08 -04:00
Brandon Thomas aee9cf8463 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:14:08 -04:00
Brandon Thomas 1bff83f377 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:14:08 -04:00
Brandon Thomas 5bd0ae87de 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:14:08 -04:00
Brandon Thomas e56823d3ec 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:14:08 -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 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 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 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 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
Brandon Thomas b3e33928e2
Render: rectangle and outline path gradients (#288)
Path gradients that follow a rectangle (path="rect") or the shape's own
outline (path="shape") were drawn as circles. They're now rasterised into
a small image of the bounds: rect takes the larger of the horizontal and
vertical shares of the distance from the focus to the edge (X-shaped
contours), shape the share of the way from the focus to the outline along
the same ray (an ellipse gives ellipses, a triangle triangles), as
measured on PowerPoint's export. The two path probe decks now match it to
a mean pixel difference of 1.7 and 0.6 (from 15.6 and 22).

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-12 17:59:32 -04:00
Brandon Thomas d0a3d924bf
Render: compound outlines (double, thick-thin, thin-thick, triple) (#287)
a:ln/@cmpd was read but every outline was drawn as one band. Each band is
now stroked along the path moved to its place across the width, in the
shares PowerPoint uses (measured on its export of 24 pt lines: double
1/3-1/3-1/3, thin-thick 1/5-1/5-3/5, triple 1/6-1/6-1/3-1/6-1/6), the
first band on the left of travel (the outside of a shape). Line corners
meet at their miter points; curves are offset with kurbo.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-12 17:58:10 -04:00
Brandon Thomas 81b6a9df39
Render: pattern fills draw PowerPoint's preset tiles (#285)
Pattern fills were approximated (dots, bars or a grid per family, on an
8-point cell), so pct30 looked like sparse dots and wave like a dot grid.
Every one of the 54 DrawingML presets now draws its 8x8 tile, each cell one
96-dpi pixel (0.75 pt), laid out from the slide's origin. The tiles were
read from PowerPoint's own export of each preset at two output pixels per
cell; the pattern probe deck now matches it to a mean pixel difference of
0.2 (from 62).

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-12 17:56:30 -04:00
Brandon Thomas 2bf62c97e2
Text: shadows and glows are drawn (#283)
Run shadows and glows (a:rPr/a:effectLst outerShdw, glow) were read but
never drawn. The text layout now carries them on each glyph run (colours
resolved, sizes scaled with autofit) and the renderer draws every run's
shadow, then every glow, blurred, under the text. A text glow is solid to
about a third of its radius past the letters and fades out by two thirds,
as measured on PowerPoint's export. needs_filters also looks at text
effects (runs, list styles, master text styles), so they render with the
blur filter.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-12 17:53:17 -04:00
Brandon Thomas 1bb86db54a
Text: gradient fills draw, starting over on every line (#281)
A gradient on text was read but drawn in its first colour. Each line is now
filled with the whole gradient, spanning that line's text from its top to
its bottom and its first to its last character, as PowerPoint does. The
gradient builder moves out of fill_path into paint::gradient so fills and
text share it (and PowerPoint's two-colour ramp). Gradient colours are
resolved when the text style is made.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-12 17:51:20 -04:00
Brandon Thomas ba56742fa9
Emoji: drawn in colour, with families, skin tones and flags joined (#278)
* Emoji: colour glyphs, and emoji take the colour emoji font

Emoji were drawn as black outlines from a symbol font, with ZWJ sequences
(families, flags) falling apart into their parts. Now:

- an emoji cluster (emoji presentation, U+FE0F, ZWJ between pictographs,
  skin tones, regional indicators, keycaps) takes an installed colour font
  (COLR, CBDT, sbix or SVG tables) before the document's fonts, so its
  sequences ligate as in PowerPoint; U+FE0E keeps a plain face;
- COLR v0 and v1 glyphs are painted in their CPAL colours (solid fills,
  linear, radial and sweep gradients, transforms, composite modes), with
  palette index 0xFFFF in the text colour.

A tiny COLRv0 test font (two squares, CC0, regenerated by
make_colr_font.py) backs the render test.

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

* Render: lock skrifa for colour glyphs

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 17:48:51 -04:00
Brandon Thomas 39a8234633
Render: slide backgrounds are laid over white (#275)
PowerPoint shows white around a background picture stretched into part of
the slide and under a see-through background colour. DeckCraft left those
areas transparent, which exports and the slide show draw black.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-12 17:45:31 -04:00
Brandon Thomas 98ea8d3195
Render: shapes filled with the background (useBgFill) show it (#274)
A shape with useBgFill="1" is a window onto the slide's background, laid
out on the slide. DeckCraft drew nothing in it, so a master that covers the
background with a white panel and cuts a background-filled frame into it
(tdf93868) rendered all white.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-12 17:44:20 -04:00
Brandon Thomas ceb6e3b990
Render: two-colour gradients ramp like PowerPoint's (#273)
PowerPoint blends a gradient of exactly two stops at 0% and 100% in linear
light, with the second colour's share following an S-shaped curve (black to
white is 105/186/238 at a quarter/half/three quarters, not 64/128/191).
Gradients with more stops, or a stop moved off an end, stay linear in sRGB.
Measured with PowerPoint's slide export on shapes and backgrounds, in both
directions. The two-colour case is expanded into sixteen pieces per
segment so vello's sRGB interpolation draws the same ramp.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-12 17:35:59 -04:00
Brandon Thomas 4d13652f0f
Pictures: duotone recolours, and layout/slide colour map overrides apply (#269)
Theme background pictures (Office 2007-era themes such as Treck) carry an
a:duotone recolour in phClr; DeckCraft read it but drew the picture
unchanged, so those backgrounds came out grey. The painter now resolves the
duotone's colours against the slide's colours and placeholder colour, and
the decoded-image cache applies them (luminance picks a colour between dark
and light) and keys on them.

Layouts and slides can replace the master's colour map
(p:clrMapOvr/a:overrideClrMapping, e.g. dark section layouts mapping bg1 to
dk1). They were read as the master's map; now Layout and Slide carry an
optional colour map, resolve::Ctx uses slide > layout > master, and the
writer keeps them.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-12 17:35:52 -04:00
LloydNicholson d556ff98b5
Fields: date fields show today's date in their own format (#36)
Every datetime field showed the deck-wide fixed date text instead of a date: empty when no slide
had a fixed date, and that text on every slide when one did. A fixed date is plain text in its
slide's placeholder; a field is an automatic date, so it now shows the local date and time in the
format of its type (ECMA-376 datetime1 to datetime13 as PowerPoint lists them for English,
datetime and datetimeFigureOut the short date). Numeric dates follow the run's language
(en-GB 09/10/2026, de-DE 09.10.2026...); formats with names stay English-only, so other languages
keep the field's saved text, as do unknown types and builds without a clock (wasm). The saved
text is kept on save.

- text: datetime module (field types, formats, local clock via chrono, not on wasm);
  Fields::field_in passes the run's language.
- render: RenderOpts::now injects the clock (tests, deterministic output); the PDF text layer
  uses the same fields.
- engine: Header & Footer turns existing date placeholders into fixed text or back into a
  field; new date fields are saved with the date they show (local, not UTC).

Co-authored-by: lloyd <lloyd@stackworx.io>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-10 06:25:32 -04:00
Dmytro Smaha 3985d768e6
feat(pictures): animated GIFs play, with pause (media.gifPlay) (#39)
* feat(pictures): animated GIFs play, with pause (media.gifPlay)

GIF pictures used to show only their first frame. The renderer now decodes every frame (cached, capped at 256 MB and 1024 px a side) and draws the frame for each GIF's play time, passed in RenderOpts::gif_times; exports and thumbnails pass none and keep frame 1. The editor canvas and the slide show redraw only when a GIF reaches its next frame. A corner button on hover (or selection) pauses and resumes a GIF from the same frame, through the new media.gifPlay command.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(pictures): animated GIFs play, with pause (media.gifPlay)

GIF pictures used to show only their first frame. The renderer now decodes every frame (cached, capped at 256 MB and 1024 px a side) and draws the frame for each GIF's play time, passed in RenderOpts::gif_times; exports and thumbnails pass none and keep frame 1. The editor canvas and the slide show redraw only when a GIF reaches its next frame. A corner button on hover (or selection) pauses and resumes a GIF from the same frame, through the new media.gifPlay command.

Also adds my entry to contributors/people.toml.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Animated GIFs: cap frames (1000), canvas (4096²), per-GIF (64 MB) and cache (192 MB) memory

A few MB of hostile GIF could decode to millions of tiny frames or a 100 Mpx canvas per frame,
and eight cached GIFs could hold 2 GB.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-10 06:21:29 -04:00
alexanderalber 788831834b
Morph: a changed geometry morphs its outline (a circle turns into a star) (#27)
* Morph: a changed geometry morphs its outline (a circle turns into a star)

Paired shapes that differ only in geometry (another preset, other adjust
values, a custom path) and fill colour no longer cross-fade: one shape moves
and its outline turns from the old geometry into the new one.

- geom::morph aligns two outlines the way manim does: the same number of
  contours, the same number of cubic curves per contour pair, then every
  point moves in a straight line. Closed contours are turned and reversed so
  the points travel least (a triangle does not twist into a circle), extra
  points go to the longest curves, and a sub-path without partner grows out
  of (or shrinks into) a point, so a cube's faces appear and a donut's hole
  closes. Fill mode, outline and fill rule switch at the midpoint.
- anim::morph_frame lists such shapes in MorphFrame::paths; the renderer
  draws them with the morphed geometry (PathMorph, a new render_blend
  argument next to TextMorph).

Ported from the path morph in alexplain (MIT, same author).

generated with Claude Code

* Morph: differing looks of an outline cross-fade instead of switching

When a sub-path pair differs in fill mode, outline or fill rule, the two
looks no longer switch at the midpoint. A fill or outline only one side has
fades out (or in), so a filled shape turning into a line loses its fill
gradually. When both fill, the even-odd side lies opaque underneath and the
other fades over it, so a donut's hole fills smoothly while it shrinks.

generated with Claude Code

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-10 06:19:32 -04:00
LloydNicholson ddbd71e297
Shapes: flipped shapes keep their text readable (#35)
Flips mirror a shape's geometry, never its text. The text was drawn with the shape's full
transform, so every label on a flipped arrow came out mirrored (flipH) or mirrored upside down
(flipV). As in PowerPoint, the text rect keeps its flipped place and the text is mirrored back
about the rect's centre: flipH text stays upright, flipV (or both flips) turns it 180°, rotation
still applies, and the same holds for shapes inside flipped groups.

render::text_transform is that transform (plus vertical-text rotation). Drawing, the PDF text
layer and link boxes, morph text, the editing caret and selection, and click-to-place-the-caret
all use it, so a click lands on the character drawn under it.

Co-authored-by: lloyd <lloyd@stackworx.io>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-10 06:18:46 -04:00
MarekSpodBiedry 57545a99c8
Tables: rows grow to fit their text, cell text in the PDF text layer (#13) (#14)
* Tables: rows grow to fit their text, cell text in the PDF text layer (#13)

A stored row height (a:tr/@h) is a minimum, as in PowerPoint: each row grows so its cells' text fits. Tables from pandoc or python-pptx store h="0" and drew every row on top of each other. Cell editing (click to edit, cell outline) uses the same heights, and the PDF text layer now includes table cell text.

* Tables: a grown table's box covers its drawn rows; resize scales from drawn heights

Handles, hit tests and marquee used the stored frame, so rows grown to fit their text drew
below the selection box (a file with h=0 rows had a 0 pt tall box). Resizing divided by the
stored row total, which is ~0 for those files.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 10:40:13 -04:00
alexanderalber fa9a94a1c0
Morph: shapes move, resize and blend; Words and Characters options morph text (#5)
* Morph: shapes move, resize and blend in the slide show

Morph used to fall back to a plain crossfade in the show: morph_pairs and
morph_xfrm existed, but nothing rendered frames with them.

- anim: morph_frame builds a frame from two slides. Paired shapes move along
  morph_xfrm (eased); a pure fill colour change blends, other content changes
  cross-fade on the same moving box. Unpaired shapes fade out/in, shapes with
  entrance animations or hidden media stay out of the frame.
- render: render_blend draws such a frame in one pass: both backdrops
  (background, master and layout graphics) cross-fade, every shape resolves
  against the slide it comes from, so placeholders keep their own layout.
  Renderer::slide is split into canvas/fields/backdrop to share the code.
- ui-egui: the show renders morph frames with these, crossfade otherwise.

generated with Claude Code

* Contributors: add my own entry (alexanderalber)

generated with Claude Code

* Morph: Words and Characters options morph text token by token

The transition option (objects/words/characters) was stored and round-tripped
but ignored by the show. With Words or Characters, paired shapes whose text
differs now morph their text instead of cross-fading it:

- text: GlyphRun records per glyph the paragraph character it draws, so
  glyphs can be grouped into words or characters.
- anim: morph_frame reads the option and lists such pairs in MorphFrame::text.
- render: morph_text draws them. Shared words (or characters) travel from their
  place in the old text to their place in the new one and grow or shrink to the
  new size; a different look cross-fades on the way. The remaining tokens fade
  while following a scale and shift fitted to the shared ones, so a line moves
  and rearranges as one. Rotated text falls back to a cross-fade.
  draw_layout_with's glyph drawing moved into draw_glyph for reuse.
- ui-egui: the show passes the pairs to render_blend.

generated with Claude Code

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-09 10:32:41 -04:00
Brandon Thomas ae9d525bce Rename SlideCraft to DeckCraft: crates, apps, binaries, app id, file extension (.deckcraft; .slidecraft files still open), packaging, docs; ROADMAP alpha and parity estimates
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 01:46:40 -04:00
Brandon Thomas 639957c68a PDF export: slides, notes pages and handouts with selectable text layer, links and bookmarks; Export dialog PDF options
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 00:39:54 -04:00
Brandon Thomas 55a5752d25 Slide show: by-paragraph text builds render per paragraph, sub-bullets follow their parent
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 00:14:59 -04:00
Brandon Thomas e62b7ccd15 M0: PowerPoint-style egui UI, slide show engine, icons, desktop app
- ui-egui: title bar, ribbon with every tab (Home, Insert, Draw, Design, Transitions,
  Animations, Slide Show, Record, Review, View) plus contextual Shape/Picture Format,
  Table Design/Layout, Chart Design, Playback and Slide Master tabs; thumbnail pane with
  sections and drag reorder; canvas with selection handles, smart guides, marquee,
  in-place text editing; notes pane; status bar with views and zoom; task panes
  (Format Shape, Background, Animation, Selection, Comments, Design Ideas); dialogs;
  Slide Sorter, Notes Page, Outline view; command palette; control channel
- anim: timelines for every animation effect, transition layers for every transition,
  morph pairing, show navigation (hidden slides, custom shows, loops)
- show: full-screen slide show with transitions, animations, pen, black/white
  screens, presenter view window, rehearse timings, reading view
- icons: 238 original icons drawn in code
- render: filters force single-threaded rendering (regression test), panic guard
- engine: chart type/data/options, SmartArt-style graphics
- apps/slidecraft: desktop host with control server, file dialogs, clipboard images
- placeholder app icon rendered by SlideCraft itself

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 00:08:13 -04:00
Brandon Thomas 6078b68e97 M0: workspace, model, geometry, fonts, text, renderer, engine, MCP and CLI
Clean-room PowerPoint-class presentation engine in pure Rust:
- geom: units, transforms and 150+ re-derived preset shapes with adjust handles
- color: theme slots and colour transforms
- model: presentations, masters, 11 layouts, placeholder inheritance, themes,
  text, tables, charts, transitions, animations, text edit operations
- fonts: font DB with craft-fonts, system fonts and open substitutes
- text: shaping, line breaking, bullets/numbering, alignment, autofit, caret hit tests
- render: vello_cpu slides with fills, gradients, pictures, effects, tables, charts
- format: native .slidecraft (zip + JSON)
- engine: ~200 commands, undo/redo, clipboard, pointer tools, keys, sample deck
- mcp: MCP server (headless or bridged to the app); slidecraft-cli

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 23:27:20 -04:00