mirror of
https://github.com/storytold/wordcraft.git
synced 2026-11-12 00:44:27 -06:00
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a98d0a71fe | ||
|
|
b9d0ebbd26 | ||
|
|
1825b9250f | ||
|
|
49cabcafc7 | ||
|
|
4ee514b103 |
@@ -92,6 +92,8 @@ struct Scan {
|
|||||||
j: usize,
|
j: usize,
|
||||||
/// Tab cluster, stop, and x where the tab started.
|
/// Tab cluster, stop, and x where the tab started.
|
||||||
pending_tab: Option<(usize, TabStop, f32)>,
|
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.
|
/// The width of the spaces so far on this line.
|
||||||
space_w: f32,
|
space_w: f32,
|
||||||
has_tab: bool,
|
has_tab: bool,
|
||||||
@@ -113,17 +115,15 @@ pub(crate) fn takes_room(c: &Cluster) -> bool {
|
|||||||
c.kind != ClKind::Marker && !(matches!(c.kind, ClKind::Object(_)) && c.adv == 0.0 && c.obj_h == 0.0)
|
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`.
|
/// How far the text after a decimal tab has been looked at for its first '.' (see
|
||||||
fn width_to_decimal(pl: &ParaLayout, tj: usize, j: usize) -> Option<f32> {
|
/// [`Scan::width_to_decimal`]): the tab, the next cluster to look at, the width before it, and
|
||||||
let mut w = 0.0;
|
/// whether that cluster is the '.'.
|
||||||
for k in tj + 1..=j {
|
#[derive(Clone, Copy, Default)]
|
||||||
let cc = pl.clusters.get(k)?;
|
struct DecimalScan {
|
||||||
if pl_text_at(cc) {
|
tab: usize,
|
||||||
return Some(w);
|
next: usize,
|
||||||
}
|
width: f32,
|
||||||
w += cc.adv;
|
found: bool,
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The objects' exclusions mirrored across a line of `width`, for a right-to-left paragraph.
|
/// The objects' exclusions mirrored across a line of `width`, for a right-to-left paragraph.
|
||||||
@@ -279,6 +279,7 @@ impl<'e> Breaker<'e> {
|
|||||||
end: LineEnd::Para,
|
end: LineEnd::Para,
|
||||||
j: self.i,
|
j: self.i,
|
||||||
pending_tab: None,
|
pending_tab: None,
|
||||||
|
decimal: None,
|
||||||
space_w: 0.0,
|
space_w: 0.0,
|
||||||
has_tab: false,
|
has_tab: false,
|
||||||
skip: false,
|
skip: false,
|
||||||
@@ -917,6 +918,29 @@ impl Scan {
|
|||||||
false
|
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.
|
/// Text after a decimal/centre/right tab shifts as it grows.
|
||||||
fn shift_pending_tab(&mut self, pl: &mut ParaLayout) {
|
fn shift_pending_tab(&mut self, pl: &mut ParaLayout) {
|
||||||
let Some((tj, stop, tx)) = self.pending_tab else { return };
|
let Some((tj, stop, tx)) = self.pending_tab else { return };
|
||||||
@@ -925,7 +949,7 @@ impl Scan {
|
|||||||
TabAlign::Right => stop.pos - tx - seg_w,
|
TabAlign::Right => stop.pos - tx - seg_w,
|
||||||
TabAlign::Center => stop.pos - tx - seg_w / 2.0,
|
TabAlign::Center => stop.pos - tx - seg_w / 2.0,
|
||||||
// Width up to the first '.' in the segment.
|
// 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,
|
_ => 0.0,
|
||||||
};
|
};
|
||||||
let shift = shift.max(0.0);
|
let shift = shift.max(0.0);
|
||||||
|
|||||||
@@ -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");
|
eprintln!("hinted header: {small} steps for 20 pages, {big} for 160");
|
||||||
assert!(small > 0 && big <= small + 8, "the pages aren't walked: {small} → {big}");
|
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);
|
||||||
|
}
|
||||||
|
|||||||
@@ -314,6 +314,7 @@ The front-of-text opt-out is retained from the earlier implementation; this patc
|
|||||||
| 2026-11-01 | trivial | SEQ fields in text boxes are numbered where their boxes are anchored, in output and after F9, and a REF finds a bookmark in a text box |
|
| 2026-11-01 | trivial | SEQ fields in text boxes are numbered where their boxes are anchored, in output and after F9, and a REF finds a bookmark in a text box |
|
||||||
| 2026-11-01 | trivial | SEQ, STYLEREF and REF fields show fresh results in output; readiness estimates unchanged |
|
| 2026-11-01 | trivial | SEQ, STYLEREF and REF fields show fresh results in output; readiness estimates unchanged |
|
||||||
| 2026-10-29 | trivial | Print Markup balloon anchor discovery walks paragraph objects once (#3614); readiness estimates unchanged |
|
| 2026-10-29 | trivial | Print Markup balloon anchor discovery walks paragraph objects once (#3614); readiness estimates unchanged |
|
||||||
|
| 2026-11-03 | trivial | A decimal tab scans the text after it once, not once per cluster. Separate from the #3412 word-measurement fix |
|
||||||
| 2026-10-30 | trivial | Layout work bounds for hostile documents (#3867): header-row repeats budgeted per layout and counting canvas members; picture-outline decode budget charged for cached outlines too, so cold and warm layouts wrap alike; linked-box chains keep exact widths up to a work cap, not 16 widths; `gridAfter` bounded |
|
| 2026-10-30 | trivial | Layout work bounds for hostile documents (#3867): header-row repeats budgeted per layout and counting canvas members; picture-outline decode budget charged for cached outlines too, so cold and warm layouts wrap alike; linked-box chains keep exact widths up to a work cap, not 16 widths; `gridAfter` bounded |
|
||||||
| 2026-10-29 | trivial | A missing font is drawn in an installed metric twin before any other substitute (`fonts::word::METRIC_TWINS`, also what the missing-font warning calls metric-compatible): Arial for Helvetica on Windows, Arial for Liberation Sans on a Mac, Tinos before macOS Times for Times New Roman, Cousine before Menlo for Courier New. Documents in fonts with no installed twin lay out as before |
|
| 2026-10-29 | trivial | A missing font is drawn in an installed metric twin before any other substitute (`fonts::word::METRIC_TWINS`, also what the missing-font warning calls metric-compatible): Arial for Helvetica on Windows, Arial for Liberation Sans on a Mac, Tinos before macOS Times for Times New Roman, Cousine before Menlo for Courier New. Documents in fonts with no installed twin lay out as before |
|
||||||
| 2026-11-01 | trivial | Columns: the line between them runs from the section's top on the page to the bottom of its tallest column; a continuous section mid-page gets its line (#5178) |
|
| 2026-11-01 | trivial | Columns: the line between them runs from the section's top on the page to the bottom of its tallest column; a continuous section mid-page gets its line (#5178) |
|
||||||
|
|||||||
Reference in New Issue
Block a user