Compare commits

..
Author SHA1 Message Date
Dylan M. Taylor 9eb72b66f3 fix: restore explicit imports in test modules (SonarCloud S2208) 2026-10-25 17:00:49 +00:00
Dylan M. TaylorandClaude Sonnet 5.5 399d923516 fix: drop unused imports
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-25 15:12:46 +00:00
Dylan M. TaylorandClaude Sonnet 5.5 0cff3ac942 fix: geom extra imports
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-25 15:12:46 +00:00
Dylan M. TaylorandClaude Sonnet 5.5 d0b097d837 refactor: explicit imports instead of glob imports in command and test modules (SonarCloud S2208)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-25 15:12:46 +00:00
skylar taylor-barrick 14f40c408c Migrate DeckCraft to egui 0.37 (#1350)
* egui037-20261022: migrate DeckCraft to egui 0.37

Preserve current-main document and PDF behavior alongside the reviewed input, capture, font, ribbon and notice-packaging migration.

Validation: cargo xtask ci passed all eight stages, with 1,877 workspace tests and three doctests passed, six tests ignored, strict native lint and 23 WASM crates checked. Native GUI, platform and performance qualification remains separate.

* E21: restore Latin coverage and enforce required font inputs

* Fix migration review findings in font validation and UI controls

* M-egui037: document UI lifecycle and regression contracts
2026-10-25 10:48:52 -04:00
Brandon ThomasandClaude Opus 5.5 9ec3926793 Release 0.11.1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 09:14:20 -04:00
Brandon ThomasandClaude Opus 5.5 79bf9a688c macOS signing: keep the temporary keychain until the build has signed with it
import-cert.sh deleted its whole private temp directory on exit, and the keychain lives there, so codesign failed with "The specified item could not be found in the keychain" in the release build.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 09:13:55 -04:00
Brandon ThomasandClaude Opus 5.5 1330ce4b86 Release 0.11.0
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 09:04:15 -04:00
aiongg a0eeb341d2 Colour names, media ids after a paste, and pressing a table outside its cells (#1344 follow-ups) (#1348)
* Colors: unknown preset and system colour names are rejected, and the writer saves black or windowText for them

* Gaps: copying text and cell ranges, list AutoFormat's option and Backspace, and a timing-sensitive control-server test

* Paste and Compare: media kept under a foreign id raises the next id, so a later picture gets an id of its own

* Tables: with a cell range selected, a press on the table outside its cells selects and moves the table

* Tables: a click on a selected table's grab band keeps the table selected instead of entering the nearest cell
2026-10-25 01:09:38 -04:00
53 changed files with 550 additions and 89 deletions
Generated
+28 -28
View File
@@ -857,7 +857,7 @@ checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f"
[[package]]
name = "deckcraft"
version = "0.10.0"
version = "0.11.1"
dependencies = [
"arboard",
"chrono",
@@ -878,7 +878,7 @@ dependencies = [
[[package]]
name = "deckcraft-anim"
version = "0.10.0"
version = "0.11.1"
dependencies = [
"deckcraft-geom",
"deckcraft-model",
@@ -887,7 +887,7 @@ dependencies = [
[[package]]
name = "deckcraft-av1"
version = "0.10.0"
version = "0.11.1"
dependencies = [
"deckcraft-bitstream",
"rayon",
@@ -896,14 +896,14 @@ dependencies = [
[[package]]
name = "deckcraft-bitstream"
version = "0.10.0"
version = "0.11.1"
dependencies = [
"craft-media",
]
[[package]]
name = "deckcraft-cli"
version = "0.10.0"
version = "0.11.1"
dependencies = [
"deckcraft-engine",
"deckcraft-mcp",
@@ -913,14 +913,14 @@ dependencies = [
[[package]]
name = "deckcraft-color"
version = "0.10.0"
version = "0.11.1"
dependencies = [
"serde",
]
[[package]]
name = "deckcraft-engine"
version = "0.10.0"
version = "0.11.1"
dependencies = [
"chrono",
"deckcraft-anim",
@@ -946,7 +946,7 @@ dependencies = [
[[package]]
name = "deckcraft-fonts"
version = "0.10.0"
version = "0.11.1"
dependencies = [
"epaint_default_fonts",
"harfrust 0.13.3",
@@ -958,7 +958,7 @@ dependencies = [
[[package]]
name = "deckcraft-format"
version = "0.10.0"
version = "0.11.1"
dependencies = [
"deckcraft-model",
"serde",
@@ -969,7 +969,7 @@ dependencies = [
[[package]]
name = "deckcraft-geom"
version = "0.10.0"
version = "0.11.1"
dependencies = [
"kurbo",
"serde",
@@ -977,7 +977,7 @@ dependencies = [
[[package]]
name = "deckcraft-h264"
version = "0.10.0"
version = "0.11.1"
dependencies = [
"deckcraft-bitstream",
"rayon",
@@ -986,7 +986,7 @@ dependencies = [
[[package]]
name = "deckcraft-hevc"
version = "0.10.0"
version = "0.11.1"
dependencies = [
"deckcraft-bitstream",
"rayon",
@@ -995,21 +995,21 @@ dependencies = [
[[package]]
name = "deckcraft-isobmff"
version = "0.10.0"
version = "0.11.1"
dependencies = [
"craft-media",
]
[[package]]
name = "deckcraft-matroska"
version = "0.10.0"
version = "0.11.1"
dependencies = [
"craft-media",
]
[[package]]
name = "deckcraft-mcp"
version = "0.10.0"
version = "0.11.1"
dependencies = [
"deckcraft-engine",
"deckcraft-render",
@@ -1019,7 +1019,7 @@ dependencies = [
[[package]]
name = "deckcraft-media"
version = "0.10.0"
version = "0.11.1"
dependencies = [
"deckcraft-av1",
"deckcraft-h264",
@@ -1037,7 +1037,7 @@ dependencies = [
[[package]]
name = "deckcraft-metafile"
version = "0.10.0"
version = "0.11.1"
dependencies = [
"image",
"kurbo",
@@ -1046,7 +1046,7 @@ dependencies = [
[[package]]
name = "deckcraft-model"
version = "0.10.0"
version = "0.11.1"
dependencies = [
"deckcraft-color",
"deckcraft-geom",
@@ -1058,15 +1058,15 @@ dependencies = [
[[package]]
name = "deckcraft-ogg"
version = "0.10.0"
version = "0.11.1"
[[package]]
name = "deckcraft-opus"
version = "0.10.0"
version = "0.11.1"
[[package]]
name = "deckcraft-pdf"
version = "0.10.0"
version = "0.11.1"
dependencies = [
"craft-pdf",
"deckcraft-fonts",
@@ -1084,7 +1084,7 @@ dependencies = [
[[package]]
name = "deckcraft-pptx"
version = "0.10.0"
version = "0.11.1"
dependencies = [
"deckcraft-color",
"deckcraft-engine",
@@ -1100,7 +1100,7 @@ dependencies = [
[[package]]
name = "deckcraft-render"
version = "0.10.0"
version = "0.11.1"
dependencies = [
"deckcraft-anim",
"deckcraft-color",
@@ -1119,7 +1119,7 @@ dependencies = [
[[package]]
name = "deckcraft-text"
version = "0.10.0"
version = "0.11.1"
dependencies = [
"chrono",
"deckcraft-color",
@@ -1133,7 +1133,7 @@ dependencies = [
[[package]]
name = "deckcraft-ui-egui"
version = "0.10.0"
version = "0.11.1"
dependencies = [
"deckcraft-anim",
"deckcraft-color",
@@ -1156,7 +1156,7 @@ dependencies = [
[[package]]
name = "deckcraft-vp9"
version = "0.10.0"
version = "0.11.1"
dependencies = [
"deckcraft-bitstream",
"rayon",
@@ -1165,7 +1165,7 @@ dependencies = [
[[package]]
name = "deckcraft-web"
version = "0.10.0"
version = "0.11.1"
dependencies = [
"deckcraft-engine",
"deckcraft-ui-egui",
@@ -5564,7 +5564,7 @@ checksum = "9440ea3e5aeabb0ac63af70daf835274065238cdd0cec83418f417eae38bacee"
[[package]]
name = "xtask"
version = "0.10.0"
version = "0.11.1"
dependencies = [
"serde_json",
]
+1 -1
View File
@@ -4,7 +4,7 @@ members = ["crates/*", "apps/*", "xtask"]
default-members = ["crates/*", "apps/deckcraft", "apps/deckcraft-cli"]
[workspace.package]
version = "0.10.0"
version = "0.11.1"
edition = "2024"
license = "MIT OR Apache-2.0"
rust-version = "1.95"
+4 -1
View File
@@ -7,7 +7,10 @@ use deckcraft_model::{
AnimClass, AnimStart, Animation, ColorRef, CustomShow, Presentation, Rgba, Shape, ShapeId, Slide, SlideId, TextBody, Transition, Xfrm,
};
use crate::*;
use crate::{
AnimState, Layer, LinkJump, MotionPath, ShowAction, ShowState, Source, Timeline, TriggerPlay, default_path, ease, morph_crop, morph_frame,
morph_pairs, morph_xfrm, show_order, transition_frame, transition_layers,
};
const W: f64 = 960.0;
const H: f64 = 540.0;
+1 -1
View File
@@ -6,7 +6,7 @@ use std::sync::Arc;
use deckcraft_model::text::Action;
use deckcraft_model::{AnimClass, AnimStart, Animation, Presentation, Shape, ShapeId, Slide, SlideId, Transition, Xfrm};
use crate::*;
use crate::{Click, HitShape, LinkJump, ShowAction, ShowState, Target, Timeline};
fn shape(id: u32) -> Shape {
Shape { id: ShapeId(id), name: format!("S{id}"), xfrm: Some(Xfrm::new(100.0, 100.0, 200.0, 100.0)), ..Default::default() }
+238
View File
@@ -515,6 +515,244 @@ pub fn preset(name: &str) -> Option<Rgba> {
Some(Rgba::rgb(c.0, c.1, c.2))
}
/// Every preset colour name a file may write (`ST_PresetColorVal`, with the later editions' aliases).
pub const PRESET_NAMES: [&str; 190] = [
"aliceBlue",
"antiqueWhite",
"aqua",
"aquamarine",
"azure",
"beige",
"bisque",
"black",
"blanchedAlmond",
"blue",
"blueViolet",
"brown",
"burlyWood",
"cadetBlue",
"chartreuse",
"chocolate",
"coral",
"cornflowerBlue",
"cornsilk",
"crimson",
"cyan",
"darkBlue",
"darkCyan",
"darkGoldenrod",
"darkGray",
"darkGrey",
"darkGreen",
"darkKhaki",
"darkMagenta",
"darkOliveGreen",
"darkOrange",
"darkOrchid",
"darkRed",
"darkSalmon",
"darkSeaGreen",
"darkSlateBlue",
"darkSlateGray",
"darkSlateGrey",
"darkTurquoise",
"darkViolet",
"dkBlue",
"dkCyan",
"dkGoldenrod",
"dkGray",
"dkGrey",
"dkGreen",
"dkKhaki",
"dkMagenta",
"dkOliveGreen",
"dkOrange",
"dkOrchid",
"dkRed",
"dkSalmon",
"dkSeaGreen",
"dkSlateBlue",
"dkSlateGray",
"dkSlateGrey",
"dkTurquoise",
"dkViolet",
"deepPink",
"deepSkyBlue",
"dimGray",
"dimGrey",
"dodgerBlue",
"firebrick",
"floralWhite",
"forestGreen",
"fuchsia",
"gainsboro",
"ghostWhite",
"gold",
"goldenrod",
"gray",
"grey",
"green",
"greenYellow",
"honeydew",
"hotPink",
"indianRed",
"indigo",
"ivory",
"khaki",
"lavender",
"lavenderBlush",
"lawnGreen",
"lemonChiffon",
"lightBlue",
"lightCoral",
"lightCyan",
"lightGoldenrodYellow",
"lightGray",
"lightGrey",
"lightGreen",
"lightPink",
"lightSalmon",
"lightSeaGreen",
"lightSkyBlue",
"lightSlateGray",
"lightSlateGrey",
"lightSteelBlue",
"lightYellow",
"ltBlue",
"ltCoral",
"ltCyan",
"ltGoldenrodYellow",
"ltGray",
"ltGrey",
"ltGreen",
"ltPink",
"ltSalmon",
"ltSeaGreen",
"ltSkyBlue",
"ltSlateGray",
"ltSlateGrey",
"ltSteelBlue",
"ltYellow",
"lime",
"limeGreen",
"linen",
"magenta",
"maroon",
"medAquamarine",
"medBlue",
"medOrchid",
"medPurple",
"medSeaGreen",
"medSlateBlue",
"medSpringGreen",
"medTurquoise",
"medVioletRed",
"mediumAquamarine",
"mediumBlue",
"mediumOrchid",
"mediumPurple",
"mediumSeaGreen",
"mediumSlateBlue",
"mediumSpringGreen",
"mediumTurquoise",
"mediumVioletRed",
"midnightBlue",
"mintCream",
"mistyRose",
"moccasin",
"navajoWhite",
"navy",
"oldLace",
"olive",
"oliveDrab",
"orange",
"orangeRed",
"orchid",
"paleGoldenrod",
"paleGreen",
"paleTurquoise",
"paleVioletRed",
"papayaWhip",
"peachPuff",
"peru",
"pink",
"plum",
"powderBlue",
"purple",
"red",
"rosyBrown",
"royalBlue",
"saddleBrown",
"salmon",
"sandyBrown",
"seaGreen",
"seaShell",
"sienna",
"silver",
"skyBlue",
"slateBlue",
"slateGray",
"slateGrey",
"snow",
"springGreen",
"steelBlue",
"tan",
"teal",
"thistle",
"tomato",
"turquoise",
"violet",
"wheat",
"white",
"whiteSmoke",
"yellow",
"yellowGreen",
];
/// Every system colour name a file may write (`ST_SystemColorVal`).
pub const SYSTEM_NAMES: [&str; 30] = [
"scrollBar",
"background",
"activeCaption",
"inactiveCaption",
"menu",
"window",
"windowFrame",
"menuText",
"windowText",
"captionText",
"activeBorder",
"inactiveBorder",
"appWorkspace",
"highlight",
"highlightText",
"btnFace",
"btnShadow",
"grayText",
"btnText",
"inactiveCaptionText",
"btnHighlight",
"3dDkShadow",
"3dLight",
"infoText",
"infoBk",
"hotLight",
"gradientActiveCaption",
"gradientInactiveCaption",
"menuHighlight",
"menuBar",
];
/// Whether `name` is a preset colour name a file may write.
pub fn is_preset_name(name: &str) -> bool {
PRESET_NAMES.contains(&name)
}
/// Whether `name` is a system colour name a file may write.
pub fn is_system_name(name: &str) -> bool {
SYSTEM_NAMES.contains(&name)
}
#[cfg(test)]
mod tests {
use super::*;
+1 -1
View File
@@ -5,7 +5,7 @@ use std::sync::Arc;
use deckcraft_model::anim::{ANIMATIONS, AnimClass, AnimStart, Animation, TextBuild, animation_info};
use serde_json::{Value, json};
use super::*;
use super::{CommandSpec, always, bad, bool_or, bool_param, cmd, has_selection, has_slide, id_param, ok, str_param, targets, usize_param};
use crate::{Result, Session};
pub fn specs() -> Vec<CommandSpec> {
+4 -1
View File
@@ -4,7 +4,10 @@ use deckcraft_geom::{Point, Rect, Xfrm};
use deckcraft_model::{Shape, ShapeId, ShapeKind};
use serde_json::{Value, json};
use super::*;
use super::{
CommandSpec, NO_ID_SPACE, bad, bool_or, cmd, edit_geometry, has_doc, has_selection, has_slide, has_two, id_param, ok, str_param, targets,
usize_param, with_param, xfrm_of,
};
use crate::{Result, Session};
pub fn specs() -> Vec<CommandSpec> {
+1 -1
View File
@@ -9,7 +9,7 @@ use deckcraft_model::{MediaId, ShapeId, ShapeKind};
use image::{GrayImage, Rgba, RgbaImage};
use serde_json::{Value, json};
use super::*;
use super::{CommandSpec, NO_ID_SPACE, bad, cmd, has_selection, str_param, targets};
use crate::{Result, Session};
/// The effects offered: `a14` element name and label.
+6 -1
View File
@@ -8,7 +8,7 @@ use std::collections::VecDeque;
use deckcraft_model::{ShapeId, ShapeKind};
use serde_json::{Value, json};
use super::*;
use super::{CommandSpec, NO_ID_SPACE, bad, cmd, f64_or, has_selection, targets};
use crate::{Result, Session};
/// Larger pictures are refused rather than matted slowly (16 megapixels).
@@ -184,7 +184,12 @@ fn remove_background(s: &mut Session, p: &Value) -> Result<Value> {
#[cfg(test)]
mod tests {
use deckcraft_model::{ShapeId, ShapeKind};
use serde_json::json;
use super::*;
use crate::Session;
use crate::cmd::base64_encode;
/// A red disc on a white-to-grey gradient.
fn disc() -> image::RgbaImage {
+1 -1
View File
@@ -2,7 +2,7 @@
use serde_json::{Value, json};
use crate::*;
use crate::Session;
fn deck() -> Session {
let mut s = Session::with_new();
+1 -1
View File
@@ -8,7 +8,7 @@ use deckcraft_model::style::PictureFill;
use deckcraft_model::{MediaId, Shape, ShapeId, ShapeKind};
use serde_json::{Value, json};
use super::*;
use super::{CommandSpec, NO_ID_SPACE, bad, bool_or, cmd, f64_param, has_doc, targets};
use crate::{Result, Session};
pub fn specs() -> Vec<CommandSpec> {
+4 -1
View File
@@ -10,7 +10,10 @@ use deckcraft_model::theme::{builtin_color_schemes, builtin_font_schemes, builti
use deckcraft_model::{Background, Fill, PhType, Presentation, Shape, ShapeId, defaults};
use serde_json::{Value, json};
use super::*;
use super::{
CommandSpec, NO_ID_SPACE, always, bad, bool_or, bool_param, cmd, color_param, color_value, f64_param, has_doc, has_slide, ok, rect_param,
str_param, usize_param, with_param,
};
use crate::{Result, Session, Target};
pub fn specs() -> Vec<CommandSpec> {
+9 -2
View File
@@ -7,7 +7,10 @@ use deckcraft_model::text::TextBody;
use deckcraft_model::{MediaId, Shape, ShapeId, Slide, SlideId};
use serde_json::{Value, json};
use super::*;
use super::{
CommandSpec, NO_ID_SPACE, bad, base64_encode, bool_or, can_redo, can_undo, cmd, has_doc, has_selection, has_slide, id_param, ids_param, ok,
richclip, str_param, targets, xfrm_of,
};
use crate::{HistoryEntry, Result, Session};
pub fn specs() -> Vec<CommandSpec> {
@@ -245,7 +248,11 @@ fn bring_media(cmd: &str, doc: &mut deckcraft_model::Presentation, media: &[deck
let mut map = HashMap::new();
for m in media {
match doc.media(m.id) {
None => doc.media.push(m.clone()),
None => {
// Keeps the foreign id, so later ids must start past it.
doc.media.push(m.clone());
doc.next_id = doc.next_id.max(m.id.0);
}
Some(x) if x.data == m.data => {}
Some(_) => {
let id = doc.add_media(&m.name, &m.content_type, m.data.as_ref().clone()).ok_or_else(|| bad(cmd, NO_ID_SPACE))?;
+1 -1
View File
@@ -7,7 +7,7 @@ use deckcraft_media::MediaInfo;
use deckcraft_model::{MediaId, MediaItem, Presentation, Shape, ShapeKind, defaults};
use serde_json::{Value, json};
use super::*;
use super::{CommandSpec, always, bad, base64_decode, base64_encode, bool_or, cmd, f64_or, has_doc, ok, str_param, usize_param};
use crate::{DocState, EngineError, Result, Session};
pub fn specs() -> Vec<CommandSpec> {
+1 -1
View File
@@ -6,7 +6,7 @@ use deckcraft_model::text::RunKind;
use deckcraft_model::{Paragraph, Shape, ShapeKind, Slide, TextBody};
use serde_json::{Value, json};
use super::*;
use super::{CommandSpec, bad, bool_or, cmd, has_doc, str_param};
use crate::{Result, Session};
pub fn specs() -> Vec<CommandSpec> {
+5 -1
View File
@@ -8,8 +8,12 @@ use deckcraft_model::{ColorRef, ColorTransform, Effects, Fill, Line, SchemeSlot,
use serde_json::{Value, json};
use super::text::{format_body, format_para, format_run};
use super::*;
use super::{
CommandSpec, always, bad, base64_decode, bool_or, bool_param, cmd, color_param, edit_shapes, f64_or, f64_param, has_doc, has_text,
has_text_or_shapes, ids_param, ok, str_param,
};
use crate::{Result, Session};
use deckcraft_model::ShapeId;
pub fn specs() -> Vec<CommandSpec> {
vec![
+1 -1
View File
@@ -8,7 +8,7 @@ use deckcraft_model::resolve::{self, Ctx};
use deckcraft_model::{Fill, Presentation, Rgba, Shape, ShapeKind};
use serde_json::{Value, json};
use super::*;
use super::{CommandSpec, bad, cmd, has_slide, str_param, usize_param, xfrm_of};
use crate::{Result, Selection, Session};
pub fn specs() -> Vec<CommandSpec> {
+4 -1
View File
@@ -8,7 +8,10 @@ use deckcraft_model::text::{AutoFit, RunKind, TextBody};
use deckcraft_model::{Chart, ColorRef, Fill, Geom, Line, MediaClip, Shape, ShapeId, ShapeKind, ShapeStyle, Table};
use serde_json::{Value, json};
use super::*;
use super::{
CommandSpec, NO_ID_SPACE, always, bad, base64_decode, base64_encode, bool_or, bool_param, cmd, color_value, edit_shapes, has_doc, has_selection,
has_slide, has_text_or_shapes, ok, rect_param, str_param, usize_param, xfrm_of,
};
use crate::{EngineError, Result, Session};
pub fn specs() -> Vec<CommandSpec> {
+1 -1
View File
@@ -4,7 +4,7 @@ use deckcraft_model::resolve::{self, Ctx};
use deckcraft_model::{Shape, ShapeKind};
use serde_json::{Value, json};
use super::*;
use super::{CommandSpec, always, bad, cmd, has_doc, id_param, str_param, usize_param, xfrm_of};
use crate::{Result, Session};
pub fn specs() -> Vec<CommandSpec> {
+6 -2
View File
@@ -5,7 +5,7 @@
use deckcraft_model::{MediaClip, ShapeId, ShapeKind};
use serde_json::{Value, json};
use super::*;
use super::{CommandSpec, NO_ID_SPACE, bad, bool_param, cmd, edit_shapes, has_doc, targets};
use crate::{EngineError, MediaStatus, Result, Session, UiRequest};
pub fn specs() -> Vec<CommandSpec> {
@@ -179,7 +179,11 @@ fn gif_play(s: &mut Session, p: &Value) -> Result<Value> {
#[cfg(test)]
mod tests {
use super::*;
use deckcraft_model::ShapeId;
use serde_json::json;
use crate::cmd::base64_encode;
use crate::{MediaStatus, Session, UiRequest};
fn wav() -> Vec<u8> {
let rate = 8000u32;
+7 -2
View File
@@ -9,7 +9,7 @@ use i_overlay::core::overlay_rule::OverlayRule;
use i_overlay::float::single::SingleFloatOverlay;
use serde_json::{Value, json};
use super::*;
use super::{CommandSpec, NO_ID_SPACE, bad, cmd, has_two, str_param, targets, xfrm_of};
use crate::{Result, Session};
/// Closed polygons (slide points) describing a filled area (non-zero winding).
@@ -255,7 +255,12 @@ fn merge(s: &mut Session, p: &Value) -> Result<Value> {
#[cfg(test)]
mod tests {
use super::*;
use deckcraft_model::{ShapeId, ShapeKind};
use serde_json::json;
use super::{Area, area_of, area_size, letters_of, merge_areas};
use crate::Session;
use crate::cmd::base64_encode;
fn square(x: f64, y: f64, s: f64) -> Area {
vec![vec![[x, y], [x + s, y], [x + s, y + s], [x, y + s]]]
+12
View File
@@ -394,8 +394,14 @@ pub(crate) fn color_value(v: &Value) -> Option<deckcraft_model::ColorRef> {
let mut c = if let Some(s) = o.get("scheme").and_then(Value::as_str) {
ColorRef::scheme(deckcraft_color::SchemeSlot::from_xml(s)?)
} else if let Some(name) = o.get("preset").and_then(Value::as_str) {
if !deckcraft_color::is_preset_name(name) {
return None;
}
ColorRef { base: deckcraft_model::ColorBase::Preset { name: name.to_string() }, mods: vec![] }
} else if let Some(name) = o.get("system").and_then(Value::as_str) {
if !deckcraft_color::is_system_name(name) {
return None;
}
let last = deckcraft_color::Rgba::from_hex(o.get("last").and_then(Value::as_str)?)?;
ColorRef { base: deckcraft_model::ColorBase::System { name: name.to_string(), last }, mods: vec![] }
} else {
@@ -657,5 +663,11 @@ mod tests {
let system = color_value(&json!({"system": "windowText", "last": "#123456"}));
assert_eq!(system, Some(ColorRef { base: ColorBase::System { name: "windowText".into(), last: Rgba::rgb(0x12, 0x34, 0x56) }, mods: vec![] }));
assert_eq!(color_value(&json!({"system": "windowText"})), None, "a system color needs its last value");
assert_eq!(color_value(&json!({"preset": "notAColor"})), None);
assert_eq!(color_value(&json!({"system": "notAColor", "last": "#000000"})), None);
for name in ["aliceBlue", "darkBlue", "ltSlateGrey"] {
assert!(color_value(&json!({"preset": name})).is_some(), "{name}");
}
assert!(color_value(&json!({"system": "3dDkShadow", "last": "#000000"})).is_some());
}
}
+1 -1
View File
@@ -6,7 +6,7 @@ use deckcraft_model::style::{Effects, Reflection, Shadow};
use deckcraft_model::{ColorRef, Geom, Line, Shape};
use serde_json::{Value, json};
use super::*;
use super::{CommandSpec, always, bad, cmd, edit_shapes, has_selection, str_param};
use crate::{Result, Session};
pub fn specs() -> Vec<CommandSpec> {
+1 -1
View File
@@ -10,7 +10,7 @@ use deckcraft_geom::{PathEl, Point, Xfrm};
use deckcraft_model::{CustomPath, Geom, Shape, ShapeId, ShapeKind};
use serde_json::{Value, json};
use super::*;
use super::{CommandSpec, bad, cmd, f64_param, has_selection, id_param, ok, str_param, xfrm_of};
use crate::{Result, Session};
pub fn specs() -> Vec<CommandSpec> {
+1 -1
View File
@@ -6,7 +6,7 @@ use deckcraft_model::style::{PictureAdjust, PictureMode};
use deckcraft_model::{ColorRef, Shape, ShapeId, ShapeKind};
use serde_json::{Value, json};
use super::*;
use super::{CommandSpec, always, bad, bool_or, cmd, current_shapes, edit_shapes, has_selection, str_param, targets, text, xfrm_of};
use crate::{Result, Session};
pub fn specs() -> Vec<CommandSpec> {
+1 -1
View File
@@ -13,7 +13,7 @@ use deckcraft_model::text::{Action, Hyperlink, RunProps, TextBody};
use deckcraft_model::{LayoutId, Master, MasterId, MediaId, Presentation, ShapeId, ShapeKind, Slide, SlideId};
use serde_json::{Value, json};
use super::*;
use super::{CommandSpec, NO_ID_SPACE, bad, bool_or, cmd, has_doc, usize_param};
use crate::{Result, Session, media_refs};
pub fn specs() -> Vec<CommandSpec> {
+3 -1
View File
@@ -5,7 +5,9 @@ use std::sync::Arc;
use deckcraft_model::Comment;
use serde_json::{Value, json};
use super::*;
use super::{
CommandSpec, always, bad, base64_decode, bool_or, bool_param, cmd, f64_or, has_doc, has_slide, has_text, id_param, ok, str_param, usize_param,
};
use crate::{Result, Session, Target, UiRequest};
pub fn specs() -> Vec<CommandSpec> {
+1 -1
View File
@@ -5,7 +5,7 @@
use deckcraft_model::{Shape, ShapeId};
use serde_json::{Value, json};
use super::*;
use super::{CommandSpec, bad, bool_or, cmd, has_slide, id_param, ids_param, ok};
use crate::{Result, Session};
pub fn specs() -> Vec<CommandSpec> {
+1 -1
View File
@@ -2,8 +2,8 @@
use serde_json::{Value, json};
use crate::Session;
use crate::tools::{Mods, PointerEvent, PointerKind};
use crate::*;
/// A blank slide with A, B, C (back to front) and G = group [D, H = group [E, F]] in front.
fn deck() -> (Session, Vec<u32>) {
+6 -1
View File
@@ -8,7 +8,10 @@ use deckcraft_model::style::{Dash, Glow, Gradient, GradientShape, GradientStop,
use deckcraft_model::{ColorRef, Effects, Fill, Geom, Line, ShapeStyle};
use serde_json::{Value, json};
use super::*;
use super::{
CommandSpec, NO_ID_SPACE, bad, bool_or, bool_param, cmd, color_param, color_value, edit_geometry, edit_shapes, f64_param, has_selection,
has_slide, id_param, ok, str_param, targets, usize_param, with_param, xfrm_of,
};
use crate::{Result, Session};
pub fn specs() -> Vec<CommandSpec> {
@@ -1519,6 +1522,8 @@ fn ink_to_shape(s: &mut Session, _p: &Value) -> Result<Value> {
mod tests {
#![allow(clippy::unwrap_used)]
use deckcraft_model::ShapeId;
use super::*;
use serde_json::json;
+1 -1
View File
@@ -6,7 +6,7 @@ use deckcraft_model::text::TextBody;
use deckcraft_model::{LayoutId, LayoutType, Presentation, Section, SlideId, defaults};
use serde_json::{Value, json};
use super::*;
use super::{CommandSpec, NO_ID_SPACE, bad, bool_or, bool_param, cmd, has_doc, has_slide, ok, opt_usize_param, str_param, usize_param};
use crate::{Result, Session, Target};
pub fn specs() -> Vec<CommandSpec> {
+1 -1
View File
@@ -14,7 +14,7 @@ use deckcraft_model::{
};
use serde_json::{Value, json};
use super::*;
use super::{CommandSpec, NO_ID_SPACE, bad, bool_or, cmd, current_shapes, has_selection, shape_of, str_param, targets, xfrm_of};
use crate::{EngineError, Result, Session};
/// Deepest outline level the Text Pane allows.
+4 -1
View File
@@ -4,7 +4,10 @@ use deckcraft_geom::{Affine, Point, Rect};
use deckcraft_model::{Shape, ShapeKind, Table};
use serde_json::{Value, json};
use super::*;
use super::{
CommandSpec, bad, bool_or, bool_param, cmd, color_param, edit_shapes, f64_param, has_selection, has_slide, id_param, ok, str_param,
table_drawn_heights, usize_param, xfrm_of,
};
use crate::{DocState, Result, Session};
pub fn specs() -> Vec<CommandSpec> {
+4 -1
View File
@@ -9,7 +9,10 @@ use deckcraft_model::text::{Align, AutoFit, Hyperlink, Paragraph, Run, RunKind,
use deckcraft_model::{Presentation, Shape, ShapeId, ShapeKind};
use serde_json::{Value, json};
use super::*;
use super::{
CommandSpec, NO_TEXT_POINT, SELECT_TEXT_OR_SHAPE, always, bad, bool_or, cmd, edit_shapes, has_doc, has_slide, has_text, id_param, ok, shape_of,
str_param, xfrm_of,
};
use crate::{DocState, HistoryEntry, Result, Selection, Session, Target, TextSel, push_undo};
/// Catalogue text-editing, selection, autocorrect, font, IME, and surrounding-deletion commands
+1 -1
View File
@@ -6,7 +6,7 @@ use deckcraft_model::Transition;
use deckcraft_model::anim::TRANSITIONS;
use serde_json::{Value, json};
use super::*;
use super::{CommandSpec, NO_ID_SPACE, always, bad, bool_param, cmd, has_slide, ok, str_param, usize_param};
use crate::{Result, Session};
pub fn specs() -> Vec<CommandSpec> {
+1 -1
View File
@@ -13,7 +13,7 @@ use deckcraft_model::text::{Action, Hyperlink, TextBody};
use deckcraft_model::{LayoutType, Shape, ShapeId, ShapeKind, SlideId, defaults};
use serde_json::{Value, json};
use super::*;
use super::{CommandSpec, NO_ID_SPACE, bad, bool_or, cmd, has_slide, str_param};
use crate::{Result, Session};
pub fn specs() -> Vec<CommandSpec> {
+5 -1
View File
@@ -286,7 +286,11 @@ pub fn adopt(doc: &mut Presentation, rev: &Presentation, slide: &Slide, replacin
let Some(m) = rev.media(id) else { continue };
match doc.media(id) {
Some(x) if x.data == m.data => {}
None => doc.media.push(m.clone()),
None => {
// Keeps the revision's id, so later ids must start past it.
doc.media.push(m.clone());
doc.next_id = doc.next_id.max(id.0);
}
Some(_) => {
let new = doc
.add_media(&m.name, &m.content_type, m.data.as_ref().clone())
+1 -1
View File
@@ -9,7 +9,7 @@ use std::collections::VecDeque;
use serde_json::{Value, json};
use crate::*;
use crate::{EngineError, Session, command_specs, sample};
/// Commands that touch the file system, other windows or real time; the soak drives the rest.
fn skipped(id: &str) -> bool {
+72 -1
View File
@@ -1,6 +1,10 @@
use serde_json::json;
use crate::*;
use crate::tools::{Mods, PointerEvent, PointerKind};
use crate::{
DocState, History, Presentation, Session, Target, TextSel, ToolKind, Value, cmd, command_specs, connect, find_command, model, sample, tools,
};
use deckcraft_model::ShapeId;
fn session() -> Session {
Session::with_new()
@@ -947,6 +951,34 @@ fn dragging_across_cells_selects_a_range_of_cells() {
assert_eq!(cmd::table::cell_block(&t, (1, 1), (2, 2)), ((0, 0), (2, 2)));
}
#[test]
fn pressing_a_table_outside_its_cells_with_a_range_selected_moves_the_table() {
let mut s = session();
let id = table_at(&mut s, &[100.0, 100.0], json!([["a", "b"], ["c", "d"]]));
let drag = |s: &mut Session, kind, x: f64, y: f64| s.pointer(tev(kind, x, y)).unwrap();
s.execute("text.edit", &json!({"id": id, "cell": [0, 0]})).unwrap();
drag(&mut s, PointerKind::Down, 150.0, 120.0);
drag(&mut s, PointerKind::Drag, 250.0, 160.0);
drag(&mut s, PointerKind::Up, 250.0, 160.0);
assert_eq!(s.doc().unwrap().selection.cells, Some((id, (0, 0), (1, 1))));
// Just left of the table's edge, where a press still hits the table but no cell.
drag(&mut s, PointerKind::Down, 98.0, 120.0);
assert_eq!(s.doc().unwrap().selection.cells, None, "the range ends");
drag(&mut s, PointerKind::Drag, 148.0, 170.0);
drag(&mut s, PointerKind::Up, 148.0, 170.0);
let st = s.doc().unwrap();
assert!(st.selection.text.is_none());
assert_eq!(st.selection.shapes, vec![id]);
let x = st.shape(id).unwrap().xfrm.unwrap();
assert!((x.x - 150.0).abs() < 10.0 && (x.y - 150.0).abs() < 10.0, "the table moves: {x:?}");
// A click (no drag) there keeps the table selected and puts no caret in the nearest cell.
let (x0, y0) = (x.x - 2.0, x.y + 20.0);
drag(&mut s, PointerKind::Down, x0, y0);
drag(&mut s, PointerKind::Up, x0, y0);
let st = s.doc().unwrap();
assert_eq!((st.selection.shapes.clone(), st.selection.text.is_none()), (vec![id], true));
}
#[test]
fn a_click_in_a_selected_table_enters_the_clicked_cell() {
let mut s = session();
@@ -1970,6 +2002,45 @@ mod copy_remap {
assert_eq!(img.get_pixel(img.width() / 2, img.height() / 2).0, [255, 0, 0, 255]);
}
#[test]
fn pictures_inserted_after_a_paste_from_another_deck_get_media_ids_of_their_own() {
let mut a = Session::with_new();
let mut b = Session::with_new();
let (pa, ..) = populate(&mut a, b"deck a picture");
// Deck a's picture uses a media id above its shape ids that deck b has not handed out yet.
{
let doc = Arc::make_mut(&mut a.doc_mut().unwrap().doc);
let high = MediaId(doc.next_id + 20);
doc.next_id = high.0;
doc.media[0].id = high;
let slide = Arc::make_mut(&mut doc.slides[0]);
let Some(ShapeKind::Picture { fill }) = slide.shapes.iter_mut().find(|x| x.id == pa).map(|x| &mut x.kind) else { panic!() };
fill.media = high;
}
assert_eq!(picture_bytes(&a, &shape(&a, 0, pa)), b"deck a picture");
select(&mut a, &[pa]);
a.execute("edit.copy", &json!({})).unwrap();
b.clipboard = a.clipboard.clone();
b.doc_mut().unwrap().selection.shapes.clear();
let r = b.execute("edit.paste", &json!({})).unwrap();
let pasted = ShapeId(r["ids"][0].as_u64().unwrap() as u32);
let mut inserted = vec![];
for i in 0..12u8 {
let png = solid_png([i, 0, 0]);
let r = b.execute("insert.picture", &json!({"data": cmd::base64_encode(&png), "name": "a.png"})).unwrap();
inserted.push((ShapeId(r["id"].as_u64().unwrap() as u32), png));
}
assert_eq!(picture_bytes(&b, &shape(&b, 0, pasted)), b"deck a picture");
for (id, png) in inserted {
assert_eq!(picture_bytes(&b, &shape(&b, 0, id)), png);
}
let d = b.doc().unwrap();
let mut ids: Vec<_> = d.doc.media.iter().map(|m| m.id).collect();
ids.sort();
ids.dedup();
assert_eq!(ids.len(), d.doc.media.len(), "media ids are unique");
}
/// Slide 0 of `s` gets a table whose first cell, background and second cell's picture bullet
/// each use a picture of their own, and a transition sound. `tag` starts the bytes of each.
/// Returns the table.
+19 -5
View File
@@ -705,10 +705,13 @@ impl Session {
}
}
Some(Hit::Shape { id, .. })
if !(mods.shift || mods.cmd) && st.selection.cells.is_some_and(|c| c.0 == id && st.selection.shapes == [id]) =>
if !(mods.shift || mods.cmd)
&& st.selection.cells.is_some_and(|c| c.0 == id && st.selection.shapes == [id])
&& in_table_cell(st, id, p) =>
{
// A press in a table whose cells are selected starts again in the cell under the
// pointer: a click puts the caret there and a drag selects another range.
// pointer: a click puts the caret there and a drag selects another range. A press
// on the table outside its cells selects and moves the table like any shape.
let before = self.text_selection();
if self.edit_text_at(id, p)? {
self.select(|_, sel| sel.cells = None)?;
@@ -736,15 +739,17 @@ impl Session {
let origs: Vec<(ShapeId, Xfrm)> =
ids.iter().filter_map(|i| st.shape(*i).filter(|s| !s.locked).map(|s| (*i, xfrm_of(&st.doc, &st.selection, s)))).collect();
// A click (no drag) on text puts the caret there; unselected text boxes and
// placeholders take the caret straight away like PowerPoint. A click in a selected
// table puts the caret in the clicked cell.
// placeholders take the caret straight away like PowerPoint. A click in a cell of a
// selected table puts the caret there; a click on its grab band keeps the table selected.
let table = st.shape(id).is_some_and(|s| matches!(s.kind, ShapeKind::Table(_)));
// A click on the frame band of a text box selects the box whole, as PowerPoint
// does, so it can be moved, duplicated or deleted; only the inside takes the caret.
let on_frame = text
&& is_text_first(st, id)
&& st.shape(id).is_some_and(|s| edit_zone(&xfrm_of(&st.doc, &st.selection, s), p, tol) == EditZone::Frame);
let enter_text = (!toggle && !on_frame && ((text && (selected || is_text_first(st, id))) || (table && selected))).then_some((id, p));
let enter_text =
(!toggle && !on_frame && ((text && (selected || is_text_first(st, id))) || (table && selected && in_table_cell(st, id, p))))
.then_some((id, p));
let ids2 = ids.clone();
self.select(|_, sel| {
sel.shapes = ids2;
@@ -1990,6 +1995,15 @@ pub fn resize_box(o: &Xfrm, h: u8, d: Vec2, keep_aspect: bool, from_center: bool
Xfrm { x: c.x - nw / 2.0, y: c.y - nh / 2.0, w: nw, h: nh, rot: o.rot, flip_h: fh, flip_v: fv }
}
/// Whether `p` lies in a cell of table `id`, not in the grab band around it (`cell_at` would take
/// the nearest cell).
fn in_table_cell(st: &crate::DocState, id: ShapeId, p: Point) -> bool {
st.shape(id).and_then(|sh| cmd::table::table_frame(st, sh)).is_some_and(|f| {
let q = f.local(p);
(0.0..=f.width()).contains(&q.x) && (0.0..=f.height()).contains(&q.y) && f.cell_at(q).is_some()
})
}
fn hit_shape(st: &crate::DocState, sh: &Shape, p: Point, tol: f64) -> Option<Hit> {
let x = xfrm_of(&st.doc, &st.selection, sh);
let inv = x.affine().inverse();
+5 -2
View File
@@ -1,6 +1,9 @@
use super::*;
use std::io::{Cursor, Write};
use std::sync::Arc;
use super::{Budget, Presentation, load, load_checked, load_within, read_entry, read_entry_max, save};
use deckcraft_model::chart::{Chart, ChartPart, ChartRel, ChartType};
use deckcraft_model::{Master, Shape, ShapeId};
use deckcraft_model::{Master, Shape, ShapeId, ShapeKind};
use serde_json::{Value, json};
const PART: &str = "ppt/embeddings/owned.xlsx";
+4 -1
View File
@@ -2,7 +2,10 @@
//! ribbons, gears, tabs and chart markers. Like the rest of `preset`, every outline is our own
//! construction, fitted to how the shapes look and to what their adjust values mean.
use super::*;
use super::{
Corner, Dir, FillMode, Geometry, Handle, Pb, Point, Rect, Vec2, corner_rect, fill, fill_eo, fill_only, mid_sites, orient, shade, simple,
stroke_only, vis,
};
/// Builds the presets this module knows; `None` for any other name.
pub(super) fn build(name: &str, w: f64, h: f64, a: &dyn Fn(usize) -> f64) -> Option<Geometry> {
+3 -1
View File
@@ -1,4 +1,6 @@
use super::*;
use super::{
Band, BezPath, CATALOG, Guide, Point, Rect, WarpLine, adjust_count, adjust_defaults, band_of_line, handles, is_follow, is_supported, warp,
};
const W: f64 = 420.0;
const H: f64 = 200.0;
+5 -1
View File
@@ -1,4 +1,8 @@
use super::*;
use super::{
ColorRef, Fill, LayoutType, Paragraph, PhType, Placeholder, Presentation, Run, RunProps, SchemeSlot, Shape, ShapeId, ShapeKind, ShapeStyle,
Slide, Table, TextBody, Theme, Xfrm, defaults, ink_pressure_is_legacy, ink_sample_width, parent_of, resolve, sibling_list_mut, style, text,
theme, walk,
};
use crate::resolve::Ctx;
#[test]
+1 -1
View File
@@ -1,6 +1,6 @@
//! Synthetic Ogg streams: a small page writer (RFC 3533) and Opus-like packets.
use super::*;
use super::{Codec, Error, OpusTiming, Packet, Stream, crc32, open, opus_packet_samples, sniff};
/// Write one page holding `segments` (lacing already computed by `lace`).
fn page(out: &mut Vec<u8>, flags: u8, granule: i64, serial: u32, seq: u32, lacing: &[u8], body: &[u8]) {
+10 -1
View File
@@ -1,6 +1,15 @@
use std::sync::Arc;
use super::*;
use std::collections::HashMap;
use deckcraft_fonts::FontDb;
use deckcraft_render::RenderOpts;
use kurbo::Rect;
use super::{
MAX_RASTER_PIXELS, PAPERS, PageLayout, PdfError, PdfOptions, Presentation, export, glyph_text_ranges, handout_boxes, master_text_pieces,
master_texts, notes_frames, notes_slide_box, paper_size, raster_scale,
};
use deckcraft_model::text::TextBody;
fn deck(n: usize) -> Presentation {
+23 -2
View File
@@ -36,9 +36,10 @@ pub fn color(w: &mut W, c: &ColorRef) {
let (tag, a) = match &c.base {
ColorBase::Rgb { rgb } => ("a:srgbClr", A::new().a("val", rgb.hex())),
ColorBase::Scheme { slot } => ("a:schemeClr", A::new().a("val", slot.xml_name())),
ColorBase::Preset { name } => ("a:prstClr", A::new().a("val", if name.is_empty() { "black" } else { name })),
// The reader keeps any name a file has; only names the schema allows are written.
ColorBase::Preset { name } => ("a:prstClr", A::new().a("val", if deckcraft_color::is_preset_name(name) { name } else { "black" })),
ColorBase::System { name, last } => {
("a:sysClr", A::new().a("val", if name.is_empty() { "windowText" } else { name }).a("lastClr", last.hex()))
("a:sysClr", A::new().a("val", if deckcraft_color::is_system_name(name) { name } else { "windowText" }).a("lastClr", last.hex()))
}
};
let rgb_alpha = match &c.base {
@@ -788,3 +789,23 @@ pub fn paragraph(w: &mut W, x: &mut Exp, o: &mut Out, p: &Paragraph, math: bool,
pub fn has_math(tb: &TextBody) -> bool {
tb.paragraphs.iter().flat_map(|p| &p.runs).any(|r| matches!(&r.kind, RunKind::Math { omml } if !omml.is_empty()))
}
#[cfg(test)]
mod tests {
use super::*;
use deckcraft_color::Rgba;
#[test]
fn unknown_preset_and_system_names_save_as_black_and_window_text() {
let write = |base: ColorBase| {
let mut w = W::frag();
color(&mut w, &ColorRef { base, mods: vec![] });
w.s
};
assert_eq!(write(ColorBase::Preset { name: "notAColor".into() }), r#"<a:prstClr val="black"/>"#);
assert_eq!(write(ColorBase::Preset { name: "darkBlue".into() }), r#"<a:prstClr val="darkBlue"/>"#);
let last = Rgba::rgb(0x12, 0x34, 0x56);
assert_eq!(write(ColorBase::System { name: "notAColor".into(), last }), r#"<a:sysClr val="windowText" lastClr="123456"/>"#);
assert_eq!(write(ColorBase::System { name: "3dLight".into(), last }), r#"<a:sysClr val="3dLight" lastClr="123456"/>"#);
}
}
+5 -1
View File
@@ -1,4 +1,8 @@
use super::*;
use super::{
Ctx, Fields, Fill, Image, Opts, PathMorph, Point, RENDERER, Rect, Region, RenderOpts, Renderer, Rgba, SlideFields, TextMorph, crop_dest,
dpi_for_scale, gif_frame, image_dpi, images, morph_path, morph_text, needs_filters, parse_path, pasteboard_region, place_slide, render_blend,
render_shapes, render_slide, render_slide_drawn, slide_extent, text_transform,
};
use deckcraft_model::chart::{ChartType, Trendline, TrendlineType};
use deckcraft_model::style::ColorRef;
use deckcraft_model::text::TextBody;
+5 -1
View File
@@ -1,10 +1,14 @@
//! Render tests for shape, line, fill and effect cases checked against PowerPoint's own exports
//! of synthetic "wild" slides (observed black-box; see `plan/powerpoint/tools/gen_wild.py`).
use super::*;
use super::{
BezPath, Fill, Image, Line, MAX_DASHES, PathEl, Point, Rect, RenderOpts, ShapeKind, dash_with_caps, head_size, images, limit_dash_count, paint,
render_slide, trim_ends,
};
use deckcraft_color::Rgba;
use deckcraft_model::style::ColorRef;
use deckcraft_model::{CustomPath, Geom, Presentation, Shape, ShapeId, Xfrm};
use kurbo::Shape as _;
fn deck_with(shapes: Vec<Shape>) -> Presentation {
let mut p = Presentation::default();
+6 -1
View File
@@ -1,4 +1,9 @@
use super::*;
use super::{
Affinity, Align, Anchor, Arc, AutoFit, BodyProps, Bullet, Caps, Cell, Ctx, Dash, Deco, DecoLine, FontDb, FontFace, GlyphRun, LINE_PITCH,
MoveMode, NoFields, Opts, ParaProps, Point, Pos, Rect, Rgba, SUB_SUPER, Shape, Spacing, Strike, TextBody, TextLayout, autonum,
break_opportunities, caps_char, chinese_num, covers_symbol, is_symbol_font, is_upright, layout, layout_scaled, map_symbol_bullet, measure_str,
resolve, shaping_features, symbol_char, symbol_font_char, symbol_font_draw, symbol_unicode, symbol_unicode_alts,
};
use deckcraft_model::Fill;
use deckcraft_model::defaults;
use deckcraft_model::text::{Paragraph, Run};
+1 -1
View File
@@ -12,7 +12,7 @@ use egui_kittest::Harness;
use egui_kittest::kittest::Queryable;
use serde_json::json;
use super::*;
use crate::backstage::{Backstage, NAV_W, Nav, Page, PrintScope, entries, format_bytes, print_page_size, print_params, print_slides, split_path};
use crate::{Services, SlideApp};
fn harness(app: SlideApp) -> Harness<'static, SlideApp> {
+18 -3
View File
@@ -61,6 +61,12 @@ revision history.
- **Impact:** long-session crash rate is sampled in CI; RSS is not measured directly.
- **Estimate:** 1–2 h (RSS sampling per platform).
### A control-server timing test may fail under load
- **Evidence:** `silent_refused_peers_do_not_delay_the_next_refusal` in
`apps/deckcraft/src/control_server.rs` asserts that a refusal arrives in under 1 s, so it can
fail on a busy machine although the server behaves correctly.
- **Estimate:** under 1 h (a looser bound or a logical clock).
## File formats
Detail in [file-format-parity.md](file-format-parity.md).
@@ -260,9 +266,17 @@ and starts at that effect. The pane has an advanced timeline with draggable bars
`review.thesaurus` looks up the selected word (checklist row partial). AutoCorrect has
PowerPoint's options (two initial capitals, sentence and day capitals, caps lock, fractions, an
editable replace list), and AutoFormat superscripts ordinals (1st) and starts bulleted and
numbered lists (`* `, `1. `, Backspace puts the typed text back) as you type; no undo of a
single correction.
- **Estimate:** 1–2 h (bundle openly licensed dictionaries).
numbered lists (`* `, `1. `) as you type; no undo of a single correction. Automatic lists start
only while Replace text as you type is on, where PowerPoint has a separate AutoFormat option,
and Backspace right after one puts the typed lead back, which PowerPoint does not.
- **Estimate:** 1–2 h (bundle openly licensed dictionaries; under 1 h for the AutoFormat option).
### Copying text and cell ranges
- **Evidence:** a copied text range carries no media, so a picture bullet in it is lost when
pasted into another deck; the writer saves a bullet whose picture is missing as `•`. A copied
range of table cells is tab-separated text, not a table, so pasting it makes text rather than
cells.
- **Estimate:** 2–4 h.
### Equations
- **Evidence:** checklist row missing; PR #55 (OMML editor, +10.5k lines) is open.
@@ -357,6 +371,7 @@ Detail in [localization-parity.md](localization-parity.md).
| Date | Change | Summary |
|---|---|---|
| 2026-10-25 | trivial | New entries: copying text and cell ranges (no media, no table), a control-server timing test that may fail under load; Proofing notes that automatic lists follow Replace text as you type and that Backspace puts the typed lead back |
| 2026-10-24 | trivial | AutoFormat starts bulleted and numbered lists as you type (`* `, `-- `, `> `, `1. `, `a) ` and the rest), with an AutoCorrect option to turn it off |
| 2026-10-22 | minor | Animation Pane ▸ Play From starts the preview at the selected effect; gap 10 keeps the effect gallery (7–13 h) |
| 2026-10-24 | trivial | Columns dialog (number and spacing of text columns); dialog count 41 → 42 |
+3 -1
View File
@@ -24,7 +24,9 @@ KEYCHAIN="$TMP_DIR/deckcraft-signing.keychain-db"
CERT="$TMP_DIR/deckcraft-signing.p12"
KC_PASS="${KEYCHAIN_PASSWORD:-$(openssl rand -hex 24)}"
trap 'rm -rf "$TMP_DIR"' EXIT
# Remove only the decoded certificate: the keychain lives in the same directory and codesign
# needs it until the workflow's "Remove temporary keychain" step deletes it.
trap 'rm -f "${CERT:-}"' EXIT
printf '%s' "$APPLE_CERTIFICATE" | base64 --decode >"$CERT"
security create-keychain -p "$KC_PASS" "$KEYCHAIN"
security set-keychain-settings -lut 21600 "$KEYCHAIN"