Compare commits

...
Author SHA1 Message Date
Dylan M. Taylor b9d0ebbd26 Merge main (keep both layout-parity lines) 2026-11-04 04:06:28 +00:00
Dylan M. Taylor 08afab50b6 Merge pull request #5524 from storytold/rel-0.15.1
Release: WordCraft v0.15.1
2026-11-03 22:15:22 -05:00
Dylan M. Taylor cd6b497c8a Release: WordCraft v0.15.1
Bump the workspace version for the point release carrying the CP4.3
proofing work: one language list, the missing-pack bar on a fresh
install, and the Options › Language pane.
2026-11-03 21:59:44 -05:00
Dylan M. Taylor 1340705fa6 Merge pull request #5515 from storytold/fix/options-language
CP4.3: Options › Language: Your languages with proofing status, Add a language…, Set as default
2026-11-03 21:59:26 -05:00
Dylan M. Taylor 98da959ceb Merge pull request #5514 from storytold/fix/missing-pack-bar
CP4.3: the missing-pack bar works on a fresh install and counts the caret's language
2026-11-03 21:54:20 -05:00
Dylan M. Taylor 1825b9250f Merge branch 'main' into fix/sec-decimal-tab-scan 2026-11-03 17:35:55 -05:00
Dylan M. Taylor 49cabcafc7 address review: layout progress doc for decimal-tab scan 2026-11-03 14:26:57 +00:00
Dylan M. Taylor 4ee514b103 Layout: look at the text after a decimal tab once, not once per cluster
Text after a decimal tab is shifted after each of its clusters, and each
shift walked the text again from the tab to find its first '.'. A long run
without a '.' (soft hyphens take no room, so it stays on one line) made a
single paragraph's line breaking quadratic. The scan now carries on from the
cluster it reached.
2026-11-02 22:23:48 -05:00
5 changed files with 80 additions and 32 deletions
Generated
+19 -19
View File
@@ -6785,7 +6785,7 @@ dependencies = [
[[package]]
name = "wordcraft"
version = "0.15.0"
version = "0.15.1"
dependencies = [
"arboard",
"chrono",
@@ -6820,7 +6820,7 @@ dependencies = [
[[package]]
name = "wordcraft-cli"
version = "0.15.0"
version = "0.15.1"
dependencies = [
"serde_json",
"sys-locale",
@@ -6833,11 +6833,11 @@ dependencies = [
[[package]]
name = "wordcraft-control-key"
version = "0.15.0"
version = "0.15.1"
[[package]]
name = "wordcraft-doc"
version = "0.15.0"
version = "0.15.1"
dependencies = [
"base64 0.23.1",
"log",
@@ -6851,7 +6851,7 @@ dependencies = [
[[package]]
name = "wordcraft-docbin"
version = "0.15.0"
version = "0.15.1"
dependencies = [
"cfb",
"chrono",
@@ -6868,7 +6868,7 @@ dependencies = [
[[package]]
name = "wordcraft-docx"
version = "0.15.0"
version = "0.15.1"
dependencies = [
"aes",
"base64 0.23.1",
@@ -6892,7 +6892,7 @@ dependencies = [
[[package]]
name = "wordcraft-engine"
version = "0.15.0"
version = "0.15.1"
dependencies = [
"cfb",
"chrono",
@@ -6925,7 +6925,7 @@ dependencies = [
[[package]]
name = "wordcraft-fonts"
version = "0.15.0"
version = "0.15.1"
dependencies = [
"brotli-decompressor",
"harfrust 0.13.3",
@@ -6939,7 +6939,7 @@ dependencies = [
[[package]]
name = "wordcraft-formats"
version = "0.15.0"
version = "0.15.1"
dependencies = [
"cfb",
"chardetng",
@@ -6958,7 +6958,7 @@ dependencies = [
[[package]]
name = "wordcraft-geom"
version = "0.15.0"
version = "0.15.1"
dependencies = [
"image",
"serde",
@@ -6966,7 +6966,7 @@ dependencies = [
[[package]]
name = "wordcraft-layout"
version = "0.15.0"
version = "0.15.1"
dependencies = [
"craft-proof",
"image",
@@ -6986,7 +6986,7 @@ dependencies = [
[[package]]
name = "wordcraft-mcp"
version = "0.15.0"
version = "0.15.1"
dependencies = [
"log",
"serde_json",
@@ -6998,7 +6998,7 @@ dependencies = [
[[package]]
name = "wordcraft-metafile"
version = "0.15.0"
version = "0.15.1"
dependencies = [
"kurbo",
"thiserror 2.0.21",
@@ -7006,7 +7006,7 @@ dependencies = [
[[package]]
name = "wordcraft-pdf"
version = "0.15.0"
version = "0.15.1"
dependencies = [
"chrono",
"craft-pdf",
@@ -7027,7 +7027,7 @@ dependencies = [
[[package]]
name = "wordcraft-render"
version = "0.15.0"
version = "0.15.1"
dependencies = [
"image",
"log",
@@ -7041,7 +7041,7 @@ dependencies = [
[[package]]
name = "wordcraft-ui-egui"
version = "0.15.0"
version = "0.15.1"
dependencies = [
"cfb",
"chrono",
@@ -7073,7 +7073,7 @@ dependencies = [
[[package]]
name = "wordcraft-web"
version = "0.15.0"
version = "0.15.1"
dependencies = [
"eframe",
"egui",
@@ -7092,7 +7092,7 @@ dependencies = [
[[package]]
name = "wordcraft-zotero"
version = "0.15.0"
version = "0.15.1"
dependencies = [
"log",
"proptest",
@@ -7255,7 +7255,7 @@ checksum = "9440ea3e5aeabb0ac63af70daf835274065238cdd0cec83418f417eae38bacee"
[[package]]
name = "xtask"
version = "0.15.0"
version = "0.15.1"
dependencies = [
"serde_json",
]
+1 -1
View File
@@ -4,7 +4,7 @@ members = ["crates/*", "apps/*", "xtask"]
default-members = ["crates/*", "apps/wordcraft", "apps/wordcraft-cli", "xtask"]
[workspace.package]
version = "0.15.0"
version = "0.15.1"
edition = "2024"
license = "MIT OR Apache-2.0"
rust-version = "1.95"
+36 -12
View File
@@ -90,6 +90,8 @@ struct Scan {
j: usize,
/// Tab cluster, stop, and x where the tab started.
pending_tab: Option<(usize, TabStop, f32)>,
/// The text after a pending decimal tab looked at so far for its '.'.
decimal: Option<DecimalScan>,
/// The width of the spaces so far on this line.
space_w: f32,
has_tab: bool,
@@ -111,17 +113,15 @@ fn takes_room(c: &Cluster) -> bool {
c.kind != ClKind::Marker && !(matches!(c.kind, ClKind::Object(_)) && c.adv == 0.0 && c.obj_h == 0.0)
}
/// The width up to the first '.' of the text after the tab at `tj`, up to cluster `j`.
fn width_to_decimal(pl: &ParaLayout, tj: usize, j: usize) -> Option<f32> {
let mut w = 0.0;
for k in tj + 1..=j {
let cc = pl.clusters.get(k)?;
if pl_text_at(cc) {
return Some(w);
}
w += cc.adv;
}
None
/// How far the text after a decimal tab has been looked at for its first '.' (see
/// [`Scan::width_to_decimal`]): the tab, the next cluster to look at, the width before it, and
/// whether that cluster is the '.'.
#[derive(Clone, Copy, Default)]
struct DecimalScan {
tab: usize,
next: usize,
width: f32,
found: bool,
}
/// The objects' exclusions mirrored across a line of `width`, for a right-to-left paragraph.
@@ -276,6 +276,7 @@ impl<'e> Breaker<'e> {
end: LineEnd::Para,
j: self.i,
pending_tab: None,
decimal: None,
space_w: 0.0,
has_tab: false,
skip: false,
@@ -839,6 +840,29 @@ impl Scan {
false
}
/// The width up to the first '.' of the text after the tab at `tj`, up to cluster `j`. The
/// text is shifted after each of its clusters, so the clusters already looked at are not
/// walked again: a long run without a '.' after a decimal tab is looked at once, not once
/// per cluster.
fn width_to_decimal(&mut self, pl: &ParaLayout, tj: usize, j: usize) -> Option<f32> {
let start = DecimalScan { tab: tj, next: tj + 1, width: 0.0, found: false };
let d = self.decimal.get_or_insert(start);
if d.tab != tj || d.next > j.saturating_add(1) {
*d = start;
}
while !d.found && d.next <= j {
crate::work::steps(1);
let cc = pl.clusters.get(d.next)?;
if pl_text_at(cc) {
d.found = true;
} else {
d.width += cc.adv;
d.next += 1;
}
}
d.found.then_some(d.width)
}
/// Text after a decimal/centre/right tab shifts as it grows.
fn shift_pending_tab(&mut self, pl: &mut ParaLayout) {
let Some((tj, stop, tx)) = self.pending_tab else { return };
@@ -847,7 +871,7 @@ impl Scan {
TabAlign::Right => stop.pos - tx - seg_w,
TabAlign::Center => stop.pos - tx - seg_w / 2.0,
// Width up to the first '.' in the segment.
TabAlign::Decimal => stop.pos - tx - width_to_decimal(pl, tj, self.j).unwrap_or(seg_w),
TabAlign::Decimal => stop.pos - tx - self.width_to_decimal(pl, tj, self.j).unwrap_or(seg_w),
_ => 0.0,
};
let shift = shift.max(0.0);
+22
View File
@@ -482,3 +482,25 @@ fn selecting_a_header_on_the_hinted_page_does_not_walk_every_page() {
eprintln!("hinted header: {small} steps for 20 pages, {big} for 160");
assert!(small > 0 && big <= small + 8, "the pages aren't walked: {small} → {big}");
}
/// A paragraph of a decimal tab at 200 pt followed by `text`.
fn after_decimal_tab(text: &str) -> Document {
use wordcraft_doc::props::{TabAlign, TabStop};
let mut d = Document::from_text(&format!("\t{text}"));
let at = Pos::body(0, 0);
d.format_paragraphs(&at, &at, &|p| p.tabs = Some(vec![TabStop { pos: 200.0, align: TabAlign::Decimal, leader: Default::default() }])).unwrap();
d
}
/// The text after a decimal tab was looked at again from the tab for its '.' after each of its
/// clusters, so a long run of clusters without a '.' (soft hyphens take no room, so they stay on
/// one line) took quadratic time.
#[test]
fn text_after_a_decimal_tab_is_looked_at_once() {
let opts = LayoutOptions::default();
// The text after the tab still ends at the stop, where it ended before.
let l = lay(&after_decimal_tab("12.5"), &opts);
let (Some(end), Some(start)) = (l.caret(&Pos::body(0, 5)), l.caret(&Pos::body(0, 0))) else { panic!("no carets") };
assert!((end.x - start.x - 200.0).abs() < 1.0, "the text ends at the stop: {} from {}", end.x, start.x);
assert_layout_linear("decimal tab", 2000, |n| after_decimal_tab(&format!("1{}", "\u{AD}".repeat(n))), &opts);
}
File diff suppressed because one or more lines are too long