From ae9d525bce365083bb62b4754bdd346feb2a79b0 Mon Sep 17 00:00:00 2001
From: Brandon Thomas
Date: Tue, 6 Oct 2026 01:46:40 -0400
Subject: [PATCH] Rename SlideCraft to DeckCraft: crates, apps, binaries, app
id, file extension (.deckcraft; .slidecraft files still open), packaging,
docs; ROADMAP alpha and parity estimates
Co-Authored-By: Claude Opus 5.5
---
.github/workflows/packaging-lint.yml | 12 +-
.github/workflows/release.yml | 54 +-
AGENTS.md | 22 +-
ATTRIBUTION.md | 18 +-
CLAUDE.md | 6 +-
Cargo.lock | 592 +++++++++---------
Cargo.toml | 50 +-
LICENSE-MIT | 2 +-
NOTICE | 4 +-
README.md | 40 +-
ROADMAP.md | 73 ++-
apps/deckcraft-cli/Cargo.toml | 19 +
.../src/main.rs | 68 +-
.../Cargo.toml | 10 +-
.../Trunk.toml | 2 +-
apps/deckcraft-web/index.html | 20 +
.../src/main.rs | 6 +-
.../src/web.rs | 20 +-
apps/{slidecraft => deckcraft}/Cargo.toml | 14 +-
apps/deckcraft/build.rs | 30 +
apps/{slidecraft => deckcraft}/src/audio.rs | 4 +-
.../src/control_server.rs | 8 +-
apps/{slidecraft => deckcraft}/src/main.rs | 60 +-
apps/slidecraft-cli/Cargo.toml | 19 -
apps/slidecraft-web/index.html | 20 -
apps/slidecraft/build.rs | 30 -
...slidecraft-1024.png => deckcraft-1024.png} | Bin
.../{slidecraft.icns => deckcraft.icns} | Bin
.../{slidecraft.ico => deckcraft.ico} | Bin
...craft.png => ai.storyteller.deckcraft.png} | Bin
...craft.png => ai.storyteller.deckcraft.png} | Bin
...craft.png => ai.storyteller.deckcraft.png} | Bin
...craft.png => ai.storyteller.deckcraft.png} | Bin
...craft.png => ai.storyteller.deckcraft.png} | Bin
...craft.png => ai.storyteller.deckcraft.png} | Bin
...craft.png => ai.storyteller.deckcraft.png} | Bin
...craft.png => ai.storyteller.deckcraft.png} | Bin
...ource.slidecraft => icon-source.deckcraft} | Bin
crates/anim/Cargo.toml | 8 +-
crates/anim/src/effects.rs | 2 +-
crates/anim/src/lib.rs | 4 +-
crates/anim/src/morph.rs | 2 +-
crates/anim/src/show.rs | 2 +-
crates/anim/src/tests.rs | 4 +-
crates/anim/src/timeline.rs | 16 +-
crates/anim/src/transition.rs | 4 +-
crates/av1/Cargo.toml | 4 +-
crates/bitstream/Cargo.toml | 2 +-
crates/bitstream/src/lib.rs | 2 +-
crates/color/Cargo.toml | 4 +-
crates/color/src/lib.rs | 2 +-
crates/engine/Cargo.toml | 24 +-
crates/engine/src/cmd/animation.rs | 4 +-
crates/engine/src/cmd/arrange.rs | 6 +-
crates/engine/src/cmd/design.rs | 52 +-
crates/engine/src/cmd/edit.rs | 22 +-
crates/engine/src/cmd/file.rs | 64 +-
crates/engine/src/cmd/format.rs | 18 +-
crates/engine/src/cmd/insert.rs | 54 +-
crates/engine/src/cmd/inspect.rs | 10 +-
crates/engine/src/cmd/media.rs | 10 +-
crates/engine/src/cmd/merge.rs | 12 +-
crates/engine/src/cmd/mod.rs | 40 +-
crates/engine/src/cmd/review.rs | 28 +-
crates/engine/src/cmd/shape.rs | 74 +--
crates/engine/src/cmd/slide.rs | 20 +-
crates/engine/src/cmd/table.rs | 7 +-
crates/engine/src/cmd/text.rs | 42 +-
crates/engine/src/cmd/transition.rs | 4 +-
crates/engine/src/connect.rs | 6 +-
crates/engine/src/keys.rs | 2 +-
crates/engine/src/lib.rs | 22 +-
crates/engine/src/links.rs | 6 +-
crates/engine/src/recovery.rs | 24 +-
crates/engine/src/sample.rs | 8 +-
crates/engine/src/tests.rs | 22 +-
crates/engine/src/tools.rs | 58 +-
crates/fonts/Cargo.toml | 4 +-
crates/fonts/build.rs | 4 +-
crates/fonts/src/lib.rs | 4 +-
crates/format/Cargo.toml | 6 +-
crates/format/src/lib.rs | 32 +-
crates/geom/Cargo.toml | 4 +-
crates/geom/src/lib.rs | 2 +-
crates/h264/Cargo.toml | 4 +-
crates/h264/src/cavlc.rs | 4 +-
crates/h264/src/deblock.rs | 2 +-
crates/h264/src/decoder.rs | 2 +-
crates/h264/src/error.rs | 2 +-
crates/h264/src/lib.rs | 2 +-
crates/h264/src/params.rs | 4 +-
crates/h264/src/slice.rs | 2 +-
crates/h264/src/slicedec.rs | 2 +-
crates/h264/src/synth_tests.rs | 2 +-
crates/hevc/Cargo.toml | 4 +-
crates/hevc/src/decoder.rs | 2 +-
crates/hevc/src/error.rs | 2 +-
crates/hevc/src/lib.rs | 2 +-
crates/hevc/src/params.rs | 2 +-
crates/hevc/src/slice.rs | 2 +-
crates/hevc/src/slicedec.rs | 2 +-
crates/hevc/src/synth_tests.rs | 2 +-
crates/isobmff/Cargo.toml | 4 +-
crates/isobmff/src/codec.rs | 2 +-
crates/matroska/Cargo.toml | 2 +-
crates/matroska/src/lib.rs | 2 +-
crates/matroska/src/mux.rs | 4 +-
crates/matroska/src/source.rs | 2 +-
crates/mcp/Cargo.toml | 8 +-
crates/mcp/src/backend.rs | 8 +-
crates/mcp/src/headless.rs | 24 +-
crates/mcp/src/lib.rs | 10 +-
crates/mcp/src/server.rs | 16 +-
crates/mcp/src/tests.rs | 2 +-
crates/mcp/src/tools.rs | 12 +-
crates/media/Cargo.toml | 20 +-
crates/media/src/audio.rs | 14 +-
crates/media/src/feed.rs | 2 +-
crates/media/src/lib.rs | 2 +-
crates/media/src/player.rs | 2 +-
crates/media/src/probe.rs | 18 +-
crates/media/src/video.rs | 58 +-
crates/media/tests/decode.rs | 6 +-
crates/media/tests/fixtures/make.sh | 8 +-
crates/model/Cargo.toml | 8 +-
crates/model/src/defaults.rs | 8 +-
crates/model/src/lib.rs | 12 +-
crates/model/src/resolve.rs | 4 +-
crates/model/src/style.rs | 6 +-
crates/model/src/tests.rs | 2 +-
crates/model/src/theme.rs | 12 +-
crates/ogg/Cargo.toml | 2 +-
crates/opus/Cargo.toml | 2 +-
crates/pdf/Cargo.toml | 12 +-
crates/pdf/src/lib.rs | 24 +-
crates/pdf/src/tests.rs | 6 +-
crates/pptx/Cargo.toml | 16 +-
crates/pptx/src/custgeom.rs | 4 +-
crates/pptx/src/lib.rs | 4 +-
crates/pptx/src/maps.rs | 12 +-
crates/pptx/src/read/chart.rs | 8 +-
crates/pptx/src/read/dml.rs | 10 +-
crates/pptx/src/read/mod.rs | 14 +-
crates/pptx/src/read/shapes.rs | 14 +-
crates/pptx/src/read/theme.rs | 8 +-
crates/pptx/src/read/timing.rs | 6 +-
crates/pptx/src/tables.rs | 16 +-
crates/pptx/src/write/chart.rs | 6 +-
crates/pptx/src/write/dml.rs | 14 +-
crates/pptx/src/write/mod.rs | 40 +-
crates/pptx/src/write/shapes.rs | 40 +-
crates/pptx/src/write/theme.rs | 6 +-
crates/pptx/src/write/timing.rs | 6 +-
crates/pptx/tests/malformed.rs | 64 +-
crates/pptx/tests/roundtrip.rs | 60 +-
crates/pptx/tests/sample.rs | 32 +-
crates/render/Cargo.toml | 14 +-
crates/render/src/chart.rs | 26 +-
crates/render/src/images.rs | 2 +-
crates/render/src/lib.rs | 56 +-
crates/render/src/paint.rs | 10 +-
crates/render/src/placed.rs | 12 +-
crates/render/src/table.rs | 22 +-
crates/render/src/tests.rs | 26 +-
crates/text/Cargo.toml | 12 +-
crates/text/src/lib.rs | 20 +-
crates/text/src/tests.rs | 8 +-
crates/ui-egui/Cargo.toml | 22 +-
crates/ui-egui/examples/icon_gallery.rs | 6 +-
crates/ui-egui/src/canvas.rs | 44 +-
crates/ui-egui/src/control.rs | 30 +-
crates/ui-egui/src/dialogs.rs | 52 +-
crates/ui-egui/src/fillui.rs | 6 +-
crates/ui-egui/src/icons.rs | 2 +-
crates/ui-egui/src/lib.rs | 39 +-
crates/ui-egui/src/media.rs | 14 +-
crates/ui-egui/src/menus.rs | 4 +-
crates/ui-egui/src/panes.rs | 26 +-
crates/ui-egui/src/ribbon.rs | 166 ++---
crates/ui-egui/src/show.rs | 28 +-
crates/ui-egui/src/status.rs | 4 +-
crates/ui-egui/src/textures.rs | 8 +-
crates/ui-egui/src/theme.rs | 6 +-
crates/ui-egui/src/thumbs.rs | 6 +-
crates/ui-egui/src/widgets.rs | 10 +-
crates/vp9/Cargo.toml | 4 +-
crates/vp9/src/error.rs | 2 +-
crates/vp9/src/header.rs | 2 +-
crates/vp9/src/lib.rs | 2 +-
docs/brand/LICENSE-brand.txt | 12 +-
docs/control-protocol.md | 6 +-
docs/mcp.md | 10 +-
docs/parity.md | 26 +-
packaging/common.sh | 16 +-
packaging/freebsd/package.sh | 22 +-
packaging/icons.sh | 22 +-
.../linux/ai.storyteller.deckcraft.desktop | 14 +
... ai.storyteller.deckcraft.metainfo.xml.in} | 20 +-
....xml => ai.storyteller.deckcraft.mime.xml} | 8 +-
.../linux/ai.storyteller.slidecraft.desktop | 14 -
...craft.yml => ai.storyteller.deckcraft.yml} | 24 +-
packaging/linux/nfpm.yaml | 16 +-
packaging/linux/package.sh | 38 +-
packaging/macos/Info.plist.in | 28 +-
packaging/macos/entitlements.plist | 2 +-
packaging/macos/import-cert.sh | 4 +-
packaging/macos/package.sh | 44 +-
packaging/web/README.md | 26 +-
packaging/web/package.sh | 8 +-
packaging/windows/deckcraft.wxs | 80 +++
packaging/windows/package.ps1 | 38 +-
packaging/windows/sign.ps1 | 6 +-
packaging/windows/slidecraft.wxs | 80 ---
xtask/src/assets.rs | 2 +-
xtask/src/ico.rs | 2 +-
xtask/src/layers.rs | 58 +-
216 files changed, 2021 insertions(+), 1998 deletions(-)
create mode 100644 apps/deckcraft-cli/Cargo.toml
rename apps/{slidecraft-cli => deckcraft-cli}/src/main.rs (78%)
rename apps/{slidecraft-web => deckcraft-web}/Cargo.toml (77%)
rename apps/{slidecraft-web => deckcraft-web}/Trunk.toml (67%)
create mode 100644 apps/deckcraft-web/index.html
rename apps/{slidecraft-web => deckcraft-web}/src/main.rs (82%)
rename apps/{slidecraft-web => deckcraft-web}/src/web.rs (90%)
rename apps/{slidecraft => deckcraft}/Cargo.toml (70%)
create mode 100644 apps/deckcraft/build.rs
rename apps/{slidecraft => deckcraft}/src/audio.rs (93%)
rename apps/{slidecraft => deckcraft}/src/control_server.rs (87%)
rename apps/{slidecraft => deckcraft}/src/main.rs (78%)
delete mode 100644 apps/slidecraft-cli/Cargo.toml
delete mode 100644 apps/slidecraft-web/index.html
delete mode 100644 apps/slidecraft/build.rs
rename assets/app-icon/{slidecraft-1024.png => deckcraft-1024.png} (100%)
rename assets/app-icon/{slidecraft.icns => deckcraft.icns} (100%)
rename assets/app-icon/{slidecraft.ico => deckcraft.ico} (100%)
rename assets/app-icon/hicolor/128x128/apps/{ai.storyteller.slidecraft.png => ai.storyteller.deckcraft.png} (100%)
rename assets/app-icon/hicolor/16x16/apps/{ai.storyteller.slidecraft.png => ai.storyteller.deckcraft.png} (100%)
rename assets/app-icon/hicolor/24x24/apps/{ai.storyteller.slidecraft.png => ai.storyteller.deckcraft.png} (100%)
rename assets/app-icon/hicolor/256x256/apps/{ai.storyteller.slidecraft.png => ai.storyteller.deckcraft.png} (100%)
rename assets/app-icon/hicolor/32x32/apps/{ai.storyteller.slidecraft.png => ai.storyteller.deckcraft.png} (100%)
rename assets/app-icon/hicolor/48x48/apps/{ai.storyteller.slidecraft.png => ai.storyteller.deckcraft.png} (100%)
rename assets/app-icon/hicolor/512x512/apps/{ai.storyteller.slidecraft.png => ai.storyteller.deckcraft.png} (100%)
rename assets/app-icon/hicolor/64x64/apps/{ai.storyteller.slidecraft.png => ai.storyteller.deckcraft.png} (100%)
rename assets/app-icon/{icon-source.slidecraft => icon-source.deckcraft} (100%)
create mode 100644 packaging/linux/ai.storyteller.deckcraft.desktop
rename packaging/linux/{ai.storyteller.slidecraft.metainfo.xml.in => ai.storyteller.deckcraft.metainfo.xml.in} (74%)
rename packaging/linux/{ai.storyteller.slidecraft.mime.xml => ai.storyteller.deckcraft.mime.xml} (51%)
delete mode 100644 packaging/linux/ai.storyteller.slidecraft.desktop
rename packaging/linux/flatpak/{ai.storyteller.slidecraft.yml => ai.storyteller.deckcraft.yml} (67%)
create mode 100644 packaging/windows/deckcraft.wxs
delete mode 100644 packaging/windows/slidecraft.wxs
diff --git a/.github/workflows/packaging-lint.yml b/.github/workflows/packaging-lint.yml
index d7362dc..e3919d9 100644
--- a/.github/workflows/packaging-lint.yml
+++ b/.github/workflows/packaging-lint.yml
@@ -46,12 +46,12 @@ jobs:
}
- name: XML, desktop entry, AppStream, Flatpak manifest
run: |
- xmllint --noout packaging/windows/slidecraft.wxs packaging/macos/*.plist* packaging/linux/*.xml*
+ xmllint --noout packaging/windows/deckcraft.wxs packaging/macos/*.plist* packaging/linux/*.xml*
# Windows Installer needs Icon ids ending in .ico or .exe, or shortcuts show a blank icon.
- if grep -oE '"$RUNNER_TEMP/ai.storyteller.slidecraft.metainfo.xml"
- appstreamcli validate --no-net --explain "$RUNNER_TEMP/ai.storyteller.slidecraft.metainfo.xml"
- python3 -c "import yaml; m = yaml.safe_load(open('packaging/linux/flatpak/ai.storyteller.slidecraft.yml')); assert m['id'] == 'ai.storyteller.slidecraft' and m['command'] == 'slidecraft'"
+ desktop-file-validate packaging/linux/ai.storyteller.deckcraft.desktop
+ sed -e 's/@VERSION@/0.0.0/' -e 's/@DATE@/2026-01-01/' packaging/linux/ai.storyteller.deckcraft.metainfo.xml.in >"$RUNNER_TEMP/ai.storyteller.deckcraft.metainfo.xml"
+ appstreamcli validate --no-net --explain "$RUNNER_TEMP/ai.storyteller.deckcraft.metainfo.xml"
+ python3 -c "import yaml; m = yaml.safe_load(open('packaging/linux/flatpak/ai.storyteller.deckcraft.yml')); assert m['id'] == 'ai.storyteller.deckcraft' and m['command'] == 'deckcraft'"
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index b660831..9c85b4f 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -1,5 +1,5 @@
# Release pipeline: every push to `release` builds installers for every platform and creates or
-# updates the draft GitHub Release "SlideCraft v" (tag v). Recipe:
+# updates the draft GitHub Release "DeckCraft v" (tag v). Recipe:
# craftrules release/playbook.md.
#
# The version has one source: `[workspace.package] version` in Cargo.toml (`cargo xtask version`,
@@ -33,7 +33,7 @@ env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: 0
RUST_BACKTRACE: 1
- SLIDECRAFT_BUILD_SHA: ${{ github.sha }}
+ DECKCRAFT_BUILD_SHA: ${{ github.sha }}
INPUT_VERSION: ${{ inputs.version }}
# Optional font input (storytold/craft-fonts docs/integration.md); releases always embed it.
# This commit adds fonts/latin-manifest.txt (the Latin presentation fonts).
@@ -66,7 +66,7 @@ jobs:
echo "date=$(date -u +%Y-%m-%d)"
echo "prerelease=$pre"
} >>"$GITHUB_OUTPUT"
- echo "### SlideCraft v$v (\`${GITHUB_SHA::9}\`)" >>"$GITHUB_STEP_SUMMARY"
+ echo "### DeckCraft v$v (\`${GITHUB_SHA::9}\`)" >>"$GITHUB_STEP_SUMMARY"
macos:
name: macOS universal
@@ -74,8 +74,8 @@ jobs:
runs-on: macos-15
environment: release
env:
- SLIDECRAFT_VERSION: ${{ needs.version.outputs.version }}
- SLIDECRAFT_BUILD_DATE: ${{ needs.version.outputs.date }}
+ DECKCRAFT_VERSION: ${{ needs.version.outputs.version }}
+ DECKCRAFT_BUILD_DATE: ${{ needs.version.outputs.date }}
steps:
- uses: actions/checkout@v4
- name: Check out craft-fonts
@@ -92,7 +92,7 @@ jobs:
key: release-macos-universal
- name: Apply version override
if: inputs.version != ''
- run: cargo xtask version set "$SLIDECRAFT_VERSION"
+ run: cargo xtask version set "$DECKCRAFT_VERSION"
- name: Import signing certificate
env:
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
@@ -129,8 +129,8 @@ jobs:
- { arch: x64, target: x86_64-pc-windows-msvc }
- { arch: x86, target: i686-pc-windows-msvc }
env:
- SLIDECRAFT_VERSION: ${{ needs.version.outputs.version }}
- SLIDECRAFT_BUILD_DATE: ${{ needs.version.outputs.date }}
+ DECKCRAFT_VERSION: ${{ needs.version.outputs.version }}
+ DECKCRAFT_BUILD_DATE: ${{ needs.version.outputs.date }}
steps:
- uses: actions/checkout@v4
- name: Check out craft-fonts
@@ -148,7 +148,7 @@ jobs:
- name: Apply version override
if: inputs.version != ''
shell: bash
- run: cargo xtask version set "$SLIDECRAFT_VERSION"
+ run: cargo xtask version set "$DECKCRAFT_VERSION"
- name: Install WiX
shell: pwsh
run: dotnet tool install --global wix --version 5.0.2
@@ -186,8 +186,8 @@ jobs:
- { arch: x86_64, runner: ubuntu-22.04, deb: amd64 }
- { arch: aarch64, runner: ubuntu-22.04-arm, deb: arm64 }
env:
- SLIDECRAFT_VERSION: ${{ needs.version.outputs.version }}
- SLIDECRAFT_BUILD_DATE: ${{ needs.version.outputs.date }}
+ DECKCRAFT_VERSION: ${{ needs.version.outputs.version }}
+ DECKCRAFT_BUILD_DATE: ${{ needs.version.outputs.date }}
NFPM_VERSION: 2.47.0
steps:
- uses: actions/checkout@v4
@@ -212,7 +212,7 @@ jobs:
key: release-linux-${{ matrix.arch }}
- name: Apply version override
if: inputs.version != ''
- run: cargo xtask version set "$SLIDECRAFT_VERSION"
+ run: cargo xtask version set "$DECKCRAFT_VERSION"
- name: Build and package (AppImage, deb, rpm, tar.gz)
env:
CRAFT_FONTS_DIR: ${{ github.workspace }}/craft-fonts
@@ -221,8 +221,8 @@ jobs:
- name: Check the packages
run: |
dpkg-deb --info dist/release/*.deb
- dpkg-deb --contents dist/release/*.deb | grep -E 'usr/bin/slidecraft$|metainfo|applications'
- ldd target/release/slidecraft
+ dpkg-deb --contents dist/release/*.deb | grep -E 'usr/bin/deckcraft$|metainfo|applications'
+ ldd target/release/deckcraft
for appimage in dist/release/*.AppImage; do
chmod +x "$appimage"
APPIMAGE_EXTRACT_AND_RUN=1 "$appimage" --version
@@ -244,14 +244,14 @@ jobs:
image: ghcr.io/flathub-infra/flatpak-github-actions:freedesktop-25.08
options: --privileged
env:
- SLIDECRAFT_VERSION: ${{ needs.version.outputs.version }}
+ DECKCRAFT_VERSION: ${{ needs.version.outputs.version }}
steps:
- uses: actions/checkout@v4
- name: Apply version override
if: inputs.version != ''
run: |
# No cargo in this container: the same edit `cargo xtask version set` makes.
- sed -i "0,/^version = \".*\"/s//version = \"$SLIDECRAFT_VERSION\"/" Cargo.toml
+ sed -i "0,/^version = \".*\"/s//version = \"$DECKCRAFT_VERSION\"/" Cargo.toml
grep -m1 '^version' Cargo.toml
- name: Vendor the crate sources (flatpak builds run offline)
run: |
@@ -262,14 +262,14 @@ jobs:
python3 "$RUNNER_TEMP/flatpak-cargo-generator.py" Cargo.lock -o packaging/linux/flatpak/cargo-sources.json
- uses: flatpak/flatpak-github-actions/flatpak-builder@v6
with:
- manifest-path: packaging/linux/flatpak/ai.storyteller.slidecraft.yml
- bundle: slidecraft-${{ needs.version.outputs.version }}-linux-x86_64.flatpak
+ manifest-path: packaging/linux/flatpak/ai.storyteller.deckcraft.yml
+ bundle: deckcraft-${{ needs.version.outputs.version }}-linux-x86_64.flatpak
upload-artifact: false
cache-key: flatpak-builder-${{ github.sha }}
- uses: actions/upload-artifact@v4
with:
name: release-flatpak
- path: slidecraft-*.flatpak
+ path: deckcraft-*.flatpak
if-no-files-found: error
freebsd:
@@ -279,8 +279,8 @@ jobs:
environment: release
timeout-minutes: 240
env:
- SLIDECRAFT_VERSION: ${{ needs.version.outputs.version }}
- SLIDECRAFT_BUILD_DATE: ${{ needs.version.outputs.date }}
+ DECKCRAFT_VERSION: ${{ needs.version.outputs.version }}
+ DECKCRAFT_BUILD_DATE: ${{ needs.version.outputs.date }}
VERSION_OVERRIDE: ${{ inputs.version }}
steps:
- uses: actions/checkout@v4
@@ -297,7 +297,7 @@ jobs:
usesh: true
cpu: 4
mem: 12288
- envs: SLIDECRAFT_VERSION SLIDECRAFT_BUILD_DATE SLIDECRAFT_BUILD_SHA VERSION_OVERRIDE
+ envs: DECKCRAFT_VERSION DECKCRAFT_BUILD_DATE DECKCRAFT_BUILD_SHA VERSION_OVERRIDE
# Copies the workspace (with dist/release) back; the target dir lives outside it so the
# copy stays small.
copyback: true
@@ -308,7 +308,7 @@ jobs:
. "$HOME/.cargo/env"
git config --global --add safe.directory "*"
export CARGO_TARGET_DIR="$HOME/target" CRAFT_FONTS_DIR="$PWD/craft-fonts" CRAFT_FONTS_REQUIRED=1
- if [ -n "$VERSION_OVERRIDE" ]; then cargo xtask version set "$SLIDECRAFT_VERSION"; fi
+ if [ -n "$VERSION_OVERRIDE" ]; then cargo xtask version set "$DECKCRAFT_VERSION"; fi
bash packaging/freebsd/package.sh
- uses: actions/upload-artifact@v4
with:
@@ -322,8 +322,8 @@ jobs:
runs-on: ubuntu-latest
environment: release
env:
- SLIDECRAFT_VERSION: ${{ needs.version.outputs.version }}
- SLIDECRAFT_BUILD_DATE: ${{ needs.version.outputs.date }}
+ DECKCRAFT_VERSION: ${{ needs.version.outputs.version }}
+ DECKCRAFT_BUILD_DATE: ${{ needs.version.outputs.date }}
TRUNK_VERSION: 0.21.14
steps:
- uses: actions/checkout@v4
@@ -346,7 +346,7 @@ jobs:
trunk --version
- name: Apply version override
if: inputs.version != ''
- run: cargo xtask version set "$SLIDECRAFT_VERSION"
+ run: cargo xtask version set "$DECKCRAFT_VERSION"
- name: Build and zip
env:
CRAFT_FONTS_DIR: ${{ github.workspace }}/craft-fonts
@@ -389,7 +389,7 @@ jobs:
working-directory: artifacts
run: |
tag="v$VERSION"
- title="SlideCraft v$VERSION"
+ title="DeckCraft v$VERSION"
pre=()
if [ "$PRERELEASE" = true ]; then pre=(--prerelease); fi
if draft="$(gh release view "$tag" --json isDraft --jq .isDraft 2>/dev/null)"; then
diff --git a/AGENTS.md b/AGENTS.md
index 7bcdc20..0e706a7 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -1,6 +1,6 @@
# AGENTS.md — rules for every agent and contributor
-SlideCraft is a clean-room, open-source, pure-Rust presentation application targeting Microsoft
+DeckCraft is a clean-room, open-source, pure-Rust presentation application targeting Microsoft
PowerPoint parity — and superiority (speed, openness, agent control). It runs natively on macOS,
Windows, Linux and FreeBSD, and on the web via WASM. Siblings with the same conventions:
PhotoCraft, VectorCraft, FilmCraft, LightCraft, PrintCraft, EffectCraft, DesignCraft
@@ -32,7 +32,7 @@ the app renders from those.
product" includes PowerPoint, Office, Windows and the fonts they install (Aptos, Calibri,
Cambria, Segoe, Consolas, Wingdings…).
- This holds even when the vendor publishes the material under an open licence, if it is **visual
- design**: SlideCraft does not embed or reference Adobe's Source Sans/Serif/Han families or Noto
+ design**: DeckCraft does not embed or reference Adobe's Source Sans/Serif/Han families or Noto
CJK (Source Han rebranded); `crates/fonts/build.rs` excludes them.
- **Never** recreate, trace, redraw or closely imitate a vendor icon, theme or graphic. Our icons
are drawn in code or come from openly licensed sets (Lucide, ISC), and our themes, colours,
@@ -58,7 +58,7 @@ Forbidden: system fonts that are not openly licensed, vendor emoji artwork, stoc
for personal use" material, screenshots of other people's software, anything whose licence you
cannot prove from its original source.
-**Screenshots of SlideCraft** are contributor-original. Everything visible in them must itself be
+**Screenshots of DeckCraft** are contributor-original. Everything visible in them must itself be
allowed: our UI, our/Lucide icons, OFL fonts, and decks built only from allowed assets.
### 1.3 Every asset is attributed
@@ -69,7 +69,7 @@ allowed: our UI, our/Lucide icons, OFL fonts, and decks built only from allowed
or `docs/brand/` has no row.
- **`NOTICE`** keeps the notices the licences require.
- Fonts live in [storytold/craft-fonts](https://github.com/storytold/craft-fonts), never in this
- repo (craftrules `standards/fonts.md`). SlideCraft embeds its `fonts/latin-manifest.txt` and
+ repo (craftrules `standards/fonts.md`). DeckCraft embeds its `fonts/latin-manifest.txt` and
`fonts/manifest.txt` when built with `CRAFT_FONTS_DIR` (local dev: `.cargo/config.toml` points at
`../craft-fonts`).
@@ -98,7 +98,7 @@ once and tell the user.
## 3. Never crash
-People trust SlideCraft with their talks; a crash loses work. **This outranks feature work.**
+People trust DeckCraft with their talks; a crash loses work. **This outranks feature work.**
Standard: craftrules `standards/never-crash.md`.
- No `unwrap()`, `expect()`, `panic!`, `unreachable!`, `todo!`, `unimplemented!` in non-test
code; no `unsafe` (`unsafe_code = "forbid"`). Every production crate root carries
@@ -118,11 +118,11 @@ Standard: craftrules `standards/never-crash.md`.
| L1 | `model` (presentation, masters, layouts, slides, shapes, text, themes, inheritance, text edit ops) |
| L2 | `fonts`, `text` (layout), |
| L3 | `render` (vello_cpu) |
-| L4 | `format` (.slidecraft), `pptx` |
+| L4 | `format` (.deckcraft), `pptx` |
| L5 | `engine` (session, history, commands, tools, keys, sample) |
| L6 | `mcp` |
| L7 | `ui-egui` (swappable; nothing below depends on egui/eframe/winit/rfd) |
-| apps | `slidecraft` (desktop), `slidecraft-cli`, `slidecraft-web` |
+| apps | `deckcraft` (desktop), `deckcraft-cli`, `deckcraft-web` |
- **Everything is a command** (`crates/engine/src/cmd/*`): id, label, ribbon/menu place,
shortcut, params doc, `enabled`, `run`, plus tests. Ids follow PowerPoint's ribbon and menus
@@ -141,16 +141,16 @@ to `main`.
## 6. Running and looking at the app
-- `cargo run --release -p slidecraft -- --sample --control 7979` (sample deck + control channel).
+- `cargo run --release -p deckcraft -- --sample --control 7979` (sample deck + control channel).
- Drive it: JSON lines on `127.0.0.1:7979`, e.g.
`{"id":1,"method":"engine.execute","params":{"command":"shape.insert","params":{"preset":"star5","rect":[100,100,200,200]}}}`
then `{"id":2,"method":"ui.screenshot","params":{"path":"/tmp/shot.png"}}`. Methods:
`crates/ui-egui/src/control.rs`, docs: `docs/control-protocol.md`.
- **For UI work, look at the result** (screenshot, read the PNG) and compare with the observations
in `plan/powerpoint/01-observed-ui.md`.
-- Headless: `slidecraft-cli render --sample --all --scale 1 out/`,
- `slidecraft-cli run --sample --cmd 'slide.new={"title":"Hi"}' --export out.png`.
-- MCP: `slidecraft-cli mcp` (headless) or `slidecraft-cli mcp --connect 7979` (drive the app).
+- Headless: `deckcraft-cli render --sample --all --scale 1 out/`,
+ `deckcraft-cli run --sample --cmd 'slide.new={"title":"Hi"}' --export out.png`.
+- MCP: `deckcraft-cli mcp` (headless) or `deckcraft-cli mcp --connect 7979` (drive the app).
See `docs/mcp.md`.
- Shell gotcha on the dev machine: `mv`/`cp` are aliased interactive; use `/bin/mv -f`, `/bin/cp -f`.
- Parallel agents: separate `CARGO_TARGET_DIR` per agent; edit only the crates you own.
diff --git a/ATTRIBUTION.md b/ATTRIBUTION.md
index 9ffb154..4567d53 100644
--- a/ATTRIBUTION.md
+++ b/ATTRIBUTION.md
@@ -1,12 +1,12 @@
# Attribution
-Every asset SlideCraft includes, bundles, embeds at build time or shows in its documentation, with
+Every asset DeckCraft includes, bundles, embeds at build time or shows in its documentation, with
its author, source and licence. The policy is in [AGENTS.md](AGENTS.md) §1. `cargo xtask assets`
fails if a file under `assets/`, `docs/images/` or `docs/brand/` is missing from this table.
-SlideCraft contains **no Microsoft, Adobe, Avid or Autodesk iconography, images, fonts, themes,
+DeckCraft contains **no Microsoft, Adobe, Avid or Autodesk iconography, images, fonts, themes,
templates or other assets**. Its themes, colour schemes, layouts, preset-shape geometry, sample
-deck and UI icons are original work of the SlideCraft contributors (drawn or generated in code).
+deck and UI icons are original work of the DeckCraft contributors (drawn or generated in code).
## In this repository
@@ -17,12 +17,12 @@ deck and UI icons are original work of the SlideCraft contributors (drawn or gen
| `docs/brand/artcraft-mark.svg`, `docs/brand/artcraft-mark.png` | ArtCraft Team | ArtCraft brand kit | ArtCraft trademark, `docs/brand/LICENSE-brand.txt` | README footer, About |
| `docs/brand/artcraft-mark-black.svg`, `docs/brand/artcraft-mark-black.png` | ArtCraft Team | ArtCraft brand kit | ArtCraft trademark, `docs/brand/LICENSE-brand.txt` | Mark for light backgrounds |
| `docs/brand/LICENSE-brand.txt` | ArtCraft Team | craftrules `standards/license-files` | — (licence text) | Terms for the ArtCraft marks |
-| `assets/app-icon/slidecraft-1024.png`, `assets/app-icon/hicolor/*/apps/ai.storyteller.slidecraft.png` | SlideCraft contributors | rendered by SlideCraft from `assets/app-icon/icon-source.slidecraft` (shapes only) | MIT OR Apache-2.0 | Placeholder app icon (presentation board) until the mascot icon is drawn |
-| `assets/app-icon/slidecraft.icns`, `assets/app-icon/slidecraft.ico` | SlideCraft contributors | generated from the PNGs above by `packaging/icons.sh` (sips/iconutil, `cargo xtask ico`) | MIT OR Apache-2.0 | macOS and Windows app icons |
-| `assets/app-icon/icon-source.slidecraft` | SlideCraft contributors | made with `slidecraft-cli run` (see git history) | MIT OR Apache-2.0 | Source of the placeholder icon |
-| `crates/media/tests/fixtures/tone*` (`tone.mp3`, `.m4a`, `.aac`, `.ogg`, `.opus`, `.flac`, `.wma`, `tone-alac.m4a`) | SlideCraft contributors | synthesised 0.5 s 440 Hz sine, encoded by `crates/media/tests/fixtures/make.sh` (ffmpeg as the encoder only) | CC0-1.0 | Audio decoding tests |
-| `crates/media/tests/fixtures/clip-*` (`clip-h264.mp4`, `clip-h264.mov`, `clip-hevc.mp4`, `clip-vp9.webm`, `clip-av1.mkv`) | SlideCraft contributors | 6 frames of a colour gradient generated by `make.sh` (python3), encoded by ffmpeg (x264 / x265 / libvpx / SVT-AV1 as encoders only) | CC0-1.0 | Video decoding tests |
-| `crates/media/tests/fixtures/make.sh` | SlideCraft contributors | this repository | MIT OR Apache-2.0 | Regenerates the media fixtures |
+| `assets/app-icon/deckcraft-1024.png`, `assets/app-icon/hicolor/*/apps/ai.storyteller.deckcraft.png` | DeckCraft contributors | rendered by DeckCraft from `assets/app-icon/icon-source.deckcraft` (shapes only) | MIT OR Apache-2.0 | Placeholder app icon (presentation board) until the mascot icon is drawn |
+| `assets/app-icon/deckcraft.icns`, `assets/app-icon/deckcraft.ico` | DeckCraft contributors | generated from the PNGs above by `packaging/icons.sh` (sips/iconutil, `cargo xtask ico`) | MIT OR Apache-2.0 | macOS and Windows app icons |
+| `assets/app-icon/icon-source.deckcraft` | DeckCraft contributors | made with `deckcraft-cli run` (see git history) | MIT OR Apache-2.0 | Source of the placeholder icon |
+| `crates/media/tests/fixtures/tone*` (`tone.mp3`, `.m4a`, `.aac`, `.ogg`, `.opus`, `.flac`, `.wma`, `tone-alac.m4a`) | DeckCraft contributors | synthesised 0.5 s 440 Hz sine, encoded by `crates/media/tests/fixtures/make.sh` (ffmpeg as the encoder only) | CC0-1.0 | Audio decoding tests |
+| `crates/media/tests/fixtures/clip-*` (`clip-h264.mp4`, `clip-h264.mov`, `clip-hevc.mp4`, `clip-vp9.webm`, `clip-av1.mkv`) | DeckCraft contributors | 6 frames of a colour gradient generated by `make.sh` (python3), encoded by ffmpeg (x264 / x265 / libvpx / SVT-AV1 as encoders only) | CC0-1.0 | Video decoding tests |
+| `crates/media/tests/fixtures/make.sh` | DeckCraft contributors | this repository | MIT OR Apache-2.0 | Regenerates the media fixtures |
## Bundled through dependencies
diff --git a/CLAUDE.md b/CLAUDE.md
index 820317c..2dcfdbe 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -1,4 +1,4 @@
-# SlideCraft — working notes for Claude
+# DeckCraft — working notes for Claude
Read [AGENTS.md](AGENTS.md) first: its rules (asset policy, clean room, never crash) bind you and
win over anything here.
@@ -10,8 +10,8 @@ win over anything here.
## Habits
- Every user-visible action is a command with tests (`crates/engine/src/cmd/*`).
-- Look at UI changes: `slidecraft --sample --control 7979` + `ui.screenshot`, or
- `slidecraft-cli render`.
+- Look at UI changes: `deckcraft --sample --control 7979` + `ui.screenshot`, or
+ `deckcraft-cli render`.
- Commit after each landed arc (`M.: what`) and push to `origin main`.
- Keep `ROADMAP.md` and `plan/STATUS.md` current.
- Don't ask the user questions; record genuinely-theirs decisions in `plan/STATUS.md`.
diff --git a/Cargo.lock b/Cargo.lock
index a5e6d15..6fd4c3d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -864,6 +864,302 @@ version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f"
+[[package]]
+name = "deckcraft"
+version = "0.1.0"
+dependencies = [
+ "arboard",
+ "cpal",
+ "deckcraft-engine",
+ "deckcraft-media",
+ "deckcraft-ui-egui",
+ "eframe",
+ "egui",
+ "image",
+ "log",
+ "rfd",
+ "serde_json",
+ "winit",
+ "winresource",
+]
+
+[[package]]
+name = "deckcraft-anim"
+version = "0.1.0"
+dependencies = [
+ "deckcraft-geom",
+ "deckcraft-model",
+ "serde",
+]
+
+[[package]]
+name = "deckcraft-av1"
+version = "0.1.0"
+dependencies = [
+ "deckcraft-bitstream",
+ "rayon",
+ "thiserror 2.0.21",
+]
+
+[[package]]
+name = "deckcraft-bitstream"
+version = "0.1.0"
+dependencies = [
+ "proptest",
+]
+
+[[package]]
+name = "deckcraft-cli"
+version = "0.1.0"
+dependencies = [
+ "deckcraft-engine",
+ "deckcraft-mcp",
+ "serde_json",
+]
+
+[[package]]
+name = "deckcraft-color"
+version = "0.1.0"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "deckcraft-engine"
+version = "0.1.0"
+dependencies = [
+ "deckcraft-color",
+ "deckcraft-fonts",
+ "deckcraft-format",
+ "deckcraft-geom",
+ "deckcraft-media",
+ "deckcraft-model",
+ "deckcraft-pdf",
+ "deckcraft-pptx",
+ "deckcraft-render",
+ "deckcraft-text",
+ "i_overlay",
+ "image",
+ "log",
+ "serde",
+ "serde_json",
+ "thiserror 2.0.21",
+]
+
+[[package]]
+name = "deckcraft-fonts"
+version = "0.1.0"
+dependencies = [
+ "epaint_default_fonts",
+ "harfrust 0.13.3",
+ "kurbo",
+ "log",
+ "skrifa 0.47.0",
+ "unicode-bidi",
+]
+
+[[package]]
+name = "deckcraft-format"
+version = "0.1.0"
+dependencies = [
+ "deckcraft-model",
+ "serde",
+ "serde_json",
+ "thiserror 2.0.21",
+ "zip",
+]
+
+[[package]]
+name = "deckcraft-geom"
+version = "0.1.0"
+dependencies = [
+ "kurbo",
+ "serde",
+]
+
+[[package]]
+name = "deckcraft-h264"
+version = "0.1.0"
+dependencies = [
+ "deckcraft-bitstream",
+ "rayon",
+ "thiserror 2.0.21",
+]
+
+[[package]]
+name = "deckcraft-hevc"
+version = "0.1.0"
+dependencies = [
+ "deckcraft-bitstream",
+ "rayon",
+ "thiserror 2.0.21",
+]
+
+[[package]]
+name = "deckcraft-isobmff"
+version = "0.1.0"
+dependencies = [
+ "deckcraft-bitstream",
+ "thiserror 2.0.21",
+]
+
+[[package]]
+name = "deckcraft-matroska"
+version = "0.1.0"
+
+[[package]]
+name = "deckcraft-mcp"
+version = "0.1.0"
+dependencies = [
+ "deckcraft-engine",
+ "deckcraft-render",
+ "log",
+ "serde_json",
+]
+
+[[package]]
+name = "deckcraft-media"
+version = "0.1.0"
+dependencies = [
+ "deckcraft-av1",
+ "deckcraft-h264",
+ "deckcraft-hevc",
+ "deckcraft-isobmff",
+ "deckcraft-matroska",
+ "deckcraft-ogg",
+ "deckcraft-opus",
+ "deckcraft-vp9",
+ "image",
+ "log",
+ "symphonia",
+ "thiserror 2.0.21",
+]
+
+[[package]]
+name = "deckcraft-model"
+version = "0.1.0"
+dependencies = [
+ "deckcraft-color",
+ "deckcraft-geom",
+ "serde",
+ "serde_json",
+ "thiserror 2.0.21",
+]
+
+[[package]]
+name = "deckcraft-ogg"
+version = "0.1.0"
+
+[[package]]
+name = "deckcraft-opus"
+version = "0.1.0"
+
+[[package]]
+name = "deckcraft-pdf"
+version = "0.1.0"
+dependencies = [
+ "deckcraft-fonts",
+ "deckcraft-model",
+ "deckcraft-render",
+ "deckcraft-text",
+ "krilla",
+ "kurbo",
+ "log",
+ "serde",
+ "thiserror 2.0.21",
+]
+
+[[package]]
+name = "deckcraft-pptx"
+version = "0.1.0"
+dependencies = [
+ "deckcraft-color",
+ "deckcraft-engine",
+ "deckcraft-geom",
+ "deckcraft-model",
+ "image",
+ "log",
+ "quick-xml 0.38.4",
+ "serde_json",
+ "thiserror 2.0.21",
+ "zip",
+]
+
+[[package]]
+name = "deckcraft-render"
+version = "0.1.0"
+dependencies = [
+ "deckcraft-color",
+ "deckcraft-fonts",
+ "deckcraft-geom",
+ "deckcraft-model",
+ "deckcraft-text",
+ "image",
+ "kurbo",
+ "log",
+ "vello_common 0.2.0",
+ "vello_cpu 0.2.0",
+]
+
+[[package]]
+name = "deckcraft-text"
+version = "0.1.0"
+dependencies = [
+ "deckcraft-color",
+ "deckcraft-fonts",
+ "deckcraft-geom",
+ "deckcraft-model",
+ "unicode-linebreak",
+ "unicode-segmentation",
+]
+
+[[package]]
+name = "deckcraft-ui-egui"
+version = "0.1.0"
+dependencies = [
+ "deckcraft-anim",
+ "deckcraft-color",
+ "deckcraft-engine",
+ "deckcraft-fonts",
+ "deckcraft-geom",
+ "deckcraft-media",
+ "deckcraft-model",
+ "deckcraft-render",
+ "deckcraft-text",
+ "egui",
+ "egui_kittest",
+ "js-sys",
+ "log",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "deckcraft-vp9"
+version = "0.1.0"
+dependencies = [
+ "deckcraft-bitstream",
+ "rayon",
+ "thiserror 2.0.21",
+]
+
+[[package]]
+name = "deckcraft-web"
+version = "0.1.0"
+dependencies = [
+ "deckcraft-engine",
+ "deckcraft-ui-egui",
+ "eframe",
+ "egui",
+ "js-sys",
+ "log",
+ "rfd",
+ "serde_json",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+]
+
[[package]]
name = "derive_arbitrary"
version = "1.5.0"
@@ -3447,302 +3743,6 @@ version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
-[[package]]
-name = "slidecraft"
-version = "0.1.0"
-dependencies = [
- "arboard",
- "cpal",
- "eframe",
- "egui",
- "image",
- "log",
- "rfd",
- "serde_json",
- "slidecraft-engine",
- "slidecraft-media",
- "slidecraft-ui-egui",
- "winit",
- "winresource",
-]
-
-[[package]]
-name = "slidecraft-anim"
-version = "0.1.0"
-dependencies = [
- "serde",
- "slidecraft-geom",
- "slidecraft-model",
-]
-
-[[package]]
-name = "slidecraft-av1"
-version = "0.1.0"
-dependencies = [
- "rayon",
- "slidecraft-bitstream",
- "thiserror 2.0.21",
-]
-
-[[package]]
-name = "slidecraft-bitstream"
-version = "0.1.0"
-dependencies = [
- "proptest",
-]
-
-[[package]]
-name = "slidecraft-cli"
-version = "0.1.0"
-dependencies = [
- "serde_json",
- "slidecraft-engine",
- "slidecraft-mcp",
-]
-
-[[package]]
-name = "slidecraft-color"
-version = "0.1.0"
-dependencies = [
- "serde",
-]
-
-[[package]]
-name = "slidecraft-engine"
-version = "0.1.0"
-dependencies = [
- "i_overlay",
- "image",
- "log",
- "serde",
- "serde_json",
- "slidecraft-color",
- "slidecraft-fonts",
- "slidecraft-format",
- "slidecraft-geom",
- "slidecraft-media",
- "slidecraft-model",
- "slidecraft-pdf",
- "slidecraft-pptx",
- "slidecraft-render",
- "slidecraft-text",
- "thiserror 2.0.21",
-]
-
-[[package]]
-name = "slidecraft-fonts"
-version = "0.1.0"
-dependencies = [
- "epaint_default_fonts",
- "harfrust 0.13.3",
- "kurbo",
- "log",
- "skrifa 0.47.0",
- "unicode-bidi",
-]
-
-[[package]]
-name = "slidecraft-format"
-version = "0.1.0"
-dependencies = [
- "serde",
- "serde_json",
- "slidecraft-model",
- "thiserror 2.0.21",
- "zip",
-]
-
-[[package]]
-name = "slidecraft-geom"
-version = "0.1.0"
-dependencies = [
- "kurbo",
- "serde",
-]
-
-[[package]]
-name = "slidecraft-h264"
-version = "0.1.0"
-dependencies = [
- "rayon",
- "slidecraft-bitstream",
- "thiserror 2.0.21",
-]
-
-[[package]]
-name = "slidecraft-hevc"
-version = "0.1.0"
-dependencies = [
- "rayon",
- "slidecraft-bitstream",
- "thiserror 2.0.21",
-]
-
-[[package]]
-name = "slidecraft-isobmff"
-version = "0.1.0"
-dependencies = [
- "slidecraft-bitstream",
- "thiserror 2.0.21",
-]
-
-[[package]]
-name = "slidecraft-matroska"
-version = "0.1.0"
-
-[[package]]
-name = "slidecraft-mcp"
-version = "0.1.0"
-dependencies = [
- "log",
- "serde_json",
- "slidecraft-engine",
- "slidecraft-render",
-]
-
-[[package]]
-name = "slidecraft-media"
-version = "0.1.0"
-dependencies = [
- "image",
- "log",
- "slidecraft-av1",
- "slidecraft-h264",
- "slidecraft-hevc",
- "slidecraft-isobmff",
- "slidecraft-matroska",
- "slidecraft-ogg",
- "slidecraft-opus",
- "slidecraft-vp9",
- "symphonia",
- "thiserror 2.0.21",
-]
-
-[[package]]
-name = "slidecraft-model"
-version = "0.1.0"
-dependencies = [
- "serde",
- "serde_json",
- "slidecraft-color",
- "slidecraft-geom",
- "thiserror 2.0.21",
-]
-
-[[package]]
-name = "slidecraft-ogg"
-version = "0.1.0"
-
-[[package]]
-name = "slidecraft-opus"
-version = "0.1.0"
-
-[[package]]
-name = "slidecraft-pdf"
-version = "0.1.0"
-dependencies = [
- "krilla",
- "kurbo",
- "log",
- "serde",
- "slidecraft-fonts",
- "slidecraft-model",
- "slidecraft-render",
- "slidecraft-text",
- "thiserror 2.0.21",
-]
-
-[[package]]
-name = "slidecraft-pptx"
-version = "0.1.0"
-dependencies = [
- "image",
- "log",
- "quick-xml 0.38.4",
- "serde_json",
- "slidecraft-color",
- "slidecraft-engine",
- "slidecraft-geom",
- "slidecraft-model",
- "thiserror 2.0.21",
- "zip",
-]
-
-[[package]]
-name = "slidecraft-render"
-version = "0.1.0"
-dependencies = [
- "image",
- "kurbo",
- "log",
- "slidecraft-color",
- "slidecraft-fonts",
- "slidecraft-geom",
- "slidecraft-model",
- "slidecraft-text",
- "vello_common 0.2.0",
- "vello_cpu 0.2.0",
-]
-
-[[package]]
-name = "slidecraft-text"
-version = "0.1.0"
-dependencies = [
- "slidecraft-color",
- "slidecraft-fonts",
- "slidecraft-geom",
- "slidecraft-model",
- "unicode-linebreak",
- "unicode-segmentation",
-]
-
-[[package]]
-name = "slidecraft-ui-egui"
-version = "0.1.0"
-dependencies = [
- "egui",
- "egui_kittest",
- "js-sys",
- "log",
- "serde",
- "serde_json",
- "slidecraft-anim",
- "slidecraft-color",
- "slidecraft-engine",
- "slidecraft-fonts",
- "slidecraft-geom",
- "slidecraft-media",
- "slidecraft-model",
- "slidecraft-render",
- "slidecraft-text",
-]
-
-[[package]]
-name = "slidecraft-vp9"
-version = "0.1.0"
-dependencies = [
- "rayon",
- "slidecraft-bitstream",
- "thiserror 2.0.21",
-]
-
-[[package]]
-name = "slidecraft-web"
-version = "0.1.0"
-dependencies = [
- "eframe",
- "egui",
- "js-sys",
- "log",
- "rfd",
- "serde_json",
- "slidecraft-engine",
- "slidecraft-ui-egui",
- "wasm-bindgen",
- "wasm-bindgen-futures",
- "web-sys",
-]
-
[[package]]
name = "slotmap"
version = "1.1.1"
diff --git a/Cargo.toml b/Cargo.toml
index c27dc37..50aeb93 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,41 +1,41 @@
[workspace]
resolver = "3"
members = ["crates/*", "apps/*", "xtask"]
-default-members = ["crates/*", "apps/slidecraft", "apps/slidecraft-cli"]
+default-members = ["crates/*", "apps/deckcraft", "apps/deckcraft-cli"]
[workspace.package]
version = "0.1.0"
edition = "2024"
license = "MIT OR Apache-2.0"
rust-version = "1.90"
-repository = "https://github.com/storytold/slidecraft"
+repository = "https://github.com/storytold/deckcraft"
[workspace.dependencies]
# internal
-slidecraft-geom = { path = "crates/geom" }
-slidecraft-color = { path = "crates/color" }
-slidecraft-model = { path = "crates/model" }
-slidecraft-fonts = { path = "crates/fonts" }
-slidecraft-text = { path = "crates/text" }
-slidecraft-render = { path = "crates/render" }
-slidecraft-pdf = { path = "crates/pdf" }
-slidecraft-pptx = { path = "crates/pptx" }
-slidecraft-format = { path = "crates/format" }
-slidecraft-anim = { path = "crates/anim" }
-slidecraft-engine = { path = "crates/engine" }
-slidecraft-mcp = { path = "crates/mcp" }
-slidecraft-ui-egui = { path = "crates/ui-egui" }
-slidecraft-media = { path = "crates/media" }
+deckcraft-geom = { path = "crates/geom" }
+deckcraft-color = { path = "crates/color" }
+deckcraft-model = { path = "crates/model" }
+deckcraft-fonts = { path = "crates/fonts" }
+deckcraft-text = { path = "crates/text" }
+deckcraft-render = { path = "crates/render" }
+deckcraft-pdf = { path = "crates/pdf" }
+deckcraft-pptx = { path = "crates/pptx" }
+deckcraft-format = { path = "crates/format" }
+deckcraft-anim = { path = "crates/anim" }
+deckcraft-engine = { path = "crates/engine" }
+deckcraft-mcp = { path = "crates/mcp" }
+deckcraft-ui-egui = { path = "crates/ui-egui" }
+deckcraft-media = { path = "crates/media" }
# codecs (ported from FilmCraft, same owner and licence)
-slidecraft-bitstream = { path = "crates/bitstream" }
-slidecraft-h264 = { path = "crates/h264" }
-slidecraft-hevc = { path = "crates/hevc" }
-slidecraft-vp9 = { path = "crates/vp9" }
-slidecraft-av1 = { path = "crates/av1" }
-slidecraft-isobmff = { path = "crates/isobmff" }
-slidecraft-matroska = { path = "crates/matroska" }
-slidecraft-ogg = { path = "crates/ogg" }
-slidecraft-opus = { path = "crates/opus" }
+deckcraft-bitstream = { path = "crates/bitstream" }
+deckcraft-h264 = { path = "crates/h264" }
+deckcraft-hevc = { path = "crates/hevc" }
+deckcraft-vp9 = { path = "crates/vp9" }
+deckcraft-av1 = { path = "crates/av1" }
+deckcraft-isobmff = { path = "crates/isobmff" }
+deckcraft-matroska = { path = "crates/matroska" }
+deckcraft-ogg = { path = "crates/ogg" }
+deckcraft-opus = { path = "crates/opus" }
# foundation
kurbo = { version = "0.13", features = ["serde"] }
diff --git a/LICENSE-MIT b/LICENSE-MIT
index d973456..326ccf1 100644
--- a/LICENSE-MIT
+++ b/LICENSE-MIT
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2026 ArtCraft Team and the SlideCraft contributors
+Copyright (c) 2026 ArtCraft Team and the DeckCraft contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/NOTICE b/NOTICE
index 483fbba..fb209cf 100644
--- a/NOTICE
+++ b/NOTICE
@@ -1,5 +1,5 @@
-SlideCraft
-Copyright (c) 2026 ArtCraft Team and the SlideCraft contributors.
+DeckCraft
+Copyright (c) 2026 ArtCraft Team and the DeckCraft contributors.
Licensed under the MIT License or the Apache License, Version 2.0, at your option
(LICENSE-MIT, LICENSE-APACHE).
diff --git a/README.md b/README.md
index 0ee333c..a73c69d 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,7 @@
-SlideCraft
+DeckCraft
Presentations and slide shows; an open-source, clean-room reimplementation of Microsoft PowerPoint, rebuilt in pure Rust.
@@ -32,7 +32,7 @@
- SlideCraft on getartcraft.com ·
+ DeckCraft on getartcraft.com ·
ArtCraft ·
All Crafting Apps
@@ -53,7 +53,7 @@
## What works today
-SlideCraft is in early development. Today:
+DeckCraft is in early development. Today:
- **Presentation model** with slide masters, eleven layouts, placeholders that inherit position and
formatting, themes (colour, font and effect schemes), sections, notes, comments, transitions and
@@ -70,25 +70,25 @@ SlideCraft is in early development. Today:
## Getting started
```sh
-git clone https://github.com/storytold/slidecraft
-cd slidecraft
-cargo run --release -p slidecraft-cli -- render --sample --all --scale 1 out/
+git clone https://github.com/storytold/deckcraft
+cd deckcraft
+cargo run --release -p deckcraft-cli -- render --sample --all --scale 1 out/
```
Fonts come from [craft-fonts](https://github.com/storytold/craft-fonts): clone it next to this repo
-(`../craft-fonts`) and local builds pick it up automatically; without it SlideCraft uses your system
+(`../craft-fonts`) and local builds pick it up automatically; without it DeckCraft uses your system
fonts.
## Agents, CLI and MCP
-Every action in SlideCraft is a command with a stable id, so people, scripts and AI agents use the
+Every action in DeckCraft is a command with a stable id, so people, scripts and AI agents use the
same verbs:
```sh
-slidecraft-cli commands format. # list commands
-slidecraft-cli run --sample --cmd 'slide.new={"layout":"titleOnly","title":"Hello"}' --save hello.slidecraft
-slidecraft-cli mcp # MCP server over stdio (headless)
-slidecraft-cli mcp --connect 7979 # drive the running app (slidecraft --control 7979)
+deckcraft-cli commands format. # list commands
+deckcraft-cli run --sample --cmd 'slide.new={"layout":"titleOnly","title":"Hello"}' --save hello.deckcraft
+deckcraft-cli mcp # MCP server over stdio (headless)
+deckcraft-cli mcp --connect 7979 # drive the running app (deckcraft --control 7979)
```
## Roadmap
@@ -97,7 +97,7 @@ See [ROADMAP.md](ROADMAP.md) for milestones, parity estimates and what's next.
## The Crafting Apps
-SlideCraft is one of the **Crafting Apps**: free, open-source creative tools from the
+DeckCraft is one of the **Crafting Apps**: free, open-source creative tools from the
[ArtCraft](https://getartcraft.com/) team, each written from scratch in Rust and each able to
stand on its own.
@@ -110,7 +110,7 @@ stand on its own.
| | **PrintCraft** | Reading, organizing and protecting PDFs | [GitHub](https://github.com/storytold/printcraft) | [Website](https://getartcraft.com/apps/printcraft) |
| | **EffectCraft** | Motion graphics and visual effects | [GitHub](https://github.com/storytold/effectcraft) | [Website](https://getartcraft.com/apps/effectcraft) |
| | **DesignCraft** | Page layout and publishing | [GitHub](https://github.com/storytold/designcraft) | [Website](https://getartcraft.com/apps/designcraft) |
-| | **SlideCraft** | **Presentations and slide shows · you are here** | [GitHub](https://github.com/storytold/slidecraft) | [Website](https://getartcraft.com/apps/slidecraft) |
+| | **DeckCraft** | **Presentations and slide shows · you are here** | [GitHub](https://github.com/storytold/deckcraft) | [Website](https://getartcraft.com/apps/deckcraft) |
And [**ArtCraft**](https://getartcraft.com/) itself, our AI image and video studio for artists who want real control.
@@ -133,26 +133,26 @@ And [**ArtCraft**](https://getartcraft.com/) itself, our AI image and video stud
discord.gg/artcraft ·
getartcraft.com ·
The Crafting Apps ·
- SlideCraft
+ DeckCraft
## License and credits
-SlideCraft is dual-licensed under [MIT](LICENSE-MIT) or [Apache-2.0](LICENSE-APACHE), at your option.
-Copyright (c) 2026 ArtCraft Team and the SlideCraft contributors. Required notices are in [NOTICE](NOTICE).
+DeckCraft is dual-licensed under [MIT](LICENSE-MIT) or [Apache-2.0](LICENSE-APACHE), at your option.
+Copyright (c) 2026 ArtCraft Team and the DeckCraft contributors. Required notices are in [NOTICE](NOTICE).
Bundled fonts, icons, images and other assets keep their own open licenses; each one is listed
with its author, source and license in [ATTRIBUTION.md](ATTRIBUTION.md).
-SlideCraft's themes, layouts, shape geometry, icons and sample decks are original work, drawn or
+DeckCraft's themes, layouts, shape geometry, icons and sample decks are original work, drawn or
generated in code; no Microsoft artwork, fonts, themes or templates are used.
The ArtCraft name, wordmark and logos in [`docs/brand/`](docs/brand/) are trademarks of the
ArtCraft Team and are not covered by this license. They may be used only unmodified, and only as
-part of this repository and SlideCraft, under [`docs/brand/LICENSE-brand.txt`](docs/brand/LICENSE-brand.txt).
+part of this repository and DeckCraft, under [`docs/brand/LICENSE-brand.txt`](docs/brand/LICENSE-brand.txt).
Forks and modified versions must remove them.
-Microsoft and PowerPoint are trademarks of the Microsoft group of companies. Adobe, Photoshop, Illustrator, Premiere Pro, Lightroom, Acrobat, After Effects and InDesign are trademarks or registered trademarks of Adobe Inc. in the United States and/or other countries. SlideCraft is an independent, open-source project and is not affiliated with, sponsored by or endorsed by Microsoft Corporation or Adobe Inc.; these names are used only to describe the workflows it is compatible with.
+Microsoft and PowerPoint are trademarks of the Microsoft group of companies. Adobe, Photoshop, Illustrator, Premiere Pro, Lightroom, Acrobat, After Effects and InDesign are trademarks or registered trademarks of Adobe Inc. in the United States and/or other countries. DeckCraft is an independent, open-source project and is not affiliated with, sponsored by or endorsed by Microsoft Corporation or Adobe Inc.; these names are used only to describe the workflows it is compatible with.
diff --git a/ROADMAP.md b/ROADMAP.md
index 4cd7b03..a8f0287 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -1,6 +1,6 @@
-# SlideCraft roadmap
+# DeckCraft roadmap
-SlideCraft aims at full PowerPoint parity — and to be better: faster, open (a documented zip+JSON
+DeckCraft aims at full PowerPoint parity — and to be better: faster, open (a documented zip+JSON
format plus PPTX), scriptable by agents (CLI, JSON control channel, MCP), and available everywhere
(macOS, Windows, Linux, BSD and the web).
@@ -34,46 +34,67 @@ format plus PPTX), scriptable by agents (CLI, JSON control channel, MCP), and av
- PDF export: slides, notes pages and handouts (1–9 per page) with a selectable real-text layer, hyperlinks and slide bookmarks (`file.export {format: "pdf", layout}`, File › Export…).
- UI: PowerPoint-style ribbon with contextual tabs, ~240 original icons, status bar, panes, command
palette, light/dark.
-- Automation: ~200 commands, every one reachable from `slidecraft-cli`, the app's JSON control
+- Automation: ~200 commands, every one reachable from `deckcraft-cli`, the app's JSON control
channel and the MCP server (headless or connected to the running app).
-- Web: `apps/slidecraft-web` runs the same UI in the browser (trunk; WebGPU with WebGL2 fallback),
+- Web: `apps/deckcraft-web` runs the same UI in the browser (trunk; WebGPU with WebGL2 fallback),
opening the sample deck; Open/Insert use the browser file picker, Save/Export download.
- Release CI: pushes to `release` build a draft GitHub Release (macOS universal dmg, Windows
x64/x86 msi + zip, Linux AppImage/deb/rpm/tar.gz + Flatpak, FreeBSD tar.gz, web zip); signing
secrets live in the `release` environment. Version: `cargo xtask version`.
-**In progress:** PPTX import/export · first signed release run.
-
-**Next (in order):** vector PDF artwork · WMA/WMV decoding · Format Shape pane depth · connectors with
-glue, freeform and edit points · merge shapes · Animation Pane polish · native macOS menu bar ·
-print · Notes/Handout masters · equations · SVG pictures.
+**Next (in order):** first signed release run · PPTX corpus hardening against real-world decks ·
+Animation Pane and presenter view polish · vector PDF artwork · Format Shape pane depth (3-D,
+picture/texture options) · edit points · native macOS menu bar · print · Notes/Handout masters ·
+equations · SVG pictures · WMA/WMV decoding.
Milestone details live in `plan/execution-plan.md` (M0–M14, local planning notes).
+## How close to an alpha (estimate, 2026-10-07)
+
+An **alpha** here means: someone can install a signed build on macOS, Windows or Linux, make a real
+deck from scratch or from a PowerPoint file, present it with transitions, animations and media,
+save it back to `.pptx`/PDF without losing work, and hit no crashes on the common paths.
+
+**DeckCraft is about 80% of the way to that alpha — roughly 30–40 wall-clock hours of a single
+Claude Opus 5.5 agent** (about 12–18 hours with three agents in parallel).
+
+| Alpha blocker | State | Estimate |
+|---|---|---|
+| First real release run: signing, notarization, installers verified on each OS | Workflows written and linted, never run | 6 h |
+| PPTX fidelity on a corpus of real decks (import, round-trip, opens without repair) | Verified on generated decks only | 8 h |
+| Presenter view and Animation Pane polish (timeline, reorder, preview) | Working, rough | 6 h |
+| Soak and fuzz the editor (random command sequences, big decks, undo/redo) for crashes and slowness | Unit tests and guards only | 5 h |
+| UI fidelity pass on the most-used ribbon groups and dialogs; first-run experience | Mostly there | 6 h |
+| Mascot app icon (owner), README/site screenshots, user docs | Placeholder icon | 3 h |
+
+Already alpha-ready: editing, text, shapes (incl. connectors, freeform, merge), themes and
+backgrounds, tables, charts, transitions, animations, slide show, audio/video playback, PDF export,
+AutoRecover, CLI/MCP automation, web build.
+
## How far from full parity (estimate, 2026-10-07)
-**Breadth: ~74% weighted** (P0 core 88%, P1 67%, P2 33%) over the 187 features of the PowerPoint
-catalogue, scored row by row in [docs/parity.md](docs/parity.md) (`cargo xtask parity` recomputes
-it). Many features scored done still lack some of PowerPoint's options, dialogs or pixel fidelity, so
-**overall parity including depth is about 55%**.
+**Breadth: ~79% weighted** (P0 core 93%, P1 74%, P2 35%) over the 187 features of the PowerPoint catalogue, scored row by
+row in [docs/parity.md](docs/parity.md) (`cargo xtask parity` recomputes it). Many features scored
+done still lack some of PowerPoint's options, dialogs or pixel fidelity, so **overall parity
+including depth is about 62%**.
-**Remaining work: about 230 wall-clock hours of a single Claude Opus 5.5 agent** (±30%), or roughly
-80–110 hours with four agents in parallel on separate crates:
+**Remaining work to 100%: about 190 wall-clock hours of a single Claude Opus 5.5 agent** (±30%),
+or roughly 65–90 hours with four agents in parallel on separate crates:
| Work | Estimate |
|---|---|
-| Open P0 (16 partial, 1 missing: PPTX fidelity, vector PDF, media playback, Format Shape pane, presenter view…) | 30 h |
-| Open P1 (23 partial, 10 missing: connectors/glue, freeform, merge shapes, print, multi-monitor, SVG…) | 45 h |
-| Open P2 (11 partial, 28 missing: equations, video export, 3-D, remove background, thesaurus…) | 40 h |
-| Depth and pixel fidelity of every ribbon group, dialog and pane against PowerPoint | 75 h |
-| Performance (incremental rendering, GPU raster) and PPTX corpus hardening | 30 h |
-| Platform verification and release hardening (signing, Flatpak, BSD, web) | 10 h |
+| Alpha blockers above | 35 h |
+| Open P0/P1 rows (vector PDF, print, edit points, SVG, multi-monitor, draw table, SmartArt text pane, Notes/Handout masters…) | 45 h |
+| Open P2 rows (equations, video export, 3-D, remove background, thesaurus, compare, record show…) | 40 h |
+| Depth and pixel fidelity of every ribbon group, dialog and pane against PowerPoint | 50 h |
+| Performance (incremental rendering, GPU raster, streaming media decode) | 20 h |
-Basis: the M0 arc (model, renderer, text engine, engine with ~200 commands, ribbon UI, show engine,
-CLI and MCP) took roughly 25 wall-clock agent hours; open catalogue rows average 1–2 hours each.
+Basis: in this session the engine, renderer, UI, show engine, PPTX, PDF, media and release
+pipeline were built in about 30 wall-clock hours with up to three agents in parallel; recent
+catalogue rows (connectors, freeform, merge shapes, AutoRecover, gradients) took 1–2 hours each.
## Agents: CLI and MCP
-Every command is reachable from `slidecraft-cli` (`run`, `describe`, `commands`, `app` for the running
-window, `render`, `convert`), from MCP (`slidecraft-cli mcp`, optionally `--connect PORT`), and from the
-app's JSON control channel (`slidecraft --control PORT`).
+Every command is reachable from `deckcraft-cli` (`run`, `describe`, `commands`, `app` for the running
+window, `render`, `convert`), from MCP (`deckcraft-cli mcp`, optionally `--connect PORT`), and from the
+app's JSON control channel (`deckcraft --control PORT`).
diff --git a/apps/deckcraft-cli/Cargo.toml b/apps/deckcraft-cli/Cargo.toml
new file mode 100644
index 0000000..898a1fb
--- /dev/null
+++ b/apps/deckcraft-cli/Cargo.toml
@@ -0,0 +1,19 @@
+[package]
+name = "deckcraft-cli"
+version.workspace = true
+edition.workspace = true
+license.workspace = true
+description = "DeckCraft on the command line: render, convert, run commands, inspect, and serve MCP"
+publish = false
+
+[lints]
+workspace = true
+
+[[bin]]
+name = "deckcraft-cli"
+path = "src/main.rs"
+
+[dependencies]
+deckcraft-engine = { workspace = true }
+deckcraft-mcp = { workspace = true }
+serde_json = { workspace = true }
diff --git a/apps/slidecraft-cli/src/main.rs b/apps/deckcraft-cli/src/main.rs
similarity index 78%
rename from apps/slidecraft-cli/src/main.rs
rename to apps/deckcraft-cli/src/main.rs
index be37bc7..c450393 100644
--- a/apps/slidecraft-cli/src/main.rs
+++ b/apps/deckcraft-cli/src/main.rs
@@ -1,23 +1,23 @@
-//! Headless SlideCraft.
+//! Headless DeckCraft.
//!
//! ```text
-//! slidecraft-cli info FILE # slides, titles, layouts (JSON)
-//! slidecraft-cli render (FILE | --sample) [--slide N | --all] [--scale S] OUT.png|OUT_DIR
-//! slidecraft-cli convert IN OUT # .slidecraft ⇄ .pptx, outline .txt, .png (first slide)
-//! slidecraft-cli run [--in FILE | --sample] [--cmd ID[=JSON]]... [--save OUT] [--export OUT] [--print]
-//! slidecraft-cli commands [FILTER] # list commands (JSON)
-//! slidecraft-cli describe ID # one command
-//! slidecraft-cli app [--port PORT] COMMAND [JSON] | --method METHOD [JSON] # drive the running app
-//! slidecraft-cli mcp [--connect PORT] [--sample] # MCP server over stdio
-//! slidecraft-cli links | --version
+//! deckcraft-cli info FILE # slides, titles, layouts (JSON)
+//! deckcraft-cli render (FILE | --sample) [--slide N | --all] [--scale S] OUT.png|OUT_DIR
+//! deckcraft-cli convert IN OUT # .deckcraft ⇄ .pptx, outline .txt, .png (first slide)
+//! deckcraft-cli run [--in FILE | --sample] [--cmd ID[=JSON]]... [--save OUT] [--export OUT] [--print]
+//! deckcraft-cli commands [FILTER] # list commands (JSON)
+//! deckcraft-cli describe ID # one command
+//! deckcraft-cli app [--port PORT] COMMAND [JSON] | --method METHOD [JSON] # drive the running app
+//! deckcraft-cli mcp [--connect PORT] [--sample] # MCP server over stdio
+//! deckcraft-cli links | --version
//! ```
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
#![forbid(unsafe_code)]
use std::process::ExitCode;
+use deckcraft_engine::Session;
use serde_json::{Value, json};
-use slidecraft_engine::Session;
macro_rules! outln {
($($arg:tt)*) => {{
@@ -26,28 +26,28 @@ macro_rules! outln {
if e.kind() == std::io::ErrorKind::BrokenPipe {
std::process::exit(0);
}
- eprintln!("slidecraft-cli: can't write to stdout: {e}");
+ eprintln!("deckcraft-cli: can't write to stdout: {e}");
std::process::exit(1);
}
}};
}
-const USAGE: &str = "usage: slidecraft-cli info FILE
- slidecraft-cli render (FILE | --sample) [--slide N | --all] [--scale S] [--edit] OUT
- slidecraft-cli convert IN OUT
- slidecraft-cli run [--in FILE | --sample] [--cmd ID[=JSON]]... [--save OUT] [--export OUT] [--print]
- slidecraft-cli commands [FILTER]
- slidecraft-cli describe COMMAND
- slidecraft-cli app [--port PORT] COMMAND [JSON] | --method METHOD [JSON]
- slidecraft-cli mcp [--connect PORT] [--sample]
- slidecraft-cli links | --version";
+const USAGE: &str = "usage: deckcraft-cli info FILE
+ deckcraft-cli render (FILE | --sample) [--slide N | --all] [--scale S] [--edit] OUT
+ deckcraft-cli convert IN OUT
+ deckcraft-cli run [--in FILE | --sample] [--cmd ID[=JSON]]... [--save OUT] [--export OUT] [--print]
+ deckcraft-cli commands [FILTER]
+ deckcraft-cli describe COMMAND
+ deckcraft-cli app [--port PORT] COMMAND [JSON] | --method METHOD [JSON]
+ deckcraft-cli mcp [--connect PORT] [--sample]
+ deckcraft-cli links | --version";
fn main() -> ExitCode {
let args: Vec = std::env::args().skip(1).collect();
let rest = args.get(1..).unwrap_or(&[]);
match args.first().map(String::as_str) {
Some("--version" | "-V" | "version") => {
- outln!("slidecraft-cli {}", env!("CARGO_PKG_VERSION"));
+ outln!("deckcraft-cli {}", env!("CARGO_PKG_VERSION"));
ExitCode::SUCCESS
}
Some("info") => report(info(rest)),
@@ -70,13 +70,13 @@ fn main() -> ExitCode {
Some("app") => report(app(rest)),
Some("mcp") => report(mcp(rest)),
Some("links") => {
- use slidecraft_engine::links::*;
+ use deckcraft_engine::links::*;
outln!("Discord {DISCORD}\nWebsite {WEBSITE}\nApp page {APP_PAGE}\nGitHub {GITHUB}");
ExitCode::SUCCESS
}
_ => {
eprintln!("{USAGE}");
- eprintln!("\nCommunity: {} · {}", slidecraft_engine::links::DISCORD, slidecraft_engine::links::APP_PAGE);
+ eprintln!("\nCommunity: {} · {}", deckcraft_engine::links::DISCORD, deckcraft_engine::links::APP_PAGE);
ExitCode::FAILURE
}
}
@@ -86,7 +86,7 @@ fn report(r: Result<(), String>) -> ExitCode {
match r {
Ok(()) => ExitCode::SUCCESS,
Err(e) => {
- eprintln!("slidecraft-cli: {e}");
+ eprintln!("deckcraft-cli: {e}");
ExitCode::FAILURE
}
}
@@ -94,7 +94,7 @@ fn report(r: Result<(), String>) -> ExitCode {
fn open(s: &mut Session, input: Option<&str>, sample: bool) -> Result<(), String> {
if sample {
- return slidecraft_engine::sample::open_sample(s).map_err(|e| e.to_string());
+ return deckcraft_engine::sample::open_sample(s).map_err(|e| e.to_string());
}
match input {
Some(p) => s.execute("file.open", &json!({"path": p})).map(|_| ()).map_err(|e| e.to_string()),
@@ -153,7 +153,7 @@ fn render(args: &[String]) -> Result<(), String> {
if i >= n {
return Err(format!("no slide {i} (the deck has {n})"));
}
- let (png, w, h) = slidecraft_engine::cmd::file::render_png(&st.doc, i, scale, edit);
+ let (png, w, h) = deckcraft_engine::cmd::file::render_png(&st.doc, i, scale, edit);
let path = if all { format!("{out}/slide-{:02}.png", i + 1) } else { out.clone() };
std::fs::write(&path, png).map_err(|e| format!("{path}: {e}"))?;
outln!("{path} ({w}×{h})");
@@ -165,7 +165,7 @@ fn convert(args: &[String]) -> Result<(), String> {
let (Some(input), Some(out)) = (args.first(), args.get(1)) else { return Err("convert needs IN and OUT".into()) };
let mut s = Session::new();
open(&mut s, Some(input), false)?;
- let fmt = slidecraft_engine::cmd::file::format_for_path(out);
+ let fmt = deckcraft_engine::cmd::file::format_for_path(out);
let r = if matches!(fmt, "png" | "jpeg") {
s.execute("file.export", &json!({"path": out, "slide": 0}))
} else {
@@ -218,14 +218,14 @@ fn run(args: &[String]) -> Result<(), String> {
fn describe(id: Option<&str>) -> Result<(), String> {
let id = id.ok_or("describe needs a command id")?;
- let c = slidecraft_engine::find_command(id).ok_or_else(|| format!("unknown command `{id}`"))?;
+ let c = deckcraft_engine::find_command(id).ok_or_else(|| format!("unknown command `{id}`"))?;
outln!("{} — {}\n where: {}\n shortcut: {}\n params: {}", c.id, c.label, c.menu.join(" ▸ "), c.shortcut.unwrap_or("—"), c.params);
Ok(())
}
fn app(args: &[String]) -> Result<(), String> {
- use slidecraft_mcp::{Backend, Remote, control_addr};
- let mut port = slidecraft_mcp::DEFAULT_PORT.to_string();
+ use deckcraft_mcp::{Backend, Remote, control_addr};
+ let mut port = deckcraft_mcp::DEFAULT_PORT.to_string();
let mut method: Option = None;
let mut rest = vec![];
let mut it = args.iter();
@@ -237,7 +237,7 @@ fn app(args: &[String]) -> Result<(), String> {
}
}
let mut r = Remote::connect(&control_addr(&port))
- .map_err(|e| format!("no SlideCraft app on port {port} ({e}); start it with `slidecraft --control {port}`"))?;
+ .map_err(|e| format!("no DeckCraft app on port {port} ({e}); start it with `deckcraft --control {port}`"))?;
let parse = |s: Option<&String>| -> Result {
s.map(|j| serde_json::from_str(j).map_err(|e| format!("bad JSON: {e}"))).transpose().map(|v| v.unwrap_or(json!({})))
};
@@ -253,7 +253,7 @@ fn app(args: &[String]) -> Result<(), String> {
}
fn mcp(args: &[String]) -> Result<(), String> {
- use slidecraft_mcp::{Backend, Headless, Remote, Server, control_addr};
+ use deckcraft_mcp::{Backend, Headless, Remote, Server, control_addr};
let mut connect: Option = None;
let mut sample = false;
let mut it = args.iter();
@@ -269,7 +269,7 @@ fn mcp(args: &[String]) -> Result<(), String> {
None => {
let mut h = Headless::new();
if sample {
- slidecraft_engine::sample::open_sample(&mut h.session).map_err(|e| e.to_string())?;
+ deckcraft_engine::sample::open_sample(&mut h.session).map_err(|e| e.to_string())?;
}
Box::new(h)
}
diff --git a/apps/slidecraft-web/Cargo.toml b/apps/deckcraft-web/Cargo.toml
similarity index 77%
rename from apps/slidecraft-web/Cargo.toml
rename to apps/deckcraft-web/Cargo.toml
index 16b8546..7014421 100644
--- a/apps/slidecraft-web/Cargo.toml
+++ b/apps/deckcraft-web/Cargo.toml
@@ -1,6 +1,6 @@
[package]
-name = "slidecraft-web"
-description = "SlideCraft in the browser (eframe web runner, WebGPU with WebGL2 fallback)"
+name = "deckcraft-web"
+description = "DeckCraft in the browser (eframe web runner, WebGPU with WebGL2 fallback)"
version.workspace = true
edition.workspace = true
license.workspace = true
@@ -10,12 +10,12 @@ publish = false
workspace = true
[[bin]]
-name = "slidecraft-web"
+name = "deckcraft-web"
path = "src/main.rs"
[dependencies]
-slidecraft-engine = { workspace = true }
-slidecraft-ui-egui = { workspace = true }
+deckcraft-engine = { workspace = true }
+deckcraft-ui-egui = { workspace = true }
egui = { workspace = true }
log = { workspace = true }
serde_json = { workspace = true }
diff --git a/apps/slidecraft-web/Trunk.toml b/apps/deckcraft-web/Trunk.toml
similarity index 67%
rename from apps/slidecraft-web/Trunk.toml
rename to apps/deckcraft-web/Trunk.toml
index c6e5c00..dac89ec 100644
--- a/apps/slidecraft-web/Trunk.toml
+++ b/apps/deckcraft-web/Trunk.toml
@@ -1,4 +1,4 @@
-# Build with: trunk build --release (from apps/slidecraft-web)
+# Build with: trunk build --release (from apps/deckcraft-web)
[build]
target = "index.html"
dist = "../../dist/web"
diff --git a/apps/deckcraft-web/index.html b/apps/deckcraft-web/index.html
new file mode 100644
index 0000000..b1834c3
--- /dev/null
+++ b/apps/deckcraft-web/index.html
@@ -0,0 +1,20 @@
+
+
+
+
+
+ DeckCraft
+
+
+
+
+
+
+ Loading DeckCraft…
+
+
diff --git a/apps/slidecraft-web/src/main.rs b/apps/deckcraft-web/src/main.rs
similarity index 82%
rename from apps/slidecraft-web/src/main.rs
rename to apps/deckcraft-web/src/main.rs
index 82f564c..366ea34 100644
--- a/apps/slidecraft-web/src/main.rs
+++ b/apps/deckcraft-web/src/main.rs
@@ -1,6 +1,6 @@
-//! SlideCraft in the browser.
+//! DeckCraft in the browser.
//!
-//! Runs the same [`slidecraft_ui_egui::SlideApp`] as the desktop app through eframe's web
+//! Runs the same [`deckcraft_ui_egui::SlideApp`] as the desktop app through eframe's web
//! runner (wgpu: WebGPU where available, WebGL2 otherwise). Build with `trunk build --release`
//! from this directory (output in `dist/web`).
//!
@@ -27,5 +27,5 @@ fn main() {
#[cfg(not(target_arch = "wasm32"))]
fn main() {
- eprintln!("slidecraft-web only runs in the browser: build it with `trunk build --release` in apps/slidecraft-web");
+ eprintln!("deckcraft-web only runs in the browser: build it with `trunk build --release` in apps/deckcraft-web");
}
diff --git a/apps/slidecraft-web/src/web.rs b/apps/deckcraft-web/src/web.rs
similarity index 90%
rename from apps/slidecraft-web/src/web.rs
rename to apps/deckcraft-web/src/web.rs
index 0bb14b7..f0199a5 100644
--- a/apps/slidecraft-web/src/web.rs
+++ b/apps/deckcraft-web/src/web.rs
@@ -1,15 +1,15 @@
//! The browser shell: web `Services`, drag-and-drop, and the eframe web runner.
-use slidecraft_engine::Session;
-use slidecraft_ui_egui::{Inbox, Services, SlideApp};
+use deckcraft_engine::Session;
+use deckcraft_ui_egui::{Inbox, Services, SlideApp};
use wasm_bindgen::JsCast as _;
-const PRESENTATION_EXTS: &[&str] = &["slidecraft", "pptx", "potx", "ppsx", "txt", "md"];
+const PRESENTATION_EXTS: &[&str] = &["deckcraft", "pptx", "potx", "ppsx", "txt", "md"];
const PICTURE_EXTS: &[&str] = &["png", "jpg", "jpeg", "gif", "webp", "bmp", "tif", "tiff"];
const AUDIO_EXTS: &[&str] = &["wav", "mp3", "m4a", "aac", "flac", "ogg", "opus", "aif", "aiff"];
const VIDEO_EXTS: &[&str] = &["mp4", "m4v", "mov", "webm"];
-const CANVAS_ID: &str = "slidecraft_canvas";
-const LOADING_ID: &str = "slidecraft_loading";
+const CANVAS_ID: &str = "deckcraft_canvas";
+const LOADING_ID: &str = "deckcraft_loading";
pub fn start() {
eframe::WebLogger::init(log::LevelFilter::Info).ok();
@@ -34,13 +34,13 @@ pub fn start() {
options,
Box::new(move |cc| {
if let Some(rs) = &cc.wgpu_render_state {
- log::info!("slidecraft-web {}: wgpu backend {:?}", env!("CARGO_PKG_VERSION"), rs.adapter.get_info().backend);
+ log::info!("deckcraft-web {}: wgpu backend {:?}", env!("CARGO_PKG_VERSION"), rs.adapter.get_info().backend);
}
let inbox: Inbox = Inbox::default();
let mut app = SlideApp::new(Session::new(), services(inbox.clone(), cc.egui_ctx.clone()));
let q = query();
if !q.contains("blank") {
- if let Err(e) = slidecraft_engine::sample::open_sample(&mut app.session) {
+ if let Err(e) = deckcraft_engine::sample::open_sample(&mut app.session) {
log::error!("sample deck: {e}");
}
} else if let Err(e) = app.run("file.new", serde_json::json!({})) {
@@ -56,7 +56,7 @@ pub fn start() {
if let Some(el) = document.get_element_by_id(LOADING_ID) {
match result {
Ok(()) => el.remove(),
- Err(e) => el.set_inner_html(&format!("SlideCraft failed to start: {e:?}
A browser with WebGPU or WebGL2 is required.
")),
+ Err(e) => el.set_inner_html(&format!("DeckCraft failed to start: {e:?}
A browser with WebGPU or WebGL2 is required.
")),
}
}
});
@@ -139,7 +139,7 @@ fn services(inbox: Inbox, ctx: egui::Context) -> Services {
/// Trigger a browser download of `bytes` named after the last component of `path`.
fn download(path: &str, bytes: &[u8]) -> Result<(), String> {
let js = |e: wasm_bindgen::JsValue| format!("{e:?}");
- let name = std::path::Path::new(path).file_name().map(|n| n.to_string_lossy().to_string()).unwrap_or_else(|| "slidecraft".into());
+ let name = std::path::Path::new(path).file_name().map(|n| n.to_string_lossy().to_string()).unwrap_or_else(|| "deckcraft".into());
let window = web_sys::window().ok_or("no window")?;
let document = window.document().ok_or("no document")?;
let parts = js_sys::Array::of1(&js_sys::Uint8Array::from(bytes));
@@ -170,7 +170,7 @@ fn mime_for(name: &str) -> &'static str {
Some("pdf") => "application/pdf",
Some("txt") => "text/plain",
Some("pptx") => "application/vnd.openxmlformats-officedocument.presentationml.presentation",
- Some("slidecraft") => "application/zip",
+ Some("deckcraft") => "application/zip",
_ => "application/octet-stream",
}
}
diff --git a/apps/slidecraft/Cargo.toml b/apps/deckcraft/Cargo.toml
similarity index 70%
rename from apps/slidecraft/Cargo.toml
rename to apps/deckcraft/Cargo.toml
index e675bc6..fb49562 100644
--- a/apps/slidecraft/Cargo.toml
+++ b/apps/deckcraft/Cargo.toml
@@ -1,26 +1,26 @@
[package]
-name = "slidecraft"
+name = "deckcraft"
version.workspace = true
edition.workspace = true
license.workspace = true
-description = "SlideCraft desktop app"
+description = "DeckCraft desktop app"
publish = false
-default-run = "slidecraft"
+default-run = "deckcraft"
[lints]
workspace = true
[dependencies]
-slidecraft-engine = { workspace = true }
-slidecraft-ui-egui = { workspace = true }
-slidecraft-media = { workspace = true }
+deckcraft-engine = { workspace = true }
+deckcraft-ui-egui = { workspace = true }
+deckcraft-media = { workspace = true }
eframe = { workspace = true, features = ["default_fonts", "wgpu", "accesskit", "wayland", "x11"] }
egui = { workspace = true }
serde_json = { workspace = true }
log = { workspace = true }
[build-dependencies]
-# Icon + VERSIONINFO for slidecraft.exe; build.rs only uses it when the target is Windows.
+# Icon + VERSIONINFO for deckcraft.exe; build.rs only uses it when the target is Windows.
winresource = "0.1"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
diff --git a/apps/deckcraft/build.rs b/apps/deckcraft/build.rs
new file mode 100644
index 0000000..5bb30d5
--- /dev/null
+++ b/apps/deckcraft/build.rs
@@ -0,0 +1,30 @@
+//! Windows only: embed the app icon and version info (VERSIONINFO) into `deckcraft.exe`, so it
+//! shows in Explorer, the taskbar, the Start menu and Alt-Tab.
+//!
+//! On every other target this does nothing. A missing resource compiler is a warning, so a
+//! cross-compile from macOS or Linux still links, unless `DECKCRAFT_REQUIRE_WINRES=1` turns it
+//! into an error (for release builds).
+
+fn main() {
+ println!("cargo:rerun-if-changed=build.rs");
+ println!("cargo:rerun-if-changed=../../assets/app-icon/deckcraft.ico");
+ println!("cargo:rerun-if-env-changed=DECKCRAFT_REQUIRE_WINRES");
+ if std::env::var("CARGO_CFG_TARGET_OS").as_deref() != Ok("windows") {
+ return;
+ }
+ let mut res = winresource::WindowsResource::new();
+ res.set_icon("../../assets/app-icon/deckcraft.ico")
+ .set("ProductName", "DeckCraft")
+ .set("FileDescription", "DeckCraft presentations")
+ .set("CompanyName", "Learning Machines LLC")
+ .set("LegalCopyright", "Copyright (c) the DeckCraft authors. MIT OR Apache-2.0.")
+ .set("OriginalFilename", "deckcraft.exe")
+ .set("InternalName", "deckcraft");
+ if let Err(e) = res.compile() {
+ if std::env::var_os("DECKCRAFT_REQUIRE_WINRES").is_some() {
+ eprintln!("embedding Windows resources failed: {e}");
+ std::process::exit(1);
+ }
+ println!("cargo:warning=deckcraft.exe built without icon/version resources: {e}");
+ }
+}
diff --git a/apps/slidecraft/src/audio.rs b/apps/deckcraft/src/audio.rs
similarity index 93%
rename from apps/slidecraft/src/audio.rs
rename to apps/deckcraft/src/audio.rs
index cbe9977..86682ad 100644
--- a/apps/slidecraft/src/audio.rs
+++ b/apps/deckcraft/src/audio.rs
@@ -1,8 +1,8 @@
//! cpal audio output for media playback: the device pulls mixed samples from the
-//! `slidecraft_media::Player`, which makes it the playback clock video frames follow.
+//! `deckcraft_media::Player`, which makes it the playback clock video frames follow.
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
-use slidecraft_media::AudioOut;
+use deckcraft_media::AudioOut;
#[derive(Default)]
pub struct CpalOut {
diff --git a/apps/slidecraft/src/control_server.rs b/apps/deckcraft/src/control_server.rs
similarity index 87%
rename from apps/slidecraft/src/control_server.rs
rename to apps/deckcraft/src/control_server.rs
index 17a110a..c2ad876 100644
--- a/apps/slidecraft/src/control_server.rs
+++ b/apps/deckcraft/src/control_server.rs
@@ -1,24 +1,24 @@
//! Loopback JSON-lines control server: one request per line, one reply per line.
-//! This is the transport the MCP server (`slidecraft-cli mcp --connect`) wraps.
+//! This is the transport the MCP server (`deckcraft-cli mcp --connect`) wraps.
use std::io::{BufRead, BufReader, Write};
use std::net::{TcpListener, TcpStream};
use std::sync::mpsc::{Receiver, Sender, channel};
use std::time::Duration;
+use deckcraft_ui_egui::ControlRequest;
use serde_json::{Value, json};
-use slidecraft_ui_egui::ControlRequest;
pub fn start(port: u16, ctx: egui::Context) -> Receiver {
let (tx, rx) = channel::();
let listener = match TcpListener::bind(("127.0.0.1", port)) {
Ok(l) => l,
Err(e) => {
- eprintln!("slidecraft: control server failed to bind 127.0.0.1:{port}: {e}");
+ eprintln!("deckcraft: control server failed to bind 127.0.0.1:{port}: {e}");
return rx;
}
};
- eprintln!("slidecraft: control server listening on 127.0.0.1:{port}");
+ eprintln!("deckcraft: control server listening on 127.0.0.1:{port}");
std::thread::spawn(move || {
for stream in listener.incoming().flatten() {
let tx = tx.clone();
diff --git a/apps/slidecraft/src/main.rs b/apps/deckcraft/src/main.rs
similarity index 78%
rename from apps/slidecraft/src/main.rs
rename to apps/deckcraft/src/main.rs
index 6627b18..d9b5587 100644
--- a/apps/slidecraft/src/main.rs
+++ b/apps/deckcraft/src/main.rs
@@ -1,10 +1,10 @@
-//! SlideCraft desktop app.
+//! DeckCraft desktop app.
//!
-//! Usage: `slidecraft [--control ] [--sample] [--show] [files…]`
+//! Usage: `deckcraft [--control ] [--sample] [--show] [files…]`
//!
-//! `--control ` (or `SLIDECRAFT_CONTROL_PORT`) starts a localhost JSON-lines control server:
+//! `--control ` (or `DECKCRAFT_CONTROL_PORT`) starts a localhost JSON-lines control server:
//! `{"id":1,"method":"ui.inspect","params":{}}` → `{"id":1,"ok":true,"result":…}`.
-//! See `slidecraft_ui_egui::control` for the methods.
+//! See `deckcraft_ui_egui::control` for the methods.
#![cfg_attr(all(target_os = "windows", not(debug_assertions)), windows_subsystem = "windows")]
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
#![forbid(unsafe_code)]
@@ -12,8 +12,8 @@
mod audio;
mod control_server;
-use slidecraft_engine::Session;
-use slidecraft_ui_egui::{Services, SlideApp};
+use deckcraft_engine::Session;
+use deckcraft_ui_egui::{Services, SlideApp};
struct App(SlideApp);
@@ -34,38 +34,38 @@ impl eframe::App for App {
fn prefs_path() -> Option {
let base = if cfg!(target_os = "macos") {
- std::env::var_os("HOME").map(|h| std::path::PathBuf::from(h).join("Library/Application Support/SlideCraft"))
+ std::env::var_os("HOME").map(|h| std::path::PathBuf::from(h).join("Library/Application Support/DeckCraft"))
} else if cfg!(windows) {
- std::env::var_os("APPDATA").map(|a| std::path::PathBuf::from(a).join("SlideCraft"))
+ std::env::var_os("APPDATA").map(|a| std::path::PathBuf::from(a).join("DeckCraft"))
} else {
std::env::var_os("XDG_CONFIG_HOME")
.map(std::path::PathBuf::from)
.or_else(|| std::env::var_os("HOME").map(|h| std::path::PathBuf::from(h).join(".config")))
- .map(|c| c.join("slidecraft"))
+ .map(|c| c.join("deckcraft"))
};
base.map(|b| b.join("ui.json"))
}
fn load_prefs(app: &mut SlideApp) {
- if std::env::var_os("SLIDECRAFT_NO_PREFS").is_some() {
+ if std::env::var_os("DECKCRAFT_NO_PREFS").is_some() {
return;
}
if let Some(p) = prefs_path()
&& let Ok(bytes) = std::fs::read(&p)
- && let Ok(ui) = serde_json::from_slice::(&bytes)
+ && let Ok(ui) = serde_json::from_slice::(&bytes)
{
app.ui = ui;
}
if let Some(p) = prefs_path().map(|p| p.with_file_name("prefs.json"))
&& let Ok(bytes) = std::fs::read(&p)
- && let Ok(prefs) = serde_json::from_slice::(&bytes)
+ && let Ok(prefs) = serde_json::from_slice::(&bytes)
{
app.session.prefs = prefs;
}
}
fn save_prefs(app: &SlideApp) {
- if std::env::var_os("SLIDECRAFT_NO_PREFS").is_some() {
+ if std::env::var_os("DECKCRAFT_NO_PREFS").is_some() {
return;
}
if let Some(p) = prefs_path() {
@@ -91,8 +91,8 @@ fn services() -> Services {
),
"video" => d.add_filter("Video", &["mp4", "m4v", "mov", "webm", "mkv", "wmv"]),
_ => d
- .add_filter("Presentations", &["slidecraft", "pptx", "potx", "ppsx"])
- .add_filter("SlideCraft", &["slidecraft"])
+ .add_filter("Presentations", &["deckcraft", "pptx", "potx", "ppsx"])
+ .add_filter("DeckCraft", &["deckcraft"])
.add_filter("PowerPoint", &["pptx", "potx", "ppsx"])
.add_filter("Outline", &["txt", "md"]),
};
@@ -109,23 +109,23 @@ fn services() -> Services {
rgba.write_to(&mut std::io::Cursor::new(&mut out), image::ImageFormat::Png).ok()?;
Some(out)
})),
- // Media is mixed and clocked by the output device; `SLIDECRAFT_NO_AUDIO` plays silently.
- audio_out: std::env::var_os("SLIDECRAFT_NO_AUDIO")
+ // Media is mixed and clocked by the output device; `DECKCRAFT_NO_AUDIO` plays silently.
+ audio_out: std::env::var_os("DECKCRAFT_NO_AUDIO")
.is_none()
- .then(|| Box::new(audio::CpalOut::default()) as Box),
+ .then(|| Box::new(audio::CpalOut::default()) as Box),
..Default::default()
}
}
-const APP_ID: &str = "ai.storyteller.slidecraft";
+const APP_ID: &str = "ai.storyteller.deckcraft";
fn app_icon() -> Option {
- let png: &[u8] = include_bytes!("../../../assets/app-icon/hicolor/256x256/apps/ai.storyteller.slidecraft.png");
+ let png: &[u8] = include_bytes!("../../../assets/app-icon/hicolor/256x256/apps/ai.storyteller.deckcraft.png");
eframe::icon_data::from_png_bytes(png).map_err(|e| log::warn!("app icon: {e}")).ok()
}
fn main() -> eframe::Result {
- let mut control_port: Option = std::env::var("SLIDECRAFT_CONTROL_PORT").ok().and_then(|p| p.parse().ok());
+ let mut control_port: Option = std::env::var("DECKCRAFT_CONTROL_PORT").ok().and_then(|p| p.parse().ok());
let mut files = Vec::new();
let mut sample = false;
let mut show = false;
@@ -136,7 +136,7 @@ fn main() -> eframe::Result {
"--sample" => sample = true,
"--show" => show = true,
"--version" => {
- println!("slidecraft {}", env!("CARGO_PKG_VERSION"));
+ println!("deckcraft {}", env!("CARGO_PKG_VERSION"));
return Ok(());
}
_ => files.push(a),
@@ -144,7 +144,7 @@ fn main() -> eframe::Result {
}
let mut options = eframe::NativeOptions {
viewport: egui::ViewportBuilder::default()
- .with_title("SlideCraft")
+ .with_title("DeckCraft")
.with_inner_size([1440.0, 900.0])
.with_min_inner_size([900.0, 560.0])
.with_drag_and_drop(true)
@@ -158,21 +158,21 @@ fn main() -> eframe::Result {
options.viewport = options.viewport.with_icon(icon);
}
#[cfg(all(unix, not(target_os = "macos")))]
- if std::env::var_os("DISPLAY").is_some() && std::env::var_os("SLIDECRAFT_WAYLAND").is_none() {
+ if std::env::var_os("DISPLAY").is_some() && std::env::var_os("DECKCRAFT_WAYLAND").is_none() {
options.event_loop_builder = Some(Box::new(|b| {
use winit::platform::x11::EventLoopBuilderExtX11;
b.with_x11();
}));
}
eframe::run_native(
- "SlideCraft",
+ "DeckCraft",
options,
Box::new(move |cc| {
let mut app = SlideApp::new(Session::new(), services());
load_prefs(&mut app);
// AutoRecover: reopen what a previous run left unsaved, then keep it current.
- if std::env::var_os("SLIDECRAFT_NO_RECOVERY").is_none() {
- app.session.recovery_dir = slidecraft_engine::recovery::default_dir();
+ if std::env::var_os("DECKCRAFT_NO_RECOVERY").is_none() {
+ app.session.recovery_dir = deckcraft_engine::recovery::default_dir();
if let Ok(v) = app.session.execute("file.recovery.open", &serde_json::json!({}))
&& let Some(n) = v.get("opened").and_then(|o| o.as_array()).map(Vec::len).filter(|n| *n > 0)
{
@@ -184,12 +184,12 @@ fn main() -> eframe::Result {
let rx = control_server::start(port, cc.egui_ctx.clone());
app = app.with_control(rx);
}
- if sample && let Err(e) = slidecraft_engine::sample::open_sample(&mut app.session) {
- eprintln!("slidecraft: sample: {e}");
+ if sample && let Err(e) = deckcraft_engine::sample::open_sample(&mut app.session) {
+ eprintln!("deckcraft: sample: {e}");
}
for f in files {
if let Err(e) = app.open_path(&f) {
- eprintln!("slidecraft: {f}: {e}");
+ eprintln!("deckcraft: {f}: {e}");
}
}
if show {
diff --git a/apps/slidecraft-cli/Cargo.toml b/apps/slidecraft-cli/Cargo.toml
deleted file mode 100644
index b1f9d36..0000000
--- a/apps/slidecraft-cli/Cargo.toml
+++ /dev/null
@@ -1,19 +0,0 @@
-[package]
-name = "slidecraft-cli"
-version.workspace = true
-edition.workspace = true
-license.workspace = true
-description = "SlideCraft on the command line: render, convert, run commands, inspect, and serve MCP"
-publish = false
-
-[lints]
-workspace = true
-
-[[bin]]
-name = "slidecraft-cli"
-path = "src/main.rs"
-
-[dependencies]
-slidecraft-engine = { workspace = true }
-slidecraft-mcp = { workspace = true }
-serde_json = { workspace = true }
diff --git a/apps/slidecraft-web/index.html b/apps/slidecraft-web/index.html
deleted file mode 100644
index 4f2d54d..0000000
--- a/apps/slidecraft-web/index.html
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
-
-
- SlideCraft
-
-
-
-
-
-
- Loading SlideCraft…
-
-
diff --git a/apps/slidecraft/build.rs b/apps/slidecraft/build.rs
deleted file mode 100644
index 40c647a..0000000
--- a/apps/slidecraft/build.rs
+++ /dev/null
@@ -1,30 +0,0 @@
-//! Windows only: embed the app icon and version info (VERSIONINFO) into `slidecraft.exe`, so it
-//! shows in Explorer, the taskbar, the Start menu and Alt-Tab.
-//!
-//! On every other target this does nothing. A missing resource compiler is a warning, so a
-//! cross-compile from macOS or Linux still links, unless `SLIDECRAFT_REQUIRE_WINRES=1` turns it
-//! into an error (for release builds).
-
-fn main() {
- println!("cargo:rerun-if-changed=build.rs");
- println!("cargo:rerun-if-changed=../../assets/app-icon/slidecraft.ico");
- println!("cargo:rerun-if-env-changed=SLIDECRAFT_REQUIRE_WINRES");
- if std::env::var("CARGO_CFG_TARGET_OS").as_deref() != Ok("windows") {
- return;
- }
- let mut res = winresource::WindowsResource::new();
- res.set_icon("../../assets/app-icon/slidecraft.ico")
- .set("ProductName", "SlideCraft")
- .set("FileDescription", "SlideCraft presentations")
- .set("CompanyName", "Learning Machines LLC")
- .set("LegalCopyright", "Copyright (c) the SlideCraft authors. MIT OR Apache-2.0.")
- .set("OriginalFilename", "slidecraft.exe")
- .set("InternalName", "slidecraft");
- if let Err(e) = res.compile() {
- if std::env::var_os("SLIDECRAFT_REQUIRE_WINRES").is_some() {
- eprintln!("embedding Windows resources failed: {e}");
- std::process::exit(1);
- }
- println!("cargo:warning=slidecraft.exe built without icon/version resources: {e}");
- }
-}
diff --git a/assets/app-icon/slidecraft-1024.png b/assets/app-icon/deckcraft-1024.png
similarity index 100%
rename from assets/app-icon/slidecraft-1024.png
rename to assets/app-icon/deckcraft-1024.png
diff --git a/assets/app-icon/slidecraft.icns b/assets/app-icon/deckcraft.icns
similarity index 100%
rename from assets/app-icon/slidecraft.icns
rename to assets/app-icon/deckcraft.icns
diff --git a/assets/app-icon/slidecraft.ico b/assets/app-icon/deckcraft.ico
similarity index 100%
rename from assets/app-icon/slidecraft.ico
rename to assets/app-icon/deckcraft.ico
diff --git a/assets/app-icon/hicolor/128x128/apps/ai.storyteller.slidecraft.png b/assets/app-icon/hicolor/128x128/apps/ai.storyteller.deckcraft.png
similarity index 100%
rename from assets/app-icon/hicolor/128x128/apps/ai.storyteller.slidecraft.png
rename to assets/app-icon/hicolor/128x128/apps/ai.storyteller.deckcraft.png
diff --git a/assets/app-icon/hicolor/16x16/apps/ai.storyteller.slidecraft.png b/assets/app-icon/hicolor/16x16/apps/ai.storyteller.deckcraft.png
similarity index 100%
rename from assets/app-icon/hicolor/16x16/apps/ai.storyteller.slidecraft.png
rename to assets/app-icon/hicolor/16x16/apps/ai.storyteller.deckcraft.png
diff --git a/assets/app-icon/hicolor/24x24/apps/ai.storyteller.slidecraft.png b/assets/app-icon/hicolor/24x24/apps/ai.storyteller.deckcraft.png
similarity index 100%
rename from assets/app-icon/hicolor/24x24/apps/ai.storyteller.slidecraft.png
rename to assets/app-icon/hicolor/24x24/apps/ai.storyteller.deckcraft.png
diff --git a/assets/app-icon/hicolor/256x256/apps/ai.storyteller.slidecraft.png b/assets/app-icon/hicolor/256x256/apps/ai.storyteller.deckcraft.png
similarity index 100%
rename from assets/app-icon/hicolor/256x256/apps/ai.storyteller.slidecraft.png
rename to assets/app-icon/hicolor/256x256/apps/ai.storyteller.deckcraft.png
diff --git a/assets/app-icon/hicolor/32x32/apps/ai.storyteller.slidecraft.png b/assets/app-icon/hicolor/32x32/apps/ai.storyteller.deckcraft.png
similarity index 100%
rename from assets/app-icon/hicolor/32x32/apps/ai.storyteller.slidecraft.png
rename to assets/app-icon/hicolor/32x32/apps/ai.storyteller.deckcraft.png
diff --git a/assets/app-icon/hicolor/48x48/apps/ai.storyteller.slidecraft.png b/assets/app-icon/hicolor/48x48/apps/ai.storyteller.deckcraft.png
similarity index 100%
rename from assets/app-icon/hicolor/48x48/apps/ai.storyteller.slidecraft.png
rename to assets/app-icon/hicolor/48x48/apps/ai.storyteller.deckcraft.png
diff --git a/assets/app-icon/hicolor/512x512/apps/ai.storyteller.slidecraft.png b/assets/app-icon/hicolor/512x512/apps/ai.storyteller.deckcraft.png
similarity index 100%
rename from assets/app-icon/hicolor/512x512/apps/ai.storyteller.slidecraft.png
rename to assets/app-icon/hicolor/512x512/apps/ai.storyteller.deckcraft.png
diff --git a/assets/app-icon/hicolor/64x64/apps/ai.storyteller.slidecraft.png b/assets/app-icon/hicolor/64x64/apps/ai.storyteller.deckcraft.png
similarity index 100%
rename from assets/app-icon/hicolor/64x64/apps/ai.storyteller.slidecraft.png
rename to assets/app-icon/hicolor/64x64/apps/ai.storyteller.deckcraft.png
diff --git a/assets/app-icon/icon-source.slidecraft b/assets/app-icon/icon-source.deckcraft
similarity index 100%
rename from assets/app-icon/icon-source.slidecraft
rename to assets/app-icon/icon-source.deckcraft
diff --git a/crates/anim/Cargo.toml b/crates/anim/Cargo.toml
index 53c2805..fd9c9ec 100644
--- a/crates/anim/Cargo.toml
+++ b/crates/anim/Cargo.toml
@@ -1,14 +1,14 @@
[package]
-name = "slidecraft-anim"
+name = "deckcraft-anim"
version.workspace = true
edition.workspace = true
license.workspace = true
-description = "SlideCraft slide show timing: animation sequences, per-shape animation state over time, and transition frame layouts"
+description = "DeckCraft slide show timing: animation sequences, per-shape animation state over time, and transition frame layouts"
[lints]
workspace = true
[dependencies]
-slidecraft-model = { workspace = true }
-slidecraft-geom = { workspace = true }
+deckcraft-model = { workspace = true }
+deckcraft-geom = { workspace = true }
serde = { workspace = true }
diff --git a/crates/anim/src/effects.rs b/crates/anim/src/effects.rs
index a795635..37776f8 100644
--- a/crates/anim/src/effects.rs
+++ b/crates/anim/src/effects.rs
@@ -2,7 +2,7 @@
use std::f64::consts::PI;
-use slidecraft_model::{AnimClass, Rgba, Xfrm};
+use deckcraft_model::{AnimClass, Rgba, Xfrm};
use crate::AnimState;
use crate::easing::{bounce_out, ease, smooth};
diff --git a/crates/anim/src/lib.rs b/crates/anim/src/lib.rs
index a5a8744..5452ff1 100644
--- a/crates/anim/src/lib.rs
+++ b/crates/anim/src/lib.rs
@@ -1,4 +1,4 @@
-//! Slide show timing for SlideCraft.
+//! Slide show timing for DeckCraft.
//!
//! - [`Timeline`]: a slide's animation list grouped into click steps, with per-shape (and
//! per-paragraph) [`AnimState`] at any point of the show.
@@ -22,8 +22,8 @@ mod transition;
#[cfg(test)]
mod tests;
+pub use deckcraft_model::Rgba;
use serde::{Deserialize, Serialize};
-pub use slidecraft_model::Rgba;
pub use easing::ease;
pub use morph::{morph_pairs, morph_xfrm};
diff --git a/crates/anim/src/morph.rs b/crates/anim/src/morph.rs
index 6689153..ddbde9c 100644
--- a/crates/anim/src/morph.rs
+++ b/crates/anim/src/morph.rs
@@ -1,6 +1,6 @@
//! Morph: matching shapes between two slides and interpolating their boxes.
-use slidecraft_model::{Shape, ShapeId, Slide, Xfrm};
+use deckcraft_model::{Shape, ShapeId, Slide, Xfrm};
use crate::clampf;
diff --git a/crates/anim/src/show.rs b/crates/anim/src/show.rs
index efd236d..a964e87 100644
--- a/crates/anim/src/show.rs
+++ b/crates/anim/src/show.rs
@@ -1,7 +1,7 @@
//! Slide show navigation: click steps, slide order (hidden slides, custom shows, ranges), loop
//! and automatic advance.
-use slidecraft_model::Presentation;
+use deckcraft_model::Presentation;
use crate::Timeline;
diff --git a/crates/anim/src/tests.rs b/crates/anim/src/tests.rs
index 9337d91..4a7b369 100644
--- a/crates/anim/src/tests.rs
+++ b/crates/anim/src/tests.rs
@@ -2,8 +2,8 @@
use std::sync::Arc;
-use slidecraft_model::anim::{ANIMATIONS, TRANSITIONS, TextBuild};
-use slidecraft_model::{
+use deckcraft_model::anim::{ANIMATIONS, TRANSITIONS, TextBuild};
+use deckcraft_model::{
AnimClass, AnimStart, Animation, ColorRef, CustomShow, Presentation, Rgba, Shape, ShapeId, Slide, SlideId, TextBody, Transition, Xfrm,
};
diff --git a/crates/anim/src/timeline.rs b/crates/anim/src/timeline.rs
index 9a60d13..e73acfd 100644
--- a/crates/anim/src/timeline.rs
+++ b/crates/anim/src/timeline.rs
@@ -1,8 +1,8 @@
//! A slide's animation sequence: click steps, effect start times and per-shape state over time.
-use slidecraft_model::anim::TextBuild;
-use slidecraft_model::style::Fill;
-use slidecraft_model::{AnimClass, AnimStart, Animation, ColorScheme, Rgba, SchemeSlot, ShapeId, Slide, Xfrm};
+use deckcraft_model::anim::TextBuild;
+use deckcraft_model::style::Fill;
+use deckcraft_model::{AnimClass, AnimStart, Animation, ColorScheme, Rgba, SchemeSlot, ShapeId, Slide, Xfrm};
use crate::AnimState;
use crate::effects::{EffectSpec, exit_base, motion_path};
@@ -116,7 +116,7 @@ fn sanitize_xfrm(x: Xfrm, sw: f64, sh: f64) -> Xfrm {
}
fn default_scheme() -> Option {
- slidecraft_model::theme::builtin_color_schemes().into_iter().next()
+ deckcraft_model::theme::builtin_color_schemes().into_iter().next()
}
impl Timeline {
@@ -139,11 +139,11 @@ impl Timeline {
owned.as_ref()
}
};
- let resolve = |c: &slidecraft_model::ColorRef| -> Rgba {
+ let resolve = |c: &deckcraft_model::ColorRef| -> Rgba {
match scheme {
Some(s) => c.resolve(s, None),
None => match &c.base {
- slidecraft_model::ColorBase::Rgb { rgb } => *rgb,
+ deckcraft_model::ColorBase::Rgb { rgb } => *rgb,
_ => Rgba::rgb(0x15, 0x60, 0x82),
},
}
@@ -180,7 +180,7 @@ impl Timeline {
(None, TextBuild::ByParagraph) => {
let mut v: Vec> = vec![];
if let Some(t) = shape.and_then(|s| s.text.as_ref()) {
- let filled = |p: &slidecraft_model::text::Paragraph| p.runs.iter().any(|r| !r.text.trim().is_empty());
+ 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 = None;
for (i, p) in t.paragraphs.iter().enumerate() {
@@ -465,7 +465,7 @@ fn status(e: &EffectTiming, step: usize, t: f64) -> Status {
Status::Active(p.clamp(0.0, 1.0))
}
-fn build_spec(a: &Animation, xf: Xfrm, sw: f64, sh: f64, resolve: &dyn Fn(&slidecraft_model::ColorRef) -> Rgba) -> EffectSpec {
+fn build_spec(a: &Animation, xf: Xfrm, sw: f64, sh: f64, resolve: &dyn Fn(&deckcraft_model::ColorRef) -> Rgba) -> EffectSpec {
let effect = if a.class == AnimClass::Exit { exit_base(&a.effect).to_string() } else { a.effect.clone() };
let path = if a.class == AnimClass::Path { Some(motion_path(&a.effect, &a.option, a.path.as_deref())) } else { None };
EffectSpec {
diff --git a/crates/anim/src/transition.rs b/crates/anim/src/transition.rs
index 008b906..876c88f 100644
--- a/crates/anim/src/transition.rs
+++ b/crates/anim/src/transition.rs
@@ -196,7 +196,7 @@ fn scale_rect(r: R, s: f64) -> R {
// ---------- effects ----------
-/// Layers of the transition `kind` (a [`slidecraft_model::anim::TRANSITIONS`] id) with `option` at
+/// Layers of the transition `kind` (a [`deckcraft_model::anim::TRANSITIONS`] id) with `option` at
/// progress `t` (0..1). Draw in order over black. At `t >= 1` the result is the new slide alone;
/// at `t <= 0` (or NaN) the old slide alone. Morph returns a plain crossfade (the UI renders morph
/// frames with [`crate::morph_pairs`]).
@@ -736,7 +736,7 @@ fn layers(kind: &str, option: &str, t: f64, depth: u32) -> Vec {
"random" if depth == 0 => {
let h = option.bytes().fold(0x51ED_u64, |h, b| hash64(h ^ b as u64));
let k = RANDOM_KINDS.get((h % RANDOM_KINDS.len() as u64) as usize).copied().unwrap_or("fade");
- let opt = slidecraft_model::anim::TRANSITIONS.iter().find(|x| x.0 == k).and_then(|x| x.4.first().copied()).unwrap_or("");
+ let opt = deckcraft_model::anim::TRANSITIONS.iter().find(|x| x.0 == k).and_then(|x| x.4.first().copied()).unwrap_or("");
layers(k, opt, t, depth + 1)
}
_ => crossfade(t),
diff --git a/crates/av1/Cargo.toml b/crates/av1/Cargo.toml
index c08e31d..ddd1c56 100644
--- a/crates/av1/Cargo.toml
+++ b/crates/av1/Cargo.toml
@@ -1,5 +1,5 @@
[package]
-name = "slidecraft-av1"
+name = "deckcraft-av1"
description = "Clean-room AV1 decoder (AV1 Bitstream & Decoding Process Specification v1.0.0 with Errata 1)"
version.workspace = true
edition.workspace = true
@@ -14,6 +14,6 @@ default = ["threads"]
threads = ["dep:rayon"]
[dependencies]
-slidecraft-bitstream = { workspace = true }
+deckcraft-bitstream = { workspace = true }
thiserror = { workspace = true }
rayon = { workspace = true, optional = true }
diff --git a/crates/bitstream/Cargo.toml b/crates/bitstream/Cargo.toml
index 99cb98f..8465f1c 100644
--- a/crates/bitstream/Cargo.toml
+++ b/crates/bitstream/Cargo.toml
@@ -1,5 +1,5 @@
[package]
-name = "slidecraft-bitstream"
+name = "deckcraft-bitstream"
version.workspace = true
edition.workspace = true
license.workspace = true
diff --git a/crates/bitstream/src/lib.rs b/crates/bitstream/src/lib.rs
index e5df0bb..b5acf03 100644
--- a/crates/bitstream/src/lib.rs
+++ b/crates/bitstream/src/lib.rs
@@ -1,4 +1,4 @@
-//! Bit-level reading and writing shared by SlideCraft's codec crates (ported from FilmCraft).
+//! Bit-level reading and writing shared by DeckCraft's codec crates (ported from FilmCraft).
//!
//! - [`BitReader`]: MSB-first reader with Exp-Golomb (`ue(v)`, `se(v)`), as used by H.264/HEVC/AV1 headers.
//! - [`BitWriter`]: the matching writer (for encoders and header rewriting).
diff --git a/crates/color/Cargo.toml b/crates/color/Cargo.toml
index f59d39d..9d5a552 100644
--- a/crates/color/Cargo.toml
+++ b/crates/color/Cargo.toml
@@ -1,9 +1,9 @@
[package]
-name = "slidecraft-color"
+name = "deckcraft-color"
version.workspace = true
edition.workspace = true
license.workspace = true
-description = "SlideCraft colour: RGB/HSL, theme colour slots, DrawingML-style colour transforms"
+description = "DeckCraft colour: RGB/HSL, theme colour slots, DrawingML-style colour transforms"
[lints]
workspace = true
diff --git a/crates/color/src/lib.rs b/crates/color/src/lib.rs
index b6a318f..ba50e9a 100644
--- a/crates/color/src/lib.rs
+++ b/crates/color/src/lib.rs
@@ -1,4 +1,4 @@
-//! SlideCraft colour: sRGB colours, theme colour slots and DrawingML-style colour transforms
+//! DeckCraft colour: sRGB colours, theme colour slots and DrawingML-style colour transforms
//! (tint, shade, luminance modulation/offset, alpha…), so theme-relative colours resolve the way
//! presentation files expect.
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
diff --git a/crates/engine/Cargo.toml b/crates/engine/Cargo.toml
index eaedebd..1f4dbfc 100644
--- a/crates/engine/Cargo.toml
+++ b/crates/engine/Cargo.toml
@@ -1,24 +1,24 @@
[package]
-name = "slidecraft-engine"
+name = "deckcraft-engine"
version.workspace = true
edition.workspace = true
license.workspace = true
-description = "SlideCraft engine: sessions, undo history, selection, the command registry every frontend dispatches through, and pointer tools"
+description = "DeckCraft engine: sessions, undo history, selection, the command registry every frontend dispatches through, and pointer tools"
[lints]
workspace = true
[dependencies]
-slidecraft-geom = { workspace = true }
-slidecraft-color = { workspace = true }
-slidecraft-model = { workspace = true }
-slidecraft-fonts = { workspace = true }
-slidecraft-text = { workspace = true }
-slidecraft-render = { workspace = true }
-slidecraft-pdf = { workspace = true }
-slidecraft-format = { workspace = true }
-slidecraft-pptx = { workspace = true }
-slidecraft-media = { workspace = true }
+deckcraft-geom = { workspace = true }
+deckcraft-color = { workspace = true }
+deckcraft-model = { workspace = true }
+deckcraft-fonts = { workspace = true }
+deckcraft-text = { workspace = true }
+deckcraft-render = { workspace = true }
+deckcraft-pdf = { workspace = true }
+deckcraft-format = { workspace = true }
+deckcraft-pptx = { workspace = true }
+deckcraft-media = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
i_overlay = { workspace = true }
diff --git a/crates/engine/src/cmd/animation.rs b/crates/engine/src/cmd/animation.rs
index c19f92c..e6844f5 100644
--- a/crates/engine/src/cmd/animation.rs
+++ b/crates/engine/src/cmd/animation.rs
@@ -2,8 +2,8 @@
use std::sync::Arc;
+use deckcraft_model::anim::{ANIMATIONS, AnimClass, AnimStart, Animation, TextBuild, animation_info};
use serde_json::{Value, json};
-use slidecraft_model::anim::{ANIMATIONS, AnimClass, AnimStart, Animation, TextBuild, animation_info};
use super::*;
use crate::{Result, Session};
@@ -227,7 +227,7 @@ fn timing(s: &mut Session, p: &Value) -> Result {
let delay = p.get("delay").and_then(Value::as_u64).map(|v| v.min(600_000) as u32);
let repeat = p.get("repeat").and_then(Value::as_u64).map(|v| v.min(u32::MAX as u64) as u32);
let rewind = bool_param(p, "rewind");
- let trigger = p.get("trigger").map(|v| v.as_u64().and_then(|x| u32::try_from(x).ok()).map(slidecraft_model::ShapeId));
+ let trigger = p.get("trigger").map(|v| v.as_u64().and_then(|x| u32::try_from(x).ok()).map(deckcraft_model::ShapeId));
each_target(s, p, |a| {
if let Some(x) = start {
a.start = x;
diff --git a/crates/engine/src/cmd/arrange.rs b/crates/engine/src/cmd/arrange.rs
index 49ee64b..c755050 100644
--- a/crates/engine/src/cmd/arrange.rs
+++ b/crates/engine/src/cmd/arrange.rs
@@ -1,8 +1,8 @@
//! Arrange: order, group, align, distribute, rotate and flip.
+use deckcraft_geom::{Point, Rect, Xfrm};
+use deckcraft_model::{Shape, ShapeId, ShapeKind};
use serde_json::{Value, json};
-use slidecraft_geom::{Point, Rect, Xfrm};
-use slidecraft_model::{Shape, ShapeId, ShapeKind};
use super::*;
use crate::{Result, Session};
@@ -145,7 +145,7 @@ fn group(s: &mut Session, p: &Value) -> Result {
pub(crate) fn child_to_parent(group: &Xfrm, ch: &Xfrm, c: &Xfrm) -> Xfrm {
let sx = if ch.w.abs() > 1e-9 { group.w / ch.w } else { 1.0 };
let sy = if ch.h.abs() > 1e-9 { group.h / ch.h } else { 1.0 };
- let a = slidecraft_geom::group_child_affine(group, Rect::new(ch.x, ch.y, ch.x + ch.w, ch.y + ch.h));
+ let a = deckcraft_geom::group_child_affine(group, Rect::new(ch.x, ch.y, ch.x + ch.w, ch.y + ch.h));
let center = a * Point::new(c.x + c.w / 2.0, c.y + c.h / 2.0);
let (w, h) = (c.w * sx.abs(), c.h * sy.abs());
let mut rot = c.rot + group.rot;
diff --git a/crates/engine/src/cmd/design.rs b/crates/engine/src/cmd/design.rs
index 6cf6b03..a8fd9a3 100644
--- a/crates/engine/src/cmd/design.rs
+++ b/crates/engine/src/cmd/design.rs
@@ -3,11 +3,11 @@
use std::sync::Arc;
+use deckcraft_geom::Size;
+use deckcraft_model::text::{Run, RunKind, TextBody};
+use deckcraft_model::theme::{builtin_color_schemes, builtin_font_schemes, builtin_themes};
+use deckcraft_model::{Background, Fill, PhType, Presentation, Shape, ShapeId, defaults};
use serde_json::{Value, json};
-use slidecraft_geom::Size;
-use slidecraft_model::text::{Run, RunKind, TextBody};
-use slidecraft_model::theme::{builtin_color_schemes, builtin_font_schemes, builtin_themes};
-use slidecraft_model::{Background, Fill, PhType, Presentation, Shape, ShapeId, defaults};
use super::*;
use crate::{Result, Session, Target};
@@ -114,7 +114,7 @@ fn colors(s: &mut Session, p: &Value) -> Result {
}
if let Some(c) = &custom {
for (k, v) in c {
- if let (Some(slot), Some(rgb)) = (slidecraft_color::SchemeSlot::from_xml(k), v.as_str().and_then(slidecraft_color::Rgba::from_hex)) {
+ if let (Some(slot), Some(rgb)) = (deckcraft_color::SchemeSlot::from_xml(k), v.as_str().and_then(deckcraft_color::Rgba::from_hex)) {
m.theme.colors.set(slot, rgb);
}
}
@@ -134,10 +134,10 @@ fn fonts(s: &mut Session, p: &Value) -> Result {
} else {
let major = str_param(p, "major").ok_or_else(|| bad("design.fonts", "give `name`, or `major` and `minor`"))?;
let minor = str_param(p, "minor").unwrap_or(major);
- slidecraft_model::theme::FontScheme {
+ deckcraft_model::theme::FontScheme {
name: "Custom".into(),
- major: slidecraft_model::theme::FontSet { latin: major.into(), ..Default::default() },
- minor: slidecraft_model::theme::FontSet { latin: minor.into(), ..Default::default() },
+ major: deckcraft_model::theme::FontSet { latin: major.into(), ..Default::default() },
+ minor: deckcraft_model::theme::FontSet { latin: minor.into(), ..Default::default() },
}
};
s.edit(|doc, sel| {
@@ -155,16 +155,12 @@ fn background(s: &mut Session, p: &Value) -> Result {
Some(Background::Fill { fill: Fill::solid(c) })
} else if let Some(i) = usize_param(p, "style") {
// Background styles: 3 theme bg fills × 4 colours (bg1, bg2, tx1, tx2).
- let slots = [
- slidecraft_color::SchemeSlot::Bg1,
- slidecraft_color::SchemeSlot::Bg2,
- slidecraft_color::SchemeSlot::Tx2,
- slidecraft_color::SchemeSlot::Tx1,
- ];
+ let slots =
+ [deckcraft_color::SchemeSlot::Bg1, deckcraft_color::SchemeSlot::Bg2, deckcraft_color::SchemeSlot::Tx2, deckcraft_color::SchemeSlot::Tx1];
let i = i.clamp(1, 12) - 1;
Some(Background::Ref {
idx: 1001 + (i / 4) as u32,
- color: slidecraft_model::ColorRef::scheme(slots.get(i % 4).copied().unwrap_or(slidecraft_color::SchemeSlot::Bg1)),
+ color: deckcraft_model::ColorRef::scheme(slots.get(i % 4).copied().unwrap_or(deckcraft_color::SchemeSlot::Bg1)),
})
} else if let Some(g) = p.get("gradient") {
let mut g = super::shape::gradient_param(g).ok_or_else(|| bad("design.background", "gradient needs two or more `stops`"))?;
@@ -172,17 +168,17 @@ fn background(s: &mut Session, p: &Value) -> Result {
Some(Background::Fill { fill: Fill::Gradient(g) })
} else if let Some(pt) = p.get("pattern") {
Some(Background::Fill {
- fill: Fill::Pattern(slidecraft_model::style::PatternFill {
+ fill: Fill::Pattern(deckcraft_model::style::PatternFill {
preset: pt.get("preset").and_then(Value::as_str).unwrap_or("pct50").to_string(),
- fg: pt.get("fg").and_then(color_value).unwrap_or(slidecraft_model::ColorRef::scheme(slidecraft_color::SchemeSlot::Accent1)),
- bg: pt.get("bg").and_then(color_value).unwrap_or(slidecraft_model::ColorRef::scheme(slidecraft_color::SchemeSlot::Bg1)),
+ fg: pt.get("fg").and_then(color_value).unwrap_or(deckcraft_model::ColorRef::scheme(deckcraft_color::SchemeSlot::Accent1)),
+ bg: pt.get("bg").and_then(color_value).unwrap_or(deckcraft_model::ColorRef::scheme(deckcraft_color::SchemeSlot::Bg1)),
}),
})
} else if p.get("picture").is_some() {
let (name, bytes) = super::insert::media_bytes(&with_param(p, "data", p.get("picture").cloned().unwrap_or_default()), "design.background")?;
let ct = super::insert::content_type(&name, &bytes);
let media = s.edit(|doc, _| Ok(doc.add_media(&name, ct, bytes)))?;
- Some(Background::Fill { fill: Fill::Picture(slidecraft_model::style::PictureFill { media, ..Default::default() }) })
+ Some(Background::Fill { fill: Fill::Picture(deckcraft_model::style::PictureFill { media, ..Default::default() }) })
} else {
return Err(bad("design.background", "give `color`, `gradient`, `pattern`, `picture`, `style` or `reset`"));
};
@@ -234,7 +230,7 @@ fn scale_shapes(shapes: &mut [Shape], sx: f64, sy: f64, k: f64, ox: f64, oy: f64
x.h *= sy;
}
if let Some(t) = sh.text.as_mut() {
- slidecraft_model::edit::format_all(t, &|r| {
+ deckcraft_model::edit::format_all(t, &|r| {
if let Some(sz) = r.size.as_mut() {
*sz = (*sz * k).max(1.0);
}
@@ -316,7 +312,7 @@ fn header_footer(s: &mut Session, p: &Value) -> Result {
for i in list {
let Some(layout) = doc.slides.get(i).map(|x| x.layout) else { continue };
let lay = doc.layout(layout).map(|(_, l)| l.clone());
- let is_title = lay.as_ref().is_some_and(|l| l.kind == slidecraft_model::LayoutType::Title);
+ let is_title = lay.as_ref().is_some_and(|l| l.kind == deckcraft_model::LayoutType::Title);
let want = |k: PhType| {
!(hf.hide_on_title && is_title)
&& match k {
@@ -343,7 +339,7 @@ fn header_footer(s: &mut Session, p: &Value) -> Result {
let Some(lp) = lp else { continue };
let body = match k {
PhType::SlideNum => TextBody {
- paragraphs: vec![slidecraft_model::Paragraph {
+ paragraphs: vec![deckcraft_model::Paragraph {
runs: vec![Run {
text: "‹#›".into(),
props: Default::default(),
@@ -354,7 +350,7 @@ fn header_footer(s: &mut Session, p: &Value) -> Result {
..Default::default()
},
PhType::Date if hf.date_text.is_empty() => TextBody {
- paragraphs: vec![slidecraft_model::Paragraph {
+ paragraphs: vec![deckcraft_model::Paragraph {
runs: vec![Run { text: today(), props: Default::default(), kind: RunKind::Field { field: "datetime1".into() } }],
..Default::default()
}],
@@ -444,7 +440,7 @@ fn insert_layout(s: &mut Session, p: &Value) -> Result {
let name = str_param(p, "name").unwrap_or("Custom Layout").to_string();
s.edit(|doc, sel| {
let mi = master_index(doc, sel);
- let id = slidecraft_model::LayoutId(doc.alloc_id());
+ let id = deckcraft_model::LayoutId(doc.alloc_id());
let tid = ShapeId(doc.alloc_id());
let size = doc.slide_size;
let m = Arc::make_mut(doc.masters.get_mut(mi).ok_or_else(|| bad("master.insertLayout", "no master"))?);
@@ -460,10 +456,10 @@ fn insert_layout(s: &mut Session, p: &Value) -> Result {
};
m.layouts.insert(
at.min(m.layouts.len()),
- slidecraft_model::Layout {
+ deckcraft_model::Layout {
id,
name,
- kind: slidecraft_model::LayoutType::Custom,
+ kind: deckcraft_model::LayoutType::Custom,
shapes: title.into_iter().collect(),
background: None,
show_master_shapes: true,
@@ -528,11 +524,11 @@ fn insert_placeholder(s: &mut Session, p: &Value) -> Result {
return Err(bad("master.insertPlaceholder", "select a layout in Slide Master view"));
}
let size = s.doc()?.doc.slide_size;
- let rect = rect_param(p, "rect").unwrap_or(slidecraft_geom::Xfrm::new(size.width * 0.25, size.height * 0.3, size.width * 0.5, size.height * 0.4));
+ let rect = rect_param(p, "rect").unwrap_or(deckcraft_geom::Xfrm::new(size.width * 0.25, size.height * 0.3, size.width * 0.5, size.height * 0.4));
let idx = s.doc()?.shapes().iter().filter_map(|x| x.ph.as_ref().map(|p| p.idx)).max().unwrap_or(0) + 1;
let shape = Shape {
xfrm: Some(rect),
- ph: Some(slidecraft_model::Placeholder { kind, idx, ..Default::default() }),
+ ph: Some(deckcraft_model::Placeholder { kind, idx, ..Default::default() }),
text: Some(TextBody::default()),
..Default::default()
};
diff --git a/crates/engine/src/cmd/edit.rs b/crates/engine/src/cmd/edit.rs
index 31666b5..dfcbec6 100644
--- a/crates/engine/src/cmd/edit.rs
+++ b/crates/engine/src/cmd/edit.rs
@@ -2,8 +2,8 @@
use std::sync::Arc;
+use deckcraft_model::{MediaId, Shape, ShapeId, Slide, SlideId};
use serde_json::{Value, json};
-use slidecraft_model::{MediaId, Shape, ShapeId, Slide, SlideId};
use super::*;
use crate::{HistoryEntry, Result, Session};
@@ -49,10 +49,10 @@ fn slides_scope(s: &Session, p: &Value) -> bool {
}
fn media_of_shapes(shapes: &[Shape], out: &mut Vec) {
- slidecraft_model::walk(shapes, &mut |s, _| {
+ deckcraft_model::walk(shapes, &mut |s, _| {
match &s.kind {
- slidecraft_model::ShapeKind::Picture { fill } => out.push(fill.media),
- slidecraft_model::ShapeKind::Media(m) => {
+ deckcraft_model::ShapeKind::Picture { fill } => out.push(fill.media),
+ deckcraft_model::ShapeKind::Media(m) => {
out.push(m.media);
if let Some(p) = m.poster {
out.push(p);
@@ -60,7 +60,7 @@ fn media_of_shapes(shapes: &[Shape], out: &mut Vec) {
}
_ => {}
}
- if let Some(slidecraft_model::Fill::Picture(pf)) = &s.fill {
+ if let Some(deckcraft_model::Fill::Picture(pf)) = &s.fill {
out.push(pf.media);
}
});
@@ -73,8 +73,8 @@ fn copy_inner(s: &mut Session, p: &Value) -> Result {
if t.is_range() {
let body = super::text::body_of(st, t).cloned().unwrap_or_default();
let (a, b) = t.ordered();
- let plain = slidecraft_model::edit::text_range(&body, a, b);
- let sliced = slidecraft_model::edit::slice(&body, a, b);
+ let plain = deckcraft_model::edit::text_range(&body, a, b);
+ let sliced = deckcraft_model::edit::slice(&body, a, b);
s.clipboard = crate::Clipboard { text: Some(sliced), plain: plain.clone(), ..Default::default() };
return Ok(plain);
}
@@ -126,8 +126,8 @@ fn cut(s: &mut Session, p: &Value) -> Result {
}
/// Give pasted shapes fresh ids (groups included) and map connector ends.
-pub(crate) fn reid(doc: &mut slidecraft_model::Presentation, shapes: &mut [Shape]) {
- fn rec(doc: &mut slidecraft_model::Presentation, shapes: &mut [Shape], depth: usize) {
+pub(crate) fn reid(doc: &mut deckcraft_model::Presentation, shapes: &mut [Shape]) {
+ fn rec(doc: &mut deckcraft_model::Presentation, shapes: &mut [Shape], depth: usize) {
if depth > 64 {
return;
}
@@ -141,7 +141,7 @@ pub(crate) fn reid(doc: &mut slidecraft_model::Presentation, shapes: &mut [Shape
rec(doc, shapes, 0);
}
-fn bring_media(doc: &mut slidecraft_model::Presentation, media: &[slidecraft_model::MediaItem]) {
+fn bring_media(doc: &mut deckcraft_model::Presentation, media: &[deckcraft_model::MediaItem]) {
for m in media {
if doc.media(m.id).is_none_or(|x| x.data != m.data) {
if doc.media(m.id).is_none() {
@@ -319,7 +319,7 @@ pub(crate) fn remove_shapes(shapes: &mut Vec, ids: &[ShapeId]) -> usize {
fn count(v: &[Shape]) -> usize {
let mut n = 0;
- slidecraft_model::walk(v, &mut |_, _| n += 1);
+ deckcraft_model::walk(v, &mut |_, _| n += 1);
n
}
diff --git a/crates/engine/src/cmd/file.rs b/crates/engine/src/cmd/file.rs
index 8db1968..55aac8b 100644
--- a/crates/engine/src/cmd/file.rs
+++ b/crates/engine/src/cmd/file.rs
@@ -1,7 +1,7 @@
//! File menu: new, open, save, export, close, properties.
+use deckcraft_model::{Presentation, defaults};
use serde_json::{Value, json};
-use slidecraft_model::{Presentation, defaults};
use super::*;
use crate::{DocState, EngineError, Result, Session};
@@ -11,11 +11,11 @@ pub fn specs() -> Vec {
cmd!(noundo "file.new", "New Presentation", ["File"], Some("Cmd+N"), "{theme?: name, size?: [w,h] pt, blank?: bool}", always, new),
cmd!(noundo "file.open", "Open…", ["File"], Some("Cmd+O"), "{path}", always, open),
cmd!(noundo "file.openBytes", "Open Data", [], None, "{name, data: base64}", always, open_bytes),
- cmd!(noundo "file.save", "Save", ["File"], Some("Cmd+S"), "{path?, format?: slidecraft|pptx}", has_doc, save),
+ cmd!(noundo "file.save", "Save", ["File"], Some("Cmd+S"), "{path?, format?: deckcraft|pptx}", has_doc, save),
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?: slidecraft|pptx} → {data: base64}", has_doc, save_bytes_cmd),
- cmd!(noundo "file.export", "Export…", ["File"], None, "{path, format: png|jpeg|pptx|slidecraft|outline|pdf, slide?: index, all?: bool, scale?: px per pt; pdf: layout?: slides|notes|handouts, perPage?: 1|2|3|4|6|9, dpi?, slides?: [index], includeHidden?, textLayer?, frame?}", has_doc, export),
+ 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|pdf, slide?: index, all?: bool, scale?: px per pt; pdf: layout?: slides|notes|handouts, perPage?: 1|2|3|4|6|9, dpi?, slides?: [index], includeHidden?, textLayer?, frame?}", has_doc, export),
cmd!(query "file.render", "Render Slide", [], None, "{slide?: index, scale?, edit?: bool} → {png: base64, width, height}", has_doc, render),
cmd!(noundo "file.close", "Close", ["File"], Some("Cmd+W"), "{}", has_doc, close),
cmd!(
@@ -36,8 +36,8 @@ pub fn specs() -> Vec {
]
}
-fn theme_named(name: &str) -> Option {
- slidecraft_model::theme::builtin_themes().into_iter().find(|t| t.name.eq_ignore_ascii_case(name))
+fn theme_named(name: &str) -> Option {
+ deckcraft_model::theme::builtin_themes().into_iter().find(|t| t.name.eq_ignore_ascii_case(name))
}
fn new(s: &mut Session, p: &Value) -> Result {
@@ -45,7 +45,7 @@ fn new(s: &mut Session, p: &Value) -> Result {
let size = p
.get("size")
.and_then(Value::as_array)
- .and_then(|a| Some(slidecraft_geom::Size::new(a.first()?.as_f64()?, a.get(1)?.as_f64()?)))
+ .and_then(|a| Some(deckcraft_geom::Size::new(a.first()?.as_f64()?, a.get(1)?.as_f64()?)))
.filter(|s| s.width >= 1.0 && s.height >= 1.0 && s.width < 5000.0 && s.height < 5000.0)
.unwrap_or(defaults::WIDE);
let doc = defaults::blank_presentation(size, theme, !bool_or(p, "blank", false));
@@ -56,25 +56,25 @@ fn new(s: &mut Session, p: &Value) -> Result {
/// Recognise and read presentation bytes (native, PPTX).
pub fn open_presentation(name: &str, bytes: &[u8]) -> Result {
- if slidecraft_format::sniff(bytes) {
- return slidecraft_format::load(bytes).map_err(|e| EngineError::Other(e.to_string()));
+ if deckcraft_format::sniff(bytes) {
+ return deckcraft_format::load(bytes).map_err(|e| EngineError::Other(e.to_string()));
}
- if slidecraft_pptx::sniff(bytes) {
- let mut p = slidecraft_pptx::import(bytes).map_err(|e| EngineError::Other(e.to_string()))?;
- slidecraft_format::repair(&mut p);
+ if deckcraft_pptx::sniff(bytes) {
+ let mut p = deckcraft_pptx::import(bytes).map_err(|e| EngineError::Other(e.to_string()))?;
+ deckcraft_format::repair(&mut p);
return Ok(p);
}
let lower = name.to_ascii_lowercase();
if lower.ends_with(".txt") || lower.ends_with(".md") {
let text = String::from_utf8_lossy(bytes);
let mut p = defaults::blank_presentation(defaults::WIDE, Default::default(), false);
- slidecraft_format::outline_to_slides(&mut p, &text);
+ deckcraft_format::outline_to_slides(&mut p, &text);
if p.slides.is_empty() {
p = defaults::new_presentation(None);
}
return Ok(p);
}
- Err(EngineError::Other(format!("{name}: not a presentation SlideCraft can read")))
+ Err(EngineError::Other(format!("{name}: not a presentation DeckCraft can read")))
}
pub fn add_opened(s: &mut Session, name: &str, path: Option, p: Presentation) -> usize {
@@ -87,7 +87,7 @@ fn open(s: &mut Session, p: &Value) -> Result {
let path = str_param(p, "path").ok_or_else(|| bad("file.open", "missing `path`"))?;
let bytes = std::fs::read(path).map_err(|e| EngineError::Other(format!("{path}: {e}")))?;
let doc = open_presentation(path, &bytes)?;
- let native = slidecraft_format::sniff(&bytes) || slidecraft_pptx::sniff(&bytes);
+ let native = deckcraft_format::sniff(&bytes) || deckcraft_pptx::sniff(&bytes);
let i = add_opened(s, path, native.then(|| path.to_string()), doc);
Ok(json!({"document": i, "slides": s.doc()?.doc.slides.len()}))
}
@@ -104,24 +104,24 @@ fn open_bytes(s: &mut Session, p: &Value) -> Result {
Ok(json!({"document": i}))
}
-/// Encode the presentation in `format` (`slidecraft`, `pptx`, `outline`).
+/// Encode the presentation in `format` (`deckcraft`, `pptx`, `outline`).
pub fn save_bytes(doc: &Presentation, format: &str) -> Result> {
match format {
- "pptx" | "potx" | "ppsx" => slidecraft_pptx::export(doc).map_err(|e| EngineError::Other(e.to_string())),
- "outline" | "txt" => Ok(slidecraft_format::slides_to_outline(doc).into_bytes()),
- "pdf" => slidecraft_pdf::export(doc, &Default::default()).map_err(|e| EngineError::Other(e.to_string())),
- _ => slidecraft_format::save(doc).map_err(|e| EngineError::Other(e.to_string())),
+ "pptx" | "potx" | "ppsx" => deckcraft_pptx::export(doc).map_err(|e| EngineError::Other(e.to_string())),
+ "outline" | "txt" => Ok(deckcraft_format::slides_to_outline(doc).into_bytes()),
+ "pdf" => deckcraft_pdf::export(doc, &Default::default()).map_err(|e| EngineError::Other(e.to_string())),
+ _ => deckcraft_format::save(doc).map_err(|e| EngineError::Other(e.to_string())),
}
}
/// PDF options from command params: `{layout: slides|notes|handouts, perPage, dpi, slides: [i],
/// includeHidden, textLayer, frame}`.
-pub fn pdf_options(p: &Value) -> slidecraft_pdf::PdfOptions {
- let mut o = slidecraft_pdf::PdfOptions { layout: slidecraft_pdf::PageLayout::Slides, ..Default::default() };
+pub fn pdf_options(p: &Value) -> deckcraft_pdf::PdfOptions {
+ let mut o = deckcraft_pdf::PdfOptions { layout: deckcraft_pdf::PageLayout::Slides, ..Default::default() };
o.layout = match str_param(p, "layout").unwrap_or("slides") {
- "notes" => slidecraft_pdf::PageLayout::Notes,
- "handouts" => slidecraft_pdf::PageLayout::Handouts { per_page: usize_param(p, "perPage").unwrap_or(6).min(9) as u8 },
- _ => slidecraft_pdf::PageLayout::Slides,
+ "notes" => deckcraft_pdf::PageLayout::Notes,
+ "handouts" => deckcraft_pdf::PageLayout::Handouts { per_page: usize_param(p, "perPage").unwrap_or(6).min(9) as u8 },
+ _ => deckcraft_pdf::PageLayout::Slides,
};
o.dpi = f64_or(p, "dpi", o.dpi).clamp(36.0, 600.0);
o.slides = p.get("slides").and_then(Value::as_array).map(|a| a.iter().filter_map(|v| v.as_u64().map(|x| x as usize)).collect());
@@ -133,7 +133,7 @@ pub fn pdf_options(p: &Value) -> slidecraft_pdf::PdfOptions {
/// PDF bytes for `doc` with [`pdf_options`] from `p`.
pub fn pdf_bytes(doc: &Presentation, p: &Value) -> Result> {
- slidecraft_pdf::export(doc, &pdf_options(p)).map_err(|e| EngineError::Other(e.to_string()))
+ deckcraft_pdf::export(doc, &pdf_options(p)).map_err(|e| EngineError::Other(e.to_string()))
}
pub fn format_for_path(path: &str) -> &'static str {
@@ -149,7 +149,7 @@ pub fn format_for_path(path: &str) -> &'static str {
} else if l.ends_with(".txt") || l.ends_with(".md") {
"outline"
} else {
- "slidecraft"
+ "deckcraft"
}
}
@@ -201,7 +201,7 @@ fn save_as(s: &mut Session, p: &Value) -> Result {
}
fn save_bytes_cmd(s: &mut Session, p: &Value) -> Result {
- let format = str_param(p, "format").unwrap_or("slidecraft");
+ let format = str_param(p, "format").unwrap_or("deckcraft");
let bytes = save_bytes(&s.doc()?.doc, format)?;
Ok(json!({"data": base64_encode(&bytes), "bytes": bytes.len()}))
}
@@ -209,7 +209,7 @@ fn save_bytes_cmd(s: &mut Session, p: &Value) -> Result {
/// Render slide `index` (current when absent) to PNG bytes.
pub fn render_png(doc: &Presentation, index: usize, scale: f64, edit: bool) -> (Vec, u32, u32) {
let img =
- slidecraft_render::render_slide(doc, index, &slidecraft_render::RenderOpts { scale: scale.clamp(0.01, 16.0), edit, ..Default::default() });
+ deckcraft_render::render_slide(doc, index, &deckcraft_render::RenderOpts { scale: scale.clamp(0.01, 16.0), edit, ..Default::default() });
(img.to_png(), img.width, img.height)
}
@@ -240,10 +240,10 @@ fn export(s: &mut Session, p: &Value) -> Result {
if *i >= st.doc.slides.len() {
return Err(bad("file.export", format!("no slide {i}")));
}
- let img = slidecraft_render::render_slide(
+ let img = deckcraft_render::render_slide(
&st.doc,
*i,
- &slidecraft_render::RenderOpts { scale: scale.clamp(0.01, 16.0), ..Default::default() },
+ &deckcraft_render::RenderOpts { scale: scale.clamp(0.01, 16.0), ..Default::default() },
);
let bytes = if format == "png" { img.to_png() } else { img.to_jpeg(92) };
let out = if slides.len() == 1 {
@@ -260,7 +260,7 @@ fn export(s: &mut Session, p: &Value) -> Result {
Ok(json!({"files": written}))
}
"pdf" => {
- let bytes = slidecraft_pdf::export(&st.doc, &pdf_options(p)).map_err(|e| EngineError::Other(e.to_string()))?;
+ let bytes = deckcraft_pdf::export(&st.doc, &pdf_options(p)).map_err(|e| EngineError::Other(e.to_string()))?;
write_file(&path, &bytes)?;
Ok(json!({"path": path, "bytes": bytes.len()}))
}
diff --git a/crates/engine/src/cmd/format.rs b/crates/engine/src/cmd/format.rs
index 9841dc5..fc97100 100644
--- a/crates/engine/src/cmd/format.rs
+++ b/crates/engine/src/cmd/format.rs
@@ -1,11 +1,11 @@
//! Home ▸ Font and Paragraph, Format menu: character, paragraph and text box formatting, and
//! the Format Painter.
+use deckcraft_model::edit as ed;
+use deckcraft_model::resolve;
+use deckcraft_model::text::{Align, Anchor, AutoFit, Bullet, Caps, ParaProps, RunProps, Spacing, Strike, TextDir};
+use deckcraft_model::{Effects, Fill, Line, Shape};
use serde_json::{Value, json};
-use slidecraft_model::edit as ed;
-use slidecraft_model::resolve;
-use slidecraft_model::text::{Align, Anchor, AutoFit, Bullet, Caps, ParaProps, RunProps, Spacing, Strike, TextDir};
-use slidecraft_model::{Effects, Fill, Line, Shape};
use super::text::{format_body, format_para, format_run};
use super::*;
@@ -289,8 +289,8 @@ fn clear(s: &mut Session, _p: &Value) -> Result {
}
fn text_shadow(s: &mut Session, p: &Value) -> Result {
let v = on_param(p, Some(current_run(s).shadow.is_some()));
- let sh = slidecraft_model::style::Shadow {
- color: slidecraft_model::ColorRef::rgb(slidecraft_color::Rgba::BLACK).with(slidecraft_color::ColorTransform::Alpha(43000)),
+ let sh = deckcraft_model::style::Shadow {
+ color: deckcraft_model::ColorRef::rgb(deckcraft_color::Rgba::BLACK).with(deckcraft_color::ColorTransform::Alpha(43000)),
blur: 3.0,
dist: 2.0,
dir: 45.0,
@@ -328,7 +328,7 @@ fn case(s: &mut Session, p: &Value) -> Result {
let mut pos = 0;
for r in &mut para.runs {
let len = r.char_len();
- if r.kind == slidecraft_model::text::RunKind::Text {
+ if r.kind == deckcraft_model::text::RunKind::Text {
let rs = from.max(pos).saturating_sub(pos);
let re = to.min(pos + len).saturating_sub(pos);
if re > rs {
@@ -349,7 +349,7 @@ fn case(s: &mut Session, p: &Value) -> Result {
if let Some(t) = sh.text.as_mut() {
for para in &mut t.paragraphs {
for r in &mut para.runs {
- if r.kind == slidecraft_model::text::RunKind::Text {
+ if r.kind == deckcraft_model::text::RunKind::Text {
r.text = ed::change_case(&r.text, &mode);
}
}
@@ -541,7 +541,7 @@ pub struct Painted {
pub fill: Option,
pub line: Option,
pub effects: Option,
- pub style: Option,
+ pub style: Option,
}
fn painter_pick(s: &mut Session, p: &Value) -> Result {
diff --git a/crates/engine/src/cmd/insert.rs b/crates/engine/src/cmd/insert.rs
index cd0fed7..f2be6b8 100644
--- a/crates/engine/src/cmd/insert.rs
+++ b/crates/engine/src/cmd/insert.rs
@@ -1,12 +1,12 @@
//! Insert tab: shapes, text boxes, pictures, tables, charts, audio, video, WordArt, fields.
+use deckcraft_color::SchemeSlot;
+use deckcraft_geom::Xfrm;
+use deckcraft_model::chart::ChartType;
+use deckcraft_model::style::PictureFill;
+use deckcraft_model::text::{AutoFit, RunKind, TextBody};
+use deckcraft_model::{Chart, ColorRef, Fill, Geom, MediaClip, Shape, ShapeId, ShapeKind, ShapeStyle, Table};
use serde_json::{Value, json};
-use slidecraft_color::SchemeSlot;
-use slidecraft_geom::Xfrm;
-use slidecraft_model::chart::ChartType;
-use slidecraft_model::style::PictureFill;
-use slidecraft_model::text::{AutoFit, RunKind, TextBody};
-use slidecraft_model::{Chart, ColorRef, Fill, Geom, MediaClip, Shape, ShapeId, ShapeKind, ShapeStyle, Table};
use super::*;
use crate::{EngineError, Result, Session};
@@ -92,7 +92,7 @@ pub fn specs() -> Vec {
/// Default size for a click-inserted shape (1 inch square, PowerPoint style).
fn default_rect(s: &Session, w: f64, h: f64) -> Xfrm {
- let size = s.active().map(|d| d.doc.slide_size).unwrap_or(slidecraft_model::defaults::WIDE);
+ let size = s.active().map(|d| d.doc.slide_size).unwrap_or(deckcraft_model::defaults::WIDE);
Xfrm::new((size.width - w) / 2.0, (size.height - h) / 2.0, w, h)
}
@@ -110,7 +110,7 @@ pub(crate) fn add_shape(s: &mut Session, mut shape: Shape, select: bool) -> Resu
_ => shape
.geom
.preset_name()
- .and_then(slidecraft_geom::preset::info)
+ .and_then(deckcraft_geom::preset::info)
.map(|i| i.label.split(':').next().unwrap_or(i.label).trim().to_string())
.unwrap_or_else(|| "Shape".into()),
};
@@ -130,8 +130,8 @@ pub(crate) fn add_shape(s: &mut Session, mut shape: Shape, select: bool) -> Resu
fn insert_shape(s: &mut Session, p: &Value) -> Result {
let preset = str_param(p, "preset").unwrap_or("rect");
- let info = slidecraft_geom::preset::info(preset).ok_or_else(|| bad("shape.insert", format!("unknown preset `{preset}`")))?;
- let line_like = slidecraft_geom::preset::is_line_like(preset);
+ let info = deckcraft_geom::preset::info(preset).ok_or_else(|| bad("shape.insert", format!("unknown preset `{preset}`")))?;
+ let line_like = deckcraft_geom::preset::is_line_like(preset);
let rect = rect_param(p, "rect").unwrap_or_else(|| default_rect(s, 72.0, if line_like { 0.0 } else { 72.0 }));
let adj: Vec = p.get("adj").and_then(Value::as_array).map(|a| a.iter().filter_map(Value::as_f64).collect()).unwrap_or_default();
let mut shape = Shape {
@@ -153,14 +153,14 @@ fn insert_shape(s: &mut Session, p: &Value) -> Result {
}
if let Some(tb) = shape.text.as_mut() {
for para in &mut tb.paragraphs {
- para.props.align = Some(slidecraft_model::text::Align::Center);
+ para.props.align = Some(deckcraft_model::text::Align::Center);
}
}
} else {
shape.kind = if preset.contains("Connector") { ShapeKind::Connector { start: None, end: None } } else { ShapeKind::Shape };
if preset == "straightConnector1" {
- shape.line = Some(slidecraft_model::Line {
- tail: Some(slidecraft_model::style::LineEnd { kind: "triangle".into(), w: "med".into(), len: "med".into() }),
+ shape.line = Some(deckcraft_model::Line {
+ tail: Some(deckcraft_model::style::LineEnd { kind: "triangle".into(), w: "med".into(), len: "med".into() }),
..Default::default()
});
}
@@ -174,7 +174,7 @@ fn insert_shape(s: &mut Session, p: &Value) -> Result {
fn presets(_s: &mut Session, _p: &Value) -> Result {
Ok(Value::Array(
- slidecraft_geom::preset::CATALOG.iter().map(|i| json!({"name": i.name, "label": i.label, "category": i.category.label()})).collect(),
+ deckcraft_geom::preset::CATALOG.iter().map(|i| json!({"name": i.name, "label": i.label, "category": i.category.label()})).collect(),
))
}
@@ -287,7 +287,7 @@ fn fitted(s: &Session, bytes: &[u8]) -> (f64, f64) {
.and_then(|r| r.into_dimensions().ok())
.map(|(w, h)| (w as f64 * 0.75, h as f64 * 0.75))
.unwrap_or((288.0, 216.0));
- let size = s.active().map(|d| d.doc.slide_size).unwrap_or(slidecraft_model::defaults::WIDE);
+ let size = s.active().map(|d| d.doc.slide_size).unwrap_or(deckcraft_model::defaults::WIDE);
let k = (size.width * 0.9 / w.max(1.0)).min(size.height * 0.9 / h.max(1.0)).min(1.0);
(w * k, h * k)
}
@@ -298,7 +298,7 @@ fn picture(s: &mut Session, p: &Value) -> Result {
if !ct.starts_with("image/") {
return Err(bad("insert.picture", format!("{name} is not a picture")));
}
- if ct != "image/svg+xml" && slidecraft_render::decode_image(&bytes).is_none() {
+ if ct != "image/svg+xml" && deckcraft_render::decode_image(&bytes).is_none() {
return Err(EngineError::Other(format!("{name}: the picture can't be read")));
}
let (w, h) = fitted(s, &bytes);
@@ -308,7 +308,7 @@ fn picture(s: &mut Session, p: &Value) -> Result {
.into_iter()
.find(|x| {
x.ph_type()
- .is_some_and(|k| matches!(k, slidecraft_model::PhType::Picture | slidecraft_model::PhType::Obj | slidecraft_model::PhType::Body))
+ .is_some_and(|k| matches!(k, deckcraft_model::PhType::Picture | deckcraft_model::PhType::Obj | deckcraft_model::PhType::Body))
&& x.text.as_ref().is_none_or(|t| t.is_empty())
})
.map(|x| (x.id, xfrm_of(&d.doc, &d.selection, x)))
@@ -349,7 +349,7 @@ fn picture(s: &mut Session, p: &Value) -> Result {
sh.kind = shape.kind.clone();
sh.xfrm = Some(rect);
sh.ph = phv.map(|mut p| {
- p.kind = slidecraft_model::PhType::Picture;
+ p.kind = deckcraft_model::PhType::Picture;
p
});
sh.text = None;
@@ -426,7 +426,7 @@ fn chart(s: &mut Session, p: &Value) -> Result {
if let Some(series) = p.get("series").and_then(Value::as_array) {
c.series = series
.iter()
- .map(|sv| slidecraft_model::chart::Series {
+ .map(|sv| deckcraft_model::chart::Series {
name: sv.get("name").and_then(Value::as_str).unwrap_or("Series").to_string(),
values: sv.get("values").and_then(Value::as_array).map(|a| a.iter().map(Value::as_f64).collect()).unwrap_or_default(),
..Default::default()
@@ -447,7 +447,7 @@ fn media(s: &mut Session, p: &Value, video: bool) -> Result {
let (name, bytes) = media_bytes(p, cmd)?;
let ct = content_type(&name, &bytes);
let bytes = std::sync::Arc::new(bytes);
- let info = slidecraft_media::probe(&bytes);
+ let info = deckcraft_media::probe(&bytes);
let has_video = info.as_ref().is_ok_and(|i| i.video.is_some());
let has_audio = info.as_ref().is_ok_and(|i| i.audio.is_some());
match &info {
@@ -521,7 +521,7 @@ fn word_art(s: &mut Session, p: &Value) -> Result {
let mut shape = new_text_box(Xfrm::new(size.width * 0.2, size.height * 0.4, size.width * 0.6, 80.0), text);
if let Some(t) = shape.text.as_mut() {
for para in &mut t.paragraphs {
- para.props.align = Some(slidecraft_model::text::Align::Center);
+ para.props.align = Some(deckcraft_model::text::Align::Center);
for r in &mut para.runs {
r.props.size = Some(54.0);
r.props.bold = Some(style % 2 == 1);
@@ -532,7 +532,7 @@ fn word_art(s: &mut Session, p: &Value) -> Result {
_ => SchemeSlot::Accent4,
})));
if style % 3 == 2 {
- r.props.outline = Some(slidecraft_model::Line::solid(ColorRef::scheme(SchemeSlot::Bg1), 1.0));
+ r.props.outline = Some(deckcraft_model::Line::solid(ColorRef::scheme(SchemeSlot::Bg1), 1.0));
}
}
}
@@ -568,7 +568,7 @@ fn symbol(s: &mut Session, p: &Value) -> Result {
}
fn hyperlink(s: &mut Session, p: &Value) -> Result {
- use slidecraft_model::text::{Action, Hyperlink};
+ use deckcraft_model::text::{Action, Hyperlink};
let action = if let Some(u) = str_param(p, "url") {
Action::Url { url: u.to_string() }
} else if let Some(i) = usize_param(p, "slide") {
@@ -589,7 +589,7 @@ fn hyperlink(s: &mut Session, p: &Value) -> Result {
}
fn action_button(s: &mut Session, p: &Value) -> Result {
- use slidecraft_model::text::{Action, Hyperlink};
+ use deckcraft_model::text::{Action, Hyperlink};
let kind = str_param(p, "kind").unwrap_or("custom");
let (preset, action) = match kind {
"back" | "previous" => ("actionButtonBackPrevious", Some(Action::PreviousSlide)),
@@ -644,9 +644,9 @@ fn chart_data(s: &mut Session, p: &Value) -> Result {
.get("categories")
.and_then(Value::as_array)
.map(|a| a.iter().map(|v| v.as_str().map(String::from).unwrap_or_else(|| v.to_string())).collect());
- let series: Option> = p.get("series").and_then(Value::as_array).map(|a| {
+ let series: Option> = p.get("series").and_then(Value::as_array).map(|a| {
a.iter()
- .map(|sv| slidecraft_model::chart::Series {
+ .map(|sv| deckcraft_model::chart::Series {
name: sv.get("name").and_then(Value::as_str).unwrap_or("Series").to_string(),
values: sv.get("values").and_then(Value::as_array).map(|v| v.iter().map(Value::as_f64).collect()).unwrap_or_default(),
..Default::default()
@@ -773,7 +773,7 @@ fn smart_art(s: &mut Session, p: &Value) -> Result {
let mut body = TextBody::from_text(items.get(i).map(String::as_str).unwrap_or(""));
body.body.autofit = Some(AutoFit::Shrink { font_scale: 1.0, line_reduction: 0.0 });
for para in &mut body.paragraphs {
- para.props.align = Some(slidecraft_model::text::Align::Center);
+ para.props.align = Some(deckcraft_model::text::Align::Center);
for r in &mut para.runs {
r.props.size = Some(20.0);
}
diff --git a/crates/engine/src/cmd/inspect.rs b/crates/engine/src/cmd/inspect.rs
index 6b9bd18..db162a3 100644
--- a/crates/engine/src/cmd/inspect.rs
+++ b/crates/engine/src/cmd/inspect.rs
@@ -1,8 +1,8 @@
//! Inspection for agents: the deck, slides and shapes as JSON (verify work without screenshots).
+use deckcraft_model::resolve::{self, Ctx};
+use deckcraft_model::{Shape, ShapeKind};
use serde_json::{Value, json};
-use slidecraft_model::resolve::{self, Ctx};
-use slidecraft_model::{Shape, ShapeKind};
use super::*;
use crate::{Result, Session};
@@ -51,8 +51,8 @@ pub fn shape_summary(ctx: Option<&Ctx>, sh: &Shape, depth: usize) -> Value {
let fill = ctx.map(|c| {
let (f, ph) = resolve::fill(c, sh);
match f {
- Some(slidecraft_model::Fill::Solid { color }) => json!(format!("#{}", c.color(&color, ph).hex())),
- Some(slidecraft_model::Fill::None) | None => json!("none"),
+ Some(deckcraft_model::Fill::Solid { color }) => json!(format!("#{}", c.color(&color, ph).hex())),
+ Some(deckcraft_model::Fill::None) | None => json!("none"),
Some(other) => json!(serde_json::to_value(&other).ok().and_then(|v| v.get("kind").cloned()).unwrap_or_default()),
}
});
@@ -116,7 +116,7 @@ fn slide(s: &mut Session, p: &Value) -> Result {
let ctx = Ctx::for_slide(&st.doc, sl);
let shapes: Vec = sl.shapes.iter().map(|sh| shape_summary(ctx.as_ref(), sh, 0)).collect();
let bg = ctx.as_ref().map(|c| match resolve::background(c, Some(sl)) {
- (slidecraft_model::Fill::Solid { color }, ph) => json!(format!("#{}", c.color(&color, ph).hex())),
+ (deckcraft_model::Fill::Solid { color }, ph) => json!(format!("#{}", c.color(&color, ph).hex())),
(f, _) => serde_json::to_value(&f).ok().and_then(|v| v.get("kind").cloned()).unwrap_or_default(),
});
Ok(json!({
diff --git a/crates/engine/src/cmd/media.rs b/crates/engine/src/cmd/media.rs
index 1e51fe8..5402ad8 100644
--- a/crates/engine/src/cmd/media.rs
+++ b/crates/engine/src/cmd/media.rs
@@ -2,8 +2,8 @@
//! requests the UI host carries out with its player ([`crate::UiRequest::Media`]); the host reports
//! back through [`Session::media_status`], which `media.info` returns with the probe.
+use deckcraft_model::{MediaClip, ShapeId, ShapeKind};
use serde_json::{Value, json};
-use slidecraft_model::{MediaClip, ShapeId, ShapeKind};
use super::*;
use crate::{EngineError, MediaStatus, Result, Session, UiRequest};
@@ -83,7 +83,7 @@ fn seek(s: &mut Session, p: &Value) -> Result {
request(s, p, "media.seek", "seek", Some(ms))
}
-pub(crate) fn probe_json(info: &slidecraft_media::MediaInfo) -> Value {
+pub(crate) fn probe_json(info: &deckcraft_media::MediaInfo) -> Value {
json!({
"container": info.container,
"durationMs": info.duration_ms,
@@ -97,7 +97,7 @@ fn info(s: &mut Session, p: &Value) -> Result {
let (id, m) = target(s, p, "media.info")?;
let st = s.doc()?;
let item = st.doc.media(m.media).ok_or_else(|| EngineError::Other("the media data is missing".into()))?;
- let probe = slidecraft_media::probe(&item.data).map(|i| probe_json(&i)).unwrap_or_else(|e| json!({"error": e.to_string()}));
+ let probe = deckcraft_media::probe(&item.data).map(|i| probe_json(&i)).unwrap_or_else(|e| json!({"error": e.to_string()}));
Ok(json!({
"id": id,
"name": item.name,
@@ -119,8 +119,8 @@ fn info(s: &mut Session, p: &Value) -> Result {
}
/// A poster frame PNG for video bytes at `ms`.
-pub(crate) fn poster_png(bytes: &slidecraft_media::Bytes, ms: u64) -> Option> {
- match slidecraft_media::poster_png(bytes, ms as f64 / 1000.0) {
+pub(crate) fn poster_png(bytes: &deckcraft_media::Bytes, ms: u64) -> Option> {
+ match deckcraft_media::poster_png(bytes, ms as f64 / 1000.0) {
Ok(png) => Some(png),
Err(e) => {
log::info!("no poster frame: {e}");
diff --git a/crates/engine/src/cmd/merge.rs b/crates/engine/src/cmd/merge.rs
index 207e3fe..4c9a6d2 100644
--- a/crates/engine/src/cmd/merge.rs
+++ b/crates/engine/src/cmd/merge.rs
@@ -2,12 +2,12 @@
//! the outlines of the selected shapes. The result takes the formatting of the first selected
//! shape, like PowerPoint.
+use deckcraft_geom::{PathEl, Rect, Xfrm};
+use deckcraft_model::{CustomPath, Geom, Shape, ShapeId, ShapeKind};
use i_overlay::core::fill_rule::FillRule;
use i_overlay::core::overlay_rule::OverlayRule;
use i_overlay::float::single::SingleFloatOverlay;
use serde_json::{Value, json};
-use slidecraft_geom::{PathEl, Rect, Xfrm};
-use slidecraft_model::{CustomPath, Geom, Shape, ShapeId, ShapeKind};
use super::*;
use crate::{Result, Session};
@@ -31,12 +31,12 @@ pub fn specs() -> Vec {
}
/// The filled outline of `s` in slide points (curves flattened).
-fn area_of(doc: &slidecraft_model::Presentation, sel: &crate::Selection, s: &Shape) -> Area {
+fn area_of(doc: &deckcraft_model::Presentation, sel: &crate::Selection, s: &Shape) -> Area {
let x = xfrm_of(doc, sel, s);
- let geo = slidecraft_render::shape_geometry(s, x.w, x.h);
+ let geo = deckcraft_render::shape_geometry(s, x.w, x.h);
let a = x.affine();
let mut out: Area = vec![];
- for sp in geo.paths.iter().filter(|p| p.fill != slidecraft_geom::preset::FillMode::None) {
+ for sp in geo.paths.iter().filter(|p| p.fill != deckcraft_geom::preset::FillMode::None) {
let mut cur: Vec<[f64; 2]> = vec![];
let flush = |cur: &mut Vec<[f64; 2]>, out: &mut Area| {
if cur.len() > 2 {
@@ -45,7 +45,7 @@ fn area_of(doc: &slidecraft_model::Presentation, sel: &crate::Selection, s: &Sha
cur.clear();
}
};
- slidecraft_geom::preset_flatten(&sp.path, &mut |el| match el {
+ deckcraft_geom::preset_flatten(&sp.path, &mut |el| match el {
PathEl::MoveTo(p) => {
flush(&mut cur, &mut out);
let q = a * p;
diff --git a/crates/engine/src/cmd/mod.rs b/crates/engine/src/cmd/mod.rs
index e42ae21..a337107 100644
--- a/crates/engine/src/cmd/mod.rs
+++ b/crates/engine/src/cmd/mod.rs
@@ -17,10 +17,10 @@ pub mod table;
pub mod text;
pub mod transition;
+use deckcraft_geom::Xfrm;
+use deckcraft_model::ShapeId;
use serde::Serialize;
use serde_json::Value;
-use slidecraft_geom::Xfrm;
-use slidecraft_model::ShapeId;
use crate::{EngineError, Result, Session};
@@ -207,31 +207,31 @@ pub(crate) fn ok() -> Result {
}
/// Parse a colour param: `"#RRGGBB"`, `"accent1"`…, or `{"scheme":"accent2","lumMod":75000}`.
-pub(crate) fn color_param(p: &Value, key: &str) -> Option {
+pub(crate) fn color_param(p: &Value, key: &str) -> Option {
let v = p.get(key)?;
color_value(v)
}
-pub(crate) fn color_value(v: &Value) -> Option {
- use slidecraft_model::ColorRef;
+pub(crate) fn color_value(v: &Value) -> Option {
+ use deckcraft_model::ColorRef;
match v {
Value::String(s) => {
- if let Some(slot) = slidecraft_color::SchemeSlot::from_xml(s) {
+ if let Some(slot) = deckcraft_color::SchemeSlot::from_xml(s) {
return Some(ColorRef::scheme(slot));
}
- if let Some(c) = slidecraft_color::Rgba::from_hex(s) {
+ if let Some(c) = deckcraft_color::Rgba::from_hex(s) {
return Some(ColorRef::rgb(c));
}
- slidecraft_color::preset(s).map(ColorRef::rgb)
+ deckcraft_color::preset(s).map(ColorRef::rgb)
}
Value::Object(o) => {
let mut c = if let Some(s) = o.get("scheme").and_then(Value::as_str) {
- ColorRef::scheme(slidecraft_color::SchemeSlot::from_xml(s)?)
+ ColorRef::scheme(deckcraft_color::SchemeSlot::from_xml(s)?)
} else {
- ColorRef::rgb(slidecraft_color::Rgba::from_hex(o.get("rgb").and_then(Value::as_str)?)?)
+ ColorRef::rgb(deckcraft_color::Rgba::from_hex(o.get("rgb").and_then(Value::as_str)?)?)
};
for (k, val) in o {
- if let Some(t) = slidecraft_color::ColorTransform::from_xml(k, val.as_i64().and_then(|x| i32::try_from(x).ok())) {
+ if let Some(t) = deckcraft_color::ColorTransform::from_xml(k, val.as_i64().and_then(|x| i32::try_from(x).ok())) {
c.mods.push(t);
}
}
@@ -242,20 +242,20 @@ pub(crate) fn color_value(v: &Value) -> Option {
}
/// Look a shape up by id in the edited tree.
-pub(crate) fn shape_of(s: &Session, id: ShapeId) -> Result {
+pub(crate) fn shape_of(s: &Session, id: ShapeId) -> Result {
s.doc()?.shape(id).cloned().ok_or_else(|| EngineError::Other(format!("no shape {id}")))
}
/// Effective box of a shape (placeholders inherit theirs).
-pub fn xfrm_of(doc: &slidecraft_model::Presentation, sel: &crate::Selection, shape: &slidecraft_model::Shape) -> Xfrm {
+pub fn xfrm_of(doc: &deckcraft_model::Presentation, sel: &crate::Selection, shape: &deckcraft_model::Shape) -> Xfrm {
if let Some(x) = shape.xfrm {
return x;
}
ctx_xfrm(doc, sel, shape)
}
-fn ctx_xfrm(doc: &slidecraft_model::Presentation, sel: &crate::Selection, shape: &slidecraft_model::Shape) -> Xfrm {
- use slidecraft_model::resolve::{self, Ctx};
+fn ctx_xfrm(doc: &deckcraft_model::Presentation, sel: &crate::Selection, shape: &deckcraft_model::Shape) -> Xfrm {
+ use deckcraft_model::resolve::{self, Ctx};
match sel.target {
crate::Target::Slides => doc.slides.get(sel.slide).and_then(|s| Ctx::for_slide(doc, s)).map(|c| resolve::xfrm(&c, shape)),
crate::Target::Master { master } => doc.masters.get(master).map(|m| resolve::xfrm(&Ctx::for_master(doc, m), shape)),
@@ -267,21 +267,21 @@ fn ctx_xfrm(doc: &slidecraft_model::Presentation, sel: &crate::Selection, shape:
}
/// Run `f` on each target shape (copy-on-write), materialising inherited boxes first.
-pub(crate) fn edit_shapes(s: &mut Session, p: &Value, cmd: &str, f: impl Fn(&mut slidecraft_model::Shape) -> Result<()>) -> Result {
+pub(crate) fn edit_shapes(s: &mut Session, p: &Value, cmd: &str, f: impl Fn(&mut deckcraft_model::Shape) -> Result<()>) -> Result {
let ids = targets(s, p)?;
if ids.is_empty() {
return Err(bad(cmd, "no shapes selected"));
}
s.edit(|doc, sel| {
let boxes: Vec<(ShapeId, Xfrm)> = {
- let st_shapes: Vec =
- ids.iter().filter_map(|id| current_shapes(doc, sel).and_then(|v| slidecraft_model::find_shape(v, *id)).cloned()).collect();
+ let st_shapes: Vec =
+ ids.iter().filter_map(|id| current_shapes(doc, sel).and_then(|v| deckcraft_model::find_shape(v, *id)).cloned()).collect();
st_shapes.iter().map(|sh| (sh.id, xfrm_of(doc, sel, sh))).collect()
};
let shapes = crate::shapes_mut(doc, sel).ok_or_else(|| bad(cmd, "no slide"))?;
let mut n = 0;
for id in &ids {
- if let Some(sh) = slidecraft_model::find_shape_mut(shapes, *id) {
+ if let Some(sh) = deckcraft_model::find_shape_mut(shapes, *id) {
if sh.xfrm.is_none() {
sh.xfrm = boxes.iter().find(|(i, _)| i == id).map(|(_, x)| *x);
}
@@ -296,7 +296,7 @@ pub(crate) fn edit_shapes(s: &mut Session, p: &Value, cmd: &str, f: impl Fn(&mut
})
}
-pub(crate) fn current_shapes<'a>(doc: &'a slidecraft_model::Presentation, sel: &crate::Selection) -> Option<&'a Vec> {
+pub(crate) fn current_shapes<'a>(doc: &'a deckcraft_model::Presentation, sel: &crate::Selection) -> Option<&'a Vec> {
match sel.target {
crate::Target::Slides => doc.slides.get(sel.slide).map(|s| &s.shapes),
crate::Target::Master { master } => doc.masters.get(master).map(|m| &m.shapes),
diff --git a/crates/engine/src/cmd/review.rs b/crates/engine/src/cmd/review.rs
index 20356a9..8e3a532 100644
--- a/crates/engine/src/cmd/review.rs
+++ b/crates/engine/src/cmd/review.rs
@@ -2,8 +2,8 @@
use std::sync::Arc;
+use deckcraft_model::Comment;
use serde_json::{Value, json};
-use slidecraft_model::Comment;
use super::*;
use crate::{Result, Session, UiRequest};
@@ -130,34 +130,34 @@ fn accessibility(s: &mut Session, _p: &Value) -> Result {
if sl.title().trim().is_empty() {
out.push(json!({"slide": i, "issue": "Missing slide title"}));
}
- slidecraft_model::walk(&sl.shapes, &mut |sh, _| {
+ deckcraft_model::walk(&sl.shapes, &mut |sh, _| {
let needs_alt = matches!(
sh.kind,
- slidecraft_model::ShapeKind::Picture { .. }
- | slidecraft_model::ShapeKind::Chart(_)
- | slidecraft_model::ShapeKind::Media(_)
- | slidecraft_model::ShapeKind::Group { .. }
+ deckcraft_model::ShapeKind::Picture { .. }
+ | deckcraft_model::ShapeKind::Chart(_)
+ | deckcraft_model::ShapeKind::Media(_)
+ | deckcraft_model::ShapeKind::Group { .. }
);
if needs_alt && sh.descr.trim().is_empty() && !sh.decorative {
out.push(json!({"slide": i, "shape": sh.id, "name": sh.name, "issue": "Missing alternative text"}));
}
});
// Low-contrast text against the slide background.
- if let Some(ctx) = slidecraft_model::resolve::Ctx::for_slide(&st.doc, sl) {
- let bg = match slidecraft_model::resolve::background(&ctx, Some(sl)) {
- (slidecraft_model::Fill::Solid { color }, ph) => Some(ctx.color(&color, ph)),
+ if let Some(ctx) = deckcraft_model::resolve::Ctx::for_slide(&st.doc, sl) {
+ let bg = match deckcraft_model::resolve::background(&ctx, Some(sl)) {
+ (deckcraft_model::Fill::Solid { color }, ph) => Some(ctx.color(&color, ph)),
_ => None,
};
if let Some(bg) = bg {
for sh in &sl.shapes {
let Some(t) = &sh.text else { continue };
let Some(para) = t.paragraphs.iter().find(|p| !p.is_empty()) else { continue };
- let rp = slidecraft_model::resolve::run(&ctx, sh, para, ¶.runs.first().map(|r| r.props.clone()).unwrap_or_default());
- let (fill, _) = slidecraft_model::resolve::fill(&ctx, sh);
+ let rp = deckcraft_model::resolve::run(&ctx, sh, para, ¶.runs.first().map(|r| r.props.clone()).unwrap_or_default());
+ let (fill, _) = deckcraft_model::resolve::fill(&ctx, sh);
if fill.as_ref().is_some_and(|f| !f.is_none()) {
continue;
}
- let fg = slidecraft_model::resolve::text_color(&ctx, &rp);
+ let fg = deckcraft_model::resolve::text_color(&ctx, &rp);
if fg.contrast(bg) < 3.0 {
out.push(json!({"slide": i, "shape": sh.id, "name": sh.name, "issue": format!("Hard-to-read text contrast ({:.1}:1)", fg.contrast(bg))}));
}
@@ -201,7 +201,7 @@ fn spelling(s: &mut Session, _p: &Value) -> Result {
let st = s.doc()?;
let mut out = vec![];
for (i, sl) in st.doc.slides.iter().enumerate() {
- slidecraft_model::walk(&sl.shapes, &mut |sh, _| {
+ deckcraft_model::walk(&sl.shapes, &mut |sh, _| {
if let Some(t) = &sh.text {
for w in t.text().split(|c: char| !c.is_alphanumeric() && c != '\'') {
if COMMON.contains(&w.to_lowercase().as_str()) {
@@ -258,7 +258,7 @@ fn custom_show(s: &mut Session, p: &Value) -> Result {
doc.custom_shows.retain(|c| c.name != name);
if !delete {
let slides = idx.iter().filter_map(|i| doc.slides.get(*i).map(|x| x.id)).collect();
- doc.custom_shows.push(slidecraft_model::CustomShow { name: name.clone(), slides });
+ doc.custom_shows.push(deckcraft_model::CustomShow { name: name.clone(), slides });
}
Ok(())
})?;
diff --git a/crates/engine/src/cmd/shape.rs b/crates/engine/src/cmd/shape.rs
index 10ac46f..7e12f60 100644
--- a/crates/engine/src/cmd/shape.rs
+++ b/crates/engine/src/cmd/shape.rs
@@ -1,10 +1,10 @@
//! Shape Format: position and size, rotation, flips, geometry, fill, outline, effects, styles,
//! names and alt text.
+use deckcraft_color::{ColorTransform, Rgba, SchemeSlot};
+use deckcraft_model::style::{Dash, Glow, Gradient, GradientShape, GradientStop, LineEnd, PatternFill, PictureFill, Reflection, Shadow};
+use deckcraft_model::{ColorRef, Effects, Fill, Geom, Line, ShapeStyle};
use serde_json::{Value, json};
-use slidecraft_color::{ColorTransform, Rgba, SchemeSlot};
-use slidecraft_model::style::{Dash, Glow, Gradient, GradientShape, GradientStop, LineEnd, PatternFill, PictureFill, Reflection, Shadow};
-use slidecraft_model::{ColorRef, Effects, Fill, Geom, Line, ShapeStyle};
use super::*;
use crate::{Result, Session};
@@ -195,7 +195,7 @@ fn adjust(s: &mut Session, p: &Value) -> Result {
let v = f64_param(p, "value").ok_or_else(|| bad("shape.adjust", "missing `value`"))?;
edit_shapes(s, p, "shape.adjust", |sh| {
if let Geom::Preset { name, adj } = &mut sh.geom {
- let defaults = slidecraft_geom::preset::info(name).map(|x| x.defaults).unwrap_or(&[]);
+ let defaults = deckcraft_geom::preset::info(name).map(|x| x.defaults).unwrap_or(&[]);
while adj.len() <= i {
let k = adj.len();
adj.push(defaults.get(k).copied().unwrap_or(0.0));
@@ -210,7 +210,7 @@ fn adjust(s: &mut Session, p: &Value) -> Result {
fn change(s: &mut Session, p: &Value) -> Result {
let name = str_param(p, "preset").ok_or_else(|| bad("shape.change", "missing `preset`"))?;
- let info = slidecraft_geom::preset::info(name).ok_or_else(|| bad("shape.change", format!("unknown preset `{name}`")))?;
+ let info = deckcraft_geom::preset::info(name).ok_or_else(|| bad("shape.change", format!("unknown preset `{name}`")))?;
edit_shapes(s, p, "shape.change", |sh| {
sh.geom = Geom::preset(info.name);
Ok(())
@@ -287,7 +287,7 @@ fn fill(s: &mut Session, p: &Value) -> Result {
}
// Transparency on an inherited (style) fill needs the effective colour.
let st = s.doc()?;
- let ctx_fill: Vec<(slidecraft_model::ShapeId, Option)> = {
+ let ctx_fill: Vec<(deckcraft_model::ShapeId, Option)> = {
let ctx = super::text::ctx_for(&st.doc, &st.selection);
targets(s, p)?
.into_iter()
@@ -296,7 +296,7 @@ fn fill(s: &mut Session, p: &Value) -> Result {
(
id,
ctx.as_ref().and_then(|c| {
- let (f, ph) = slidecraft_model::resolve::fill(c, sh);
+ let (f, ph) = deckcraft_model::resolve::fill(c, sh);
f.map(|f| match f {
Fill::Solid { color } => Fill::solid(ColorRef::rgb(c.color(&color, ph))),
other => other,
@@ -338,14 +338,14 @@ fn line(s: &mut Session, p: &Value) -> Result {
let head = str_param(p, "head").map(line_end);
let tail = str_param(p, "tail").map(line_end);
let cap = str_param(p, "cap").map(|c| match c {
- "round" => slidecraft_model::style::LineCap::Round,
- "square" => slidecraft_model::style::LineCap::Square,
- _ => slidecraft_model::style::LineCap::Flat,
+ "round" => deckcraft_model::style::LineCap::Round,
+ "square" => deckcraft_model::style::LineCap::Square,
+ _ => deckcraft_model::style::LineCap::Flat,
});
let join = str_param(p, "join").map(|c| match c {
- "bevel" => slidecraft_model::style::LineJoin::Bevel,
- "miter" => slidecraft_model::style::LineJoin::Miter,
- _ => slidecraft_model::style::LineJoin::Round,
+ "bevel" => deckcraft_model::style::LineJoin::Bevel,
+ "miter" => deckcraft_model::style::LineJoin::Miter,
+ _ => deckcraft_model::style::LineJoin::Round,
});
edit_shapes(s, p, "shape.line", |sh| {
if reset {
@@ -536,7 +536,7 @@ fn quick_style(s: &mut Session, p: &Value) -> Result {
sh.line.clone_from(&l);
sh.effects = None;
if let Some(t) = sh.text.as_mut() {
- slidecraft_model::edit::format_all(t, &|r| r.fill = None);
+ deckcraft_model::edit::format_all(t, &|r| r.fill = None);
}
Ok(())
})
@@ -596,7 +596,7 @@ fn set_default(s: &mut Session, _p: &Value) -> Result {
fn crop(s: &mut Session, p: &Value) -> Result {
let c = [f64_param(p, "left"), f64_param(p, "top"), f64_param(p, "right"), f64_param(p, "bottom")];
edit_shapes(s, p, "picture.crop", |sh| {
- if let slidecraft_model::ShapeKind::Picture { fill } = &mut sh.kind {
+ if let deckcraft_model::ShapeKind::Picture { fill } = &mut sh.kind {
for (i, v) in c.iter().enumerate() {
if let (Some(v), Some(slot)) = (v, fill.crop.get_mut(i)) {
*slot = v.clamp(-10.0, 0.95);
@@ -609,7 +609,7 @@ fn crop(s: &mut Session, p: &Value) -> Result {
fn picture_adjust(s: &mut Session, p: &Value) -> Result {
edit_shapes(s, p, "picture.adjust", |sh| {
- if let slidecraft_model::ShapeKind::Picture { fill } = &mut sh.kind {
+ if let deckcraft_model::ShapeKind::Picture { fill } = &mut sh.kind {
let a = &mut fill.adjust;
if bool_or(p, "reset", false) {
*a = Default::default();
@@ -636,7 +636,7 @@ fn picture_adjust(s: &mut Session, p: &Value) -> Result {
fn picture_reset(s: &mut Session, p: &Value) -> Result {
edit_shapes(s, p, "picture.reset", |sh| {
- if let slidecraft_model::ShapeKind::Picture { fill } = &mut sh.kind {
+ if let deckcraft_model::ShapeKind::Picture { fill } = &mut sh.kind {
fill.adjust = Default::default();
fill.crop = [0.0; 4];
fill.alpha = None;
@@ -652,7 +652,7 @@ fn picture_change(s: &mut Session, p: &Value) -> Result {
let ct = super::insert::content_type(&name, &bytes);
let media = s.edit(|doc, _| Ok(doc.add_media(&name, ct, bytes)))?;
edit_shapes(s, p, "picture.change", |sh| {
- if let slidecraft_model::ShapeKind::Picture { fill } = &mut sh.kind {
+ if let deckcraft_model::ShapeKind::Picture { fill } = &mut sh.kind {
fill.media = media;
fill.crop = [0.0; 4];
}
@@ -662,7 +662,7 @@ fn picture_change(s: &mut Session, p: &Value) -> Result {
fn media_options(s: &mut Session, p: &Value) -> Result {
edit_shapes(s, p, "media.options", |sh| {
- if let slidecraft_model::ShapeKind::Media(m) = &mut sh.kind {
+ if let deckcraft_model::ShapeKind::Media(m) = &mut sh.kind {
if let Some(v) = bool_param(p, "autoplay") {
m.autoplay = v;
}
@@ -703,8 +703,8 @@ fn media_options(s: &mut Session, p: &Value) -> Result {
Ok(json!({}))
}
-fn shape_id(p: &Value, key: &str) -> Option {
- p.get(key).and_then(Value::as_u64).map(|v| slidecraft_model::ShapeId(v as u32))
+fn shape_id(p: &Value, key: &str) -> Option {
+ p.get(key).and_then(Value::as_u64).map(|v| deckcraft_model::ShapeId(v as u32))
}
fn sites(s: &mut Session, p: &Value) -> Result {
@@ -722,7 +722,7 @@ fn connect(s: &mut Session, p: &Value) -> Result {
if a.is_empty() || b.is_empty() {
return Err(bad("shape.connect", "both shapes need connection sites (lines and groups have none)"));
}
- let pick = |v: &[slidecraft_geom::Point], key: &str| usize_param(p, key).filter(|i| *i < v.len());
+ let pick = |v: &[deckcraft_geom::Point], key: &str| usize_param(p, key).filter(|i| *i < v.len());
let (ia, ib) = match (pick(&a, "fromSite"), pick(&b, "toSite")) {
(Some(i), Some(j)) => (i, j),
(fi, fj) => {
@@ -744,24 +744,24 @@ fn connect(s: &mut Session, p: &Value) -> Result {
None => {
let preset = str_param(p, "preset").unwrap_or("straightConnector1");
let r = s.execute("shape.insert", &json!({"preset": preset, "rect": [p0.x, p0.y, 1.0, 1.0]}))?;
- slidecraft_model::ShapeId(r.get("id").and_then(Value::as_u64).ok_or_else(|| bad("shape.connect", "insert failed"))? as u32)
+ deckcraft_model::ShapeId(r.get("id").and_then(Value::as_u64).ok_or_else(|| bad("shape.connect", "insert failed"))? as u32)
}
};
s.edit(|doc, sel| {
let list = crate::shapes_mut(doc, sel).ok_or_else(|| bad("shape.connect", "no slide"))?;
- let sh = slidecraft_model::find_shape_mut(list, id).ok_or_else(|| bad("shape.connect", "no such line"))?;
+ let sh = deckcraft_model::find_shape_mut(list, id).ok_or_else(|| bad("shape.connect", "no such line"))?;
if !sh.is_line() {
return Err(bad("shape.connect", "`id` is not a line or connector"));
}
sh.xfrm = Some(crate::tools::line_xfrm(p0, p1, 0.0));
- sh.kind = slidecraft_model::ShapeKind::Connector { start: Some((from, ia as u32)), end: Some((to, ib as u32)) };
+ sh.kind = deckcraft_model::ShapeKind::Connector { start: Some((from, ia as u32)), end: Some((to, ib as u32)) };
Ok(())
})?;
Ok(json!({"id": id.0, "fromSite": ia, "toSite": ib}))
}
/// Path data through `pts` (shape-local): straight segments, or a Catmull-Rom curve as cubics.
-pub fn freeform_path(pts: &[slidecraft_geom::Point], closed: bool, smooth: bool) -> String {
+pub fn freeform_path(pts: &[deckcraft_geom::Point], closed: bool, smooth: bool) -> String {
let f = |v: f64| {
let s = format!("{:.2}", v);
s.trim_end_matches('0').trim_end_matches('.').to_string()
@@ -769,7 +769,7 @@ pub fn freeform_path(pts: &[slidecraft_geom::Point], closed: bool, smooth: bool)
let mut d = format!("M {} {}", f(pts[0].x), f(pts[0].y));
let n = pts.len();
if smooth && n > 2 {
- let at = |i: isize| -> slidecraft_geom::Point {
+ let at = |i: isize| -> deckcraft_geom::Point {
if closed { pts[i.rem_euclid(n as isize) as usize] } else { pts[i.clamp(0, n as isize - 1) as usize] }
};
let segs = if closed { n } else { n - 1 };
@@ -791,12 +791,12 @@ pub fn freeform_path(pts: &[slidecraft_geom::Point], closed: bool, smooth: bool)
}
fn freeform(s: &mut Session, p: &Value) -> Result {
- let pts: Vec = p
+ let pts: Vec = p
.get("points")
.and_then(Value::as_array)
.map(|a| {
a.iter()
- .filter_map(|v| Some(slidecraft_geom::Point::new(v.get(0)?.as_f64()?, v.get(1)?.as_f64()?)))
+ .filter_map(|v| Some(deckcraft_geom::Point::new(v.get(0)?.as_f64()?, v.get(1)?.as_f64()?)))
.filter(|q| q.x.is_finite() && q.y.is_finite())
.take(20_000)
.collect()
@@ -807,30 +807,30 @@ fn freeform(s: &mut Session, p: &Value) -> Result {
}
let closed = bool_or(p, "closed", false) && pts.len() > 2;
let smooth = bool_or(p, "smooth", false);
- let mut bb = slidecraft_geom::Rect::from_points(pts[0], pts[0]);
+ let mut bb = deckcraft_geom::Rect::from_points(pts[0], pts[0]);
for q in &pts {
bb = bb.union_pt(*q);
}
let (w, h) = (bb.width().max(1.0), bb.height().max(1.0));
- let local: Vec = pts.iter().map(|q| slidecraft_geom::Point::new(q.x - bb.x0, q.y - bb.y0)).collect();
+ let local: Vec = pts.iter().map(|q| deckcraft_geom::Point::new(q.x - bb.x0, q.y - bb.y0)).collect();
let d = freeform_path(&local, closed, smooth);
// Closed shapes take the default shape style, open paths the line style.
let r = s.execute("shape.insert", &json!({"preset": if closed { "rect" } else { "line" }, "rect": [bb.x0, bb.y0, w, h]}))?;
- let id = slidecraft_model::ShapeId(r.get("id").and_then(Value::as_u64).ok_or_else(|| bad("shape.freeform", "insert failed"))? as u32);
+ let id = deckcraft_model::ShapeId(r.get("id").and_then(Value::as_u64).ok_or_else(|| bad("shape.freeform", "insert failed"))? as u32);
s.edit(|doc, sel| {
let list = crate::shapes_mut(doc, sel).ok_or_else(|| bad("shape.freeform", "no slide"))?;
- let sh = slidecraft_model::find_shape_mut(list, id).ok_or_else(|| bad("shape.freeform", "insert failed"))?;
+ let sh = deckcraft_model::find_shape_mut(list, id).ok_or_else(|| bad("shape.freeform", "insert failed"))?;
sh.geom = Geom::Custom {
- paths: vec![slidecraft_model::CustomPath {
+ paths: vec![deckcraft_model::CustomPath {
w,
h,
d: d.clone(),
- fill: if closed { slidecraft_geom::preset::FillMode::Norm } else { slidecraft_geom::preset::FillMode::None },
+ fill: if closed { deckcraft_geom::preset::FillMode::Norm } else { deckcraft_geom::preset::FillMode::None },
stroke: true,
}],
};
- sh.xfrm = Some(slidecraft_geom::Xfrm::new(bb.x0, bb.y0, w, h));
- sh.kind = slidecraft_model::ShapeKind::Shape;
+ sh.xfrm = Some(deckcraft_geom::Xfrm::new(bb.x0, bb.y0, w, h));
+ sh.kind = deckcraft_model::ShapeKind::Shape;
sh.name = format!("Freeform {}", id.0);
Ok(())
})?;
diff --git a/crates/engine/src/cmd/slide.rs b/crates/engine/src/cmd/slide.rs
index a706262..3d8ad34 100644
--- a/crates/engine/src/cmd/slide.rs
+++ b/crates/engine/src/cmd/slide.rs
@@ -2,9 +2,9 @@
use std::sync::Arc;
+use deckcraft_model::text::TextBody;
+use deckcraft_model::{LayoutId, LayoutType, Presentation, Section, SlideId, defaults};
use serde_json::{Value, json};
-use slidecraft_model::text::TextBody;
-use slidecraft_model::{LayoutId, LayoutType, Presentation, Section, SlideId, defaults};
use super::*;
use crate::{Result, Session, Target};
@@ -173,12 +173,12 @@ fn duplicate(s: &mut Session, p: &Value) -> Result {
.get(*i)
.map(|x| {
let mut v = vec![];
- slidecraft_model::walk(&x.shapes, &mut |sh, _| v.push(sh.id));
+ deckcraft_model::walk(&x.shapes, &mut |sh, _| v.push(sh.id));
v
})
.unwrap_or_default();
let mut new_shape_ids = vec![];
- slidecraft_model::walk(©.shapes, &mut |sh, _| new_shape_ids.push(sh.id));
+ deckcraft_model::walk(©.shapes, &mut |sh, _| new_shape_ids.push(sh.id));
for a in &mut copy.animations {
if let Some(pos) = old_ids.iter().position(|x| *x == a.shape) {
a.shape = new_shape_ids.get(pos).copied().unwrap_or(a.shape);
@@ -360,16 +360,16 @@ fn hide(s: &mut Session, p: &Value) -> Result {
/// Re-map a slide's placeholders onto a new layout (content kept, matched by type/index).
pub fn apply_layout(doc: &mut Presentation, index: usize, layout: LayoutId) -> Result<()> {
- let lay_shapes: Vec =
+ let lay_shapes: Vec =
doc.layout(layout).map(|(_, l)| l.shapes.clone()).ok_or_else(|| bad("slide.layout", "no such layout"))?;
let mut fresh = vec![];
for ls in lay_shapes.iter().filter(|x| x.ph.as_ref().is_some_and(|p| !p.kind.is_footer_kind())) {
- let id = slidecraft_model::ShapeId(doc.alloc_id());
+ let id = deckcraft_model::ShapeId(doc.alloc_id());
fresh.push((id, ls.clone()));
}
let slide = doc.slides.get_mut(index).ok_or_else(|| bad("slide.layout", "no slide"))?;
let slide = Arc::make_mut(slide);
- let mut old: Vec = std::mem::take(&mut slide.shapes);
+ let mut old: Vec = std::mem::take(&mut slide.shapes);
let mut out = vec![];
for (id, ls) in fresh {
let Some(lph) = ls.ph.clone() else { continue };
@@ -389,13 +389,13 @@ pub fn apply_layout(doc: &mut Presentation, index: usize, layout: LayoutId) -> R
out.push(o);
}
None => {
- out.push(slidecraft_model::Shape { id, name: ls.name.clone(), ph: Some(lph), text: Some(TextBody::default()), ..Default::default() })
+ out.push(deckcraft_model::Shape { id, name: ls.name.clone(), ph: Some(lph), text: Some(TextBody::default()), ..Default::default() })
}
}
}
// Leftover placeholders with content stay as they were (positioned); empty ones go.
for o in old {
- let empty_ph = o.ph.is_some() && o.text.as_ref().is_none_or(|t| t.is_empty()) && matches!(o.kind, slidecraft_model::ShapeKind::Shape);
+ let empty_ph = o.ph.is_some() && o.text.as_ref().is_none_or(|t| t.is_empty()) && matches!(o.kind, deckcraft_model::ShapeKind::Shape);
if !empty_ph {
out.push(o);
}
@@ -574,7 +574,7 @@ fn from_outline(s: &mut Session, p: &Value) -> Result {
let text = str_param(p, "text").unwrap_or("").to_string();
s.edit(|doc, sel| {
let before = doc.slides.len();
- let n = slidecraft_format::outline_to_slides(doc, &text);
+ let n = deckcraft_format::outline_to_slides(doc, &text);
// outline_to_slides appends; move them after the current slide.
let at = (sel.slide + 1).min(before);
let new: Vec<_> = doc.slides.drain(before..).collect();
diff --git a/crates/engine/src/cmd/table.rs b/crates/engine/src/cmd/table.rs
index fb8bcfa..e9f6f16 100644
--- a/crates/engine/src/cmd/table.rs
+++ b/crates/engine/src/cmd/table.rs
@@ -1,7 +1,7 @@
//! Table Design and Table Layout tabs.
+use deckcraft_model::{ShapeKind, Table};
use serde_json::{Value, json};
-use slidecraft_model::{ShapeKind, Table};
use super::*;
use crate::{Result, Session};
@@ -108,7 +108,7 @@ fn with_table(s: &mut Session, p: &Value, cmd: &str, f: impl Fn(&mut Table) -> R
// The edited cell may have moved out of range.
s.select(|doc, sel| {
let dims = super::current_shapes(doc, sel)
- .and_then(|v| slidecraft_model::find_shape(v, id))
+ .and_then(|v| deckcraft_model::find_shape(v, id))
.and_then(|x| if let ShapeKind::Table(t) = &x.kind { Some((t.n_rows(), t.n_cols())) } else { None });
if let (Some(t), Some((nr, nc))) = (sel.text.as_mut(), dims)
&& let Some((r, c)) = t.cell
@@ -196,8 +196,7 @@ fn options(s: &mut Session, p: &Value) -> Result {
}
fn cell_fill(s: &mut Session, p: &Value) -> Result {
- let fill =
- if bool_or(p, "none", false) { Some(slidecraft_model::Fill::None) } else { color_param(p, "color").map(slidecraft_model::Fill::solid) };
+ let fill = if bool_or(p, "none", false) { Some(deckcraft_model::Fill::None) } else { color_param(p, "color").map(deckcraft_model::Fill::solid) };
let cells: Option> = p
.get("cells")
.and_then(Value::as_array)
diff --git a/crates/engine/src/cmd/text.rs b/crates/engine/src/cmd/text.rs
index 751251b..a35f55e 100644
--- a/crates/engine/src/cmd/text.rs
+++ b/crates/engine/src/cmd/text.rs
@@ -2,11 +2,11 @@
use std::sync::Arc;
+use deckcraft_model::edit::{self as ed, Pos};
+use deckcraft_model::resolve::{self, Ctx};
+use deckcraft_model::text::{AutoFit, Run, RunKind, RunProps, TextBody};
+use deckcraft_model::{Presentation, ShapeId, ShapeKind};
use serde_json::{Value, json};
-use slidecraft_model::edit::{self as ed, Pos};
-use slidecraft_model::resolve::{self, Ctx};
-use slidecraft_model::text::{AutoFit, Run, RunKind, RunProps, TextBody};
-use slidecraft_model::{Presentation, ShapeId, ShapeKind};
use super::*;
use crate::{DocState, Result, Selection, Session, Target, TextSel};
@@ -52,7 +52,7 @@ fn body_mut<'a>(doc: &'a mut Presentation, sel: &Selection, t: &TextSel) -> Opti
return doc.slides.get_mut(sel.slide).map(|s| &mut Arc::make_mut(s).notes);
}
let shapes = crate::shapes_mut(doc, sel)?;
- let sh = slidecraft_model::find_shape_mut(shapes, t.shape)?;
+ let sh = deckcraft_model::find_shape_mut(shapes, t.shape)?;
if let Some((r, c)) = t.cell {
if let ShapeKind::Table(tb) = &mut sh.kind {
return tb.cell_mut(r, c).map(|x| &mut x.text);
@@ -92,14 +92,14 @@ pub fn fit_text_box(s: &mut Session, id: ShapeId) -> Result<()> {
return Ok(());
}
let x = xfrm_of(&doc, &sel, &sh);
- let geo = slidecraft_render::shape_geometry(&sh, x.w, x.h);
- let fields = slidecraft_text::NoFields;
+ let geo = deckcraft_render::shape_geometry(&sh, x.w, x.h);
+ let fields = deckcraft_text::NoFields;
let mut nx = x;
if bp.wrap == Some(false) {
- nx.w = slidecraft_text::fit_width(&ctx, &sh, &body, geo.text_rect, &fields).max(20.0);
+ nx.w = deckcraft_text::fit_width(&ctx, &sh, &body, geo.text_rect, &fields).max(20.0);
}
- let geo = slidecraft_render::shape_geometry(&sh, nx.w, nx.h);
- let need = slidecraft_text::fit_height(&ctx, &sh, &body, geo.text_rect, &fields);
+ let geo = deckcraft_render::shape_geometry(&sh, nx.w, nx.h);
+ let need = deckcraft_text::fit_height(&ctx, &sh, &body, geo.text_rect, &fields);
let extra = (nx.h - geo.text_rect.height()).max(0.0);
nx.h = (need + extra).max(8.0);
if (nx.h - x.h).abs() < 0.01 && (nx.w - x.w).abs() < 0.01 && sh.xfrm.is_some() {
@@ -109,7 +109,7 @@ pub fn fit_text_box(s: &mut Session, id: ShapeId) -> Result<()> {
let st = s.doc_mut()?;
let mut d = (*st.doc).clone();
if let Some(list) = crate::shapes_mut(&mut d, &st.selection)
- && let Some(target) = slidecraft_model::find_shape_mut(list, id)
+ && let Some(target) = deckcraft_model::find_shape_mut(list, id)
{
target.xfrm = Some(nx);
}
@@ -127,7 +127,7 @@ pub(crate) fn ctx_for<'a>(doc: &'a Presentation, sel: &Selection) -> Option Option {
+pub fn layout_for(st: &DocState, t: &TextSel) -> Option {
if t.notes || t.cell.is_some() {
return None;
}
@@ -135,12 +135,12 @@ pub fn layout_for(st: &DocState, t: &TextSel) -> Option Result {
if sh.text.is_none() && cell.is_none() {
s.edit(|doc, sel| {
if let Some(list) = crate::shapes_mut(doc, sel)
- && let Some(x) = slidecraft_model::find_shape_mut(list, id)
+ && let Some(x) = deckcraft_model::find_shape_mut(list, id)
{
x.text = Some(TextBody::default());
}
@@ -313,7 +313,7 @@ pub(crate) fn insert_field(s: &mut Session, field: &str, text: &str) -> Result Result {
let t = st.selection.text.clone().ok_or_else(|| bad("text.move", "not editing text"))?;
let body = body_of(st, &t).cloned().unwrap_or_default();
let layout = layout_for(st, &t);
- let lp = |c: Pos| slidecraft_text::Pos { para: c.0, ch: c.1 };
+ let lp = |c: Pos| deckcraft_text::Pos { para: c.0, ch: c.1 };
let caret = t.caret;
let collapse_to = |left: bool| if left { t.ordered().0 } else { t.ordered().1 };
let new = match to.as_str() {
@@ -490,7 +490,7 @@ fn set(s: &mut Session, p: &Value) -> Result {
};
let Some(body) = target else { return Err(bad("text.set", "no text here (tables need `cell`)")) };
let keep = body.paragraphs.first().and_then(|p| p.runs.first()).map(|r| r.props.clone()).unwrap_or_default();
- let levels: Vec<(u8, slidecraft_model::text::ParaProps)> = body.paragraphs.iter().map(|p| (p.level, p.props.clone())).collect();
+ let levels: Vec<(u8, deckcraft_model::text::ParaProps)> = body.paragraphs.iter().map(|p| (p.level, p.props.clone())).collect();
let mut nb = TextBody::from_text(&text);
for (i, para) in nb.paragraphs.iter_mut().enumerate() {
// Leading tabs set the outline level.
@@ -593,7 +593,7 @@ pub(crate) fn format_run(s: &mut Session, f: &(dyn Fn(&mut RunProps) + Send + Sy
/// Apply paragraph changes to the paragraphs touched by the selection (or all paragraphs of the
/// selected shapes).
-pub(crate) fn format_para(s: &mut Session, f: &(dyn Fn(&mut slidecraft_model::text::Paragraph) + Send + Sync)) -> Result {
+pub(crate) fn format_para(s: &mut Session, f: &(dyn Fn(&mut deckcraft_model::text::Paragraph) + Send + Sync)) -> Result {
let st = s.doc()?;
if let Some(t) = st.selection.text.clone() {
let (a, b) = t.ordered();
@@ -639,7 +639,7 @@ pub(crate) fn format_para(s: &mut Session, f: &(dyn Fn(&mut slidecraft_model::te
}
/// Apply body (text box) changes to the edited or selected shapes.
-pub(crate) fn format_body(s: &mut Session, f: &(dyn Fn(&mut slidecraft_model::text::BodyProps) + Send + Sync)) -> Result {
+pub(crate) fn format_body(s: &mut Session, f: &(dyn Fn(&mut deckcraft_model::text::BodyProps) + Send + Sync)) -> Result {
let st = s.doc()?;
let ids: Vec = match &st.selection.text {
Some(t) if !t.notes => vec![t.shape],
diff --git a/crates/engine/src/cmd/transition.rs b/crates/engine/src/cmd/transition.rs
index b7ea2e5..616cf00 100644
--- a/crates/engine/src/cmd/transition.rs
+++ b/crates/engine/src/cmd/transition.rs
@@ -2,9 +2,9 @@
use std::sync::Arc;
+use deckcraft_model::Transition;
+use deckcraft_model::anim::TRANSITIONS;
use serde_json::{Value, json};
-use slidecraft_model::Transition;
-use slidecraft_model::anim::TRANSITIONS;
use super::*;
use crate::{Result, Session};
diff --git a/crates/engine/src/connect.rs b/crates/engine/src/connect.rs
index 87b325a..8cdbecd 100644
--- a/crates/engine/src/connect.rs
+++ b/crates/engine/src/connect.rs
@@ -1,8 +1,8 @@
//! Connectors glued to shapes: connection sites, snapping line ends to them, and rerouting attached
//! connectors whenever the shapes they connect move, resize or rotate.
-use slidecraft_geom::{Point, Xfrm};
-use slidecraft_model::{Presentation, Shape, ShapeId, ShapeKind};
+use deckcraft_geom::{Point, Xfrm};
+use deckcraft_model::{Presentation, Shape, ShapeId, ShapeKind};
use crate::Selection;
use crate::cmd::xfrm_of;
@@ -13,7 +13,7 @@ pub fn sites(doc: &Presentation, sel: &Selection, s: &Shape) -> Vec {
return vec![];
}
let x = xfrm_of(doc, sel, s);
- let mut local = slidecraft_render::shape_geometry(s, x.w, x.h).sites;
+ let mut local = deckcraft_render::shape_geometry(s, x.w, x.h).sites;
if local.is_empty() {
local = vec![Point::new(x.w / 2.0, 0.0), Point::new(0.0, x.h / 2.0), Point::new(x.w / 2.0, x.h), Point::new(x.w, x.h / 2.0)];
}
diff --git a/crates/engine/src/keys.rs b/crates/engine/src/keys.rs
index 41f323c..966a109 100644
--- a/crates/engine/src/keys.rs
+++ b/crates/engine/src/keys.rs
@@ -176,7 +176,7 @@ impl Session {
let st = self.doc()?;
let Some(t) = st.selection.text.clone() else { return Ok(Value::Null) };
let Some((r, c)) = t.cell else { return Ok(Value::Null) };
- let Some(slidecraft_model::ShapeKind::Table(tb)) = st.shape(t.shape).map(|s| s.kind.clone()) else { return Ok(Value::Null) };
+ let Some(deckcraft_model::ShapeKind::Table(tb)) = st.shape(t.shape).map(|s| s.kind.clone()) else { return Ok(Value::Null) };
let (nr, nc) = (tb.n_rows(), tb.n_cols());
let idx = r * nc + c;
if !back && idx + 1 >= nr * nc {
diff --git a/crates/engine/src/lib.rs b/crates/engine/src/lib.rs
index 8f9d261..a7d47aa 100644
--- a/crates/engine/src/lib.rs
+++ b/crates/engine/src/lib.rs
@@ -1,4 +1,4 @@
-//! The SlideCraft engine façade.
+//! The DeckCraft engine façade.
//!
//! Every user-visible action is a command with a stable id (`slide.new`, `shape.insert`,
//! `text.insert`, `format.bold`…) and JSON parameters. The egui UI, the CLI, the control channel
@@ -18,15 +18,15 @@ pub mod tools;
use std::sync::Arc;
+use deckcraft_model::{Presentation, Shape, ShapeId, Slide, SlideId};
use serde::{Deserialize, Serialize};
use serde_json::Value;
-use slidecraft_model::{Presentation, Shape, ShapeId, Slide, SlideId};
pub use cmd::{CommandInfo, CommandSpec, command_specs, find_command};
-pub use slidecraft_geom as geom;
-pub use slidecraft_media as media;
-pub use slidecraft_model as model;
-pub use slidecraft_render as render;
+pub use deckcraft_geom as geom;
+pub use deckcraft_media as media;
+pub use deckcraft_model as model;
+pub use deckcraft_render as render;
pub use tools::{Mods, PointerEvent, PointerKind, ToolKind};
#[derive(Debug, thiserror::Error)]
@@ -45,8 +45,8 @@ pub enum EngineError {
Internal(String, String),
}
-impl From for EngineError {
- fn from(e: slidecraft_model::ModelError) -> Self {
+impl From for EngineError {
+ fn from(e: deckcraft_model::ModelError) -> Self {
EngineError::Other(e.to_string())
}
}
@@ -184,7 +184,7 @@ impl DocState {
}
}
pub fn shape(&self, id: ShapeId) -> Option<&Shape> {
- slidecraft_model::find_shape(self.shapes(), id)
+ deckcraft_model::find_shape(self.shapes(), id)
}
pub fn selected_shapes(&self) -> Vec<&Shape> {
self.selection.shapes.iter().filter_map(|id| self.shape(*id)).collect()
@@ -282,8 +282,8 @@ pub struct Clipboard {
pub shapes: Vec,
pub slides: Vec,
/// Media the copied shapes/slides reference.
- pub media: Vec,
- pub text: Option,
+ pub media: Vec,
+ pub text: Option,
pub plain: String,
/// How many times the shapes were pasted (each paste offsets further).
pub pastes: u32,
diff --git a/crates/engine/src/links.rs b/crates/engine/src/links.rs
index 0dc6a15..ac0c787 100644
--- a/crates/engine/src/links.rs
+++ b/crates/engine/src/links.rs
@@ -3,6 +3,6 @@
pub const DISCORD: &str = "https://discord.gg/artcraft";
pub const WEBSITE: &str = "https://getartcraft.com/";
pub const APPS: &str = "https://getartcraft.com/apps";
-pub const APP_PAGE: &str = "https://getartcraft.com/apps/slidecraft";
-pub const GITHUB: &str = "https://github.com/storytold/slidecraft";
-pub const ISSUES: &str = "https://github.com/storytold/slidecraft/issues";
+pub const APP_PAGE: &str = "https://getartcraft.com/apps/deckcraft";
+pub const GITHUB: &str = "https://github.com/storytold/deckcraft";
+pub const ISSUES: &str = "https://github.com/storytold/deckcraft/issues";
diff --git a/crates/engine/src/recovery.rs b/crates/engine/src/recovery.rs
index ba6e3c9..30daa81 100644
--- a/crates/engine/src/recovery.rs
+++ b/crates/engine/src/recovery.rs
@@ -4,7 +4,7 @@
//! `file.recovery.open` reopens what was left there as unsaved presentations that remember where
//! they were saved.
//!
-//! An entry is `.slidecraft` (the presentation) plus `.json` (`{"path", "title",
+//! An entry is `.deckcraft` (the presentation) plus `.json` (`{"path", "title",
//! "saved"}`: the original file, the title and when it was written, Unix seconds).
use std::path::{Path, PathBuf};
@@ -17,16 +17,16 @@ use crate::{DocState, EngineError, Result, Session};
pub fn default_dir() -> Option {
let home = std::env::var_os("HOME").map(PathBuf::from);
if cfg!(target_os = "macos") {
- return home.map(|h| h.join("Library/Application Support/SlideCraft/Recovery"));
+ return home.map(|h| h.join("Library/Application Support/DeckCraft/Recovery"));
}
if cfg!(target_os = "windows") {
- return std::env::var_os("APPDATA").map(|a| PathBuf::from(a).join("SlideCraft").join("Recovery"));
+ return std::env::var_os("APPDATA").map(|a| PathBuf::from(a).join("DeckCraft").join("Recovery"));
}
- std::env::var_os("XDG_DATA_HOME").map(PathBuf::from).or_else(|| home.map(|h| h.join(".local/share"))).map(|d| d.join("slidecraft/recovery"))
+ std::env::var_os("XDG_DATA_HOME").map(PathBuf::from).or_else(|| home.map(|h| h.join(".local/share"))).map(|d| d.join("deckcraft/recovery"))
}
fn files(dir: &Path, uid: u64) -> (PathBuf, PathBuf) {
- (dir.join(format!("{uid}.slidecraft")), dir.join(format!("{uid}.json")))
+ (dir.join(format!("{uid}.deckcraft")), dir.join(format!("{uid}.json")))
}
fn now_secs() -> u64 {
@@ -58,7 +58,7 @@ pub fn save(s: &Session, dir: &Path) -> Result {
continue;
}
let (doc, meta) = files(dir, d.uid);
- let bytes = slidecraft_format::save(&d.doc).map_err(|e| EngineError::Other(e.to_string()))?;
+ let bytes = deckcraft_format::save(&d.doc).map_err(|e| EngineError::Other(e.to_string()))?;
// Write then rename, so a crash mid-write never leaves a torn file.
let tmp = doc.with_extension("tmp");
std::fs::write(&tmp, &bytes).and_then(|_| std::fs::rename(&tmp, &doc)).map_err(|e| EngineError::Other(format!("{}: {e}", doc.display())))?;
@@ -75,7 +75,7 @@ pub fn list(dir: &Path) -> Vec<(u64, Value)> {
let Ok(rd) = std::fs::read_dir(dir) else { return out };
for e in rd.flatten() {
let p = e.path();
- if p.extension().and_then(|x| x.to_str()) != Some("slidecraft") {
+ if p.extension().and_then(|x| x.to_str()) != Some("deckcraft") {
continue;
}
let Some(uid) = p.file_stem().and_then(|s| s.to_str()).and_then(|s| s.parse::().ok()) else { continue };
@@ -98,8 +98,8 @@ pub fn open(s: &mut Session, dir: &Path) -> Result> {
for (uid, meta) in list(dir) {
let (doc, _) = files(dir, uid);
let Ok(bytes) = std::fs::read(&doc) else { continue };
- let Ok(mut d) = slidecraft_format::load(&bytes) else { continue };
- slidecraft_format::repair(&mut d);
+ let Ok(mut d) = deckcraft_format::load(&bytes) else { continue };
+ deckcraft_format::repair(&mut d);
let path = meta.get("path").and_then(Value::as_str).map(str::to_string);
let title = meta.get("title").and_then(Value::as_str).filter(|t| !t.is_empty()).unwrap_or("Presentation");
let mut st = DocState::new(d, path, format!("{title} (Recovered)"));
@@ -117,7 +117,7 @@ mod tests {
use super::*;
fn temp(name: &str) -> PathBuf {
- let dir = std::env::temp_dir().join(format!("slidecraft-{name}-{}", std::process::id()));
+ let dir = std::env::temp_dir().join(format!("deckcraft-{name}-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
dir
}
@@ -143,7 +143,7 @@ mod tests {
// Saving drops the entry.
s.recovery_dir = Some(dir.clone());
assert_eq!(save(&s, &dir).unwrap(), 1);
- let path = dir.join("saved.slidecraft");
+ let path = dir.join("saved.deckcraft");
s.execute("file.saveAs", &json!({"path": path.to_string_lossy()})).unwrap();
assert!(list(&dir).is_empty());
let _ = std::fs::remove_dir_all(&dir);
@@ -153,7 +153,7 @@ mod tests {
fn corrupt_entries_are_skipped() {
let dir = temp("recovery-corrupt");
std::fs::create_dir_all(&dir).unwrap();
- std::fs::write(dir.join("7.slidecraft"), b"not a presentation").unwrap();
+ std::fs::write(dir.join("7.deckcraft"), b"not a presentation").unwrap();
std::fs::write(dir.join("7.json"), b"[1, 2]").unwrap();
let mut s = Session::new();
assert!(open(&mut s, &dir).unwrap().is_empty());
diff --git a/crates/engine/src/sample.rs b/crates/engine/src/sample.rs
index 886ffaa..80c11ca 100644
--- a/crates/engine/src/sample.rs
+++ b/crates/engine/src/sample.rs
@@ -1,5 +1,5 @@
//! A sample deck built entirely in code (our own text, shapes and data), for demos, screenshots
-//! and tests: `slidecraft --sample`.
+//! and tests: `deckcraft --sample`.
use serde_json::{Value, json};
@@ -15,13 +15,13 @@ fn last_id(v: &Value) -> u64 {
/// Open the sample presentation in a new document.
pub fn open_sample(s: &mut Session) -> Result<()> {
- run(s, "file.new", json!({"theme": "SlideCraft"}))?;
+ run(s, "file.new", json!({"theme": "DeckCraft"}))?;
let st = s.doc()?;
let title = st.current_slide().and_then(|x| x.shapes.first()).map(|x| x.id.0).unwrap_or(0);
let sub = st.current_slide().and_then(|x| x.shapes.get(1)).map(|x| x.id.0).unwrap_or(0);
run(s, "text.set", json!({"id": title, "text": "Northern Lights"}))?;
run(s, "text.set", json!({"id": sub, "text": "A short tour of the aurora — where it comes from, when to look, and how to photograph it"}))?;
- run(s, "slide.notes", json!({"text": "Welcome everyone. This deck was made in SlideCraft."}))?;
+ run(s, "slide.notes", json!({"text": "Welcome everyone. This deck was made in DeckCraft."}))?;
// Decorative bands on the title slide.
let b = run(s, "shape.insert", json!({"preset": "rect", "rect": [0, 470, 960, 70]}))?;
run(s, "shape.fill", json!({"id": last_id(&b), "gradient": {"stops": [[0, "accent1"], [0.5, "accent4"], [1, "accent2"]], "angle": 0}}))?;
@@ -114,7 +114,7 @@ pub fn open_sample(s: &mut Session) -> Result<()> {
run(s, "transition.set", json!({"kind": "split"}))?;
// Closing.
- run(s, "slide.new", json!({"layout": "title", "title": "Clear skies!", "body": "Made with SlideCraft — free and open source"}))?;
+ run(s, "slide.new", json!({"layout": "title", "title": "Clear skies!", "body": "Made with DeckCraft — free and open source"}))?;
let moon = run(s, "shape.insert", json!({"preset": "moon", "rect": [820, 40, 80, 120]}))?;
run(s, "shape.fill", json!({"id": last_id(&moon), "color": "accent6"}))?;
run(s, "shape.line", json!({"id": last_id(&moon), "none": true}))?;
diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs
index fb52d08..43ac6c4 100644
--- a/crates/engine/src/tests.rs
+++ b/crates/engine/src/tests.rs
@@ -174,7 +174,7 @@ fn sample_deck_builds_and_renders() {
assert!(st.doc.validate().is_empty());
assert!(!st.is_dirty());
for i in 0..st.doc.slides.len() {
- let img = slidecraft_render::render_slide(&st.doc, i, &slidecraft_render::RenderOpts { scale: 0.2, ..Default::default() });
+ let img = deckcraft_render::render_slide(&st.doc, i, &deckcraft_render::RenderOpts { scale: 0.2, ..Default::default() });
assert_eq!(img.width, 192);
}
let v = s.execute("document.inspect", &json!({})).unwrap();
@@ -187,7 +187,7 @@ fn save_and_reopen_native() {
sample::open_sample(&mut s).unwrap();
let bytes = s.execute("file.saveBytes", &json!({})).unwrap()["data"].as_str().unwrap().to_string();
let n = s.doc().unwrap().doc.slides.len();
- s.execute("file.openBytes", &json!({"name": "x.slidecraft", "data": bytes})).unwrap();
+ s.execute("file.openBytes", &json!({"name": "x.deckcraft", "data": bytes})).unwrap();
assert_eq!(s.doc().unwrap().doc.slides.len(), n);
assert_eq!(s.documents().len(), 2);
}
@@ -249,9 +249,9 @@ fn themes_and_slide_size() {
assert!(v["shapes"].as_array().unwrap().iter().any(|x| x["placeholder"] == "sldNum"));
}
-fn line_ends(s: &Session, id: u64) -> (slidecraft_geom::Point, slidecraft_geom::Point) {
+fn line_ends(s: &Session, id: u64) -> (deckcraft_geom::Point, deckcraft_geom::Point) {
let st = s.doc().unwrap();
- let sh = st.shape(slidecraft_model::ShapeId(id as u32)).unwrap();
+ let sh = st.shape(deckcraft_model::ShapeId(id as u32)).unwrap();
connect::endpoints(&cmd::xfrm_of(&st.doc, &st.selection, sh))
}
@@ -271,7 +271,7 @@ fn connectors_follow_glued_shapes() {
s.execute("shape.move", &json!({"dx": 50, "dy": 20})).unwrap();
let (q0, q1) = line_ends(&s, c);
assert!((q0 - p0).hypot() < 1e-6);
- assert!(((q1 - p1) - slidecraft_geom::Vec2::new(50.0, 20.0)).hypot() < 1e-6, "{p1:?} → {q1:?}");
+ assert!(((q1 - p1) - deckcraft_geom::Vec2::new(50.0, 20.0)).hypot() < 1e-6, "{p1:?} → {q1:?}");
// Undo puts both back in one step.
s.execute("edit.undo", &json!({})).unwrap();
assert!((line_ends(&s, c).1 - p1).hypot() < 1e-6);
@@ -279,8 +279,8 @@ fn connectors_follow_glued_shapes() {
s.execute("edit.select", &json!({"ids": [a]})).unwrap();
s.execute("edit.delete", &json!({})).unwrap();
let st = s.doc().unwrap();
- let sh = st.shape(slidecraft_model::ShapeId(c as u32)).unwrap();
- assert!(matches!(sh.kind, slidecraft_model::ShapeKind::Connector { start: None, end: Some(_) }));
+ let sh = st.shape(deckcraft_model::ShapeId(c as u32)).unwrap();
+ assert!(matches!(sh.kind, deckcraft_model::ShapeKind::Connector { start: None, end: Some(_) }));
}
#[test]
@@ -301,7 +301,7 @@ fn drawing_a_line_between_shapes_glues_it() {
let st = s.doc().unwrap();
let line = st.shapes().iter().find(|x| x.is_line()).unwrap();
match line.kind {
- slidecraft_model::ShapeKind::Connector { start: Some((x, _)), end: Some((y, _)) } => {
+ deckcraft_model::ShapeKind::Connector { start: Some((x, _)), end: Some((y, _)) } => {
assert_eq!((x.0 as u64, y.0 as u64), (a, b));
}
ref k => panic!("not glued: {k:?}"),
@@ -336,7 +336,7 @@ fn freeform_tools_and_command() {
let st = s.doc().unwrap();
let sh = st.shapes().last().unwrap();
match &sh.geom {
- slidecraft_model::Geom::Custom { paths } => assert!(paths[0].d.ends_with('Z') && paths[0].d.matches(" L ").count() == 2, "{}", paths[0].d),
+ deckcraft_model::Geom::Custom { paths } => assert!(paths[0].d.ends_with('Z') && paths[0].d.matches(" L ").count() == 2, "{}", paths[0].d),
g => panic!("{g:?}"),
}
let x = sh.xfrm.unwrap();
@@ -359,10 +359,10 @@ fn freeform_tools_and_command() {
s.pointer(ev(tools::PointerKind::Up, x, y)).unwrap();
}
let r = s.key("Enter", Default::default()).unwrap();
- let id = slidecraft_model::ShapeId(r["id"].as_u64().unwrap() as u32);
+ let id = deckcraft_model::ShapeId(r["id"].as_u64().unwrap() as u32);
let st = s.doc().unwrap();
match &st.shape(id).unwrap().geom {
- slidecraft_model::Geom::Custom { paths } => assert_eq!(paths[0].d.matches(" C ").count(), 3),
+ deckcraft_model::Geom::Custom { paths } => assert_eq!(paths[0].d.matches(" C ").count(), 3),
g => panic!("{g:?}"),
}
}
diff --git a/crates/engine/src/tools.rs b/crates/engine/src/tools.rs
index 0b35fb7..aef394e 100644
--- a/crates/engine/src/tools.rs
+++ b/crates/engine/src/tools.rs
@@ -3,10 +3,10 @@
use std::sync::Arc;
+use deckcraft_geom::{Affine, Point, Rect, Vec2, Xfrm};
+use deckcraft_model::{Shape, ShapeId, ShapeKind};
use serde::{Deserialize, Serialize};
use serde_json::{Value, json};
-use slidecraft_geom::{Affine, Point, Rect, Vec2, Xfrm};
-use slidecraft_model::{Shape, ShapeId, ShapeKind};
use crate::cmd::{self, xfrm_of};
use crate::{HistoryEntry, Result, Session, TextSel};
@@ -55,7 +55,7 @@ pub enum ToolKind {
/// Ink: pen (`pen`, `highlighter`) or `eraser`.
Ink {
mode: String,
- color: slidecraft_color::Rgba,
+ color: deckcraft_color::Rgba,
width: f64,
},
}
@@ -229,7 +229,7 @@ impl Session {
}
continue;
}
- let geo = slidecraft_render::shape_geometry(sh, x.w, x.h);
+ let geo = deckcraft_render::shape_geometry(sh, x.w, x.h);
for hd in &geo.handles {
if (x.affine() * hd.pos - p).hypot() <= tol * 1.6 {
return Some(Hit::Adjust { id: *id, index: hd.index });
@@ -277,7 +277,7 @@ impl Session {
return Ok(Value::Null);
}
self.tool.hover = self.hit_test(p, tol);
- let line_tool = matches!(&self.tool.kind, ToolKind::Shape { preset } if slidecraft_geom::preset::is_line_like(preset));
+ let line_tool = matches!(&self.tool.kind, ToolKind::Shape { preset } if deckcraft_geom::preset::is_line_like(preset));
if line_tool {
self.update_sites(p, tol, None);
} else {
@@ -485,7 +485,7 @@ impl Session {
self.edit(|doc, sel| {
let list = crate::shapes_mut(doc, sel).ok_or_else(|| cmd::bad("move", "no slide"))?;
for (id, o) in &origs2 {
- if let Some(sh) = slidecraft_model::find_shape_mut(list, *id) {
+ if let Some(sh) = deckcraft_model::find_shape_mut(list, *id) {
sh.xfrm = Some(Xfrm { x: o.x + d.x, y: o.y + d.y, ..*o });
}
}
@@ -556,7 +556,7 @@ impl Session {
let st = self.doc()?;
let Some(sh) = st.shape(id).cloned() else { return Ok(Value::Null) };
let local = orig.affine().inverse() * p;
- let geo = slidecraft_render::shape_geometry(&sh, orig.w, orig.h);
+ let geo = deckcraft_render::shape_geometry(&sh, orig.w, orig.h);
let Some(h) = geo.handles.iter().find(|h| h.index == index) else { return Ok(Value::Null) };
let free2d = matches!(sh.geom.preset_name(), Some(n) if n.contains("Callout") && !n.contains("Arrow"));
let vals: Vec<(usize, f64)> = if free2d {
@@ -568,10 +568,10 @@ impl Session {
};
self.edit(|doc, sel| {
let list = crate::shapes_mut(doc, sel).ok_or_else(|| cmd::bad("adjust", "no slide"))?;
- if let Some(sh) = slidecraft_model::find_shape_mut(list, id)
- && let slidecraft_model::Geom::Preset { name, adj } = &mut sh.geom
+ if let Some(sh) = deckcraft_model::find_shape_mut(list, id)
+ && let deckcraft_model::Geom::Preset { name, adj } = &mut sh.geom
{
- let defaults = slidecraft_geom::preset::info(name).map(|x| x.defaults).unwrap_or(&[]);
+ let defaults = deckcraft_geom::preset::info(name).map(|x| x.defaults).unwrap_or(&[]);
for (i, v) in &vals {
while adj.len() <= *i {
let k = adj.len();
@@ -589,7 +589,7 @@ impl Session {
self.tool.marquee = Some(Rect::from_points(start, p));
}
Drag::Create { start } => {
- let line = matches!(&self.tool.kind, ToolKind::Shape { preset } if slidecraft_geom::preset::is_line_like(preset));
+ let line = matches!(&self.tool.kind, ToolKind::Shape { preset } if deckcraft_geom::preset::is_line_like(preset));
let x = if line {
let mut q = if mods.shift { constrain_45(start, p) } else { p };
if let Some((_, _, at)) = self.update_sites(q, tol, None) {
@@ -693,7 +693,7 @@ impl Session {
let dragged = (p - start).hypot() > 2.0;
let res = match &kind {
ToolKind::Shape { preset } => {
- let line = slidecraft_geom::preset::is_line_like(preset);
+ let line = deckcraft_geom::preset::is_line_like(preset);
let rect = match preview.filter(|_| dragged) {
Some(x) => x,
None if line => Xfrm::new(start.x, start.y, 72.0, 0.0),
@@ -789,7 +789,7 @@ impl Session {
let x = xfrm_of(&st.doc, &st.selection, &sh);
let local = x.affine().inverse() * p;
if let ShapeKind::Table(t) = &sh.kind {
- let cell = slidecraft_render_cell(t, local);
+ let cell = deckcraft_render_cell(t, local);
return self.execute("text.edit", &json!({"id": id, "cell": [cell.0, cell.1]}));
}
Ok(Value::Null)
@@ -857,7 +857,7 @@ impl Session {
fn set_xfrm(&mut self, id: ShapeId, x: Xfrm) -> Result<()> {
self.edit(|doc, sel| {
let list = crate::shapes_mut(doc, sel).ok_or_else(|| cmd::bad("tool", "no slide"))?;
- if let Some(sh) = slidecraft_model::find_shape_mut(list, id) {
+ if let Some(sh) = deckcraft_model::find_shape_mut(list, id) {
sh.xfrm = Some(x);
if let ShapeKind::Table(t) = &mut sh.kind {
// Tables resize their columns and rows proportionally.
@@ -875,7 +875,7 @@ impl Session {
self.execute("shape.setBounds", &json!({"id": id, "x": x.x, "y": x.y, "w": x.w, "h": x.h}))?;
if x.flip_h || x.flip_v {
self.edit(|doc, sel| {
- if let Some(sh) = crate::shapes_mut(doc, sel).and_then(|l| slidecraft_model::find_shape_mut(l, id))
+ if let Some(sh) = crate::shapes_mut(doc, sel).and_then(|l| deckcraft_model::find_shape_mut(l, id))
&& let Some(xx) = sh.xfrm.as_mut()
{
xx.flip_h = x.flip_h;
@@ -969,14 +969,14 @@ impl Session {
let rr = r + Vec2::new(d, 0.0);
guides.push(Guide { vertical: true, pos: x, from: b.y0.min(rr.y0), to: b.y1.max(rr.y1) });
} else if self.prefs.snap_to_grid {
- off.x = slidecraft_geom::snap(r.x0, self.prefs.grid_spacing) - r.x0;
+ off.x = deckcraft_geom::snap(r.x0, self.prefs.grid_spacing) - r.x0;
}
if let Some((d, y, b)) = best_y {
off.y = d;
let rr = r + Vec2::new(0.0, d);
guides.push(Guide { vertical: false, pos: y, from: b.x0.min(rr.x0), to: b.x1.max(rr.x1) });
} else if self.prefs.snap_to_grid {
- off.y = slidecraft_geom::snap(r.y0, self.prefs.grid_spacing) - r.y0;
+ off.y = deckcraft_geom::snap(r.y0, self.prefs.grid_spacing) - r.y0;
}
(off, guides)
}
@@ -1014,7 +1014,7 @@ impl Session {
fn add_ink(&mut self, points: Vec<(f64, f64, f32)>) -> Result {
let ToolKind::Ink { mode, color, width } = self.tool.kind.clone() else { return Ok(Value::Null) };
- let stroke = slidecraft_model::InkStroke { points, color, width, highlighter: mode == "highlighter" };
+ let stroke = deckcraft_model::InkStroke { points, color, width, highlighter: mode == "highlighter" };
// Strokes join the slide's ink layer (one ink shape per slide).
let existing = self.doc()?.shapes().iter().rev().find(|s| matches!(s.kind, ShapeKind::Ink { .. })).map(|s| s.id);
self.edit(|doc, sel| {
@@ -1035,7 +1035,7 @@ impl Session {
}
};
let list = crate::shapes_mut(doc, sel).ok_or_else(|| cmd::bad("ink", "no slide"))?;
- if let Some(sh) = slidecraft_model::find_shape_mut(list, id)
+ if let Some(sh) = deckcraft_model::find_shape_mut(list, id)
&& let ShapeKind::Ink { strokes } = &mut sh.kind
{
strokes.push(stroke);
@@ -1051,7 +1051,7 @@ impl Session {
let sel_before = st.selection.clone();
self.edit(|doc, sel| {
let list = crate::shapes_mut(doc, sel).ok_or_else(|| cmd::bad("ink", "no slide"))?;
- if let Some(sh) = slidecraft_model::find_shape_mut(list, ink)
+ if let Some(sh) = deckcraft_model::find_shape_mut(list, ink)
&& let ShapeKind::Ink { strokes } = &mut sh.kind
{
strokes.retain(|s| !s.points.iter().any(|(x, y, _)| (Point::new(*x, *y) - p).hypot() <= tol * 2.0 + s.width));
@@ -1186,7 +1186,7 @@ fn hit_shape(st: &crate::DocState, sh: &Shape, p: Point, tol: f64) -> Option {
// A group is hit when any child is hit; the group is what gets selected.
let ch = Rect::new(child.x, child.y, child.x + child.w, child.y + child.h);
- let m = slidecraft_geom::group_child_affine(&x, ch);
+ let m = deckcraft_geom::group_child_affine(&x, ch);
let q = m.inverse() * p;
for c in children.iter().rev() {
if let Some(cx) = c.xfrm {
@@ -1208,9 +1208,9 @@ fn hit_shape(st: &crate::DocState, sh: &Shape, p: Point, tol: f64) -> Option {
if sh.is_line() {
- let geo = slidecraft_render::shape_geometry(sh, x.w, x.h);
+ let geo = deckcraft_render::shape_geometry(sh, x.w, x.h);
let lw = sh.line.as_ref().and_then(|l| l.width).unwrap_or(1.0);
- if slidecraft_geom::near_path(&geo.outline(), local, tol.max(lw)) {
+ if deckcraft_geom::near_path(&geo.outline(), local, tol.max(lw)) {
return Some(Hit::Shape { id: sh.id, text: false });
}
return None;
@@ -1221,11 +1221,11 @@ fn hit_shape(st: &crate::DocState, sh: &Shape, p: Point, tol: f64) -> Option Option (usize, usize) {
}
/// Which table cell contains a table-local point.
-fn slidecraft_render_cell(t: &slidecraft_model::Table, p: Point) -> (usize, usize) {
+fn deckcraft_render_cell(t: &deckcraft_model::Table, p: Point) -> (usize, usize) {
let mut y = 0.0;
let mut row = t.rows.len().saturating_sub(1);
for (i, r) in t.rows.iter().enumerate() {
diff --git a/crates/fonts/Cargo.toml b/crates/fonts/Cargo.toml
index 11194a1..64944f0 100644
--- a/crates/fonts/Cargo.toml
+++ b/crates/fonts/Cargo.toml
@@ -1,9 +1,9 @@
[package]
-name = "slidecraft-fonts"
+name = "deckcraft-fonts"
version.workspace = true
edition.workspace = true
license.workspace = true
-description = "SlideCraft fonts: font database (craft-fonts + system), substitutes for Office fonts, metrics, outlines, shaping"
+description = "DeckCraft fonts: font database (craft-fonts + system), substitutes for Office fonts, metrics, outlines, shaping"
[lints]
workspace = true
diff --git a/crates/fonts/build.rs b/crates/fonts/build.rs
index 0e351e0..76e7dc4 100644
--- a/crates/fonts/build.rs
+++ b/crates/fonts/build.rs
@@ -7,7 +7,7 @@
use std::fmt::Write as _;
use std::path::PathBuf;
-/// Families SlideCraft never embeds (AGENTS.md §1: no Adobe-authored visual design).
+/// Families DeckCraft never embeds (AGENTS.md §1: no Adobe-authored visual design).
const EXCLUDED_FAMILIES: &[&str] = &["Source Sans 3", "Source Serif 4", "Source Han Sans", "Source Han Serif", "Noto Sans CJK SC"];
fn main() {
@@ -53,7 +53,7 @@ fn manifest(dir: &std::path::Path, manifest: &std::path::Path) -> Result` files with the embedded media, and `mimetype`. Readers ignore unknown
//! fields, so newer files open in older builds with what those builds understand.
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
@@ -7,11 +7,13 @@
use std::io::{Cursor, Read, Write};
use std::sync::Arc;
-use slidecraft_model::text::{Paragraph, TextBody};
-use slidecraft_model::{LayoutType, Presentation, defaults};
+use deckcraft_model::text::{Paragraph, TextBody};
+use deckcraft_model::{LayoutType, Presentation, defaults};
-pub const MIMETYPE: &str = "application/vnd.storyteller.slidecraft+zip";
-pub const EXTENSION: &str = "slidecraft";
+pub const MIMETYPE: &str = "application/vnd.storyteller.deckcraft+zip";
+/// Files written before the SlideCraft → DeckCraft rename (`.slidecraft`) are still read.
+pub const LEGACY_MIMETYPE: &str = concat!("application/vnd.storyteller.", "slide", "craft+zip");
+pub const EXTENSION: &str = "deckcraft";
/// Format version written into the manifest.
pub const VERSION: u32 = 1;
/// Largest entry we will inflate (guards against zip bombs).
@@ -19,7 +21,7 @@ const MAX_ENTRY: u64 = 2 << 30;
#[derive(Debug, thiserror::Error)]
pub enum FormatError {
- #[error("not a SlideCraft file: {0}")]
+ #[error("not a DeckCraft file: {0}")]
NotOurs(String),
#[error("damaged file: {0}")]
Damaged(String),
@@ -48,9 +50,9 @@ pub fn save(p: &Presentation) -> Result> {
z.start_file("mimetype", stored).map_err(w)?;
z.write_all(MIMETYPE.as_bytes()).map_err(|e| FormatError::Write(e.to_string()))?;
let m = Manifest {
- format: "slidecraft".into(),
+ format: "deckcraft".into(),
version: VERSION,
- generator: format!("SlideCraft {}", env!("CARGO_PKG_VERSION")),
+ generator: format!("DeckCraft {}", env!("CARGO_PKG_VERSION")),
presentation: p.clone(),
};
let json = serde_json::to_vec(&m).map_err(|e| FormatError::Write(e.to_string()))?;
@@ -79,9 +81,9 @@ fn is_compressed(ct: &str) -> bool {
|| ct.contains("zip")
}
-/// Does this look like a `.slidecraft` file?
+/// Does this look like a `.deckcraft` file?
pub fn sniff(bytes: &[u8]) -> bool {
- bytes.starts_with(b"PK") && bytes.windows(MIMETYPE.len()).take(200).any(|w| w == MIMETYPE.as_bytes())
+ bytes.starts_with(b"PK") && [MIMETYPE, LEGACY_MIMETYPE].iter().any(|m| bytes.windows(m.len()).take(200).any(|w| w == m.as_bytes()))
}
fn read_entry(z: &mut zip::ZipArchive>, name: &str) -> Result> {
@@ -99,7 +101,7 @@ pub fn load(bytes: &[u8]) -> Result {
let mut z = zip::ZipArchive::new(Cursor::new(bytes)).map_err(|e| FormatError::NotOurs(e.to_string()))?;
let json = read_entry(&mut z, "presentation.json").map_err(|_| FormatError::NotOurs("no presentation.json".into()))?;
let m: Manifest = serde_json::from_slice(&json).map_err(|e| FormatError::Damaged(e.to_string()))?;
- if m.format != "slidecraft" {
+ if m.format != "deckcraft" {
return Err(FormatError::NotOurs(m.format));
}
let mut p = m.presentation;
@@ -126,7 +128,7 @@ pub fn repair(p: &mut Presentation) {
let needs_layout = p.slides.get(i).is_some_and(|s| p.layout(s.layout).is_none());
let dup = p.slides.get(i).is_some_and(|s| !seen.insert(s.id));
if needs_layout || dup {
- let new_id = if dup { Some(slidecraft_model::SlideId(p.alloc_id())) } else { None };
+ let new_id = if dup { Some(deckcraft_model::SlideId(p.alloc_id())) } else { None };
if let Some(s) = p.slides.get_mut(i) {
let s = Arc::make_mut(s);
if needs_layout {
@@ -150,8 +152,8 @@ pub fn repair(p: &mut Presentation) {
pub fn outline_to_slides(p: &mut Presentation, text: &str) -> usize {
let Some(lay) = defaults::layout_of_kind(p, LayoutType::TitleAndContent) else { return 0 };
let mut made = 0;
- let mut cur: Option = None;
- let flush = |p: &mut Presentation, s: Option, made: &mut usize| {
+ let mut cur: Option = None;
+ let flush = |p: &mut Presentation, s: Option, made: &mut usize| {
if let Some(s) = s {
p.slides.push(Arc::new(s));
*made += 1;
@@ -242,7 +244,7 @@ mod tests {
#[test]
fn repair_fixes_missing_layout_and_size() {
let mut p = Presentation::default();
- Arc::make_mut(&mut p.slides[0]).layout = slidecraft_model::LayoutId(99999);
+ Arc::make_mut(&mut p.slides[0]).layout = deckcraft_model::LayoutId(99999);
p.slide_size.width = f64::NAN;
repair(&mut p);
assert!(p.validate().is_empty());
diff --git a/crates/geom/Cargo.toml b/crates/geom/Cargo.toml
index e261479..b648d9e 100644
--- a/crates/geom/Cargo.toml
+++ b/crates/geom/Cargo.toml
@@ -1,9 +1,9 @@
[package]
-name = "slidecraft-geom"
+name = "deckcraft-geom"
version.workspace = true
edition.workspace = true
license.workspace = true
-description = "SlideCraft geometry: units (EMU/pt), transforms, preset shape generators, adjust handles, hit testing"
+description = "DeckCraft geometry: units (EMU/pt), transforms, preset shape generators, adjust handles, hit testing"
[lints]
workspace = true
diff --git a/crates/geom/src/lib.rs b/crates/geom/src/lib.rs
index 2a0e7d1..7a245d8 100644
--- a/crates/geom/src/lib.rs
+++ b/crates/geom/src/lib.rs
@@ -1,4 +1,4 @@
-//! SlideCraft geometry: units, shape transforms and preset shape generators.
+//! DeckCraft geometry: units, shape transforms and preset shape generators.
//!
//! The document model keeps positions in points (1/72 inch) as `f64`. Office Open XML stores EMU
//! (English Metric Units, 12 700 per point); [`emu_to_pt`] and [`pt_to_emu`] convert losslessly for
diff --git a/crates/h264/Cargo.toml b/crates/h264/Cargo.toml
index 18ddb56..bb037ab 100644
--- a/crates/h264/Cargo.toml
+++ b/crates/h264/Cargo.toml
@@ -1,5 +1,5 @@
[package]
-name = "slidecraft-h264"
+name = "deckcraft-h264"
description = "Clean-room pure-Rust H.264/AVC decoder (ITU-T H.264)"
version.workspace = true
edition.workspace = true
@@ -14,6 +14,6 @@ default = ["threads"]
threads = ["dep:rayon"]
[dependencies]
-slidecraft-bitstream = { workspace = true }
+deckcraft-bitstream = { workspace = true }
thiserror = { workspace = true }
rayon = { workspace = true, optional = true }
diff --git a/crates/h264/src/cavlc.rs b/crates/h264/src/cavlc.rs
index f6aa085..9711d25 100644
--- a/crates/h264/src/cavlc.rs
+++ b/crates/h264/src/cavlc.rs
@@ -2,7 +2,7 @@
use crate::cavlc_tables::*;
use crate::error::{Result, ensure};
-use slidecraft_bitstream::BitReader;
+use deckcraft_bitstream::BitReader;
use std::sync::OnceLock;
/// Prefix-code lookup keyed by (number of leading zeros, next 5 bits after the first one bit).
@@ -182,7 +182,7 @@ pub(crate) fn residual_block(r: &mut BitReader, coeff: &mut [i32], start_idx: us
#[cfg(test)]
mod tests {
use super::*;
- use slidecraft_bitstream::BitWriter;
+ use deckcraft_bitstream::BitWriter;
fn kraft(codes: &[&str]) -> f64 {
codes.iter().filter(|c| !c.is_empty()).map(|c| 0.5f64.powi(c.len() as i32)).sum()
diff --git a/crates/h264/src/deblock.rs b/crates/h264/src/deblock.rs
index fb918ff..ded8396 100644
--- a/crates/h264/src/deblock.rs
+++ b/crates/h264/src/deblock.rs
@@ -480,7 +480,7 @@ mod tests {
mod speed {
use super::*;
- /// Rough timing of the edge filters (`cargo test --release -p slidecraft-h264 --lib edge_filter_speed -- --ignored --nocapture`).
+ /// Rough timing of the edge filters (`cargo test --release -p deckcraft-h264 --lib edge_filter_speed -- --ignored --nocapture`).
#[test]
#[ignore]
fn edge_filter_speed() {
diff --git a/crates/h264/src/decoder.rs b/crates/h264/src/decoder.rs
index 3178c64..5f8941e 100644
--- a/crates/h264/src/decoder.rs
+++ b/crates/h264/src/decoder.rs
@@ -14,7 +14,7 @@ use crate::slice::{NalHeader, Poc, PocState, SliceHeader, SliceType, nal_type};
use crate::slicedec::{PicState, SliceDecoder};
use crate::transform::LevelScale;
use crate::{ColorInfo, Picture};
-use slidecraft_bitstream::{BitReader, annexb_nals, length_prefixed_nals, unescape_rbsp};
+use deckcraft_bitstream::{BitReader, annexb_nals, length_prefixed_nals, unescape_rbsp};
use std::collections::VecDeque;
use std::sync::{Arc, Mutex};
diff --git a/crates/h264/src/error.rs b/crates/h264/src/error.rs
index f7a37c6..7c996b9 100644
--- a/crates/h264/src/error.rs
+++ b/crates/h264/src/error.rs
@@ -1,4 +1,4 @@
-use slidecraft_bitstream::BitError;
+use deckcraft_bitstream::BitError;
/// Errors produced by the decoder.
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
diff --git a/crates/h264/src/lib.rs b/crates/h264/src/lib.rs
index 0173e9d..a65a18e 100644
--- a/crates/h264/src/lib.rs
+++ b/crates/h264/src/lib.rs
@@ -5,7 +5,7 @@
//! multiple slices, long-term references and all MMCOs, deblocking.
//!
//! ```no_run
-//! let mut dec = slidecraft_h264::Decoder::new();
+//! let mut dec = deckcraft_h264::Decoder::new();
//! let stream = std::fs::read("video.h264").unwrap();
//! for pic in dec.decode(&stream, 0).unwrap() {
//! println!("{}x{} poc {}", pic.width, pic.height, pic.poc);
diff --git a/crates/h264/src/params.rs b/crates/h264/src/params.rs
index bc5db10..f6a3a9e 100644
--- a/crates/h264/src/params.rs
+++ b/crates/h264/src/params.rs
@@ -2,7 +2,7 @@
use crate::error::{Result, ensure, unsupported};
use crate::tables::*;
-use slidecraft_bitstream::BitReader;
+use deckcraft_bitstream::BitReader;
/// Scaling matrices after applying fall-back rules, stored in raster order (weightScale).
#[derive(Clone, Debug, PartialEq, Eq)]
@@ -665,7 +665,7 @@ impl Pps {
#[cfg(test)]
mod tests {
use super::*;
- use slidecraft_bitstream::BitWriter;
+ use deckcraft_bitstream::BitWriter;
#[test]
fn scaling_list_default_signal() {
diff --git a/crates/h264/src/slice.rs b/crates/h264/src/slice.rs
index 357d89f..c80cf0e 100644
--- a/crates/h264/src/slice.rs
+++ b/crates/h264/src/slice.rs
@@ -2,7 +2,7 @@
use crate::error::{Result, ensure};
use crate::params::{Pps, Sps};
-use slidecraft_bitstream::BitReader;
+use deckcraft_bitstream::BitReader;
/// NAL unit types used by the decoder.
pub mod nal_type {
diff --git a/crates/h264/src/slicedec.rs b/crates/h264/src/slicedec.rs
index 16bba04..10132d6 100644
--- a/crates/h264/src/slicedec.rs
+++ b/crates/h264/src/slicedec.rs
@@ -11,7 +11,7 @@ use crate::picture::*;
use crate::slice::{SliceHeader, SliceType};
use crate::tables::*;
use crate::transform::{self, LevelScale};
-use slidecraft_bitstream::BitReader;
+use deckcraft_bitstream::BitReader;
/// Per-slice data kept with the picture (deblocking, motion field export).
#[derive(Clone, Debug, Default)]
diff --git a/crates/h264/src/synth_tests.rs b/crates/h264/src/synth_tests.rs
index fba3cd5..b0e0d5d 100644
--- a/crates/h264/src/synth_tests.rs
+++ b/crates/h264/src/synth_tests.rs
@@ -6,7 +6,7 @@
use crate::Decoder;
use crate::cabac::{Cabac, NEXT_STATE, RANGE_TAB_LPS};
-use slidecraft_bitstream::{BitWriter, escape_rbsp};
+use deckcraft_bitstream::{BitWriter, escape_rbsp};
const MB_W: usize = 4;
const MB_H: usize = 3;
diff --git a/crates/hevc/Cargo.toml b/crates/hevc/Cargo.toml
index cf008d4..b9bcdfa 100644
--- a/crates/hevc/Cargo.toml
+++ b/crates/hevc/Cargo.toml
@@ -1,5 +1,5 @@
[package]
-name = "slidecraft-hevc"
+name = "deckcraft-hevc"
description = "Clean-room pure-Rust H.265/HEVC decoder (ITU-T H.265)"
version.workspace = true
edition.workspace = true
@@ -14,6 +14,6 @@ default = ["threads"]
threads = ["dep:rayon"]
[dependencies]
-slidecraft-bitstream = { workspace = true }
+deckcraft-bitstream = { workspace = true }
thiserror = { workspace = true }
rayon = { workspace = true, optional = true }
diff --git a/crates/hevc/src/decoder.rs b/crates/hevc/src/decoder.rs
index eb0a302..cd5450f 100644
--- a/crates/hevc/src/decoder.rs
+++ b/crates/hevc/src/decoder.rs
@@ -12,7 +12,7 @@ use crate::picture::{Frame, FrameRef};
use crate::slice::{NalHeader, SliceHeader, nal_type};
use crate::slicedec::{F_INTRA, F_SKIP, PicState, SliceDecoder, SliceJob};
use crate::{ColorInfo, Picture, Plane};
-use slidecraft_bitstream::{annexb_nals, length_prefixed_nals, unescape_rbsp};
+use deckcraft_bitstream::{annexb_nals, length_prefixed_nals, unescape_rbsp};
use std::collections::VecDeque;
use std::sync::{Arc, Mutex};
diff --git a/crates/hevc/src/error.rs b/crates/hevc/src/error.rs
index f7a37c6..7c996b9 100644
--- a/crates/hevc/src/error.rs
+++ b/crates/hevc/src/error.rs
@@ -1,4 +1,4 @@
-use slidecraft_bitstream::BitError;
+use deckcraft_bitstream::BitError;
/// Errors produced by the decoder.
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
diff --git a/crates/hevc/src/lib.rs b/crates/hevc/src/lib.rs
index 26c5658..19c09a2 100644
--- a/crates/hevc/src/lib.rs
+++ b/crates/hevc/src/lib.rs
@@ -7,7 +7,7 @@
//! (parsing), dependent slice segments, short- and long-term reference picture sets.
//!
//! ```no_run
-//! let mut dec = slidecraft_hevc::Decoder::new();
+//! let mut dec = deckcraft_hevc::Decoder::new();
//! let stream = std::fs::read("video.h265").unwrap();
//! for pic in dec.decode(&stream, 0).unwrap() {
//! println!("{}x{} poc {}", pic.width, pic.height, pic.poc);
diff --git a/crates/hevc/src/params.rs b/crates/hevc/src/params.rs
index 7a3d1b7..fbcff55 100644
--- a/crates/hevc/src/params.rs
+++ b/crates/hevc/src/params.rs
@@ -4,7 +4,7 @@
use crate::error::{Result, ensure, unsupported};
use crate::spec_tables::{DEFAULT_SCALING_INTER, DEFAULT_SCALING_INTRA};
use crate::tables::scan_diag;
-use slidecraft_bitstream::BitReader;
+use deckcraft_bitstream::BitReader;
/// General profile / tier / level information.
#[derive(Clone, Debug, Default, PartialEq, Eq)]
diff --git a/crates/hevc/src/slice.rs b/crates/hevc/src/slice.rs
index 976b1ba..6b4c7ae 100644
--- a/crates/hevc/src/slice.rs
+++ b/crates/hevc/src/slice.rs
@@ -2,7 +2,7 @@
use crate::error::{Result, ensure, invalid};
use crate::params::{Pps, Sps, StRps};
-use slidecraft_bitstream::BitReader;
+use deckcraft_bitstream::BitReader;
pub mod nal_type {
pub const TRAIL_N: u8 = 0;
diff --git a/crates/hevc/src/slicedec.rs b/crates/hevc/src/slicedec.rs
index f52910b..db5b46d 100644
--- a/crates/hevc/src/slicedec.rs
+++ b/crates/hevc/src/slicedec.rs
@@ -799,7 +799,7 @@ impl<'a> SliceDecoder<'a> {
let (bl, bc) = (self.sps.pcm_bit_depth_luma, self.sps.pcm_bit_depth_chroma);
let total_bits = n * n * bl as usize + 2 * (n / 2) * (n / 2) * bc as usize;
ensure!(start + total_bits.div_ceil(8) <= data.len(), "PCM samples truncated");
- let mut r = slidecraft_bitstream::BitReader::new(&data[start..]);
+ let mut r = deckcraft_bitstream::BitReader::new(&data[start..]);
let (sy, sc) = (self.pic.bd_y - bl, self.pic.bd_c - bc);
let w = self.pic.width;
for y in 0..n {
diff --git a/crates/hevc/src/synth_tests.rs b/crates/hevc/src/synth_tests.rs
index 892b028..2d5b9fc 100644
--- a/crates/hevc/src/synth_tests.rs
+++ b/crates/hevc/src/synth_tests.rs
@@ -8,7 +8,7 @@
use crate::Decoder;
use crate::cabac::{Contexts, NEXT_STATE, RANGE_TAB_LPS, init_contexts};
use crate::spec_tables::*;
-use slidecraft_bitstream::{BitWriter, escape_rbsp};
+use deckcraft_bitstream::{BitWriter, escape_rbsp};
const W: usize = 96;
const H: usize = 64;
diff --git a/crates/isobmff/Cargo.toml b/crates/isobmff/Cargo.toml
index eb2dc56..04c339c 100644
--- a/crates/isobmff/Cargo.toml
+++ b/crates/isobmff/Cargo.toml
@@ -1,5 +1,5 @@
[package]
-name = "slidecraft-isobmff"
+name = "deckcraft-isobmff"
description = "Clean-room ISO BMFF (MP4) and QuickTime (MOV) demuxer and muxer"
version.workspace = true
edition.workspace = true
@@ -10,5 +10,5 @@ rust-version.workspace = true
workspace = true
[dependencies]
-slidecraft-bitstream = { workspace = true }
+deckcraft-bitstream = { workspace = true }
thiserror = { workspace = true }
diff --git a/crates/isobmff/src/codec.rs b/crates/isobmff/src/codec.rs
index d78a3c8..4f13294 100644
--- a/crates/isobmff/src/codec.rs
+++ b/crates/isobmff/src/codec.rs
@@ -2,7 +2,7 @@
use crate::bytes::{BoxBuf, Cur, FourCc, boxes};
use crate::error::{Error, Result};
-use slidecraft_bitstream::BitReader;
+use deckcraft_bitstream::BitReader;
/// One sample description (an entry of `stsd`).
#[derive(Clone, Debug, PartialEq)]
diff --git a/crates/matroska/Cargo.toml b/crates/matroska/Cargo.toml
index 258a21a..48d2c23 100644
--- a/crates/matroska/Cargo.toml
+++ b/crates/matroska/Cargo.toml
@@ -1,5 +1,5 @@
[package]
-name = "slidecraft-matroska"
+name = "deckcraft-matroska"
description = "Clean-room Matroska (MKV) and WebM demuxer and muxer"
version.workspace = true
edition.workspace = true
diff --git a/crates/matroska/src/lib.rs b/crates/matroska/src/lib.rs
index 7f2604c..0f7deb1 100644
--- a/crates/matroska/src/lib.rs
+++ b/crates/matroska/src/lib.rs
@@ -3,7 +3,7 @@
//!
//! - [`open`] / [`open_with`] parse the EBML header and the first Segment (Info, Tracks, SeekHead,
//! Cues, Chapters, Tags, Attachments) and, by default, scan every Cluster's block headers to build
-//! per-track [`Sample`] tables — the same shape as `slidecraft-isobmff`, so callers can treat both
+//! per-track [`Sample`] tables — the same shape as `deckcraft-isobmff`, so callers can treat both
//! containers alike. Frame data is read on demand with [`MkvFile::read_sample`].
//! - [`Demuxer`] iterates [`Packet`]s in file order (from any [`ByteSource`], `Read + Seek`, or a
//! byte slice) and seeks to the keyframe preceding a time, via the index, Cues, or a cluster scan.
diff --git a/crates/matroska/src/mux.rs b/crates/matroska/src/mux.rs
index d64443c..462c155 100644
--- a/crates/matroska/src/mux.rs
+++ b/crates/matroska/src/mux.rs
@@ -78,7 +78,7 @@ impl Default for MuxOptions {
lacing: LacingMode::None,
max_lace_frames: 8,
title: None,
- writing_app: "slidecraft".into(),
+ writing_app: "deckcraft".into(),
}
}
}
@@ -166,7 +166,7 @@ impl MkvWriter {
let info_pos = base + out.len() as u64;
let mut info = Vec::new();
el_uint(&mut info, TIMESTAMP_SCALE, opts.timestamp_scale);
- el_str(&mut info, MUXING_APP, "slidecraft-matroska");
+ el_str(&mut info, MUXING_APP, "deckcraft-matroska");
el_str(&mut info, WRITING_APP, &opts.writing_app);
if let Some(t) = &opts.title {
el_str(&mut info, TITLE, t);
diff --git a/crates/matroska/src/source.rs b/crates/matroska/src/source.rs
index e6f056f..41aabe7 100644
--- a/crates/matroska/src/source.rs
+++ b/crates/matroska/src/source.rs
@@ -6,7 +6,7 @@ use std::sync::Arc;
/// Random-access, read-only byte source (file, in-memory buffer, web Blob…).
///
-/// Same shape as `slidecraft_isobmff::ByteSource`, so one adapter can serve both demuxers.
+/// Same shape as `deckcraft_isobmff::ByteSource`, so one adapter can serve both demuxers.
pub trait ByteSource {
/// Total length in bytes.
fn len(&self) -> u64;
diff --git a/crates/mcp/Cargo.toml b/crates/mcp/Cargo.toml
index 109669b..ab2efad 100644
--- a/crates/mcp/Cargo.toml
+++ b/crates/mcp/Cargo.toml
@@ -1,15 +1,15 @@
[package]
-name = "slidecraft-mcp"
+name = "deckcraft-mcp"
version.workspace = true
edition.workspace = true
license.workspace = true
-description = "SlideCraft MCP server: JSON-RPC 2.0 over stdio, driving a running app or a headless session"
+description = "DeckCraft MCP server: JSON-RPC 2.0 over stdio, driving a running app or a headless session"
[lints]
workspace = true
[dependencies]
-slidecraft-engine = { workspace = true }
-slidecraft-render = { workspace = true }
+deckcraft-engine = { workspace = true }
+deckcraft-render = { workspace = true }
serde_json = { workspace = true }
log = { workspace = true }
diff --git a/crates/mcp/src/backend.rs b/crates/mcp/src/backend.rs
index 8609e94..f368654 100644
--- a/crates/mcp/src/backend.rs
+++ b/crates/mcp/src/backend.rs
@@ -7,7 +7,7 @@ use std::time::Duration;
use serde_json::{Value, json};
/// Something that answers control-channel methods (`engine.execute`, `document.inspect`,
-/// `ui.pointer`, `ui.render`, …). See `slidecraft_ui_egui::control` for the full list.
+/// `ui.pointer`, `ui.render`, …). See `deckcraft_ui_egui::control` for the full list.
pub trait Backend {
/// Call one method. `Ok` carries the `result`, `Err` the error message.
fn call(&mut self, method: &str, params: Value) -> Result;
@@ -17,7 +17,7 @@ pub trait Backend {
fn describe(&self) -> String;
}
-/// A running SlideCraft app, reached through its loopback control port.
+/// A running DeckCraft app, reached through its loopback control port.
pub struct Remote {
addr: String,
conn: Option<(BufReader, TcpStream)>,
@@ -85,7 +85,7 @@ impl Backend for Remote {
self.conn = None;
self.roundtrip(&line).map_err(|e| {
self.conn = None;
- format!("SlideCraft app at {} is not reachable: {e}", self.addr)
+ format!("DeckCraft app at {} is not reachable: {e}", self.addr)
})?
}
};
@@ -102,6 +102,6 @@ impl Backend for Remote {
}
fn describe(&self) -> String {
- format!("connected to the SlideCraft app at {}", self.addr)
+ format!("connected to the DeckCraft app at {}", self.addr)
}
}
diff --git a/crates/mcp/src/headless.rs b/crates/mcp/src/headless.rs
index f15c319..512e7cc 100644
--- a/crates/mcp/src/headless.rs
+++ b/crates/mcp/src/headless.rs
@@ -1,7 +1,7 @@
//! An in-process engine session that answers the engine-level control-channel methods itself.
+use deckcraft_engine::{Mods, PointerEvent, PointerKind, Session, ToolKind};
use serde_json::{Value, json};
-use slidecraft_engine::{Mods, PointerEvent, PointerKind, Session, ToolKind};
use crate::backend::Backend;
@@ -17,8 +17,8 @@ impl Default for Headless {
}
/// Hint appended to errors for methods that need a window.
-pub(crate) const NEEDS_APP: &str = "it needs the desktop app: start `slidecraft --control 7979` and run the MCP server \
-with `slidecraft-cli mcp --connect 7979`";
+pub(crate) const NEEDS_APP: &str = "it needs the desktop app: start `deckcraft --control 7979` and run the MCP server \
+with `deckcraft-cli mcp --connect 7979`";
fn s<'a>(p: &'a Value, k: &str) -> Option<&'a str> {
p.get(k).and_then(Value::as_str)
@@ -47,11 +47,11 @@ pub fn tool_kind(name: &str, preset: Option<&str>) -> Option {
"select" | "selection" | "arrow" => ToolKind::Select,
"textBox" | "text" | "textbox" => ToolKind::TextBox,
"shape" => ToolKind::Shape { preset: preset.unwrap_or("rect").to_string() },
- "pen" => ToolKind::Ink { mode: "pen".into(), color: slidecraft_engine::model::Rgba::BLACK, width: 2.0 },
- "highlighter" => ToolKind::Ink { mode: "highlighter".into(), color: slidecraft_engine::model::Rgba::rgb(255, 230, 0), width: 10.0 },
- "eraser" => ToolKind::Ink { mode: "eraser".into(), color: slidecraft_engine::model::Rgba::BLACK, width: 2.0 },
- other if slidecraft_engine::geom::preset::info(other).is_some() => ToolKind::Shape { preset: other.to_string() },
- other if slidecraft_engine::tools::is_freeform_tool(other) => ToolKind::Shape { preset: other.to_string() },
+ "pen" => ToolKind::Ink { mode: "pen".into(), color: deckcraft_engine::model::Rgba::BLACK, width: 2.0 },
+ "highlighter" => ToolKind::Ink { mode: "highlighter".into(), color: deckcraft_engine::model::Rgba::rgb(255, 230, 0), width: 10.0 },
+ "eraser" => ToolKind::Ink { mode: "eraser".into(), color: deckcraft_engine::model::Rgba::BLACK, width: 2.0 },
+ other if deckcraft_engine::geom::preset::info(other).is_some() => ToolKind::Shape { preset: other.to_string() },
+ other if deckcraft_engine::tools::is_freeform_tool(other) => ToolKind::Shape { preset: other.to_string() },
_ => return None,
})
}
@@ -69,7 +69,7 @@ impl Headless {
fn exec(&mut self, id: &str, params: &Value) -> Result {
let r = self.session.execute(id, params).map_err(|e| {
let ui_only = ["app.", "view.", "window.", "ui."].iter().any(|p| id.starts_with(p));
- if ui_only && slidecraft_engine::find_command(id).is_none() { format!("`{id}` is a UI command; {NEEDS_APP}") } else { e.to_string() }
+ if ui_only && deckcraft_engine::find_command(id).is_none() { format!("`{id}` is a UI command; {NEEDS_APP}") } else { e.to_string() }
});
self.session.ui_requests.clear();
r
@@ -101,17 +101,17 @@ impl Headless {
return Err(format!("no slide {i}"));
}
let scale = p.get("scale").and_then(Value::as_f64).unwrap_or(1.0).clamp(0.05, 8.0);
- let img = slidecraft_engine::render::render_slide(
+ let img = deckcraft_engine::render::render_slide(
&st.doc,
i,
- &slidecraft_engine::render::RenderOpts { scale, edit: p.get("edit").and_then(Value::as_bool).unwrap_or(false), ..Default::default() },
+ &deckcraft_engine::render::RenderOpts { scale, edit: p.get("edit").and_then(Value::as_bool).unwrap_or(false), ..Default::default() },
);
let png = img.to_png();
if let Some(path) = s(p, "path") {
std::fs::write(path, &png).map_err(|e| format!("{path}: {e}"))?;
return Ok(json!({"path": path, "width": img.width, "height": img.height}));
}
- Ok(json!({"png": slidecraft_engine::cmd::base64_encode(&png), "width": img.width, "height": img.height, "slide": i}))
+ Ok(json!({"png": deckcraft_engine::cmd::base64_encode(&png), "width": img.width, "height": img.height, "slide": i}))
}
}
diff --git a/crates/mcp/src/lib.rs b/crates/mcp/src/lib.rs
index 08123f8..b51ff31 100644
--- a/crates/mcp/src/lib.rs
+++ b/crates/mcp/src/lib.rs
@@ -1,14 +1,14 @@
-//! SlideCraft's MCP server.
+//! DeckCraft's MCP server.
//!
//! [Model Context Protocol](https://modelcontextprotocol.io) over stdio: newline-delimited
-//! JSON-RPC 2.0, hand-written (no async runtime). The server exposes SlideCraft as a set of MCP
+//! JSON-RPC 2.0, hand-written (no async runtime). The server exposes DeckCraft as a set of MCP
//! tools and resources and forwards everything to a [`Backend`]:
//!
//! - [`Remote`] talks to a running desktop app through its loopback JSON-lines control channel
-//! (`slidecraft --control 7979`): one `{"id","method","params"}` line in, one
+//! (`deckcraft --control 7979`): one `{"id","method","params"}` line in, one
//! `{"id","ok","result"|"error"}` line out (see `docs/control-protocol.md`).
-//! - [`Headless`] hosts an in-process [`slidecraft_engine::Session`] and implements the
-//! engine-level control-channel methods itself (rendering pages with `slidecraft-render`), so
+//! - [`Headless`] hosts an in-process [`deckcraft_engine::Session`] and implements the
+//! engine-level control-channel methods itself (rendering pages with `deckcraft-render`), so
//! agents can build layouts and look at them without a window.
//!
//! Entry points: [`Server::serve`] (stdio loop) and [`Server::handle_line`] (one message).
diff --git a/crates/mcp/src/server.rs b/crates/mcp/src/server.rs
index 1e231ff..576e0b4 100644
--- a/crates/mcp/src/server.rs
+++ b/crates/mcp/src/server.rs
@@ -18,17 +18,17 @@ const INVALID_PARAMS: i64 = -32602;
const INTERNAL_ERROR: i64 = -32603;
const RESOURCE_NOT_FOUND: i64 = -32002;
-const INSTRUCTIONS: &str = "SlideCraft is a presentation app (a PowerPoint clone). Coordinates are points on the \
+const INSTRUCTIONS: &str = "DeckCraft is a presentation app (a PowerPoint clone). Coordinates are points on the \
current slide (y down; the default 16:9 slide is 960×540). Every action is a command: find ids and parameters with \
list_commands and run them with run_command (or several with batch). Typical flow: new_presentation → add_slide \
{layout:\"titleAndContent\", title, body} → add_shape / add_text_box / insert_picture / insert_chart → \
inspect_slide to read back ids, boxes and text → render_slide to look at the result. Shapes are selected with \
run_command edit.select {ids:[…]}; formatting commands (format.*, shape.fill, shape.line…) act on the selection or \
-on `ids`. Save with save_presentation (.slidecraft or .pptx).";
+on `ids`. Save with save_presentation (.deckcraft or .pptx).";
/// Resource URIs.
-pub const DOC_URI: &str = "slidecraft://document";
-pub const COMMANDS_URI: &str = "slidecraft://commands";
+pub const DOC_URI: &str = "deckcraft://document";
+pub const COMMANDS_URI: &str = "deckcraft://commands";
/// An MCP server bound to one backend.
pub struct Server {
@@ -135,13 +135,13 @@ impl Server {
Ok(json!({
"protocolVersion": version,
"capabilities": {"tools": {}, "resources": {}},
- "serverInfo": {"name": "slidecraft", "title": "SlideCraft", "version": env!("CARGO_PKG_VERSION"), "websiteUrl": slidecraft_engine::links::APP_PAGE},
+ "serverInfo": {"name": "deckcraft", "title": "DeckCraft", "version": env!("CARGO_PKG_VERSION"), "websiteUrl": deckcraft_engine::links::APP_PAGE},
"instructions": format!(
"{INSTRUCTIONS} Backend: {}. Community: {} (Discord), {} (app page), {} (source). The `app.links` command returns these links.",
self.backend.describe(),
- slidecraft_engine::links::DISCORD,
- slidecraft_engine::links::APP_PAGE,
- slidecraft_engine::links::GITHUB
+ deckcraft_engine::links::DISCORD,
+ deckcraft_engine::links::APP_PAGE,
+ deckcraft_engine::links::GITHUB
),
}))
}
diff --git a/crates/mcp/src/tests.rs b/crates/mcp/src/tests.rs
index 806fff7..e5a7ef2 100644
--- a/crates/mcp/src/tests.rs
+++ b/crates/mcp/src/tests.rs
@@ -23,7 +23,7 @@ fn tool(s: &mut Server, name: &str, args: Value) -> Value {
fn handshake_and_tool_list() {
let mut s = Server::new(Box::new(Headless::new()));
let r = call(&mut s, 1, "initialize", json!({"protocolVersion": "2025-06-18"}));
- assert_eq!(r["result"]["serverInfo"]["name"], "slidecraft");
+ assert_eq!(r["result"]["serverInfo"]["name"], "deckcraft");
let tools = call(&mut s, 2, "tools/list", json!({}));
let names: Vec<&str> = tools["result"]["tools"].as_array().unwrap().iter().map(|t| t["name"].as_str().unwrap()).collect();
for n in ["run_command", "render_slide", "add_slide", "add_shape", "pointer", "inspect_slide"] {
diff --git a/crates/mcp/src/tools.rs b/crates/mcp/src/tools.rs
index 9cc12ea..abc791b 100644
--- a/crates/mcp/src/tools.rs
+++ b/crates/mcp/src/tools.rs
@@ -60,7 +60,7 @@ fn tool(name: &str, title: &str, desc: &str, schema: Value, read_only: bool) ->
json!({"name": name, "title": title, "description": desc, "inputSchema": schema, "annotations": {"title": title, "readOnlyHint": read_only, "openWorldHint": false}})
}
-const APP_ONLY: &str = " Desktop app only (`slidecraft-cli mcp --connect PORT`).";
+const APP_ONLY: &str = " Desktop app only (`deckcraft-cli mcp --connect PORT`).";
pub fn tool_definitions() -> Vec {
vec![
@@ -94,21 +94,21 @@ pub fn tool_definitions() -> Vec {
tool(
"new_presentation",
"New presentation",
- "Start a new presentation (one title slide). Optional theme: SlideCraft, Harbor, Ember, Meadow, Nocturne, Paper, Coral Reef, Slate.",
+ "Start a new presentation (one title slide). Optional theme: DeckCraft, Harbor, Ember, Meadow, Nocturne, Paper, Coral Reef, Slate.",
obj(json!({"theme": string("Theme name"), "blank": boolean("No first slide")}), &[]),
false,
),
tool(
"open_presentation",
"Open presentation",
- "Open a .slidecraft or .pptx file by path.",
+ "Open a .deckcraft or .pptx file by path.",
obj(json!({"path": string("File path")}), &["path"]),
false,
),
tool(
"save_presentation",
"Save presentation",
- "Save the active presentation (.slidecraft or .pptx by extension).",
+ "Save the active presentation (.deckcraft or .pptx by extension).",
obj(json!({"path": string("File path (optional when it was opened from a file)")}), &[]),
false,
),
@@ -117,7 +117,7 @@ pub fn tool_definitions() -> Vec {
"Export",
"Export slides: png/jpeg (one slide, or all with all:true), pptx, outline text.",
obj(
- json!({"path": string("Output path"), "format": string("png|jpeg|pptx|slidecraft|outline"), "slide": int("Slide index for images"), "all": boolean("All slides (images)"), "scale": num("Pixels per point for images (default 2)")}),
+ json!({"path": string("Output path"), "format": string("png|jpeg|pptx|deckcraft|outline"), "slide": int("Slide index for images"), "all": boolean("All slides (images)"), "scale": num("Pixels per point for images (default 2)")}),
&["path"],
),
false,
@@ -255,7 +255,7 @@ pub fn tool_definitions() -> Vec {
obj(json!({"text": string("Text")}), &["text"]),
false,
),
- tool("screenshot", "Screenshot", &format!("Screenshot of the whole SlideCraft window (UI included).{APP_ONLY}"), obj(json!({}), &[]), true),
+ tool("screenshot", "Screenshot", &format!("Screenshot of the whole DeckCraft window (UI included).{APP_ONLY}"), obj(json!({}), &[]), true),
tool(
"call_app",
"Call app method",
diff --git a/crates/media/Cargo.toml b/crates/media/Cargo.toml
index 0b57d48..e785f36 100644
--- a/crates/media/Cargo.toml
+++ b/crates/media/Cargo.toml
@@ -1,22 +1,22 @@
[package]
-name = "slidecraft-media"
+name = "deckcraft-media"
version.workspace = true
edition.workspace = true
license.workspace = true
-description = "SlideCraft media: probing, audio decoding (MP3, AAC/M4A, WAV, AIFF, FLAC, ALAC, Vorbis, Opus) to f32 PCM, H.264/HEVC/VP9/AV1 video decoding, poster frames, trim/fade/volume and the playback mixer and clock"
+description = "DeckCraft media: probing, audio decoding (MP3, AAC/M4A, WAV, AIFF, FLAC, ALAC, Vorbis, Opus) to f32 PCM, H.264/HEVC/VP9/AV1 video decoding, poster frames, trim/fade/volume and the playback mixer and clock"
[lints]
workspace = true
[dependencies]
-slidecraft-isobmff = { workspace = true }
-slidecraft-matroska = { workspace = true }
-slidecraft-ogg = { workspace = true }
-slidecraft-opus = { workspace = true }
-slidecraft-h264 = { workspace = true }
-slidecraft-hevc = { workspace = true }
-slidecraft-vp9 = { workspace = true }
-slidecraft-av1 = { workspace = true }
+deckcraft-isobmff = { workspace = true }
+deckcraft-matroska = { workspace = true }
+deckcraft-ogg = { workspace = true }
+deckcraft-opus = { workspace = true }
+deckcraft-h264 = { workspace = true }
+deckcraft-hevc = { workspace = true }
+deckcraft-vp9 = { workspace = true }
+deckcraft-av1 = { workspace = true }
thiserror = { workspace = true }
log = { workspace = true }
image = { workspace = true }
diff --git a/crates/media/src/audio.rs b/crates/media/src/audio.rs
index 18c81c0..80ad66a 100644
--- a/crates/media/src/audio.rs
+++ b/crates/media/src/audio.rs
@@ -84,15 +84,15 @@ pub fn decode(bytes: &Bytes) -> Result {
/// Ogg Opus with our decoder; `None` when the file holds no Opus stream.
fn decode_ogg_opus(bytes: &Bytes) -> Option> {
let src: &[u8] = bytes;
- let file = slidecraft_ogg::open(src).ok()?;
- let s = file.stream_of(slidecraft_ogg::Codec::Opus)?;
+ let file = deckcraft_ogg::open(src).ok()?;
+ let s = file.stream_of(deckcraft_ogg::Codec::Opus)?;
let stream = file.streams.get(s)?;
Some((|| {
let head = stream.headers.first().ok_or(MediaError::Corrupt("Opus header missing".into()))?;
- let mut dec = slidecraft_opus::Decoder::new(head).map_err(|e| MediaError::Corrupt(e.to_string()))?;
+ let mut dec = deckcraft_opus::Decoder::new(head).map_err(|e| MediaError::Corrupt(e.to_string()))?;
dec.set_trim_pre_skip(true);
let channels = dec.channels() as u16;
- let timing = slidecraft_ogg::OpusTiming::of(stream, dec.pre_skip() as u32);
+ let timing = deckcraft_ogg::OpusTiming::of(stream, dec.pre_skip() as u32);
let mut out = Vec::new();
for i in 0..stream.packets.len() {
let Ok(p) = file.read_packet(src, s, i) else { continue };
@@ -113,13 +113,13 @@ fn decode_ogg_opus(bytes: &Bytes) -> Option> {
/// WebM / Matroska Opus with our decoder; `None` when there is no Opus track.
fn decode_mkv_opus(bytes: &Bytes) -> Option> {
let src: &[u8] = bytes;
- let file = slidecraft_matroska::open(src).ok()?;
+ let file = deckcraft_matroska::open(src).ok()?;
let (ti, head) = file.tracks.iter().enumerate().find_map(|(i, t)| match &t.codec {
- slidecraft_matroska::Codec::Opus { head } => Some((i, head.clone())),
+ deckcraft_matroska::Codec::Opus { head } => Some((i, head.clone())),
_ => None,
})?;
Some((|| {
- let mut dec = slidecraft_opus::Decoder::new(&head).map_err(|e| MediaError::Corrupt(e.to_string()))?;
+ let mut dec = deckcraft_opus::Decoder::new(&head).map_err(|e| MediaError::Corrupt(e.to_string()))?;
dec.set_trim_pre_skip(true);
let channels = dec.channels() as u16;
let mut out = Vec::new();
diff --git a/crates/media/src/feed.rs b/crates/media/src/feed.rs
index b430995..721be85 100644
--- a/crates/media/src/feed.rs
+++ b/crates/media/src/feed.rs
@@ -42,7 +42,7 @@ impl VideoFeed {
let (tx, cmd_rx) = std::sync::mpsc::channel::();
let (frame_tx, rx) = std::sync::mpsc::sync_channel::<(u64, Option )>(AHEAD);
let worker = move || worker(dec, cmd_rx, frame_tx);
- if let Err(e) = std::thread::Builder::new().name("slidecraft-video-decode".into()).spawn(worker) {
+ if let Err(e) = std::thread::Builder::new().name("deckcraft-video-decode".into()).spawn(worker) {
return Err(crate::MediaError::Corrupt(format!("couldn't start the decoder: {e}")));
}
Ok(VideoFeed { size, current: None, next: None, generation: 0, ended: false, tx, rx })
diff --git a/crates/media/src/lib.rs b/crates/media/src/lib.rs
index 91a9241..d31b5d6 100644
--- a/crates/media/src/lib.rs
+++ b/crates/media/src/lib.rs
@@ -1,4 +1,4 @@
-//! SlideCraft media: everything between embedded media bytes and the speakers / the screen.
+//! DeckCraft media: everything between embedded media bytes and the speakers / the screen.
//!
//! - [`probe`]: container, duration, audio channels / sample rate, video size / frame rate.
//! - [`audio::decode`]: every common audio format to interleaved f32 [`audio::Pcm`] — MP3, AAC
diff --git a/crates/media/src/player.rs b/crates/media/src/player.rs
index 9a07e6f..9ac4a80 100644
--- a/crates/media/src/player.rs
+++ b/crates/media/src/player.rs
@@ -226,7 +226,7 @@ impl Player {
let _ = s.set(crate::audio::decode(&b).map(Arc::new).map_err(|e| e.to_string()));
};
if crate::THREADS {
- if std::thread::Builder::new().name("slidecraft-audio-decode".into()).spawn(job).is_err() {
+ if std::thread::Builder::new().name("deckcraft-audio-decode".into()).spawn(job).is_err() {
let _ = slot.set(Err("couldn't start the decoder".into()));
}
} else {
diff --git a/crates/media/src/probe.rs b/crates/media/src/probe.rs
index 2b8ad5c..80a8a38 100644
--- a/crates/media/src/probe.rs
+++ b/crates/media/src/probe.rs
@@ -9,7 +9,7 @@ pub struct AudioInfo {
pub codec: String,
pub sample_rate: u32,
pub channels: u16,
- /// SlideCraft can decode it.
+ /// DeckCraft can decode it.
pub decodable: bool,
}
@@ -93,9 +93,9 @@ fn codec_label(short: &str) -> String {
}
fn probe_mp4(bytes: &Bytes) -> Result {
- use slidecraft_isobmff::{CodecConfig, TrackKind};
+ use deckcraft_isobmff::{CodecConfig, TrackKind};
let src: &[u8] = bytes;
- let f = slidecraft_isobmff::open(src).map_err(|e| MediaError::Corrupt(e.to_string()))?;
+ let f = deckcraft_isobmff::open(src).map_err(|e| MediaError::Corrupt(e.to_string()))?;
let mut info = MediaInfo { container: if f.is_quicktime { "mov" } else { "mp4" }, ..Default::default() };
let mut dur = if f.timescale > 0 { f.duration as f64 / f.timescale as f64 } else { 0.0 };
if let Some(ti) = f.track_of_kind(TrackKind::Video) {
@@ -133,9 +133,9 @@ fn probe_mp4(bytes: &Bytes) -> Result {
}
fn probe_mkv(bytes: &Bytes) -> Result {
- use slidecraft_matroska::{Codec, TrackKind};
+ use deckcraft_matroska::{Codec, TrackKind};
let src: &[u8] = bytes;
- let f = slidecraft_matroska::open(src).map_err(|e| MediaError::Corrupt(e.to_string()))?;
+ let f = deckcraft_matroska::open(src).map_err(|e| MediaError::Corrupt(e.to_string()))?;
let mut info = MediaInfo { container: if f.is_webm() { "webm" } else { "matroska" }, ..Default::default() };
let dur = f.duration_ns().map(|n| n as f64 / 1e9).unwrap_or(0.0);
info.duration_ms = ms(dur);
@@ -166,11 +166,11 @@ fn probe_mkv(bytes: &Bytes) -> Result {
fn probe_ogg(bytes: &Bytes) -> Option {
let src: &[u8] = bytes;
- let f = slidecraft_ogg::open(src).ok()?;
- let s = f.stream_of(slidecraft_ogg::Codec::Opus)?;
+ let f = deckcraft_ogg::open(src).ok()?;
+ let s = f.stream_of(deckcraft_ogg::Codec::Opus)?;
let st = f.streams.get(s)?;
- let head = slidecraft_opus::OpusHead::parse(st.headers.first()?).ok()?;
- let timing = slidecraft_ogg::OpusTiming::of(st, head.pre_skip as u32);
+ let head = deckcraft_opus::OpusHead::parse(st.headers.first()?).ok()?;
+ let timing = deckcraft_ogg::OpusTiming::of(st, head.pre_skip as u32);
Some(MediaInfo {
container: "ogg",
duration_ms: ms(timing.total as f64 / 48_000.0),
diff --git a/crates/media/src/video.rs b/crates/media/src/video.rs
index 882f5b3..bd40c44 100644
--- a/crates/media/src/video.rs
+++ b/crates/media/src/video.rs
@@ -3,8 +3,8 @@
use std::collections::VecDeque;
-use slidecraft_isobmff::CodecConfig;
-use slidecraft_matroska::Codec as MkvCodec;
+use deckcraft_isobmff::CodecConfig;
+use deckcraft_matroska::Codec as MkvCodec;
use crate::yuv::{self, Matrix, Planes};
use crate::{Bytes, Container, MediaError, Result};
@@ -41,15 +41,15 @@ fn dec_err(e: impl std::fmt::Display) -> MediaError {
struct H264 {
avcc: Vec,
- dec: slidecraft_h264::Decoder,
+ dec: deckcraft_h264::Decoder,
}
impl H264 {
fn new(avcc: Vec) -> Result {
- let dec = slidecraft_h264::Decoder::from_avcc(&avcc).map_err(dec_err)?;
+ let dec = deckcraft_h264::Decoder::from_avcc(&avcc).map_err(dec_err)?;
Ok(H264 { avcc, dec })
}
- fn convert(p: slidecraft_h264::Picture) -> Pic {
+ fn convert(p: deckcraft_h264::Picture) -> Pic {
let (w, h) = (p.width as usize, p.height as usize);
let planes = Planes {
y: &p.y,
@@ -77,7 +77,7 @@ impl Codec for H264 {
self.dec.flush().into_iter().map(H264::convert).collect()
}
fn reset(&mut self) {
- if let Ok(d) = slidecraft_h264::Decoder::from_avcc(&self.avcc) {
+ if let Ok(d) = deckcraft_h264::Decoder::from_avcc(&self.avcc) {
self.dec = d;
}
}
@@ -120,19 +120,19 @@ macro_rules! plane_pic {
struct Hevc {
hvcc: Vec,
- dec: slidecraft_hevc::Decoder,
+ dec: deckcraft_hevc::Decoder,
}
impl Hevc {
fn new(hvcc: Vec) -> Result {
- let dec = slidecraft_hevc::Decoder::from_hvcc(&hvcc).map_err(dec_err)?;
+ let dec = deckcraft_hevc::Decoder::from_hvcc(&hvcc).map_err(dec_err)?;
Ok(Hevc { hvcc, dec })
}
- fn convert(p: slidecraft_hevc::Picture) -> Pic {
+ fn convert(p: deckcraft_hevc::Picture) -> Pic {
let sx = u32::from(p.chroma_width < p.width);
let sy = u32::from(p.chroma_height < p.height);
let m = Matrix::from_code(p.color.matrix, p.height);
- plane_pic!(&p, slidecraft_hevc::Plane, sx, sy, m, p.color.full_range, p.chroma_width == 0)
+ plane_pic!(&p, deckcraft_hevc::Plane, sx, sy, m, p.color.full_range, p.chroma_width == 0)
}
}
@@ -144,23 +144,23 @@ impl Codec for Hevc {
self.dec.flush().into_iter().map(Hevc::convert).collect()
}
fn reset(&mut self) {
- if let Ok(d) = slidecraft_hevc::Decoder::from_hvcc(&self.hvcc) {
+ if let Ok(d) = deckcraft_hevc::Decoder::from_hvcc(&self.hvcc) {
self.dec = d;
}
}
}
struct Vp9 {
- dec: slidecraft_vp9::Decoder,
+ dec: deckcraft_vp9::Decoder,
}
impl Vp9 {
- fn convert(p: slidecraft_vp9::Picture) -> Pic {
+ fn convert(p: deckcraft_vp9::Picture) -> Pic {
let (w, h) = (p.width as usize, p.height as usize);
if p.color.color_space == 7 {
// RGB (4:4:4): the planes carry G, B, R.
let shift = p.bit_depth.saturating_sub(8);
- let at = |pl: &slidecraft_vp9::Plane, i: usize| if i < pl.len() { (pl.get(i) >> shift) as u8 } else { 0 };
+ let at = |pl: &deckcraft_vp9::Plane, i: usize| if i < pl.len() { (pl.get(i) >> shift) as u8 } else { 0 };
let mut rgba = Vec::with_capacity(w * h * 4);
for row in 0..h {
for col in 0..w {
@@ -178,7 +178,7 @@ impl Vp9 {
5 => Matrix::Bt2020,
_ => Matrix::from_code(2, p.height),
};
- plane_pic!(&p, slidecraft_vp9::Plane, u32::from(p.subsampling_x), u32::from(p.subsampling_y), m, p.color.full_range, false)
+ plane_pic!(&p, deckcraft_vp9::Plane, u32::from(p.subsampling_x), u32::from(p.subsampling_y), m, p.color.full_range, false)
}
}
@@ -195,13 +195,13 @@ impl Codec for Vp9 {
}
struct Av1 {
- dec: slidecraft_av1::Decoder,
+ dec: deckcraft_av1::Decoder,
config_obus: Vec,
primed: bool,
}
impl Av1 {
- fn convert(p: slidecraft_av1::Picture) -> Pic {
+ fn convert(p: deckcraft_av1::Picture) -> Pic {
let (w, h) = (p.width as usize, p.height as usize);
let cw = p.plane_width(1) as usize;
let rgba = yuv::to_rgba(
@@ -239,7 +239,7 @@ impl Codec for Av1 {
self.dec.flush().into_iter().map(Av1::convert).collect()
}
fn reset(&mut self) {
- self.dec = slidecraft_av1::Decoder::new();
+ self.dec = deckcraft_av1::Decoder::new();
self.primed = false;
}
}
@@ -257,8 +257,8 @@ struct Track {
}
enum Demux {
- Mp4(Box, usize),
- Mkv(Box, usize),
+ Mp4(Box, usize),
+ Mkv(Box, usize),
}
impl Demux {
@@ -273,15 +273,15 @@ impl Demux {
fn open_track(bytes: &[u8]) -> Result<(Demux, Track, Box)> {
match Container::sniff(bytes) {
Container::Mp4 => {
- let f = slidecraft_isobmff::open(bytes).map_err(dec_err)?;
- let ti = f.track_of_kind(slidecraft_isobmff::TrackKind::Video).ok_or(MediaError::Missing("video track"))?;
+ let f = deckcraft_isobmff::open(bytes).map_err(dec_err)?;
+ let ti = f.track_of_kind(deckcraft_isobmff::TrackKind::Video).ok_or(MediaError::Missing("video track"))?;
let t = &f.tracks[ti];
let entry = t.entries.first().ok_or(MediaError::Missing("video track"))?;
let codec: Box = match &entry.codec {
CodecConfig::Avc(a) => Box::new(H264::new(a.to_bytes())?),
CodecConfig::Hevc(c) => Box::new(Hevc::new(c.to_bytes())?),
- CodecConfig::Vp9(_) => Box::new(Vp9 { dec: slidecraft_vp9::Decoder::new() }),
- CodecConfig::Av1(c) => Box::new(Av1 { dec: slidecraft_av1::Decoder::new(), config_obus: c.config_obus.clone(), primed: false }),
+ CodecConfig::Vp9(_) => Box::new(Vp9 { dec: deckcraft_vp9::Decoder::new() }),
+ CodecConfig::Av1(c) => Box::new(Av1 { dec: deckcraft_av1::Decoder::new(), config_obus: c.config_obus.clone(), primed: false }),
other => return Err(MediaError::Unsupported(format!("{} video", codec_name(other)))),
};
let (w, h) = entry.video.as_ref().map(|v| (v.width as u32, v.height as u32)).unwrap_or((t.width, t.height));
@@ -290,20 +290,20 @@ fn open_track(bytes: &[u8]) -> Result<(Demux, Track, Box)> {
Ok((Demux::Mp4(Box::new(f), ti), track, codec))
}
Container::Matroska => {
- let f = slidecraft_matroska::open(bytes).map_err(dec_err)?;
+ let f = deckcraft_matroska::open(bytes).map_err(dec_err)?;
let ti = f
.tracks
.iter()
- .position(|t| t.kind == slidecraft_matroska::TrackKind::Video && !t.samples.is_empty())
+ .position(|t| t.kind == deckcraft_matroska::TrackKind::Video && !t.samples.is_empty())
.ok_or(MediaError::Missing("video track"))?;
let t = &f.tracks[ti];
let codec: Box = match &t.codec {
MkvCodec::Avc { avcc } => Box::new(H264::new(avcc.clone())?),
MkvCodec::Hevc { hvcc } => Box::new(Hevc::new(hvcc.clone())?),
- MkvCodec::Vp9 { .. } => Box::new(Vp9 { dec: slidecraft_vp9::Decoder::new() }),
+ MkvCodec::Vp9 { .. } => Box::new(Vp9 { dec: deckcraft_vp9::Decoder::new() }),
MkvCodec::Av1 { av1c } => Box::new(Av1 {
- dec: slidecraft_av1::Decoder::new(),
- config_obus: slidecraft_isobmff::Av1Config::parse(av1c).map(|c| c.config_obus).unwrap_or_default(),
+ dec: deckcraft_av1::Decoder::new(),
+ config_obus: deckcraft_isobmff::Av1Config::parse(av1c).map(|c| c.config_obus).unwrap_or_default(),
primed: false,
}),
other => return Err(MediaError::Unsupported(format!("{} video", mkv_codec_name(other)))),
diff --git a/crates/media/tests/decode.rs b/crates/media/tests/decode.rs
index e3a6be7..b317944 100644
--- a/crates/media/tests/decode.rs
+++ b/crates/media/tests/decode.rs
@@ -7,7 +7,7 @@
use std::sync::Arc;
use std::time::Duration;
-use slidecraft_media::{Bytes, Container, MediaError, audio, probe, video};
+use deckcraft_media::{Bytes, Container, MediaError, audio, probe, video};
fn fixture(name: &str) -> Bytes {
let p = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures").join(name);
@@ -299,7 +299,7 @@ fn av1_mkv() {
#[test]
fn video_feed_follows_the_clock() {
- let mut feed = slidecraft_media::VideoFeed::new(fixture("clip-h264.mov")).expect("feed");
+ let mut feed = deckcraft_media::VideoFeed::new(fixture("clip-h264.mov")).expect("feed");
assert_eq!(feed.size(), (64, 48));
let t = Duration::from_secs(5);
assert!(feed.frame_at_blocking(0.0, t).is_some_and(|f| f.time == 0.0));
@@ -314,7 +314,7 @@ fn video_feed_follows_the_clock() {
#[test]
fn player_plays_decoded_audio_through_the_mixer() {
- use slidecraft_media::{ClipParams, Player, VoiceSpec};
+ use deckcraft_media::{ClipParams, Player, VoiceSpec};
let b: Bytes = Arc::new(wav(48_000, 1, &sine(48_000, 0.5)));
let mut p = Player::new(None);
p.play(1, 1, &b, VoiceSpec { clip: ClipParams { volume: 0.5, ..Default::default() }, looping: false, duration: 0.5, audio: true }, None);
diff --git a/crates/media/tests/fixtures/make.sh b/crates/media/tests/fixtures/make.sh
index 463cffb..bf09b67 100755
--- a/crates/media/tests/fixtures/make.sh
+++ b/crates/media/tests/fixtures/make.sh
@@ -27,11 +27,11 @@ for f in range(6):
sys.stdout.buffer.write(bytes(v for y in range(h) for x in range(w) for v in ((x * 4 + f * 20) % 256, (y * 5) % 256, (f * 40) % 256)))
" > "$3"
}
-frames 64 48 /tmp/slidecraft-fixture-64x48.rgb
-frames 64 64 /tmp/slidecraft-fixture-64x64.rgb
-VID="-f rawvideo -pix_fmt rgb24 -s 64x48 -r 10 -i /tmp/slidecraft-fixture-64x48.rgb"
+frames 64 48 /tmp/deckcraft-fixture-64x48.rgb
+frames 64 64 /tmp/deckcraft-fixture-64x64.rgb
+VID="-f rawvideo -pix_fmt rgb24 -s 64x48 -r 10 -i /tmp/deckcraft-fixture-64x48.rgb"
$FF $VID $TONE44 -c:v libx264 -pix_fmt yuv420p -g 5 -bf 2 -c:a aac -b:a 48k -shortest $META clip-h264.mp4
$FF $VID -c:v libx264 -pix_fmt yuv420p -g 5 -bf 0 $META clip-h264.mov
$FF $VID -c:v libx265 -pix_fmt yuv420p -x265-params log-level=error:keyint=5 -tag:v hvc1 $META clip-hevc.mp4
$FF $VID $TONE -c:v libvpx-vp9 -pix_fmt yuv420p -g 5 -b:v 100k -c:a libopus -b:a 32k -shortest $META clip-vp9.webm
-$FF -f rawvideo -pix_fmt rgb24 -s 64x64 -r 10 -i /tmp/slidecraft-fixture-64x64.rgb -c:v libsvtav1 -pix_fmt yuv420p -g 5 -svtav1-params loglevel=0 $META clip-av1.mkv
+$FF -f rawvideo -pix_fmt rgb24 -s 64x64 -r 10 -i /tmp/deckcraft-fixture-64x64.rgb -c:v libsvtav1 -pix_fmt yuv420p -g 5 -svtav1-params loglevel=0 $META clip-av1.mkv
diff --git a/crates/model/Cargo.toml b/crates/model/Cargo.toml
index c0db212..cb6b4f1 100644
--- a/crates/model/Cargo.toml
+++ b/crates/model/Cargo.toml
@@ -1,16 +1,16 @@
[package]
-name = "slidecraft-model"
+name = "deckcraft-model"
version.workspace = true
edition.workspace = true
license.workspace = true
-description = "SlideCraft document model: presentations, masters, layouts, slides, shapes, text, themes, transitions, animations"
+description = "DeckCraft document model: presentations, masters, layouts, slides, shapes, text, themes, transitions, animations"
[lints]
workspace = true
[dependencies]
-slidecraft-geom = { workspace = true }
-slidecraft-color = { workspace = true }
+deckcraft-geom = { workspace = true }
+deckcraft-color = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
thiserror = { workspace = true }
diff --git a/crates/model/src/defaults.rs b/crates/model/src/defaults.rs
index 09e0e3a..8a2b5b4 100644
--- a/crates/model/src/defaults.rs
+++ b/crates/model/src/defaults.rs
@@ -3,8 +3,8 @@
use std::sync::Arc;
-use slidecraft_color::SchemeSlot;
-use slidecraft_geom::{Size, Xfrm};
+use deckcraft_color::SchemeSlot;
+use deckcraft_geom::{Size, Xfrm};
use crate::style::ColorRef;
use crate::text::{Anchor, AutoFit, BodyProps, Bullet, LevelStyle, ListStyle, ParaProps, RunProps, Spacing, TextBody, TextDir};
@@ -175,7 +175,7 @@ pub fn build_master(size: Size, theme: Theme, first_id: u32) -> (Master, u32) {
para: ParaProps { align: Some(align), ..Default::default() },
run: RunProps {
size: Some(12.0),
- fill: Some(Fill::solid(ColorRef::scheme(SchemeSlot::Tx1).with(slidecraft_color::ColorTransform::Tint(75000)))),
+ fill: Some(Fill::solid(ColorRef::scheme(SchemeSlot::Tx1).with(deckcraft_color::ColorTransform::Tint(75000)))),
..Default::default()
},
},
@@ -320,7 +320,7 @@ pub fn build_master(size: Size, theme: Theme, first_id: u32) -> (Master, u32) {
para: ParaProps { margin_left: Some(0.0), indent: Some(0.0), bullet: Some(Bullet::None), ..Default::default() },
run: RunProps {
size: Some(24.0),
- fill: Some(Fill::solid(ColorRef::scheme(SchemeSlot::Tx1).with(slidecraft_color::ColorTransform::Tint(82000)))),
+ fill: Some(Fill::solid(ColorRef::scheme(SchemeSlot::Tx1).with(deckcraft_color::ColorTransform::Tint(82000)))),
..Default::default()
},
},
diff --git a/crates/model/src/lib.rs b/crates/model/src/lib.rs
index a7845cc..6940fa4 100644
--- a/crates/model/src/lib.rs
+++ b/crates/model/src/lib.rs
@@ -1,4 +1,4 @@
-//! The SlideCraft document model.
+//! The DeckCraft document model.
//!
//! A [`Presentation`] has slide masters (each with a theme and layouts) and slides. Each slide
//! uses a layout; placeholders on a slide inherit position, formatting and text styles from the
@@ -19,9 +19,9 @@ pub mod theme;
use std::sync::Arc;
+pub use deckcraft_color::{ColorScheme, ColorTransform, Rgba, SchemeSlot};
+pub use deckcraft_geom::{Rect, Size, Xfrm};
use serde::{Deserialize, Serialize};
-pub use slidecraft_color::{ColorScheme, ColorTransform, Rgba, SchemeSlot};
-pub use slidecraft_geom::{Rect, Size, Xfrm};
pub use anim::{AnimClass, AnimStart, Animation, Transition};
pub use chart::Chart;
@@ -208,13 +208,13 @@ pub struct CustomPath {
pub h: f64,
/// `M x y`, `L x y`, `C x1 y1 x2 y2 x y`, `Q x1 y1 x y`, `Z`.
pub d: String,
- pub fill: slidecraft_geom::preset::FillMode,
+ pub fill: deckcraft_geom::preset::FillMode,
pub stroke: bool,
}
impl Default for CustomPath {
fn default() -> Self {
- CustomPath { w: 0.0, h: 0.0, d: String::new(), fill: slidecraft_geom::preset::FillMode::Norm, stroke: true }
+ CustomPath { w: 0.0, h: 0.0, d: String::new(), fill: deckcraft_geom::preset::FillMode::Norm, stroke: true }
}
}
@@ -371,7 +371,7 @@ impl Shape {
}
/// Is this a line-like shape (no area)?
pub fn is_line(&self) -> bool {
- matches!(self.kind, ShapeKind::Connector { .. }) || self.geom.preset_name().is_some_and(slidecraft_geom::preset::is_line_like)
+ matches!(self.kind, ShapeKind::Connector { .. }) || self.geom.preset_name().is_some_and(deckcraft_geom::preset::is_line_like)
}
pub fn kind_name(&self) -> &'static str {
match &self.kind {
diff --git a/crates/model/src/resolve.rs b/crates/model/src/resolve.rs
index 2abab4b..44e7a8d 100644
--- a/crates/model/src/resolve.rs
+++ b/crates/model/src/resolve.rs
@@ -1,8 +1,8 @@
//! Inheritance: effective position, fill, line, body and text properties of a shape, following
//! slide → layout placeholder → master placeholder → master text styles → theme.
-use slidecraft_color::{ColorScheme, Rgba, SchemeSlot};
-use slidecraft_geom::Xfrm;
+use deckcraft_color::{ColorScheme, Rgba, SchemeSlot};
+use deckcraft_geom::Xfrm;
use crate::style::{ColorRef, Effects, Fill, Line};
use crate::text::{Anchor, AutoFit, BodyProps, LevelStyle, ListStyle, ParaProps, RunProps};
diff --git a/crates/model/src/style.rs b/crates/model/src/style.rs
index fc51251..ff1b4e0 100644
--- a/crates/model/src/style.rs
+++ b/crates/model/src/style.rs
@@ -1,7 +1,7 @@
//! Colours, fills, lines and effects.
+use deckcraft_color::{ColorScheme, ColorTransform, Rgba, SchemeSlot};
use serde::{Deserialize, Serialize};
-use slidecraft_color::{ColorScheme, ColorTransform, Rgba, SchemeSlot};
use crate::MediaId;
@@ -50,10 +50,10 @@ impl ColorRef {
ColorBase::Rgb { rgb } => *rgb,
ColorBase::Scheme { slot: SchemeSlot::PhClr } => ph.unwrap_or_else(|| scheme.get(SchemeSlot::Accent1)),
ColorBase::Scheme { slot } => scheme.get(*slot),
- ColorBase::Preset { name } => slidecraft_color::preset(name).unwrap_or(Rgba::BLACK),
+ ColorBase::Preset { name } => deckcraft_color::preset(name).unwrap_or(Rgba::BLACK),
ColorBase::System { last, .. } => *last,
};
- slidecraft_color::apply(base, &self.mods)
+ deckcraft_color::apply(base, &self.mods)
}
/// Alpha (0–1) from an `alpha` transform, if any.
pub fn alpha(&self) -> f64 {
diff --git a/crates/model/src/tests.rs b/crates/model/src/tests.rs
index f434be1..3c5db2e 100644
--- a/crates/model/src/tests.rs
+++ b/crates/model/src/tests.rs
@@ -11,7 +11,7 @@ fn new_presentation_is_valid() {
let s = &p.slides[0];
let (m, l) = p.master_for(s).unwrap();
assert_eq!(l.unwrap().kind, LayoutType::Title);
- assert_eq!(m.theme.name, "SlideCraft");
+ assert_eq!(m.theme.name, "DeckCraft");
// Title slide has a centred title and a subtitle.
let kinds: Vec<_> = s.shapes.iter().filter_map(|x| x.ph_type()).collect();
assert_eq!(kinds, vec![PhType::CtrTitle, PhType::SubTitle]);
diff --git a/crates/model/src/theme.rs b/crates/model/src/theme.rs
index 3a6ec4f..26912ea 100644
--- a/crates/model/src/theme.rs
+++ b/crates/model/src/theme.rs
@@ -1,7 +1,7 @@
//! Themes: colour scheme, font scheme and format scheme (the fill/line/effect "style matrix").
+use deckcraft_color::{ColorScheme, ColorTransform, Rgba, SchemeSlot};
use serde::{Deserialize, Serialize};
-use slidecraft_color::{ColorScheme, ColorTransform, Rgba, SchemeSlot};
use crate::style::{ColorRef, Effects, Fill, Gradient, GradientShape, GradientStop, Line, Shadow};
@@ -50,8 +50,8 @@ pub struct Theme {
impl Default for Theme {
fn default() -> Self {
builtin_themes().into_iter().next().unwrap_or_else(|| Theme {
- name: "SlideCraft".into(),
- colors: ColorScheme { name: "SlideCraft".into(), colors: [Rgba::BLACK; 12] },
+ name: "DeckCraft".into(),
+ colors: ColorScheme { name: "DeckCraft".into(), colors: [Rgba::BLACK; 12] },
fonts: FontScheme::default(),
format: FormatScheme::default(),
raw_extra: None,
@@ -99,7 +99,7 @@ pub fn default_format() -> FormatScheme {
})
};
FormatScheme {
- name: "SlideCraft".into(),
+ name: "DeckCraft".into(),
fills: vec![Fill::solid(ph()), grad(110000, 92000), grad(105000, 80000)],
lines: vec![Line::solid(ph(), 0.75), Line::solid(ph(), 1.0), Line::solid(ph(), 1.5)],
effects: vec![
@@ -145,9 +145,9 @@ pub fn builtin_themes() -> Vec {
};
vec![
t(
- "SlideCraft",
+ "DeckCraft",
scheme(
- "SlideCraft",
+ "DeckCraft",
["000000", "FFFFFF", "1E2B3C", "EDEBE7", "2E6FD8", "F26B1D", "2EA36F", "8A5CD8", "D83F6B", "E3B21C", "2E6FD8", "7A5BA6"],
),
"Inter",
diff --git a/crates/ogg/Cargo.toml b/crates/ogg/Cargo.toml
index f30ddf4..9e38883 100644
--- a/crates/ogg/Cargo.toml
+++ b/crates/ogg/Cargo.toml
@@ -1,5 +1,5 @@
[package]
-name = "slidecraft-ogg"
+name = "deckcraft-ogg"
description = "Clean-room Ogg (RFC 3533) demuxer with Ogg Opus (RFC 7845) and Ogg Vorbis stream mapping"
version.workspace = true
edition.workspace = true
diff --git a/crates/opus/Cargo.toml b/crates/opus/Cargo.toml
index e911357..257cd81 100644
--- a/crates/opus/Cargo.toml
+++ b/crates/opus/Cargo.toml
@@ -1,5 +1,5 @@
[package]
-name = "slidecraft-opus"
+name = "deckcraft-opus"
description = "Clean-room pure-Rust Opus decoder (RFC 6716 / RFC 8251; OpusHead, multistream surround)"
version.workspace = true
edition.workspace = true
diff --git a/crates/pdf/Cargo.toml b/crates/pdf/Cargo.toml
index 3e4e367..50f9adb 100644
--- a/crates/pdf/Cargo.toml
+++ b/crates/pdf/Cargo.toml
@@ -1,18 +1,18 @@
[package]
-name = "slidecraft-pdf"
+name = "deckcraft-pdf"
version.workspace = true
edition.workspace = true
license.workspace = true
-description = "SlideCraft PDF export (krilla): slides, notes pages and handouts with a selectable text layer, links and bookmarks"
+description = "DeckCraft PDF export (krilla): slides, notes pages and handouts with a selectable text layer, links and bookmarks"
[lints]
workspace = true
[dependencies]
-slidecraft-model = { workspace = true }
-slidecraft-fonts = { workspace = true }
-slidecraft-text = { workspace = true }
-slidecraft-render = { workspace = true }
+deckcraft-model = { workspace = true }
+deckcraft-fonts = { workspace = true }
+deckcraft-text = { workspace = true }
+deckcraft-render = { workspace = true }
krilla = { workspace = true }
kurbo = { workspace = true }
serde = { workspace = true }
diff --git a/crates/pdf/src/lib.rs b/crates/pdf/src/lib.rs
index f48670a..97eb641 100644
--- a/crates/pdf/src/lib.rs
+++ b/crates/pdf/src/lib.rs
@@ -1,6 +1,6 @@
//! PDF export.
//!
-//! Each slide is drawn by the SlideCraft renderer at print resolution (so the PDF looks exactly like
+//! Each slide is drawn by the DeckCraft renderer at print resolution (so the PDF looks exactly like
//! the slide, effects included) and overlaid with an invisible layer of real text from the text
//! layout engine with embedded font subsets, so the text can be searched, selected and copied.
//! Hyperlinks and shape actions become link annotations, slide titles become bookmarks.
@@ -10,6 +10,10 @@
use std::collections::HashMap;
+use deckcraft_fonts::FontDb;
+use deckcraft_model::Presentation;
+use deckcraft_model::text::Action as ClickAction;
+use deckcraft_render::{PlacedText, RenderOpts};
use krilla::action::{Action, LinkAction};
use krilla::annotation::{Annotation, LinkAnnotation, Target};
use krilla::destination::XyzDestination;
@@ -24,10 +28,6 @@ use krilla::surface::Surface;
use krilla::text::{Font, GlyphId, KrillaGlyph, TextDirection};
use kurbo::{Affine, Rect};
use serde::{Deserialize, Serialize};
-use slidecraft_fonts::FontDb;
-use slidecraft_model::Presentation;
-use slidecraft_model::text::Action as ClickAction;
-use slidecraft_render::{PlacedText, RenderOpts};
#[derive(Debug, thiserror::Error)]
pub enum PdfError {
@@ -97,7 +97,7 @@ pub fn export(pres: &Presentation, opts: &PdfOptions) -> Result> {
}
let mut pdf = krilla::Document::new();
let title = opts.title.clone().unwrap_or_else(|| pres.props.title.clone());
- let mut meta = Metadata::new().creator("SlideCraft".into()).producer("SlideCraft".into());
+ let mut meta = Metadata::new().creator("DeckCraft".into()).producer("DeckCraft".into());
if !title.trim().is_empty() {
meta = meta.title(title);
}
@@ -228,7 +228,7 @@ fn grey(v: u8, opacity: f32) -> Fill {
}
impl Exporter<'_> {
- fn font(&mut self, face: &slidecraft_fonts::FontFace) -> Option {
+ fn font(&mut self, face: &deckcraft_fonts::FontFace) -> Option {
self.fonts.entry(face.id()).or_insert_with(|| Font::new(face.data().to_vec().into(), face.index())).clone()
}
@@ -239,7 +239,7 @@ impl Exporter<'_> {
let k = b.width() / sw;
// Raster at the requested dpi relative to the box size on paper.
let scale = (b.width() / 72.0 * self.opts.dpi.clamp(36.0, 600.0)) / sw;
- let img = slidecraft_render::render_slide(pres, i, &RenderOpts { scale, threads: 4, ..Default::default() });
+ let img = deckcraft_render::render_slide(pres, i, &RenderOpts { scale, threads: 4, ..Default::default() });
if img.width > 0 && img.height > 0 {
let mut rgba = img.pixels.clone();
for px in rgba.as_chunks_mut::<4>().0 {
@@ -272,7 +272,7 @@ impl Exporter<'_> {
}
if self.opts.text_layer {
let view = Affine::translate((b.x0, b.y0)) * Affine::scale(k);
- for pt in slidecraft_render::place_slide(pres, i).texts {
+ for pt in deckcraft_render::place_slide(pres, i).texts {
self.text_layer(s, &pt, view);
}
let _ = sh;
@@ -332,7 +332,7 @@ impl Exporter<'_> {
let k = b.width() / pres.slide_size.width.max(1.0);
let view = Affine::translate((b.x0, b.y0)) * Affine::scale(k);
let mut out = Vec::new();
- for l in slidecraft_render::place_slide(pres, i).links {
+ for l in deckcraft_render::place_slide(pres, i).links {
let r = view.transform_rect_bbox(l.rect).intersect(b);
let Some(rect) = krilla::geom::Rect::from_ltrb(r.x0 as f32, r.y0 as f32, r.x1 as f32, r.y1 as f32) else { continue };
let goto = |slide: Option| {
@@ -355,8 +355,8 @@ impl Exporter<'_> {
out
}
- fn ui_font(&mut self) -> Option<(Font, std::sync::Arc)> {
- let face = FontDb::global().face(slidecraft_fonts::DEFAULT_FAMILY, "Regular");
+ fn ui_font(&mut self) -> Option<(Font, std::sync::Arc)> {
+ let face = FontDb::global().face(deckcraft_fonts::DEFAULT_FAMILY, "Regular");
let f = self.font(&face)?;
Some((f, face))
}
diff --git a/crates/pdf/src/tests.rs b/crates/pdf/src/tests.rs
index c6d8a72..305f145 100644
--- a/crates/pdf/src/tests.rs
+++ b/crates/pdf/src/tests.rs
@@ -1,13 +1,13 @@
use std::sync::Arc;
use super::*;
-use slidecraft_model::text::TextBody;
+use deckcraft_model::text::TextBody;
fn deck(n: usize) -> Presentation {
- let mut p = slidecraft_model::defaults::new_presentation(None);
+ let mut p = deckcraft_model::defaults::new_presentation(None);
let layout = p.slides[0].layout;
while p.slides.len() < n {
- let s = slidecraft_model::defaults::new_slide(&mut p, layout);
+ let s = deckcraft_model::defaults::new_slide(&mut p, layout);
p.slides.push(Arc::new(s));
}
for (i, s) in p.slides.iter_mut().enumerate() {
diff --git a/crates/pptx/Cargo.toml b/crates/pptx/Cargo.toml
index 5074e90..0ea79d6 100644
--- a/crates/pptx/Cargo.toml
+++ b/crates/pptx/Cargo.toml
@@ -1,26 +1,26 @@
[package]
-name = "slidecraft-pptx"
+name = "deckcraft-pptx"
version.workspace = true
edition.workspace = true
license.workspace = true
-description = "SlideCraft PPTX: Office Open XML (PresentationML/DrawingML) import and export, written from the public ECMA-376 standard"
+description = "DeckCraft PPTX: Office Open XML (PresentationML/DrawingML) import and export, written from the public ECMA-376 standard"
[lints]
workspace = true
[dependencies]
-slidecraft-model = { workspace = true }
-slidecraft-geom = { workspace = true }
-slidecraft-color = { workspace = true }
+deckcraft-model = { workspace = true }
+deckcraft-geom = { workspace = true }
+deckcraft-color = { workspace = true }
quick-xml = { workspace = true }
zip = { workspace = true }
thiserror = { workspace = true }
log = { workspace = true }
[dev-dependencies]
-slidecraft-engine = { workspace = true }
+deckcraft-engine = { workspace = true }
serde_json = { workspace = true }
-slidecraft-color = { workspace = true }
-slidecraft-geom = { workspace = true }
+deckcraft-color = { workspace = true }
+deckcraft-geom = { workspace = true }
image = { workspace = true }
zip = { workspace = true }
diff --git a/crates/pptx/src/custgeom.rs b/crates/pptx/src/custgeom.rs
index 34b649b..ae12214 100644
--- a/crates/pptx/src/custgeom.rs
+++ b/crates/pptx/src/custgeom.rs
@@ -8,8 +8,8 @@ use std::collections::HashMap;
use std::f64::consts::PI;
use std::fmt::Write as _;
-use slidecraft_geom::preset::FillMode;
-use slidecraft_model::CustomPath;
+use deckcraft_geom::preset::FillMode;
+use deckcraft_model::CustomPath;
use crate::xml::{A, El, W};
diff --git a/crates/pptx/src/lib.rs b/crates/pptx/src/lib.rs
index a25e20a..a6018b7 100644
--- a/crates/pptx/src/lib.rs
+++ b/crates/pptx/src/lib.rs
@@ -2,7 +2,7 @@
//! ECMA-376 standard.
//!
//! [`import`] reads a package leniently (unknown parts are skipped, malformed parts are logged and
-//! ignored, every size and depth is bounded) into the SlideCraft model; [`export`] writes a valid
+//! ignored, every size and depth is bounded) into the DeckCraft model; [`export`] writes a valid
//! package that PowerPoint opens without repair.
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
#![forbid(unsafe_code)]
@@ -16,7 +16,7 @@ mod tables;
mod write;
mod xml;
-use slidecraft_model::Presentation;
+use deckcraft_model::Presentation;
/// Extension URI of the PowerPoint 2010 section list in `presentation.xml`.
pub(crate) const SECTION_EXT_URI: &str = "{521415D9-36F7-43E2-AB2F-B90AF26B5E84}";
diff --git a/crates/pptx/src/maps.rs b/crates/pptx/src/maps.rs
index 520f958..c700d8e 100644
--- a/crates/pptx/src/maps.rs
+++ b/crates/pptx/src/maps.rs
@@ -1,7 +1,7 @@
//! Mappings between our transition / animation ids and PresentationML elements and preset
//! numbers, shared by the reader and the writer.
-use slidecraft_model::anim::{ANIMATIONS, AnimClass};
+use deckcraft_model::anim::{ANIMATIONS, AnimClass};
use crate::xml::{A, El, W};
@@ -13,7 +13,7 @@ const SIMPLE: &[(&str, &str, &str)] =
pub fn transition_from_xml(e: &El) -> Option<(String, String)> {
let (k, o) = transition_from_xml_raw(e)?;
// Kinds without options keep none.
- let has_opts = slidecraft_model::anim::TRANSITIONS.iter().find(|t| t.0 == k).is_none_or(|t| !t.4.is_empty());
+ let has_opts = deckcraft_model::anim::TRANSITIONS.iter().find(|t| t.0 == k).is_none_or(|t| !t.4.is_empty());
Some((k, if has_opts { o } else { String::new() }))
}
@@ -78,7 +78,7 @@ fn transition_from_xml_raw(e: &El) -> Option<(String, String)> {
}
"prstTrans" => {
let prst = e.attr("prst").unwrap_or("fallOver");
- let known = slidecraft_model::anim::TRANSITIONS.iter().any(|t| t.0 == prst);
+ let known = deckcraft_model::anim::TRANSITIONS.iter().any(|t| t.0 == prst);
if !known {
return None;
}
@@ -303,7 +303,7 @@ pub fn fallback_effect(class: AnimClass) -> &'static str {
}
/// The preset id to write for an animation.
-pub fn preset_for(a: &slidecraft_model::Animation) -> u32 {
+pub fn preset_for(a: &deckcraft_model::Animation) -> u32 {
if let Some(p) = a.preset_id {
let consistent = match effect_for_preset(a.class, p) {
Some(e) => e == a.effect,
@@ -313,7 +313,7 @@ pub fn preset_for(a: &slidecraft_model::Animation) -> u32 {
return p;
}
}
- match slidecraft_model::anim::animation_info(&a.effect, a.class) {
+ match deckcraft_model::anim::animation_info(&a.effect, a.class) {
Some(info) if info.2 == a.class => info.3,
_ => match a.class {
AnimClass::Entrance | AnimClass::Exit => 10,
@@ -331,7 +331,7 @@ mod tests {
#[test]
fn every_transition_kind_round_trips() {
- for (kind, _, _, _, opts) in slidecraft_model::anim::TRANSITIONS {
+ for (kind, _, _, _, opts) in deckcraft_model::anim::TRANSITIONS {
if *kind == "none" {
continue;
}
diff --git a/crates/pptx/src/read/chart.rs b/crates/pptx/src/read/chart.rs
index 52f0bd0..a9a960a 100644
--- a/crates/pptx/src/read/chart.rs
+++ b/crates/pptx/src/read/chart.rs
@@ -1,7 +1,7 @@
//! Chart parts (`c:chartSpace`) → [`Chart`]. The original XML is kept in `Chart.raw`.
-use slidecraft_model::chart::{Chart, ChartType, Series};
-use slidecraft_model::style::Fill;
+use deckcraft_model::chart::{Chart, ChartType, Series};
+use deckcraft_model::style::Fill;
use crate::xml::El;
@@ -19,9 +19,9 @@ fn simple_fill(sp: Option<&El>) -> Option {
None
}
-fn simple_line(sp: Option<&El>) -> Option {
+fn simple_line(sp: Option<&El>) -> Option {
let ln = sp?.child("ln")?;
- let mut l = slidecraft_model::Line { width: super::dml::pt(ln, "w"), ..Default::default() };
+ let mut l = deckcraft_model::Line { width: super::dml::pt(ln, "w"), ..Default::default() };
l.fill = simple_fill(Some(ln));
Some(l)
}
diff --git a/crates/pptx/src/read/dml.rs b/crates/pptx/src/read/dml.rs
index 5ed63f4..b4f08e0 100644
--- a/crates/pptx/src/read/dml.rs
+++ b/crates/pptx/src/read/dml.rs
@@ -1,12 +1,12 @@
//! DrawingML reading: colours, fills, lines, effects, transforms and text.
-use slidecraft_color::{ColorTransform, Rgba, SchemeSlot};
-use slidecraft_geom::{Xfrm, emu_to_pt};
-use slidecraft_model::style::{
+use deckcraft_color::{ColorTransform, Rgba, SchemeSlot};
+use deckcraft_geom::{Xfrm, emu_to_pt};
+use deckcraft_model::style::{
ColorBase, ColorRef, Compound, Dash, Effects, Fill, Glow, Gradient, GradientShape, GradientStop, Line, LineCap, LineEnd, LineJoin, PatternFill,
PictureFill, PictureMode, Reflection, Shadow,
};
-use slidecraft_model::text::{
+use deckcraft_model::text::{
Action, Align, Anchor, AutoFit, BodyProps, Bullet, Caps, Hyperlink, LevelStyle, ListStyle, ParaProps, Paragraph, Run, RunKind, RunProps, Spacing,
Strike, TabStop, TextBody, TextDir,
};
@@ -165,7 +165,7 @@ pub fn blip_fill(imp: &mut Imp, part: &Part, bf: &El) -> Option {
}
/// Media for an element with `r:embed` (or `r:link`).
-pub fn media_ref(imp: &mut Imp, part: &Part, b: &El) -> Option {
+pub fn media_ref(imp: &mut Imp, part: &Part, b: &El) -> Option {
if let Some(id) = b.attr("r:embed").filter(|s| !s.is_empty())
&& let Some(r) = part.rels.get(id)
{
diff --git a/crates/pptx/src/read/mod.rs b/crates/pptx/src/read/mod.rs
index 57e1246..b8c403b 100644
--- a/crates/pptx/src/read/mod.rs
+++ b/crates/pptx/src/read/mod.rs
@@ -11,9 +11,9 @@ pub(crate) use chart::{chart_from as chart_from_el, read_chart_xml};
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
-use slidecraft_geom::{Size, emu_to_pt};
-use slidecraft_model::text::TextBody;
-use slidecraft_model::{
+use deckcraft_geom::{Size, emu_to_pt};
+use deckcraft_model::text::TextBody;
+use deckcraft_model::{
Background, Comment, CustomShow, HeaderFooter, Layout, LayoutId, LayoutType, Master, MasterId, MediaId, MediaItem, PhType, Presentation,
Properties, Section, ShowSettings, Slide, SlideId, defaults,
};
@@ -422,12 +422,12 @@ pub fn color_map(cm: &El) -> Vec<(String, String)> {
fn background(imp: &mut Imp, part: &Part, csld: &El) -> Option {
let bg = csld.child("bg")?;
if let Some(pr) = bg.child("bgPr") {
- return Some(Background::Fill { fill: dml::fill_of(imp, part, pr).unwrap_or(slidecraft_model::Fill::None) });
+ return Some(Background::Fill { fill: dml::fill_of(imp, part, pr).unwrap_or(deckcraft_model::Fill::None) });
}
let r = bg.child("bgRef")?;
Some(Background::Ref {
idx: r.u32("idx").unwrap_or(1001),
- color: dml::color(r).unwrap_or(slidecraft_model::ColorRef::scheme(slidecraft_color::SchemeSlot::Bg1)),
+ color: dml::color(r).unwrap_or(deckcraft_model::ColorRef::scheme(deckcraft_color::SchemeSlot::Bg1)),
})
}
@@ -641,7 +641,7 @@ fn read_show(sp: &El, shows: &HashMap) -> ShowSettings {
s.custom_show = Some(shows.get(id).cloned().unwrap_or_else(|| id.to_string()));
}
if let Some(c) = sp.child("penClr").and_then(dml::color)
- && let slidecraft_model::ColorBase::Rgb { rgb } = c.base
+ && let deckcraft_model::ColorBase::Rgb { rgb } = c.base
{
s.pen_color = rgb;
}
@@ -677,7 +677,7 @@ fn header_footer(p: &Presentation) -> HeaderFooter {
Some(PhType::Date) => {
hf.date = true;
if let Some(t) = &sh.text
- && !t.paragraphs.iter().flat_map(|p| &p.runs).any(|r| matches!(r.kind, slidecraft_model::text::RunKind::Field { .. }))
+ && !t.paragraphs.iter().flat_map(|p| &p.runs).any(|r| matches!(r.kind, deckcraft_model::text::RunKind::Field { .. }))
&& hf.date_text.is_empty()
{
hf.date_text = t.text();
diff --git a/crates/pptx/src/read/shapes.rs b/crates/pptx/src/read/shapes.rs
index 82d0155..2e10e36 100644
--- a/crates/pptx/src/read/shapes.rs
+++ b/crates/pptx/src/read/shapes.rs
@@ -3,12 +3,12 @@
use std::collections::{HashMap, HashSet};
-use slidecraft_color::SchemeSlot;
-use slidecraft_geom::{Xfrm, preset};
-use slidecraft_model::style::{ColorRef, Effects, Fill};
-use slidecraft_model::table::{Cell, Row, Table};
-use slidecraft_model::text::{Anchor, TextDir};
-use slidecraft_model::{Geom, MediaClip, PhType, Placeholder, Shape, ShapeId, ShapeKind, ShapeStyle};
+use deckcraft_color::SchemeSlot;
+use deckcraft_geom::{Xfrm, preset};
+use deckcraft_model::style::{ColorRef, Effects, Fill};
+use deckcraft_model::table::{Cell, Row, Table};
+use deckcraft_model::text::{Anchor, TextDir};
+use deckcraft_model::{Geom, MediaClip, PhType, Placeholder, Shape, ShapeId, ShapeKind, ShapeStyle};
use super::{Imp, Part, dml};
use crate::xml::El;
@@ -188,7 +188,7 @@ fn sp_pr(imp: &mut Imp, part: &Part, s: &mut Shape, pr: &El) {
if let Some(pg) = pr.child("prstGeom") {
s.geom = preset_geom(pg);
} else if let Some(cg) = pr.child("custGeom") {
- let (w, h) = s.xfrm.map(|x| (slidecraft_geom::pt_to_emu(x.w) as f64, slidecraft_geom::pt_to_emu(x.h) as f64)).unwrap_or((21600.0, 21600.0));
+ let (w, h) = s.xfrm.map(|x| (deckcraft_geom::pt_to_emu(x.w) as f64, deckcraft_geom::pt_to_emu(x.h) as f64)).unwrap_or((21600.0, 21600.0));
let paths = crate::custgeom::read_cust_geom(cg, w, h);
if !paths.is_empty() {
s.geom = Geom::Custom { paths };
diff --git a/crates/pptx/src/read/theme.rs b/crates/pptx/src/read/theme.rs
index 66c15e9..5c3aa74 100644
--- a/crates/pptx/src/read/theme.rs
+++ b/crates/pptx/src/read/theme.rs
@@ -1,8 +1,8 @@
//! Theme parts (`a:theme`).
-use slidecraft_color::{ColorScheme, Rgba, SchemeSlot};
-use slidecraft_model::style::{ColorBase, Effects};
-use slidecraft_model::theme::{FontScheme, FontSet, FormatScheme, Theme, default_format};
+use deckcraft_color::{ColorScheme, Rgba, SchemeSlot};
+use deckcraft_model::style::{ColorBase, Effects};
+use deckcraft_model::theme::{FontScheme, FontSet, FormatScheme, Theme, default_format};
use super::{Imp, Part, dml};
use crate::xml::El;
@@ -22,7 +22,7 @@ pub fn read_theme(imp: &mut Imp, part: &Part, root: &El) -> Theme {
let rgb = match c.base {
ColorBase::Rgb { rgb } => rgb,
ColorBase::System { last, .. } => last,
- ColorBase::Preset { name } => slidecraft_color::preset(&name).unwrap_or(Rgba::BLACK),
+ ColorBase::Preset { name } => deckcraft_color::preset(&name).unwrap_or(Rgba::BLACK),
ColorBase::Scheme { .. } => continue,
};
scheme.set(slot, rgb);
diff --git a/crates/pptx/src/read/timing.rs b/crates/pptx/src/read/timing.rs
index 1efe38a..500bb48 100644
--- a/crates/pptx/src/read/timing.rs
+++ b/crates/pptx/src/read/timing.rs
@@ -1,7 +1,7 @@
//! Slide transitions (`p:transition`) and animations (`p:timing`).
-use slidecraft_model::Shape;
-use slidecraft_model::anim::{AnimClass, AnimStart, Animation, TextBuild, Transition};
+use deckcraft_model::Shape;
+use deckcraft_model::anim::{AnimClass, AnimStart, Animation, TextBuild, Transition};
use super::shapes::IdCtx;
use super::{Imp, Part, dml};
@@ -32,7 +32,7 @@ pub fn transition(imp: &mut Imp, part: &Part, t: &El) -> Option {
None => tr.kind = "none".into(),
},
}
- let default_dur = slidecraft_model::anim::TRANSITIONS.iter().find(|x| x.0 == tr.kind).map(|x| x.3).unwrap_or(1000);
+ let default_dur = deckcraft_model::anim::TRANSITIONS.iter().find(|x| x.0 == tr.kind).map(|x| x.3).unwrap_or(1000);
tr.duration_ms = ms_attr(t, "dur").unwrap_or(match t.attr("spd") {
Some("fast") => 500,
Some("med") => 750,
diff --git a/crates/pptx/src/tables.rs b/crates/pptx/src/tables.rs
index b73e90a..0025bd6 100644
--- a/crates/pptx/src/tables.rs
+++ b/crates/pptx/src/tables.rs
@@ -1,5 +1,5 @@
//! Table styles: our style ids ↔ table style GUIDs, and `tableStyles.xml` definitions for the
-//! styles we write (generated from theme accents, matching how SlideCraft draws them).
+//! styles we write (generated from theme accents, matching how DeckCraft draws them).
use crate::opc::NS_A;
use crate::xml::{A, W};
@@ -44,7 +44,7 @@ pub fn guid(text: &str) -> String {
}
fn own_guid(style: &str) -> String {
- guid(&format!("slidecraft-table-style:{style}"))
+ guid(&format!("deckcraft-table-style:{style}"))
}
/// Table style GUID → our style id (or the GUID itself when unknown).
@@ -53,7 +53,7 @@ pub fn style_from_guid(g: &str) -> String {
if let Some((_, s)) = KNOWN.iter().find(|(k, _)| *k == up) {
return s.to_string();
}
- for (id, _) in slidecraft_model::table::table_styles() {
+ for (id, _) in deckcraft_model::table::table_styles() {
if own_guid(&id) == up {
return id;
}
@@ -71,7 +71,7 @@ pub fn guid_for_style(style: &str) -> (String, bool) {
{
return (g.to_string(), false);
}
- let known = slidecraft_model::table::table_styles().iter().any(|(id, _)| id == style);
+ let known = deckcraft_model::table::table_styles().iter().any(|(id, _)| id == style);
if known { (own_guid(style), true) } else { ("{5C22544A-7EE6-4342-B048-85BDC9FD1C3A}".into(), false) }
}
@@ -202,7 +202,7 @@ fn style_def(w: &mut W, id: &str, name: &str) {
pub fn table_styles_xml(used: &[String]) -> Vec {
let mut w = W::new();
w.open("a:tblStyleLst", A::new().a("xmlns:a", NS_A).a("def", "{5C22544A-7EE6-4342-B048-85BDC9FD1C3A}"));
- let names = slidecraft_model::table::table_styles();
+ let names = deckcraft_model::table::table_styles();
let mut done: Vec<&str> = vec![];
for s in used {
if done.contains(&s.as_str()) {
@@ -213,7 +213,7 @@ pub fn table_styles_xml(used: &[String]) -> Vec