From 8c27dc101043a4a24132bd73ad39a592f9c2b2ad Mon Sep 17 00:00:00 2001 From: Laurenz Date: Sun, 28 Mar 2021 12:44:05 +0200 Subject: =?UTF-8?q?Write=20spaces=20and=20linebreaks=20into=20text=20runs?= =?UTF-8?q?=20=E2=9C=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/exec/context.rs | 62 ++++++++++++++++++++++------------------------------- src/exec/mod.rs | 12 +++++------ 2 files changed, 32 insertions(+), 42 deletions(-) (limited to 'src/exec') diff --git a/src/exec/context.rs b/src/exec/context.rs index 6101047e..987f9f7f 100644 --- a/src/exec/context.rs +++ b/src/exec/context.rs @@ -8,7 +8,6 @@ use crate::geom::{Align, Dir, Gen, GenAxis, Length, Linear, Sides, Size}; use crate::layout::{ AnyNode, PadNode, PageRun, ParChild, ParNode, StackChild, StackNode, TextNode, Tree, }; -use crate::parse::{is_newline, Scanner}; use crate::syntax::Span; /// The context for execution. @@ -73,28 +72,14 @@ impl<'a> ExecContext<'a> { /// Push a word space into the active paragraph. pub fn push_word_space(&mut self) { - let em = self.state.font.resolve_size(); - let amount = self.state.par.word_spacing.resolve(em); - self.stack.par.push_soft(ParChild::Spacing(amount)); + self.stack.par.push_soft(self.make_text_node(" ")); } /// Push text into the active paragraph. /// /// The text is split into lines at newlines. - pub fn push_text(&mut self, text: &str) { - let mut scanner = Scanner::new(text); - let mut text = String::new(); - - while let Some(c) = scanner.eat_merging_crlf() { - if is_newline(c) { - self.stack.par.push_text(mem::take(&mut text), &self.state); - self.linebreak(); - } else { - text.push(c); - } - } - - self.stack.par.push_text(text, &self.state); + pub fn push_text(&mut self, text: impl Into) { + self.stack.par.push(self.make_text_node(text)); } /// Push spacing into paragraph or stack depending on `axis`. @@ -112,7 +97,7 @@ impl<'a> ExecContext<'a> { /// Apply a forced line break. pub fn linebreak(&mut self) { - self.stack.par.push_hard(ParChild::Linebreak); + self.stack.par.push_hard(self.make_text_node("\n")); } /// Apply a forced paragraph break. @@ -140,6 +125,12 @@ impl<'a> ExecContext<'a> { self.pagebreak(true, false, Span::default()); Pass::new(self.tree, self.diags) } + + fn make_text_node(&self, text: impl Into) -> ParChild { + let align = self.state.aligns.cross; + let props = self.state.font.resolve_props(); + ParChild::Text(TextNode { text: text.into(), props }, align) + } } struct PageBuilder { @@ -231,24 +222,10 @@ impl ParBuilder { } fn push(&mut self, child: ParChild) { - self.children.extend(self.last.any()); - self.children.push(child); - } - - fn push_text(&mut self, text: String, state: &State) { - self.children.extend(self.last.any()); - - let align = state.aligns.cross; - let props = state.font.resolve_props(); - - if let Some(ParChild::Text(prev, prev_align)) = self.children.last_mut() { - if *prev_align == align && prev.props == props { - prev.text.push_str(&text); - return; - } + if let Some(soft) = self.last.any() { + self.push_inner(soft); } - - self.children.push(ParChild::Text(TextNode { text, props }, align)); + self.push_inner(child); } fn push_soft(&mut self, child: ParChild) { @@ -257,6 +234,19 @@ impl ParBuilder { fn push_hard(&mut self, child: ParChild) { self.last.hard(); + self.push_inner(child); + } + + fn push_inner(&mut self, child: ParChild) { + if let ParChild::Text(curr, curr_align) = &child { + if let Some(ParChild::Text(prev, prev_align)) = self.children.last_mut() { + if prev_align == curr_align && prev.props == curr.props { + prev.text.push_str(&curr.text); + return; + } + } + } + self.children.push(child); } diff --git a/src/exec/mod.rs b/src/exec/mod.rs index 69a41beb..b6765d1e 100644 --- a/src/exec/mod.rs +++ b/src/exec/mod.rs @@ -64,7 +64,7 @@ impl ExecWithMap for Tree { impl ExecWithMap for Node { fn exec_with_map(&self, ctx: &mut ExecContext, map: &NodeMap) { match self { - Node::Text(text) => ctx.push_text(text), + Node::Text(text) => ctx.push_text(text.clone()), Node::Space => ctx.push_word_space(), _ => map[&(self as *const _)].exec(ctx), } @@ -75,9 +75,9 @@ impl Exec for Value { fn exec(&self, ctx: &mut ExecContext) { match self { Value::None => {} - Value::Int(v) => ctx.push_text(&pretty(v)), - Value::Float(v) => ctx.push_text(&pretty(v)), - Value::Str(v) => ctx.push_text(v), + Value::Int(v) => ctx.push_text(pretty(v)), + Value::Float(v) => ctx.push_text(pretty(v)), + Value::Str(v) => ctx.push_text(v.clone()), Value::Template(v) => v.exec(ctx), Value::Error => {} other => { @@ -85,7 +85,7 @@ impl Exec for Value { // the representation in monospace. let prev = Rc::clone(&ctx.state.font.families); ctx.set_monospace(); - ctx.push_text(&pretty(other)); + ctx.push_text(pretty(other)); ctx.state.font.families = prev; } } @@ -104,7 +104,7 @@ impl Exec for TemplateNode { fn exec(&self, ctx: &mut ExecContext) { match self { Self::Tree { tree, map } => tree.exec_with_map(ctx, &map), - Self::Str(v) => ctx.push_text(v), + Self::Str(v) => ctx.push_text(v.clone()), Self::Func(v) => v.exec(ctx), } } -- cgit v1.2.3 From d74c9378b81618419dc8c6315e391b6012955218 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Sat, 3 Apr 2021 17:36:33 +0200 Subject: =?UTF-8?q?New=20paragraph=20layout=20=F0=9F=9A=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous paragraph layout algorithm had a couple of flaws: - It always produced line break opportunities between runs although on the textual level there might have been none. - It didn't handle trailing spacing correctly in some cases. - It wouldn't have been easily adaptable to Knuth-Plass style optimal line breaking because it was fundamentally structured first-fit run-by-run. The new paragraph layout algorithm fixes these flaws. It proceeds roughly in the following stages: 1. Collect all text in the paragraph. 2. Compute BiDi embedding levels. 3. Shape all runs, layout all children and store the resulting items in a reusable (possibly even cacheable) `ParLayout`. 3. Iterate over all line breaks in the concatenated text. 4. Construct lightweight `LineLayout` objects for full lines instead of runs. These mostly borrow from the `ParLayout` and only reshape the first and last run if necessary. The design allows to use Harfbuzz's UNSAFE_TO_BREAK mechanism to make reshaping more efficient. The size of a `LineLayout` can be measured without building the line's frame. 5. Build only the selected line's frames and stack them. --- src/exec/context.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) (limited to 'src/exec') diff --git a/src/exec/context.rs b/src/exec/context.rs index 987f9f7f..d33d62ef 100644 --- a/src/exec/context.rs +++ b/src/exec/context.rs @@ -6,7 +6,7 @@ use crate::env::Env; use crate::eval::TemplateValue; use crate::geom::{Align, Dir, Gen, GenAxis, Length, Linear, Sides, Size}; use crate::layout::{ - AnyNode, PadNode, PageRun, ParChild, ParNode, StackChild, StackNode, TextNode, Tree, + AnyNode, PadNode, PageRun, ParChild, ParNode, StackChild, StackNode, Tree, }; use crate::syntax::Span; @@ -129,7 +129,7 @@ impl<'a> ExecContext<'a> { fn make_text_node(&self, text: impl Into) -> ParChild { let align = self.state.aligns.cross; let props = self.state.font.resolve_props(); - ParChild::Text(TextNode { text: text.into(), props }, align) + ParChild::Text(text.into(), props, align) } } @@ -238,10 +238,12 @@ impl ParBuilder { } fn push_inner(&mut self, child: ParChild) { - if let ParChild::Text(curr, curr_align) = &child { - if let Some(ParChild::Text(prev, prev_align)) = self.children.last_mut() { - if prev_align == curr_align && prev.props == curr.props { - prev.text.push_str(&curr.text); + if let ParChild::Text(curr_text, curr_props, curr_align) = &child { + if let Some(ParChild::Text(prev_text, prev_props, prev_align)) = + self.children.last_mut() + { + if prev_align == curr_align && prev_props == curr_props { + prev_text.push_str(&curr_text); return; } } -- cgit v1.2.3 From 3d2ee54848db80a8ede7e00fd5a53bc059138122 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Wed, 7 Apr 2021 01:01:30 +0200 Subject: =?UTF-8?q?Refactor=20and=20comment=20=E2=99=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/exec/state.rs | 1 + 1 file changed, 1 insertion(+) (limited to 'src/exec') diff --git a/src/exec/state.rs b/src/exec/state.rs index c579bc4e..82f653e9 100644 --- a/src/exec/state.rs +++ b/src/exec/state.rs @@ -97,6 +97,7 @@ pub struct ParState { /// The spacing between lines (dependent on scaled font size). pub leading: Linear, /// The spacing between words (dependent on scaled font size). + // TODO: Don't ignore this. pub word_spacing: Linear, } -- cgit v1.2.3