Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
57 changes: 29 additions & 28 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ members = ["crates/*", "apps/*", "xtask"]
default-members = ["crates/*", "apps/deckcraft", "apps/deckcraft-cli"]

[workspace.package]
version = "0.11.1"
version = "0.12.0"
edition = "2024"
license = "MIT OR Apache-2.0"
rust-version = "1.95"
Expand Down
3 changes: 2 additions & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ Ranked; detail and the full beta plan in [docs/roadmap.md](docs/roadmap.md).
and animation bounce end. Still open: a corpus of real decks, #70, #74, #77, EMF+ records,
MicroType Express font subsets and SVG. 36–64 h.
3. **Localization:** catalog, system language (#15), zh-Hans, es, fr, de, ja with IME. 27–46 h.
4. **Output:** vector PDF, printing. 10–16 h.
4. **Output:** printing (vector PDF landed). 5–8 h.
5. **Depth that users hit:** font weights (#69), chart data and elements, Animation Pane timeline,
presenter view on the right display, native macOS menu bar, Format Shape pane. 33–56 h.

Expand Down Expand Up @@ -270,6 +270,7 @@ app's JSON control channel (`deckcraft --control PORT`).
| 2026-10-12 | Title bar: Export button next to Save, opening the Export dialog on every platform (a shortcut beside File › Export) |
| 2026-10-13 | PPTX export writes modern threaded comments (`p188`) next to the flattened legacy part; replies and resolved state survive a round trip, and real PowerPoint decks stop importing their comments twice (#70) |
| 2026-10-12 | Canvas: a right click selects the shape under the pointer before the context menu opens (`contextclick` pointer kind for the control channel and MCP); in edited text the menu offers text items |
| 2026-10-15 | Vector PDF export: shapes, gradients, transparency, tables and charts as PDF paths, real text in font subsets, pictures embedded as they are; effects and patterns rasterize per element (`vector: false` keeps the raster mode) |
| 2026-10-12 | Canvas: dragging the frame of a text box being edited moves it (move cursor on the frame, one undo step); the caret zone follows rotated boxes |
| 2026-10-13 | Start screen: a missing Recent file is marked and its click shows the error there; entries can be removed from the list (#172) |
| 2026-10-12 | Tables: the caret and text selection show in the cell being edited; clicks and double-clicks place the caret in the clicked cell (and select the word); dragging inner column borders, the right edge and row borders resizes them in one undo step; Tab skips cells hidden under a merge |
Expand Down
5 changes: 3 additions & 2 deletions crates/engine/src/cmd/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ pub fn specs() -> Vec<CommandSpec> {
cmd!(noundo "file.saveAs", "Save As…", ["File"], Some("Cmd+Shift+S"), "{path, format?}", has_doc, save_as),
cmd!(noundo "file.saveTemplate", "Save as Template…", ["File"], None, "{path}", has_doc, save_as),
cmd!(query "file.saveBytes", "Save to Data", [], None, "{format?: deckcraft|pptx} → {data: base64}", has_doc, save_bytes_cmd),
cmd!(noundo "file.export", "Export…", ["File"], None, "{path, format?: png|jpeg|pptx|deckcraft|outline|rtf|pdf|gif (else by path), slide?: index, all?: bool, scale?: px per pt, layout?: slides|notes|handouts (pdf), perPage?: 1|2|3|4|6|9, paper?: letter|a4|legal|a3 (pdf), orientation?: portrait|landscape (pdf), dpi?, slides?: [index]|range (\"1-3,5\"), includeHidden?, textLayer?, frame?, transparentBackground?: bool (png: leave the slide background out), width?: px 16-3840|small|medium|large|extraLarge (gif), secondsPerSlide?: 0.1-600 (gif; 5), loop?: bool (gif; true: forever)} → {path, bytes} or {files} (png, jpeg); gif adds frames, width, height, secondsPerSlide", has_doc, export),
cmd!(noundo "file.export", "Export…", ["File"], None, "{path, format?: png|jpeg|pptx|deckcraft|outline|rtf|pdf|gif (else by path), slide?: index, all?: bool, scale?: px per pt, layout?: slides|notes|handouts (pdf), perPage?: 1|2|3|4|6|9, paper?: letter|a4|legal|a3 (pdf), orientation?: portrait|landscape (pdf), vector? (pdf; true), dpi?, slides?: [index]|range (\"1-3,5\"), includeHidden?, textLayer?, frame?, transparentBackground?: bool (png: leave the slide background out), width?: px 16-3840|small|medium|large|extraLarge (gif), secondsPerSlide?: 0.1-600 (gif; 5), loop?: bool (gif; true: forever)} → {path, bytes} or {files} (png, jpeg); gif adds frames, width, height, secondsPerSlide", has_doc, export),
cmd!(
query "file.info",
"Info",
Expand Down Expand Up @@ -352,7 +352,7 @@ pub fn save_bytes(doc: &Presentation, format: &str) -> Result<Vec<u8>> {
/// Paper sizes for notes and handouts, for the UI's Print page.
pub use deckcraft_pdf::{PAPERS, paper_size};

/// PDF options from command params: `{layout: slides|notes|handouts, perPage, dpi, slides,
/// PDF options from command params: `{layout: slides|notes|handouts, perPage, vector, dpi, slides,
/// includeHidden, textLayer, frame, paper: letter|a4|legal|a3, orientation: portrait|landscape}` for a presentation of `count` slides. `slides` is a list of
/// 0-based indices or a range as typed in PowerPoint's Print page (`"1-3, 5"`, 1-based).
pub fn pdf_options(p: &Value, count: usize) -> Result<deckcraft_pdf::PdfOptions> {
Expand All @@ -362,6 +362,7 @@ pub fn pdf_options(p: &Value, count: usize) -> Result<deckcraft_pdf::PdfOptions>
"handouts" => deckcraft_pdf::PageLayout::Handouts { per_page: usize_param(p, "perPage").unwrap_or(6).clamp(1, 9) as u8 },
_ => deckcraft_pdf::PageLayout::Slides,
};
o.vector = bool_or(p, "vector", true);
o.dpi = f64_or(p, "dpi", o.dpi).clamp(36.0, 600.0);
o.slides = match p.get("slides") {
Some(Value::Array(a)) => Some(a.iter().filter_map(|v| v.as_u64().map(|x| x as usize)).filter(|i| *i < count).collect()),
Expand Down
2 changes: 1 addition & 1 deletion crates/mcp/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ fn export_tool_lists_pdf_and_its_options() {
}
assert_eq!(props["layout"]["enum"], json!(["slides", "notes", "handouts"]));
assert_eq!(props["perPage"]["enum"], json!([1, 2, 3, 4, 6, 9]));
for p in ["dpi", "slides", "includeHidden", "textLayer", "frame", "paper", "orientation"] {
for p in ["vector", "dpi", "slides", "includeHidden", "textLayer", "frame", "paper", "orientation"] {
assert!(props.get(p).is_some(), "{p}: {export}");
}
// What the schema describes is accepted.
Expand Down
5 changes: 3 additions & 2 deletions crates/mcp/src/tools.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,11 @@ pub fn tool_definitions() -> Vec<Value> {
"scale": num("Pixels per point for images (default 2)"),
"layout": {"type": "string", "enum": ["slides", "notes", "handouts"], "description": "PDF page layout (default slides)"},
"perPage": {"type": "integer", "enum": [1, 2, 3, 4, 6, 9], "description": "PDF handouts: slides per page (default 6)"},
"dpi": num("PDF: raster resolution of slide artwork, 36-600 (default 200)"),
"vector": boolean("PDF: vector slides with real text (default true); false: each slide one raster image"),
"dpi": num("PDF: resolution of raster slides (vector: false) and of effects rasterized in place, 36-600 (default 200)"),
"slides": {"type": "array", "items": {"type": "integer", "minimum": 0}, "description": "PDF and GIF: slide indices to export, in order (default all)"},
"includeHidden": boolean("PDF and GIF: include hidden slides (default false)"),
"textLayer": boolean("PDF: invisible text layer for search, select and copy (default true)"),
"textLayer": boolean("PDF raster slides: invisible text layer for search, select and copy (default true)"),
"frame": boolean("PDF notes/handouts: thin frame around each slide (default true)"),
"paper": {"type": "string", "enum": ["letter", "a4", "legal", "a3"], "description": "PDF notes/handouts: paper size (default letter)"},
"orientation": {"type": "string", "enum": ["portrait", "landscape"], "description": "PDF notes/handouts: page orientation (default portrait)"},
Expand Down
3 changes: 2 additions & 1 deletion crates/pdf/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ name = "deckcraft-pdf"
version.workspace = true
edition.workspace = true
license.workspace = true
description = "DeckCraft PDF export (craft-pdf/Krilla): slides, notes pages and handouts with a selectable text layer, links and bookmarks"
description = "DeckCraft PDF export (craft-pdf/Krilla): vector slides, notes pages and handouts with real text, links and bookmarks"

[lints]
workspace = true

[dependencies]
deckcraft-color = { workspace = true }
deckcraft-model = { workspace = true }
deckcraft-fonts = { workspace = true }
deckcraft-text = { workspace = true }
Expand Down
Loading