Skip to content

web: print straight to the system print dialog, not just export - #66

Open
Dejan-Port wants to merge 3 commits into
storytold:mainfrom
Dejan-Port:print-to-system-dialog
Open

Dejan-Port wants to merge 3 commits into
storytold:mainfrom
Dejan-Port:print-to-system-dialog

Conversation

@Dejan-Port

Copy link
Copy Markdown

What

DeckCraft currently has no way to print at all — no file.print command (only file.export, which always writes to a path), and no Print entry anywhere in the UI, not even behind a shortcut.

This adds a print path that goes straight to the browser's native print dialog instead of forcing a download first:

  • Services::print, a web-only hook (PDF bytes in, system print dialog out), alongside the existing download hook
  • SlideApp::print_now(), which builds the PDF via the engine's cmd::file::pdf_bytes (the same lowest-level function file.export already uses under the hood) and hands the bytes to the hook
  • Ctrl/Cmd+P wired up in ui_shortcut — doesn't collide with the existing Ctrl+Shift+P command palette shortcut
  • a "Print…" entry in the File menu, right after "Export…"
  • the web runner's print service implementation: PDF bytes → Blob → object URL → hidden <iframe> → iframe.contentWindow().print() once the iframe has finished loading

Why

Export-only means every print is a round trip through the OS's file picker and PDF viewer just to get to a print dialog. This skips straight to it, the same way Word/Excel/PowerPoint's own Ctrl+P does.

