Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
113 commits
Select commit Hold shift + click to select a range
283be17
fix: DeckCraft upstream #93
FabPei Oct 12, 2026
0fea876
Web: point web app favicon to deckcraft.ico
GhostMitra Oct 13, 2026
b919e33
UI: close About dialog on Cancel and test Options Cancel from File ta…
GhostMitra Oct 15, 2026
b27bd05
Preferences: checkboxes and slider rails show in light mode (fixes #766)
marcoarnulfo Oct 15, 2026
13e06ca
Shapes: Edit Points — Straight/Curved Segment and Open/Close Path fro…
marcoarnulfo Oct 15, 2026
d28b4e0
Review: reject revisions after Compare, one or all
marcoarnulfo Oct 15, 2026
013941e
engine: a hostile text selection can't drive paragraph commands past …
midasmatterhorn Oct 15, 2026
5f0a088
PPTX: paragraph builds edited in PowerPoint keep their clicks (part o…
marcoarnulfo Oct 16, 2026
beb883d
Files: save and open outlines as RTF
NikitkaRa Oct 13, 2026
a55b6cf
Clamp text.selectParagraph paragraph index to avoid overflow
Silvertree2010 Oct 17, 2026
afd6d3a
Header & Footer: the automatic date takes a format
Hamza0101 Oct 17, 2026
85b994e
Hide the slide show media controls when the pointer is idle (#967)
MarekSpodBiedry Oct 18, 2026
ea98ebb
Drop a clippy allow the workspace already sets
MarekSpodBiedry Oct 18, 2026
a7833e0
#807: Browse for Themes brings a template's slide master and layouts
Faabyy Oct 15, 2026
8bc3d3f
Text boxes: a click on the frame selects the box whole (#787)
Faabyy Oct 15, 2026
3610cd6
Thumbnails: navigating slides scrolls the pane to keep the current sl…
albayazid Oct 17, 2026
8e83e8b
Render: quick styles render on empty placeholder text boxes (#832)
albayazid Oct 15, 2026
37d69f0
Tests: Ctrl+A in the slide sorter selects every slide (#936)
albayazid Oct 18, 2026
756ca23
Canvas: the Format and Animation Painter override the cursor (#830)
albayazid Oct 19, 2026
4b474d7
Replace Fonts test: a window wide enough to hold Home's ribbon
Hamza0101 Oct 19, 2026
bc12031
Canvas: the resize cursor follows the handle's diagonal, whatever the…
albayazid Oct 15, 2026
52a9a45
Render: a quick style on an empty placeholder is covered by a test (#…
albayazid Oct 19, 2026
0b476e8
Tests: the Options dialog's Cancel, Esc and the close button restore …
albayazid Oct 19, 2026
0405fb7
Charts: a series' own data-label setting decides whether its labels s…
albayazid Oct 18, 2026
502fb2b
Indent table rows in outline export so they stay on their slide
Silvertree2010 Oct 20, 2026
dc69e57
Tests: Cancel restores every Options setting, not only the first (#878)
albayazid Oct 20, 2026
fdfdb9e
Reuse Slides: choose which slides of the other file to insert (#1103)
Faabyy Oct 21, 2026
3cd6a81
Roadmap: record reuse-picker-1103
Faabyy Oct 21, 2026
08b7b5e
Charts: bars draw their series' pattern fill and no fill
pick2510 Oct 22, 2026
53972cf
Make chart axis flatness thresholds relative to data magnitude
Silvertree2010 Oct 22, 2026
adcfa70
Design Ideas: no ideas for slides with diagrams, connectors or other …
xc4pr Oct 22, 2026
b61898b
Theme: text fields show in the light theme (#1204)
xc4pr Oct 23, 2026
6d312e0
Design Ideas: an empty text box that shows also blocks the ideas (#1200)
xc4pr Oct 23, 2026
e126fad
Design Ideas: decide whether an empty box shows from its resolved fil…
xc4pr Oct 23, 2026
d86f7ae
Theme: the field test only counts a fill or outline that is actually …
xc4pr Oct 23, 2026
077682e
engine: merge main and cover direct hostile paragraph selections
midasmatterhorn Oct 23, 2026
8212c1a
Status bar: keep the save message clear of a long language name
Katacc Oct 21, 2026
284db01
Status bar: clip the language label before the controls on the right
Katacc Oct 21, 2026
50667e9
Doc comment for the status bar
Katacc Oct 23, 2026
8117d5b
arrange.group: nest groups at most 32 deep
Katacc Oct 21, 2026
9b2ab3d
test: give outline table-row regression test a titled slide and asser…
Silvertree2010 Oct 23, 2026
8789394
fix: keep chart axis steps positive and finite and drop absolute floo…
Silvertree2010 Oct 23, 2026
a48a2d7
chart.options takes textColor so a chart's text can be coloured
zberry Oct 18, 2026
1b03ae9
format.color on a selected chart colours the chart's text
zberry Oct 18, 2026
be9bf48
Chart text without a colour of its own takes the theme's text colour
zberry Oct 18, 2026
30b7eff
Pie data labels take the chart's text colour when it has one
zberry Oct 18, 2026
3ac39f9
Chart text colour is written to the chart part, so an imported chart …
zberry Oct 23, 2026
0c51042
Chart text without a colour of its own takes PowerPoint's default grey
zberry Oct 23, 2026
aaba9fe
Progress log: chart text colour
zberry Oct 23, 2026
52bf97d
Render: table body text on tinted cells uses the theme's dark colour,…
zberry Oct 18, 2026
18e8bb0
Fix arrange.distribute span to use the selection's full extent
Silvertree2010 Oct 24, 2026
fc914a0
Reject '+'-signed numeric character references in HTML paste
Silvertree2010 Oct 24, 2026
6d7b323
Reject invalid index in slide.hide and slide.layout
Silvertree2010 Oct 24, 2026
e56c62c
Merge PR #793: UI: close About dialog on Cancel and test Options Canc…
aiongg Oct 24, 2026
a774f01
Merge PR #962: Tests: the Options dialog's Cancel, Esc and the close …
aiongg Oct 24, 2026
a1c92dc
Merge PR #1146: Charts: bars draw their series' pattern fill and no fill
aiongg Oct 24, 2026
ee721e2
Merge PR #1198: Make chart axis flatness thresholds relative to data …
aiongg Oct 24, 2026
ca0e1a3
Merge PR #1208: Theme: text fields show in the light theme (#1204)
aiongg Oct 24, 2026
601a20e
Merge PR #805: Preferences: checkboxes and slider rails show in light…
aiongg Oct 24, 2026
f8a7006
Merge PR #965: Tests: Ctrl+A in the slide sorter selects every slide …
aiongg Oct 24, 2026
f6597e0
Merge PR #1043: Replace Fonts test: a window wide enough to hold Home…
aiongg Oct 24, 2026
5827d3b
Merge PR #904: Clamp text.selectParagraph paragraph index to avoid ov…
aiongg Oct 24, 2026
7fd34fb
Merge PR #813: engine: a hostile text selection can't drive paragraph…
aiongg Oct 24, 2026
cac379b
Merge PR #956: Charts: a series' own data-label setting decides wheth…
aiongg Oct 24, 2026
c7388bd
Merge PR #320: Text: Tab no longer ends text entry in lists (#93)
aiongg Oct 24, 2026
aed303e
Integration: #956 chart.options dataLabels resets the series' own set…
aiongg Oct 24, 2026
6971d55
Merge PR #799: Text boxes: a click on the frame selects the box whole…
aiongg Oct 24, 2026
f2e2fca
Merge PR #1015: Chart text can be coloured (chart.options textColor, …
aiongg Oct 24, 2026
4f56789
Merge PR #945: Thumbnails: navigating slides scrolls the pane to keep…
aiongg Oct 24, 2026
3aa8f4b
Merge PR #1016: Render: table body text on tinted cells picks the the…
aiongg Oct 24, 2026
e6f7648
Integration: #945 allow unwrap and expect in the thumbs tests module
aiongg Oct 24, 2026
2109dfa
Merge PR #1244: Status bar: keep a long language name from overlapping
aiongg Oct 24, 2026
be54138
Merge PR #848: Render: quick styles render on empty placeholder text …
aiongg Oct 24, 2026
84702f3
Merge PR #806: Canvas: the resize cursor follows the handle's rotated…
aiongg Oct 24, 2026
b280f77
Merge PR #846: Canvas: the Format and Animation Painter override the …
aiongg Oct 24, 2026
78afd2f
Merge PR #1245: Title: arrange.group: nest groups at most 32 deep
aiongg Oct 24, 2026
2fd3275
Merge PR #1251: Fix arrange.distribute span to use the selection's fu…
aiongg Oct 24, 2026
c480b74
Merge PR #833: Shapes: Edit Points — Straight/Curved Segment and Open…
aiongg Oct 24, 2026
29cc5f8
Integration: #833 drop the leftover PNG debug hook from the Edit Poin…
aiongg Oct 24, 2026
0b6ea6a
Integration: #1244 test that a long language name leaves the Notes bu…
aiongg Oct 24, 2026
92724e0
Merge PR #1205: Design Ideas: no ideas for slides with diagrams, conn…
aiongg Oct 24, 2026
dd9111b
Merge PR #384: Web: point web app favicon to deckcraft.ico
aiongg Oct 24, 2026
987f49a
Merge PR #1252: Reject '+'-signed numeric character references in HTM…
aiongg Oct 24, 2026
b7c505a
Merge PR #1254: Reject invalid index in slide.hide and slide.layout
aiongg Oct 24, 2026
5880086
Merge PR #927: Header & Footer: the automatic date takes a format
aiongg Oct 24, 2026
0313e01
Merge PR #850: Review: reject revisions after Compare, one or all
aiongg Oct 24, 2026
f52c911
Integration: #927 Header & Footer dialog sends dateFormat only when p…
aiongg Oct 24, 2026
2693336
Merge PR #809: Design: Browse for Themes brings a template's slide ma…
aiongg Oct 24, 2026
5b5fd5e
Merge PR #1109: Reuse Slides: choose which slides of the other file t…
aiongg Oct 24, 2026
bb5da20
Merge PR #978: Hide the slide show media controls when the pointer is…
aiongg Oct 24, 2026
882f1cb
Merge PR #1075: Indent table rows in outline export so they stay on t…
aiongg Oct 24, 2026
abd8768
Merge PR #565: Files: save and open outlines as RTF
aiongg Oct 24, 2026
3a37fb6
Integration: #565 rtf_to_outline never splits a multi-byte character …
aiongg Oct 24, 2026
80e5506
Integration: cargo fmt
aiongg Oct 24, 2026
820b163
Merge integration track r1-t01 (charts, tables, render, shapes, slide…
aiongg Oct 24, 2026
8957e19
Merge integration track r1-t02 (dialogs, view, text editing, panes, s…
aiongg Oct 24, 2026
36cd258
Merge PR #883: PPTX: paragraph builds edited in the reference app kee…
aiongg Oct 24, 2026
7c1ae73
Integration: #565 collapse the nested if in rtf_to_outline (clippy)
aiongg Oct 24, 2026
ded9094
Integration: crates/model/src/chart.rs keeps main's CRLF line endings
aiongg Oct 24, 2026
f639ef9
Integration: #927 Header & Footer dialog: reading the format must not…
aiongg Oct 24, 2026
30755b3
Integration: #1244 Notes-button test starts with the pane closed and …
aiongg Oct 24, 2026
9c31b56
Integration: #978 media-controls test turns presenter view off so its…
aiongg Oct 24, 2026
75bec53
Integration: RTF import drops NUL units and masks \u to 16 bits so th…
aiongg Oct 24, 2026
9dcb7ba
Integration: the automatic-date test reads the clock before and after…
aiongg Oct 24, 2026
2bbfe46
Integration: slides with two visible pictures get no layout ideas
aiongg Oct 24, 2026
027075d
Integration: a rejected positional revision stays rejected after a sl…
aiongg Oct 24, 2026
e679fa5
Integration: right-clicking a control handle in Edit Points keeps the…
aiongg Oct 24, 2026
910b03f
Integration: the GIF play/pause click works only while the pointer is…
aiongg Oct 24, 2026
a453292
Merge main into the integration batch
aiongg Oct 24, 2026
56b15df
Integration: a huge RTF left indent no longer overflows when the outl…
aiongg Oct 24, 2026
44b33a2
Integration: a subnormal chart data range gives a finite nice scale
aiongg Oct 24, 2026
202255c
Integration: restore chart.rs CRLF line endings
aiongg Oct 24, 2026
528af4e
Integration: a rejected positional revision is not turned into a dele…
aiongg Oct 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ app's JSON control channel (`deckcraft --control PORT`).

| Date | What landed |
|---|---|
| 2026-10-23 | Chart text colour: `chart.options {textColor}` and Font Color on a selected chart colour its title, axes, legend and data labels; the colour is written to the chart part (`c:txPr` on the axes, legend, labels and chart, the title's run defaults) so an imported chart keeps its `txPr` colours through an edit and a save; text without a colour of its own takes PowerPoint's default, the theme's text colour at 65 % plus 35 % (595959 on a light theme, light grey on a dark one) |
| 2026-10-23 | Format Background ▸ Apply to All also sets the background of the slide masters the slides use (and clears their layouts' overrides), so new slides keep it (#891) |
| 2026-10-22 | Table text takes the master's other-text style (`otherStyle`), shapes and text boxes the presentation's default text style, as PowerPoint for Mac draws them (synthetic probe: 14 pt cells under a 14 pt `otherStyle` and a 26 pt `defaultTextStyle`); a private deck's table had been drawn 20 pt instead of 18 |
| 2026-10-18 | Find (Ctrl+F): "Find what:" has the keyboard as soon as the dialog opens, and keeps it after Enter finds a match, so the search can be typed and changed without clicking the field (#951) |
Expand All @@ -145,7 +146,10 @@ app's JSON control channel (`deckcraft --control PORT`).
| 2026-10-23 | Normal view: a slide zoomed past the window gets a horizontal and a vertical scroll bar on the axes that overflow; drag the thumb or click the track to reach every edge (#1157) |
| 2026-10-22 | Shape Fill and Shape Outline format the members of a selected group, including nested groups, in one undo step. Fill transparency retains each member's effective colour (#1079) |
| 2026-10-21 | 3-D contours, extrusion depths and bevels keep their size in points inside a group whose child coordinates scale its shapes: a 1 pt contour on a small extruded cube in a legacy group drew hundreds of points wide, a band across the slide |
| 2026-10-21 | Groups nest at most 32 deep (`arrange.group` refuses more), as deep as PPTX import reads and slides draw: a deck grouped 39 or more levels deep saved without complaint and then could not be opened again |
| 2026-10-20 | Chart number formats with explicit empty positive, negative or zero sections hide the corresponding labels, including all values with three semicolons; a blank whole format still uses General. Synthetic formatter regressions |
| 2026-10-22 | Chart bars and their legend keys draw the series' own pattern fill (`a:pattFill`, now read and written) and draw nothing for `a:noFill`, with the series outline where it has one, as PowerPoint for Mac does (synthetic probe); they took the palette's colour before. Lines, areas and markers use a pattern's foreground colour |
| 2026-10-21 | Reuse Slides: pick which slides of the other presentation to insert from a list (`slide.reuseList`, #1103) |
| 2026-10-18 | Native chart sidecar bytes move from JSON arrays into referenced ZIP entries, with shared media byte/item limits and bounded reads; older inline sidecars still load. Synthetic format regressions cover large workbooks and malformed members |
| 2026-10-18 | Slide thumbnails and the Slide Sorter render only the slides in view: on a deck of more than 600 slides they used to evict their own thumbnails, so the pane flashed white and never finished (#969) |
| 2026-10-18 | Resizing with the mouse snaps the edge being dragged to the slide, guides and other shapes: a fixed edge that already lined up used to take the snap and leave the dragged edge free, and a fixed edge near a line shifted the dragged one (#961) |
Expand All @@ -158,14 +162,26 @@ app's JSON control channel (`deckcraft --control PORT`).
| 2026-10-17 | While a placeholder's text is being edited, a click on a shape in front of it selects that shape instead of moving the caret, as in PowerPoint (#931) |
| 2026-10-16 | Symbol-font characters: a run's U+F0xx symbol code (from `<a:sym typeface="Symbol"/>`) now draws from the installed Symbol face even when it has no private-use mapping — Apple's Symbol on macOS maps the glyphs to real Unicode, so the code is remapped to that character (the Adobe Symbol encoding) instead of falling to a look-alike. Bullets and metafile text take the same remap |
| 2026-10-15 | Native saves keep an imported chart's embedded workbook, drawings and relationships; headless PPTX → edit → native save/reopen → PPTX tests verify the payloads and stale-workbook rejection |
| 2026-10-18 | Charts: a series' own data-label setting is honoured — numbers PowerPoint shows on a graph no longer disappear, and a bare `c:showVal` means on (#868) |
| 2026-10-15 | Render: quick styles render on empty placeholders — the fill gate now checks for style and line overrides too (#832) |
| 2026-10-15 | Canvas: the resize cursor follows the handle's rotated diagonal — rotated shapes showed the wrong diagonal cursor (#745) |
| 2026-10-20 | Tests: the Options dialog's Cancel, Esc and close button put the settings back the way they were (#878) |
| 2026-10-20 | Tests: Ctrl+A in the slide sorter selects every slide, as in PowerPoint (#936) |
| 2026-10-18 | Thumbnails: navigating slides with the arrow keys scrolls the thumbnail pane to keep the current slide visible (#917) |
| 2026-10-15 | PPTX trigger sequences keep separate On Click steps, including paragraph builds; synthetic import/edit/save/reopen and headless show regressions |
| 2026-10-16 | Export: PNG with a transparent background — the slide background is left out, only the slide's content is drawn (Export… dialog, File › Export, web download, `file.export`/`file.render` `transparentBackground`, `deckcraft-cli render --transparent`) (#784) |
| 2026-10-16 | Arrange ▸ Align: Align to Slide and Align Selected Objects, as in PowerPoint. One object aligns to the slide and several to each other until the other choice is ticked; the choice holds for that selection and also drives Distribute |
| 2026-10-16 | Fitted canvas: discrete mouse-wheel steps navigate immediately, including rapid and batched input, while trackpad gestures retain their cooldown and zoomed slides still pan. |
| 2026-10-16 | PPTX import: a paragraph build edited in PowerPoint (paragraphs with their own start, other shapes' effects in between, builds by 2nd-level paragraphs) takes PowerPoint's clicks in the show; it used to be rebuilt as one build, 34 clicks instead of 10 on the reporter's slide. Paragraph builds with sub-points save as PowerPoint writes them (#844) |
| 2026-10-15 | CLI: .txt and .md export includes table cell text, which it used to skip silently (#814) |
| 2026-10-15 | A `.pptx` slide that can't be read is reported, not dropped in silence: `lostSlides` from `file.open`, a message and a repaired copy in the app, a warning from `deckcraft-cli` (#203) |
| 2026-10-16 | AutoFormat as you type: ordinals (1st, 22nd, 103rd) get superscript suffixes after a space or punctuation; Tools ▸ AutoCorrect and `text.autocorrect {ordinals}` turn it off |
| 2026-10-16 | A PowerPoint Show (`.ppsx`) opened from the command line or the file association starts its slide show, as in PowerPoint; File › Open still opens it for editing (#862) |
| 2026-10-15 | Canvas: the Format and Animation Painter override the cursor to show the tool is armed (#830) |
| 2026-10-15 | Edit Points: right-click a segment for Straight/Curved Segment, a point for Open/Close Path (`shape.segmentType`, `shape.openPath`, `shape.closePath`); Delete Segment and Add Point in the menu still to come |
| 2026-10-15 | Design › Browse for Themes… with a `.pptx` or `.potx` brings its slide master and layouts, not just the theme: the master the current slide uses is replaced (a preserved one stays beside it), the template stretches to the deck's slide size, and each slide moves to the layout with the same name or kind and keeps its content. One undo step (#807) |
| 2026-10-15 | Clicking the frame of a text box or placeholder selects the box whole, so it can be moved, duplicated or deleted; only a click inside puts the caret in the text (#787) |
| 2026-10-15 | Review › Compare: Reject and Reject All drop revisions without applying them (`review.reject`); Accept All skips what was rejected |
| 2026-10-15 | PDF export and rendering: `anchorCtr` centres the drawn text block — right-aligned text no longer ran past the box edge (#736) |
| 2026-10-14 | connected MCP rejects unrelated, malformed, incomplete and oversized replies without replay; buffered unsolicited bytes force a fresh connection before sending (#718) |
| 2026-10-14 | Empty bulleted and numbered paragraphs show gray markers while editing, including on first click and after Enter. Markers use the list style and stay out of slide shows and exports. |
Expand Down Expand Up @@ -265,6 +281,7 @@ app's JSON control channel (`deckcraft --control PORT`).
| 2026-10-22 | trivial | Progress log: Animation Pane ▸ Play From. Scores unchanged |
| 2026-10-23 | trivial | Progress log: scroll bars on the zoomed slide pane (#1157). Scores and milestone names unchanged |
| 2026-10-20 | trivial | Explicit empty chart number-format sections hide their values; blank whole-code General fallback is retained. Synthetic formatter regressions; chart-element scores and readiness estimates unchanged |
| 2026-10-21 | trivial | Progress log: Reuse Slides picker (#1103). Scores and milestone names unchanged |
| 2026-10-18 | minor | Native chart payloads use referenced ZIP members and share media byte/item limits; old inline sidecars remain readable. Synthetic tests cover multi-megabyte layout, all shape owners, shared budgets, missing/corrupt members and invalid references. Scores and milestone scope unchanged |
| 2026-10-16 | minor | Symbol-font U+F0xx codes remap to the installed Symbol face's real Unicode (Apple Symbol on macOS) |
| 2026-10-15 | trivial | Progress log: chart package parts survive a native save. Scores and milestone names unchanged |
Expand All @@ -276,6 +293,10 @@ app's JSON control channel (`deckcraft --control PORT`).
| 2026-10-16 | trivial | Progress log: responsive mouse-wheel navigation on the fitted canvas |
| 2026-10-21 | trivial | Progress log: theme `spDef` anchor no longer applied to a file's own shapes |
| 2026-10-21 | trivial | Progress log: 3-D sizes in scaled groups stay in points |
| 2026-10-22 | trivial | Progress log: chart series pattern and no fills |
| 2026-10-15 | trivial | Progress log: Browse for Themes applies a template's master and layouts (#807) |
| 2026-10-15 | trivial | Progress log: Compare Reject and Reject All |
| 2026-10-16 | trivial | Progress log: PPTX paragraph builds edited in PowerPoint keep their clicks (#844) |
| 2026-10-14 | trivial | remote MCP checks reply IDs, envelopes and bounded newline framing (#718) |
| 2026-10-14 | minor | Gray list markers on empty paragraphs while editing; caret keeps the list indent before typing |
| 2026-10-14 | minor | Community PR wave folded: headline breadth 84% over 191 features (P0 92%, P1 82%, P2 55%) and the feature-area breadths from `cargo xtask parity`; one row per area; Upcoming item 2 merged; per-PR count notes dropped from the progress log. Ready stays ~55%, stage alpha |
Expand Down
2 changes: 1 addition & 1 deletion apps/deckcraft-web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<title>DeckCraft</title>
<link data-trunk rel="rust" data-bin="deckcraft-web" data-wasm-opt="z" data-weak-refs />
<link data-trunk rel="icon" href="../../assets/app-icon/hicolor/32x32/apps/ai.storyteller.deckcraft.png" />
<link data-trunk rel="icon" href="../../assets/app-icon/deckcraft.ico" />
<style>
html, body { margin: 0; padding: 0; width: 100%; height: 100%; overflow: hidden; background: #262626; }
#deckcraft_canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
Expand Down
3 changes: 3 additions & 0 deletions contributors/people.toml
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,6 @@ real_name = "Ryan C. Shoemaker"
real_name = "Marco Arnulfo"
display_name = "Marco Arnulfo"
sync_github_name = false

[people.GhostMitra]
sync_github_name = true
23 changes: 10 additions & 13 deletions crates/anim/src/timeline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

use std::collections::{HashMap, HashSet};

use deckcraft_model::anim::TextBuild;
use deckcraft_model::anim::{BuildRole, TextBuild, paragraph_build_roles};
use deckcraft_model::style::Fill;
use deckcraft_model::{AnimClass, AnimStart, Animation, ColorScheme, Rgba, SchemeSlot, ShapeId, Slide, Xfrm};

Expand All @@ -12,7 +12,6 @@ use crate::effects::{EffectSpec, exit_base, motion_path};
/// Delay between paragraphs of a by-paragraph build that isn't on click (seconds).
pub const PARAGRAPH_STAGGER: f64 = 0.5;
const MAX_EFFECTS: usize = 100_000;
const MAX_PARAGRAPHS: usize = 1_000;
const MAX_ITERATIONS: f64 = 1.0e6;

/// Which sequence an effect belongs to.
Expand Down Expand Up @@ -198,17 +197,15 @@ impl Timeline {
(None, TextBuild::ByParagraph) => {
let mut v: Vec<Option<usize>> = vec![];
if let Some(t) = shape.and_then(|s| s.text.as_ref()) {
let filled = |p: &deckcraft_model::text::Paragraph| p.runs.iter().any(|r| !r.text.trim().is_empty());
let top = t.paragraphs.iter().filter(|p| filled(p)).map(|p| p.level).min().unwrap_or(0);
let mut owner: Option<usize> = None;
for (i, p) in t.paragraphs.iter().enumerate() {
if p.level <= top && filled(p) && v.len() < MAX_PARAGRAPHS {
owner = Some(i);
v.push(Some(i));
} else if let Some(o) = owner
&& grouped.insert((a.shape, i, o))
{
tl.groups.push((a.shape, i, o));
for (i, role) in paragraph_build_roles(t).into_iter().enumerate() {
match role {
BuildRole::Top => v.push(Some(i)),
BuildRole::Member(o) => {
if grouped.insert((a.shape, i, o)) {
tl.groups.push((a.shape, i, o));
}
}
BuildRole::Unowned => {}
}
}
}
Expand Down
25 changes: 22 additions & 3 deletions crates/engine/src/cmd/arrange.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,22 @@ pub(crate) fn union_bounds(xs: &[Xfrm]) -> Option<Rect> {
Some(it.fold(first, |a, b| a.union(b)))
}

/// How deep groups may nest: as deep as the PPTX reader reads and the renderer draws. A saved
/// .deckcraft file stops opening at about 39 (the JSON reader's recursion limit), and walks over
/// the shape tree stop at 64.
pub const MAX_GROUP_DEPTH: usize = 32;

/// How many groups deep `shapes` nest (0 without groups).
fn group_depth(shapes: &[Shape]) -> usize {
let mut d = 0;
deckcraft_model::walk(shapes, &mut |sh, depth| {
if sh.is_group() {
d = d.max(depth + 1);
}
});
d
}

fn group(s: &mut Session, p: &Value) -> Result<Value> {
let ids = targets(s, p)?;
s.edit(|doc, sel| {
Expand All @@ -179,6 +195,9 @@ fn group(s: &mut Session, p: &Value) -> Result<Value> {
if members.len() < 2 {
return Err(bad("arrange.group", "select two or more objects on the slide"));
}
if group_depth(&members) + 1 > MAX_GROUP_DEPTH {
return Err(bad("arrange.group", format!("groups can be nested at most {MAX_GROUP_DEPTH} deep")));
}
for m in &mut members {
if m.xfrm.is_none() {
m.xfrm = Some(xfrm_of(doc, sel, m));
Expand Down Expand Up @@ -322,9 +341,9 @@ fn distribute(s: &mut Session, p: &Value) -> Result<Value> {
let (start, end) = if to_slide {
if horiz { (0.0, size.width) } else { (0.0, size.height) }
} else {
let f = boxes.first().map(|b| b.1).unwrap_or_default();
let l = boxes.last().map(|b| b.1).unwrap_or_default();
if horiz { (f.x0, l.x1) } else { (f.y0, l.y1) }
let lo = |f: fn(&Rect) -> f64| boxes.iter().map(|b| f(&b.1)).fold(f64::INFINITY, f64::min);
let hi = |f: fn(&Rect) -> f64| boxes.iter().map(|b| f(&b.1)).fold(f64::NEG_INFINITY, f64::max);
if horiz { (lo(|r| r.x0), hi(|r| r.x1)) } else { (lo(|r| r.y0), hi(|r| r.y1)) }
};
let n = boxes.len();
let gap = if n > 1 { ((end - start) - total) / (n - 1) as f64 } else { 0.0 };
Expand Down
Loading
Loading