mirror of
https://github.com/storytold/deckcraft.git
synced 2026-11-10 20:39:36 -06:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fc51f46c83 |
Generated
+28
-28
@@ -1028,7 +1028,7 @@ checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deckcraft"
|
name = "deckcraft"
|
||||||
version = "0.15.0"
|
version = "0.17.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arboard",
|
"arboard",
|
||||||
"chrono",
|
"chrono",
|
||||||
@@ -1050,7 +1050,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deckcraft-anim"
|
name = "deckcraft-anim"
|
||||||
version = "0.15.0"
|
version = "0.17.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"deckcraft-geom",
|
"deckcraft-geom",
|
||||||
"deckcraft-model",
|
"deckcraft-model",
|
||||||
@@ -1059,7 +1059,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deckcraft-av1"
|
name = "deckcraft-av1"
|
||||||
version = "0.15.0"
|
version = "0.17.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"deckcraft-bitstream",
|
"deckcraft-bitstream",
|
||||||
"rayon",
|
"rayon",
|
||||||
@@ -1068,14 +1068,14 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deckcraft-bitstream"
|
name = "deckcraft-bitstream"
|
||||||
version = "0.15.0"
|
version = "0.17.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"craft-media",
|
"craft-media",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deckcraft-cli"
|
name = "deckcraft-cli"
|
||||||
version = "0.15.0"
|
version = "0.17.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"deckcraft-engine",
|
"deckcraft-engine",
|
||||||
"deckcraft-mcp",
|
"deckcraft-mcp",
|
||||||
@@ -1085,14 +1085,14 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deckcraft-color"
|
name = "deckcraft-color"
|
||||||
version = "0.15.0"
|
version = "0.17.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deckcraft-engine"
|
name = "deckcraft-engine"
|
||||||
version = "0.15.0"
|
version = "0.17.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"deckcraft-anim",
|
"deckcraft-anim",
|
||||||
@@ -1118,7 +1118,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deckcraft-fonts"
|
name = "deckcraft-fonts"
|
||||||
version = "0.15.0"
|
version = "0.17.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"epaint_default_fonts",
|
"epaint_default_fonts",
|
||||||
"harfrust 0.13.3",
|
"harfrust 0.13.3",
|
||||||
@@ -1130,7 +1130,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deckcraft-format"
|
name = "deckcraft-format"
|
||||||
version = "0.15.0"
|
version = "0.17.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"deckcraft-model",
|
"deckcraft-model",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -1141,7 +1141,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deckcraft-geom"
|
name = "deckcraft-geom"
|
||||||
version = "0.15.0"
|
version = "0.17.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"kurbo",
|
"kurbo",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -1149,7 +1149,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deckcraft-h264"
|
name = "deckcraft-h264"
|
||||||
version = "0.15.0"
|
version = "0.17.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"deckcraft-bitstream",
|
"deckcraft-bitstream",
|
||||||
"rayon",
|
"rayon",
|
||||||
@@ -1158,7 +1158,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deckcraft-hevc"
|
name = "deckcraft-hevc"
|
||||||
version = "0.15.0"
|
version = "0.17.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"deckcraft-bitstream",
|
"deckcraft-bitstream",
|
||||||
"rayon",
|
"rayon",
|
||||||
@@ -1167,21 +1167,21 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deckcraft-isobmff"
|
name = "deckcraft-isobmff"
|
||||||
version = "0.15.0"
|
version = "0.17.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"craft-media",
|
"craft-media",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deckcraft-matroska"
|
name = "deckcraft-matroska"
|
||||||
version = "0.15.0"
|
version = "0.17.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"craft-media",
|
"craft-media",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deckcraft-mcp"
|
name = "deckcraft-mcp"
|
||||||
version = "0.15.0"
|
version = "0.17.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"deckcraft-engine",
|
"deckcraft-engine",
|
||||||
"deckcraft-render",
|
"deckcraft-render",
|
||||||
@@ -1191,7 +1191,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deckcraft-media"
|
name = "deckcraft-media"
|
||||||
version = "0.15.0"
|
version = "0.17.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"deckcraft-av1",
|
"deckcraft-av1",
|
||||||
"deckcraft-h264",
|
"deckcraft-h264",
|
||||||
@@ -1209,7 +1209,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deckcraft-metafile"
|
name = "deckcraft-metafile"
|
||||||
version = "0.15.0"
|
version = "0.17.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"image",
|
"image",
|
||||||
"kurbo",
|
"kurbo",
|
||||||
@@ -1218,7 +1218,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deckcraft-model"
|
name = "deckcraft-model"
|
||||||
version = "0.15.0"
|
version = "0.17.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"deckcraft-color",
|
"deckcraft-color",
|
||||||
"deckcraft-geom",
|
"deckcraft-geom",
|
||||||
@@ -1230,15 +1230,15 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deckcraft-ogg"
|
name = "deckcraft-ogg"
|
||||||
version = "0.15.0"
|
version = "0.17.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deckcraft-opus"
|
name = "deckcraft-opus"
|
||||||
version = "0.15.0"
|
version = "0.17.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deckcraft-pdf"
|
name = "deckcraft-pdf"
|
||||||
version = "0.15.0"
|
version = "0.17.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"craft-pdf",
|
"craft-pdf",
|
||||||
"deckcraft-fonts",
|
"deckcraft-fonts",
|
||||||
@@ -1256,7 +1256,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deckcraft-pptx"
|
name = "deckcraft-pptx"
|
||||||
version = "0.15.0"
|
version = "0.17.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"deckcraft-color",
|
"deckcraft-color",
|
||||||
"deckcraft-engine",
|
"deckcraft-engine",
|
||||||
@@ -1272,7 +1272,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deckcraft-render"
|
name = "deckcraft-render"
|
||||||
version = "0.15.0"
|
version = "0.17.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"deckcraft-anim",
|
"deckcraft-anim",
|
||||||
"deckcraft-color",
|
"deckcraft-color",
|
||||||
@@ -1291,7 +1291,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deckcraft-text"
|
name = "deckcraft-text"
|
||||||
version = "0.15.0"
|
version = "0.17.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"deckcraft-color",
|
"deckcraft-color",
|
||||||
@@ -1305,7 +1305,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deckcraft-ui-egui"
|
name = "deckcraft-ui-egui"
|
||||||
version = "0.15.0"
|
version = "0.17.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"craft-ui",
|
"craft-ui",
|
||||||
"craft-update",
|
"craft-update",
|
||||||
@@ -1331,7 +1331,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deckcraft-vp9"
|
name = "deckcraft-vp9"
|
||||||
version = "0.15.0"
|
version = "0.17.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"deckcraft-bitstream",
|
"deckcraft-bitstream",
|
||||||
"rayon",
|
"rayon",
|
||||||
@@ -1340,7 +1340,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deckcraft-web"
|
name = "deckcraft-web"
|
||||||
version = "0.15.0"
|
version = "0.17.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"craft-web-api",
|
"craft-web-api",
|
||||||
"craft-web-host",
|
"craft-web-host",
|
||||||
@@ -6325,7 +6325,7 @@ checksum = "9440ea3e5aeabb0ac63af70daf835274065238cdd0cec83418f417eae38bacee"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "xtask"
|
name = "xtask"
|
||||||
version = "0.15.0"
|
version = "0.17.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_json",
|
"serde_json",
|
||||||
]
|
]
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ members = ["crates/*", "apps/*", "xtask"]
|
|||||||
default-members = ["crates/*", "apps/deckcraft", "apps/deckcraft-cli"]
|
default-members = ["crates/*", "apps/deckcraft", "apps/deckcraft-cli"]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.15.0"
|
version = "0.17.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
rust-version = "1.95"
|
rust-version = "1.95"
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ pub(super) fn freeform(s: &mut Session, p: &Value) -> Result<Value> {
|
|||||||
a.iter()
|
a.iter()
|
||||||
.filter_map(|v| Some(deckcraft_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())
|
.filter(|q| q.x.is_finite() && q.y.is_finite())
|
||||||
.take(crate::tools::MAX_GESTURE_POINTS)
|
.take(20_000)
|
||||||
.collect()
|
.collect()
|
||||||
})
|
})
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
//! Fonts a presentation uses that this machine doesn't have, so opening it can say so (its text
|
//! Fonts a presentation uses that this machine doesn't have, so opening it can say so (its text
|
||||||
//! draws in a stand-in; see `FontDb::document_face`).
|
//! draws in a stand-in; see `FontDb::document_face`).
|
||||||
|
|
||||||
use std::collections::HashSet;
|
|
||||||
|
|
||||||
use deckcraft_fonts::FontDb;
|
use deckcraft_fonts::FontDb;
|
||||||
use deckcraft_model::resolve::{self, Ctx};
|
use deckcraft_model::resolve::{self, Ctx};
|
||||||
use deckcraft_model::text::{Bullet, TextBody};
|
use deckcraft_model::text::{Bullet, TextBody};
|
||||||
@@ -14,13 +12,8 @@ use deckcraft_model::{Presentation, Shape, ShapeKind};
|
|||||||
/// font to load and are left for the round trip.
|
/// font to load and are left for the round trip.
|
||||||
pub fn install_embedded_fonts(p: &Presentation) {
|
pub fn install_embedded_fonts(p: &Presentation) {
|
||||||
let db = FontDb::global();
|
let db = FontDb::global();
|
||||||
// Faces can name one media item many times; each is copied and enumerated once.
|
|
||||||
let mut seen = HashSet::new();
|
|
||||||
for font in &p.embedded_fonts {
|
for font in &p.embedded_fonts {
|
||||||
for id in font.faces().into_iter().flatten() {
|
for id in font.faces().into_iter().flatten() {
|
||||||
if !seen.insert(id) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let Some(item) = p.media(id) else { continue };
|
let Some(item) = p.media(id) else { continue };
|
||||||
let Some(sfnt) = deckcraft_fonts::embedded_sfnt(&item.data) else { continue };
|
let Some(sfnt) = deckcraft_fonts::embedded_sfnt(&item.data) else { continue };
|
||||||
db.add_font(sfnt.to_vec());
|
db.add_font(sfnt.to_vec());
|
||||||
@@ -34,8 +27,12 @@ pub fn install_embedded_fonts(p: &Presentation) {
|
|||||||
/// and can't be installed, so they aren't reported.
|
/// and can't be installed, so they aren't reported.
|
||||||
pub fn missing_fonts(p: &Presentation) -> Vec<String> {
|
pub fn missing_fonts(p: &Presentation) -> Vec<String> {
|
||||||
let db = FontDb::global();
|
let db = FontDb::global();
|
||||||
// used_fonts is already free of case-insensitive duplicates.
|
let mut out: Vec<String> = Vec::new();
|
||||||
let mut out: Vec<String> = used_fonts(p).into_iter().filter(|f| !f.starts_with('.') && !db.has_family(f)).collect();
|
for family in used_fonts(p) {
|
||||||
|
if !family.starts_with('.') && !db.has_family(&family) && !out.iter().any(|f| f.eq_ignore_ascii_case(&family)) {
|
||||||
|
out.push(family);
|
||||||
|
}
|
||||||
|
}
|
||||||
out.sort_by_key(|f| f.to_lowercase());
|
out.sort_by_key(|f| f.to_lowercase());
|
||||||
out
|
out
|
||||||
}
|
}
|
||||||
@@ -43,10 +40,9 @@ pub fn missing_fonts(p: &Presentation) -> Vec<String> {
|
|||||||
/// The families the slides' text runs and bullets are drawn in.
|
/// The families the slides' text runs and bullets are drawn in.
|
||||||
pub fn used_fonts(p: &Presentation) -> Vec<String> {
|
pub fn used_fonts(p: &Presentation) -> Vec<String> {
|
||||||
let mut out: Vec<String> = Vec::new();
|
let mut out: Vec<String> = Vec::new();
|
||||||
let mut seen: HashSet<String> = HashSet::new();
|
|
||||||
let mut add = |f: String| {
|
let mut add = |f: String| {
|
||||||
let f = f.trim().to_string();
|
let f = f.trim().to_string();
|
||||||
if !f.is_empty() && seen.insert(f.to_ascii_lowercase()) {
|
if !f.is_empty() && !out.iter().any(|o| o.eq_ignore_ascii_case(&f)) {
|
||||||
out.push(f);
|
out.push(f);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -304,12 +304,7 @@ impl Dictionary {
|
|||||||
|
|
||||||
/// `w` made of dictionary parts allowed in compounds (`COMPOUNDFLAG`, or the begin, middle and
|
/// `w` made of dictionary parts allowed in compounds (`COMPOUNDFLAG`, or the begin, middle and
|
||||||
/// end flags), each at least `COMPOUNDMIN` characters long.
|
/// end flags), each at least `COMPOUNDMIN` characters long.
|
||||||
fn compounded(&self, w: &[char], index: usize, failed: &mut HashSet<(usize, usize)>) -> bool {
|
fn compounded(&self, w: &[char], index: usize) -> bool {
|
||||||
// `w` is always a tail of the checked word, so its length names it: a (tail, part index)
|
|
||||||
// that failed once fails again however the earlier parts were split.
|
|
||||||
if failed.contains(&(w.len(), index)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if self.compound.is_none() && self.compound_end.is_none() {
|
if self.compound.is_none() && self.compound_end.is_none() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -327,11 +322,10 @@ impl Dictionary {
|
|||||||
if [self.compound, self.compound_end].into_iter().flatten().any(|f| self.compound_part(rest, f, true)) {
|
if [self.compound, self.compound_end].into_iter().flatten().any(|f| self.compound_part(rest, f, true)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if self.compounded(rest, index + 1, failed) {
|
if self.compounded(rest, index + 1) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
failed.insert((w.len(), index));
|
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -358,7 +352,7 @@ impl Dictionary {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.compounded(&w, 0, &mut HashSet::new())
|
self.compounded(&w, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Is `word` spelt correctly? Capitalised and upper-case forms of a lower-case word pass.
|
/// Is `word` spelt correctly? Capitalised and upper-case forms of a lower-case word pass.
|
||||||
@@ -937,17 +931,6 @@ SFX D Y 1\nSFX D 0 ed/S .\n";
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn overlapping_compound_roots_with_a_failing_ending_stay_cheap() {
|
|
||||||
// Roots a..aaaaaaaa overlap, so many splits of "aaa…ab" reach the same (tail, part) state; unmemoized that is millions of searches.
|
|
||||||
let d = Dictionary::parse(
|
|
||||||
b"COMPOUNDFLAG X\nCOMPOUNDMIN 1\n".as_slice(),
|
|
||||||
b"8\na/X\naa/X\naaa/X\naaaa/X\naaaaa/X\naaaaaa/X\naaaaaaa/X\naaaaaaaa/X\n".as_slice(),
|
|
||||||
);
|
|
||||||
assert!(!d.check(&format!("{}b", "a".repeat(99))));
|
|
||||||
assert!(d.check(&"a".repeat(20)));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn runs_give_words_their_language() {
|
fn runs_give_words_their_language() {
|
||||||
use deckcraft_model::{Paragraph, Run, RunProps};
|
use deckcraft_model::{Paragraph, Run, RunProps};
|
||||||
|
|||||||
@@ -5433,38 +5433,6 @@ fn pen_pressure_is_kept_and_a_mouse_draws_at_full_width() {
|
|||||||
assert_eq!(got, vec![vec![0.2, 1.0, 0.4], vec![1.0, 1.0]]);
|
assert_eq!(got, vec![vec![0.2, 1.0, 0.4], vec![1.0, 1.0]]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn a_long_ink_stroke_is_extended_in_place_and_capped() {
|
|
||||||
let mut s = session();
|
|
||||||
s.set_tool(tools::ToolKind::Ink { mode: "pen".into(), color: deckcraft_model::Rgba::rgb(20, 20, 20), width: 4.0 });
|
|
||||||
let ev = |kind, x: f64| tools::PointerEvent { kind, x, y: 80.0, mods: Mods::default(), tol: 3.0, pressure: Some(0.5) };
|
|
||||||
s.pointer(ev(PointerKind::Down, 0.0)).unwrap();
|
|
||||||
let n = tools::MAX_GESTURE_POINTS + 5_000;
|
|
||||||
for i in 1..n {
|
|
||||||
s.pointer(ev(PointerKind::Drag, (i % 500) as f64)).unwrap();
|
|
||||||
}
|
|
||||||
assert_eq!(s.tool.ink_preview.len(), tools::MAX_GESTURE_POINTS);
|
|
||||||
s.pointer(ev(PointerKind::Up, 0.0)).unwrap();
|
|
||||||
let kept: usize = ink_pressures(&s).iter().map(Vec::len).sum();
|
|
||||||
assert!((2..=tools::MAX_GESTURE_POINTS).contains(&kept), "{kept}");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn double_clicking_a_capped_freeform_keeps_its_vertices() {
|
|
||||||
let mut s = session();
|
|
||||||
let ev = |kind, x: f64, y: f64| tools::PointerEvent { kind, x, y, mods: Default::default(), tol: 3.0, pressure: None };
|
|
||||||
s.set_tool(tools::ToolKind::Shape { preset: "freeform".into() });
|
|
||||||
s.pointer(ev(tools::PointerKind::Down, 100.0, 100.0)).unwrap();
|
|
||||||
// Densely clicked vertices, each within the double-click tolerance of the last.
|
|
||||||
for i in 0..tools::MAX_GESTURE_POINTS + 10 {
|
|
||||||
s.pointer(ev(tools::PointerKind::Down, 300.0 + i as f64 * 0.001, 100.0)).unwrap();
|
|
||||||
}
|
|
||||||
s.pointer(ev(tools::PointerKind::DoubleClick, 300.0, 100.0)).unwrap();
|
|
||||||
let st = s.doc().unwrap();
|
|
||||||
let deckcraft_model::Geom::Custom { paths } = &st.shapes().last().unwrap().geom else { panic!("no freeform") };
|
|
||||||
assert_eq!(paths[0].d.matches(" L ").count(), tools::MAX_GESTURE_POINTS - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ink_pressures(s: &Session) -> Vec<Vec<f32>> {
|
fn ink_pressures(s: &Session) -> Vec<Vec<f32>> {
|
||||||
let st = s.doc().unwrap();
|
let st = s.doc().unwrap();
|
||||||
let Some(sh) = st.shapes().iter().rev().find(|sh| matches!(sh.kind, deckcraft_model::ShapeKind::Ink { .. })) else {
|
let Some(sh) = st.shapes().iter().rev().find(|sh| matches!(sh.kind, deckcraft_model::ShapeKind::Ink { .. })) else {
|
||||||
@@ -6902,27 +6870,6 @@ fn master_body_placeholder_format_keeps_per_level_formatting() {
|
|||||||
assert_eq!(shown(&s), vec![Some(28.0), Some(24.0)]);
|
assert_eq!(shown(&s), vec![Some(28.0), Some(24.0)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn many_distinct_fonts_are_listed_once_each_without_a_quadratic_scan() {
|
|
||||||
// 30,000 runs in 15,000 families, each written twice in different case.
|
|
||||||
let mut p = deckcraft_model::Presentation::default();
|
|
||||||
let slide = std::sync::Arc::make_mut(&mut p.slides[0]);
|
|
||||||
let mut body = deckcraft_model::text::TextBody::from_text("x");
|
|
||||||
let run = body.paragraphs[0].runs[0].clone();
|
|
||||||
body.paragraphs[0].runs = (0..30_000)
|
|
||||||
.map(|i| {
|
|
||||||
let mut r = run.clone();
|
|
||||||
let name = format!("Qqq Font {}", i / 2);
|
|
||||||
r.props.font = Some(if i % 2 == 0 { name } else { name.to_uppercase() });
|
|
||||||
r
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
slide.shapes[0].text = Some(body);
|
|
||||||
let used = crate::fonts::used_fonts(&p);
|
|
||||||
assert_eq!(used.iter().filter(|f| f.starts_with("Qqq Font ")).count(), 15_000);
|
|
||||||
assert_eq!(crate::fonts::missing_fonts(&p).iter().filter(|f| f.to_ascii_lowercase().starts_with("qqq font ")).count(), 15_000);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn slide_master_view_rejects_invalid_indices() {
|
fn slide_master_view_rejects_invalid_indices() {
|
||||||
let mut s = Session::with_new();
|
let mut s = Session::with_new();
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ mod geometry;
|
|||||||
mod hit;
|
mod hit;
|
||||||
mod pointer;
|
mod pointer;
|
||||||
|
|
||||||
pub(crate) use drag::MAX_GESTURE_POINTS;
|
|
||||||
pub use hit::{line_xfrm, resize_box, table_rows, text_pos};
|
pub use hit::{line_xfrm, resize_box, table_rows, text_pos};
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
@@ -276,8 +275,6 @@ pub struct ToolState {
|
|||||||
pub path_preview: Vec<Point>,
|
pub path_preview: Vec<Point>,
|
||||||
pub path_smooth: bool,
|
pub path_smooth: bool,
|
||||||
pub path_closing: bool,
|
pub path_closing: bool,
|
||||||
/// Whether the last freeform press added a vertex (it adds none at the point cap).
|
|
||||||
pub press_added_vertex: bool,
|
|
||||||
/// The shape in Edit Points: its vertices are handles instead of the box's.
|
/// The shape in Edit Points: its vertices are handles instead of the box's.
|
||||||
pub edit_points: Option<ShapeId>,
|
pub edit_points: Option<ShapeId>,
|
||||||
/// The vertex (path, index) whose control handles show in Edit Points.
|
/// The vertex (path, index) whose control handles show in Edit Points.
|
||||||
|
|||||||
@@ -4,12 +4,8 @@ use super::{Drag, Hit, Point, Result, Session, ShapeId, ShapeKind, TextSel, Tool
|
|||||||
impl Session {
|
impl Session {
|
||||||
pub(super) fn double_click(&mut self, p: Point, tol: f64) -> Result<Value> {
|
pub(super) fn double_click(&mut self, p: Point, tol: f64) -> Result<Value> {
|
||||||
if let Some(Drag::Freeform { mode, mut points, .. }) = self.tool.drag.clone() {
|
if let Some(Drag::Freeform { mode, mut points, .. }) = self.tool.drag.clone() {
|
||||||
// The double-click's own click added a duplicate vertex (none at the point cap, where
|
// The double-click's own click added a duplicate vertex.
|
||||||
// the existing vertices are all kept).
|
while points.len() > 2 && points.last().zip(points.get(points.len() - 2)).is_some_and(|(a, b)| (*a - *b).hypot() <= tol) {
|
||||||
while self.tool.press_added_vertex
|
|
||||||
&& points.len() > 2
|
|
||||||
&& points.last().zip(points.get(points.len() - 2)).is_some_and(|(a, b)| (*a - *b).hypot() <= tol)
|
|
||||||
{
|
|
||||||
points.pop();
|
points.pop();
|
||||||
}
|
}
|
||||||
self.tool.drag = Some(Drag::Freeform { mode, points, pressed: false });
|
self.tool.drag = Some(Drag::Freeform { mode, points, pressed: false });
|
||||||
|
|||||||
@@ -12,16 +12,9 @@ struct MoveDrag {
|
|||||||
enter_text: Option<(ShapeId, Point)>,
|
enter_text: Option<(ShapeId, Point)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Samples one ink or freeform gesture keeps; later pointer moves extend it no further.
|
|
||||||
pub(crate) const MAX_GESTURE_POINTS: usize = 100_000;
|
|
||||||
|
|
||||||
impl Session {
|
impl Session {
|
||||||
/// Update the active drag interaction using the pointer position and modifiers.
|
/// Update the active drag interaction using the pointer position and modifiers.
|
||||||
pub(super) fn drag(&mut self, p: Point, mods: Mods, tol: f64) -> Result<Value> {
|
pub(super) fn drag(&mut self, p: Point, mods: Mods, tol: f64) -> Result<Value> {
|
||||||
if matches!(self.tool.drag, Some(Drag::Ink { .. })) {
|
|
||||||
self.extend_ink(p);
|
|
||||||
return Ok(Value::Null);
|
|
||||||
}
|
|
||||||
let Some(drag) = self.tool.drag.clone() else { return Ok(Value::Null) };
|
let Some(drag) = self.tool.drag.clone() else { return Ok(Value::Null) };
|
||||||
match drag {
|
match drag {
|
||||||
Drag::Guide { index } => {
|
Drag::Guide { index } => {
|
||||||
@@ -188,7 +181,7 @@ impl Session {
|
|||||||
}
|
}
|
||||||
Drag::Freeform { mode, mut points, pressed } => {
|
Drag::Freeform { mode, mut points, pressed } => {
|
||||||
// Dragging scribbles (except for curves, which only take clicked points).
|
// Dragging scribbles (except for curves, which only take clicked points).
|
||||||
if pressed && mode != "curve" && points.len() < MAX_GESTURE_POINTS && points.last().is_none_or(|l| (*l - p).hypot() >= 1.5) {
|
if pressed && mode != "curve" && points.last().is_none_or(|l| (*l - p).hypot() >= 1.5) {
|
||||||
points.push(p);
|
points.push(p);
|
||||||
}
|
}
|
||||||
let mut v = points.clone();
|
let mut v = points.clone();
|
||||||
@@ -198,24 +191,15 @@ impl Session {
|
|||||||
self.tool.path_preview = v;
|
self.tool.path_preview = v;
|
||||||
self.tool.drag = Some(Drag::Freeform { mode, points, pressed });
|
self.tool.drag = Some(Drag::Freeform { mode, points, pressed });
|
||||||
}
|
}
|
||||||
// Handled above.
|
Drag::Ink { mut points } => {
|
||||||
Drag::Ink { .. } => {}
|
points.push(self.ink_sample(p));
|
||||||
|
self.tool.ink_preview = points.clone();
|
||||||
|
self.tool.drag = Some(Drag::Ink { points });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(Value::Null)
|
Ok(Value::Null)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ink grows by one sample per move: extend it (and its preview) in place rather than
|
|
||||||
/// copying the whole stroke twice per event, up to [`MAX_GESTURE_POINTS`].
|
|
||||||
fn extend_ink(&mut self, p: Point) {
|
|
||||||
let sample = self.ink_sample(p);
|
|
||||||
if let Some(Drag::Ink { points }) = self.tool.drag.as_mut()
|
|
||||||
&& points.len() < MAX_GESTURE_POINTS
|
|
||||||
{
|
|
||||||
points.push(sample);
|
|
||||||
self.tool.ink_preview.push(sample);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn drag_move(&mut self, drag: MoveDrag, p: Point, mods: Mods, tol: f64) -> Result<Value> {
|
fn drag_move(&mut self, drag: MoveDrag, p: Point, mods: Mods, tol: f64) -> Result<Value> {
|
||||||
let MoveDrag { ids, start, origs, moved, duplicate, enter_text } = drag;
|
let MoveDrag { ids, start, origs, moved, duplicate, enter_text } = drag;
|
||||||
let mut d = p - start;
|
let mut d = p - start;
|
||||||
|
|||||||
@@ -228,10 +228,7 @@ impl Session {
|
|||||||
self.tool.drag = Some(Drag::Freeform { mode, points, pressed: false });
|
self.tool.drag = Some(Drag::Freeform { mode, points, pressed: false });
|
||||||
return self.finish_freeform(true);
|
return self.finish_freeform(true);
|
||||||
}
|
}
|
||||||
self.tool.press_added_vertex = points.len() < super::MAX_GESTURE_POINTS;
|
points.push(p);
|
||||||
if self.tool.press_added_vertex {
|
|
||||||
points.push(p);
|
|
||||||
}
|
|
||||||
self.tool.path_preview = points.clone();
|
self.tool.path_preview = points.clone();
|
||||||
self.tool.drag = Some(Drag::Freeform { mode, points, pressed: true });
|
self.tool.drag = Some(Drag::Freeform { mode, points, pressed: true });
|
||||||
return Ok(Value::Null);
|
return Ok(Value::Null);
|
||||||
|
|||||||
Reference in New Issue
Block a user