mirror of
https://github.com/storytold/deckcraft.git
synced 2026-10-30 13:36:52 -06:00
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 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
49278565ea
commit
ae9d525bce
@@ -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<Value> {
|
||||
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;
|
||||
|
||||
@@ -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<Value> {
|
||||
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;
|
||||
|
||||
@@ -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<Value> {
|
||||
}
|
||||
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<Value> {
|
||||
} 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<Value> {
|
||||
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<Value> {
|
||||
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<Value> {
|
||||
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<Value> {
|
||||
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<Value> {
|
||||
..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<Value> {
|
||||
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<Value> {
|
||||
};
|
||||
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<Value> {
|
||||
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()
|
||||
};
|
||||
|
||||
@@ -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<MediaId>) {
|
||||
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<MediaId>) {
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
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<String> {
|
||||
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<Value> {
|
||||
}
|
||||
|
||||
/// 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<Shape>, 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
|
||||
}
|
||||
|
||||
|
||||
@@ -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<CommandSpec> {
|
||||
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<CommandSpec> {
|
||||
]
|
||||
}
|
||||
|
||||
fn theme_named(name: &str) -> Option<slidecraft_model::Theme> {
|
||||
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> {
|
||||
deckcraft_model::theme::builtin_themes().into_iter().find(|t| t.name.eq_ignore_ascii_case(name))
|
||||
}
|
||||
|
||||
fn new(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
@@ -45,7 +45,7 @@ fn new(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
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<Value> {
|
||||
|
||||
/// Recognise and read presentation bytes (native, PPTX).
|
||||
pub fn open_presentation(name: &str, bytes: &[u8]) -> Result<Presentation> {
|
||||
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<String>, p: Presentation) -> usize {
|
||||
@@ -87,7 +87,7 @@ fn open(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
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<Value> {
|
||||
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<Vec<u8>> {
|
||||
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<Vec<u8>> {
|
||||
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<Value> {
|
||||
}
|
||||
|
||||
fn save_bytes_cmd(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
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<Value> {
|
||||
/// Render slide `index` (current when absent) to PNG bytes.
|
||||
pub fn render_png(doc: &Presentation, index: usize, scale: f64, edit: bool) -> (Vec<u8>, 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<Value> {
|
||||
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<Value> {
|
||||
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()}))
|
||||
}
|
||||
|
||||
@@ -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<Value> {
|
||||
}
|
||||
fn text_shadow(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
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<Value> {
|
||||
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<Value> {
|
||||
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<Fill>,
|
||||
pub line: Option<Line>,
|
||||
pub effects: Option<Effects>,
|
||||
pub style: Option<slidecraft_model::ShapeStyle>,
|
||||
pub style: Option<deckcraft_model::ShapeStyle>,
|
||||
}
|
||||
|
||||
fn painter_pick(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
|
||||
@@ -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<CommandSpec> {
|
||||
|
||||
/// 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<Value> {
|
||||
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<f64> = 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<Value> {
|
||||
}
|
||||
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<Value> {
|
||||
|
||||
fn presets(_s: &mut Session, _p: &Value) -> Result<Value> {
|
||||
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<Value> {
|
||||
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<Value> {
|
||||
.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<Value> {
|
||||
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<Value> {
|
||||
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<Value> {
|
||||
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<Value> {
|
||||
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<Value> {
|
||||
_ => 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<Value> {
|
||||
}
|
||||
|
||||
fn hyperlink(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
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<Value> {
|
||||
}
|
||||
|
||||
fn action_button(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
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<Value> {
|
||||
.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<Vec<slidecraft_model::chart::Series>> = p.get("series").and_then(Value::as_array).map(|a| {
|
||||
let series: Option<Vec<deckcraft_model::chart::Series>> = 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<Value> {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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<Value> {
|
||||
let ctx = Ctx::for_slide(&st.doc, sl);
|
||||
let shapes: Vec<Value> = 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!({
|
||||
|
||||
@@ -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<Value> {
|
||||
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<Value> {
|
||||
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<Value> {
|
||||
}
|
||||
|
||||
/// A poster frame PNG for video bytes at `ms`.
|
||||
pub(crate) fn poster_png(bytes: &slidecraft_media::Bytes, ms: u64) -> Option<Vec<u8>> {
|
||||
match slidecraft_media::poster_png(bytes, ms as f64 / 1000.0) {
|
||||
pub(crate) fn poster_png(bytes: &deckcraft_media::Bytes, ms: u64) -> Option<Vec<u8>> {
|
||||
match deckcraft_media::poster_png(bytes, ms as f64 / 1000.0) {
|
||||
Ok(png) => Some(png),
|
||||
Err(e) => {
|
||||
log::info!("no poster frame: {e}");
|
||||
|
||||
@@ -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<CommandSpec> {
|
||||
}
|
||||
|
||||
/// 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;
|
||||
|
||||
@@ -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<Value> {
|
||||
}
|
||||
|
||||
/// Parse a colour param: `"#RRGGBB"`, `"accent1"`…, or `{"scheme":"accent2","lumMod":75000}`.
|
||||
pub(crate) fn color_param(p: &Value, key: &str) -> Option<slidecraft_model::ColorRef> {
|
||||
pub(crate) fn color_param(p: &Value, key: &str) -> Option<deckcraft_model::ColorRef> {
|
||||
let v = p.get(key)?;
|
||||
color_value(v)
|
||||
}
|
||||
|
||||
pub(crate) fn color_value(v: &Value) -> Option<slidecraft_model::ColorRef> {
|
||||
use slidecraft_model::ColorRef;
|
||||
pub(crate) fn color_value(v: &Value) -> Option<deckcraft_model::ColorRef> {
|
||||
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<slidecraft_model::ColorRef> {
|
||||
}
|
||||
|
||||
/// Look a shape up by id in the edited tree.
|
||||
pub(crate) fn shape_of(s: &Session, id: ShapeId) -> Result<slidecraft_model::Shape> {
|
||||
pub(crate) fn shape_of(s: &Session, id: ShapeId) -> Result<deckcraft_model::Shape> {
|
||||
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<Value> {
|
||||
pub(crate) fn edit_shapes(s: &mut Session, p: &Value, cmd: &str, f: impl Fn(&mut deckcraft_model::Shape) -> Result<()>) -> Result<Value> {
|
||||
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<slidecraft_model::Shape> =
|
||||
ids.iter().filter_map(|id| current_shapes(doc, sel).and_then(|v| slidecraft_model::find_shape(v, *id)).cloned()).collect();
|
||||
let st_shapes: Vec<deckcraft_model::Shape> =
|
||||
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<slidecraft_model::Shape>> {
|
||||
pub(crate) fn current_shapes<'a>(doc: &'a deckcraft_model::Presentation, sel: &crate::Selection) -> Option<&'a Vec<deckcraft_model::Shape>> {
|
||||
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),
|
||||
|
||||
@@ -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<Value> {
|
||||
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<Value> {
|
||||
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<Value> {
|
||||
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(())
|
||||
})?;
|
||||
|
||||
@@ -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<Value> {
|
||||
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<Value> {
|
||||
|
||||
fn change(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
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<Value> {
|
||||
}
|
||||
// Transparency on an inherited (style) fill needs the effective colour.
|
||||
let st = s.doc()?;
|
||||
let ctx_fill: Vec<(slidecraft_model::ShapeId, Option<Fill>)> = {
|
||||
let ctx_fill: Vec<(deckcraft_model::ShapeId, Option<Fill>)> = {
|
||||
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<Value> {
|
||||
(
|
||||
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<Value> {
|
||||
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<Value> {
|
||||
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<Value> {
|
||||
fn crop(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
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<Value> {
|
||||
|
||||
fn picture_adjust(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
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<Value> {
|
||||
|
||||
fn picture_reset(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
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<Value> {
|
||||
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<Value> {
|
||||
|
||||
fn media_options(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
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<Value> {
|
||||
Ok(json!({}))
|
||||
}
|
||||
|
||||
fn shape_id(p: &Value, key: &str) -> Option<slidecraft_model::ShapeId> {
|
||||
p.get(key).and_then(Value::as_u64).map(|v| slidecraft_model::ShapeId(v as u32))
|
||||
fn shape_id(p: &Value, key: &str) -> Option<deckcraft_model::ShapeId> {
|
||||
p.get(key).and_then(Value::as_u64).map(|v| deckcraft_model::ShapeId(v as u32))
|
||||
}
|
||||
|
||||
fn sites(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
@@ -722,7 +722,7 @@ fn connect(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
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<Value> {
|
||||
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<Value> {
|
||||
let pts: Vec<slidecraft_geom::Point> = p
|
||||
let pts: Vec<deckcraft_geom::Point> = 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<Value> {
|
||||
}
|
||||
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<slidecraft_geom::Point> = pts.iter().map(|q| slidecraft_geom::Point::new(q.x - bb.x0, q.y - bb.y0)).collect();
|
||||
let local: Vec<deckcraft_geom::Point> = 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(())
|
||||
})?;
|
||||
|
||||
@@ -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<Value> {
|
||||
.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<Value> {
|
||||
|
||||
/// 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<slidecraft_model::Shape> =
|
||||
let lay_shapes: Vec<deckcraft_model::Shape> =
|
||||
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<slidecraft_model::Shape> = std::mem::take(&mut slide.shapes);
|
||||
let mut old: Vec<deckcraft_model::Shape> = 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<Value> {
|
||||
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();
|
||||
|
||||
@@ -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<Value> {
|
||||
}
|
||||
|
||||
fn cell_fill(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
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<Vec<(usize, usize)>> = p
|
||||
.get("cells")
|
||||
.and_then(Value::as_array)
|
||||
|
||||
@@ -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<Ctx<
|
||||
}
|
||||
|
||||
/// Lay out the text being edited (for vertical caret movement and hit testing).
|
||||
pub fn layout_for(st: &DocState, t: &TextSel) -> Option<slidecraft_text::TextLayout> {
|
||||
pub fn layout_for(st: &DocState, t: &TextSel) -> Option<deckcraft_text::TextLayout> {
|
||||
if t.notes || t.cell.is_some() {
|
||||
return None;
|
||||
}
|
||||
@@ -135,12 +135,12 @@ pub fn layout_for(st: &DocState, t: &TextSel) -> Option<slidecraft_text::TextLay
|
||||
let body = sh.text.as_ref()?;
|
||||
let ctx = ctx_for(&st.doc, &st.selection)?;
|
||||
let x = xfrm_of(&st.doc, &st.selection, sh);
|
||||
let geo = slidecraft_render::shape_geometry(sh, x.w, x.h);
|
||||
Some(slidecraft_text::layout(
|
||||
let geo = deckcraft_render::shape_geometry(sh, x.w, x.h);
|
||||
Some(deckcraft_text::layout(
|
||||
&ctx,
|
||||
sh,
|
||||
body,
|
||||
&slidecraft_text::Opts { rect: geo.text_rect, fields: &slidecraft_text::NoFields, prompt_color: None, no_shrink: false },
|
||||
&deckcraft_text::Opts { rect: geo.text_rect, fields: &deckcraft_text::NoFields, prompt_color: None, no_shrink: false },
|
||||
))
|
||||
}
|
||||
|
||||
@@ -175,7 +175,7 @@ fn edit(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
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<V
|
||||
let at = ed::delete(body, a, b);
|
||||
let props = ed::props_at(body, at);
|
||||
let src = TextBody {
|
||||
paragraphs: vec![slidecraft_model::text::Paragraph {
|
||||
paragraphs: vec![deckcraft_model::text::Paragraph {
|
||||
runs: vec![Run { text: text.clone(), props, kind: RunKind::Field { field: field.clone() } }],
|
||||
..Default::default()
|
||||
}],
|
||||
@@ -375,7 +375,7 @@ fn move_caret(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
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<Value> {
|
||||
};
|
||||
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<Value> {
|
||||
pub(crate) fn format_para(s: &mut Session, f: &(dyn Fn(&mut deckcraft_model::text::Paragraph) + Send + Sync)) -> Result<Value> {
|
||||
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<Value> {
|
||||
pub(crate) fn format_body(s: &mut Session, f: &(dyn Fn(&mut deckcraft_model::text::BodyProps) + Send + Sync)) -> Result<Value> {
|
||||
let st = s.doc()?;
|
||||
let ids: Vec<ShapeId> = match &st.selection.text {
|
||||
Some(t) if !t.notes => vec![t.shape],
|
||||
|
||||
@@ -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};
|
||||
|
||||
Reference in New Issue
Block a user