Mirrors the same feature already added to WordCraft (storytold/wordcraft#209) and GridCraft (storytold/gridcraft#143).

Testing

Built with trunk build --release, deployed, and smoke-tested in Firefox: Ctrl+P and File → Print… both open the system print dialog on the current presentation's PDF.

🤖 Generated with Claude Code

Dejan-Port and others added 2 commits October 10, 2026 12:40
DeckCraft has no file.print command and no Print entry anywhere in the
UI — only Export…, which always writes a file. This adds a print path
that goes straight to the browser's native print dialog:

- Services::print (web-only hook, PDF bytes in -> system print dialog)
- SlideApp::print_now(), building the PDF via the engine's
  cmd::file::pdf_bytes (the same lowest-level function export already
  uses) and handing the bytes to the hook
- Ctrl/Cmd+P wired in ui_shortcut (doesn't collide with the existing
  Ctrl+Shift+P command palette shortcut)
- a Print… entry in the File menu, after Export…
- the web runner's print service: PDF bytes -> Blob -> object URL ->
  hidden iframe -> iframe.contentWindow().print() once it has loaded

Mirrors the same feature already added to WordCraft (storytold/wordcraft#209)
and GridCraft (storytold/gridcraft#143).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
DeckCraft had no File-tab equivalent at all — only a hamburger-style
File menu entry (used for the native macOS menu bar) and a separate
Home/start screen, with no single place mirroring Word's or Excel's
backstage view.

This adds a File tab (first in the ribbon, like real PowerPoint) that
opens a full-screen backstage panel:

- New (blank presentation + recents)
- Open (browse… + recents)
- Save / Save As (run immediately, same as the title bar's Save button)
- Print (the system print dialog added earlier on this branch)
- Export (opens the existing Export dialog, same formats as before)
- About

The panel is filled with PowerPoint's red (0xB7272C), fixed regardless
of light/dark mode — the same treatment WordCraft's backstage gives
its blue and the matching addition gives GridCraft's green.

Builds on this branch's existing print commit; no behavior changes to
anything outside the new File tab and backstage module.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@Dejan-Port

Copy link
Copy Markdown
Author

Pushed a second commit here: a File tab + full backstage panel (New/Open/Save/Print/Export/About), filled with PowerPoint's red, mirroring WordCraft's blue backstage (storytold/wordcraft#209) and the matching one just added to GridCraft (storytold/gridcraft#143). DeckCraft had no File-tab equivalent at all before this, so it seemed natural to land alongside the print feature rather than as a separate PR, since Print is one of its pages.

🤖 Generated with Claude Code

Same shape as storytold/wordcraft#250 and storytold/gridcraft#143's
most recent commit: DeckCraft had no localization at all, so this adds
a catalog-based i18n system (crates/ui-egui/src/i18n — generic, ported
with only the env var and a doc-comment product name changed) plus
Serbian in both scripts.

- lib.rs: a `tl!` macro, an `i18n` module, a persisted `language`
  setting (default: follow the OS), `i18n::set_current` once per
  frame.
- Translation at a few high-leverage points rather than per call site:
  `big_button`/`big_button_ex`/`small_button`/`small_button_ex`/
  `icon_toggle`/`color_split`/`color_grid`/`section`/`drop_button` in
  widgets.rs translate their own label/tip, so every ribbon caller
  picks it up for free. `dialogs.rs`'s `title()` (every dialog window
  title goes through one match statement), `buttons()` (OK/Cancel),
  `field()` and `check()` (the generic field renderers used by most of
  the simple dialogs) do the same. The 19 remaining hardcoded
  `.on_hover_text("...")` call sites in ribbon.rs/credits.rs (DeckCraft
  attaches tooltips to the Response after the widget call, unlike
  GridCraft's widgets which take a `tip` param directly, so these
  couldn't be swept up the same way) are wrapped individually. Title
  bar, ribbon tab names and the File backstage are translated directly
  where they're drawn. The backstage gets a new Options page with the
  language picker.
- 216 strings, clean-room translated into Serbian Latin (sr-latn.tsv),
  then mechanically transliterated into Cyrillic (sr.tsv).
- 11 tests: locale matching, catalog well-formedness (including that a
  translation's leading/trailing whitespace matches its English
  source's — DeckCraft has a couple of intentionally-indented field
  labels, unlike WordCraft/GridCraft, so the stricter blanket check
  those two use doesn't fit here), full key-set parity between
  scripts, and font glyph coverage checked against the actual bytes of
  egui's bundled default font (Ubuntu-Light) for both Cyrillic and
  Serbian Latin's š č ć ž đ — DeckCraft's own fonts (deckcraft-fonts)
  only embed real files with CRAFT_FONTS_DIR set at build time, which
  wasn't available building this.

Scope: ribbon, dialog titles/fields/buttons, File backstage. Several
dialog bodies (table/chart data editors, SmartArt picker, equation
editor) draw bespoke UI outside the shared helpers and aren't covered
yet. Missing entries just show in English, so this is safe to land
incrementally.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@Dejan-Port

Copy link
Copy Markdown
Author

Pushed one more commit: an interface-language system with Serbian, both scripts (sr Cyrillic, sr-latn Latin) — same pairing as storytold/wordcraft#250 and the matching addition to storytold/gridcraft#143.

DeckCraft had zero localization before this. Translation happens at a few high-leverage points rather than at each of the hundreds of call sites: the shared ribbon widgets (big_button, icon_toggle, color_grid, section, drop_button, etc.) translate their own label/tip internally, and dialogs.rs's title() (every dialog window title goes through this one match statement), buttons(), field() and check() do the same — so every existing and future caller picks up translation for free. DeckCraft attaches tooltips via .on_hover_text(...) chained onto the Response rather than as a widget parameter (unlike GridCraft), so the 19 call sites doing that are wrapped individually. Title bar, ribbon tab names and the File backstage are translated directly, and the backstage gets a new Options page with the language picker.

216 strings, clean-room translated, then mechanically transliterated into Cyrillic. 11 tests, including one that checks actual font bytes — DeckCraft's own fonts only embed real files with CRAFT_FONTS_DIR set at build time (not available here), so I verified egui's own default font (Ubuntu-Light) genuinely has the Cyrillic and Serbian Latin (š č ć ž đ) glyphs used, rather than assuming.

Scope, stated plainly: ribbon, dialog titles/fields/buttons, File backstage. A few dialog bodies (table/chart data editors, the SmartArt picker, the equation editor) draw bespoke UI outside the shared helpers and aren't covered yet — a string without a translation just shows in English, so more coverage can land incrementally.

🤖 Generated with Claude Code

anishguduru pushed a commit to anishguduru/deckcraft that referenced this pull request Oct 16, 2026
Windows on ARM64: release builds, tested on ARM64 hardware (storytold#61)
anishguduru pushed a commit to anishguduru/deckcraft that referenced this pull request Oct 16, 2026
It was only for finding the WARP crash (fixed in the previous commit) and
was merged with storytold#66 by accident; it ran a 60-iteration crash-dump loop and
edited the runner's registry on every push.

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

Copy link
Copy Markdown
Contributor

This PR conflicts with main (Cargo.lock, web.rs, backstage.rs, dialogs.rs, lib.rs, ribbon.rs, widgets.rs and more), so this review covers the diff and the commit list only. Nothing was built; I did not run a web build or a browser.

The branch has three commits, and the title and description cover only the first:

  1. web: print straight to the system print dialog, not just export: the print_pdf hidden-iframe helper in deckcraft-web.
  2. ui: File backstage (New/Open/Save/Print/Export), PowerPoint red: adds a 165-line crates/ui-egui/src/backstage.rs with its own page list and APP_COLOR.
  3. i18n: add an interface language system, with Serbian (sr, sr-latn): a catalog parser, sys-locale and ttf-parser dependencies, Serbian catalogs, and tl! wrapping across the dialogs and menus.

What I found against current main:

  • Main already has a full crates/ui-egui/src/backstage.rs (with a print module, print scope/range/layout options and the Export page), so commit 2 is an add/add conflict that would replace it with a much smaller page. The description's "no Print entry anywhere in the UI" no longer matches main.
  • On the web, main's SlideApp::print_pdf (app/files.rs) builds the PDF and downloads it when there is no pick_save. So the one thing in commit 1 that main lacks is the hand-off to the browser's print dialog; I did not find Services::print or print_now on main.
  • Commit 3 adds an interface-language framework. Main has none (dialogs/language.rs is the proofing language), but other open PRs add one too (i18n: add Russian, Spanish, French, Portuguese and German interfaces #110, i18n(ja): Japanese menus, ribbon and status bar (first step) #1119), so these overlap and will conflict with each other.

On the web print code itself: it builds a Blob and object URL, appends a hidden display:none iframe, sets onload before src, and calls contentWindow.print(). It never removes the iframe or revokes the URL, which the comment says is deliberate, so each print leaves one iframe and blob for the life of the page. The description reports a Firefox test only. I did not test other browsers; printing a PDF from a display:none iframe is, from what I know, not reliable in every browser, so it is worth checking in Chrome/Edge and Safari.

Suggestion: split commit 1 into its own PR rebased on main, wired into print_pdf's web branch, and drop commits 2 and 3 from this one.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants