From 57ca9628c14378e8816a8a1f1530ae69c2269907 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Sat, 27 Mar 2021 21:52:39 +0100 Subject: [PATCH] =?UTF-8?q?Better=20space=20coalescing=20logic=20?= =?UTF-8?q?=F0=9F=8C=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This creates a smaller state machine helper type for softness coalescing, which does not own the resulting nodes. While this creates a bit more duplication in stack and par builder, it makes it a lot easier to integrate additional logic into the paragraph builder. Furthermore: - Line breaks are now "hard", that is, not coalesced with each other. - Text nodes with equal style are now merged allowing for example `f{}i` to form a ligature. --- src/exec/context.rs | 215 +++++++++++++++++---------------- src/layout/par.rs | 3 + src/library/align.rs | 2 +- src/library/image.rs | 2 +- src/library/lang.rs | 2 +- src/library/markup.rs | 10 +- src/library/pad.rs | 2 +- src/library/page.rs | 6 +- src/library/par.rs | 2 +- src/library/shapes.rs | 8 +- src/library/spacing.rs | 2 +- tests/ref/control/for.png | Bin 2715 -> 2700 bytes tests/ref/markup/heading.png | Bin 5177 -> 5187 bytes tests/ref/markup/linebreak.png | Bin 4292 -> 3574 bytes tests/ref/markup/nbsp.png | Bin 1769 -> 1664 bytes tests/typ/control/for.typ | 4 +- tests/typ/expand.typ | 2 +- tests/typ/expr/call.typ | 2 +- tests/typ/library/font.typ | 2 +- tests/typ/markup/linebreak.typ | 14 +-- tests/typ/markup/nbsp.typ | 3 +- tests/typ/repr.typ | 12 +- tests/typ/spacing.typ | 8 +- 23 files changed, 149 insertions(+), 152 deletions(-) diff --git a/src/exec/context.rs b/src/exec/context.rs index 333ad3bad..6101047e1 100644 --- a/src/exec/context.rs +++ b/src/exec/context.rs @@ -65,87 +65,66 @@ impl<'a> ExecContext<'a> { mem::replace(&mut self.stack, stack).build() } + /// Push any node into the active paragraph. + pub fn push(&mut self, node: impl Into) { + let align = self.state.aligns.cross; + self.stack.par.push(ParChild::Any(node.into(), align)); + } + + /// 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)); + } + /// 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 line = String::new(); - let push = |this: &mut Self, text| { - let props = this.state.font.resolve_props(); - let node = TextNode { text, props }; - let align = this.state.aligns.cross; - this.stack.par.folder.push(ParChild::Text(node, align)) - }; + let mut text = String::new(); while let Some(c) = scanner.eat_merging_crlf() { if is_newline(c) { - push(self, mem::take(&mut line)); - self.push_linebreak(); + self.stack.par.push_text(mem::take(&mut text), &self.state); + self.linebreak(); } else { - line.push(c); + text.push(c); } } - push(self, line); - } - - /// Push a word space. - pub fn push_word_space(&mut self) { - let em = self.state.font.resolve_size(); - let amount = self.state.par.word_spacing.resolve(em); - self.push_spacing(GenAxis::Cross, amount, 1); - } - - /// Apply a forced line break. - pub fn push_linebreak(&mut self) { - let em = self.state.font.resolve_size(); - let amount = self.state.par.leading.resolve(em); - self.push_spacing(GenAxis::Main, amount, 2); - } - - /// Apply a forced paragraph break. - pub fn push_parbreak(&mut self) { - let em = self.state.font.resolve_size(); - let amount = self.state.par.spacing.resolve(em); - self.push_spacing(GenAxis::Main, amount, 1); + self.stack.par.push_text(text, &self.state); } /// Push spacing into paragraph or stack depending on `axis`. - /// - /// The `softness` configures how the spacing interacts with surrounding - /// spacing. - pub fn push_spacing(&mut self, axis: GenAxis, amount: Length, softness: u8) { + pub fn push_spacing(&mut self, axis: GenAxis, amount: Length) { match axis { GenAxis::Main => { - let spacing = StackChild::Spacing(amount); - self.stack.finish_par(&self.state); - self.stack.folder.push_soft(spacing, softness); + self.stack.parbreak(&self.state); + self.stack.push_hard(StackChild::Spacing(amount)); } GenAxis::Cross => { - let spacing = ParChild::Spacing(amount); - self.stack.par.folder.push_soft(spacing, softness); + self.stack.par.push_hard(ParChild::Spacing(amount)); } } } - /// Push any node into the active paragraph. - pub fn push_into_par(&mut self, node: impl Into) { - let align = self.state.aligns.cross; - self.stack.par.folder.push(ParChild::Any(node.into(), align)); + /// Apply a forced line break. + pub fn linebreak(&mut self) { + self.stack.par.push_hard(ParChild::Linebreak); } - /// Push any node directly into the stack of paragraphs. - /// - /// This finishes the active paragraph and starts a new one. - pub fn push_into_stack(&mut self, node: impl Into) { - let aligns = self.state.aligns; - self.stack.finish_par(&self.state); - self.stack.folder.push(StackChild::Any(node.into(), aligns)); + /// Apply a forced paragraph break. + pub fn parbreak(&mut self) { + let em = self.state.font.resolve_size(); + let amount = self.state.par.spacing.resolve(em); + self.stack.parbreak(&self.state); + self.stack.push_soft(StackChild::Spacing(amount)); } - /// Finish the active page. - pub fn finish_page(&mut self, keep: bool, hard: bool, source: Span) { + /// Apply a forced page break. + pub fn pagebreak(&mut self, keep: bool, hard: bool, source: Span) { if let Some(builder) = &mut self.page { let page = mem::replace(builder, PageBuilder::new(&self.state, hard)); let stack = mem::replace(&mut self.stack, StackBuilder::new(&self.state)); @@ -158,7 +137,7 @@ impl<'a> ExecContext<'a> { /// Finish execution and return the created layout tree. pub fn finish(mut self) -> Pass { assert!(self.page.is_some()); - self.finish_page(true, false, Span::default()); + self.pagebreak(true, false, Span::default()); Pass::new(self.tree, self.diags) } } @@ -189,7 +168,8 @@ impl PageBuilder { struct StackBuilder { dirs: Gen, - folder: SoftFolder, + children: Vec, + last: Last, par: ParBuilder, } @@ -197,20 +177,36 @@ impl StackBuilder { fn new(state: &State) -> Self { Self { dirs: Gen::new(Dir::TTB, state.lang.dir), - folder: SoftFolder::new(), + children: vec![], + last: Last::None, par: ParBuilder::new(state), } } - fn finish_par(&mut self, state: &State) { + fn push_soft(&mut self, child: StackChild) { + self.last.soft(child); + } + + fn push_hard(&mut self, child: StackChild) { + self.last.hard(); + self.children.push(child); + } + + fn parbreak(&mut self, state: &State) { let par = mem::replace(&mut self.par, ParBuilder::new(state)); - self.folder.extend(par.build()); + if let Some(par) = par.build() { + self.children.extend(self.last.any()); + self.children.push(par); + } } fn build(self) -> StackNode { - let Self { dirs, mut folder, par } = self; - folder.extend(par.build()); - StackNode { dirs, children: folder.finish() } + let Self { dirs, mut children, par, mut last } = self; + if let Some(par) = par.build() { + children.extend(last.any()); + children.push(par); + } + StackNode { dirs, children } } } @@ -218,7 +214,8 @@ struct ParBuilder { aligns: Gen, dir: Dir, line_spacing: Length, - folder: SoftFolder, + children: Vec, + last: Last, } impl ParBuilder { @@ -228,13 +225,43 @@ impl ParBuilder { aligns: state.aligns, dir: state.lang.dir, line_spacing: state.par.leading.resolve(em), - folder: SoftFolder::new(), + children: vec![], + last: Last::None, } } + 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; + } + } + + self.children.push(ParChild::Text(TextNode { text, props }, align)); + } + + fn push_soft(&mut self, child: ParChild) { + self.last.soft(child); + } + + fn push_hard(&mut self, child: ParChild) { + self.last.hard(); + self.children.push(child); + } + fn build(self) -> Option { - let Self { aligns, dir, line_spacing, folder } = self; - let children = folder.finish(); + let Self { aligns, dir, line_spacing, children, .. } = self; (!children.is_empty()).then(|| { let node = ParNode { dir, line_spacing, children }; StackChild::Any(node.into(), aligns) @@ -242,54 +269,28 @@ impl ParBuilder { } } -/// This is used to remove leading and trailing word/line/paragraph spacing -/// as well as collapse sequences of spacings into just one. -struct SoftFolder { - nodes: Vec, - last: Last, -} - +/// Finite state machine for spacing coalescing. enum Last { None, - Hard, - Soft(N, u8), + Any, + Soft(N), } -impl SoftFolder { - fn new() -> Self { - Self { nodes: vec![], last: Last::Hard } - } - - fn push(&mut self, node: N) { - let last = mem::replace(&mut self.last, Last::None); - if let Last::Soft(soft, _) = last { - self.nodes.push(soft); - } - self.nodes.push(node); - } - - fn push_soft(&mut self, node: N, softness: u8) { - if softness == 0 { - self.last = Last::Hard; - self.nodes.push(node); - } else { - match self.last { - Last::Hard => {} - Last::Soft(_, other) if softness >= other => {} - _ => self.last = Last::Soft(node, softness), - } +impl Last { + fn any(&mut self) -> Option { + match mem::replace(self, Self::Any) { + Self::Soft(soft) => Some(soft), + _ => None, } } - fn finish(self) -> Vec { - self.nodes - } -} - -impl Extend for SoftFolder { - fn extend>(&mut self, iter: T) { - for elem in iter { - self.push(elem); + fn soft(&mut self, soft: N) { + if let Self::Any = self { + *self = Self::Soft(soft); } } + + fn hard(&mut self) { + *self = Self::None; + } } diff --git a/src/layout/par.rs b/src/layout/par.rs index 02e27cbdc..e0b428216 100644 --- a/src/layout/par.rs +++ b/src/layout/par.rs @@ -23,6 +23,8 @@ pub enum ParChild { Text(TextNode, Align), /// Any child node and how to align it in its line. Any(AnyNode, Align), + /// A forced linebreak. + Linebreak, } /// A consecutive, styled run of text. @@ -55,6 +57,7 @@ impl Layout for ParNode { layouter.push_frame(frame, align); } } + ParChild::Linebreak => layouter.finish_line(), } } layouter.finish() diff --git a/src/library/align.rs b/src/library/align.rs index d5811bf4a..ccb25b346 100644 --- a/src/library/align.rs +++ b/src/library/align.rs @@ -52,7 +52,7 @@ pub fn align(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { if let Some(vertical) = vertical { ctx.state.aligns.main = vertical.to_align(Dir::TTB); if ctx.state.aligns.main != snapshot.aligns.main { - ctx.push_linebreak(); + ctx.parbreak(); } } diff --git a/src/library/image.rs b/src/library/image.rs index 020f7d50d..ed7b2268a 100644 --- a/src/library/image.rs +++ b/src/library/image.rs @@ -23,7 +23,7 @@ pub fn image(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { let loaded = ctx.env.resources.load(&path.v, ImageResource::parse); if let Some((res, img)) = loaded { let dimensions = img.buf.dimensions(); - ctx.push_into_par(ImageNode { res, dimensions, width, height }); + ctx.push(ImageNode { res, dimensions, width, height }); } else { ctx.diag(error!(path.span, "failed to load image")); } diff --git a/src/library/lang.rs b/src/library/lang.rs index 79015c7d2..1248b707d 100644 --- a/src/library/lang.rs +++ b/src/library/lang.rs @@ -32,7 +32,7 @@ pub fn lang(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { } } - ctx.push_parbreak(); + ctx.parbreak(); }) } diff --git a/src/library/markup.rs b/src/library/markup.rs index d7d750ea1..ac2356a9c 100644 --- a/src/library/markup.rs +++ b/src/library/markup.rs @@ -14,7 +14,7 @@ use crate::syntax::{HeadingNode, RawNode}; /// A template that inserts a line break. pub fn linebreak(_: &mut EvalContext, _: &mut FuncArgs) -> Value { Value::template(Node::LINEBREAK, move |ctx| { - ctx.push_linebreak(); + ctx.linebreak(); }) } @@ -24,7 +24,7 @@ pub fn linebreak(_: &mut EvalContext, _: &mut FuncArgs) -> Value { /// A template that inserts a paragraph break. pub fn parbreak(_: &mut EvalContext, _: &mut FuncArgs) -> Value { Value::template(Node::PARBREAK, move |ctx| { - ctx.push_parbreak(); + ctx.parbreak(); }) } @@ -118,7 +118,7 @@ pub fn heading(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { body.exec(ctx); ctx.state = snapshot; - ctx.push_parbreak(); + ctx.parbreak(); }) } @@ -155,7 +155,7 @@ pub fn raw(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { Value::template(Node::RAW, move |ctx| { if block { - ctx.push_parbreak(); + ctx.parbreak(); } let snapshot = ctx.state.clone(); @@ -164,7 +164,7 @@ pub fn raw(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { ctx.state = snapshot; if block { - ctx.push_parbreak(); + ctx.parbreak(); } }) } diff --git a/src/library/pad.rs b/src/library/pad.rs index d6b690070..7c422239d 100644 --- a/src/library/pad.rs +++ b/src/library/pad.rs @@ -32,6 +32,6 @@ pub fn pad(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { Value::template("pad", move |ctx| { let child = ctx.exec_group(&body).into(); - ctx.push_into_par(PadNode { padding, child }); + ctx.push(PadNode { padding, child }); }) } diff --git a/src/library/page.rs b/src/library/page.rs index fb3542edc..7b8557bef 100644 --- a/src/library/page.rs +++ b/src/library/page.rs @@ -83,13 +83,13 @@ pub fn page(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { std::mem::swap(&mut page.size.width, &mut page.size.height); } - ctx.finish_page(false, true, span); + ctx.pagebreak(false, true, span); if let Some(body) = &body { // TODO: Restrict body to a single page? body.exec(ctx); ctx.state = snapshot; - ctx.finish_page(true, false, span); + ctx.pagebreak(true, false, span); } }) } @@ -101,6 +101,6 @@ pub fn page(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { pub fn pagebreak(_: &mut EvalContext, args: &mut FuncArgs) -> Value { let span = args.span; Value::template("pagebreak", move |ctx| { - ctx.finish_page(true, true, span); + ctx.pagebreak(true, true, span); }) } diff --git a/src/library/par.rs b/src/library/par.rs index cf2549bf7..92fc20e8a 100644 --- a/src/library/par.rs +++ b/src/library/par.rs @@ -27,6 +27,6 @@ pub fn par(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { ctx.state.par.word_spacing = word_spacing; } - ctx.push_parbreak(); + ctx.parbreak(); }) } diff --git a/src/library/shapes.rs b/src/library/shapes.rs index 6f9e66770..6c6a2f0b3 100644 --- a/src/library/shapes.rs +++ b/src/library/shapes.rs @@ -63,13 +63,13 @@ fn rect_impl( let node = FixedNode { width, height, aspect, child }; if let Some(color) = fill { - ctx.push_into_par(BackgroundNode { + ctx.push(BackgroundNode { shape: BackgroundShape::Rect, fill: Fill::Color(color), child: node.into(), }); } else { - ctx.push_into_par(node); + ctx.push(node); } }) } @@ -146,13 +146,13 @@ fn ellipse_impl( }; if let Some(color) = fill { - ctx.push_into_par(BackgroundNode { + ctx.push(BackgroundNode { shape: BackgroundShape::Ellipse, fill: Fill::Color(color), child: node.into(), }); } else { - ctx.push_into_par(node); + ctx.push(node); } }) } diff --git a/src/library/spacing.rs b/src/library/spacing.rs index 6a67a6535..36cd88fbd 100644 --- a/src/library/spacing.rs +++ b/src/library/spacing.rs @@ -32,7 +32,7 @@ fn spacing_impl( Value::template(name, move |ctx| { if let Some(linear) = spacing { let amount = linear.resolve(ctx.state.font.resolve_size()); - ctx.push_spacing(axis, amount, 0); + ctx.push_spacing(axis, amount); } }) } diff --git a/tests/ref/control/for.png b/tests/ref/control/for.png index 1c7dfd42e0386542f358d60d1315ea3cc53feacf..bf6b535c66602bb2d227c2ce78db4456f0cf644c 100644 GIT binary patch literal 2700 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 literal 2715 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 diff --git a/tests/ref/markup/heading.png b/tests/ref/markup/heading.png index a32229e7a8775ceae49cc5fa9ce03cc679a7522e..a7de742d2801e4ce9311819e716802054c7ea62e 100644 GIT binary patch literal 5187 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 BOO5~l delta 4769 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 diff --git a/tests/ref/markup/linebreak.png b/tests/ref/markup/linebreak.png index 512fa0f5e0d1c549263dd8234ca8be382fe567e6..32236e7428f4d4bf6013247c27dce89618c189fb 100644 GIT binary patch literal 3574 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 literal 4292 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 JRU#j~_#Xna`G5cb diff --git a/tests/ref/markup/nbsp.png b/tests/ref/markup/nbsp.png index cc920776a177b46de0041f606816a71b7eaafc6a..8834bb2bcf8ec6da115bfea95592a6179edf2951 100644 GIT binary patch delta 1660 zcmV-?27~$O4S)@h7k_*R00000s%4SR000I@Nkl zfIGk)$Q_^#lnzJ-N(ZC^N}z--;eQ_xGkKo7dC6VQNuJpc877iR_!+Gf7DuE-ixw?f zv}nU^4;BrfAvA=B&=49zLud#Mp&>MchR_fiLPKZ>4S(Uj2>NIUfJ?OnIiyP$ z9}NMRWSWl{#N|;Q4FSlb0!{*e_4r3bh#9CuPw_DkO4Om#5E?>lNlcgdq5!%s^V4G@ zSid_t18O2VE)R*Y15N<90896%IRP(`ETb{TJmyTO2=S+<$j?F89~~j0a(*m?rM~MG zK_WQ00@nvY$bV}m-E#)Wvs{%t%SF*qhIt4Ng0O8dM!y537-K%ZqQU#0oE+QAQbTA6 z4WS`4goe-%8p2%>(&cv#(2$?pAB_-Czgu1ajF>-rCWPN104lyVmoEQl2=KmzriuVo z(xW4Qezn1OK=@%fA$ijJbnHsVPwPu*tx2EP_$$K8aetm(Q(hAg>kD7jR1wna>k4{} zKn%5SNbCAi_dcM{>$5_-W?t6k+Q;$ZUe>3~+ur|g1kSR!#hp3@xcaz{Q-Xbv;QUzM zSs=>QdEa*dwoZbGLv4t@OOWhRIuH+0T*iwiI?3V|ClkPROnWb#WB{E)vKxy?aNf$K zklfNs27j{ILN7aNx43obE(l4iLXr(w9H9N6U?l>!mR=yr2I$yjFtU-9a{!DZO1*iA zhb00S0d}$Vl~E$Zin*$u7A&RjC=raQPWMcVw+Llc)Nw|V zF+ONTK-?|Dv0fbkS;mrFZV|{Q557K7qE@<8yC@S*p9YCA8n`9CFTw_p-(C*!=5o+_ z5Pw1~R_Mi~yhq2m&z1Tx*bxt1LtD1Nq1dmkCUfYD}U>=W;sKUDZ*J2?6Lq^&fw*_P6NQ~FnI9N zC3IR~ll9fgz9RWy|+NL#=8u-#oL5mysHXn_h$4i0e&qrt0xd3?CjI$9?*Sj3X1%K4_ zC!1>Tce)(z6lvW3(wD4L<1@0~PeKTzLe2dTija?IMf1orA(eZJj{3-#!`MATHaC9{ zEuxQvpvx#HcQfS87`s0T!WTtDXbArj;pJ2I<-~8byXS)ld-oZe7W42^7g|efpR#>* z|Jewivxhe}gog0b5#Tjd1YT<$m48$oarO0|Dyu+#t;5eGtq6Q|E%u`bkk8JYL?WP9 z=f^1l?AE&}!`1rnczgF=PB9x1M;c|!B661m6Rw41X}p>$=XT zn_OW#j;c}cWs&e$qV~0L;90)99Q{#*x`cit#-S|x7)en=j4@{PBx^ktb_r;Iaxmw* zCZ21>qO3??zDYj#5W?~KmMF0-yO(wLD0M8Um#Ma)xA@>XO|}mFm$FS1`MBb8d|*wD zGI7?_O}T2v*+on{|&>35Oz6dvXL#3%0_`wW13##i@_vV>IyMnB||8AWRU=P zS1VNzC4iMDh{vq@Aqa$%hzVjnq;XiUHkA>In8e<%S5tEWu$LVSM|IKlbXlzjw%&9b z#KH7?>_mo`r2kO}kTSV$*Qs~J`cDOjzs~E#+b6>GPxpe_;ZH&MQ==g?goe-%8bU*8 z2o0ejG=zrG5E?>5Xb26VAvA!h-6uZjk%Qb&~eMq#%3GC!{H$07>+}V z&dyvSijhz`F3m|sF1cSek8(X`d3rvd=XrO&Z+@?S@4lbUm!`O^n4?Y&0)emK=8-f2 zkm0&I*?Y&2iHb$BwXG_t)ly3xYk)OEXBs{Scag1-`T$u2;>_1B7(Qg4_}F5{9C3TT zMmT5mniM@SB9;m=aON}Gi|i|F{?7_~=G3(mPoy`f=J5AJUH1dG1@%)wdJ+_PzQDpI z%%*t{11${N#UJHVcMZ~qF53yMGd$Q76?);6Jq_!Jc_NN;1iWt13e^G>m>v!tNwSdg zlZf?tiWO`KmjB?A1O=P6MffFm@R#BojZNGa1&E|qCF=x`SG=WrikA|=V{mWPmB}DW zSuq@{z(%n!H)S=nkciu{tNvICELYw=dSzS7p_ujYP&tFZ0?=X#lFcfF+Aj*Un+cr@ zZ_T>6qWRt7t)%9N6!X;nJ~{JhoRvuK*CW;Lmu^6`uj348=otp5zYYhD`d5#h(Wut) zL}y;Bi!}__X8l%&u5*1<>uO|lNt3A&n|iM6vPD%y3Kv2E>>$^CKhK4TrfIJi)A4=m zuoj+zEd8+0<$ky*uW zouXz-)6-A(1fm=@+>J0A)DY@*--K(x4PX>dW*ud1HgT-{oiA##&Gj${0Zda^LuN-bHrtr-)SU&A7g~FE>H) z*i4td;xDr1J3fPQwF=GR*!|qt&r^pWf$K?<3IcW({?-0<6j<4}TT(FkG z`Wej9cLA}qA6_!rwl#O;5min^?m}qgNQcw16v}YbKptU0-_kBWW6M0*hD&!oOEr8V z2iDWxbfOkYBfkudza%3sG?39T-RMu5$|XSWgm`6~sIhl5LNL~G)F(~wlU6V>m#A^~ zD!i8;L5!=t$)cFG=WQN#$d|sZiNvfY;V1qJVRa*<(y1n+Ezw359J)m{f4c79i=^7za3hbWvAm>ySfYl`F~kq_5z@f#mr8Ir{u4 zd?YdR1sRD@s|c^!9kO5D?wx2<9tERiPz9;dv`KrD5HOJ`E7xg&YpGm3D;aBS&ccor{?$!nD{N4Vv&Z_DN!m)H z$EawT8113g=7ab@sgpww{>Z`%@!ml1RFvbP&i5liN*wdm&c*}h8l(IR0^#RJ>tyed z7~4qeJuE^mGwEAe=A?TkVEgo4kJ$?}8V~#tmvu^!@lJ;le%x}@Sv@)v%*m7nCbBYqJ$Y3nsR{B#^_u<*)$88nc8BW;Gp4;4_M*r(mf6QRiz~-Y7i#O zLm#gpgkkOX_Iw=ECR<*Ai3Sf`v47u>vSd%}|JZtX0C^?sMIBDGx%c0oxn8{D#Bm6q F{|9qRLW%$Y diff --git a/tests/typ/control/for.typ b/tests/typ/control/for.typ index 5eaa6ae84..bca1af468 100644 --- a/tests/typ/control/for.typ +++ b/tests/typ/control/for.typ @@ -14,8 +14,8 @@ // Dictionary is not traversed in insertion order. // Should output `age: 1, name: Typst,`. -#for k, v in (name: "Typst", age: 2) [ - {k}: {v}, \ +#for k, v in (Name: "Typst", Age: 2) [ + {k}: {v}. ] // String. diff --git a/tests/typ/expand.typ b/tests/typ/expand.typ index 230447443..8b858a4ef 100644 --- a/tests/typ/expand.typ +++ b/tests/typ/expand.typ @@ -7,7 +7,7 @@ // Top-level paragraph fills page, boxed paragraph only when width is fixed. L #right[R] \ #rect(width: 50pt)[L #right[R]] \ -#rect[L #right[R]] \ +#rect[L #right[R]] // Pad inherits expansion behaviour. #pad[PL #right[PR]] \ diff --git a/tests/typ/expr/call.typ b/tests/typ/expr/call.typ index 0e941124c..dcf11806e 100644 --- a/tests/typ/expr/call.typ +++ b/tests/typ/expr/call.typ @@ -60,7 +60,7 @@ #args[a] \ #args(a) \ #args(a, [b]) \ -#args(a)[b] \ +#args(a)[b] // Template can be argument or body depending on whitespace. #if "template" == type[b] [Sure ] diff --git a/tests/typ/library/font.typ b/tests/typ/library/font.typ index 13f711250..397452e37 100644 --- a/tests/typ/library/font.typ +++ b/tests/typ/library/font.typ @@ -53,7 +53,7 @@ Emoji: 🐪, 🌋, 🏞 #font(sans-serif: "PT Sans") #font(sans-serif)[Sans-serif.] \ #font(monospace)[Monospace.] \ -#font(monospace, monospace: ("Nope", "Latin Modern Math"))[Math.] \ +#font(monospace, monospace: ("Nope", "Latin Modern Math"))[Math.] --- // Ref: false diff --git a/tests/typ/markup/linebreak.typ b/tests/typ/markup/linebreak.typ index a0a501b65..b1b83dcc8 100644 --- a/tests/typ/markup/linebreak.typ +++ b/tests/typ/markup/linebreak.typ @@ -10,17 +10,11 @@ Line \ Break // Directly before word does not work. No \Break ---- -// Leading line break. -\ Leading +\ Before -// Trailing before paragraph break. -Trailing 1 \ +Multiple \ \ \ -Trailing 2 - -// Trailing before end of document. -Trailing 3 \ +Times --- #let linebreak() = [ @@ -28,4 +22,4 @@ Trailing 3 \ #circle(radius: 2pt, fill: #000) \ ] -A \ B \ C \ +A \ B \ C diff --git a/tests/typ/markup/nbsp.typ b/tests/typ/markup/nbsp.typ index 5af6c84f9..372268eb5 100644 --- a/tests/typ/markup/nbsp.typ +++ b/tests/typ/markup/nbsp.typ @@ -1,5 +1,4 @@ // Test the non breaking space. --- -// Parsed correctly, but not actually doing anything at the moment. -The non-breaking~space does not work. +The non-breaking~space does work. diff --git a/tests/typ/repr.typ b/tests/typ/repr.typ index cf137745c..f24045107 100644 --- a/tests/typ/repr.typ +++ b/tests/typ/repr.typ @@ -9,7 +9,7 @@ {name} \ {ke-bab} \ -{α} \ +{α} // Error: 2-3 unknown variable {_} @@ -18,7 +18,7 @@ // Literal values. {none} (empty) \ {true} \ -{false} \ +{false} --- // Numerical values. @@ -33,16 +33,16 @@ {2.5rad} \ {45deg} \ // Not in monospace via repr. -#repr(45deg) \ +#repr(45deg) --- // Colors. -{#f7a20500} \ +{#f7a20500} --- // Strings and escaping. {"hi"} \ -{"a\n[]\"\u{1F680}string"} \ +{"a\n[]\"\u{1F680}string"} --- // Templates. @@ -54,4 +54,4 @@ {rect} \ {f} \ -{() => none} \ +{() => none} diff --git a/tests/typ/spacing.typ b/tests/typ/spacing.typ index ccaf084a2..bb86a59c8 100644 --- a/tests/typ/spacing.typ +++ b/tests/typ/spacing.typ @@ -7,7 +7,7 @@ A#let;B \ A#let x = 1;B #test(x, 1) \ A #let x = 2;B #test(x, 2) \ -A#let x = 3; B #test(x, 3) \ +A#let x = 3; B #test(x, 3) --- // Spacing around if-else. @@ -17,7 +17,7 @@ A#if true [B] C \ A #if true{"B"}C \ A #if true{"B"} C \ A#if false [] #else [B]C \ -A#if true [B] #else [] C \ +A#if true [B] #else [] C --- // Spacing around while loop. @@ -25,11 +25,11 @@ A#if true [B] #else [] C \ #let c = true; A#while c [{c = false}B]C \ #let c = true; A#while c [{c = false}B] C \ #let c = true; A #while c { c = false; "B" }C \ -#let c = true; A #while c { c = false; "B" } C \ +#let c = true; A #while c { c = false; "B" } C --- // Spacing around for loop. A#for _ in (none,) [B]C \ A#for _ in (none,) [B] C \ -A #for _ in (none,) {"B"}C \ +A #for _ in (none,) {"B"}C