The model, renderer and PPTX already carried cell borders, diagonals,
margins and vertical alignment, but nothing set them. New commands:
table.borders (all, outside, inside, inside horizontal/vertical, each
side, diagonals or none, with pen colour, width and dash; the facing
side of a neighbouring cell follows, as in PowerPoint), table.cellMargins
(Normal, None, Narrow, Wide or per side) and table.cellAlign. They act on
the given cells, the selected range, the cell with the caret, or the
whole table. Table Design gains a Borders menu with pen weight, style and
colour; Table Layout's Top/Center/Bottom now align cell text (they set
the text body's anchor, which cells override) and gain Cell Margins.
Co-authored-by: Cursor <cursoragent@cursor.com>
Draw > Ink to Shape recognises each pen stroke on the slide's ink layer
as a line, rectangle, oval or isosceles triangle (apex up or down) and
replaces it with that preset shape, outlined in the stroke's colour and
width with no fill. Strokes it can't recognise, and highlighter strokes,
stay ink. The ink shape is removed once it has no strokes left. The
whole conversion is one undo step.
The recogniser (engine/src/inkshape.rs) is pure geometry: ends meeting,
area covered within the bounding box, and spread from the fitted
ellipse. Ribbon big buttons now carry an accessibility label.
Co-authored-by: Cursor <cursoragent@cursor.com>
These Office 2016 charts were drawn as plain columns. Now:
- waterfall: bars float from the running total, total points
(cx:subtotals, now read into Chart.subtotals) stand on the axis and
reset it, increases/decreases/totals take accents 1-3 with a matching
legend, and connectors join each bar to the next;
- funnel: centred bars as wide as their value, category labels on the
left and values inside;
- treemap: squarified tiles in proportion to their value, labelled;
- sunburst: the innermost level as a ring with an open centre.
Co-authored-by: Cursor <cursoragent@cursor.com>
View › Grid Options sets the grid spacing, snap to grid, snap to
other objects, and whether gridlines and guides are drawn. The
canvas grid follows that spacing.
Co-authored-by: Cursor <cursoragent@cursor.com>
Decks from SharePoint and Microsoft 365 carry customXml items (document
library metadata and content types), docMetadata/LabelInfo.xml (the
Microsoft Purview sensitivity label) and docProps/custom.xml. All were
dropped on save, so a labelled corporate deck came back unlabelled. They
are now kept verbatim with their content types, their own relationships
(itemProps) and the root or presentation relationship linking them. Kept
parts never replace a part the writer makes.
Co-authored-by: Cursor <cursoragent@cursor.com>
Waterfall, funnel, treemap, sunburst, histogram, Pareto and box &
whisker charts live in a chartEx part behind an mc:AlternateContent
choice requiring cx1. DeckCraft took the fallback, usually a text box
saying the chart isn't available, so these charts were lost on open
and on save.
The cx1 choice is now taken for chartEx frames and the part is read
into the chart model: categories (innermost level), values, number
format, series name, title, legend and data labels. Unchanged charts
are written back as their chartEx part in a cx1 choice with a plain
chart of the same data as fallback, as PowerPoint writes them; an
edited one is saved as a plain chart. Region maps are not read.
Co-authored-by: Cursor <cursoragent@cursor.com>
An owned corpus of those deck kinds survives open, save and reopen. The
check found the handout master — printed header, footer, date and slide
number — was dropped on save, so import and export now keep that part.
p:custDataLst points at ppt/tags/tagN.xml parts that add-ins such as
think-cell, Mekko Graphics and Office Timeline use to recognise their
decks, slides and shapes. They were dropped on open, so a saved deck lost
its add-in identity. The model now carries name/value tags on the
presentation, slides and shapes; the reader resolves every tags part and
the writer emits one tags part per holder, in the schema's element order.
Co-authored-by: Cursor <cursoragent@cursor.com>
deckcraft_pptx::import_theme reads the theme of a .thmx (its main document is
a theme part) or of a presentation's first master, with the media its format
scheme uses. design.browseTheme {path | name, data} applies it to the current
master as one undo step, giving the theme's pictures ids of this deck.
Design tab: Browse for Themes… next to the theme gallery and in the Slide
Master Themes menu; the desktop picker offers Office Theme (.thmx) and
presentations. Ribbon small buttons expose their label to accessibility (same
change as #512). Parity row Browse themes M → D.
Co-authored-by: Cursor <cursoragent@cursor.com>
review.compare {path | name, data} reads a revised presentation (native or
PPTX) and lists slide-level revisions against the open one: inserted,
deleted and changed slides, with what changed (text, text formatting,
moved, resized, rotated, formatting, picture, objects inserted/deleted,
layout, notes, transition, animations, background, hidden). Slides match by
id, or by position when the decks share none (fixed for the session so
accepting doesn't switch the matching half way). Shapes match by id, then
name.
review.accept {index | all} applies revisions as one undo step: a revised
slide's media is copied (remapped on an id clash), its shape ids kept unless
another slide uses them (animations follow), its layout matched by id, then
name. review.revisions and review.endCompare complete the set.
Review tab: Compare (file picker), Accept All, End Review; the Revisions pane
lists changes with Go-to and Accept. Parity row: Compare presentations M → P.
Co-authored-by: Cursor <cursoragent@cursor.com>
The editing canvas, thumbnails and layout previews were drawn between
physical pixels, so a linear filter blurred small text. The slide show
draws much larger glyphs, which is why presenting still looked sharp (#184).
Co-authored-by: Cursor <cursoragent@cursor.com>
AutoCorrect gains PowerPoint's options: correct TWo INitial CApitals,
capitalize the first letter of sentences and the names of days, fix
accidental cAPS LOCK, and replace 1/2, 1/4, 3/4 with fraction
characters. "word..." becomes "word…", "word," and "word." are corrected
too, and the built-in list grows from 13 to ~70 entries. The case rules
apply only to typing, not pasted text.
New text.autocorrect sets the options and adds or deletes replace
entries (your own win over the built-in list; deleted built-ins stay
deleted) and returns the list. Preferences ▸ AutoCorrect Options… opens
an AutoCorrect dialog with the options and the editable list.
Co-authored-by: Cursor <cursoragent@cursor.com>
Insert › Links gains Zoom, with PowerPoint's three kinds: Slide Zoom
(pictures of chosen slides on the current slide), Section Zoom (one per
chosen section) and Summary Zoom (a new Title Only slide in front of
the chosen parts). Each zoom is a picture of its target that jumps to
it in the show. With Return to Zoom, on by default for section and
summary zooms as in PowerPoint, the show comes back to the zoom slide,
fully built, after the target slide or the section's last slide; going
elsewhere (a slide number, See All Slides) forgets it. Hyperlink gains
zoom_return for this; .pptx saves zooms as the linked pictures
PowerPoint writes as their fallback, so the return flag is DeckCraft-
only for now. Live thumbnails, the zoom-in animation and the native
p166:sldZm element are still missing; gaps.md says so.
Co-authored-by: Cursor <cursoragent@cursor.com>
A selected text warp shows one yellow handle per adjust.
Dragging it sets that guide value in one undo step.
Co-authored-by: Cursor <cursoragent@cursor.com>
New picture.artisticEffect applies one of 7 effects (blur, pencil
grayscale, line drawing, film grain, cutout, glow edges, photocopy) to a
picture's pixels, as PowerPoint does: the processed PNG becomes the
picture and the original is kept on PictureFill::artistic, so another
effect starts from the original and `none` (or Reset Picture) restores
it. Change Picture drops the effect.
PPTX: written and read as PowerPoint does, the blip being the processed
picture with the original and the effect name in
a:extLst/a14:imgProps/a14:imgLayer, so a deck from PowerPoint keeps its
effect, original and all, when saved again.
Picture Format gets an Artistic Effects menu.
Co-authored-by: Cursor <cursoragent@cursor.com>
Open, save and reopen decks we author, and compare package parts and
text. The check showed a notes master's header and footer were replaced
by an empty notes master on save.
Co-authored-by: Cursor <cursoragent@cursor.com>
Follow-up to the transition sound picker. The Sound box now lists
[Stop Previous Sound] and six built-in sounds (Chime, Click, Camera,
Whoosh, Drum Roll, Laser) that are synthesised as small WAVs when
chosen, so no sound files ship with the app; the same built-in sound is
embedded once however many slides use it. Loop Until Next Sound repeats
a sound in the show until another slide's sound or Stop Previous Sound.
PPTX reads and writes p:stSnd loop="1" and p:endSnd, keeps the sound's
file name in p:snd/@name, and setting a slide's transition to None no
longer drops its sound.
Co-authored-by: Cursor <cursoragent@cursor.com>
shape.pointType makes a vertex smooth (handles in line and equal),
straight (in line) or corner (independent), turning its segments into
curves so it has handles. shape.moveHandle drags a control handle; the
other one follows as the point's type says (given, or read from the
handles). shape.points also reports each vertex's type.
Canvas: clicking a vertex shows its handles, which drag (one undo step
per drag) and follow the type chosen for it. Right-click a vertex for
Smooth / Straight / Corner Point and Delete Point, or anywhere for Exit
Edit Points; a shape's context menu gets Edit Points. The selection box
is hidden while points are edited.
Co-authored-by: Cursor <cursoragent@cursor.com>
New cmd::points with shape.editPoints (enter/leave; a preset becomes a
freeform in shape-local points), shape.points (vertices in slide points),
shape.movePoint, shape.addPoint (splits a line, quadratic or cubic
segment) and shape.deletePoint. After each change the box is fitted
around the outline and kept in place on the slide, rotation and flips
included. Closing edges are made explicit so they take points too.
On the canvas the edited shape shows its vertices as handles: drag one
(one undo step per drag), Cmd/Ctrl-click a vertex to delete it or the
outline to add one; Escape, Enter or a click elsewhere leaves Edit
Points. Shape Format ▸ Edit Shape gets an Edit Points item.
Not yet: smooth/straight/corner point types and Bézier control handles.
Co-authored-by: Cursor <cursoragent@cursor.com>
Picture Format › Adjust gains Compress Pictures, with PowerPoint's
options: HD 330, Print 220, Web 150 or E-mail 96 ppi, apply to the
selected pictures or all, and delete cropped areas. The new
picture.compress command downsamples each PNG, JPEG, BMP, TIFF or WebP
picture to the pixels its shown size needs at that resolution (JPEG
stays JPEG at quality 85, the rest become PNG), cuts away cropped areas
when asked, and only replaces a picture when the result is smaller or a
crop was removed. Originals nothing else uses are dropped so the saved
file shrinks; one shared with a picture that wasn't compressed stays.
One undo step restores everything.
Co-authored-by: Cursor <cursoragent@cursor.com>
font.replace {from, to} renames a font family everywhere it is named
explicitly: runs (latin, east Asian, complex script and symbol faces), end
marks, bullet fonts, list styles of shapes, layouts and masters, the master
text styles, the deck default style, notes, tables and the theme fonts, so
placeholders that follow the theme change too. Names compare
case-insensitively, theme references (+mn-lt) are left alone, a replace that
finds nothing adds no undo step, and missing fonts are recomputed.
font.used lists the families the slides draw in, for the dialog's Replace
list.
Home gets a Replace Fonts… button opening the dialog: Replace (fonts in the
deck), With (type or pick from installed fonts), and it stays open with a
confirmation like PowerPoint's. Ribbon small buttons now expose their label
to accessibility, which the UI test uses to click it.
Co-authored-by: Cursor <cursoragent@cursor.com>
Home › New Slide gains Reuse Slides… and Reuse Slides (Keep Source
Formatting)…, backed by a new slide.reuse command that inserts the
slides of a .pptx or .deckcraft file (all, a list, or a range) after the
current slide in one undo step. By default each slide moves to the
destination layout with the same name or kind, so it takes this deck's
theme; keepFormatting brings the source master and its layouts along.
Pictures, media and sounds are copied under fresh media ids, so ids
that clash between the two decks no longer mix up pictures, and shape
ids are renumbered with animations, triggers and connector ends
following them.
Co-authored-by: Cursor <cursoragent@cursor.com>
shape.effects gains `threeD`: top and bottom bevel (preset, width,
height), depth, contour, material, lighting, camera preset, X/Y/Z
rotation, distance from ground, and reset. The settings are read from and
written back to the shape's kept a:scene3d / a:sp3d XML (new
cmd::three_d), so they render through the existing camera and bevel code
and survive a PPTX save; extrusion and contour colours are kept as is.
The `bevel` key used to set only Effects::bevel, which neither the
renderer nor the PPTX writer reads, so a bevel set from the app vanished
on save; it now goes through the same path.
The Effects page of the Format Shape pane gets 3-D Format and 3-D
Rotation sections.
Co-authored-by: Cursor <cursoragent@cursor.com>
ui_soak_tests.rs drives the app through the egui test harness with seeded
random clicks, drags, keys (plain, Ctrl, Shift), typing and scrolling over the
sample deck. Every ten actions it checks that every document validates, the
selection is in range, undo, journal, thumbnail and texture caches stay
bounded, and no command or render panic was caught; at the end, that input
really reached the editor and the mean frame stays under 250 ms. Two seeds x
150 actions run in CI; DECKCRAFT_UI_SOAK_STEPS/SEED run it long.
Panics swallowed by the guards were invisible to tests: Session now counts
them (panics_caught) and deckcraft_render::panics_caught() counts render
panics drawn as blank. Textures::thumb_count exposes the thumbnail cache size.
Co-authored-by: Cursor <cursoragent@cursor.com>
shape.fill gains pictureProps for a picture fill: tile on/off, tile
offset X/Y (pt), scale X/Y, alignment and mirror type, stretch offsets
(left/top/right/bottom), and rotate with shape. `transparency` now also
sets a picture fill's opacity. Values are clamped and unknown keywords or
non-finite numbers ignored. The Fill page of the Format Shape pane gets a
"Picture or texture fill" option with a picture source button and these
controls. The model, renderer and PPTX reader/writer already carry the
fields; a new engine test checks they survive a PPTX save and reopen.
Co-authored-by: Cursor <cursoragent@cursor.com>
Transition sounds were read from and written to .pptx but could not be
set and never played. Transitions › Timing gets a Sound box ([No Sound]
or Other Sound…), backed by a new transition.sound command that embeds
an audio file on the selected slides (or removes it with none). In the
slide show the sound plays as its slide comes in and, as in PowerPoint,
keeps playing into the next slide unless that slide has a sound of its
own. Built-in sounds, Stop Previous Sound and Loop Until Next Sound are
still missing; the parity docs say so.
Co-authored-by: Cursor <cursoragent@cursor.com>
Shape Format ▸ Text Effects lists the 40 prstTxWarp presets and
No Transform. format.textWarp sets or clears that warp on the
selected text, with optional adjust values.
Co-authored-by: Cursor <cursoragent@cursor.com>
shape.effects gains shadowProps / glowProps / reflectionProps that adjust
the current effect (or add a default one): shadow colour, transparency,
blur, angle and distance; glow colour, transparency and size; reflection
transparency, size, distance and blur. Values are clamped, non-finite ones
ignored. The Effects page of the Format Shape pane exposes them as sliders
and colour pickers.
Co-authored-by: Cursor <cursoragent@cursor.com>
soak_tests.rs drives every non-file command with plausible and hostile
parameters over a ~100-slide deck, checking after each step that the
document validates, the selection is in range, the undo stack is bounded,
undo then redo restores the exact document, and every 100 steps that the
deck saves and reopens as .deckcraft and .pptx. Four seeds x 400 steps run
in CI (under a second); DECKCRAFT_SOAK_STEPS/SEED run it long.
The long soak found that a chart a few points wide panicked in f64::clamp
(label gutter min above max), and the render guard then blanked the whole
slide. chart.rs now uses a non-panicking clamp_soft for the gutters and the
zero line; a render test draws tiny charts next to a shape without the
guard.
Co-authored-by: Cursor <cursoragent@cursor.com>
The Format Shape pane's Line page had only colour, width and dash. It now
has PowerPoint's compound type, cap type, join type, begin/end arrow type
and begin/end arrow size drop-downs, and the Size page has Lock aspect
ratio for the width and height fields. shape.line gains compound,
headSize and tailSize, and changing an arrow's kind keeps its size.
Co-authored-by: Cursor <cursoragent@cursor.com>
The pane's bars now come from deckcraft_anim::Timeline, the same schedule the
slide show plays, so With/After Previous, delays, repeats and auto-reverse line
up on a shared seconds ruler. Dragging a bar sets the delay and dragging its
right edge sets the duration (snapped to 0.05 s), each through
animation.timing, so one undoable command per drag. Bar geometry and drag maths
live in animtimeline.rs as pure, unit-tested functions; kittest drives the
drags. Parity and gaps docs updated.
Co-authored-by: Cursor <cursoragent@cursor.com>
The loopback control server spawned a thread per connection, read
unbounded lines, queued requests without limit and set no socket
deadlines. It now caps simultaneous clients (16) and request lines
(16 MiB), uses a bounded request queue (64) that answers busy when full,
closes sockets idle for 10 minutes or blocked on a write for 30 seconds,
and frees a connection slot whenever a client thread ends.
Co-authored-by: Cursor <cursoragent@cursor.com>
Cmd/Ctrl+L and the on-screen button draw the show's laser colour at the pointer. The pen and the laser are not on together, and a click does not advance the slide while the laser is up.
Co-authored-by: Cursor <cursoragent@cursor.com>
Searching for Export ran file.export with an empty object and reported a missing path. The palette now offers only commands that accept that call, so Export opens the dialog.
Co-authored-by: Cursor <cursoragent@cursor.com>
A deck that embeds a TrueType or OpenType face, including one wrapped as Embedded OpenType, now draws with that face and saves it back. MicroType Express subsets stay in the file and still draw in a stand-in.
Co-authored-by: Cursor <cursoragent@cursor.com>
INTERSECTCLIPRECT, EXCLUDECLIPRECT and EXTSELECTCLIPRGN limit what later records paint, including a region made of several rectangles. A rotated map stores the bounds of the clip rectangle.
Co-authored-by: Cursor <cursoragent@cursor.com>
The writer emitted the legacy comment part only: replies were flattened
into sibling comments, the resolved flag was dropped. Modern PowerPoint
decks carry a second, threaded part (p188) as well - and DeckCraft read
both, importing every comment twice.
Export now writes the modern threaded part (p188:cmLst, with the slide's
commentRel anchor ext and the modern authors part) next to the flattened
legacy part, the same pair of parts PowerPoint itself writes: older
readers keep the legacy view, PowerPoint 2021+ and DeckCraft read
threads, replies and resolved state. The reader prefers the modern part
when both are present and scales modern positions as EMU instead of
legacy 1/8 pt, so real PowerPoint decks stop double-importing their
comments and keep their marker positions.
Comment ids are deterministic GUIDs from the existing tables::guid, so
exports stay reproducible without a new dependency.
Known limitation for a follow-up: comments anchor with
p188:unknownAnchor (schema-valid) rather than a slide moniker, which
needs part identity the model does not have yet.
Tests: threads plus resolved state round-trip on top-level comments and
replies, both parts and the anchor-rel wiring asserted in the raw
package, part numbering with comment-free slides skipped, legacy-only
and modern-only packages import each with their own position units, and
XML-special characters survive the trip.
Progress log entry for the wild-case rendering fixes; "Bevel and 3-D
rotation" moves from missing to partial (approximate bevels, cameras and
face lighting; no extrusion, rigs or 3-D pane); gap estimate narrowed.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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>
- ui-parity.md: title bar row; status line and history.
- ROADMAP.md: progress log line. Quick Access toolbar / title bar commands stays P (no
customization); no gaps.md entry covered this.
- ui-parity.md: context menu row; status line and history.
- ROADMAP.md: progress log line. No checklist row changes score; no gaps.md entry covered this.
A right click on an unselected shape opened the slide's context menu
(Layout, Reset Slide...) because only the primary button reached the
pointer tool; the shape had to be left-clicked first. The right-button
press now goes through Session::pointer as PointerKind::ContextClick
(`contextclick` on the control channel and MCP) and selects like
PowerPoint before the menu opens: an unselected shape is selected alone
(a text box or placeholder is not put into text editing, nothing is
dragged), a selected shape keeps the selection, multiple selection
included, and empty slide clears it. In the box whose text is being
edited, the caret and text selection stay, and the context menu offers
text items (Paragraph, Bullets, Numbering, Link) next to Cut, Copy and
Paste, which act on the selected text.
- ui-parity.md: Direct manipulation row added; status line and history.
- ROADMAP.md: progress log line. The checklist rows involved (Text boxes, In-place text editing)
are already D; no gaps.md entry covered this.
- file-format-parity.md: SmartArt row notes the text rectangle; status line and history.
- ROADMAP.md: progress log line. No checklist row changes score (Insert SmartArt stays P), and
the gaps.md SmartArt entry (pre-drawn shapes only) still holds.
PowerPoint stores chart data in ppt/embeddings. Saving used to drop that part and the chart relationship, so the deck still rendered but the workbook was gone. An edit still omits the workbook rather than writing numbers that no longer match it.
Co-authored-by: Cursor <cursoragent@cursor.com>