From 7f139517b9ba8cf4f51083d5387971571d1aa950 Mon Sep 17 00:00:00 2001 From: Andrew Voynov <37143421+Andrew15-5@users.noreply.github.com> Date: Mon, 16 Dec 2024 15:18:34 +0300 Subject: [PATCH 01/10] Derivation comment for calculation in `repeat` (#5575) --- crates/typst-layout/src/repeat.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/crates/typst-layout/src/repeat.rs b/crates/typst-layout/src/repeat.rs index b761438c8..bfc7b32ce 100644 --- a/crates/typst-layout/src/repeat.rs +++ b/crates/typst-layout/src/repeat.rs @@ -33,8 +33,17 @@ pub fn layout_repeat( let fill = region.size.x; let width = piece.width(); - // count * width + (count - 1) * gap = fill, but count is an integer so - // we need to round down and get the remainder. + // We need to fit the body N times, but the number of gaps is (N - 1): + // N * w + (N - 1) * g ≤ F + // where N - body count (count) + // w - body width (width) + // g - gap width (gap) + // F - available space to fill (fill) + // + // N * w + N * g - g ≤ F + // N * (w + g) ≤ F + g + // N ≤ (F + g) / (w + g) + // N = ⌊(F + g) / (w + g)⌋ let count = ((fill + gap) / (width + gap)).floor(); let remaining = (fill + gap) % (width + gap); @@ -52,7 +61,7 @@ pub fn layout_repeat( if width > Abs::zero() { for _ in 0..(count as usize).min(1000) { frame.push_frame(Point::with_x(offset), piece.clone()); - offset += piece.width() + gap; + offset += width + gap; } } From d3620df4c63b5856832b23b26eba71aecd9f543e Mon Sep 17 00:00:00 2001 From: +merlan #flirora Date: Mon, 16 Dec 2024 08:45:57 -0500 Subject: [PATCH 02/10] Add reversed numbering (#5563) --- crates/typst-layout/src/lists.rs | 8 ++++-- crates/typst-library/src/model/enum.rs | 23 +++++++++++++++--- .../ref/enum-numbering-reversed-overriden.png | Bin 0 -> 666 bytes tests/ref/enum-numbering-reversed.png | Bin 0 -> 620 bytes tests/suite/model/enum.typ | 17 +++++++++++++ 5 files changed, 42 insertions(+), 6 deletions(-) create mode 100644 tests/ref/enum-numbering-reversed-overriden.png create mode 100644 tests/ref/enum-numbering-reversed.png diff --git a/crates/typst-layout/src/lists.rs b/crates/typst-layout/src/lists.rs index 08c2a2f45..0d51a1e4e 100644 --- a/crates/typst-layout/src/lists.rs +++ b/crates/typst-layout/src/lists.rs @@ -74,6 +74,7 @@ pub fn layout_enum( regions: Regions, ) -> SourceResult { let numbering = elem.numbering(styles); + let reversed = elem.reversed(styles); let indent = elem.indent(styles); let body_indent = elem.body_indent(styles); let gutter = elem.spacing(styles).unwrap_or_else(|| { @@ -86,7 +87,9 @@ pub fn layout_enum( let mut cells = vec![]; let mut locator = locator.split(); - let mut number = elem.start(styles); + let mut number = + elem.start(styles) + .unwrap_or_else(|| if reversed { elem.children.len() } else { 1 }); let mut parents = EnumElem::parents_in(styles); let full = elem.full(styles); @@ -127,7 +130,8 @@ pub fn layout_enum( item.body.clone().styled(EnumElem::set_parents(smallvec![number])), locator.next(&item.body.span()), )); - number = number.saturating_add(1); + number = + if reversed { number.saturating_sub(1) } else { number.saturating_add(1) }; } let grid = CellGrid::new( diff --git a/crates/typst-library/src/model/enum.rs b/crates/typst-library/src/model/enum.rs index e0121ba24..eb3c2ea45 100644 --- a/crates/typst-library/src/model/enum.rs +++ b/crates/typst-library/src/model/enum.rs @@ -9,7 +9,7 @@ use crate::foundations::{ cast, elem, scope, Array, Content, NativeElement, Packed, Show, Smart, StyleChain, Styles, TargetElem, }; -use crate::html::{attr, tag, HtmlElem}; +use crate::html::{attr, tag, HtmlAttr, HtmlElem}; use crate::layout::{Alignment, BlockElem, Em, HAlignment, Length, VAlignment, VElem}; use crate::model::{ListItemLike, ListLike, Numbering, NumberingPattern, ParElem}; @@ -127,8 +127,7 @@ pub struct EnumElem { /// [Ahead], /// ) /// ``` - #[default(1)] - pub start: usize, + pub start: Smart, /// Whether to display the full numbering, including the numbers of /// all parent enumerations. @@ -144,6 +143,17 @@ pub struct EnumElem { #[default(false)] pub full: bool, + /// Whether to reverse the numbering for this enumeration. + /// + /// ```example + /// #set enum(reversed: true) + /// + Coffee + /// + Tea + /// + Milk + /// ``` + #[default(false)] + pub reversed: bool, + /// The indentation of each item. #[resolve] pub indent: Length, @@ -217,7 +227,12 @@ impl EnumElem { impl Show for Packed { fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult { if TargetElem::target_in(styles).is_html() { - return Ok(HtmlElem::new(tag::ol) + let mut elem = HtmlElem::new(tag::ol); + if self.reversed(styles) { + elem = + elem.with_attr(const { HtmlAttr::constant("reversed") }, "reversed"); + } + return Ok(elem .with_body(Some(Content::sequence(self.children.iter().map(|item| { let mut li = HtmlElem::new(tag::li); if let Some(nr) = item.number(styles) { diff --git a/tests/ref/enum-numbering-reversed-overriden.png b/tests/ref/enum-numbering-reversed-overriden.png new file mode 100644 index 0000000000000000000000000000000000000000..827f38d1d57648b5a673159ab19f849df0198375 GIT binary patch literal 666 zcmV;L0%iS)P)<7Vw)<{`yZN`diYAYt0Tl?Ym3C?!T&h?0=E}rkz`|ib;=kR<_4gS%oVVGft8U8Eq zH&3N=iUJw@T$7a=F14Eq{;tfUa?A_uRPe0}rXv#V)xO@AB7*f#a4=AAXg_?sbe{0ZV&{VA!30+>8Q1PJb8_`|Kp1AYN_f)wK+NmS4r7=K-t3r~6FasW zZx2zyUrnE-PBdn%lT`5P#9*fc@3^PZ5Pn=+^U{rh=6NDGkxbViloR?(Jj?Vn;WTQB%QgqfyL@F90No;N<8t3EuA~ zoUzV$sx?&ZGZTa`^3#J@EePqU+W=;%;7o7l`sHw`-}T=f|H%u4VTP-PbDj+`Z>a31 z_f&AaE7&e}M1fS#FcCat$FXJEe>EC@(7+Gda%!I(KJB7`F*{zHmBWYqL@=x_q)HWU z1Xh$Ff=Apy$0s56iZRE)8ZQur8Lk#CMx`9;8P>g`f>Y;To)bINfV7PYo^M-Mi@nl> zFsNYk)`lc_`{)5Oc;RZWE>pR2gbcRKL7ga;I@Sa{N)y4$n#Xr>GAMNa&Jl}}7YM@) zR}25n=6~hOeaB!kc&T8=z7xBWF zl<_?P9o**wR6!v%Tiy`DYXAzoxfo`c;r|}~1Cu#{AAX{UQUCw|07*qoM6N<$f?anv Ak^lez literal 0 HcmV?d00001 diff --git a/tests/ref/enum-numbering-reversed.png b/tests/ref/enum-numbering-reversed.png new file mode 100644 index 0000000000000000000000000000000000000000..12d77df45ae3408bddf5f972aa4510d1f077ac8d GIT binary patch literal 620 zcmV-y0+aoTP)0(K(MrbiwL%b*utJx2WZEZHh`Rhp0upkz! zCa$O?(B?GfX^z^~4rn_z=RrC<=M$X$Fz>v1Cg}5Ay}vili-!lkC0L_VLSYJ1ctgO6 zAEKKj34iTL6#C6=%Xw=yKis{eia80w{hqKU-vJ#UH)Ok>gBlPo;*p|-4)ehx% z>wqm;xYHPqN~Pi0Zvt>^fiJ@sL1}hZ7{V)lXOh*RJSlC*x4k02sAUi(08g16aXil5 z9JksS2@|ch`B0z>BB&AO3ZEe1Fh^<{{GXunQ~TUj5kUQow$}MHr4Z zqc=VMH#Zrbm{iM4i)!5OBn*2swOX`9lt%EfA&Q5FKKYT=ZW8DMq44^Gg$SZ4!f>Qe zfirUebUr*0?KR1YA)>Hk-n2YawgIDCfg} Date: Mon, 16 Dec 2024 22:09:38 +0800 Subject: [PATCH 03/10] Consider parameters when iterating items in scope (#5517) --- crates/typst-ide/src/matchers.rs | 41 ++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/crates/typst-ide/src/matchers.rs b/crates/typst-ide/src/matchers.rs index 4aeba29be..18262f701 100644 --- a/crates/typst-ide/src/matchers.rs +++ b/crates/typst-ide/src/matchers.rs @@ -123,6 +123,36 @@ pub fn named_items( } } + if let Some(v) = parent.cast::().filter(|v| { + // Check if the node is in the body of the closure. + let body = parent.find(v.body().span()); + body.is_some_and(|n| n.find(node.span()).is_some()) + }) { + for param in v.params().children() { + match param { + ast::Param::Pos(pattern) => { + for ident in pattern.bindings() { + if let Some(t) = recv(NamedItem::Var(ident)) { + return Some(t); + } + } + } + ast::Param::Named(n) => { + if let Some(t) = recv(NamedItem::Var(n.name())) { + return Some(t); + } + } + ast::Param::Spread(s) => { + if let Some(sink_ident) = s.sink_ident() { + if let Some(t) = recv(NamedItem::Var(sink_ident)) { + return Some(t); + } + } + } + } + } + } + ancestor = Some(parent.clone()); continue; } @@ -269,6 +299,17 @@ mod tests { assert!(!has_named_items(r#"#let a = 1;#let b = 2;"#, 8, "b")); } + #[test] + fn test_param_named_items() { + // Has named items + assert!(has_named_items(r#"#let f(a) = 1;#let b = 2;"#, 12, "a")); + assert!(has_named_items(r#"#let f(a: b) = 1;#let b = 2;"#, 15, "a")); + + // Doesn't have named items + assert!(!has_named_items(r#"#let f(a) = 1;#let b = 2;"#, 19, "a")); + assert!(!has_named_items(r#"#let f(a: b) = 1;#let b = 2;"#, 15, "b")); + } + #[test] fn test_import_named_items() { // Cannot test much. From 8b1e0d3a233950bd8fd553e118ec6342efb42855 Mon Sep 17 00:00:00 2001 From: Malo <57839069+MDLC01@users.noreply.github.com> Date: Mon, 16 Dec 2024 15:10:42 +0100 Subject: [PATCH 04/10] Improve `symbol` `repr` (#5505) --- .../typst-library/src/foundations/symbol.rs | 42 ++++++++++++- tests/suite/symbols/symbol.typ | 62 +++++++++++++++++++ 2 files changed, 103 insertions(+), 1 deletion(-) diff --git a/crates/typst-library/src/foundations/symbol.rs b/crates/typst-library/src/foundations/symbol.rs index fcb3a3ce5..880305284 100644 --- a/crates/typst-library/src/foundations/symbol.rs +++ b/crates/typst-library/src/foundations/symbol.rs @@ -246,10 +246,50 @@ impl Debug for List { impl crate::foundations::Repr for Symbol { fn repr(&self) -> EcoString { - eco_format!("\"{}\"", self.get()) + match &self.0 { + Repr::Single(c) => eco_format!("symbol(\"{}\")", *c), + Repr::Complex(variants) => { + eco_format!("symbol{}", repr_variants(variants.iter().copied(), "")) + } + Repr::Modified(arc) => { + let (list, modifiers) = arc.as_ref(); + if modifiers.is_empty() { + eco_format!("symbol{}", repr_variants(list.variants(), "")) + } else { + eco_format!("symbol{}", repr_variants(list.variants(), modifiers)) + } + } + } } } +fn repr_variants<'a>( + variants: impl Iterator, + applied_modifiers: &str, +) -> String { + crate::foundations::repr::pretty_array_like( + &variants + .filter(|(variant, _)| { + // Only keep variants that can still be accessed, i.e., variants + // that contain all applied modifiers. + parts(applied_modifiers).all(|am| variant.split('.').any(|m| m == am)) + }) + .map(|(variant, c)| { + let trimmed_variant = variant + .split('.') + .filter(|&m| parts(applied_modifiers).all(|am| m != am)); + if trimmed_variant.clone().all(|m| m.is_empty()) { + eco_format!("\"{c}\"") + } else { + let trimmed_modifiers = trimmed_variant.collect::>().join("."); + eco_format!("(\"{}\", \"{}\")", trimmed_modifiers, c) + } + }) + .collect::>(), + false, + ) +} + impl Serialize for Symbol { fn serialize(&self, serializer: S) -> Result where diff --git a/tests/suite/symbols/symbol.typ b/tests/suite/symbols/symbol.typ index 30d87e44f..f2f6abf85 100644 --- a/tests/suite/symbols/symbol.typ +++ b/tests/suite/symbols/symbol.typ @@ -49,3 +49,65 @@ --- symbol-unknown-modifier --- // Error: 13-20 unknown symbol modifier #emoji.face.garbage + +--- symbol-repr --- +#test( + repr(sym.amp), + `symbol("&", ("inv", "⅋"))`.text, +) +#test( + repr(sym.amp.inv), + `symbol("⅋")`.text, +) +#test( + repr(sym.arrow.double.r), + ``` + symbol( + "⇒", + ("bar", "⤇"), + ("long", "⟹"), + ("long.bar", "⟾"), + ("not", "⇏"), + ("l", "⇔"), + ("l.long", "⟺"), + ("l.not", "⇎"), + ) + ```.text, +) +#test(repr(sym.smash), "symbol(\"⨳\")") + +#let envelope = symbol( + "🖂", + ("stamped", "🖃"), + ("stamped.pen", "🖆"), + ("lightning", "🖄"), + ("fly", "🖅"), +) +#test( + repr(envelope), + ``` + symbol( + "🖂", + ("stamped", "🖃"), + ("stamped.pen", "🖆"), + ("lightning", "🖄"), + ("fly", "🖅"), + ) + ```.text, +) +#test( + repr(envelope.stamped), + `symbol("🖃", ("pen", "🖆"))`.text, +) +#test( + repr(envelope.stamped.pen), + `symbol("🖆")`.text, +) +#test( + repr(envelope.lightning), + `symbol("🖄")`.text, +) +#test( + repr(envelope.fly), + `symbol("🖅")`.text, +) From 75273937f762ebeb05f71e91434b298b52b44670 Mon Sep 17 00:00:00 2001 From: Johann Birnick <6528009+jbirnick@users.noreply.github.com> Date: Mon, 16 Dec 2024 10:22:00 -0800 Subject: [PATCH 05/10] Transform high level headings to HTML (#5525) --- crates/typst-library/src/html/dom.rs | 14 ++++++++---- crates/typst-library/src/model/heading.rs | 28 +++++++++++++++++++---- 2 files changed, 33 insertions(+), 9 deletions(-) diff --git a/crates/typst-library/src/html/dom.rs b/crates/typst-library/src/html/dom.rs index 3d558fb0f..5b6eab4d6 100644 --- a/crates/typst-library/src/html/dom.rs +++ b/crates/typst-library/src/html/dom.rs @@ -122,8 +122,8 @@ impl HtmlTag { let bytes = string.as_bytes(); let mut i = 0; while i < bytes.len() { - if !bytes[i].is_ascii_alphanumeric() { - panic!("constant tag name must be ASCII alphanumeric"); + if !bytes[i].is_ascii() || !charsets::is_valid_in_tag_name(bytes[i] as char) { + panic!("not all characters are valid in a tag name"); } i += 1; } @@ -220,8 +220,10 @@ impl HtmlAttr { let bytes = string.as_bytes(); let mut i = 0; while i < bytes.len() { - if !bytes[i].is_ascii_alphanumeric() { - panic!("constant attribute name must be ASCII alphanumeric"); + if !bytes[i].is_ascii() + || !charsets::is_valid_in_attribute_name(bytes[i] as char) + { + panic!("not all characters are valid in an attribute name"); } i += 1; } @@ -621,5 +623,9 @@ pub mod attr { href name value + role } + + #[allow(non_upper_case_globals)] + pub const aria_level: HtmlAttr = HtmlAttr::constant("aria-level"); } diff --git a/crates/typst-library/src/model/heading.rs b/crates/typst-library/src/model/heading.rs index fc0e4ad25..ec9cf4e99 100644 --- a/crates/typst-library/src/model/heading.rs +++ b/crates/typst-library/src/model/heading.rs @@ -1,14 +1,15 @@ use std::num::NonZeroUsize; +use ecow::eco_format; use typst_utils::NonZeroExt; -use crate::diag::SourceResult; +use crate::diag::{warning, SourceResult}; use crate::engine::Engine; use crate::foundations::{ elem, Content, NativeElement, Packed, Resolve, Show, ShowSet, Smart, StyleChain, Styles, Synthesize, TargetElem, }; -use crate::html::{tag, HtmlElem}; +use crate::html::{attr, tag, HtmlElem}; use crate::introspection::{ Count, Counter, CounterUpdate, Locatable, Locator, LocatorLink, }; @@ -272,9 +273,26 @@ impl Show for Packed { // Meanwhile, a level 1 Typst heading is a section heading. For this // reason, levels are offset by one: A Typst level 1 heading becomes // a `

`. - let level = self.resolve_level(styles); - let t = [tag::h2, tag::h3, tag::h4, tag::h5, tag::h6][level.get().min(5) - 1]; - HtmlElem::new(t).with_body(Some(realized)).pack().spanned(span) + let level = self.resolve_level(styles).get(); + if level >= 6 { + engine.sink.warn(warning!(span, + "heading of level {} was transformed to \ +
, which is not \ + supported by all assistive technology", + level, level + 1; + hint: "HTML only supports

to

, not ", level + 1; + hint: "you may want to restructure your document so that \ + it doesn't contain deep headings")); + HtmlElem::new(tag::div) + .with_body(Some(realized)) + .with_attr(attr::role, "heading") + .with_attr(attr::aria_level, eco_format!("{}", level + 1)) + .pack() + .spanned(span) + } else { + let t = [tag::h2, tag::h3, tag::h4, tag::h5, tag::h6][level - 1]; + HtmlElem::new(t).with_body(Some(realized)).pack().spanned(span) + } } else { let realized = BlockBody::Content(realized); BlockElem::new().with_body(Some(realized)).pack().spanned(span) From 51020fcf3cd6fbe62d148d2188b9aaac4445bf63 Mon Sep 17 00:00:00 2001 From: Eric Biedert Date: Mon, 16 Dec 2024 21:23:13 +0100 Subject: [PATCH 06/10] Get numbering of page counter from style chain (#5589) --- .../src/introspection/counter.rs | 30 ++++++++++-------- tests/ref/counter-page-display.png | Bin 0 -> 150 bytes tests/suite/introspection/counter.typ | 8 +++++ 3 files changed, 24 insertions(+), 14 deletions(-) create mode 100644 tests/ref/counter-page-display.png diff --git a/crates/typst-library/src/introspection/counter.rs b/crates/typst-library/src/introspection/counter.rs index b884844c6..f67c6daab 100644 --- a/crates/typst-library/src/introspection/counter.rs +++ b/crates/typst-library/src/introspection/counter.rs @@ -366,20 +366,22 @@ impl Counter { .custom() .or_else(|| { let styles = styles?; - let CounterKey::Selector(Selector::Elem(func, _)) = self.0 else { - return None; - }; - - if func == HeadingElem::elem() { - HeadingElem::numbering_in(styles).clone() - } else if func == FigureElem::elem() { - FigureElem::numbering_in(styles).clone() - } else if func == EquationElem::elem() { - EquationElem::numbering_in(styles).clone() - } else if func == FootnoteElem::elem() { - Some(FootnoteElem::numbering_in(styles).clone()) - } else { - None + match self.0 { + CounterKey::Page => PageElem::numbering_in(styles).clone(), + CounterKey::Selector(Selector::Elem(func, _)) => { + if func == HeadingElem::elem() { + HeadingElem::numbering_in(styles).clone() + } else if func == FigureElem::elem() { + FigureElem::numbering_in(styles).clone() + } else if func == EquationElem::elem() { + EquationElem::numbering_in(styles).clone() + } else if func == FootnoteElem::elem() { + Some(FootnoteElem::numbering_in(styles).clone()) + } else { + None + } + } + _ => None, } }) .unwrap_or_else(|| NumberingPattern::from_str("1.1").unwrap().into()); diff --git a/tests/ref/counter-page-display.png b/tests/ref/counter-page-display.png new file mode 100644 index 0000000000000000000000000000000000000000..040b634d5976906bbce629db4767413e3ea52211 GIT binary patch literal 150 zcmeAS@N?(olHy`uVBq!ia0vp^6+kS<0VEg>Eo$`yQn8*cjv*Ddl7HAcG$dYm6xi*q zE4Q@*!dTXJw4ICn#G{kb_Ol&%J8$v(fBp56 rU(q$IQ{MiMpS$$C8}|gTF$LX>f+v31PV_aI53 Date: Tue, 17 Dec 2024 10:25:15 +0100 Subject: [PATCH 07/10] Remove deprecated things and compatibility behaviours (#5591) --- crates/typst-library/src/foundations/mod.rs | 1 - crates/typst-library/src/foundations/ops.rs | 16 --- .../typst-library/src/foundations/styles.rs | 62 +----------- crates/typst-library/src/foundations/ty.rs | 18 ---- .../src/introspection/counter.rs | 46 +-------- .../typst-library/src/introspection/locate.rs | 92 ++---------------- .../typst-library/src/introspection/query.rs | 20 +--- .../typst-library/src/introspection/state.rs | 80 +-------------- crates/typst-library/src/layout/measure.rs | 22 +---- crates/typst-library/src/model/outline.rs | 11 +-- docs/changelog/0.11.0.md | 8 +- docs/changelog/0.12.0.md | 4 +- docs/changelog/earlier.md | 2 +- tests/ref/context-compatibility-locate.png | Bin 1523 -> 0 bytes tests/ref/context-compatibility-styling.png | Bin 380 -> 0 bytes tests/ref/outline-indent-no-numbering.png | Bin 4654 -> 2924 bytes tests/ref/outline-indent-numbering.png | Bin 10607 -> 7101 bytes tests/suite/foundations/context.typ | 40 -------- tests/suite/foundations/type.typ | 8 -- tests/suite/model/outline.typ | 4 - tests/suite/styling/fold.typ | 12 +-- 21 files changed, 35 insertions(+), 411 deletions(-) delete mode 100644 tests/ref/context-compatibility-locate.png delete mode 100644 tests/ref/context-compatibility-styling.png diff --git a/crates/typst-library/src/foundations/mod.rs b/crates/typst-library/src/foundations/mod.rs index 28f983186..d960a666c 100644 --- a/crates/typst-library/src/foundations/mod.rs +++ b/crates/typst-library/src/foundations/mod.rs @@ -119,7 +119,6 @@ pub(super) fn define(global: &mut Scope, inputs: Dict, features: &Features) { global.define_func::(); global.define_func::(); global.define_func::(); - global.define_func::