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
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
craft-i18n.rev text eol=lf
10 changes: 10 additions & 0 deletions ATTRIBUTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,16 @@ families excluded by `crates/fonts/build.rs`:
Web (wasm32) builds embed only Inter Regular/Bold (and a Variable face where the Latin manifest
provides one) and BIZ UDPGothic Regular.

## Embedded at build time from craft-i18n (`CRAFT_I18N_DIR`)

Builds with a craft-i18n checkout (at the commit in `craft-i18n.rev`) embed its interface
catalogs for the languages DeckCraft offers (`locales/<locale>/common.tsv` and `deckcraft.tsv`).
They are text, not files in this repository; without the checkout the interface is English only.

| Asset | Author | Source | Licence | Use |
|---|---|---|---|---|
| craft-i18n interface catalogs | craft-i18n translators, credited in its [ATTRIBUTION.md](https://github.com/storytold/craft-i18n/blob/main/ATTRIBUTION.md) | https://github.com/storytold/craft-i18n at `craft-i18n.rev` | MIT OR Apache-2.0 | Interface languages other than English (German first); clean-room translations from the English interface labels |

## PDF writer colour profiles

`craft-pdf` is used with only its `write` feature and default features disabled. Krilla
Expand Down
2 changes: 2 additions & 0 deletions Cargo.lock

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

6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@ craft-pdf = { git = "https://github.com/storytold/craft-libs", rev = "c5a1cbfb5f
# craft-update: app-specific pretend versions apply to startup and manual checks.
craft-update = { git = "https://github.com/storytold/craft-libs", version = "=0.1.0", rev = "c5a1cbfb5f9049f9270955caffb5bee5997aebf7", features = ["egui"] }
craft-ui = { git = "https://github.com/storytold/craft-libs", rev = "c5a1cbfb5f9049f9270955caffb5bee5997aebf7", version = "=0.1.0" }
# Interface translations: lookup, fallback, plurals and system-locale matching over the craft-i18n
# catalogs that `crates/ui-egui/build.rs` embeds from `CRAFT_I18N_DIR` (storytold/craft-i18n,
# pinned in `craft-i18n.rev`). Same craft-libs rev as craft-ui.
craft-i18n = { git = "https://github.com/storytold/craft-libs", rev = "c5a1cbfb5f9049f9270955caffb5bee5997aebf7", version = "=0.1.0" }
# The system's preferred interface languages (#15). Native: OS APIs; wasm: the browser's (`js`).
sys-locale = { version = "0.3", features = ["js"] }
i_overlay = "9"
quick-xml = "0.41"
zip = { version = "3", default-features = false, features = ["deflate"] }
Expand Down
17 changes: 12 additions & 5 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@

**Stage: alpha** · next: beta, ~20 points and ~120–210 h away

> **Last reviewed:** 2026-11-04 · **Last updated:** 2026-11-08 · **Change:** minor (Russian interface, partial; parity scores unchanged) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
> **Last reviewed:** 2026-11-04 · **Last updated:** 2026-11-04 · **Change:** trivial (Ogg Opus recovery includes trailing packets without granules in its reported duration; parity scores unchanged) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
> **Last reviewed:** 2026-11-02 · **Last updated:** 2026-11-03 · **Change:** trivial (progress log: web `?host=parent` bridge opens and saves through a same-origin framing page; scores unchanged) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)

> **Last reviewed:** 2026-11-02 · **Last updated:** 2026-11-03 · **Change:** trivial (community PR wave 10: PPTX and native saves keep table cell centred anchoring, text overflow, compatible line spacing, normalised character height, Thai distributed alignment, RTL table direction, alternate run language and reflection transforms; imported effects placed by parent time-node delays; Reuse Slides fits the destination size and brings custom table styles; deleted slides leave custom shows; merged shapes drop their animations; Compress Pictures measures grouped pictures at displayed size; parity scores unchanged) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
> **Last reviewed:** 2026-11-02 · **Last updated:** 2026-11-02 · **Change:** trivial (File › About replaces Account; product links, credits and appearance retained; parity scores unchanged) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-11-01 · **Change:** trivial (progress log: fix for a workflow-breaking regression: Ctrl+C/X/V use the system clipboard again, broken for all text and objects from 0.11.1 to 0.15.0; pasted and duplicated placeholders keep their look; stage and scores unchanged) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-11-01 · **Change:** trivial (locked No Fill and no-outline click-through regression coverage added; No Fill hit-testing follows resolved fill instead of the presence of a style (#1621); scores unchanged) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-28 · **Change:** minor (interface languages from craft-i18n: language setting and German, #15; localization ~8% → ~15%; previous: trivial, progress log: Edit Points handle drags with Shift, Ctrl/Cmd or Alt/Option set the point type) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-30 · **Change:** trivial (print-preview tests use egui's emitted URL, and macOS removes its previews before exit; parity scores unchanged) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-30 · **Change:** trivial (progress log: read-only `slide.actions` lists shape and table text actions; parity scores unchanged) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-27 · **Change:** trivial (progress log: Set Up Slide Show ▸ Keep mouse cursor visible, #1445; stage and scores unchanged) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-26 · **Change:** trivial (progress log: dragging several selected slides moves them all; previous: trivial, progress log: Edit Points handle drags with Shift, Ctrl/Cmd or Alt/Option set the point type; previous: trivial, progress log: scroll bars on the zoomed slide pane, #1157; previous: minor, native chart payloads stored in bounded ZIP members; previous: major, community PR wave 2026-10-14: ~60 landed items in the progress log, among them the Font, Paragraph, Photo Album, theme colour and font and Date and Time dialogs, presenter and show controls, the SmartArt Text Pane and PPTX corpus slices; breadth 84% after `cargo xtask parity`, ready stays ~55%, stage stays alpha; duplicated rows and headers folded; previous: minor, hours to ~95% per audience added) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
Expand Down Expand Up @@ -72,7 +75,7 @@ EMF/SVG pictures, the data-loss and startup-crash bugs (#60, #40, #59) and the b
| UI/UX fidelity | ~60% | 30–50 | [docs/ui-parity.md](docs/ui-parity.md) |
| File formats (PPTX, PDF, legacy, export) | ~58% | 60–110 | [docs/file-format-parity.md](docs/file-format-parity.md) |
| Hardware (GPU raster, video decode, displays, pens) | ~40% | 25–45 | [docs/hardware-parity.md](docs/hardware-parity.md) |
| Localization | ~8% | 55–90 + native review | [docs/localization-parity.md](docs/localization-parity.md) |
| Localization | ~15% | 45–80 + native review | [docs/localization-parity.md](docs/localization-parity.md) |
| Performance (unmeasured) | ~55% | 10–20 | [docs/hardware-parity.md](docs/hardware-parity.md#performance) |
| Stability | ~60% | 10–20 | [docs/gaps.md](docs/gaps.md#stability) |
| Platforms | ~80% | 5–10 | [Downloads in README](README.md#downloads) |
Expand Down Expand Up @@ -107,7 +110,7 @@ EMF/SVG pictures, the data-loss and startup-crash bugs (#60, #40, #59) and the b

## Languages

English only today; no string catalog yet (Ukrainian is in review as PR #34). Detail:
English, German and Russian. Interface text comes from the shared [craft-i18n](https://github.com/storytold/craft-i18n) catalogs (`locales/<lang>/deckcraft.tsv`); the interface follows the system language, or Preferences › Interface language (#15). Detail:
[docs/localization-parity.md](docs/localization-parity.md).

| Language | Code | UI strings | Status | Hours to full |
Expand All @@ -121,12 +124,12 @@ English only today; no string catalog yet (Ukrainian is in review as PR #34). De
| Portuguese | pt | 0% | none | 3–5 |
| Indonesian | id | 0% | none | 3–5 |
| Japanese | ja | 0% | none | 4–7 |
| German | de | 0% | none | 3–5 |
| German | de | ~80% | partial | 1–3 |
| Korean | ko | 0% | none | 4–7 |
| Vietnamese | vi | 0% | none | 3–5 |

Plus 8–14 h once for the catalog infrastructure. Other languages shipped: none. PowerPoint for Mac
ships 26.
The catalog infrastructure is in place: a new language is a `deckcraft.tsv` in craft-i18n plus one entry in `LANGUAGES` (`crates/ui-egui/src/i18n.rs`). Other languages shipped: Russian (`ru`,
partial). PowerPoint for Mac ships 26.

## Upcoming

Expand Down Expand Up @@ -154,6 +157,7 @@ app's JSON control channel (`deckcraft --control PORT`).

| Date | What landed |
|---|---|
| 2026-11-08 | Russian interface (`ru`): the craft-i18n catalog covers the ribbon, menus, command palette, status bar, File tab, dialogs and panes; spinners read numbers as the interface language writes them |
| 2026-11-04 | Ogg Opus recovery keeps trailing packets without granules in the total duration, matching their recovered positions; valid end trimming stays covered |
| 2026-11-03 | Web: the `?host=parent` bridge (as in GridCraft, WordCraft and PdfCraft) lets a same-origin page that frames DeckCraft send a presentation and store its saves; Save writes an opened .pptx back as .pptx under its own name, as on the desktop |
| 2026-11-02 | Slide show on Linux and the BSDs: Presenter View stays off when winit lists exactly one display, as in PowerPoint; if it lists none, the setting is kept. On one display the presenter window covered the show, and the hidden window stalled every frame: slide changes took up to ~1.7 s under Hyprland (#1156) |
Expand All @@ -170,6 +174,7 @@ app's JSON control channel (`deckcraft --control PORT`).
| 2026-11-01 | Copy, Cut, Paste, Duplicate and Alt+drag of a title, body or other placeholder on a slide keep its look: the copy is a regular shape that takes the size, font, bullets, indents, autofit and box its layout and master gave it, instead of 18 pt unbulleted text |
| 2026-11-01 | Clipboard: Ctrl+C, Ctrl+X and Ctrl+V use the system clipboard again, so text and pictures paste from other apps and in-app copies paste back. Since the egui 0.37 migration (#1350) the desktop build had a clipboard that only worked inside the app. On native Wayland, text goes through egui's Wayland clipboard; HTML stays on X11, macOS and Windows |
| 2026-10-31 | No Fill shape interiors allow selection and editing of underlying objects, even when a quick style remains; hit-testing uses the renderer’s fill resolution. Outlines, rotation, text targets and actual fills keep their hit behaviour (#1621) |
| 2026-10-28 | Interface languages (#15): DeckCraft reads the craft-i18n catalogs (`CRAFT_I18N_DIR`, pinned in `craft-i18n.rev`), follows the system language and has Preferences › Interface language and `app.language`. German is the first language: ribbon, menus, command palette, status bar, File tab, dialogs and panes |
| 2026-10-30 | Print-preview tests use the URL emitted by egui and its exact file path; previews are explicitly removed before the macOS process exits |
| 2026-10-30 | `slide.actions` lists shape click and hover actions and text-run hyperlinks (including table cells and grouped shapes) for audit; listing never runs an action. The UI pane is a follow-up |
| 2026-10-29 | Slide Show: going back to the previous slide plays the transition of the slide being left, reversed, as PowerPoint does, instead of the previous slide's own entrance transition (#1531); a jump (Go to Slide, links) still plays the target slide's transition |
Expand Down Expand Up @@ -340,6 +345,7 @@ app's JSON control channel (`deckcraft --control PORT`).

| Date | Change | Summary |
|---|---|---|
| 2026-11-08 | minor | Russian interface (`ru`), partial. Scores unchanged |
| 2026-11-04 | trivial | Ogg Opus recovery duration includes its recovered tail; parity scores unchanged |
| 2026-11-02 | trivial | Progress log: run alternate-language metadata round-trips through native and PPTX formats; product parity scores unchanged |
| 2026-11-03 | trivial | Reuse Slides brings referenced custom table styles along; scores unchanged |
Expand All @@ -348,6 +354,7 @@ app's JSON control channel (`deckcraft --control PORT`).
| 2026-11-02 | trivial | Progress log: tiled picture fills keep their size in scaled groups. Scores and milestone names unchanged |
| 2026-11-01 | trivial | Workflow-breaking regression fixed: the desktop build uses the system clipboard again (eframe `clipboard` feature) |
| 2026-10-31 | trivial | No Fill hit-testing follows resolved fill instead of the presence of a style (#1621); scores unchanged |
| 2026-10-28 | minor | Interface languages from craft-i18n with German (#15): Localization ~8% → ~15%, German row partial |
| 2026-10-30 | trivial | Progress log: read-only slide action inspection; parity scores unchanged |
| 2026-10-29 | trivial | Progress log: going back in the slide show reverses the left slide's transition (#1531). Scores and milestone names unchanged |
| 2026-10-29 | trivial | Progress log: the Picture Styles row leaves room for Crop and Size (#1232). Scores and milestone names unchanged |
Expand Down
1 change: 1 addition & 0 deletions craft-i18n.rev
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
bfed1eb8df6f6fa9afe6e1c6b4683e110fd9560d
2 changes: 2 additions & 0 deletions crates/ui-egui/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ deckcraft-media = { workspace = true }
egui = { workspace = true, features = ["default_fonts", "monochrome_emoji_fonts"] }
egui_extras = { workspace = true }
craft-ui = { workspace = true }
craft-i18n = { workspace = true }
sys-locale = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
log = { workspace = true }
Expand Down
84 changes: 83 additions & 1 deletion crates/ui-egui/build.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
//! Bake the contributor credits into the binary as static Rust tables. Nothing is read at run time.
//! Bake the contributor credits into the binary as static Rust tables, and embed the craft-i18n
//! interface catalogs. Nothing is read at run time.
//!
//! - contributors/contributors.json: commit stats per GitHub username, with a mark for whose
//! people.toml entry is verified.
//! - contributors/people.toml: the names people chose for themselves. A name is used only when the
//! JSON says the entry was verified (`people_entry`).
//!
//! A missing or malformed file gives empty tables (or no names) and a warning.
//!
//! - `CRAFT_I18N_DIR`, a craft-i18n checkout (storytold/craft-i18n, at the SHA in
//! `craft-i18n.rev`): `craft_i18n.rs` holds `CRAFT_I18N`, one (locale, shared catalog, DeckCraft
//! catalog) entry per locale that has a `deckcraft.tsv`. Unset, or not a checkout (a warning),
//! it is empty and the interface is English only; with `CRAFT_I18N_REQUIRED=1` (releases) that
//! is an error instead. Recipe: craft-i18n's `docs/integration.md`.

use std::fmt::Write as _;

Expand All @@ -23,6 +30,81 @@ fn main() {
if let Err(e) = std::fs::write(&out, generate(&json, &people)) {
println!("cargo::warning=writing {}: {e}", out.display());
}
let out = out.with_file_name("craft_i18n.rs");
if let Err(e) = std::fs::write(&out, craft_i18n()) {
println!("cargo::error=writing {}: {e}", out.display());
}
}

/// The catalog this app reads in each locale: `locales/<locale>/deckcraft.tsv`.
const APP_CATALOG: &str = "deckcraft.tsv";

/// `CRAFT_I18N` from `CRAFT_I18N_DIR`: the `Embedded` list the craft-i18n crate reads.
fn craft_i18n() -> String {
println!("cargo::rerun-if-env-changed=CRAFT_I18N_DIR");
println!("cargo::rerun-if-env-changed=CRAFT_I18N_REQUIRED");
let required = std::env::var("CRAFT_I18N_REQUIRED").is_ok_and(|v| v.trim() == "1");
let entries = match std::env::var_os("CRAFT_I18N_DIR").filter(|d| !d.is_empty()) {
// Not canonicalized: on Windows that gives `\\?\C:\…`, which `include_str!` can't read.
Some(dir) => match locales(&std::path::PathBuf::from(dir).join("locales")) {
Ok(entries) => entries,
Err(e) => {
let level = if required { "error" } else { "warning" };
println!("cargo::{level}=CRAFT_I18N_DIR: {e}; the interface is English only");
Vec::new()
}
},
None => {
if required {
println!("cargo::error=CRAFT_I18N_REQUIRED=1 but CRAFT_I18N_DIR is unset: a release must embed the translations");
}
Vec::new()
}
};
let mut o = String::from("/// (locale, shared catalog, DeckCraft's catalog), one entry per locale; `\"\"` for a missing file.\n");
o.push_str("pub static CRAFT_I18N: &craft_i18n::Embedded = &[\n");
for (code, shared, app) in entries {
// `{:?}` escapes the path (Windows backslashes) as a Rust string literal.
let shared = shared.map_or_else(|| "\"\"".to_string(), |p| format!("include_str!({p:?})"));
let _ = writeln!(o, " ({code:?}, {shared}, include_str!({app:?})),");
}
o.push_str("];\n");
o
}

/// A locale's code, its `common.tsv` if any, and its app catalog.
type Locale = (String, Option<std::path::PathBuf>, std::path::PathBuf);

/// Every locale under `dir` with this app's catalog, by code: (code, its `common.tsv` if any, its
/// app catalog). An error when `dir` isn't a craft-i18n `locales/` directory.
fn locales(dir: &std::path::Path) -> Result<Vec<Locale>, String> {
// Build scripts run in the crate's directory and `include_str!` resolves against `OUT_DIR`, so
// a relative path would mean two different places.
if !dir.is_absolute() {
return Err(format!("{} is not an absolute path", dir.display()));
}
let list = std::fs::read_dir(dir).map_err(|e| format!("{} is not a craft-i18n checkout ({e})", dir.display()))?;
println!("cargo::rerun-if-changed={}", dir.display());
let mut out = Vec::new();
for entry in list.flatten() {
let path = entry.path();
let Some(code) = path.file_name().and_then(std::ffi::OsStr::to_str).map(str::to_string) else { continue };
let app = path.join(APP_CATALOG);
if !app.is_file() {
continue;
}
println!("cargo::rerun-if-changed={}", app.display());
let shared = Some(path.join("common.tsv")).filter(|p| p.is_file());
if let Some(p) = &shared {
println!("cargo::rerun-if-changed={}", p.display());
}
out.push((code, shared, app));
}
if out.is_empty() {
return Err(format!("{} has no locale with a {APP_CATALOG}", dir.display()));
}
out.sort();
Ok(out)
}

fn read<T>(path: &std::path::Path, fallback: &str, parse: impl Fn(&str) -> Result<T, String>) -> Option<T> {
Expand Down
2 changes: 2 additions & 0 deletions crates/ui-egui/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,8 @@ impl SlideApp {
self.requeue_injected_synthetic();
return;
}
// The language every label this pass draws is looked up in (Preferences › Language).
crate::i18n::set_current(crate::i18n::Lang::from_pref(&self.ui.language));
let t0 = now_ms();
let t = theme::Tokens::get(&ctx);
self.note_ui_text(&ctx);
Expand Down
Loading