From cce744cf780dd730f1638e17a0e106faeb38de22 Mon Sep 17 00:00:00 2001 From: wznmickey Date: Sun, 17 Nov 2024 04:04:07 -0500 Subject: [PATCH 01/61] Fix unnecessary hyphenation (#5394) --- crates/typst-layout/src/inline/linebreak.rs | 7 ++++++- tests/ref/issue-5360-unnecessary-hyphenation.png | Bin 0 -> 1569 bytes tests/suite/layout/inline/justify.typ | 5 +++++ 3 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 tests/ref/issue-5360-unnecessary-hyphenation.png diff --git a/crates/typst-layout/src/inline/linebreak.rs b/crates/typst-layout/src/inline/linebreak.rs index 26621cd7b..236d68921 100644 --- a/crates/typst-layout/src/inline/linebreak.rs +++ b/crates/typst-layout/src/inline/linebreak.rs @@ -543,7 +543,12 @@ fn raw_ratio( ) -> f64 { // Determine how much the line's spaces would need to be stretched // to make it the desired width. - let delta = available_width - line_width; + let mut delta = available_width - line_width; + + // Avoid possible floating point errors in previous calculation. + if delta.approx_eq(Abs::zero()) { + delta = Abs::zero(); + } // Determine how much stretch or shrink is natural. let adjustability = if delta >= Abs::zero() { stretchability } else { shrinkability }; diff --git a/tests/ref/issue-5360-unnecessary-hyphenation.png b/tests/ref/issue-5360-unnecessary-hyphenation.png new file mode 100644 index 0000000000000000000000000000000000000000..90a8180a519f9d6d49a97b38d7f139cd84d1e3ef GIT binary patch literal 1569 zcmcIke@qi+81Ckz3ma*~I5WkvscadY>qMX!pyw0=%tW@irdhG0|uvRf({sM^7Zmk5Dfgsg4zU1#CO1&|c5N9lhS|O7x%o!?t9}_r2dg z&-Z@M_dd`2p7?BMaZ2*4WFC)~Qd&~<1$U~so$-1S7f&475aaQd{#jb|$(QO^rtd8I zdHq*s@8fLsOyj`tmG5@!>!@^hy;xtLYCrp_wLNRo+8L^<`%ukgV}6CJtjj55hP+U) z-%Y=rdi3-3dx<~kqyYk8394yU zz=j2I(C!c!2#KLjrEQUGRyd|Cj+1i8C-+|q)E+h&dIP!#fxRY_d4~FJg%it~yn|&> z77xK!BfY^MPY^ecw}4a&H3=~L7UzRb|Jnp)FFcnhJdvafCl$T6P`BHU6g#jvIA%xk zF*c5&Bki-<4=(2S?{@fhM|J_xU9i8x@m`r>eq{9VW1mYg?IN#Z^BX*zF3l2XQsPg9 zyu3W`f}(9GmBv$0E#P^8nFRc{p2hST6VhHju^oKAot_XgYcaAB8<6W=a{Q2W_7Lm| z_6S39PsnLUVs_$qaY3D==8|M0@zi-Vprcw1n@6(otLFKu@Ldu7Du6Esdbv8+17tll zXr@b{FyGKzt$$SQ+vD-=A<5JIk_qnT*QqfyUap8_fM=}i zy!HJ}8hf`kUpjS3dNQ5G)A?QtC*?0sFPqfHYBgUmcNA;|$p-M9O`2dgQ~^ytf*Q>+ zn|X4~rlOyr%!g)r&^#>a`AvimS?M7fPiHrx(=vrmMqabdXD%>((H&-Dv$?)f8tVj1 z{0o;)tX9IOwV|HYnEq)i^g%Fs0VPkN-fhw++sI=Al2=t#MGiF4B@n*Sup8=+ zcNRBlEkqg(=eXhn-lO2$QH`lvvwCLkB1TK5QW%} z#nQ-W9Y=Db$z+1#Zln-oqabR62TdRUunu8@$OEJPcO%{j#5&swG^wTKu=;f34jmD* zSs`2)>J@R~hqqWGnbx}t)=AJVj}|mn5@Ir8$iu{_IX+645t$rD=aTDJZ~*JD3|%ss z%hsV(8`_)80nAP|`~OR#?vv=YFU&?Sm)lnn-XH)iXLz!77|7q=w Date: Sun, 17 Nov 2024 10:05:01 +0100 Subject: [PATCH 02/61] Timings for `state.at` and `state.get` (#5411) --- crates/typst-library/src/introspection/state.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/typst-library/src/introspection/state.rs b/crates/typst-library/src/introspection/state.rs index 13c96b502..62aba5ffb 100644 --- a/crates/typst-library/src/introspection/state.rs +++ b/crates/typst-library/src/introspection/state.rs @@ -280,6 +280,7 @@ impl State { /// Retrieves the value of the state at the current location. /// /// This is equivalent to `{state.at(here())}`. + #[typst_macros::time(name = "state.get", span = span)] #[func(contextual)] pub fn get( &self, @@ -303,6 +304,7 @@ impl State { /// _Compatibility:_ For compatibility with Typst 0.10 and lower, this /// function also works without a known context if the `selector` is a /// location. This behaviour will be removed in a future release. + #[typst_macros::time(name = "state.at", span = span)] #[func(contextual)] pub fn at( &self, From 5672cc2a29379f7743f66c46a7192d698a733047 Mon Sep 17 00:00:00 2001 From: Ian Wrzesinski <133046678+wrzian@users.noreply.github.com> Date: Sun, 17 Nov 2024 04:18:44 -0500 Subject: [PATCH 03/61] Update list/enum tests and recognize #762 as already closed (#5433) --- tests/ref/enum-syntax-number-length.png | Bin 0 -> 592 bytes tests/suite/model/enum.typ | 9 +++++++++ tests/suite/model/list.typ | 6 ++---- 3 files changed, 11 insertions(+), 4 deletions(-) create mode 100644 tests/ref/enum-syntax-number-length.png diff --git a/tests/ref/enum-syntax-number-length.png b/tests/ref/enum-syntax-number-length.png new file mode 100644 index 0000000000000000000000000000000000000000..8b64df629de7b6dcb2939b2d4f099c28e9a4b090 GIT binary patch literal 592 zcmV-W0vUxr*06>(ye9A~ z;6*|lw%WEr_1Sv;XK5v@y$!1i{yHvBry z2mpqghLi~W;6gvf?QTrJ({0gchaM0OZ*2JcxD@>N8}RX;%0QzKoUV>QY?G<_olgkn zUJN);M8QWwAzK9Qi2=3Cu$QLfy1+hq10wLPPM|8!M2!OS@ zl-nx;$9E=Z>A5~D>20FPDZjyb^2xvjtD^!axqe8)lq!Mnv7f5VL{jc2 zks5ALFz2V+yT#zsgYLNPlu^;wm6CxgA)E0G;mFpuB8b58EeZJK3L9>xLr3q7>jBZQ ehBd5Vb^Hf`opyV|OG{S(0000 Date: Sun, 17 Nov 2024 20:08:23 +0100 Subject: [PATCH 04/61] Use `codex` for symbols (#5421) --- Cargo.lock | 6 + Cargo.toml | 1 + crates/typst-eval/src/markup.rs | 4 +- crates/typst-eval/src/math.rs | 2 +- crates/typst-library/Cargo.toml | 1 + .../typst-library/src/foundations/symbol.rs | 143 +- crates/typst-library/src/math/mod.rs | 4 +- crates/typst-library/src/symbols.rs | 49 + crates/typst-library/src/symbols/emoji.rs | 1363 ----------------- crates/typst-library/src/symbols/mod.rs | 24 - crates/typst-library/src/symbols/sym.rs | 1000 ------------ crates/typst-macros/src/lib.rs | 47 - crates/typst-macros/src/symbols.rs | 129 -- docs/src/lib.rs | 6 +- tests/suite/symbols/symbol.typ | 13 + 15 files changed, 145 insertions(+), 2647 deletions(-) create mode 100644 crates/typst-library/src/symbols.rs delete mode 100644 crates/typst-library/src/symbols/emoji.rs delete mode 100644 crates/typst-library/src/symbols/mod.rs delete mode 100644 crates/typst-library/src/symbols/sym.rs delete mode 100644 crates/typst-macros/src/symbols.rs diff --git a/Cargo.lock b/Cargo.lock index bfd1ccda6..78ab71c59 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -395,6 +395,11 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "codex" +version = "0.1.0" +source = "git+https://github.com/typst/codex?rev=343a9b1#343a9b199430681ba3ca0e2242097c6419492d55" + [[package]] name = "color-print" version = "0.3.6" @@ -2849,6 +2854,7 @@ dependencies = [ "bumpalo", "chinese-number", "ciborium", + "codex", "comemo", "csv", "ecow", diff --git a/Cargo.toml b/Cargo.toml index e1f6dccb4..cebc2e972 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,6 +46,7 @@ clap = { version = "4.4", features = ["derive", "env", "wrap_help"] } clap_complete = "4.2.1" clap_mangen = "0.2.10" codespan-reporting = "0.11" +codex = { git = "https://github.com/typst/codex", rev = "343a9b1" } color-print = "0.3.6" comemo = "0.4" csv = "1" diff --git a/crates/typst-eval/src/markup.rs b/crates/typst-eval/src/markup.rs index e28eb9ddb..25ea5751a 100644 --- a/crates/typst-eval/src/markup.rs +++ b/crates/typst-eval/src/markup.rs @@ -122,7 +122,7 @@ impl Eval for ast::Escape<'_> { type Output = Value; fn eval(self, _: &mut Vm) -> SourceResult { - Ok(Value::Symbol(Symbol::single(self.get().into()))) + Ok(Value::Symbol(Symbol::single(self.get()))) } } @@ -130,7 +130,7 @@ impl Eval for ast::Shorthand<'_> { type Output = Value; fn eval(self, _: &mut Vm) -> SourceResult { - Ok(Value::Symbol(Symbol::single(self.get().into()))) + Ok(Value::Symbol(Symbol::single(self.get()))) } } diff --git a/crates/typst-eval/src/math.rs b/crates/typst-eval/src/math.rs index c61a32514..51dc0a3d5 100644 --- a/crates/typst-eval/src/math.rs +++ b/crates/typst-eval/src/math.rs @@ -32,7 +32,7 @@ impl Eval for ast::MathShorthand<'_> { type Output = Value; fn eval(self, _: &mut Vm) -> SourceResult { - Ok(Value::Symbol(Symbol::single(self.get().into()))) + Ok(Value::Symbol(Symbol::single(self.get()))) } } diff --git a/crates/typst-library/Cargo.toml b/crates/typst-library/Cargo.toml index a1fb1033f..d854e4d53 100644 --- a/crates/typst-library/Cargo.toml +++ b/crates/typst-library/Cargo.toml @@ -23,6 +23,7 @@ bitflags = { workspace = true } bumpalo = { workspace = true } chinese-number = { workspace = true } ciborium = { workspace = true } +codex = { workspace = true } comemo = { workspace = true } csv = { workspace = true } ecow = { workspace = true } diff --git a/crates/typst-library/src/foundations/symbol.rs b/crates/typst-library/src/foundations/symbol.rs index 86676fa22..fcb3a3ce5 100644 --- a/crates/typst-library/src/foundations/symbol.rs +++ b/crates/typst-library/src/foundations/symbol.rs @@ -1,6 +1,3 @@ -#[doc(inline)] -pub use typst_macros::symbols; - use std::cmp::Reverse; use std::collections::BTreeSet; use std::fmt::{self, Debug, Display, Formatter, Write}; @@ -8,10 +5,10 @@ use std::sync::Arc; use ecow::{eco_format, EcoString}; use serde::{Serialize, Serializer}; -use typst_syntax::{Span, Spanned}; +use typst_syntax::{is_ident, Span, Spanned}; use crate::diag::{bail, SourceResult, StrResult}; -use crate::foundations::{cast, func, scope, ty, Array, Func}; +use crate::foundations::{cast, func, scope, ty, Array, Func, NativeFunc, Repr as _}; /// A Unicode symbol. /// @@ -46,73 +43,90 @@ use crate::foundations::{cast, func, scope, ty, Array, Func}; #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct Symbol(Repr); -/// The character of a symbol, possibly with a function. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub struct SymChar(char, Option Func>); - /// The internal representation. #[derive(Clone, Eq, PartialEq, Hash)] enum Repr { - Single(SymChar), - Const(&'static [(&'static str, SymChar)]), - Multi(Arc<(List, EcoString)>), + /// A native symbol that has no named variant. + Single(char), + /// A native symbol with multiple named variants. + Complex(&'static [(&'static str, char)]), + /// A symbol with multiple named variants, where some modifiers may have + /// been applied. Also used for symbols defined at runtime by the user with + /// no modifier applied. + Modified(Arc<(List, EcoString)>), } /// A collection of symbols. #[derive(Clone, Eq, PartialEq, Hash)] enum List { - Static(&'static [(&'static str, SymChar)]), - Runtime(Box<[(EcoString, SymChar)]>), + Static(&'static [(&'static str, char)]), + Runtime(Box<[(EcoString, char)]>), } impl Symbol { /// Create a new symbol from a single character. - pub const fn single(c: SymChar) -> Self { + pub const fn single(c: char) -> Self { Self(Repr::Single(c)) } /// Create a symbol with a static variant list. #[track_caller] - pub const fn list(list: &'static [(&'static str, SymChar)]) -> Self { + pub const fn list(list: &'static [(&'static str, char)]) -> Self { debug_assert!(!list.is_empty()); - Self(Repr::Const(list)) + Self(Repr::Complex(list)) } /// Create a symbol with a runtime variant list. #[track_caller] - pub fn runtime(list: Box<[(EcoString, SymChar)]>) -> Self { + pub fn runtime(list: Box<[(EcoString, char)]>) -> Self { debug_assert!(!list.is_empty()); - Self(Repr::Multi(Arc::new((List::Runtime(list), EcoString::new())))) + Self(Repr::Modified(Arc::new((List::Runtime(list), EcoString::new())))) } - /// Get the symbol's char. + /// Get the symbol's character. pub fn get(&self) -> char { - self.sym().char() - } - - /// Resolve the symbol's `SymChar`. - pub fn sym(&self) -> SymChar { match &self.0 { Repr::Single(c) => *c, - Repr::Const(_) => find(self.variants(), "").unwrap(), - Repr::Multi(arc) => find(self.variants(), &arc.1).unwrap(), + Repr::Complex(_) => find(self.variants(), "").unwrap(), + Repr::Modified(arc) => find(self.variants(), &arc.1).unwrap(), } } /// Try to get the function associated with the symbol, if any. pub fn func(&self) -> StrResult { - self.sym() - .func() - .ok_or_else(|| eco_format!("symbol {self} is not callable")) + match self.get() { + '⌈' => Ok(crate::math::ceil::func()), + '⌊' => Ok(crate::math::floor::func()), + '–' => Ok(crate::math::accent::dash::func()), + '⋅' | '\u{0307}' => Ok(crate::math::accent::dot::func()), + '¨' => Ok(crate::math::accent::dot_double::func()), + '\u{20db}' => Ok(crate::math::accent::dot_triple::func()), + '\u{20dc}' => Ok(crate::math::accent::dot_quad::func()), + '∼' => Ok(crate::math::accent::tilde::func()), + '´' => Ok(crate::math::accent::acute::func()), + '˝' => Ok(crate::math::accent::acute_double::func()), + '˘' => Ok(crate::math::accent::breve::func()), + 'ˇ' => Ok(crate::math::accent::caron::func()), + '^' => Ok(crate::math::accent::hat::func()), + '`' => Ok(crate::math::accent::grave::func()), + '¯' => Ok(crate::math::accent::macron::func()), + '○' => Ok(crate::math::accent::circle::func()), + '→' => Ok(crate::math::accent::arrow::func()), + '←' => Ok(crate::math::accent::arrow_l::func()), + '↔' => Ok(crate::math::accent::arrow_l_r::func()), + '⇀' => Ok(crate::math::accent::harpoon::func()), + '↼' => Ok(crate::math::accent::harpoon_lt::func()), + _ => bail!("symbol {self} is not callable"), + } } /// Apply a modifier to the symbol. pub fn modified(mut self, modifier: &str) -> StrResult { - if let Repr::Const(list) = self.0 { - self.0 = Repr::Multi(Arc::new((List::Static(list), EcoString::new()))); + if let Repr::Complex(list) = self.0 { + self.0 = Repr::Modified(Arc::new((List::Static(list), EcoString::new()))); } - if let Repr::Multi(arc) = &mut self.0 { + if let Repr::Modified(arc) = &mut self.0 { let (list, modifiers) = Arc::make_mut(arc); if !modifiers.is_empty() { modifiers.push('.'); @@ -127,11 +141,11 @@ impl Symbol { } /// The characters that are covered by this symbol. - pub fn variants(&self) -> impl Iterator { + pub fn variants(&self) -> impl Iterator { match &self.0 { Repr::Single(c) => Variants::Single(Some(*c).into_iter()), - Repr::Const(list) => Variants::Static(list.iter()), - Repr::Multi(arc) => arc.0.variants(), + Repr::Complex(list) => Variants::Static(list.iter()), + Repr::Modified(arc) => arc.0.variants(), } } @@ -139,7 +153,7 @@ impl Symbol { pub fn modifiers(&self) -> impl Iterator + '_ { let mut set = BTreeSet::new(); let modifiers = match &self.0 { - Repr::Multi(arc) => arc.1.as_str(), + Repr::Modified(arc) => arc.1.as_str(), _ => "", }; for modifier in self.variants().flat_map(|(name, _)| name.split('.')) { @@ -192,7 +206,14 @@ impl Symbol { if list.iter().any(|(prev, _)| &v.0 == prev) { bail!(span, "duplicate variant"); } - list.push((v.0, SymChar::pure(v.1))); + if !v.0.is_empty() { + for modifier in v.0.split('.') { + if !is_ident(modifier) { + bail!(span, "invalid symbol modifier: {}", modifier.repr()); + } + } + } + list.push((v.0, v.1)); } Ok(Symbol::runtime(list.into_boxed_slice())) } @@ -204,40 +225,12 @@ impl Display for Symbol { } } -impl SymChar { - /// Create a symbol character without a function. - pub const fn pure(c: char) -> Self { - Self(c, None) - } - - /// Create a symbol character with a function. - pub const fn with_func(c: char, func: fn() -> Func) -> Self { - Self(c, Some(func)) - } - - /// Get the character of the symbol. - pub const fn char(&self) -> char { - self.0 - } - - /// Get the function associated with the symbol. - pub fn func(&self) -> Option { - self.1.map(|f| f()) - } -} - -impl From for SymChar { - fn from(c: char) -> Self { - SymChar(c, None) - } -} - impl Debug for Repr { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { Self::Single(c) => Debug::fmt(c, f), - Self::Const(list) => list.fmt(f), - Self::Multi(lists) => lists.fmt(f), + Self::Complex(list) => list.fmt(f), + Self::Modified(lists) => lists.fmt(f), } } } @@ -286,20 +279,20 @@ cast! { let mut iter = array.into_iter(); match (iter.next(), iter.next(), iter.next()) { (Some(a), Some(b), None) => Self(a.cast()?, b.cast()?), - _ => Err("point array must contain exactly two entries")?, + _ => Err("variant array must contain exactly two entries")?, } }, } /// Iterator over variants. enum Variants<'a> { - Single(std::option::IntoIter), - Static(std::slice::Iter<'static, (&'static str, SymChar)>), - Runtime(std::slice::Iter<'a, (EcoString, SymChar)>), + Single(std::option::IntoIter), + Static(std::slice::Iter<'static, (&'static str, char)>), + Runtime(std::slice::Iter<'a, (EcoString, char)>), } impl<'a> Iterator for Variants<'a> { - type Item = (&'a str, SymChar); + type Item = (&'a str, char); fn next(&mut self) -> Option { match self { @@ -312,9 +305,9 @@ impl<'a> Iterator for Variants<'a> { /// Find the best symbol from the list. fn find<'a>( - variants: impl Iterator, + variants: impl Iterator, modifiers: &str, -) -> Option { +) -> Option { let mut best = None; let mut best_score = None; diff --git a/crates/typst-library/src/math/mod.rs b/crates/typst-library/src/math/mod.rs index 53b1f0724..5a83c854f 100644 --- a/crates/typst-library/src/math/mod.rs +++ b/crates/typst-library/src/math/mod.rs @@ -207,9 +207,7 @@ pub fn module() -> Module { math.define("wide", HElem::new(WIDE.into()).pack()); // Symbols. - for (name, symbol) in crate::symbols::SYM { - math.define(*name, symbol.clone()); - } + crate::symbols::define_math(&mut math); Module::new("math", math) } diff --git a/crates/typst-library/src/symbols.rs b/crates/typst-library/src/symbols.rs new file mode 100644 index 000000000..1617d3aa8 --- /dev/null +++ b/crates/typst-library/src/symbols.rs @@ -0,0 +1,49 @@ +//! Modifiable symbols. + +use crate::foundations::{category, Category, Module, Scope, Symbol, Value}; + +/// These two modules give names to symbols and emoji to make them easy to +/// insert with a normal keyboard. Alternatively, you can also always directly +/// enter Unicode symbols into your text and formulas. In addition to the +/// symbols listed below, math mode defines `dif` and `Dif`. These are not +/// normal symbol values because they also affect spacing and font style. +#[category] +pub static SYMBOLS: Category; + +impl From for Scope { + fn from(module: codex::Module) -> Scope { + let mut scope = Self::new(); + extend_scope_from_codex_module(&mut scope, module); + scope + } +} + +impl From for Symbol { + fn from(symbol: codex::Symbol) -> Self { + match symbol { + codex::Symbol::Single(c) => Symbol::single(c), + codex::Symbol::Multi(list) => Symbol::list(list), + } + } +} + +fn extend_scope_from_codex_module(scope: &mut Scope, module: codex::Module) { + for (name, definition) in module.iter() { + let value = match definition { + codex::Def::Symbol(s) => Value::Symbol(s.into()), + codex::Def::Module(m) => Value::Module(Module::new(name, m.into())), + }; + scope.define(name, value); + } +} + +/// Hook up all `symbol` definitions. +pub(super) fn define(global: &mut Scope) { + global.category(SYMBOLS); + extend_scope_from_codex_module(global, codex::ROOT); +} + +/// Hook up all math `symbol` definitions, i.e., elements of the `sym` module. +pub(super) fn define_math(math: &mut Scope) { + extend_scope_from_codex_module(math, codex::SYM); +} diff --git a/crates/typst-library/src/symbols/emoji.rs b/crates/typst-library/src/symbols/emoji.rs deleted file mode 100644 index 1bd57ec0b..000000000 --- a/crates/typst-library/src/symbols/emoji.rs +++ /dev/null @@ -1,1363 +0,0 @@ -use crate::foundations::{Module, Scope, Symbol}; - -/// A module with all emoji. -pub fn emoji() -> Module { - let mut scope = Scope::new(); - for (name, symbol) in EMOJI { - scope.define(*name, symbol.clone()); - } - Module::new("emoji", scope) -} - -/// A list of named emoji. -const EMOJI: &[(&str, Symbol)] = typst_macros::symbols! { - abacus: '🧮', - abc: '🔤', - abcd: '🔡', - ABCD: '🔠', - accordion: '🪗', - aesculapius: '⚕', - airplane: [ - '✈', - landing: '🛬', - small: '🛩', - takeoff: '🛫', - ], - alembic: '⚗', - alien: ['👽', monster: '👾'], - ambulance: '🚑', - amphora: '🏺', - anchor: '⚓', - anger: '💢', - ant: '🐜', - apple: [green: '🍏', red: '🍎'], - arm: [mech: '🦾', muscle: '💪', selfie: '🤳'], - arrow: [ - r.filled: '➡', - r.hook: '↪', - r.soon: '🔜', - l.filled: '⬅', - l.hook: '↩', - l.back: '🔙', - l.end: '🔚', - t.filled: '⬆', - t.curve: '⤴', - t.top: '🔝', - b.filled: '⬇', - b.curve: '⤵', - l.r: '↔', - l.r.on: '🔛', - t.b: '↕', - bl: '↙', - br: '↘', - tl: '↖', - tr: '↗', - ], - arrows: [cycle: '🔄'], - ast: ['*', box: '✳'], - atm: '🏧', - atom: '⚛', - aubergine: '🍆', - avocado: '🥑', - axe: '🪓', - baby: ['👶', angel: '👼', box: '🚼'], - babybottle: '🍼', - backpack: '🎒', - bacon: '🥓', - badger: '🦡', - badminton: '🏸', - bagel: '🥯', - baggageclaim: '🛄', - baguette: '🥖', - balloon: '🎈', - ballot: [check: '☑'], - ballotbox: '🗳', - banana: '🍌', - banjo: '🪕', - bank: '🏦', - barberpole: '💈', - baseball: '⚾', - basecap: '🧢', - basket: '🧺', - basketball: ['⛹', ball: '🏀'], - bat: '🦇', - bathtub: ['🛀', foam: '🛁'], - battery: ['🔋', low: '🪫'], - beach: [palm: '🏝', umbrella: '🏖'], - beads: '📿', - beans: '🫘', - bear: '🐻', - beaver: '🦫', - bed: ['🛏', person: '🛌'], - bee: '🐝', - beer: ['🍺', clink: '🍻'], - beet: '🫜', - beetle: ['🪲', lady: '🐞'], - bell: ['🔔', ding: '🛎', not: '🔕'], - bento: '🍱', - bicyclist: ['🚴', mountain: '🚵'], - bike: ['🚲', not: '🚳'], - bikini: '👙', - billiards: '🎱', - bin: '🗑', - biohazard: '☣', - bird: '🐦', - bison: '🦬', - blood: '🩸', - blouse: '👚', - blowfish: '🐡', - blueberries: '🫐', - boar: '🐗', - boat: [ - sail: '⛵', - row: '🚣', - motor: '🛥', - speed: '🚤', - canoe: '🛶', - ], - bolt: '🔩', - bomb: '💣', - bone: '🦴', - book: [ - red: '📕', - blue: '📘', - green: '📗', - orange: '📙', - spiral: '📒', - open: '📖', - ], - bookmark: '🔖', - books: '📚', - boomerang: '🪃', - bordercontrol: '🛂', - bouquet: '💐', - bow: '🏹', - bowl: [spoon: '🥣', steam: '🍜'], - bowling: '🎳', - boxing: '🥊', - boy: '👦', - brain: '🧠', - bread: '🍞', - brick: '🧱', - bride: '👰', - bridge: [fog: '🌁', night: '🌉'], - briefcase: '💼', - briefs: '🩲', - brightness: [high: '🔆', low: '🔅'], - broccoli: '🥦', - broom: '🧹', - brush: '🖌', - bubble: [ - speech.r: '💬', - speech.l: '🗨', - thought: '💭', - anger.r: '🗯', - ], - bubbles: '🫧', - bubbletea: '🧋', - bucket: '🪣', - buffalo: [water: '🐃'], - bug: '🐛', - builder: '👷', - burger: '🍔', - burrito: '🌯', - bus: [ - '🚌', - front: '🚍', - small: '🚐', - stop: '🚏', - trolley: '🚎', - ], - butter: '🧈', - butterfly: '🦋', - button: ['🔲', alt: '🔳', radio: '🔘'], - cabinet: [file: '🗄'], - cablecar: ['🚠', small: '🚡'], - cactus: '🌵', - cake: [ - '🎂', - fish: '🍥', - moon: '🥮', - slice: '🍰', - ], - calendar: ['📅', spiral: '🗓', tearoff: '📆'], - camel: ['🐫', dromedar: '🐪'], - camera: [ - '📷', - flash: '📸', - movie: '🎥', - movie.box: '🎦', - video: '📹', - ], - camping: '🏕', - can: '🥫', - candle: '🕯', - candy: '🍬', - cane: '🦯', - car: [ - '🚗', - front: '🚘', - pickup: '🛻', - police: '🚓', - police.front: '🚔', - racing: '🏎', - rickshaw: '🛺', - suv: '🚙', - ], - card: [credit: '💳', id: '🪪'], - cardindex: '📇', - carrot: '🥕', - cart: '🛒', - cassette: '📼', - castle: [eu: '🏰', jp: '🏯'], - cat: [ - '🐈', - face: '🐱', - face.angry: '😾', - face.cry: '😿', - face.heart: '😻', - face.joy: '😹', - face.kiss: '😽', - face.laugh: '😸', - face.shock: '🙀', - face.smile: '😺', - face.smirk: '😼', - ], - chain: '🔗', - chains: '⛓', - chair: '🪑', - champagne: '🍾', - chart: [ - bar: '📊', - up: '📈', - down: '📉', - yen.up: '💹', - ], - checkmark: [heavy: '✔', box: '✅'], - cheese: '🧀', - cherries: '🍒', - chess: '♟', - chestnut: '🌰', - chicken: [ - '🐔', - baby: '🐥', - baby.egg: '🐣', - baby.head: '🐤', - leg: '🍗', - male: '🐓', - ], - child: '🧒', - chipmunk: '🐿', - chocolate: '🍫', - chopsticks: '🥢', - church: ['⛪', love: '💒'], - cigarette: ['🚬', not: '🚭'], - circle: [ - black: '⚫', - blue: '🔵', - brown: '🟤', - green: '🟢', - orange: '🟠', - purple: '🟣', - white: '⚪', - red: '🔴', - yellow: '🟡', - stroked: '⭕', - ], - circus: '🎪', - city: [ - '🏙', - dusk: '🌆', - night: '🌃', - sunset: '🌇', - ], - clamp: '🗜', - clapperboard: '🎬', - climbing: '🧗', - clip: '📎', - clipboard: '📋', - clips: '🖇', - clock: [ - one: '🕐', - one.thirty: '🕜', - two: '🕑', - two.thirty: '🕝', - three: '🕒', - three.thirty: '🕞', - four: '🕓', - four.thirty: '🕟', - five: '🕔', - five.thirty: '🕠', - six: '🕕', - six.thirty: '🕡', - seven: '🕖', - seven.thirty: '🕢', - eight: '🕗', - eight.thirty: '🕣', - nine: '🕘', - nine.thirty: '🕤', - ten: '🕙', - ten.thirty: '🕥', - eleven: '🕚', - eleven.thirty: '🕦', - twelve: '🕛', - twelve.thirty: '🕧', - alarm: '⏰', - old: '🕰', - timer: '⏲', - ], - cloud: [ - '☁', - dust: '💨', - rain: '🌧', - snow: '🌨', - storm: '⛈', - sun: '⛅', - sun.hidden: '🌥', - sun.rain: '🌦', - thunder: '🌩', - ], - coat: ['🧥', lab: '🥼'], - cockroach: '🪳', - cocktail: [martini: '🍸', tropical: '🍹'], - coconut: '🥥', - coffee: '☕', - coffin: '⚰', - coin: '🪙', - comet: '☄', - compass: '🧭', - computer: '🖥', - computermouse: '🖱', - confetti: '🎊', - construction: '🚧', - controller: '🎮', - cookie: ['🍪', fortune: '🥠'], - cooking: '🍳', - cool: '🆒', - copyright: '©', - coral: '🪸', - corn: '🌽', - couch: '🛋', - couple: '💑', - cow: ['🐄', face: '🐮'], - crab: '🦀', - crane: '🏗', - crayon: '🖍', - cricket: '🦗', - cricketbat: '🏏', - crocodile: '🐊', - croissant: '🥐', - crossmark: ['❌', box: '❎'], - crown: '👑', - crutch: '🩼', - crystal: '🔮', - cucumber: '🥒', - cup: [straw: '🥤'], - cupcake: '🧁', - curling: '🥌', - curry: '🍛', - custard: '🍮', - customs: '🛃', - cutlery: '🍴', - cyclone: '🌀', - dancing: [man: '🕺', woman: '💃', women.bunny: '👯'], - darts: '🎯', - dash: [wave.double: '〰'], - deer: '🦌', - desert: '🏜', - detective: '🕵', - diamond: [ - blue: '🔷', - blue.small: '🔹', - orange: '🔶', - orange.small: '🔸', - dot: '💠', - ], - die: '🎲', - dino: [pod: '🦕', rex: '🦖'], - disc: [cd: '💿', dvd: '📀', mini: '💽'], - discoball: '🪩', - diving: '🤿', - dodo: '🦤', - dog: [ - '🐕', - face: '🐶', - guide: '🦮', - poodle: '🐩', - ], - dollar: '💲', - dolphin: '🐬', - donut: '🍩', - door: '🚪', - dove: [peace: '🕊'], - dragon: ['🐉', face: '🐲'], - dress: ['👗', kimono: '👘', sari: '🥻'], - drop: '💧', - drops: '💦', - drum: ['🥁', big: '🪘'], - duck: '🦆', - dumpling: '🥟', - eagle: '🦅', - ear: ['👂', aid: '🦻'], - egg: '🥚', - eighteen: [not: '🔞'], - elephant: '🐘', - elevator: '🛗', - elf: '🧝', - email: '📧', - excl: [ - '❗', - white: '❕', - double: '‼', - quest: '⁉', - ], - explosion: '💥', - extinguisher: '🧯', - eye: '👁', - eyes: '👀', - face: [ - grin: '😀', - angry: '😠', - angry.red: '😡', - anguish: '😧', - astonish: '😲', - bandage: '🤕', - beam: '😁', - blank: '😶', - clown: '🤡', - cold: '🥶', - concern: '😦', - cool: '😎', - cover: '🤭', - cowboy: '🤠', - cry: '😭', - devil.smile: '😈', - devil.frown: '👿', - diagonal: '🫤', - disguise: '🥸', - distress: '😫', - dizzy: '😵', - dotted: '🫥', - down: '😞', - down.sweat: '😓', - drool: '🤤', - explode: '🤯', - eyeroll: '🙄', - friendly: '☺', - fear: '😨', - fear.sweat: '😰', - fever: '🤒', - flush: '😳', - frown: '☹', - frown.slight: '🙁', - frust: '😣', - goofy: '🤪', - halo: '😇', - happy: '😊', - heart: '😍', - hearts: '🥰', - heat: '🥵', - hug: '🤗', - inv: '🙃', - joy: '😂', - kiss: '😗', - kiss.smile: '😙', - kiss.heart: '😘', - kiss.blush: '😚', - lick: '😋', - lie: '🤥', - mask: '😷', - meh: '😒', - melt: '🫠', - money: '🤑', - monocle: '🧐', - nausea: '🤢', - nerd: '🤓', - neutral: '😐', - open: '😃', - party: '🥳', - peek: '🫣', - plead: '🥺', - relief: '😌', - rofl: '🤣', - sad: '😔', - salute: '🫡', - shock: '😱', - shush: '🤫', - skeptic: '🤨', - sleep: '😴', - sleepy: '😪', - smile: '😄', - smile.slight: '🙂', - smile.sweat: '😅', - smile.tear: '🥲', - smirk: '😏', - sneeze: '🤧', - speak.not: '🫢', - squint: '😆', - stars: '🤩', - straight: '😑', - suffer: '😖', - surprise: '😯', - symbols: '🤬', - tear: '😢', - tear.relief: '😥', - tear.withheld: '🥹', - teeth: '😬', - think: '🤔', - tired: '🫩', - tongue: '😛', - tongue.squint: '😝', - tongue.wink: '😜', - triumph: '😤', - unhappy: '😕', - vomit: '🤮', - weary: '😩', - wink: '😉', - woozy: '🥴', - worry: '😟', - wow: '😮', - yawn: '🥱', - zip: '🤐', - ], - factory: '🏭', - fairy: '🧚', - faith: [ - christ: '✝', - dharma: '☸', - islam: '☪', - judaism: '✡', - menorah: '🕎', - om: '🕉', - orthodox: '☦', - peace: '☮', - star.dot: '🔯', - worship: '🛐', - yinyang: '☯', - ], - falafel: '🧆', - family: '👪', - fax: '📠', - feather: '🪶', - feeding: [breast: '🤱'], - fencing: '🤺', - ferriswheel: '🎡', - filebox: '🗃', - filedividers: '🗂', - film: '🎞', - finger: [ - r: '👉', - l: '👈', - t: '👆', - t.alt: '☝', - b: '👇', - front: '🫵', - m: '🖕', - ], - fingerprint: '🫆', - fingers: [cross: '🤞', pinch: '🤌', snap: '🫰'], - fire: '🔥', - firecracker: '🧨', - fireengine: '🚒', - fireworks: '🎆', - fish: ['🐟', tropical: '🐠'], - fishing: '🎣', - fist: [ - front: '👊', - r: '🤜', - l: '🤛', - raised: '✊', - ], - flag: [ - black: '🏴', - white: '🏳', - goal: '🏁', - golf: '⛳', - red: '🚩', - ], - flags: [jp.crossed: '🎌'], - flamingo: '🦩', - flashlight: '🔦', - flatbread: '🫓', - fleur: '⚜', - floppy: '💾', - flower: [ - hibiscus: '🌺', - lotus: '🪷', - pink: '🌸', - rose: '🌹', - sun: '🌻', - tulip: '🌷', - white: '💮', - wilted: '🥀', - yellow: '🌼', - ], - fly: '🪰', - fog: '🌫', - folder: ['📁', open: '📂'], - fondue: '🫕', - foot: '🦶', - football: ['⚽', am: '🏈'], - forex: '💱', - fountain: '⛲', - fox: '🦊', - free: '🆓', - fries: '🍟', - frisbee: '🥏', - frog: [face: '🐸'], - fuelpump: '⛽', - garlic: '🧄', - gear: '⚙', - gem: '💎', - genie: '🧞', - ghost: '👻', - giraffe: '🦒', - girl: '👧', - glass: [ - clink: '🥂', - milk: '🥛', - pour: '🫗', - tumbler: '🥃', - ], - glasses: ['👓', sun: '🕶'], - globe: [ - am: '🌎', - as.au: '🌏', - eu.af: '🌍', - meridian: '🌐', - ], - gloves: '🧤', - goal: '🥅', - goat: '🐐', - goggles: '🥽', - golfing: '🏌', - gorilla: '🦍', - grapes: '🍇', - guard: [man: '💂'], - guitar: '🎸', - gymnastics: '🤸', - haircut: '💇', - hammer: ['🔨', pick: '⚒', wrench: '🛠'], - hamsa: '🪬', - hamster: [face: '🐹'], - hand: [ - raised: '✋', - raised.alt: '🤚', - r: '🫱', - l: '🫲', - t: '🫴', - b: '🫳', - ok: '👌', - call: '🤙', - love: '🤟', - part: '🖖', - peace: '✌', - pinch: '🤏', - rock: '🤘', - splay: '🖐', - wave: '👋', - write: '✍', - ], - handbag: '👜', - handball: '🤾', - handholding: [man.man: '👬', woman.man: '👫', woman.woman: '👭'], - hands: [ - folded: '🙏', - palms: '🤲', - clap: '👏', - heart: '🫶', - open: '👐', - raised: '🙌', - shake: '🤝', - ], - harp: '🪉', - hash: '#', - hat: [ribbon: '👒', top: '🎩'], - headphone: '🎧', - heart: [ - '❤', - arrow: '💘', - beat: '💓', - black: '🖤', - blue: '💙', - box: '💟', - broken: '💔', - brown: '🤎', - double: '💕', - excl: '❣', - green: '💚', - grow: '💗', - orange: '🧡', - purple: '💜', - real: '🫀', - revolve: '💞', - ribbon: '💝', - spark: '💖', - white: '🤍', - yellow: '💛', - ], - hedgehog: '🦔', - helicopter: '🚁', - helix: '🧬', - helmet: [cross: '⛑', military: '🪖'], - hippo: '🦛', - hockey: '🏑', - hole: '🕳', - honey: '🍯', - hongbao: '🧧', - hook: '🪝', - horn: [postal: '📯'], - horse: [ - '🐎', - carousel: '🎠', - face: '🐴', - race: '🏇', - ], - hospital: '🏥', - hotdog: '🌭', - hotel: ['🏨', love: '🏩'], - hotspring: '♨', - hourglass: ['⌛', flow: '⏳'], - house: [ - '🏠', - derelict: '🏚', - garden: '🏡', - multiple: '🏘', - ], - hundred: '💯', - hut: '🛖', - ice: '🧊', - icecream: ['🍨', shaved: '🍧', soft: '🍦'], - icehockey: '🏒', - id: '🆔', - info: 'ℹ', - izakaya: '🏮', - jar: '🫙', - jeans: '👖', - jigsaw: '🧩', - joystick: '🕹', - juggling: '🤹', - juice: '🧃', - kaaba: '🕋', - kadomatsu: '🎍', - kangaroo: '🦘', - gachi: '🈷', - go: '🈴', - hi: '㊙', - ka: '🉑', - kachi: '🈹', - kara: '🈳', - kon: '🈲', - man: '🈵', - muryo: '🈚', - shin: '🈸', - shuku: '㊗', - toku: '🉐', - yo: '🈺', - yubi: '🈯', - yuryo: '🈶', - koko: '🈁', - sa: '🈂', - kebab: '🥙', - key: ['🔑', old: '🗝'], - keyboard: '⌨', - kiss: '💏', - kissmark: '💋', - kite: '🪁', - kiwi: '🥝', - knife: ['🔪', dagger: '🗡'], - knot: '🪢', - koala: '🐨', - koinobori: '🎏', - label: '🏷', - lacrosse: '🥍', - ladder: '🪜', - lamp: [diya: '🪔'], - laptop: '💻', - a: '🅰', - ab: '🆎', - b: '🅱', - cl: '🆑', - o: '🅾', - leaf: [ - clover.three: '☘', - clover.four: '🍀', - fall: '🍂', - herb: '🌿', - maple: '🍁', - wind: '🍃', - ], - leftluggage: '🛅', - leg: ['🦵', mech: '🦿'], - lemon: '🍋', - leopard: '🐆', - letter: [love: '💌'], - liberty: '🗽', - lightbulb: '💡', - lightning: '⚡', - lion: '🦁', - lipstick: '💄', - litter: ['🚮', not: '🚯'], - lizard: '🦎', - llama: '🦙', - lobster: '🦞', - lock: [ - '🔒', - key: '🔐', - open: '🔓', - pen: '🔏', - ], - lollipop: '🍭', - lotion: '🧴', - luggage: '🧳', - lungs: '🫁', - mage: '🧙', - magnet: '🧲', - magnify: [r: '🔎', l: '🔍'], - mahjong: [dragon.red: '🀄'], - mail: ['✉', arrow: '📩'], - mailbox: [ - closed.empty: '📪', - closed.full: '📫', - open.empty: '📭', - open.full: '📬', - ], - mammoth: '🦣', - man: [ - '👨', - box: '🚹', - crown: '🤴', - guapimao: '👲', - levitate: '🕴', - old: '👴', - pregnant: '🫃', - turban: '👳', - tuxedo: '🤵', - ], - mango: '🥭', - map: [world: '🗺', jp: '🗾'], - martialarts: '🥋', - masks: '🎭', - mate: '🧉', - matryoshka: '🪆', - meat: ['🥩', bone: '🍖'], - medal: [ - first: '🥇', - second: '🥈', - third: '🥉', - sports: '🏅', - military: '🎖', - ], - megaphone: ['📢', simple: '📣'], - melon: '🍈', - merperson: '🧜', - metro: 'Ⓜ', - microbe: '🦠', - microphone: ['🎤', studio: '🎙'], - microscope: '🔬', - milkyway: '🌌', - mirror: '🪞', - mixer: '🎛', - money: [ - bag: '💰', - dollar: '💵', - euro: '💶', - pound: '💷', - yen: '💴', - wings: '💸', - ], - monkey: [ - '🐒', - face: '🐵', - hear.not: '🙉', - see.not: '🙈', - speak.not: '🙊', - ], - moon: [ - crescent: '🌙', - full: '🌕', - full.face: '🌝', - new: '🌑', - new.face: '🌚', - wane.one: '🌖', - wane.two: '🌗', - wane.three.face: '🌜', - wane.three: '🌘', - wax.one: '🌒', - wax.two: '🌓', - wax.two.face: '🌛', - wax.three: '🌔', - ], - mortarboard: '🎓', - mosque: '🕌', - mosquito: '🦟', - motorcycle: '🏍', - motorway: '🛣', - mountain: [ - '⛰', - fuji: '🗻', - snow: '🏔', - sunrise: '🌄', - ], - mouse: ['🐁', face: '🐭'], - mousetrap: '🪤', - mouth: ['👄', bite: '🫦'], - moyai: '🗿', - museum: '🏛', - mushroom: '🍄', - musicalscore: '🎼', - nails: [polish: '💅'], - namebadge: '📛', - nazar: '🧿', - necktie: '👔', - needle: '🪡', - nest: [empty: '🪹', eggs: '🪺'], - new: '🆕', - newspaper: ['📰', rolled: '🗞'], - ng: '🆖', - ningyo: '🎎', - ninja: '🥷', - noentry: '⛔', - nose: '👃', - notebook: ['📓', deco: '📔'], - notepad: '🗒', - notes: ['🎵', triple: '🎶'], - numbers: '🔢', - octopus: '🐙', - office: '🏢', - oil: '🛢', - ok: '🆗', - olive: '🫒', - oni: '👹', - onion: '🧅', - orangutan: '🦧', - otter: '🦦', - owl: '🦉', - ox: '🐂', - oyster: '🦪', - package: '📦', - paella: '🥘', - page: ['📄', curl: '📃', pencil: '📝'], - pager: '📟', - pages: [tabs: '📑'], - painting: '🖼', - palette: '🎨', - pancakes: '🥞', - panda: '🐼', - parachute: '🪂', - park: '🏞', - parking: '🅿', - parrot: '🦜', - partalteration: '〽', - party: '🎉', - peach: '🍑', - peacock: '🦚', - peanuts: '🥜', - pear: '🍐', - pedestrian: ['🚶', not: '🚷'], - pen: [ball: '🖊', fountain: '🖋'], - pencil: '✏', - penguin: '🐧', - pepper: ['🫑', hot: '🌶'], - person: [ - '🧑', - angry: '🙎', - beard: '🧔', - blonde: '👱', - bow: '🙇', - crown: '🫅', - deaf: '🧏', - facepalm: '🤦', - frown: '🙍', - hijab: '🧕', - kneel: '🧎', - lotus: '🧘', - massage: '💆', - no: '🙅', - ok: '🙆', - old: '🧓', - pregnant: '🫄', - raise: '🙋', - sassy: '💁', - shrug: '🤷', - stand: '🧍', - steam: '🧖', - ], - petri: '🧫', - phone: [ - '📱', - arrow: '📲', - classic: '☎', - not: '📵', - off: '📴', - receiver: '📞', - signal: '📶', - vibrate: '📳', - ], - piano: '🎹', - pick: '⛏', - pie: '🥧', - pig: ['🐖', face: '🐷', nose: '🐽'], - pill: '💊', - pin: ['📌', round: '📍'], - pinata: '🪅', - pineapple: '🍍', - pingpong: '🏓', - pistol: '🔫', - pizza: '🍕', - placard: '🪧', - planet: '🪐', - plant: '🪴', - plaster: '🩹', - plate: [cutlery: '🍽'], - playback: [ - down: '⏬', - eject: '⏏', - forward: '⏩', - pause: '⏸', - record: '⏺', - repeat: '🔁', - repeat.once: '🔂', - repeat.v: '🔃', - restart: '⏮', - rewind: '⏪', - shuffle: '🔀', - skip: '⏭', - stop: '⏹', - toggle: '⏯', - up: '⏫', - ], - playingcard: [flower: '🎴', joker: '🃏'], - plunger: '🪠', - policeofficer: '👮', - poo: '💩', - popcorn: '🍿', - post: [eu: '🏤', jp: '🏣'], - postbox: '📮', - potato: ['🥔', sweet: '🍠'], - pouch: '👝', - powerplug: '🔌', - present: '🎁', - pretzel: '🥨', - printer: '🖨', - prints: [foot: '👣', paw: '🐾'], - prohibited: '🚫', - projector: '📽', - pumpkin: [lantern: '🎃'], - purse: '👛', - quest: ['❓', white: '❔'], - rabbit: ['🐇', face: '🐰'], - raccoon: '🦝', - radio: '📻', - radioactive: '☢', - railway: '🛤', - rainbow: '🌈', - ram: '🐏', - rat: '🐀', - razor: '🪒', - receipt: '🧾', - recycling: '♻', - reg: '®', - restroom: '🚻', - rhino: '🦏', - ribbon: ['🎀', remind: '🎗'], - rice: [ - '🍚', - cracker: '🍘', - ear: '🌾', - onigiri: '🍙', - ], - ring: '💍', - ringbuoy: '🛟', - robot: '🤖', - rock: '🪨', - rocket: '🚀', - rollercoaster: '🎢', - rosette: '🏵', - rugby: '🏉', - ruler: ['📏', triangle: '📐'], - running: '🏃', - safetypin: '🧷', - safetyvest: '🦺', - sake: '🍶', - salad: '🥗', - salt: '🧂', - sandwich: '🥪', - santa: [man: '🎅', woman: '🤶'], - satdish: '📡', - satellite: '🛰', - saw: '🪚', - saxophone: '🎷', - scales: '⚖', - scarf: '🧣', - school: '🏫', - scissors: '✂', - scooter: ['🛴', motor: '🛵'], - scorpion: '🦂', - screwdriver: '🪛', - scroll: '📜', - seal: '🦭', - seat: '💺', - seedling: '🌱', - shark: '🦈', - sheep: '🐑', - shell: [spiral: '🐚'], - shield: '🛡', - ship: ['🚢', cruise: '🛳', ferry: '⛴'], - shirt: [sports: '🎽', t: '👕'], - shoe: [ - '👞', - ballet: '🩰', - flat: '🥿', - heel: '👠', - hike: '🥾', - ice: '⛸', - roller: '🛼', - sandal.heel: '👡', - ski: '🎿', - sneaker: '👟', - tall: '👢', - thong: '🩴', - ], - shopping: '🛍', - shorts: '🩳', - shoshinsha: '🔰', - shovel: '🪏', - shower: '🚿', - shrimp: ['🦐', fried: '🍤'], - shrine: '⛩', - sign: [crossing: '🚸', stop: '🛑'], - silhouette: [ - '👤', - double: '👥', - hug: '🫂', - speak: '🗣', - ], - siren: '🚨', - skateboard: '🛹', - skewer: [dango: '🍡', oden: '🍢'], - skiing: '⛷', - skull: ['💀', bones: '☠'], - skunk: '🦨', - sled: '🛷', - slide: '🛝', - slider: '🎚', - sloth: '🦥', - slots: '🎰', - snail: '🐌', - snake: '🐍', - snowboarding: '🏂', - snowflake: '❄', - snowman: ['⛄', snow: '☃'], - soap: '🧼', - socks: '🧦', - softball: '🥎', - sos: '🆘', - soup: '🍲', - spaghetti: '🍝', - sparkle: [box: '❇'], - sparkler: '🎇', - sparkles: '✨', - speaker: [ - '🔈', - not: '🔇', - wave: '🔉', - waves: '🔊', - ], - spider: '🕷', - spiderweb: '🕸', - spinach: '🥬', - splatter: '🫟', - sponge: '🧽', - spoon: '🥄', - square: [ - black: '⬛', - black.tiny: '▪', - black.small: '◾', - black.medium: '◼', - white: '⬜', - white.tiny: '▫', - white.small: '◽', - white.medium: '◻', - blue: '🟦', - brown: '🟫', - green: '🟩', - orange: '🟧', - purple: '🟪', - red: '🟥', - yellow: '🟨', - ], - squid: '🦑', - stadium: '🏟', - star: [ - '⭐', - arc: '💫', - box: '✴', - glow: '🌟', - shoot: '🌠', - ], - stethoscope: '🩺', - store: [big: '🏬', small: '🏪'], - strawberry: '🍓', - suit: [ - club: '♣', - diamond: '♦', - heart: '♥', - spade: '♠', - ], - sun: ['☀', cloud: '🌤', face: '🌞'], - sunrise: '🌅', - superhero: '🦸', - supervillain: '🦹', - surfing: '🏄', - sushi: '🍣', - swan: '🦢', - swimming: '🏊', - swimsuit: '🩱', - swords: '⚔', - symbols: '🔣', - synagogue: '🕍', - syringe: '💉', - taco: '🌮', - takeout: '🥡', - tamale: '🫔', - tanabata: '🎋', - tangerine: '🍊', - tap: ['🚰', not: '🚱'], - taxi: ['🚕', front: '🚖'], - teacup: '🍵', - teapot: '🫖', - teddy: '🧸', - telescope: '🔭', - temple: '🛕', - ten: '🔟', - tengu: '👺', - tennis: '🎾', - tent: '⛺', - testtube: '🧪', - thermometer: '🌡', - thread: '🧵', - thumb: [up: '👍', down: '👎'], - ticket: [event: '🎟', travel: '🎫'], - tiger: ['🐅', face: '🐯'], - tm: '™', - toilet: '🚽', - toiletpaper: '🧻', - tomato: '🍅', - tombstone: '🪦', - tongue: '👅', - toolbox: '🧰', - tooth: '🦷', - toothbrush: '🪥', - tornado: '🌪', - tower: [tokyo: '🗼'], - trackball: '🖲', - tractor: '🚜', - trafficlight: [v: '🚦', h: '🚥'], - train: [ - '🚆', - car: '🚃', - light: '🚈', - metro: '🚇', - mono: '🚝', - mountain: '🚞', - speed: '🚄', - speed.bullet: '🚅', - steam: '🚂', - stop: '🚉', - suspend: '🚟', - tram: '🚊', - tram.car: '🚋', - ], - transgender: '⚧', - tray: [inbox: '📥', mail: '📨', outbox: '📤'], - tree: [ - deciduous: '🌳', - evergreen: '🌲', - leafless: '🪾', - palm: '🌴', - xmas: '🎄', - ], - triangle: [ - r: '▶', - l: '◀', - t: '🔼', - b: '🔽', - t.red: '🔺', - b.red: '🔻', - ], - trident: '🔱', - troll: '🧌', - trophy: '🏆', - truck: ['🚚', trailer: '🚛'], - trumpet: '🎺', - tsukimi: '🎑', - turkey: '🦃', - turtle: '🐢', - tv: '📺', - ufo: '🛸', - umbrella: [ - open: '☂', - closed: '🌂', - rain: '☔', - sun: '⛱', - ], - unicorn: '🦄', - unknown: '🦳', - up: '🆙', - urn: '⚱', - vampire: '🧛', - violin: '🎻', - volcano: '🌋', - volleyball: '🏐', - vs: '🆚', - waffle: '🧇', - wand: '🪄', - warning: '⚠', - watch: ['⌚', stop: '⏱'], - watermelon: '🍉', - waterpolo: '🤽', - wave: '🌊', - wc: '🚾', - weightlifting: '🏋', - whale: ['🐋', spout: '🐳'], - wheel: '🛞', - wheelchair: ['🦽', box: '♿', motor: '🦼'], - wind: '🌬', - windchime: '🎐', - window: '🪟', - wine: '🍷', - wolf: '🐺', - woman: [ - '👩', - box: '🚺', - crown: '👸', - old: '👵', - pregnant: '🤰', - ], - wood: '🪵', - worm: '🪱', - wrench: '🔧', - wrestling: '🤼', - xray: '🩻', - yarn: '🧶', - yoyo: '🪀', - zebra: '🦓', - zodiac: [ - aquarius: '♒', - aries: '♈', - cancer: '♋', - capri: '♑', - gemini: '♊', - leo: '♌', - libra: '♎', - ophi: '⛎', - pisces: '♓', - sagit: '♐', - scorpio: '♏', - taurus: '♉', - virgo: '♍', - ], - zombie: '🧟', - zzz: '💤', -}; diff --git a/crates/typst-library/src/symbols/mod.rs b/crates/typst-library/src/symbols/mod.rs deleted file mode 100644 index 7e0fb55bd..000000000 --- a/crates/typst-library/src/symbols/mod.rs +++ /dev/null @@ -1,24 +0,0 @@ -//! Modifiable symbols. - -mod emoji; -mod sym; - -pub use self::emoji::*; -pub use self::sym::*; - -use crate::foundations::{category, Category, Scope}; - -/// These two modules give names to symbols and emoji to make them easy to -/// insert with a normal keyboard. Alternatively, you can also always directly -/// enter Unicode symbols into your text and formulas. In addition to the -/// symbols listed below, math mode defines `dif` and `Dif`. These are not -/// normal symbol values because they also affect spacing and font style. -#[category] -pub static SYMBOLS: Category; - -/// Hook up all `symbol` definitions. -pub(super) fn define(global: &mut Scope) { - global.category(SYMBOLS); - global.define_module(sym()); - global.define_module(emoji()); -} diff --git a/crates/typst-library/src/symbols/sym.rs b/crates/typst-library/src/symbols/sym.rs deleted file mode 100644 index bdda4bde3..000000000 --- a/crates/typst-library/src/symbols/sym.rs +++ /dev/null @@ -1,1000 +0,0 @@ -use crate::foundations::{Module, Scope, Symbol}; - -/// A module with all general symbols. -pub fn sym() -> Module { - let mut scope = Scope::new(); - for (name, symbol) in SYM { - scope.define(*name, symbol.clone()); - } - Module::new("sym", scope) -} - -/// The list of general symbols. -pub(crate) const SYM: &[(&str, Symbol)] = typst_macros::symbols! { - // Control. - wj: '\u{2060}', - zwj: '\u{200D}', - zwnj: '\u{200C}', - zws: '\u{200B}', - lrm: '\u{200E}', - rlm: '\u{200F}', - - // Spaces. - space: [ - ' ', - nobreak: '\u{A0}', - nobreak.narrow: '\u{202F}', - en: '\u{2002}', - quad: '\u{2003}', - third: '\u{2004}', - quarter: '\u{2005}', - sixth: '\u{2006}', - med: '\u{205F}', - fig: '\u{2007}', - punct: '\u{2008}', - thin: '\u{2009}', - hair: '\u{200A}', - ], - - // Delimiters. - paren: [l: '(', l.double: '⦅', r: ')', r.double: '⦆', t: '⏜', b: '⏝'], - brace: [l: '{', l.double: '⦃', r: '}', r.double: '⦄', t: '⏞', b: '⏟'], - bracket: [l: '[', l.double: '⟦', r: ']', r.double: '⟧', t: '⎴', b: '⎵'], - shell: [l: '❲', l.double: '⟬', r: '❳', r.double: '⟭', t: '⏠', b: '⏡'], - bar: [v: '|', v.double: '‖', v.triple: '⦀', v.broken: '¦', v.circle: '⦶', h: '―'], - fence: [l: '⧘', l.double: '⧚', r: '⧙', r.double: '⧛', dotted: '⦙'], - angle: [ - '∠', - l: '⟨', - l.curly: '⧼', - l.dot: '⦑', - l.double: '《', - r: '⟩', - r.curly: '⧽', - r.dot: '⦒', - r.double: '》', - acute: '⦟', - arc: '∡', - arc.rev: '⦛', - oblique: '⦦', - rev: '⦣', - right: '∟', - right.rev: '⯾', - right.arc: '⊾', - right.dot: '⦝', - right.sq: '⦜', - s: '⦞', - spatial: '⟀', - spheric: '∢', - spheric.rev: '⦠', - spheric.top: '⦡', - ], - ceil: [ - #[call(crate::math::ceil)] l: '⌈', - r: '⌉', - ], - floor: [ - #[call(crate::math::floor)] l: '⌊', - r: '⌋', - ], - - // Punctuation. - amp: ['&', inv: '⅋'], - ast: [ - op: '∗', - basic: '*', - low: '⁎', - double: '⁑', - triple: '⁂', - small: '﹡', - circle: '⊛', - square: '⧆', - ], - at: '@', - backslash: ['\\', circle: '⦸', not: '⧷'], - co: '℅', - colon: [':', double: '∷', eq: '≔', double.eq: '⩴'], - comma: ',', - dagger: ['†', double: '‡'], - dash: [ - #[call(crate::math::accent::dash)] en: '–', - em: '—', - em.two: '⸺', - em.three: '⸻', - fig: '‒', - wave: '〜', - colon: '∹', - circle: '⊝', - wave.double: '〰', - ], - dot: [ - #[call(crate::math::accent::dot)] op: '⋅', - basic: '.', - c: '·', - circle: '⊙', - circle.big: '⨀', - square: '⊡', - #[call(crate::math::accent::dot_double)] double: '¨', - #[call(crate::math::accent::dot_triple)] triple: '\u{20db}', - #[call(crate::math::accent::dot_quad)] quad: '\u{20dc}', - ], - excl: ['!', double: '‼', inv: '¡', quest: '⁉'], - quest: ['?', double: '⁇', excl: '⁈', inv: '¿'], - interrobang: '‽', - hash: '#', - hyph: ['‐', minus: '\u{2D}', nobreak: '\u{2011}', point: '‧', soft: '\u{ad}'], - numero: '№', - percent: '%', - permille: '‰', - pilcrow: ['¶', rev: '⁋'], - section: '§', - semi: [';', rev: '⁏'], - slash: ['/', double: '⫽', triple: '⫻', big: '⧸'], - dots: [h.c: '⋯', h: '…', v: '⋮', down: '⋱', up: '⋰'], - tilde: [ - #[call(crate::math::accent::tilde)] op: '∼', - basic: '~', - dot: '⩪', - eq: '≃', - eq.not: '≄', - eq.rev: '⋍', - equiv: '≅', - equiv.not: '≇', - nequiv: '≆', - not: '≁', - rev: '∽', - rev.equiv: '≌', - triple: '≋', - ], - - // Accents, quotes, and primes. - acute: [ - #[call(crate::math::accent::acute)] '´', - #[call(crate::math::accent::acute_double)] double: '˝', - ], - breve: #[call(crate::math::accent::breve)] '˘', - caret: '‸', - caron: #[call(crate::math::accent::caron)] 'ˇ', - hat: #[call(crate::math::accent::hat)] '^', - diaer: #[call(crate::math::accent::dot_double)] '¨', - grave: #[call(crate::math::accent::grave)] '`', - macron: #[call(crate::math::accent::macron)] '¯', - quote: [ - double: '"', - single: '\'', - l.double: '“', - l.single: '‘', - r.double: '”', - r.single: '’', - angle.l.double: '«', - angle.l.single: '‹', - angle.r.double: '»', - angle.r.single: '›', - high.double: '‟', - high.single: '‛', - low.double: '„', - low.single: '‚', - ], - prime: [ - '′', - rev: '‵', - double: '″', - double.rev: '‶', - triple: '‴', - triple.rev: '‷', - quad: '⁗', - ], - - // https://en.wikipedia.org/wiki/List_of_mathematical_symbols_by_subject - // Arithmetic. - plus: [ - '+', - circle: '⊕', - circle.arrow: '⟴', - circle.big: '⨁', - dot: '∔', - double: '⧺', - minus: '±', - small: '﹢', - square: '⊞', - triangle: '⨹', - triple: '⧻', - ], - minus: [ - '−', - circle: '⊖', - dot: '∸', - plus: '∓', - square: '⊟', - tilde: '≂', - triangle: '⨺', - ], - div: ['÷', circle: '⨸'], - times: [ - '×', - big: '⨉', - circle: '⊗', - circle.big: '⨂', - div: '⋇', - three.l: '⋋', - three.r: '⋌', - l: '⋉', - r: '⋊', - square: '⊠', - triangle: '⨻', - ], - ratio: '∶', - - // Relations. - eq: [ - '=', - star: '≛', - circle: '⊜', - colon: '≕', - def: '≝', - delta: '≜', - equi: '≚', - est: '≙', - gt: '⋝', - lt: '⋜', - m: '≞', - not: '≠', - prec: '⋞', - quest: '≟', - small: '﹦', - succ: '⋟', - triple: '≡', - triple.not: '≢', - quad: '≣', - ], - gt: [ - '>', - circle: '⧁', - dot: '⋗', - approx: '⪆', - double: '≫', - eq: '≥', - eq.slant: '⩾', - eq.lt: '⋛', - eq.not: '≱', - equiv: '≧', - lt: '≷', - lt.not: '≹', - neq: '⪈', - napprox: '⪊', - nequiv: '≩', - not: '≯', - ntilde: '⋧', - small: '﹥', - tilde: '≳', - tilde.not: '≵', - tri: '⊳', - tri.eq: '⊵', - tri.eq.not: '⋭', - tri.not: '⋫', - triple: '⋙', - triple.nested: '⫸', - ], - lt: [ - '<', - circle: '⧀', - dot: '⋖', - approx: '⪅', - double: '≪', - eq: '≤', - eq.slant: '⩽' , - eq.gt: '⋚', - eq.not: '≰', - equiv: '≦', - gt: '≶', - gt.not: '≸', - neq: '⪇', - napprox: '⪉', - nequiv: '≨', - not: '≮', - ntilde: '⋦', - small: '﹤', - tilde: '≲', - tilde.not: '≴', - tri: '⊲', - tri.eq: '⊴', - tri.eq.not: '⋬', - tri.not: '⋪', - triple: '⋘', - triple.nested: '⫷', - ], - approx: ['≈', eq: '≊', not: '≉'], - prec: [ - '≺', - approx: '⪷', - curly.eq: '≼', - curly.eq.not: '⋠', - double: '⪻', - eq: '⪯', - equiv: '⪳', - napprox: '⪹', - neq: '⪱', - nequiv: '⪵', - not: '⊀', - ntilde: '⋨', - tilde: '≾', - ], - succ: [ - '≻', - approx: '⪸', - curly.eq: '≽', - curly.eq.not: '⋡', - double: '⪼', - eq: '⪰', - equiv: '⪴', - napprox: '⪺', - neq: '⪲', - nequiv: '⪶', - not: '⊁', - ntilde: '⋩', - tilde: '≿', - ], - equiv: ['≡', not: '≢'], - prop: '∝', - original: '⊶', - image: '⊷', - asymp: [ - '≍', - not: '≭', - ], - - // Set theory. - emptyset: [ - '∅', - arrow.r: '⦳', - arrow.l: '⦴', - bar: '⦱', - circle: '⦲', - rev: '⦰', - ], - nothing: [ - '∅', - arrow.r: '⦳', - arrow.l: '⦴', - bar: '⦱', - circle: '⦲', - rev: '⦰', - ], - without: '∖', - complement: '∁', - in: [ - '∈', - not: '∉', - rev: '∋', - rev.not: '∌', - rev.small: '∍', - small: '∊', - ], - subset: [ - '⊂', - dot: '⪽', - double: '⋐', - eq: '⊆', - eq.not: '⊈', - eq.sq: '⊑', - eq.sq.not: '⋢', - neq: '⊊', - not: '⊄', - sq: '⊏', - sq.neq: '⋤', - ], - supset: [ - '⊃', - dot: '⪾', - double: '⋑', - eq: '⊇', - eq.not: '⊉', - eq.sq: '⊒', - eq.sq.not: '⋣', - neq: '⊋', - not: '⊅', - sq: '⊐', - sq.neq: '⋥', - ], - union: [ - '∪', - arrow: '⊌', - big: '⋃', - dot: '⊍', - dot.big: '⨃', - double: '⋓', - minus: '⩁', - or: '⩅', - plus: '⊎', - plus.big: '⨄', - sq: '⊔', - sq.big: '⨆', - sq.double: '⩏', - ], - sect: [ - '∩', - and: '⩄', - big: '⋂', - dot: '⩀', - double: '⋒', - sq: '⊓', - sq.big: '⨅', - sq.double: '⩎', - ], - - // Calculus. - infinity: [ - '∞', - bar: '⧞', - incomplete: '⧜', - tie: '⧝', - ], - oo: '∞', - partial: '∂', - gradient: '∇', - nabla: '∇', - sum: ['∑', integral: '⨋'], - product: ['∏', co: '∐'], - integral: [ - '∫', - arrow.hook: '⨗', - ccw: '⨑', - cont: '∮', - cont.ccw: '∳', - cont.cw: '∲', - cw: '∱', - dash: '⨍', - dash.double: '⨎', - double: '∬', - quad: '⨌', - sect: '⨙', - slash: '⨏', - square: '⨖', - surf: '∯', - times: '⨘', - triple: '∭', - union: '⨚', - vol: '∰', - ], - laplace: '∆', - - // Logic. - forall: '∀', - exists: ['∃', not: '∄'], - top: '⊤', - bot: '⊥', - not: '¬', - and: ['∧', big: '⋀', curly: '⋏', dot: '⟑', double: '⩓'], - or: ['∨', big: '⋁', curly: '⋎', dot: '⟇', double: '⩔'], - xor: ['⊕', big: '⨁'], - models: '⊧', - forces: ['⊩', not: '⊮'], - therefore: '∴', - because: '∵', - qed: '∎', - - // Function and category theory. - compose: '∘', - convolve: '∗', - multimap: ['⊸', double: '⧟'], - - // Game theory. - tiny: '⧾', - miny: '⧿', - - // Number theory. - divides: ['∣', not: '∤'], - - // Algebra. - wreath: '≀', - - // Geometry. - parallel: [ - '∥', - struck: '⫲', - circle: '⦷', - eq: '⋕', - equiv: '⩨', - not: '∦', - slanted.eq: '⧣', - slanted.eq.tilde: '⧤', - slanted.equiv: '⧥', - tilde: '⫳', - ], - perp: ['⟂', circle: '⦹'], - - // Miscellaneous Technical. - diameter: '⌀', - join: ['⨝', r: '⟖', l: '⟕', l.r: '⟗'], - degree: ['°', c: '℃', f: '℉'], - smash: '⨳', - - // Currency. - bitcoin: '₿', - dollar: '$', - euro: '€', - franc: '₣', - lira: '₺', - peso: '₱', - pound: '£', - ruble: '₽', - rupee: '₹', - won: '₩', - yen: '¥', - - // Miscellaneous. - ballot: ['☐', cross: '☒', check: '☑', check.heavy: '🗹'], - checkmark: ['✓', light: '🗸', heavy: '✔'], - crossmark: ['✗', heavy: '✘'], - floral: ['❦', l: '☙', r: '❧'], - refmark: '※', - copyright: ['©', sound: '℗'], - copyleft: '🄯', - trademark: ['™', registered: '®', service: '℠'], - maltese: '✠', - suit: [ - club.filled: '♣', - club.stroked: '♧', - diamond.filled: '♦', - diamond.stroked: '♢', - heart.filled: '♥', - heart.stroked: '♡', - spade.filled: '♠', - spade.stroked: '♤', - ], - - // Music. - note: [ - up: '🎜', - down: '🎝', - whole: '𝅝', - half: '𝅗𝅥', - quarter: '𝅘𝅥', - quarter.alt: '♩', - eighth: '𝅘𝅥𝅮', - eighth.alt: '♪', - eighth.beamed: '♫', - sixteenth: '𝅘𝅥𝅯', - sixteenth.beamed: '♬', - grace: '𝆕', - grace.slash: '𝆔', - ], - rest: [ - whole: '𝄻', - multiple: '𝄺', - multiple.measure: '𝄩', - half: '𝄼', - quarter: '𝄽', - eighth: '𝄾', - sixteenth: '𝄿', - ], - natural: [ - '♮', - t: '𝄮', - b: '𝄯', - ], - flat: [ - '♭', - t: '𝄬', - b: '𝄭', - double: '𝄫', - quarter: '𝄳', - ], - sharp: [ - '♯', - t: '𝄰', - b: '𝄱', - double: '𝄪', - quarter: '𝄲', - ], - - // Shapes. - bullet: '•', - circle: [ - #[call(crate::math::accent::circle)] stroked: '○', - stroked.tiny: '∘', - stroked.small: '⚬', - stroked.big: '◯', - filled: '●', - filled.tiny: '⦁', - filled.small: '∙', - filled.big: '⬤', - dotted: '◌', - nested: '⊚', - ], - ellipse: [ - stroked.h: '⬭', - stroked.v: '⬯', - filled.h: '⬬', - filled.v: '⬮', - ], - triangle: [ - stroked.t: '△', - stroked.b: '▽', - stroked.r: '▷', - stroked.l: '◁', - stroked.bl: '◺', - stroked.br: '◿', - stroked.tl: '◸', - stroked.tr: '◹', - stroked.small.t: '▵', - stroked.small.b: '▿', - stroked.small.r: '▹', - stroked.small.l: '◃', - stroked.rounded: '🛆', - stroked.nested: '⟁', - stroked.dot: '◬', - filled.t: '▲', - filled.b: '▼', - filled.r: '▶', - filled.l: '◀', - filled.bl: '◣', - filled.br: '◢', - filled.tl: '◤', - filled.tr: '◥', - filled.small.t: '▴', - filled.small.b: '▾', - filled.small.r: '▸', - filled.small.l: '◂', - ], - square: [ - stroked: '□', - stroked.tiny: '▫', - stroked.small: '◽', - stroked.medium: '◻', - stroked.big: '⬜', - stroked.dotted: '⬚', - stroked.rounded: '▢', - filled: '■', - filled.tiny: '▪', - filled.small: '◾', - filled.medium: '◼', - filled.big: '⬛', - ], - rect: [ - stroked.h: '▭', - stroked.v: '▯', - filled.h: '▬', - filled.v: '▮', - ], - penta: [stroked: '⬠', filled: '⬟'], - hexa: [stroked: '⬡', filled: '⬢'], - diamond: [ - stroked: '◇', - stroked.small: '⋄', - stroked.medium: '⬦', - stroked.dot: '⟐', - filled: '◆', - filled.medium: '⬥', - filled.small: '⬩', - ], - lozenge: [ - stroked: '◊', - stroked.small: '⬫', - stroked.medium: '⬨', - filled: '⧫', - filled.small: '⬪', - filled.medium: '⬧', - ], - parallelogram: [ - stroked: '▱', - filled: '▰', - ], - star: [op: '⋆', stroked: '☆', filled: '★'], - - // Arrows, harpoons, and tacks. - arrow: [ - #[call(crate::math::accent::arrow)] r: '→', - r.long.bar: '⟼', - r.bar: '↦', - r.curve: '⤷', - r.turn: '⮎', - r.dashed: '⇢', - r.dotted: '⤑', - r.double: '⇒', - r.double.bar: '⤇', - r.double.long: '⟹', - r.double.long.bar: '⟾', - r.double.not: '⇏', - r.filled: '➡', - r.hook: '↪', - r.long: '⟶', - r.long.squiggly: '⟿', - r.loop: '↬', - r.not: '↛', - r.quad: '⭆', - r.squiggly: '⇝', - r.stop: '⇥', - r.stroked: '⇨', - r.tail: '↣', - r.tilde: '⥲', - r.triple: '⇛', - r.twohead.bar: '⤅', - r.twohead: '↠', - r.wave: '↝', - #[call(crate::math::accent::arrow_l)] l: '←', - l.bar: '↤', - l.curve: '⤶', - l.turn: '⮌', - l.dashed: '⇠', - l.dotted: '⬸', - l.double: '⇐', - l.double.bar: '⤆', - l.double.long: '⟸', - l.double.long.bar: '⟽', - l.double.not: '⇍', - l.filled: '⬅', - l.hook: '↩', - l.long: '⟵', - l.long.bar: '⟻', - l.long.squiggly: '⬳', - l.loop: '↫', - l.not: '↚', - l.quad: '⭅', - l.squiggly: '⇜', - l.stop: '⇤', - l.stroked: '⇦', - l.tail: '↢', - l.tilde: '⭉', - l.triple: '⇚', - l.twohead.bar: '⬶', - l.twohead: '↞', - l.wave: '↜', - t: '↑', - t.bar: '↥', - t.curve: '⤴', - t.turn: '⮍', - t.dashed: '⇡', - t.double: '⇑', - t.filled: '⬆', - t.quad: '⟰', - t.stop: '⤒', - t.stroked: '⇧', - t.triple: '⤊', - t.twohead: '↟', - b: '↓', - b.bar: '↧', - b.curve: '⤵', - b.turn: '⮏', - b.dashed: '⇣', - b.double: '⇓', - b.filled: '⬇', - b.quad: '⟱', - b.stop: '⤓', - b.stroked: '⇩', - b.triple: '⤋', - b.twohead: '↡', - #[call(crate::math::accent::arrow_l_r)] l.r: '↔', - l.r.double: '⇔', - l.r.double.long: '⟺', - l.r.double.not: '⇎', - l.r.filled: '⬌', - l.r.long: '⟷', - l.r.not: '↮', - l.r.stroked: '⬄', - l.r.wave: '↭', - t.b: '↕', - t.b.double: '⇕', - t.b.filled: '⬍', - t.b.stroked: '⇳', - tr: '↗', - tr.double: '⇗', - tr.filled: '⬈', - tr.hook: '⤤', - tr.stroked: '⬀', - br: '↘', - br.double: '⇘', - br.filled: '⬊', - br.hook: '⤥', - br.stroked: '⬂', - tl: '↖', - tl.double: '⇖', - tl.filled: '⬉', - tl.hook: '⤣', - tl.stroked: '⬁', - bl: '↙', - bl.double: '⇙', - bl.filled: '⬋', - bl.hook: '⤦', - bl.stroked: '⬃', - tl.br: '⤡', - tr.bl: '⤢', - ccw: '↺', - ccw.half: '↶', - cw: '↻', - cw.half: '↷', - zigzag: '↯', - ], - arrows: [ - rr: '⇉', - ll: '⇇', - tt: '⇈', - bb: '⇊', - lr: '⇆', - lr.stop: '↹', - rl: '⇄', - tb: '⇅', - bt: '⇵', - rrr: '⇶', - lll: '⬱', - ], - arrowhead: [ - t: '⌃', - b: '⌄', - ], - harpoon: [ - #[call(crate::math::accent::harpoon)] rt: '⇀', - rt.bar: '⥛', - rt.stop: '⥓', - rb: '⇁', - rb.bar: '⥟', - rb.stop: '⥗', - #[call(crate::math::accent::harpoon_lt)] lt: '↼', - lt.bar: '⥚', - lt.stop: '⥒', - lb: '↽', - lb.bar: '⥞', - lb.stop: '⥖', - tl: '↿', - tl.bar: '⥠', - tl.stop: '⥘', - tr: '↾', - tr.bar: '⥜', - tr.stop: '⥔', - bl: '⇃', - bl.bar: '⥡', - bl.stop: '⥙', - br: '⇂', - br.bar: '⥝', - br.stop: '⥕', - lt.rt: '⥎', - lb.rb: '⥐', - lb.rt: '⥋', - lt.rb: '⥊', - tl.bl: '⥑', - tr.br: '⥏', - tl.br: '⥍', - tr.bl: '⥌', - ], - harpoons: [ - rtrb: '⥤', - blbr: '⥥', - bltr: '⥯', - lbrb: '⥧', - ltlb: '⥢', - ltrb: '⇋', - ltrt: '⥦', - rblb: '⥩', - rtlb: '⇌', - rtlt: '⥨', - tlbr: '⥮', - tltr: '⥣', - ], - tack: [ - r: '⊢', - r.not: '⊬', - r.long: '⟝', - r.short: '⊦', - r.double: '⊨', - r.double.not: '⊭', - l: '⊣', - l.long: '⟞', - l.short: '⫞', - l.double: '⫤', - t: '⊥', - t.big: '⟘', - t.double: '⫫', - t.short: '⫠', - b: '⊤', - b.big: '⟙', - b.double: '⫪', - b.short: '⫟', - l.r: '⟛', - ], - - // Lowercase Greek. - alpha: 'α', - beta: ['β', alt: 'ϐ'], - chi: 'χ', - delta: 'δ', - epsilon: ['ε', alt: 'ϵ'], - eta: 'η', - gamma: 'γ', - iota: 'ι', - kai: 'ϗ', - kappa: ['κ', alt: 'ϰ'], - lambda: 'λ', - mu: 'μ', - nu: 'ν', - ohm: ['Ω', inv: '℧'], - omega: 'ω', - omicron: 'ο', - phi: ['φ', alt: 'ϕ'], - pi: ['π', alt: 'ϖ'], - psi: 'ψ', - rho: ['ρ', alt: 'ϱ'], - sigma: ['σ', alt: 'ς'], - tau: 'τ', - theta: ['θ', alt: 'ϑ'], - upsilon: 'υ', - xi: 'ξ', - zeta: 'ζ', - - // Uppercase Greek. - Alpha: 'Α', - Beta: 'Β', - Chi: 'Χ', - Delta: 'Δ', - Epsilon: 'Ε', - Eta: 'Η', - Gamma: 'Γ', - Iota: 'Ι', - Kai: 'Ϗ', - Kappa: 'Κ', - Lambda: 'Λ', - Mu: 'Μ', - Nu: 'Ν', - Omega: 'Ω', - Omicron: 'Ο', - Phi: 'Φ', - Pi: 'Π', - Psi: 'Ψ', - Rho: 'Ρ', - Sigma: 'Σ', - Tau: 'Τ', - Theta: 'Θ', - Upsilon: 'Υ', - Xi: 'Ξ', - Zeta: 'Ζ', - - // Hebrew. - // In math, the following symbols are replaced with corresponding characters - // from Letterlike Symbols. - // See https://github.com/typst/typst/pull/3375. - aleph: 'א', - alef: 'א', - beth: 'ב', - bet: 'ב', - gimmel: 'ג', - gimel: 'ג', - daleth: 'ד', - dalet: 'ד', - shin: 'ש', - - // Double-struck. - AA: '𝔸', - BB: '𝔹', - CC: 'ℂ', - DD: '𝔻', - EE: '𝔼', - FF: '𝔽', - GG: '𝔾', - HH: 'ℍ', - II: '𝕀', - JJ: '𝕁', - KK: '𝕂', - LL: '𝕃', - MM: '𝕄', - NN: 'ℕ', - OO: '𝕆', - PP: 'ℙ', - QQ: 'ℚ', - RR: 'ℝ', - SS: '𝕊', - TT: '𝕋', - UU: '𝕌', - VV: '𝕍', - WW: '𝕎', - XX: '𝕏', - YY: '𝕐', - ZZ: 'ℤ', - - // Miscellaneous letter-likes. - ell: 'ℓ', - planck: ['ℎ', reduce: 'ℏ'], - angstrom: 'Å', - kelvin: 'K', - Re: 'ℜ', - Im: 'ℑ', - dotless: [i: 'ı', j: 'ȷ'], -}; diff --git a/crates/typst-macros/src/lib.rs b/crates/typst-macros/src/lib.rs index acc5e6034..e1c3c13ab 100644 --- a/crates/typst-macros/src/lib.rs +++ b/crates/typst-macros/src/lib.rs @@ -9,7 +9,6 @@ mod category; mod elem; mod func; mod scope; -mod symbols; mod time; mod ty; @@ -338,52 +337,6 @@ pub fn derive_cast(item: BoundaryStream) -> BoundaryStream { .into() } -/// Defines a list of `Symbol`s. -/// -/// The `#[call(path)]` attribute can be used to specify a function to call when -/// the symbol is invoked. The function must be `NativeFunc`. -/// -/// ```ignore -/// const EMOJI: &[(&str, Symbol)] = symbols! { -/// // A plain symbol without modifiers. -/// abacus: '🧮', -/// -/// // A symbol with a modifierless default and one modifier. -/// alien: ['👽', monster: '👾'], -/// -/// // A symbol where each variant has a modifier. The first one will be -/// // the default. -/// clock: [one: '🕐', two: '🕑', ...], -/// -/// // A callable symbol without modifiers. -/// breve: #[call(crate::math::breve)] '˘', -/// -/// // A callable symbol with a modifierless default and one modifier. -/// acute: [ -/// #[call(crate::math::acute)] '´', -/// double: '˝', -/// ], -/// -/// // A callable symbol where each variant has a modifier. -/// arrow: [ -/// #[call(crate::math::arrow)] r: '→', -/// r.long.bar: '⟼', -/// #[call(crate::math::arrow_l)] l: '←', -/// l.long.bar: '⟻', -/// ], -/// } -/// ``` -/// -/// _Note:_ While this could use `macro_rules!` instead of a proc-macro, it was -/// horribly slow in rust-analyzer. The underlying cause might be -/// [this issue](https://github.com/rust-lang/rust-analyzer/issues/11108). -#[proc_macro] -pub fn symbols(stream: BoundaryStream) -> BoundaryStream { - symbols::symbols(stream.into()) - .unwrap_or_else(|err| err.to_compile_error()) - .into() -} - /// Times function invocations. /// /// When tracing is enabled in the typst-cli, this macro will record the diff --git a/crates/typst-macros/src/symbols.rs b/crates/typst-macros/src/symbols.rs deleted file mode 100644 index 9917f4369..000000000 --- a/crates/typst-macros/src/symbols.rs +++ /dev/null @@ -1,129 +0,0 @@ -use proc_macro2::TokenStream; -use quote::quote; -use syn::ext::IdentExt; -use syn::parse::{Parse, ParseStream, Parser}; -use syn::punctuated::Punctuated; -use syn::{Ident, LitChar, Path, Result, Token}; - -use crate::util::foundations; - -/// Expand the `symbols!` macro. -pub fn symbols(stream: TokenStream) -> Result { - let list: Punctuated = - Punctuated::parse_terminated.parse2(stream)?; - let pairs = list.iter().map(|symbol| { - let name = symbol.name.to_string(); - let kind = match &symbol.kind { - Kind::Single(c, h) => { - let symbol = construct_sym_char(c, h); - quote! { #foundations::Symbol::single(#symbol), } - } - Kind::Multiple(variants) => { - let variants = variants.iter().map(|variant| { - let name = &variant.name; - let c = &variant.c; - let symbol = construct_sym_char(c, &variant.handler); - quote! { (#name, #symbol) } - }); - quote! { - #foundations::Symbol::list(&[#(#variants),*]) - } - } - }; - quote! { (#name, #kind) } - }); - Ok(quote! { &[#(#pairs),*] }) -} - -fn construct_sym_char(ch: &LitChar, handler: &Handler) -> TokenStream { - match &handler.0 { - None => quote! { #foundations::SymChar::pure(#ch), }, - Some(path) => quote! { - #foundations::SymChar::with_func( - #ch, - <#path as ::typst_library::foundations::NativeFunc>::func, - ), - }, - } -} - -struct Symbol { - name: syn::Ident, - kind: Kind, -} - -enum Kind { - Single(syn::LitChar, Handler), - Multiple(Punctuated), -} - -struct Variant { - name: String, - c: syn::LitChar, - handler: Handler, -} - -struct Handler(Option); - -impl Parse for Symbol { - fn parse(input: ParseStream) -> Result { - let name = input.call(Ident::parse_any)?; - input.parse::()?; - let kind = input.parse()?; - Ok(Self { name, kind }) - } -} - -impl Parse for Kind { - fn parse(input: ParseStream) -> Result { - let handler = input.parse::()?; - if input.peek(syn::LitChar) { - Ok(Self::Single(input.parse()?, handler)) - } else { - if handler.0.is_some() { - return Err(input.error("unexpected handler")); - } - let content; - syn::bracketed!(content in input); - Ok(Self::Multiple(Punctuated::parse_terminated(&content)?)) - } - } -} - -impl Parse for Variant { - fn parse(input: ParseStream) -> Result { - let mut name = String::new(); - let handler = input.parse::()?; - if input.peek(syn::Ident::peek_any) { - name.push_str(&input.call(Ident::parse_any)?.to_string()); - while input.peek(Token![.]) { - input.parse::()?; - name.push('.'); - name.push_str(&input.call(Ident::parse_any)?.to_string()); - } - input.parse::()?; - } - let c = input.parse()?; - Ok(Self { name, c, handler }) - } -} - -impl Parse for Handler { - fn parse(input: ParseStream) -> Result { - let Ok(attrs) = input.call(syn::Attribute::parse_outer) else { - return Ok(Self(None)); - }; - let handler = attrs - .iter() - .find_map(|attr| { - if attr.path().is_ident("call") { - if let Ok(path) = attr.parse_args::() { - return Some(Self(Some(path))); - } - } - None - }) - .unwrap_or(Self(None)); - Ok(handler) - } -} diff --git a/docs/src/lib.rs b/docs/src/lib.rs index bc9b53c9a..9228a9906 100644 --- a/docs/src/lib.rs +++ b/docs/src/lib.rs @@ -671,15 +671,15 @@ fn symbols_model(resolver: &dyn Resolver, group: &GroupData) -> SymbolsModel { for (variant, c) in symbol.variants() { let shorthand = |list: &[(&'static str, char)]| { - list.iter().copied().find(|&(_, x)| x == c.char()).map(|(s, _)| s) + list.iter().copied().find(|&(_, x)| x == c).map(|(s, _)| s) }; list.push(SymbolModel { name: complete(variant), markup_shorthand: shorthand(typst::syntax::ast::Shorthand::LIST), math_shorthand: shorthand(typst::syntax::ast::MathShorthand::LIST), - codepoint: c.char() as _, - accent: typst::math::Accent::combine(c.char()).is_some(), + codepoint: c as _, + accent: typst::math::Accent::combine(c).is_some(), alternates: symbol .variants() .filter(|(other, _)| other != &variant) diff --git a/tests/suite/symbols/symbol.typ b/tests/suite/symbols/symbol.typ index 8f9a49ca2..30d87e44f 100644 --- a/tests/suite/symbols/symbol.typ +++ b/tests/suite/symbols/symbol.typ @@ -33,6 +33,19 @@ // Error: 2-10 expected at least one variant #symbol() +--- symbol-constructor-invalid-modifier --- +// Error: 2:3-2:24 invalid symbol modifier: " id!" +#symbol( + ("invalid. id!", "x") +) + +--- symbol-constructor-duplicate-variant --- +// Error: 3:3-3:29 duplicate variant +#symbol( + ("duplicate.variant", "x"), + ("duplicate.variant", "y"), +) + --- symbol-unknown-modifier --- // Error: 13-20 unknown symbol modifier #emoji.face.garbage From 3d7284fc1bb52b4431e3e4742254fd7699f18dc0 Mon Sep 17 00:00:00 2001 From: Iagoba Apellaniz Date: Tue, 26 Nov 2024 13:34:53 +0100 Subject: [PATCH 05/61] Support for Basque language (#5429) --- crates/typst-library/src/text/lang.rs | 4 +++- crates/typst-library/translations/eu.txt | 8 ++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 crates/typst-library/translations/eu.txt diff --git a/crates/typst-library/src/text/lang.rs b/crates/typst-library/src/text/lang.rs index d63dddb70..b9b9ef555 100644 --- a/crates/typst-library/src/text/lang.rs +++ b/crates/typst-library/src/text/lang.rs @@ -14,7 +14,7 @@ macro_rules! translation { }; } -const TRANSLATIONS: [(&str, &str); 36] = [ +const TRANSLATIONS: [(&str, &str); 37] = [ translation!("ar"), translation!("ca"), translation!("cs"), @@ -23,6 +23,7 @@ const TRANSLATIONS: [(&str, &str); 36] = [ translation!("en"), translation!("es"), translation!("et"), + translation!("eu"), translation!("fi"), translation!("fr"), translation!("gl"), @@ -60,6 +61,7 @@ pub struct Lang([u8; 3], u8); impl Lang { pub const ALBANIAN: Self = Self(*b"sq ", 2); pub const ARABIC: Self = Self(*b"ar ", 2); + pub const BASQUE: Self = Self(*b"eu ", 2); pub const BOKMÅL: Self = Self(*b"nb ", 2); pub const CATALAN: Self = Self(*b"ca ", 2); pub const CHINESE: Self = Self(*b"zh ", 2); diff --git a/crates/typst-library/translations/eu.txt b/crates/typst-library/translations/eu.txt new file mode 100644 index 000000000..d89f89b6f --- /dev/null +++ b/crates/typst-library/translations/eu.txt @@ -0,0 +1,8 @@ +figure = Irudia +table = Taula +equation = Ekuazioa +bibliography = Bibliografia +heading = Atala +outline = Aurkibidea +raw = Kodea +page = orria From 580e6811b0281f19fa9c3e438500a9e8bb9b95ef Mon Sep 17 00:00:00 2001 From: Johann Birnick <6528009+jbirnick@users.noreply.github.com> Date: Tue, 26 Nov 2024 04:35:45 -0800 Subject: [PATCH 06/61] Docs: note `par.linebreaks` in `text.costs` (#5434) --- crates/typst-library/src/text/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/typst-library/src/text/mod.rs b/crates/typst-library/src/text/mod.rs index 6aeebbbaf..62c870513 100644 --- a/crates/typst-library/src/text/mod.rs +++ b/crates/typst-library/src/text/mod.rs @@ -495,7 +495,9 @@ pub struct TextElem { /// /// Hyphenation is generally avoided by placing the whole word on the next /// line, so a higher hyphenation cost can result in awkward justification - /// spacing. + /// spacing. Note: Hyphenation costs will only be applied when the + /// [`linebreaks`]($par.linebreaks) are set to "optimized". (For example + /// by default implied by [`justify`]($par.justify).) /// /// Runts are avoided by placing more or fewer words on previous lines, so a /// higher runt cost can result in more awkward in justification spacing. From 6d35972c3d2caaea3d6e358f6af121ba37881dbb Mon Sep 17 00:00:00 2001 From: Max Date: Tue, 26 Nov 2024 12:36:53 +0000 Subject: [PATCH 07/61] Update NewCM fonts to version 7.0.0 (#5465) --- Cargo.lock | 2 +- Cargo.toml | 2 +- .../typst-library/src/text/font/exceptions.rs | 3 +-- tests/ref/math-attach-descender-collision.png | Bin 739 -> 737 bytes tests/ref/math-attach-horizontal-align.png | Bin 1898 -> 1899 bytes tests/ref/math-attach-nested.png | Bin 979 -> 981 bytes tests/ref/math-attach-postscripts.png | Bin 497 -> 496 bytes tests/ref/math-equation-numbering.png | Bin 4684 -> 4699 bytes tests/ref/math-style-greek-exceptions.png | Bin 301 -> 315 bytes tests/ref/math-underover-line-subscript.png | Bin 1541 -> 1540 bytes 10 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 78ab71c59..330dcce5c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2677,7 +2677,7 @@ dependencies = [ [[package]] name = "typst-assets" version = "0.12.0" -source = "git+https://github.com/typst/typst-assets?rev=5c0dcc0#5c0dcc062eddd9c1b1a8994fe4948ab604a1690f" +source = "git+https://github.com/typst/typst-assets?rev=8cccef9#8cccef93b5da73a1c80389722cf2b655b624f577" [[package]] name = "typst-cli" diff --git a/Cargo.toml b/Cargo.toml index cebc2e972..1f6eb5acc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,7 @@ typst-svg = { path = "crates/typst-svg", version = "0.12.0" } typst-syntax = { path = "crates/typst-syntax", version = "0.12.0" } typst-timing = { path = "crates/typst-timing", version = "0.12.0" } typst-utils = { path = "crates/typst-utils", version = "0.12.0" } -typst-assets = { git = "https://github.com/typst/typst-assets", rev = "5c0dcc0" } +typst-assets = { git = "https://github.com/typst/typst-assets", rev = "8cccef9" } typst-dev-assets = { git = "https://github.com/typst/typst-dev-assets", rev = "b07d156" } arrayvec = "0.7.4" az = "1.2" diff --git a/crates/typst-library/src/text/font/exceptions.rs b/crates/typst-library/src/text/font/exceptions.rs index 6393df4b2..465ec510c 100644 --- a/crates/typst-library/src/text/font/exceptions.rs +++ b/crates/typst-library/src/text/font/exceptions.rs @@ -182,8 +182,7 @@ static EXCEPTION_MAP: phf::Map<&'static str, Exception> = phf::phf_map! { "NewCM10-Regular" => Exception::new() .family("New Computer Modern"), "NewCMMath-Bold" => Exception::new() - .family("New Computer Modern Math") - .weight(700), + .family("New Computer Modern Math"), "NewCMMath-Book" => Exception::new() .family("New Computer Modern Math") .weight(450), diff --git a/tests/ref/math-attach-descender-collision.png b/tests/ref/math-attach-descender-collision.png index 71654916b2686009c086630e874f1a0ca88d8cbb..fdca569552cffeab5d1118ac035ef09ee6e47f01 100644 GIT binary patch delta 713 zcmV;)0yh2Q1>psdB!8$$L_t(|+U?ifOH*MS2XOzE=rZW83n2^*BI>GLsE7%fX_$#G zD;vXvk@9MhSCVs|dPW(mmM z*uE$nJlm&UihIwdse!0+oQVL}u#zhX>>bnq!>y>%$382U??i=4HHS6HyG2BKhRtKT zrR5F=K&5N@=0;57Ftysy1OT0H0J!*tnIDBa`*Njf7=O5;vqagLXnIn+4o`F6e4s@K zbZYs91IyPvTW{9@1hN6FA<=3x@eA*0&GSH4j|SRT3@i;uw0`=9yiVRKhR`73r85_W zDNJDspICVHX+%K;_7tFq2n?yEkqCUCzvUzXJNgaD^;HL52(5s#iw$p9WbWO`*)P1Y zD>D32gMa=WOuPWZh5dQuUy5#9%kw_}JNhMif@i&54L;4|5B0P5`tO3kXc>Z{3#0Ow zBXbGbIOaUY?Z|S5Iv^U1bpIgmK;H{YO4T6|;dV(7SkEv5Nb44=#&_?nR+hP{i&tWM znG^tu0iektUISrA=WW0cs{%fGUqd`N{AdI~b3!)<=-Qo0HGxdq$iTkFMKbWi`Un}g v)(0|h0m#77YBKO)%}4qtL17A0I2qtSv9_165wTHf00000NkvXXu0mjfe#uue delta 715 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 diff --git a/tests/ref/math-attach-nested.png b/tests/ref/math-attach-nested.png index a62e62eb2e54ff7bea2b0eea8c12189caaa7a9a1..31b796fa2dc9fe41061881cc4f10edd156b7a86d 100644 GIT binary patch delta 959 zcmV;w13>)K2h|6VB!8SqL_t(|+U?j~Pg8dsz;R#2cC%No-7I^!GP_w8F1B4PSwgaf zJ(%nQ86%()oDbVD3KIf}%!wir1lfkDFdiHrwYIc{K2StEN-1=M7EZN1w4=1Ar9IzW zoy69*6pQUmKUcr=yZI(3=bxN&4v-@U^5J|qAI^uf6}G=!B7dTLN0;?56>ZQbko|De zX*{>dL3p5uE94^l_@HWbN9E3b*#Bxfm$$#NXFbgOA}f^qlbds_xrJ{+d|6>ZPa>90 z6i2U2DpwmpwqRK&6~VHUet4?7k_3Kp6;DqL4vO691zGz&iBtye1k(@mAYpi(8Z|)2 z^m&l!AM~?hAb$xh*2&h4!`LnYAgu$k7G*D^H6&&$=6CZi8HcUMhK7kJ`4~`^hLt@c zv7RdKd0IcYk{RqsqdW8rAU@{8D#^d8|8i zIDOHD(_vE3^o6=U0p8T&XcmF}3LE=>j*y(<>xPlalfdV{Itnker5_IX6WRsW0s zp50&w41Nl{^?21_@T4E^xscFR02ql|4?(=A5hw_;Tm(Kp8Qtn|&<7A8&RPVBgo!a^ z{>C<@9)G4?P?*N6`!WivYi+>tL1N8ntAL&(#L!cBzv+W^&KKOW`V@5;gu4QEbr*n% zIOenQ>yQszHOPWL_*sv>NO(?Q_M~@s<3|83vF`gT0AUf3bu~Ji)h4CJD)Sg>WHU2p7V+3OBr_hkxQTk(`J9uR^#zAFF;z zqBE3_@Y~kq3GxyCRDB)(or>l>e7aa*y>I?l&cm&pAPMU^4lAU3FN@X}6?Pj)VtWCk zlO0V#Lv<8|b#aSy+>2|Ohv#Z4NwnK;E@f+I$c&>(MA30yCY`~)Q08GCBnwCEp^pJr z9l9W(FgzGw&woe?dX}f#vJJy!8vytuP_%0M8LcBZ%W-}){gidMR&|*KvY$b1nWXI% ziS1?e6Yzk`WCjMe`+BNd1>`BI~rWXRbE7abg(`5O!Yt&_p7eW``7H z_btse`RpV_Ju=*N-fs5nRvh?o@~t^GNHjNjn-n5F-G9P1Rf}A#o2YIjAop~c#(L6+ zGZ*UXB^}+Ju<9e=O+EG&5jd=H+u&W&(0tXTt~>{P{)@fjN_*zvU?8bo01yMQLd07& z8iPZh0B`-hVKfSvhkLIibprs@amT|XcoKo4Fpg622gqvGQpgVwB+h08h)Bd3HXXB! zO^(pv)_=@Cthtj_c(UFCINl}JlD-P){gxO88pf}_-@%VXe=Peo4OxU$!PQ9>z$|f0 zweiPcKe*~pguV~36@8xYikqc#z7g*Q06nqB1E&DD4N$0>?arE6t#Y59Hx8H|RUb60 z``!E0a9V1qn2yW@Nn&qGh`l!AtJ=KR*yvF<(tp@E&rCh&GYO03C6Doc&}P=TzQ9%` zM}M9rIeEs%vqtN~qr;=b8Vgpiay*kT>JoU`1yNnYyy2{$lx_;8c4cn4Dhz1LB+CME zq$xqTQMP`sZe`!_<9DD{24{;GXOPrvl&>mtzt$xhz7|O)>OQfo(Jky74!_JJGq{T? zFMpAq@mCZxJ58iB!w>t0d1bR55Z&$_($b$%O)K-P+DAIpG5h}E_r42~nk*}mwx=g} zsIRwQ{z+<)o9A~$jR(bHhIdHEQy#e%`phwLv=h2dt>x@X!n4%&kB6fk-f+P!8#xOz zH3wruGQ|)1ub&)-fq}~AMZW3cIynwcjW&$z(Nv?gfr?jD5=<09jd({TiWf9N&51^ciuY?w@^?5yIGzYOAUb&dj_ZR>){08?ptkfO>Ksm84 z|GmZkld>WVz_#Q;Zdz{v!22g{%{rG002Asx!=H#$f#+k6Dk8+H-*`o&V7}TKH2^PJ z(7~j83D2U1H-D39K7=Em?-(ttIC*aTaW&O~+KI^jR1pvG9@K&&zkY}Wfao)ylhZ@g zCPh+23IULsninU))V2{gb4EY$!S~=Ypq-eP`RwF%0%#}hU4}Pau$_q!_)7@I)li{< zFaCC;hrcP%tB36=D@=S4=DV!@iNnf5I&3NV=cuE)(NE=HC6*Q*K$8<+N#d|8V;u&I z?L#D%vz@!D!(li0WqP)zLnPFGTk9_I3zzndJj#+-+%?yt2|q>n8o>QbP_$2{ng9R* M07*qoM6N<$f<(001ONa4 delta 471 zcmV;|0Vw|P1MvfpB!9R`L_t(|+U?alOIu+a$8q06q4XuRgG1*|U5ag?r5$Z?EVP4- zEv1F(rt%-6h>8m04OG0Ml3=0;YQ#G#QM{lDYECplRJ>nflCQ%d%JD?V0nx$pU7z8@ z4<4Q;i3cjlgiY9l?-f=`iCfgwc6vv!5(a&1LfpERhcnUT2Y>bq!j+bCbj?toQwChO z^Z8?d^_OiIwAOMnM=Inw{tsLjf!~}(h!wB_KroGrkmUpb_Xq&g{RHKq;}# z|GdWkost3!z_#Rmu3BvZ!22t0#X6G>0AuPogCB@gfaiUdDk8+HU3);7=hGS6!I~ zKKtE?9{!>L-8$Hwvc%XsVYb8Cn>eh@rNf4jdx|>MjZiNCGO@I9ADW!_iV}xi8LQA= zXdfWKoUPna4Gz1(C)2Yr86vTETbj3tTe!Gq=s`Y-`EB!CG~vexUjoFDOi&fl9JBxc N002ovPDHLkV1k^u-F*N6 diff --git a/tests/ref/math-equation-numbering.png b/tests/ref/math-equation-numbering.png index 7e8757bd1a2deb4788bda0fb5d2b30e4f0d36998..b127a9a1d8635178b4d58bfe788bf691878e15fa 100644 GIT binary patch delta 4526 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 M07*qoM6N<$g6ye~i~s-t delta 4555 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 delta 1490 zcmV;@1ugo7422AkBoE9za0f0j}>bp)D9>4i%R(gLN}Ql!$B zwlpxc#nO?sFs1FZw2AaOote@O)6Vs4JkN2S%s6qL>I;pT&&%_hnQwmc=A4`Ojog$7 zC&IvxRkC56%__MO(&`tJ5{Fc#h+kXMw^JLBlib@os1Urc9=V zCX-2ST;zgzp23s7t7Ocya;?d99ANMZNXsS1ztj~s-1I~fT`SkmI9ZHzq#J4S%?3uv=*%Y zz-~BfHd~0P$84S^mPxa@kCG&KMEj^Z102{%8=ZWP}qLM#-b z6QJmw&>oaAAyO9$A$R*=_taq&s}L!xMCL3&(m7i_a|w^kKidV0+uc^|b_W3(mmYPZ zpC9_h?FJxk{oVjd(Cy9~QRioWqd7S_R)B$dCqWs{k>?`-)e9`3 z^yKXP;aqN<_5YDMhF8tPg-r6Io|u8>fpgh2`vI8Pg>v;H;6!o%X#mbwqtq6{^`fRT z-NwSo@S5Qa0P4+vt}XM-F#z&Mk$jU&m;HIfch|e{9u?+lX+k zGT9Emr)c_ef6%(8V;K>jzaYH7 zmlMQNe`1DMPE;lC<3u07qH=ZTt>EvxaZOX&}Om_fWSE2{DT z3EC~71hydE+e_a9sJRA;JrC)&0JTe?^ad!FDgdVLguHdIKNLUwb_vJ-=AneJQ>{oz zEfn9*>@mXf$B_OpPWt_f))BVFk8}%xGB^Od}DVimX6@lB@tP4|_HyeX;FTF|zn23m;)d%|Af*QQXbLF}rC45Lk)8<7qS zL{BV5al$(Hhl2oQyNF`V9*%g@4Px#07*qoM6N<$f&iD$tN;K2 From fd5e642fb495e98bde0f7e7fd16dca7ac355c60a Mon Sep 17 00:00:00 2001 From: Max Date: Tue, 26 Nov 2024 12:37:53 +0000 Subject: [PATCH 08/61] Tags shouldn't affect row height in equations (#5458) --- crates/typst-layout/src/math/run.rs | 5 ++++- .../ref/math-equation-tag-affects-row-height.png | Bin 0 -> 329 bytes tests/suite/math/equation.typ | 9 +++++++++ 3 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 tests/ref/math-equation-tag-affects-row-height.png diff --git a/crates/typst-layout/src/math/run.rs b/crates/typst-layout/src/math/run.rs index 8f12c5098..b07f5893a 100644 --- a/crates/typst-layout/src/math/run.rs +++ b/crates/typst-layout/src/math/run.rs @@ -419,7 +419,10 @@ impl MathRunFrameBuilder { } fn affects_row_height(fragment: &MathFragment) -> bool { - !matches!(fragment, MathFragment::Align | MathFragment::Linebreak) + !matches!( + fragment, + MathFragment::Align | MathFragment::Linebreak | MathFragment::Tag(_) + ) } /// Create the spacing between two fragments in a given style. diff --git a/tests/ref/math-equation-tag-affects-row-height.png b/tests/ref/math-equation-tag-affects-row-height.png new file mode 100644 index 0000000000000000000000000000000000000000..d0cc235993bbdf4c0ac463e42f91a8d107ab68d9 GIT binary patch literal 329 zcmV-P0k-~$P)du%@AI8iWNC*;3mu0VZ;$6%0=81XG>2|Np-T5jnaKk{NH1Yw Date: Tue, 26 Nov 2024 12:39:00 +0000 Subject: [PATCH 09/61] Fix `path` with infinite length causing panic (#5457) --- crates/typst-layout/src/shapes.rs | 4 ++++ tests/suite/visualize/line.typ | 4 ++++ tests/suite/visualize/path.typ | 4 ++++ tests/suite/visualize/polygon.typ | 4 ++++ 4 files changed, 16 insertions(+) diff --git a/crates/typst-layout/src/shapes.rs b/crates/typst-layout/src/shapes.rs index 81be12190..a35021721 100644 --- a/crates/typst-layout/src/shapes.rs +++ b/crates/typst-layout/src/shapes.rs @@ -114,6 +114,10 @@ pub fn layout_path( path.close_path(); } + if !size.is_finite() { + bail!(elem.span(), "cannot create path with infinite length"); + } + // Prepare fill and stroke. let fill = elem.fill(styles); let fill_rule = elem.fill_rule(styles); diff --git a/tests/suite/visualize/line.typ b/tests/suite/visualize/line.typ index 7259f72ba..6cbbbb493 100644 --- a/tests/suite/visualize/line.typ +++ b/tests/suite/visualize/line.typ @@ -90,3 +90,7 @@ --- line-bad-point-component-type --- // Error: 14-26 expected relative length, found angle #line(start: (3deg, 10pt), length: 5cm) + +--- line-infinite-length --- +// Error: 2-54 cannot create line with infinite length +#line(start: (0pt, 0pt), end: (float.inf * 1pt, 0pt)) diff --git a/tests/suite/visualize/path.typ b/tests/suite/visualize/path.typ index 95f7a803c..55c0f5340 100644 --- a/tests/suite/visualize/path.typ +++ b/tests/suite/visualize/path.typ @@ -71,6 +71,10 @@ // Error: 7-31 point array must contain exactly two entries #path(((0%, 0%), (0%, 0%, 0%))) +--- path-infinite-length --- +// Error: 2-42 cannot create path with infinite length +#path((0pt, 0pt), (float.inf * 1pt, 0pt)) + --- issue-path-in-sized-container --- // Paths used to implement `LayoutMultiple` rather than `LayoutSingle` without // fulfilling the necessary contract of respecting region expansion. diff --git a/tests/suite/visualize/polygon.typ b/tests/suite/visualize/polygon.typ index 7d8342c8e..ec27194df 100644 --- a/tests/suite/visualize/polygon.typ +++ b/tests/suite/visualize/polygon.typ @@ -51,3 +51,7 @@ --- polygon-bad-point-array --- // Error: 10-17 point array must contain exactly two entries #polygon((50pt,)) + +--- polygon-infinite-size --- +// Error: 2-57 cannot create polygon with infinite size +#polygon((0pt, 0pt), (0pt, 1pt), (float.inf * 1pt, 0pt)) From 22748aaf2e1938f955dc1f98520d6c57a20af097 Mon Sep 17 00:00:00 2001 From: Stephen Waits Date: Tue, 26 Nov 2024 12:50:30 -0800 Subject: [PATCH 10/61] Replace _magic_ "preview" literals with a constant (#5452) --- crates/typst-ide/src/tests.rs | 4 ++++ crates/typst-kit/src/package.rs | 9 ++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/crates/typst-ide/src/tests.rs b/crates/typst-ide/src/tests.rs index 52b189fa0..5a73fa375 100644 --- a/crates/typst-ide/src/tests.rs +++ b/crates/typst-ide/src/tests.rs @@ -119,6 +119,10 @@ impl IdeWorld for TestWorld { fn packages(&self) -> &[(PackageSpec, Option)] { const LIST: &[(PackageSpec, Option)] = &[( PackageSpec { + // NOTE: This literal, `"preview"`, should match the const, `DEFAULT_NAMESPACE`, + // defined in `crates/typst-kit/src/package.rs`. However, we should always use the + // literal here, not `DEFAULT_NAMESPACE`, so that this test fails if its value + // changes in an unexpected way. namespace: EcoString::inline("preview"), name: EcoString::inline("example"), version: PackageVersion { major: 0, minor: 1, patch: 0 }, diff --git a/crates/typst-kit/src/package.rs b/crates/typst-kit/src/package.rs index 412c7982f..bb0ad7c25 100644 --- a/crates/typst-kit/src/package.rs +++ b/crates/typst-kit/src/package.rs @@ -15,6 +15,9 @@ use crate::download::{Downloader, Progress}; /// The default Typst registry. pub const DEFAULT_REGISTRY: &str = "https://packages.typst.org"; +/// The public namespace in the default Typst registry. +pub const DEFAULT_NAMESPACE: &str = "preview"; + /// The default packages sub directory within the package and package cache paths. pub const DEFAULT_PACKAGES_SUBDIR: &str = "typst/packages"; @@ -85,7 +88,7 @@ impl PackageStorage { } // Download from network if it doesn't exist yet. - if spec.namespace == "preview" { + if spec.namespace == DEFAULT_NAMESPACE { self.download_package(spec, &dir, progress)?; if dir.exists() { return Ok(dir); @@ -101,7 +104,7 @@ impl PackageStorage { &self, spec: &VersionlessPackageSpec, ) -> StrResult { - if spec.namespace == "preview" { + if spec.namespace == DEFAULT_NAMESPACE { // For `@preview`, download the package index and find the latest // version. self.download_index()? @@ -155,7 +158,7 @@ impl PackageStorage { package_dir: &Path, progress: &mut dyn Progress, ) -> PackageResult<()> { - assert_eq!(spec.namespace, "preview"); + assert_eq!(spec.namespace, DEFAULT_NAMESPACE); let url = format!("{DEFAULT_REGISTRY}/preview/{}-{}.tar.gz", spec.name, spec.version); From 8fe8b2a23940f76077aa36eda305febd22e7cadc Mon Sep 17 00:00:00 2001 From: Max Date: Tue, 26 Nov 2024 20:51:22 +0000 Subject: [PATCH 11/61] Ignore leading and trailing ignorant fragments in `math.lr` (#5473) --- crates/typst-layout/src/math/lr.rs | 22 +++++++++++++++++----- tests/ref/math-lr-ignore-ignorant.png | Bin 0 -> 970 bytes tests/suite/math/delimited.typ | 17 +++++++++++++++++ 3 files changed, 34 insertions(+), 5 deletions(-) create mode 100644 tests/ref/math-lr-ignore-ignorant.png diff --git a/crates/typst-layout/src/math/lr.rs b/crates/typst-layout/src/math/lr.rs index aba9012f2..d195e67dc 100644 --- a/crates/typst-layout/src/math/lr.rs +++ b/crates/typst-layout/src/math/lr.rs @@ -28,8 +28,21 @@ pub fn layout_lr( } let mut fragments = ctx.layout_into_fragments(body, styles)?; + + // Ignore leading and trailing ignorant fragments. + let start_idx = fragments + .iter() + .position(|f| !f.is_ignorant()) + .unwrap_or(fragments.len()); + let end_idx = fragments + .iter() + .skip(start_idx) + .rposition(|f| !f.is_ignorant()) + .map_or(start_idx, |i| start_idx + i + 1); + let inner_fragments = &mut fragments[start_idx..end_idx]; + let axis = scaled!(ctx, styles, axis_height); - let max_extent = fragments + let max_extent = inner_fragments .iter() .map(|fragment| (fragment.ascent() - axis).max(fragment.descent() + axis)) .max() @@ -39,7 +52,7 @@ pub fn layout_lr( let height = elem.size(styles); // Scale up fragments at both ends. - match fragments.as_mut_slice() { + match inner_fragments { [one] => scale(ctx, styles, one, relative_to, height, None), [first, .., last] => { scale(ctx, styles, first, relative_to, height, Some(MathClass::Opening)); @@ -49,7 +62,7 @@ pub fn layout_lr( } // Handle MathFragment::Variant fragments that should be scaled up. - for fragment in &mut fragments { + for fragment in inner_fragments { if let MathFragment::Variant(ref mut variant) = fragment { if variant.mid_stretched == Some(false) { variant.mid_stretched = Some(true); @@ -60,11 +73,10 @@ pub fn layout_lr( // Remove weak SpacingFragment immediately after the opening or immediately // before the closing. - let original_len = fragments.len(); let mut index = 0; fragments.retain(|fragment| { index += 1; - (index != 2 && index + 1 != original_len) + (index != start_idx + 2 && index + 1 != end_idx) || !matches!(fragment, MathFragment::Spacing(_, true)) }); diff --git a/tests/ref/math-lr-ignore-ignorant.png b/tests/ref/math-lr-ignore-ignorant.png new file mode 100644 index 0000000000000000000000000000000000000000..b82a827f59714684078d46e431f454e6a74a7830 GIT binary patch literal 970 zcmV;*12z1KP)1%+s$Yym5TNCB;E zf`A}fDIiT`r;)a_v^~x_cV;?Qc(^edUpf!xyYtN?AGzEiOo9J&O$mep;XpVL{#)S+ zAJoU61AuZ)n*fTAXXA6N^;806tnUS|e%I>W4j4>Z4*)f8;c#^-z8`|C=NkP2e~dfn z(nzn==&SwG2VAvS}N*K~=R9%+>N*K=DRsEz1l(74(Y9Z1A zau_=A2!`Vu{b7$@)q1x-+;T&)V@E&@LyAe!4HRQx2(t-=T|2=ahS+LZ*IfaB71x2$;S#c@gE$*dTX8kZ$2KtyXBlezw1=0- zr^$k69IX*=b0USq169&xnGm+19+(hrK<)fJ>_%J1!hf*UAs-2qdneuM+fO;$zk+c? zJEt=*f%xGv8pnx2Wu+L+RCrekvHVczHYgLIoC5{~#pc?(P(#llKiuXanr zJ`KLjrN3*Zn6 context it + $ (1 / 2) $ +}) +#box({ + show ")": it => context it + $ (1 / 2) $ +}) +#box({ + show "(": it => context it + show ")": it => context it + $ (1 / 2) $ +}) + --- issue-4188-lr-corner-brackets --- // Test positioning of U+231C to U+231F $⌜a⌟⌞b⌝$ = $⌜$$a$$⌟$$⌞$$b$$⌝$ From 85d3a49a1a0bd50556b8b724a15aa29b074a2db7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20d=27Herbais=20de=20Thun?= Date: Tue, 26 Nov 2024 21:51:46 +0100 Subject: [PATCH 12/61] Added warning when explicit return in code (not markup) discards joined content (#5413) Co-authored-by: Laurenz --- crates/typst-eval/src/call.rs | 4 +- crates/typst-eval/src/code.rs | 37 ++++++-- crates/typst-eval/src/flow.rs | 33 ++++++-- .../typst-library/src/foundations/content.rs | 2 +- .../src/introspection/counter.rs | 5 ++ .../typst-library/src/introspection/state.rs | 5 ++ crates/typst-library/src/model/figure.rs | 4 +- tests/suite/scripting/return.typ | 84 +++++++++++++++++++ 8 files changed, 156 insertions(+), 18 deletions(-) diff --git a/crates/typst-eval/src/call.rs b/crates/typst-eval/src/call.rs index 9dfb7693c..f48734cbc 100644 --- a/crates/typst-eval/src/call.rs +++ b/crates/typst-eval/src/call.rs @@ -253,8 +253,8 @@ pub fn eval_closure( // Handle control flow. let output = body.eval(&mut vm)?; match vm.flow { - Some(FlowEvent::Return(_, Some(explicit))) => return Ok(explicit), - Some(FlowEvent::Return(_, None)) => {} + Some(FlowEvent::Return(_, Some(explicit), _)) => return Ok(explicit), + Some(FlowEvent::Return(_, None, _)) => {} Some(flow) => bail!(flow.forbidden()), None => {} } diff --git a/crates/typst-eval/src/code.rs b/crates/typst-eval/src/code.rs index 918d9d2a4..ba5256c1f 100644 --- a/crates/typst-eval/src/code.rs +++ b/crates/typst-eval/src/code.rs @@ -1,12 +1,15 @@ use ecow::{eco_vec, EcoVec}; -use typst_library::diag::{bail, error, At, SourceResult}; +use typst_library::diag::{bail, error, warning, At, SourceResult}; +use typst_library::engine::Engine; use typst_library::foundations::{ - ops, Array, Capturer, Closure, Content, ContextElem, Dict, Func, NativeElement, Str, - Value, + ops, Array, Capturer, Closure, Content, ContextElem, Dict, Func, NativeElement, + Selector, Str, Value, }; +use typst_library::introspection::{Counter, State}; use typst_syntax::ast::{self, AstNode}; +use typst_utils::singleton; -use crate::{CapturesVisitor, Eval, Vm}; +use crate::{CapturesVisitor, Eval, FlowEvent, Vm}; impl Eval for ast::Code<'_> { type Output = Value; @@ -54,7 +57,8 @@ fn eval_code<'a>( output = ops::join(output, value).at(span)?; - if vm.flow.is_some() { + if let Some(event) = &vm.flow { + warn_for_discarded_content(&mut vm.engine, event, &output); break; } } @@ -358,3 +362,26 @@ impl Eval for ast::Contextual<'_> { Ok(ContextElem::new(func).pack()) } } + +/// Emits a warning when we discard content while returning unconditionally. +fn warn_for_discarded_content(engine: &mut Engine, event: &FlowEvent, joined: &Value) { + let FlowEvent::Return(span, Some(_), false) = event else { return }; + let Value::Content(tree) = &joined else { return }; + + let selector = singleton!( + Selector, + Selector::Or(eco_vec![State::select_any(), Counter::select_any()]) + ); + + let mut warning = warning!( + *span, + "this return unconditionally discards the content before it"; + hint: "try omitting the `return` to automatically join all values" + ); + + if tree.query_first(selector).is_some() { + warning.hint("state/counter updates are content that must end up in the document to have an effect"); + } + + engine.sink.warn(warning); +} diff --git a/crates/typst-eval/src/flow.rs b/crates/typst-eval/src/flow.rs index 231e68998..b5ba487f5 100644 --- a/crates/typst-eval/src/flow.rs +++ b/crates/typst-eval/src/flow.rs @@ -17,8 +17,8 @@ pub enum FlowEvent { /// Skip the remainder of the current iteration in a loop. Continue(Span), /// Stop execution of a function early, optionally returning an explicit - /// value. - Return(Span, Option), + /// value. The final boolean indicates whether the return was conditional. + Return(Span, Option, bool), } impl FlowEvent { @@ -31,7 +31,7 @@ impl FlowEvent { Self::Continue(span) => { error!(span, "cannot continue outside of loop") } - Self::Return(span, _) => { + Self::Return(span, _, _) => { error!(span, "cannot return outside of function") } } @@ -43,13 +43,20 @@ impl Eval for ast::Conditional<'_> { fn eval(self, vm: &mut Vm) -> SourceResult { let condition = self.condition(); - if condition.eval(vm)?.cast::().at(condition.span())? { - self.if_body().eval(vm) + let output = if condition.eval(vm)?.cast::().at(condition.span())? { + self.if_body().eval(vm)? } else if let Some(else_body) = self.else_body() { - else_body.eval(vm) + else_body.eval(vm)? } else { - Ok(Value::None) + Value::None + }; + + // Mark the return as conditional. + if let Some(FlowEvent::Return(_, _, conditional)) = &mut vm.flow { + *conditional = true; } + + Ok(output) } } @@ -95,6 +102,11 @@ impl Eval for ast::WhileLoop<'_> { vm.flow = flow; } + // Mark the return as conditional. + if let Some(FlowEvent::Return(_, _, conditional)) = &mut vm.flow { + *conditional = true; + } + Ok(output) } } @@ -168,6 +180,11 @@ impl Eval for ast::ForLoop<'_> { vm.flow = flow; } + // Mark the return as conditional. + if let Some(FlowEvent::Return(_, _, conditional)) = &mut vm.flow { + *conditional = true; + } + Ok(output) } } @@ -200,7 +217,7 @@ impl Eval for ast::FuncReturn<'_> { fn eval(self, vm: &mut Vm) -> SourceResult { let value = self.body().map(|body| body.eval(vm)).transpose()?; if vm.flow.is_none() { - vm.flow = Some(FlowEvent::Return(self.span(), value)); + vm.flow = Some(FlowEvent::Return(self.span(), value, false)); } Ok(Value::None) } diff --git a/crates/typst-library/src/foundations/content.rs b/crates/typst-library/src/foundations/content.rs index a274b8bfa..69103e080 100644 --- a/crates/typst-library/src/foundations/content.rs +++ b/crates/typst-library/src/foundations/content.rs @@ -426,7 +426,7 @@ impl Content { /// selector. /// /// Elements produced in `show` rules will not be included in the results. - pub fn query_first(&self, selector: Selector) -> Option { + pub fn query_first(&self, selector: &Selector) -> Option { let mut result = None; self.traverse(&mut |element| { if result.is_none() && selector.matches(&element, None) { diff --git a/crates/typst-library/src/introspection/counter.rs b/crates/typst-library/src/introspection/counter.rs index 2e7180c66..b884844c6 100644 --- a/crates/typst-library/src/introspection/counter.rs +++ b/crates/typst-library/src/introspection/counter.rs @@ -393,6 +393,11 @@ impl Counter { let context = Context::new(Some(location), styles); state.display(engine, context.track(), &numbering) } + + /// Selects all state updates. + pub fn select_any() -> Selector { + CounterUpdateElem::elem().select() + } } #[scope] diff --git a/crates/typst-library/src/introspection/state.rs b/crates/typst-library/src/introspection/state.rs index 62aba5ffb..772a4fbfb 100644 --- a/crates/typst-library/src/introspection/state.rs +++ b/crates/typst-library/src/introspection/state.rs @@ -261,6 +261,11 @@ impl State { fn selector(&self) -> Selector { select_where!(StateUpdateElem, Key => self.key.clone()) } + + /// Selects all state updates. + pub fn select_any() -> Selector { + StateUpdateElem::elem().select() + } } #[scope] diff --git a/crates/typst-library/src/model/figure.rs b/crates/typst-library/src/model/figure.rs index abdf2a4ee..3e2777c1a 100644 --- a/crates/typst-library/src/model/figure.rs +++ b/crates/typst-library/src/model/figure.rs @@ -257,7 +257,7 @@ impl Synthesize for Packed { // Determine the figure's kind. let kind = elem.kind(styles).unwrap_or_else(|| { elem.body() - .query_first(Selector::can::()) + .query_first(&Selector::can::()) .map(|elem| FigureKind::Elem(elem.func())) .unwrap_or_else(|| FigureKind::Elem(ImageElem::elem())) }); @@ -289,7 +289,7 @@ impl Synthesize for Packed { let descendant = match kind { FigureKind::Elem(func) => elem .body() - .query_first(Selector::Elem(func, None)) + .query_first(&Selector::Elem(func, None)) .map(Cow::Owned), FigureKind::Name(_) => None, }; diff --git a/tests/suite/scripting/return.typ b/tests/suite/scripting/return.typ index 63e1c0b99..5cb2d15a9 100644 --- a/tests/suite/scripting/return.typ +++ b/tests/suite/scripting/return.typ @@ -85,3 +85,87 @@ // Error: 16-16 expected semicolon or line break #return a + b Hello World ] + +--- return-discard-content --- +// Test that discarding joined content is a warning. +#let f() = { + [Hello, World!] + // Warning: 3-16 this return unconditionally discards the content before it + // Hint: 3-16 try omitting the `return` to automatically join all values + return "nope" +} + +#test(f(), "nope") + +--- return-discard-content-nested --- +#let f() = { + [Hello, World!] + { + // Warning: 5-18 this return unconditionally discards the content before it + // Hint: 5-18 try omitting the `return` to automatically join all values + return "nope" + } +} + +#test(f(), "nope") + +--- return-discard-state --- +// Test that discarding a joined content with state is special warning + +#let f() = { + state("hello").update("world") + + // Warning: 3-19 this return unconditionally discards the content before it + // Hint: 3-19 try omitting the `return` to automatically join all values + // Hint: 3-19 state/counter updates are content that must end up in the document to have an effect + return [ Hello ] +} + +#test(f(), [ Hello ]) + +--- return-discard-loop --- +// Test that return from within a control flow construct is not a warning. +#let f1() = { + state("hello").update("world") + for x in range(3) { + return "nope1" + } +} + +#let f2() = { + state("hello").update("world") + let i = 0 + while i < 10 { + return "nope2" + } +} + +#test(f1(), "nope1") +#test(f2(), "nope2") + +--- return-no-discard --- +// Test that returning the joined content is not a warning. +#let f() = { + state("hello").update("world") + return +} + +#test(f(), state("hello").update("world")) + +--- return-discard-not-content --- +// Test that non-content joined value is not a warning. +#let f() = { + (33,) + return (66,) +} + +#test(f(), (66, )) + +--- return-discard-markup --- +// Test that discarding markup is not a warning. +#let f() = [ + hello + #return [nope] +] + +#test(f(), [nope]) From e550dce62d3def1fb2dfbd1f2b15491e1424da09 Mon Sep 17 00:00:00 2001 From: Stephen Waits Date: Wed, 27 Nov 2024 01:09:44 -0800 Subject: [PATCH 13/61] More magic "preview" -> DEFAULT_NAMESPACE (#5478) --- crates/typst-kit/src/package.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/crates/typst-kit/src/package.rs b/crates/typst-kit/src/package.rs index bb0ad7c25..e7eb71ee4 100644 --- a/crates/typst-kit/src/package.rs +++ b/crates/typst-kit/src/package.rs @@ -31,7 +31,7 @@ pub struct PackageStorage { package_path: Option, /// The downloader used for fetching the index and packages. downloader: Downloader, - /// The cached index of the preview namespace. + /// The cached index of the default namespace. index: OnceCell>, } @@ -105,7 +105,7 @@ impl PackageStorage { spec: &VersionlessPackageSpec, ) -> StrResult { if spec.namespace == DEFAULT_NAMESPACE { - // For `@preview`, download the package index and find the latest + // For `DEFAULT_NAMESPACE`, download the package index and find the latest // version. self.download_index()? .iter() @@ -134,7 +134,7 @@ impl PackageStorage { pub fn download_index(&self) -> StrResult<&[PackageInfo]> { self.index .get_or_try_init(|| { - let url = format!("{DEFAULT_REGISTRY}/preview/index.json"); + let url = format!("{DEFAULT_REGISTRY}/{DEFAULT_NAMESPACE}/index.json"); match self.downloader.download(&url) { Ok(response) => response.into_json().map_err(|err| { eco_format!("failed to parse package index: {err}") @@ -151,7 +151,7 @@ impl PackageStorage { /// Download a package over the network. /// /// # Panics - /// Panics if the package spec namespace isn't `preview`. + /// Panics if the package spec namespace isn't `DEFAULT_NAMESPACE`. pub fn download_package( &self, spec: &PackageSpec, @@ -160,8 +160,10 @@ impl PackageStorage { ) -> PackageResult<()> { assert_eq!(spec.namespace, DEFAULT_NAMESPACE); - let url = - format!("{DEFAULT_REGISTRY}/preview/{}-{}.tar.gz", spec.name, spec.version); + let url = format!( + "{DEFAULT_REGISTRY}/{DEFAULT_NAMESPACE}/{}-{}.tar.gz", + spec.name, spec.version + ); let data = match self.downloader.download_with_progress(&url, progress) { Ok(data) => data, From 6bf1350b16b90ae915836f94dff440da671ebd45 Mon Sep 17 00:00:00 2001 From: +merlan #flirora Date: Wed, 27 Nov 2024 06:04:54 -0500 Subject: [PATCH 14/61] Add support for interpreting f32 in float.{from-bytes, to-bytes} (#5480) --- crates/typst-library/src/foundations/float.rs | 47 +++++++++++++------ tests/suite/foundations/float.typ | 7 ++- 2 files changed, 38 insertions(+), 16 deletions(-) diff --git a/crates/typst-library/src/foundations/float.rs b/crates/typst-library/src/foundations/float.rs index bb3232ee8..c3d4e0e73 100644 --- a/crates/typst-library/src/foundations/float.rs +++ b/crates/typst-library/src/foundations/float.rs @@ -128,16 +128,21 @@ impl f64 { #[default(Endianness::Little)] endian: Endianness, ) -> StrResult { - // Convert slice to an array of length 8. - let buf: [u8; 8] = match bytes.as_ref().try_into() { - Ok(buffer) => buffer, - Err(_) => bail!("bytes must have a length of exactly 8"), + // Convert slice to an array of length 4 or 8. + if let Ok(buffer) = <[u8; 8]>::try_from(bytes.as_ref()) { + return Ok(match endian { + Endianness::Little => f64::from_le_bytes(buffer), + Endianness::Big => f64::from_be_bytes(buffer), + }); + }; + if let Ok(buffer) = <[u8; 4]>::try_from(bytes.as_ref()) { + return Ok(match endian { + Endianness::Little => f32::from_le_bytes(buffer), + Endianness::Big => f32::from_be_bytes(buffer), + } as f64); }; - Ok(match endian { - Endianness::Little => f64::from_le_bytes(buf), - Endianness::Big => f64::from_be_bytes(buf), - }) + bail!("bytes must have a length of 4 or 8"); } /// Converts a float to bytes. @@ -153,13 +158,25 @@ impl f64 { #[named] #[default(Endianness::Little)] endian: Endianness, - ) -> Bytes { - match endian { - Endianness::Little => self.to_le_bytes(), - Endianness::Big => self.to_be_bytes(), - } - .as_slice() - .into() + #[named] + #[default(8)] + size: u32, + ) -> StrResult { + Ok(match size { + 8 => match endian { + Endianness::Little => self.to_le_bytes(), + Endianness::Big => self.to_be_bytes(), + } + .as_slice() + .into(), + 4 => match endian { + Endianness::Little => (self as f32).to_le_bytes(), + Endianness::Big => (self as f32).to_be_bytes(), + } + .as_slice() + .into(), + _ => bail!("size must be either 4 or 8"), + }) } } diff --git a/tests/suite/foundations/float.typ b/tests/suite/foundations/float.typ index 2e9e07f2a..716ecd6b6 100644 --- a/tests/suite/foundations/float.typ +++ b/tests/suite/foundations/float.typ @@ -53,8 +53,13 @@ #test(1.0.to-bytes(), bytes((0, 0, 0, 0, 0, 0, 240, 63))) #test(1.0.to-bytes(endian: "big"), bytes((63, 240, 0, 0, 0, 0, 0, 0))) +#test(float.from-bytes(bytes((0, 0, 32, 64))), 2.5) +#test(float.from-bytes(bytes((64, 32, 0, 0)), endian: "big"), 2.5) +#test(2.5.to-bytes(size: 4), bytes((0, 0, 32, 64))) +#test(2.5.to-bytes(size: 4, endian: "big"), bytes((64, 32, 0, 0))) + --- float-from-bytes-bad-length --- -// Error: 2-54 bytes must have a length of exactly 8 +// Error: 2-54 bytes must have a length of 4 or 8 #float.from-bytes(bytes((0, 0, 0, 0, 0, 0, 0, 1, 0))) --- float-repr --- From f29fbea2fcabed9ed6fc3c19d585edc6fe45370b Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 27 Nov 2024 11:11:49 +0000 Subject: [PATCH 15/61] Use extended shape information for glyph `text_like`-ness (#5483) --- crates/typst-layout/src/math/fragment.rs | 11 +++++++++-- crates/typst-layout/src/math/stretch.rs | 1 + .../ref/math-attach-scripts-extended-shapes.png | Bin 0 -> 1057 bytes tests/ref/math-lr-scripts.png | Bin 0 -> 611 bytes tests/ref/math-stretch-vertical-scripts.png | Bin 0 -> 379 bytes tests/suite/math/attach.typ | 6 ++++++ tests/suite/math/delimited.typ | 6 ++++++ tests/suite/math/stretch.typ | 6 ++++++ 8 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 tests/ref/math-attach-scripts-extended-shapes.png create mode 100644 tests/ref/math-lr-scripts.png create mode 100644 tests/ref/math-stretch-vertical-scripts.png diff --git a/crates/typst-layout/src/math/fragment.rs b/crates/typst-layout/src/math/fragment.rs index 691a97f70..ac8946681 100644 --- a/crates/typst-layout/src/math/fragment.rs +++ b/crates/typst-layout/src/math/fragment.rs @@ -148,7 +148,8 @@ impl MathFragment { pub fn is_text_like(&self) -> bool { match self { - Self::Glyph(_) | Self::Variant(_) => self.class() != MathClass::Large, + Self::Glyph(glyph) => !glyph.extended_shape, + Self::Variant(variant) => !variant.extended_shape, MathFragment::Frame(frame) => frame.text_like, _ => false, } @@ -247,6 +248,7 @@ pub struct GlyphFragment { pub dests: SmallVec<[Destination; 1]>, pub hidden: bool, pub limits: Limits, + pub extended_shape: bool, } impl GlyphFragment { @@ -302,6 +304,7 @@ impl GlyphFragment { span, dests: LinkElem::dests_in(styles), hidden: HideElem::hidden_in(styles), + extended_shape: false, }; fragment.set_id(ctx, id); fragment @@ -332,7 +335,8 @@ impl GlyphFragment { let accent_attach = accent_attach(ctx, id, self.font_size).unwrap_or((width + italics) / 2.0); - if !is_extended_shape(ctx, id) { + let extended_shape = is_extended_shape(ctx, id); + if !extended_shape { width += italics; } @@ -342,6 +346,7 @@ impl GlyphFragment { self.descent = -bbox.y_min.scaled(ctx, self.font_size); self.italics_correction = italics; self.accent_attach = accent_attach; + self.extended_shape = extended_shape; } pub fn height(&self) -> Abs { @@ -358,6 +363,7 @@ impl GlyphFragment { math_size: self.math_size, span: self.span, limits: self.limits, + extended_shape: self.extended_shape, frame: self.into_frame(), mid_stretched: None, } @@ -465,6 +471,7 @@ pub struct VariantFragment { pub span: Span, pub limits: Limits, pub mid_stretched: Option, + pub extended_shape: bool, } impl VariantFragment { diff --git a/crates/typst-layout/src/math/stretch.rs b/crates/typst-layout/src/math/stretch.rs index 9b5cd47a9..3d7c88cfc 100644 --- a/crates/typst-layout/src/math/stretch.rs +++ b/crates/typst-layout/src/math/stretch.rs @@ -295,6 +295,7 @@ fn assemble( span: base.span, limits: base.limits, mid_stretched: None, + extended_shape: true, } } diff --git a/tests/ref/math-attach-scripts-extended-shapes.png b/tests/ref/math-attach-scripts-extended-shapes.png new file mode 100644 index 0000000000000000000000000000000000000000..c99d3c7fdbf1ece5e07dc52ebaf6ae8359dcfd37 GIT binary patch literal 1057 zcmV++1m63JP)2JqPVzZ7IXTTa#6ETC z06u{C9A4Ab?hnjJ(cZzCw-8mQJ_nxBA!q7Ek>{R`kfxhez~|)$C48j|fG3&%Fntoy z)Yn`7!3-2R3%ZpHgy~Ze!k(g33!;3a20I}EsX{-F5SVfWTz>*chzu4*2ydP_svb6I zjg_;z;QDI4Hn0twk4?&O)5i`({l6M3t*-Q+H{~@vehEgSAE|*?bVKjA5A1|HEjibV z+}p6->X6}Cs|$do7JbY;qi4?ILu$H-7rb+vkNRGtXB*zibzTCyfM;);%ka3s;ygan@2?pv znGz%D#^IGoZ~jt_8u+)o%(6OU`1U0Mr`k0E!Tv0X6gD>Z{lSz2 z_;wl5@15SWq=Ozjnf~F@m8&esJNK%kW{`g5HqyulA5NY`G@S&OXS}BnwLS#Ed)L8C zv$Tl$J20vXfSj+l7b$sfyTXU>cOla2Aa&07`M&%+wbq%ns4QSWnZM?{2w)zzqqd>r zgtU5A1#E6fHxg`}XSXKJ8v?-A8EhlueD1!}t`6>4Z@Pw5l^b?v46t7Pbe^t2`hB>m zr>Cp&`y-_xu*TJ)4rc3{B}Dz*D4Mk*syO0=zl@4`c`*R^F5>>cNia(+P?@SY7v-0fl;qgQhZ-QJRjKAyjMg!LD?1aSZY?}-xqO9drc6RZX@L}Fy z@eeU9}FBW5p26ijS4dKJK6Wq|&0C2MjhzETdS-Q3mc;e-@4KW5#+6-XJ zLeWP7fDaAf!<#c~NC?&jpl}uEBFhgDb?FiH_4PVgE=Dxh2zXPT08l||OBHC{0B^Z) zbZn|VGAADao(i5<`KDstg*gCw+Bv==)y;hXqVeT=vsA;LVLHz4Or;llo0QynetgNykA bydV7!t$WH?;%)5K00000NkvXXu0mjf^UwmD literal 0 HcmV?d00001 diff --git a/tests/ref/math-lr-scripts.png b/tests/ref/math-lr-scripts.png new file mode 100644 index 0000000000000000000000000000000000000000..7ce35475be4aa21022611c155bf2fee8b3340d9c GIT binary patch literal 611 zcmV-p0-XJcP)FIKXstJFX=(bV`BjSwr9xRSM6 z+B7wNi8LX_2GfmclHJYz%(Kfv8*}imYoX5f^uP>!U>F#f4s1{z3}!Hc8SDU@JPlaw z&7p0tZaDzI=6}NKa0cz`u^X&FjN>>2qwDy3 zJ0Z~2yCADDD$Dz2r{E{Pm<88{`l<$81gs7c{B-Ap{jiYulI`{7C;xM;y+v5*1#Bi}$Z zUPQLbz?%^E*$it%c#EDNpq5a-p!L#}5CjQS{k|ZmKp<>4Or6wrkHaj*N4Z)2S;4FJ x*$-#MFL`Son~S4eGtcYn^M%0-X7GOl{{~1`6Uc<_=okP1002ovPDHLkV1fW?A)){P literal 0 HcmV?d00001 diff --git a/tests/ref/math-stretch-vertical-scripts.png b/tests/ref/math-stretch-vertical-scripts.png new file mode 100644 index 0000000000000000000000000000000000000000..ce933c3c36895bcc76d253ceac8f071cc5d9bfc1 GIT binary patch literal 379 zcmV->0fhdEP)jxL}(PZpGKWSzrJV zSm4oxA5O^NNn_Wr$lwW`1pst#Sf@=2+eTiF<#}Me{^QwBy4!Sc3b;M(rbP!wH5?r^ zIM@R&ZG=3P?)ceI@39G+NeMsRce0W$5W>m#=cLMYtX2mP@AQBdeJ`YP7W*%Br+Uu+ zT~RrMy-zAQhASuYq;km**QwwJ@bl5l>>q{=I~R8>k3`YNJQX~xGlQELrWhs=;46X! zDtOD-9Xr=DR|_m~A8;;RV}W1KOPdVv5f1YRGQdq7 Date: Wed, 27 Nov 2024 06:14:20 -0500 Subject: [PATCH 16/61] Add Bulgarian translation entries (#5476) --- crates/typst-library/src/text/lang.rs | 4 +++- crates/typst-library/translations/bg.txt | 8 ++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 crates/typst-library/translations/bg.txt diff --git a/crates/typst-library/src/text/lang.rs b/crates/typst-library/src/text/lang.rs index b9b9ef555..c75e5225f 100644 --- a/crates/typst-library/src/text/lang.rs +++ b/crates/typst-library/src/text/lang.rs @@ -14,8 +14,9 @@ macro_rules! translation { }; } -const TRANSLATIONS: [(&str, &str); 37] = [ +const TRANSLATIONS: [(&str, &str); 38] = [ translation!("ar"), + translation!("bg"), translation!("ca"), translation!("cs"), translation!("da"), @@ -63,6 +64,7 @@ impl Lang { pub const ARABIC: Self = Self(*b"ar ", 2); pub const BASQUE: Self = Self(*b"eu ", 2); pub const BOKMÅL: Self = Self(*b"nb ", 2); + pub const BULGARIAN: Self = Self(*b"bg ", 2); pub const CATALAN: Self = Self(*b"ca ", 2); pub const CHINESE: Self = Self(*b"zh ", 2); pub const CROATIAN: Self = Self(*b"hr ", 2); diff --git a/crates/typst-library/translations/bg.txt b/crates/typst-library/translations/bg.txt new file mode 100644 index 000000000..101a25221 --- /dev/null +++ b/crates/typst-library/translations/bg.txt @@ -0,0 +1,8 @@ +figure = Фиг. +table = Таблица +equation = Уравнение +bibliography = Библиография +heading = Раздел +outline = Съдържание +raw = Приложение +page = стр. \ No newline at end of file From 578ba640f3666b5dc309ec0a03d6f6d5f2ae14a2 Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 27 Nov 2024 11:16:25 +0000 Subject: [PATCH 17/61] Fix weak spacing being ignored unconditionally in `math.lr` (#5477) --- crates/typst-layout/src/math/lr.rs | 14 +++++++++++--- tests/ref/math-lr-weak-spacing.png | Bin 647 -> 1171 bytes tests/suite/math/delimited.typ | 1 + 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/crates/typst-layout/src/math/lr.rs b/crates/typst-layout/src/math/lr.rs index d195e67dc..01a7f4ccd 100644 --- a/crates/typst-layout/src/math/lr.rs +++ b/crates/typst-layout/src/math/lr.rs @@ -62,7 +62,7 @@ pub fn layout_lr( } // Handle MathFragment::Variant fragments that should be scaled up. - for fragment in inner_fragments { + for fragment in inner_fragments.iter_mut() { if let MathFragment::Variant(ref mut variant) = fragment { if variant.mid_stretched == Some(false) { variant.mid_stretched = Some(true); @@ -74,10 +74,18 @@ pub fn layout_lr( // Remove weak SpacingFragment immediately after the opening or immediately // before the closing. let mut index = 0; + let opening_exists = inner_fragments + .first() + .is_some_and(|f| f.class() == MathClass::Opening); + let closing_exists = inner_fragments + .last() + .is_some_and(|f| f.class() == MathClass::Closing); fragments.retain(|fragment| { + let discard = (index == start_idx + 1 && opening_exists + || index + 2 == end_idx && closing_exists) + && matches!(fragment, MathFragment::Spacing(_, true)); index += 1; - (index != start_idx + 2 && index + 1 != end_idx) - || !matches!(fragment, MathFragment::Spacing(_, true)) + !discard }); ctx.extend(fragments); diff --git a/tests/ref/math-lr-weak-spacing.png b/tests/ref/math-lr-weak-spacing.png index 871aaa2ebaae745a58eca5693ff7251a5ebee37c..2478ca2a3dd281a756ea9580786b9c360109eb81 100644 GIT binary patch delta 1164 zcmV;71atd`1(OMo7k@nn00000Lz?o@000D9Nkl?r9Kdnxg(w$eJa|we z2RWFi2RAgLMN}BnMhIg~R0K*DgrG7OI22@Q43#cmm7&lAb||PoTR_X!lukEVrh6yd z3vGpNQ@hR5ng2}ZEB(KJXa<_L{|t$V`F}3&<(HRylXrM|Gk^Gx%+SJGSPN_6`xw^k zBv_qqeXw!NTK$6g@7UR?n^t%;!CO7E9GUyn+exrlO}N1ilt>`8ju4=;8HOv7l{=6P zwICGu0bI|_aiI7?UD)$zl0nIg_>|j_UbHrk>J{MI^06pvCF1%WUY(0JBjLp z+#^>Gw{t5RXpYU82U)3k2b|<6!6gr02lZhiTaC2sf)ypKu}EjTLal`eIxj>(S5d+0 z+(vopdR6kjh@~Sr;r9W$qmlJLIoGE?oVPc1`8MkJ!haJ!zO%r&cu*_=z63}-i`ZJi z;7uU11#lV=3T8E_4%?F<;|m5hfbItCu5~qTxLWfM4FK({fR#QPx)DdtqY3$};hMcxgXE^-2TN3~zK2tkFd;EE%JF>8X$qE6$D6 z3MVBHEGgf7M-e0>X&(VCtcA6(7QS!c-W}Nlr+>~ou50>XYqE#nY=2<=ncFfC$A&+& zOhwqll0!|68bs&|buqdLut`HM zFn<8Jo5#Xkew$Yrz}lJROkl`4W!T7UJ4h=onez+eI_}mI#?M)khdoDNX@5Q#caDYeEx?BGO)Z$yfLotngV<)!RgHzY z9D@xaQ7p^nZin`vZ`;*S2bNGz64;g7kakf`TEe-k_uQO z!NnLUVq1NDl<9O2tVGzC#yu?iE9ahIvT$p02RDlgJusNvoY_%bUtbDsmw?f5fPY<3 z+)-u)VgOpsTmN$B^j~QMM4! z%jE$aR%B*m-eLmT>Hz3m=0@-tRE2}&482%&TV?Ja+aG_H>4$Zh1n1EFacSB|KnrVO eE&Trq{|ld1Y-_4G?v?-m002ovP6b4+LSTYp;vQT8 delta 635 zcmV->0)+jO35Nxc7k?uN00000dPb2t0006`Nkl6ZaK>txuG>_ zYin+E@9=pZgeM60v>=G*b@_hzeh%;JH;#WKP7SMJHLQmJVt-guNUWxJAIMvjeIzMy zua`niRN(?*m$}T0Ake<23JR2jtr4I<2*HUT$V`ueXBdI`JA!X^M8*h!fBiaXfXm9l z^J^V2`2?8Jm}I*<=MdQSbia2EJWJ5o&yHa}}JBbZ3O=;Q1cFf_cl>EN>#7%%DE3Hti( z8XwQ4V9Lr5e-f_dd^M9rklxQJQ?SdKF!BnKCIrX9*=x)+*o3QpdoZ#I)0D3K?i?;H z0{7}|VddfO%KEemj8(#_FlS8g;3`OxfRg|fU5HoHL4Vix zE*CT($$9Ox<@Q;=Vg+y<12V!`UCFaO5dvA@-T_+}@NLBamQqB;Z72=ToG!tyJvv4{ zd@8$_cCNpyx<-#T0YG01)Q{4xD{VHwa5M0vWLR`jM+ZFpy0QVH#!25TNY=Ob1N`!}X6(rjK6A3uUGFRE}=Ik5`9>{-$ae*(tO V%+W6@xoZFb002ovPDHLkV1jaTCg1=7 diff --git a/tests/suite/math/delimited.typ b/tests/suite/math/delimited.typ index fc00333d3..226740501 100644 --- a/tests/suite/math/delimited.typ +++ b/tests/suite/math/delimited.typ @@ -87,6 +87,7 @@ $ 1/(2 y (x) (2(3)) $ // Test ignoring weak spacing immediately after the opening // and immediately before the closing. $ [#h(1em, weak: true)A(dif x, f(x) dif x)sum#h(1em, weak: true)] $ +$ lr(\[#h(1em, weak: true)lr(A dif x, f(x) dif x\))sum#h(1em, weak:true)a) $ --- math-lr-nested --- // Test nested lr calls. From 27cc489a1d98c6badd6151b5ed70af4a532692d9 Mon Sep 17 00:00:00 2001 From: 3w36zj6 <52315048+3w36zj6@users.noreply.github.com> Date: Thu, 28 Nov 2024 00:38:41 +0900 Subject: [PATCH 18/61] Add missing full stop to documentation of `ImageElem` struct (#5484) --- crates/typst-library/src/visualize/image/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/typst-library/src/visualize/image/mod.rs b/crates/typst-library/src/visualize/image/mod.rs index 359db2528..868a3c5b4 100644 --- a/crates/typst-library/src/visualize/image/mod.rs +++ b/crates/typst-library/src/visualize/image/mod.rs @@ -46,7 +46,7 @@ use crate::World; /// ``` #[elem(scope, Show, LocalName, Figurable)] pub struct ImageElem { - /// Path to an image file + /// Path to an image file. /// /// For more details, see the [Paths section]($syntax/#paths). #[required] From 89d96c623dc882fcdbff02e201bcfb27e6bbfd2a Mon Sep 17 00:00:00 2001 From: +merlan #flirora Date: Wed, 27 Nov 2024 11:36:04 -0500 Subject: [PATCH 19/61] Let decimal constructor accept decimal values (#5481) --- crates/typst-library/src/foundations/decimal.rs | 5 +++++ tests/suite/foundations/decimal.typ | 4 +++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/crates/typst-library/src/foundations/decimal.rs b/crates/typst-library/src/foundations/decimal.rs index cf11e1ddd..d363a6a41 100644 --- a/crates/typst-library/src/foundations/decimal.rs +++ b/crates/typst-library/src/foundations/decimal.rs @@ -317,6 +317,7 @@ impl Decimal { }) .at(value.span) } + ToDecimal::Decimal(decimal) => Ok(decimal), } } } @@ -429,6 +430,8 @@ impl Hash for Decimal { /// A value that can be cast to a decimal. pub enum ToDecimal { + /// A decimal to be converted to itself. + Decimal(Decimal), /// A string with the decimal's representation. Str(EcoString), /// An integer to be converted to the equivalent decimal. @@ -439,7 +442,9 @@ pub enum ToDecimal { cast! { ToDecimal, + v: Decimal => Self::Decimal(v), v: i64 => Self::Int(v), + v: bool => Self::Int(v as i64), v: f64 => Self::Float(v), v: Str => Self::Str(EcoString::from(v)), } diff --git a/tests/suite/foundations/decimal.typ b/tests/suite/foundations/decimal.typ index bae0d2e6a..6b3787e96 100644 --- a/tests/suite/foundations/decimal.typ +++ b/tests/suite/foundations/decimal.typ @@ -4,10 +4,12 @@ #test(decimal("\u{2212}7654.321"), decimal("-7654.321")) #test(decimal({ 3.141592653 }), decimal("3.141592653000000012752934707")) #test(decimal({ -3.141592653 }), decimal("-3.141592653000000012752934707")) +#test(decimal(decimal(3)), decimal("3.0")) +#test(decimal(true), decimal("1.0")) #test(type(decimal(10)), decimal) --- decimal-constructor-bad-type --- -// Error: 10-17 expected integer, float, or string, found type +// Error: 10-17 expected decimal, integer, boolean, float, or string, found type #decimal(decimal) --- decimal-constructor-bad-value --- From 9ce40cb91fd5c6d088c30cc99c3e41e618e6c6fc Mon Sep 17 00:00:00 2001 From: +merlan #flirora Date: Fri, 29 Nov 2024 04:20:35 -0500 Subject: [PATCH 20/61] architecture.md: Update crate list (#5487) --- docs/dev/architecture.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/dev/architecture.md b/docs/dev/architecture.md index d5bbba2e3..bbae06792 100644 --- a/docs/dev/architecture.md +++ b/docs/dev/architecture.md @@ -11,12 +11,20 @@ Let's start with a broad overview of the directories in this repository: and library. - `crates/typst-cli`: Typst's command line interface. This is a relatively small layer on top of the compiler and the exporters. +- `crates/typst-eval`: The interpreter for the Typst language. - `crates/typst-ide`: Exposes IDE functionality. +- `crates/typst-kit`: Contains various default implementation of + functionality used in `typst-cli`. +- `crates/typst-layout`: Typst's layout engine. +- `crates/typst-library`: Typst's standard library. - `crates/typst-macros`: Procedural macros for the compiler. - `crates/typst-pdf`: The PDF exporter. +- `crates/typst-realize`: Typst's realization subsystem. - `crates/typst-render`: A renderer for Typst frames. - `crates/typst-svg`: The SVG exporter. - `crates/typst-syntax`: Home to the parser and syntax tree definition. +- `crates/typst-timing`: Performance timing for Typst. +- `crates/typst-utils`: Utilities for Typst. - `docs`: Generates the content of the official [documentation][docs] from markdown files and the inline Rust documentation. Only generates the content and structure, not the concrete @@ -30,8 +38,8 @@ The source-to-PDF compilation process of a Typst file proceeds in four phases. 1. **Parsing:** Turns a source string into a syntax tree. 2. **Evaluation:** Turns a syntax tree and its dependencies into content. -4. **Layout:** Layouts content into frames. -5. **Export:** Turns frames into an output format like PDF or a raster graphic. +3. **Layout:** Layouts content into frames. +4. **Export:** Turns frames into an output format like PDF or a raster graphic. The Typst compiler is _incremental:_ Recompiling a document that was compiled previously is much faster than compiling from scratch. Most of the hard work is From 3ab131c22ca6e7ef9e28d6352a96bb0846fc608b Mon Sep 17 00:00:00 2001 From: wznmickey Date: Fri, 29 Nov 2024 04:21:19 -0500 Subject: [PATCH 21/61] Handle SIGPIPE (#5444) --- Cargo.lock | 10 ++++++++++ Cargo.toml | 1 + crates/typst-cli/Cargo.toml | 1 + crates/typst-cli/src/main.rs | 4 ++++ 4 files changed, 16 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 330dcce5c..0afce4620 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2245,6 +2245,15 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "sigpipe" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5584bfb3e0d348139d8210285e39f6d2f8a1902ac06de343e06357d1d763d8e6" +dependencies = [ + "libc", +] + [[package]] name = "simd-adler32" version = "0.3.7" @@ -2705,6 +2714,7 @@ dependencies = [ "serde_json", "serde_yaml 0.9.34+deprecated", "shell-escape", + "sigpipe", "tar", "tempfile", "toml", diff --git a/Cargo.toml b/Cargo.toml index 1f6eb5acc..66885bcf9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -104,6 +104,7 @@ serde = { version = "1.0.184", features = ["derive"] } serde_json = "1" serde_yaml = "0.9" shell-escape = "0.1.5" +sigpipe = "0.1" siphasher = "1" smallvec = { version = "1.11.1", features = ["union", "const_generics", "const_new"] } stacker = "0.1.15" diff --git a/crates/typst-cli/Cargo.toml b/crates/typst-cli/Cargo.toml index 8f1407d6e..4b4abc55f 100644 --- a/crates/typst-cli/Cargo.toml +++ b/crates/typst-cli/Cargo.toml @@ -46,6 +46,7 @@ serde = { workspace = true } serde_json = { workspace = true } serde_yaml = { workspace = true } shell-escape = { workspace = true } +sigpipe = { workspace = true } tar = { workspace = true } tempfile = { workspace = true } toml = { workspace = true } diff --git a/crates/typst-cli/src/main.rs b/crates/typst-cli/src/main.rs index 631befe56..e4861d6f9 100644 --- a/crates/typst-cli/src/main.rs +++ b/crates/typst-cli/src/main.rs @@ -44,6 +44,10 @@ static ARGS: LazyLock = LazyLock::new(|| { /// Entry point. fn main() -> ExitCode { + // Handle SIGPIPE + // https://stackoverflow.com/questions/65755853/simple-word-count-rust-program-outputs-valid-stdout-but-panicks-when-piped-to-he/65760807 + sigpipe::reset(); + let res = dispatch(); if let Err(msg) = res { From 055263ee9f2253de9f176970df9d47d3b2bd2467 Mon Sep 17 00:00:00 2001 From: Malo <57839069+MDLC01@users.noreply.github.com> Date: Fri, 29 Nov 2024 10:25:27 +0100 Subject: [PATCH 22/61] =?UTF-8?q?Make=20`math.italic`=20compatible=20with?= =?UTF-8?q?=20=C4=A7=20(#5492)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/typst-layout/src/math/text.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/typst-layout/src/math/text.rs b/crates/typst-layout/src/math/text.rs index 3b923c5b0..05d22d12d 100644 --- a/crates/typst-layout/src/math/text.rs +++ b/crates/typst-layout/src/math/text.rs @@ -150,8 +150,8 @@ fn styled_char(styles: StyleChain, c: char, auto_italic: bool) -> char { auto_italic && matches!( c, - 'a'..='z' | 'ı' | 'ȷ' | 'A'..='Z' | 'α'..='ω' | - '∂' | 'ϵ' | 'ϑ' | 'ϰ' | 'ϕ' | 'ϱ' | 'ϖ' + 'a'..='z' | 'ħ' | 'ı' | 'ȷ' | 'A'..='Z' | + 'α'..='ω' | '∂' | 'ϵ' | 'ϑ' | 'ϰ' | 'ϕ' | 'ϱ' | 'ϖ' ) && matches!(variant, Sans | Serif), ); @@ -306,6 +306,7 @@ fn latin_exception( ('e', Cal, false, _) => 'ℯ', ('g', Cal, false, _) => 'ℊ', ('o', Cal, false, _) => 'ℴ', + ('ħ', Serif, .., true) => 'ℏ', ('ı', Serif, .., true) => '𝚤', ('ȷ', Serif, .., true) => '𝚥', _ => return None, From d40c8ab6ab4f7051a12e5ce9433439f3a5afb99f Mon Sep 17 00:00:00 2001 From: Laurenz Date: Fri, 29 Nov 2024 16:03:08 +0100 Subject: [PATCH 23/61] Compile-time `PicoStr` interning (#5491) --- crates/typst-eval/src/markup.rs | 5 +- crates/typst-ide/src/analyze.rs | 4 +- crates/typst-ide/src/complete.rs | 4 +- crates/typst-ide/src/definition.rs | 3 +- crates/typst-ide/src/tooltip.rs | 2 +- crates/typst-library/src/foundations/label.rs | 20 +- crates/typst-library/src/foundations/str.rs | 9 +- .../typst-library/src/model/bibliography.rs | 23 +- crates/typst-pdf/src/catalog.rs | 2 +- crates/typst-pdf/src/named_destination.rs | 4 +- crates/typst-pdf/src/page.rs | 2 +- crates/typst-svg/src/lib.rs | 2 +- crates/typst-utils/src/lib.rs | 2 +- crates/typst-utils/src/pico.rs | 405 ++++++++++++++++-- 14 files changed, 405 insertions(+), 82 deletions(-) diff --git a/crates/typst-eval/src/markup.rs b/crates/typst-eval/src/markup.rs index 25ea5751a..3a5ebe1fc 100644 --- a/crates/typst-eval/src/markup.rs +++ b/crates/typst-eval/src/markup.rs @@ -11,6 +11,7 @@ use typst_library::text::{ LinebreakElem, RawContent, RawElem, SmartQuoteElem, SpaceElem, TextElem, }; use typst_syntax::ast::{self, AstNode}; +use typst_utils::PicoStr; use crate::{Eval, Vm}; @@ -204,7 +205,7 @@ impl Eval for ast::Label<'_> { type Output = Value; fn eval(self, _: &mut Vm) -> SourceResult { - Ok(Value::Label(Label::new(self.get()))) + Ok(Value::Label(Label::new(PicoStr::intern(self.get())))) } } @@ -212,7 +213,7 @@ impl Eval for ast::Ref<'_> { type Output = Content; fn eval(self, vm: &mut Vm) -> SourceResult { - let target = Label::new(self.target()); + let target = Label::new(PicoStr::intern(self.target())); let mut elem = RefElem::new(target); if let Some(supplement) = self.supplement() { elem.push_supplement(Smart::Custom(Some(Supplement::Content( diff --git a/crates/typst-ide/src/analyze.rs b/crates/typst-ide/src/analyze.rs index 5e3dfd700..eaf7248b7 100644 --- a/crates/typst-ide/src/analyze.rs +++ b/crates/typst-ide/src/analyze.rs @@ -88,9 +88,7 @@ pub fn analyze_labels(document: &Document) -> (Vec<(Label, Option)>, let split = output.len(); // Bibliography keys. - for (key, detail) in BibliographyElem::keys(document.introspector.track()) { - output.push((Label::new(key.as_str()), detail)); - } + output.extend(BibliographyElem::keys(document.introspector.track())); (output, split) } diff --git a/crates/typst-ide/src/complete.rs b/crates/typst-ide/src/complete.rs index a2791e071..510db54ce 100644 --- a/crates/typst-ide/src/complete.rs +++ b/crates/typst-ide/src/complete.rs @@ -1254,11 +1254,11 @@ impl<'a> CompletionContext<'a> { eco_format!( "{}{}{}", if open { "<" } else { "" }, - label.as_str(), + label.resolve(), if close { ">" } else { "" } ) }), - label: label.as_str().into(), + label: label.resolve().as_str().into(), detail, }); } diff --git a/crates/typst-ide/src/definition.rs b/crates/typst-ide/src/definition.rs index 94def1c18..9303aee43 100644 --- a/crates/typst-ide/src/definition.rs +++ b/crates/typst-ide/src/definition.rs @@ -1,6 +1,7 @@ use typst::foundations::{Label, Selector, Value}; use typst::model::Document; use typst::syntax::{ast, LinkedNode, Side, Source, Span}; +use typst::utils::PicoStr; use crate::utils::globals; use crate::{ @@ -71,7 +72,7 @@ pub fn definition( // Try to jump to the referenced content. DerefTarget::Ref(node) => { - let label = Label::new(node.cast::()?.target()); + let label = Label::new(PicoStr::intern(node.cast::()?.target())); let selector = Selector::Label(label); let elem = document?.introspector.query_first(&selector)?; return Some(Definition::Span(elem.span())); diff --git a/crates/typst-ide/src/tooltip.rs b/crates/typst-ide/src/tooltip.rs index d62826522..30aca24fb 100644 --- a/crates/typst-ide/src/tooltip.rs +++ b/crates/typst-ide/src/tooltip.rs @@ -181,7 +181,7 @@ fn label_tooltip(document: &Document, leaf: &LinkedNode) -> Option { }; for (label, detail) in analyze_labels(document).0 { - if label.as_str() == target { + if label.resolve().as_str() == target { return Some(Tooltip::Text(detail?)); } } diff --git a/crates/typst-library/src/foundations/label.rs b/crates/typst-library/src/foundations/label.rs index 726958df7..2f5520b1c 100644 --- a/crates/typst-library/src/foundations/label.rs +++ b/crates/typst-library/src/foundations/label.rs @@ -1,7 +1,7 @@ use ecow::{eco_format, EcoString}; -use typst_utils::PicoStr; +use typst_utils::{PicoStr, ResolvedPicoStr}; -use crate::foundations::{func, scope, ty, Repr}; +use crate::foundations::{func, scope, ty, Repr, Str}; /// A label for an element. /// @@ -45,17 +45,17 @@ use crate::foundations::{func, scope, ty, Repr}; /// Currently, labels can only be attached to elements in markup mode, not in /// code mode. This might change in the future. #[ty(scope, cast)] -#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub struct Label(PicoStr); impl Label { - /// Creates a label from a string, interning it. - pub fn new(name: impl Into) -> Self { - Self(name.into()) + /// Creates a label from an interned string. + pub fn new(name: PicoStr) -> Self { + Self(name) } /// Resolves the label to a string. - pub fn as_str(&self) -> &'static str { + pub fn resolve(self) -> ResolvedPicoStr { self.0.resolve() } @@ -71,15 +71,15 @@ impl Label { #[func(constructor)] pub fn construct( /// The name of the label. - name: PicoStr, + name: Str, ) -> Label { - Self(name) + Self(PicoStr::intern(name.as_str())) } } impl Repr for Label { fn repr(&self) -> EcoString { - eco_format!("<{}>", self.as_str()) + eco_format!("<{}>", self.resolve()) } } diff --git a/crates/typst-library/src/foundations/str.rs b/crates/typst-library/src/foundations/str.rs index 1431e9f98..72fdcc53a 100644 --- a/crates/typst-library/src/foundations/str.rs +++ b/crates/typst-library/src/foundations/str.rs @@ -7,7 +7,6 @@ use comemo::Tracked; use ecow::EcoString; use serde::{Deserialize, Serialize}; use typst_syntax::{Span, Spanned}; -use typst_utils::PicoStr; use unicode_segmentation::UnicodeSegmentation; use crate::diag::{bail, At, SourceResult, StrResult}; @@ -753,12 +752,6 @@ cast! { v: Str => v.into(), } -cast! { - PicoStr, - self => Value::Str(self.resolve().into()), - v: Str => v.as_str().into(), -} - cast! { String, self => Value::Str(self.into()), @@ -784,7 +777,7 @@ cast! { .map_err(|_| "bytes are not valid utf-8")? .into() ), - v: Label => Self::Str(v.as_str().into()), + v: Label => Self::Str(v.resolve().as_str().into()), v: Type => Self::Str(v.long_name().into()), v: Str => Self::Str(v), } diff --git a/crates/typst-library/src/model/bibliography.rs b/crates/typst-library/src/model/bibliography.rs index 569167311..280ac4a42 100644 --- a/crates/typst-library/src/model/bibliography.rs +++ b/crates/typst-library/src/model/bibliography.rs @@ -179,16 +179,13 @@ impl BibliographyElem { } /// Find all bibliography keys. - pub fn keys( - introspector: Tracked, - ) -> Vec<(EcoString, Option)> { + pub fn keys(introspector: Tracked) -> Vec<(Label, Option)> { let mut vec = vec![]; for elem in introspector.query(&Self::elem().select()).iter() { let this = elem.to_packed::().unwrap(); - for entry in this.bibliography().entries() { - let key = entry.key().into(); + for (key, entry) in this.bibliography().iter() { let detail = entry.title().map(|title| title.value.to_str().into()); - vec.push((key, detail)) + vec.push((Label::new(key), detail)) } } vec @@ -341,7 +338,7 @@ impl Bibliography { }; for entry in library { - match map.entry(entry.key().into()) { + match map.entry(PicoStr::intern(entry.key())) { indexmap::map::Entry::Vacant(vacant) => { vacant.insert(entry); } @@ -366,8 +363,8 @@ impl Bibliography { self.map.contains_key(&key.into()) } - fn entries(&self) -> impl Iterator { - self.map.values() + fn iter(&self) -> impl Iterator { + self.map.iter().map(|(&k, v)| (k, v)) } } @@ -661,7 +658,7 @@ impl<'a> Generator<'a> { errors.push(error!( child.span(), "key `{}` does not exist in the bibliography", - key.as_str() + key.resolve() )); continue; }; @@ -775,7 +772,9 @@ impl<'a> Generator<'a> { let mut output = std::mem::take(&mut self.failures); for (info, citation) in self.infos.iter().zip(&rendered.citations) { let supplement = |i: usize| info.subinfos.get(i)?.supplement.clone(); - let link = |i: usize| links.get(info.subinfos.get(i)?.key.as_str()).copied(); + let link = |i: usize| { + links.get(info.subinfos.get(i)?.key.resolve().as_str()).copied() + }; let renderer = ElemRenderer { routines: self.routines, @@ -820,7 +819,7 @@ impl<'a> Generator<'a> { let mut first_occurrences = HashMap::new(); for info in &self.infos { for subinfo in &info.subinfos { - let key = subinfo.key.as_str(); + let key = subinfo.key.resolve(); first_occurrences.entry(key).or_insert(info.location); } } diff --git a/crates/typst-pdf/src/catalog.rs b/crates/typst-pdf/src/catalog.rs index 1412afe63..35c5ce681 100644 --- a/crates/typst-pdf/src/catalog.rs +++ b/crates/typst-pdf/src/catalog.rs @@ -174,7 +174,7 @@ pub fn write_catalog( let mut dests_name_tree = name_dict.destinations(); let mut names = dests_name_tree.names(); for &(name, dest_ref, ..) in &ctx.references.named_destinations.dests { - names.insert(Str(name.as_str().as_bytes()), dest_ref); + names.insert(Str(name.resolve().as_bytes()), dest_ref); } } diff --git a/crates/typst-pdf/src/named_destination.rs b/crates/typst-pdf/src/named_destination.rs index 90552335c..7ae2c5e6f 100644 --- a/crates/typst-pdf/src/named_destination.rs +++ b/crates/typst-pdf/src/named_destination.rs @@ -53,12 +53,12 @@ pub fn write_named_destinations( .collect(); // Named destinations must be sorted by key. - matches.sort_by_key(|&(_, label)| label); + matches.sort_by_key(|&(_, label)| label.resolve()); for (loc, label) in matches { // Don't encode named destinations that would exceed the limit. Those // will instead be encoded as normal links. - if label.as_str().len() > Str::PDFA_LIMIT { + if label.resolve().len() > Str::PDFA_LIMIT { continue; } diff --git a/crates/typst-pdf/src/page.rs b/crates/typst-pdf/src/page.rs index 27daf6c95..4e95f3c70 100644 --- a/crates/typst-pdf/src/page.rs +++ b/crates/typst-pdf/src/page.rs @@ -154,7 +154,7 @@ fn write_page( .action() .action_type(ActionType::GoTo) // `key` must be a `Str`, not a `Name`. - .pair(Name(b"D"), Str(key.as_str().as_bytes())); + .pair(Name(b"D"), Str(key.resolve().as_bytes())); continue; } else { ctx.document.introspector.position(*loc) diff --git a/crates/typst-svg/src/lib.rs b/crates/typst-svg/src/lib.rs index 0ae7b2dad..fb7d27c22 100644 --- a/crates/typst-svg/src/lib.rs +++ b/crates/typst-svg/src/lib.rs @@ -241,7 +241,7 @@ impl SVGRenderer { self.xml.write_attribute("class", "typst-group"); if let Some(label) = group.label { - self.xml.write_attribute("data-typst-label", label.as_str()); + self.xml.write_attribute("data-typst-label", &label.resolve()); } if let Some(clip_path) = &group.clip_path { diff --git a/crates/typst-utils/src/lib.rs b/crates/typst-utils/src/lib.rs index e199e1bbb..61703250a 100644 --- a/crates/typst-utils/src/lib.rs +++ b/crates/typst-utils/src/lib.rs @@ -16,7 +16,7 @@ pub use self::bitset::{BitSet, SmallBitSet}; pub use self::deferred::Deferred; pub use self::duration::format_duration; pub use self::hash::LazyHash; -pub use self::pico::PicoStr; +pub use self::pico::{PicoStr, ResolvedPicoStr}; pub use self::round::{round_int_with_precision, round_with_precision}; pub use self::scalar::Scalar; diff --git a/crates/typst-utils/src/pico.rs b/crates/typst-utils/src/pico.rs index 7fcd33435..dbab14a1c 100644 --- a/crates/typst-utils/src/pico.rs +++ b/crates/typst-utils/src/pico.rs @@ -1,87 +1,418 @@ +use std::borrow::Borrow; use std::cmp::Ordering; use std::collections::HashMap; -use std::fmt::{self, Debug, Formatter}; -use std::num::NonZeroU32; +use std::fmt::{self, Debug, Display, Formatter}; +use std::hash::{Hash, Hasher}; +use std::num::NonZeroU64; +use std::ops::Deref; use std::sync::{LazyLock, RwLock}; -/// The global string interner. -static INTERNER: LazyLock> = LazyLock::new(|| { - RwLock::new(Interner { to_id: HashMap::new(), from_id: Vec::new() }) -}); +/// Marks a number as a bitcode encoded `PicoStr``. +const MARKER: u64 = 1 << 63; + +/// The global runtime string interner. +static INTERNER: LazyLock> = + LazyLock::new(|| RwLock::new(Interner { seen: HashMap::new(), strings: Vec::new() })); /// A string interner. struct Interner { - to_id: HashMap<&'static str, PicoStr>, - from_id: Vec<&'static str>, + seen: HashMap<&'static str, PicoStr>, + strings: Vec<&'static str>, } -/// An interned string. +/// An interned string representation that is cheap to copy and hash, but more +/// expensive to access. /// -/// The API is purposefully kept small. This is because it might be relatively -/// slow to look up a string in the interner, so we want to avoid doing it -/// unnecessarily. For this reason, the user should use the [`PicoStr::resolve`] -/// method to get the underlying string, such that the lookup is done only once. +/// This type takes up 8 bytes and is copyable and null-optimized (i.e. +/// `Option` also takes 8 bytes). +/// +/// Supports compile-time string interning via [`PicoStr::constant`] in two +/// flavors: +/// - Strings of length at most 12 containing only chars from 'a'-'z', '1'-'4', +/// and '-' are stored inline in the number +/// - Other strings _can_ be compile-time interned the same way, but must first +/// be added to the list in `exceptions::LIST`. +/// +/// No such restrictions apply at runtime (via [`PicoStr::intern`]). #[derive(Copy, Clone, Eq, PartialEq, Hash)] -pub struct PicoStr(NonZeroU32); +pub struct PicoStr(NonZeroU64); impl PicoStr { - /// Creates a new interned string. - pub fn new(string: &str) -> Self { + /// Intern a string at runtime. + pub fn intern(string: &str) -> PicoStr { + // Try to use bitcode or exception representations. + if let Ok(value) = PicoStr::try_constant(string) { + return value; + } + // Try to find an existing entry that we can reuse. // // We could check with just a read lock, but if the string is not yet // present, we would then need to recheck after acquiring a write lock, // which is probably not worth it. let mut interner = INTERNER.write().unwrap(); - if let Some(&id) = interner.to_id.get(string) { + if let Some(&id) = interner.seen.get(string) { return id; } // Create a new entry forever by leaking the string. PicoStr is only // used for strings that aren't created en masse, so it is okay. - let num = u32::try_from(interner.from_id.len() + 1) - .and_then(NonZeroU32::try_from) - .expect("out of string ids"); - - let id = Self(num); + let num = exceptions::LIST.len() + interner.strings.len() + 1; + let id = Self(NonZeroU64::new(num as u64).unwrap()); let string = Box::leak(string.to_string().into_boxed_str()); - interner.to_id.insert(string, id); - interner.from_id.push(string); + interner.seen.insert(string, id); + interner.strings.push(string); id } - /// Resolves the interned string. - pub fn resolve(&self) -> &'static str { - INTERNER.read().unwrap().from_id[(self.0.get() - 1) as usize] + /// Creates a compile-time constant `PicoStr`. + /// + /// Should only be used in const contexts because it can panic. + #[track_caller] + pub const fn constant(string: &'static str) -> PicoStr { + match PicoStr::try_constant(string) { + Ok(value) => value, + Err(err) => panic!("{}", err.message()), + } + } + + /// Try to intern a string statically at compile-time. + pub const fn try_constant(string: &str) -> Result { + // Try to encode with bitcode. + let value = match bitcode::encode(string) { + // Store representation marker in high bit. Bitcode doesn't use + // 4 high bits. + Ok(v) => v | MARKER, + + // If that fails, try to use the exception list. + Err(e) => { + if let Some(i) = exceptions::get(string) { + // Offset by one to make it non-zero. + i as u64 + 1 + } else { + return Err(e); + } + } + }; + + match NonZeroU64::new(value) { + Some(value) => Ok(Self(value)), + None => unreachable!(), + } + } + + /// Resolve to a decoded string. + pub fn resolve(self) -> ResolvedPicoStr { + // If high bit is set, this is a bitcode-encoded string. + let value = self.0.get(); + if value & MARKER != 0 { + return bitcode::decode(value & !MARKER); + } + + let index = (value - 1) as usize; + let string = if let Some(runtime) = index.checked_sub(exceptions::LIST.len()) { + INTERNER.read().unwrap().strings[runtime] + } else { + exceptions::LIST[index] + }; + + ResolvedPicoStr(Repr::Static(string)) } } impl Debug for PicoStr { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - self.resolve().fmt(f) + Debug::fmt(self.resolve().as_str(), f) } } -impl Ord for PicoStr { +/// A 5-bit encoding for strings with length up two 12 that are restricted to a +/// specific charset. +mod bitcode { + use super::{Repr, ResolvedPicoStr}; + + /// Maps from encodings to their bytes. + const DECODE: &[u8; 32] = b"\0abcdefghijklmnopqrstuvwxyz-1234"; + + /// Maps from bytes to their encodings. + const ENCODE: &[u8; 256] = &{ + let mut map = [0; 256]; + let mut i = 0; + while i < DECODE.len() { + map[DECODE[i] as usize] = i as u8; + i += 1; + } + map + }; + + /// Try to encode a string as a 64-bit integer. + pub const fn encode(string: &str) -> Result { + let bytes = string.as_bytes(); + + if bytes.len() > 12 { + return Err(EncodingError::TooLong); + } + + let mut num: u64 = 0; + let mut i = bytes.len(); + while i > 0 { + i -= 1; + let b = bytes[i]; + let v = ENCODE[b as usize]; + if v == 0 { + return Err(EncodingError::BadChar); + } + num <<= 5; + num |= v as u64; + } + + Ok(num) + } + + /// Decode the string for a 64-bit integer. + pub const fn decode(mut value: u64) -> ResolvedPicoStr { + let mut buf = [0; 12]; + let mut len = 0; + + while value != 0 { + let v = value & 0b11111; + buf[len as usize] = DECODE[v as usize]; + len += 1; + value >>= 5; + } + + ResolvedPicoStr(Repr::Inline(buf, len)) + } + + /// A failure during compile-time interning. + pub enum EncodingError { + TooLong, + BadChar, + } + + impl EncodingError { + pub const fn message(&self) -> &'static str { + match self { + Self::TooLong => { + "the maximum auto-internible string length is 12. \ + you can add an exception to typst-utils/src/pico.rs \ + to intern longer strings." + } + Self::BadChar => { + "can only auto-intern the chars 'a'-'z', '1'-'4', and '-'. \ + you can add an exception to typst-utils/src/pico.rs \ + to intern other strings." + } + } + } + } +} + +/// Compile-time interned strings that cannot be encoded with `bitcode`. +mod exceptions { + use std::cmp::Ordering; + + /// A global list of non-bitcode-encodable compile-time internible strings. + pub const LIST: &[&str] = &[ + "cjk-latin-spacing", + "discretionary-ligatures", + "historical-ligatures", + "number-clearance", + "number-margin", + "numbering-scope", + "page-numbering", + "par-line-marker", + "transparentize", + ]; + + /// Try to find the index of an exception if it exists. + pub const fn get(string: &str) -> Option { + let mut lo = 0; + let mut hi = LIST.len(); + while lo < hi { + let mid = (lo + hi) / 2; + match strcmp(string, LIST[mid]) { + Ordering::Less => hi = mid, + Ordering::Greater => lo = mid + 1, + Ordering::Equal => return Some(mid), + } + } + None + } + + /// Compare two strings. + const fn strcmp(a: &str, b: &str) -> Ordering { + let a = a.as_bytes(); + let b = b.as_bytes(); + let l = min(a.len(), b.len()); + + let mut i = 0; + while i < l { + if a[i] == b[i] { + i += 1; + } else if a[i] < b[i] { + return Ordering::Less; + } else { + return Ordering::Greater; + } + } + + if i < b.len() { + Ordering::Less + } else if i < a.len() { + Ordering::Greater + } else { + Ordering::Equal + } + } + + /// Determine the minimum of two integers. + const fn min(a: usize, b: usize) -> usize { + if a < b { + a + } else { + b + } + } +} + +/// This is returned by [`PicoStr::resolve`]. +/// +/// Dereferences to a `str`. +pub struct ResolvedPicoStr(Repr); + +/// Representation of a resolved string. +enum Repr { + Inline([u8; 12], u8), + Static(&'static str), +} + +impl ResolvedPicoStr { + /// Retrieve the underlying string. + pub fn as_str(&self) -> &str { + match &self.0 { + Repr::Inline(buf, len) => unsafe { + std::str::from_utf8_unchecked(&buf[..*len as usize]) + }, + Repr::Static(s) => s, + } + } +} + +impl Debug for ResolvedPicoStr { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + Debug::fmt(self.as_str(), f) + } +} + +impl Display for ResolvedPicoStr { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + Display::fmt(self.as_str(), f) + } +} + +impl Deref for ResolvedPicoStr { + type Target = str; + + fn deref(&self) -> &Self::Target { + self.as_str() + } +} + +impl AsRef for ResolvedPicoStr { + fn as_ref(&self) -> &str { + self.as_str() + } +} + +impl Borrow for ResolvedPicoStr { + fn borrow(&self) -> &str { + self.as_str() + } +} + +impl Eq for ResolvedPicoStr {} + +impl PartialEq for ResolvedPicoStr { + fn eq(&self, other: &Self) -> bool { + self.as_str().eq(other.as_str()) + } +} + +impl Ord for ResolvedPicoStr { fn cmp(&self, other: &Self) -> Ordering { - self.resolve().cmp(other.resolve()) + self.as_str().cmp(other.as_str()) } } -impl PartialOrd for PicoStr { +impl PartialOrd for ResolvedPicoStr { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } -impl AsRef for PicoStr { - fn as_ref(&self) -> &str { - self.resolve() +impl Hash for ResolvedPicoStr { + fn hash(&self, state: &mut H) { + self.as_str().hash(state); } } -impl From<&str> for PicoStr { - fn from(value: &str) -> Self { - Self::new(value) +#[cfg(test)] +mod tests { + use super::*; + + #[track_caller] + fn roundtrip(s: &str) { + assert_eq!(PicoStr::intern(s).resolve().as_str(), s); + } + + #[test] + fn test_pico_str() { + // Test comparing compile-time and runtime-interned bitcode string. + const H1: PicoStr = PicoStr::constant("h1"); + assert_eq!(H1, PicoStr::intern("h1")); + assert_eq!(H1.resolve().as_str(), "h1"); + + // Test comparing compile-time and runtime-interned exception. + const DISC: PicoStr = PicoStr::constant("discretionary-ligatures"); + assert_eq!(DISC, PicoStr::intern("discretionary-ligatures")); + assert_eq!(DISC.resolve().as_str(), "discretionary-ligatures"); + + // Test just roundtripping some strings. + roundtrip(""); + roundtrip("hi"); + roundtrip("∆@ Date: Sat, 30 Nov 2024 10:10:22 +0100 Subject: [PATCH 24/61] Add documentation example to `array.sorted()` (#5475) Co-authored-by: Malo <57839069+MDLC01@users.noreply.github.com> --- crates/typst-library/src/foundations/array.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/crates/typst-library/src/foundations/array.rs b/crates/typst-library/src/foundations/array.rs index 9c8aecac2..4667ee765 100644 --- a/crates/typst-library/src/foundations/array.rs +++ b/crates/typst-library/src/foundations/array.rs @@ -815,6 +815,19 @@ impl Array { /// /// Returns an error if two values could not be compared or if the key /// function (if given) yields an error. + /// + /// To sort according to multiple criteria at once, e.g. in case of equality + /// between some criteria, the key function can return an array. The results + /// are in lexicographic order. + /// + /// ```example + /// #let array = ( + /// (a: 2, b: 4), + /// (a: 1, b: 5), + /// (a: 2, b: 3), + /// ) + /// #array.sorted(key: it => (it.a, it.b)) + /// ``` #[func] pub fn sorted( self, From f8f2ba6a5f8c8ca7dbb85cf17b73332a0c301c60 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Mon, 2 Dec 2024 13:37:33 +0100 Subject: [PATCH 25/61] Rename `Document` to `PagedDocument` --- crates/typst-cli/src/compile.rs | 9 ++++---- crates/typst-cli/src/query.rs | 4 ++-- crates/typst-ide/src/analyze.rs | 7 ++++-- crates/typst-ide/src/complete.rs | 13 ++++++----- crates/typst-ide/src/definition.rs | 4 ++-- crates/typst-ide/src/jump.rs | 8 +++---- crates/typst-ide/src/tooltip.rs | 7 +++--- crates/typst-layout/src/pages/mod.rs | 12 +++++------ crates/typst-library/src/layout/page.rs | 25 +++++++++++++++++++++- crates/typst-library/src/model/document.rs | 24 --------------------- crates/typst-library/src/routines.rs | 7 +++--- crates/typst-pdf/src/lib.rs | 17 +++++++-------- crates/typst-render/src/lib.rs | 6 +++--- crates/typst-svg/src/lib.rs | 6 +++--- crates/typst/src/lib.rs | 8 +++---- docs/src/lib.rs | 9 ++++---- docs/src/main.rs | 4 ++-- tests/src/custom.rs | 7 +++--- tests/src/run.rs | 9 ++++---- 19 files changed, 93 insertions(+), 93 deletions(-) diff --git a/crates/typst-cli/src/compile.rs b/crates/typst-cli/src/compile.rs index 74d818a54..c6d37ffec 100644 --- a/crates/typst-cli/src/compile.rs +++ b/crates/typst-cli/src/compile.rs @@ -12,8 +12,7 @@ use typst::diag::{ bail, At, Severity, SourceDiagnostic, SourceResult, StrResult, Warned, }; use typst::foundations::{Datetime, Smart}; -use typst::layout::{Frame, Page, PageRanges}; -use typst::model::Document; +use typst::layout::{Frame, Page, PageRanges, PagedDocument}; use typst::syntax::{FileId, Source, Span}; use typst::WorldExt; use typst_pdf::{PdfOptions, PdfStandards}; @@ -171,7 +170,7 @@ pub fn compile_once( /// Export into the target format. fn export( world: &mut SystemWorld, - document: &Document, + document: &PagedDocument, command: &CompileCommand, watching: bool, ) -> SourceResult<()> { @@ -189,7 +188,7 @@ fn export( } /// Export to a PDF. -fn export_pdf(document: &Document, command: &CompileCommand) -> SourceResult<()> { +fn export_pdf(document: &PagedDocument, command: &CompileCommand) -> SourceResult<()> { let options = PdfOptions { ident: Smart::Auto, timestamp: convert_datetime( @@ -229,7 +228,7 @@ enum ImageExportFormat { /// Export to one or multiple images. fn export_image( world: &mut SystemWorld, - document: &Document, + document: &PagedDocument, command: &CompileCommand, watching: bool, fmt: ImageExportFormat, diff --git a/crates/typst-cli/src/query.rs b/crates/typst-cli/src/query.rs index 90d99a5a2..947a64850 100644 --- a/crates/typst-cli/src/query.rs +++ b/crates/typst-cli/src/query.rs @@ -3,7 +3,7 @@ use ecow::{eco_format, EcoString}; use serde::Serialize; use typst::diag::{bail, HintedStrResult, StrResult, Warned}; use typst::foundations::{Content, IntoValue, LocatableSelector, Scope}; -use typst::model::Document; +use typst::layout::PagedDocument; use typst::syntax::Span; use typst::World; use typst_eval::{eval_string, EvalMode}; @@ -53,7 +53,7 @@ pub fn query(command: &QueryCommand) -> HintedStrResult<()> { fn retrieve( world: &dyn World, command: &QueryCommand, - document: &Document, + document: &PagedDocument, ) -> HintedStrResult> { let selector = eval_string( &typst::ROUTINES, diff --git a/crates/typst-ide/src/analyze.rs b/crates/typst-ide/src/analyze.rs index eaf7248b7..0b41fb68d 100644 --- a/crates/typst-ide/src/analyze.rs +++ b/crates/typst-ide/src/analyze.rs @@ -1,7 +1,8 @@ use comemo::Track; use ecow::{eco_vec, EcoString, EcoVec}; use typst::foundations::{Label, Styles, Value}; -use typst::model::{BibliographyElem, Document}; +use typst::layout::PagedDocument; +use typst::model::BibliographyElem; use typst::syntax::{ast, LinkedNode, SyntaxKind}; use crate::IdeWorld; @@ -65,7 +66,9 @@ pub fn analyze_import(world: &dyn IdeWorld, source: &LinkedNode) -> Option (Vec<(Label, Option)>, usize) { +pub fn analyze_labels( + document: &PagedDocument, +) -> (Vec<(Label, Option)>, usize) { let mut output = vec![]; // Labels in the document. diff --git a/crates/typst-ide/src/complete.rs b/crates/typst-ide/src/complete.rs index 510db54ce..5c2b500a0 100644 --- a/crates/typst-ide/src/complete.rs +++ b/crates/typst-ide/src/complete.rs @@ -9,8 +9,7 @@ use typst::foundations::{ fields_on, repr, AutoValue, CastInfo, Func, Label, NoneValue, ParamInfo, Repr, StyleChain, Styles, Type, Value, }; -use typst::layout::{Alignment, Dir}; -use typst::model::Document; +use typst::layout::{Alignment, Dir, PagedDocument}; use typst::syntax::ast::AstNode; use typst::syntax::{ ast, is_id_continue, is_id_start, is_ident, FileId, LinkedNode, Side, Source, @@ -38,7 +37,7 @@ use crate::{analyze_expr, analyze_import, analyze_labels, named_items, IdeWorld} /// when the document is available. pub fn autocomplete( world: &dyn IdeWorld, - document: Option<&Document>, + document: Option<&PagedDocument>, source: &Source, cursor: usize, explicit: bool, @@ -1063,7 +1062,7 @@ fn code_completions(ctx: &mut CompletionContext, hash: bool) { /// Context for autocompletion. struct CompletionContext<'a> { world: &'a (dyn IdeWorld + 'a), - document: Option<&'a Document>, + document: Option<&'a PagedDocument>, text: &'a str, before: &'a str, after: &'a str, @@ -1079,7 +1078,7 @@ impl<'a> CompletionContext<'a> { /// Create a new autocompletion context. fn new( world: &'a (dyn IdeWorld + 'a), - document: Option<&'a Document>, + document: Option<&'a PagedDocument>, source: &'a Source, leaf: &'a LinkedNode<'a>, cursor: usize, @@ -1507,7 +1506,7 @@ impl BracketMode { mod tests { use std::collections::BTreeSet; - use typst::model::Document; + use typst::layout::PagedDocument; use typst::syntax::{FileId, Source, VirtualPath}; use typst::World; @@ -1607,7 +1606,7 @@ mod tests { fn test_full( world: &TestWorld, source: &Source, - doc: Option<&Document>, + doc: Option<&PagedDocument>, cursor: isize, ) -> Response { autocomplete(world, doc, source, source.cursor(cursor), true) diff --git a/crates/typst-ide/src/definition.rs b/crates/typst-ide/src/definition.rs index 9303aee43..c789430a2 100644 --- a/crates/typst-ide/src/definition.rs +++ b/crates/typst-ide/src/definition.rs @@ -1,5 +1,5 @@ use typst::foundations::{Label, Selector, Value}; -use typst::model::Document; +use typst::layout::PagedDocument; use typst::syntax::{ast, LinkedNode, Side, Source, Span}; use typst::utils::PicoStr; @@ -25,7 +25,7 @@ pub enum Definition { /// when the document is available. pub fn definition( world: &dyn IdeWorld, - document: Option<&Document>, + document: Option<&PagedDocument>, source: &Source, cursor: usize, side: Side, diff --git a/crates/typst-ide/src/jump.rs b/crates/typst-ide/src/jump.rs index 2dd5cf610..ba62b0ab9 100644 --- a/crates/typst-ide/src/jump.rs +++ b/crates/typst-ide/src/jump.rs @@ -1,7 +1,7 @@ use std::num::NonZeroUsize; -use typst::layout::{Frame, FrameItem, Point, Position, Size}; -use typst::model::{Destination, Document, Url}; +use typst::layout::{Frame, FrameItem, PagedDocument, Point, Position, Size}; +use typst::model::{Destination, Url}; use typst::syntax::{FileId, LinkedNode, Side, Source, Span, SyntaxKind}; use typst::visualize::Geometry; use typst::WorldExt; @@ -30,7 +30,7 @@ impl Jump { /// Determine where to jump to based on a click in a frame. pub fn jump_from_click( world: &dyn IdeWorld, - document: &Document, + document: &PagedDocument, frame: &Frame, click: Point, ) -> Option { @@ -110,7 +110,7 @@ pub fn jump_from_click( /// Find the output location in the document for a cursor position. pub fn jump_from_cursor( - document: &Document, + document: &PagedDocument, source: &Source, cursor: usize, ) -> Vec { diff --git a/crates/typst-ide/src/tooltip.rs b/crates/typst-ide/src/tooltip.rs index 30aca24fb..adfbeda50 100644 --- a/crates/typst-ide/src/tooltip.rs +++ b/crates/typst-ide/src/tooltip.rs @@ -4,8 +4,7 @@ use ecow::{eco_format, EcoString}; use if_chain::if_chain; use typst::engine::Sink; use typst::foundations::{repr, Capturer, CastInfo, Repr, Value}; -use typst::layout::Length; -use typst::model::Document; +use typst::layout::{Length, PagedDocument}; use typst::syntax::ast::AstNode; use typst::syntax::{ast, LinkedNode, Side, Source, SyntaxKind}; use typst::utils::{round_with_precision, Numeric}; @@ -21,7 +20,7 @@ use crate::{analyze_expr, analyze_import, analyze_labels, IdeWorld}; /// document is available. pub fn tooltip( world: &dyn IdeWorld, - document: Option<&Document>, + document: Option<&PagedDocument>, source: &Source, cursor: usize, side: Side, @@ -173,7 +172,7 @@ fn length_tooltip(length: Length) -> Option { } /// Tooltip for a hovered reference or label. -fn label_tooltip(document: &Document, leaf: &LinkedNode) -> Option { +fn label_tooltip(document: &PagedDocument, leaf: &LinkedNode) -> Option { let target = match leaf.kind() { SyntaxKind::RefMarker => leaf.text().trim_start_matches('@'), SyntaxKind::Label => leaf.text().trim_start_matches('<').trim_end_matches('>'), diff --git a/crates/typst-layout/src/pages/mod.rs b/crates/typst-layout/src/pages/mod.rs index b969749a1..667e16b3f 100644 --- a/crates/typst-layout/src/pages/mod.rs +++ b/crates/typst-layout/src/pages/mod.rs @@ -11,8 +11,8 @@ use typst_library::foundations::{Content, StyleChain}; use typst_library::introspection::{ Introspector, Locator, ManualPageCounter, SplitLocator, TagElem, }; -use typst_library::layout::{FrameItem, Page, Point}; -use typst_library::model::{Document, DocumentInfo}; +use typst_library::layout::{FrameItem, Page, PagedDocument, Point}; +use typst_library::model::DocumentInfo; use typst_library::routines::{Arenas, Pair, RealizationKind, Routines}; use typst_library::World; @@ -26,12 +26,12 @@ use self::run::{layout_blank_page, layout_page_run, LayoutedPage}; /// elements. In contrast to [`layout_fragment`](crate::layout_fragment), /// this does not take regions since the regions are defined by the page /// configuration in the content and style chain. -#[typst_macros::time(name = "document")] +#[typst_macros::time(name = "layout document")] pub fn layout_document( engine: &mut Engine, content: &Content, styles: StyleChain, -) -> SourceResult { +) -> SourceResult { layout_document_impl( engine.routines, engine.world, @@ -56,7 +56,7 @@ fn layout_document_impl( route: Tracked, content: &Content, styles: StyleChain, -) -> SourceResult { +) -> SourceResult { let mut locator = Locator::root().split(); let mut engine = Engine { routines, @@ -86,7 +86,7 @@ fn layout_document_impl( let pages = layout_pages(&mut engine, &mut children, locator, styles)?; let introspector = Introspector::new(&pages); - Ok(Document { pages, info, introspector }) + Ok(PagedDocument { pages, info, introspector }) } /// Layouts the document's pages. diff --git a/crates/typst-library/src/layout/page.rs b/crates/typst-library/src/layout/page.rs index 09aafa7f3..68fd89745 100644 --- a/crates/typst-library/src/layout/page.rs +++ b/crates/typst-library/src/layout/page.rs @@ -12,11 +12,12 @@ use crate::foundations::{ cast, elem, Args, AutoValue, Cast, Construct, Content, Context, Dict, Fold, Func, NativeElement, Set, Smart, StyleChain, Value, }; +use crate::introspection::Introspector; use crate::layout::{ Abs, Alignment, FlushElem, Frame, HAlignment, Length, OuterVAlignment, Ratio, Rel, Sides, SpecificAlignment, }; -use crate::model::Numbering; +use crate::model::{DocumentInfo, Numbering}; use crate::text::LocalName; use crate::visualize::{Color, Paint}; @@ -451,6 +452,17 @@ impl PagebreakElem { } } +/// A finished document with metadata and page frames. +#[derive(Debug, Default, Clone)] +pub struct PagedDocument { + /// The document's finished pages. + pub pages: Vec, + /// Details about the document. + pub info: DocumentInfo, + /// Provides the ability to execute queries on the document. + pub introspector: Introspector, +} + /// A finished page. #[derive(Debug, Clone)] pub struct Page { @@ -942,3 +954,14 @@ papers! { (PRESENTATION_16_9: 297.0, 167.0625, "presentation-16-9") (PRESENTATION_4_3: 280.0, 210.0, "presentation-4-3") } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_paged_document_is_send_and_sync() { + fn ensure_send_and_sync() {} + ensure_send_and_sync::(); + } +} diff --git a/crates/typst-library/src/model/document.rs b/crates/typst-library/src/model/document.rs index b693d7856..c333dff33 100644 --- a/crates/typst-library/src/model/document.rs +++ b/crates/typst-library/src/model/document.rs @@ -6,8 +6,6 @@ use crate::foundations::{ cast, elem, Args, Array, Construct, Content, Datetime, Fields, Smart, StyleChain, Styles, Value, }; -use crate::introspection::Introspector; -use crate::layout::Page; /// The root element of a document and its metadata. /// @@ -86,17 +84,6 @@ cast! { v: Array => Self(v.into_iter().map(Value::cast).collect::>()?), } -/// A finished document with metadata and page frames. -#[derive(Debug, Default, Clone)] -pub struct Document { - /// The document's finished pages. - pub pages: Vec, - /// Details about the document. - pub info: DocumentInfo, - /// Provides the ability to execute queries on the document. - pub introspector: Introspector, -} - /// Details about the document. #[derive(Debug, Default, Clone, PartialEq, Hash)] pub struct DocumentInfo { @@ -132,14 +119,3 @@ impl DocumentInfo { } } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_document_is_send_and_sync() { - fn ensure_send_and_sync() {} - ensure_send_and_sync::(); - } -} diff --git a/crates/typst-library/src/routines.rs b/crates/typst-library/src/routines.rs index 6b78b7fbf..000b3bba5 100644 --- a/crates/typst-library/src/routines.rs +++ b/crates/typst-library/src/routines.rs @@ -16,10 +16,11 @@ use crate::foundations::{ use crate::introspection::{Introspector, Locator, SplitLocator}; use crate::layout::{ Abs, BoxElem, ColumnsElem, Fragment, Frame, GridElem, InlineItem, MoveElem, PadElem, - Region, Regions, Rel, RepeatElem, RotateElem, ScaleElem, Size, SkewElem, StackElem, + PagedDocument, Region, Regions, Rel, RepeatElem, RotateElem, ScaleElem, Size, + SkewElem, StackElem, }; use crate::math::EquationElem; -use crate::model::{Document, DocumentInfo, EnumElem, ListElem, TableElem}; +use crate::model::{DocumentInfo, EnumElem, ListElem, TableElem}; use crate::visualize::{ CircleElem, EllipseElem, ImageElem, LineElem, PathElem, PolygonElem, RectElem, SquareElem, @@ -90,7 +91,7 @@ routines! { engine: &mut Engine, content: &Content, styles: StyleChain, - ) -> SourceResult + ) -> SourceResult /// Lays out content into multiple regions. fn layout_fragment( diff --git a/crates/typst-pdf/src/lib.rs b/crates/typst-pdf/src/lib.rs index efc99b749..f9b4e902d 100644 --- a/crates/typst-pdf/src/lib.rs +++ b/crates/typst-pdf/src/lib.rs @@ -24,8 +24,7 @@ use pdf_writer::{Chunk, Name, Pdf, Ref, Str, TextStr}; use serde::{Deserialize, Serialize}; use typst_library::diag::{bail, SourceResult, StrResult}; use typst_library::foundations::{Datetime, Smart}; -use typst_library::layout::{Abs, Em, PageRanges, Transform}; -use typst_library::model::Document; +use typst_library::layout::{Abs, Em, PageRanges, PagedDocument, Transform}; use typst_library::text::Font; use typst_library::visualize::Image; use typst_syntax::Span; @@ -49,7 +48,7 @@ use crate::resources::{ /// /// Returns the raw bytes making up the PDF file. #[typst_macros::time(name = "pdf")] -pub fn pdf(document: &Document, options: &PdfOptions) -> SourceResult> { +pub fn pdf(document: &PagedDocument, options: &PdfOptions) -> SourceResult> { PdfBuilder::new(document, options) .phase(|builder| builder.run(traverse_pages))? .phase(|builder| { @@ -176,7 +175,7 @@ struct PdfBuilder { /// this phase. struct WithDocument<'a> { /// The Typst document that is exported. - document: &'a Document, + document: &'a PagedDocument, /// Settings for PDF export. options: &'a PdfOptions<'a>, } @@ -186,7 +185,7 @@ struct WithDocument<'a> { /// /// This phase allocates some global references. struct WithResources<'a> { - document: &'a Document, + document: &'a PagedDocument, options: &'a PdfOptions<'a>, /// The content of the pages encoded as PDF content streams. /// @@ -235,7 +234,7 @@ impl<'a> From<(WithDocument<'a>, (Vec>, Resources<()>))> /// We are now writing objects corresponding to resources, and giving them references, /// that will be collected in [`References`]. struct WithGlobalRefs<'a> { - document: &'a Document, + document: &'a PagedDocument, options: &'a PdfOptions<'a>, pages: Vec>, /// Resources are the same as in previous phases, but each dictionary now has a reference. @@ -278,7 +277,7 @@ struct References { /// tree is going to be written, and given a reference. It is also at this point that /// the page contents is actually written. struct WithRefs<'a> { - document: &'a Document, + document: &'a PagedDocument, options: &'a PdfOptions<'a>, globals: GlobalRefs, pages: Vec>, @@ -304,7 +303,7 @@ impl<'a> From<(WithGlobalRefs<'a>, References)> for WithRefs<'a> { /// /// Each sub-resource gets its own isolated resource dictionary. struct WithEverything<'a> { - document: &'a Document, + document: &'a PagedDocument, options: &'a PdfOptions<'a>, globals: GlobalRefs, pages: Vec>, @@ -336,7 +335,7 @@ impl<'a> From<(WithRefs<'a>, Ref)> for WithEverything<'a> { impl<'a> PdfBuilder> { /// Start building a PDF for a Typst document. - fn new(document: &'a Document, options: &'a PdfOptions<'a>) -> Self { + fn new(document: &'a PagedDocument, options: &'a PdfOptions<'a>) -> Self { Self { alloc: Ref::new(1), pdf: Pdf::new(), diff --git a/crates/typst-render/src/lib.rs b/crates/typst-render/src/lib.rs index 8de3852d4..8595e7908 100644 --- a/crates/typst-render/src/lib.rs +++ b/crates/typst-render/src/lib.rs @@ -7,9 +7,9 @@ mod text; use tiny_skia as sk; use typst_library::layout::{ - Abs, Axes, Frame, FrameItem, FrameKind, GroupItem, Page, Point, Size, Transform, + Abs, Axes, Frame, FrameItem, FrameKind, GroupItem, Page, PagedDocument, Point, Size, + Transform, }; -use typst_library::model::Document; use typst_library::visualize::{Color, Geometry, Paint}; /// Export a page into a raster image. @@ -43,7 +43,7 @@ pub fn render(page: &Page, pixel_per_pt: f32) -> sk::Pixmap { /// Export a document with potentially multiple pages into a single raster image. pub fn render_merged( - document: &Document, + document: &PagedDocument, pixel_per_pt: f32, gap: Abs, fill: Option, diff --git a/crates/typst-svg/src/lib.rs b/crates/typst-svg/src/lib.rs index fb7d27c22..f9ce4b860 100644 --- a/crates/typst-svg/src/lib.rs +++ b/crates/typst-svg/src/lib.rs @@ -11,9 +11,9 @@ use std::fmt::{self, Display, Formatter, Write}; use ecow::EcoString; use ttf_parser::OutlineBuilder; use typst_library::layout::{ - Abs, Frame, FrameItem, FrameKind, GroupItem, Page, Point, Ratio, Size, Transform, + Abs, Frame, FrameItem, FrameKind, GroupItem, Page, PagedDocument, Point, Ratio, Size, + Transform, }; -use typst_library::model::Document; use typst_library::visualize::{Geometry, Gradient, Pattern}; use typst_utils::hash128; use xmlwriter::XmlWriter; @@ -35,7 +35,7 @@ pub fn svg(page: &Page) -> String { /// Export a document with potentially multiple pages into a single SVG file. /// /// The padding will be added around and between the individual frames. -pub fn svg_merged(document: &Document, padding: Abs) -> String { +pub fn svg_merged(document: &PagedDocument, padding: Abs) -> String { let width = 2.0 * padding + document .pages diff --git a/crates/typst/src/lib.rs b/crates/typst/src/lib.rs index db1673d2d..feb17ba9e 100644 --- a/crates/typst/src/lib.rs +++ b/crates/typst/src/lib.rs @@ -47,7 +47,7 @@ use typst_library::diag::{warning, FileError, SourceDiagnostic, SourceResult, Wa use typst_library::engine::{Engine, Route, Sink, Traced}; use typst_library::foundations::{StyleChain, Styles, Value}; use typst_library::introspection::Introspector; -use typst_library::model::Document; +use typst_library::layout::PagedDocument; use typst_library::routines::Routines; use typst_syntax::{FileId, Span}; use typst_timing::{timed, TimingScope}; @@ -57,7 +57,7 @@ use typst_timing::{timed, TimingScope}; /// - Returns `Ok(document)` if there were no fatal errors. /// - Returns `Err(errors)` if there were fatal errors. #[typst_macros::time] -pub fn compile(world: &dyn World) -> Warned> { +pub fn compile(world: &dyn World) -> Warned> { let mut sink = Sink::new(); let output = compile_impl(world.track(), Traced::default().track(), &mut sink) .map_err(deduplicate); @@ -80,7 +80,7 @@ fn compile_impl( world: Tracked, traced: Tracked, sink: &mut Sink, -) -> SourceResult { +) -> SourceResult { let library = world.library(); let styles = StyleChain::new(&library.styles); @@ -103,7 +103,7 @@ fn compile_impl( let mut iter = 0; let mut subsink; - let mut document = Document::default(); + let mut document = PagedDocument::default(); // Relayout until all introspections stabilize. // If that doesn't happen within five attempts, we give up. diff --git a/docs/src/lib.rs b/docs/src/lib.rs index 9228a9906..5ca3724ab 100644 --- a/docs/src/lib.rs +++ b/docs/src/lib.rs @@ -21,10 +21,10 @@ use typst::foundations::{ Scope, Smart, Type, Value, FOUNDATIONS, }; use typst::introspection::INTROSPECTION; -use typst::layout::{Abs, Margin, PageElem, LAYOUT}; +use typst::layout::{Abs, Margin, PageElem, PagedDocument, LAYOUT}; use typst::loading::DATA_LOADING; use typst::math::MATH; -use typst::model::{Document, MODEL}; +use typst::model::MODEL; use typst::symbols::SYMBOLS; use typst::text::{Font, FontBook, TEXT}; use typst::utils::LazyHash; @@ -105,7 +105,8 @@ pub trait Resolver { fn image(&self, filename: &str, data: &[u8]) -> String; /// Produce HTML for an example. - fn example(&self, hash: u128, source: Option, document: &Document) -> Html; + fn example(&self, hash: u128, source: Option, document: &PagedDocument) + -> Html; /// Determine the commits between two tags. fn commits(&self, from: &str, to: &str) -> Vec; @@ -800,7 +801,7 @@ mod tests { None } - fn example(&self, _: u128, _: Option, _: &Document) -> Html { + fn example(&self, _: u128, _: Option, _: &PagedDocument) -> Html { Html::new(String::new()) } diff --git a/docs/src/main.rs b/docs/src/main.rs index d87e359b9..f8d7c9345 100644 --- a/docs/src/main.rs +++ b/docs/src/main.rs @@ -2,7 +2,7 @@ use std::fs; use std::path::{Path, PathBuf}; use clap::Parser; -use typst::model::Document; +use typst::layout::PagedDocument; use typst_docs::{provide, Html, Resolver}; use typst_render::render; @@ -25,7 +25,7 @@ impl<'a> Resolver for CliResolver<'a> { &self, hash: u128, source: Option, - document: &Document, + document: &PagedDocument, ) -> typst_docs::Html { if self.verbose { eprintln!( diff --git a/tests/src/custom.rs b/tests/src/custom.rs index 9a5fef03c..965312abc 100644 --- a/tests/src/custom.rs +++ b/tests/src/custom.rs @@ -1,7 +1,8 @@ use std::fmt::Write; use typst::foundations::Smart; -use typst::model::{Document, DocumentInfo}; +use typst::layout::PagedDocument; +use typst::model::DocumentInfo; use typst::World; use crate::collect::Test; @@ -18,7 +19,7 @@ macro_rules! test_eq { /// Run special checks for specific tests for which it is not worth it to create /// custom annotations. -pub fn check(test: &Test, world: &TestWorld, doc: Option<&Document>) -> String { +pub fn check(test: &Test, world: &TestWorld, doc: Option<&PagedDocument>) -> String { let mut sink = String::new(); match test.name.as_str() { "document-set-author-date" => { @@ -41,6 +42,6 @@ pub fn check(test: &Test, world: &TestWorld, doc: Option<&Document>) -> String { } /// Extract the document information. -fn info(doc: Option<&Document>) -> DocumentInfo { +fn info(doc: Option<&PagedDocument>) -> DocumentInfo { doc.map(|doc| doc.info.clone()).unwrap_or_default() } diff --git a/tests/src/run.rs b/tests/src/run.rs index 1ea19a16a..1aa702041 100644 --- a/tests/src/run.rs +++ b/tests/src/run.rs @@ -5,8 +5,7 @@ use std::path::Path; use ecow::eco_vec; use tiny_skia as sk; use typst::diag::{SourceDiagnostic, Warned}; -use typst::layout::{Abs, Frame, FrameItem, Page, Transform}; -use typst::model::Document; +use typst::layout::{Abs, Frame, FrameItem, Page, PagedDocument, Transform}; use typst::visualize::Color; use typst::WorldExt; use typst_pdf::PdfOptions; @@ -116,7 +115,7 @@ impl<'a> Runner<'a> { /// Run custom checks for which it is not worth to create special /// annotations. - fn check_custom(&mut self, doc: Option<&Document>) { + fn check_custom(&mut self, doc: Option<&PagedDocument>) { let errors = crate::custom::check(self.test, &self.world, doc); if !errors.is_empty() { log!(self, "custom check failed"); @@ -127,7 +126,7 @@ impl<'a> Runner<'a> { } /// Check that the document output is correct. - fn check_document(&mut self, document: Option<&Document>) { + fn check_document(&mut self, document: Option<&PagedDocument>) { let live_path = format!("{}/render/{}.png", crate::STORE_PATH, self.test.name); let ref_path = format!("{}/{}.png", crate::REF_PATH, self.test.name); let has_ref = Path::new(&ref_path).exists(); @@ -351,7 +350,7 @@ impl<'a> Runner<'a> { } /// Draw all frames into one image with padding in between. -fn render(document: &Document, pixel_per_pt: f32) -> sk::Pixmap { +fn render(document: &PagedDocument, pixel_per_pt: f32) -> sk::Pixmap { for page in &document.pages { let limit = Abs::cm(100.0); if page.frame.width() > limit || page.frame.height() > limit { From 008b59839f276bc5563fea2ac3350da63a0355d8 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Mon, 2 Dec 2024 13:15:28 +0100 Subject: [PATCH 26/61] Add some more spans --- crates/typst-eval/src/call.rs | 4 +++- crates/typst-eval/src/code.rs | 2 +- crates/typst-eval/src/lib.rs | 3 ++- crates/typst-library/src/foundations/args.rs | 2 +- .../typst-library/src/foundations/content.rs | 18 ++++++++++++++---- crates/typst-library/src/foundations/func.rs | 3 ++- crates/typst-library/src/foundations/ty.rs | 2 ++ crates/typst-library/src/model/outline.rs | 13 ++++++++----- crates/typst-library/src/model/terms.rs | 10 +++++++--- crates/typst-macros/src/cast.rs | 2 +- 10 files changed, 41 insertions(+), 18 deletions(-) diff --git a/crates/typst-eval/src/call.rs b/crates/typst-eval/src/call.rs index f48734cbc..513d1dd2c 100644 --- a/crates/typst-eval/src/call.rs +++ b/crates/typst-eval/src/call.rs @@ -391,7 +391,9 @@ fn wrap_args_in_math( } Ok(Value::Content( callee.display().spanned(callee_span) - + LrElem::new(TextElem::packed('(') + body + TextElem::packed(')')).pack(), + + LrElem::new(TextElem::packed('(') + body + TextElem::packed(')')) + .pack() + .spanned(args.span), )) } diff --git a/crates/typst-eval/src/code.rs b/crates/typst-eval/src/code.rs index ba5256c1f..34373fd4a 100644 --- a/crates/typst-eval/src/code.rs +++ b/crates/typst-eval/src/code.rs @@ -359,7 +359,7 @@ impl Eval for ast::Contextual<'_> { }; let func = Func::from(closure).spanned(body.span()); - Ok(ContextElem::new(func).pack()) + Ok(ContextElem::new(func).pack().spanned(body.span())) } } diff --git a/crates/typst-eval/src/lib.rs b/crates/typst-eval/src/lib.rs index 69c20e8cc..5eae7c1df 100644 --- a/crates/typst-eval/src/lib.rs +++ b/crates/typst-eval/src/lib.rs @@ -148,7 +148,8 @@ pub fn eval_string( EvalMode::Math => Value::Content( EquationElem::new(root.cast::().unwrap().eval(&mut vm)?) .with_block(false) - .pack(), + .pack() + .spanned(span), ), }; diff --git a/crates/typst-library/src/foundations/args.rs b/crates/typst-library/src/foundations/args.rs index ee282a874..a60e6d7f2 100644 --- a/crates/typst-library/src/foundations/args.rs +++ b/crates/typst-library/src/foundations/args.rs @@ -187,7 +187,7 @@ impl Args { self.items.retain(|item| { if item.name.is_some() { return true; - }; + } let span = item.value.span; let spanned = Spanned::new(std::mem::take(&mut item.value.v), span); match T::from_value(spanned).at(span) { diff --git a/crates/typst-library/src/foundations/content.rs b/crates/typst-library/src/foundations/content.rs index 69103e080..bfafbc486 100644 --- a/crates/typst-library/src/foundations/content.rs +++ b/crates/typst-library/src/foundations/content.rs @@ -481,17 +481,20 @@ impl Content { impl Content { /// Strongly emphasize this content. pub fn strong(self) -> Self { - StrongElem::new(self).pack() + let span = self.span(); + StrongElem::new(self).pack().spanned(span) } /// Emphasize this content. pub fn emph(self) -> Self { - EmphElem::new(self).pack() + let span = self.span(); + EmphElem::new(self).pack().spanned(span) } /// Underline this content. pub fn underlined(self) -> Self { - UnderlineElem::new(self).pack() + let span = self.span(); + UnderlineElem::new(self).pack().spanned(span) } /// Link the content somewhere. @@ -506,17 +509,24 @@ impl Content { /// Pad this content at the sides. pub fn padded(self, padding: Sides>) -> Self { + let span = self.span(); PadElem::new(self) .with_left(padding.left) .with_top(padding.top) .with_right(padding.right) .with_bottom(padding.bottom) .pack() + .spanned(span) } /// Transform this content's contents without affecting layout. pub fn moved(self, delta: Axes>) -> Self { - MoveElem::new(self).with_dx(delta.x).with_dy(delta.y).pack() + let span = self.span(); + MoveElem::new(self) + .with_dx(delta.x) + .with_dy(delta.y) + .pack() + .spanned(span) } } diff --git a/crates/typst-library/src/foundations/func.rs b/crates/typst-library/src/foundations/func.rs index e34f48a17..40c826df9 100644 --- a/crates/typst-library/src/foundations/func.rs +++ b/crates/typst-library/src/foundations/func.rs @@ -443,7 +443,7 @@ pub trait NativeFunc { Func::from(Self::data()) } - /// Get the function data for the native Rust type. + /// Get the function data for the native Rust function. fn data() -> &'static NativeFuncData; } @@ -462,6 +462,7 @@ pub struct NativeFuncData { pub keywords: &'static [&'static str], /// Whether this function makes use of context. pub contextual: bool, + /// Definitions in the scope of the function. pub scope: LazyLock, /// A list of parameter information for each parameter. pub params: LazyLock>, diff --git a/crates/typst-library/src/foundations/ty.rs b/crates/typst-library/src/foundations/ty.rs index 680c4f6a1..8b20e2c62 100644 --- a/crates/typst-library/src/foundations/ty.rs +++ b/crates/typst-library/src/foundations/ty.rs @@ -199,6 +199,7 @@ pub trait NativeType { pub struct NativeTypeData { /// The type's normal name (e.g. `str`), as exposed to Typst. pub name: &'static str, + /// The type's long name (e.g. `string`), for error messages. pub long_name: &'static str, /// The function's title case name (e.g. `String`). pub title: &'static str, @@ -208,6 +209,7 @@ pub struct NativeTypeData { pub keywords: &'static [&'static str], /// The constructor for this type. pub constructor: LazyLock>, + /// Definitions in the scope of the type. pub scope: LazyLock, } diff --git a/crates/typst-library/src/model/outline.rs b/crates/typst-library/src/model/outline.rs index 85257c2c7..0be1a9d0a 100644 --- a/crates/typst-library/src/model/outline.rs +++ b/crates/typst-library/src/model/outline.rs @@ -248,7 +248,7 @@ impl Show for Packed { )?; // Add the overridable outline entry, followed by a line break. - seq.push(entry.pack()); + seq.push(entry.pack().spanned(self.span())); seq.push(LinebreakElem::shared().clone()); ancestors.push(elem); @@ -332,15 +332,18 @@ impl OutlineIndent { } if !ancestors.is_empty() { - seq.push(HideElem::new(hidden).pack()); - seq.push(SpaceElem::shared().clone()); + seq.push(HideElem::new(hidden).pack().spanned(span)); + seq.push(SpaceElem::shared().clone().spanned(span)); } } // Length => indent with some fixed spacing per level Some(Smart::Custom(OutlineIndent::Rel(length))) => { seq.push( - HElem::new(Spacing::Rel(*length)).pack().repeat(ancestors.len()), + HElem::new(Spacing::Rel(*length)) + .pack() + .spanned(span) + .repeat(ancestors.len()), ); } @@ -535,7 +538,7 @@ impl Show for Packed { ); seq.push(SpaceElem::shared().clone()); } else { - seq.push(HElem::new(Fr::one().into()).pack()); + seq.push(HElem::new(Fr::one().into()).pack().spanned(self.span())); } // Add the page number. diff --git a/crates/typst-library/src/model/terms.rs b/crates/typst-library/src/model/terms.rs index 036a03e27..bbcb63fc2 100644 --- a/crates/typst-library/src/model/terms.rs +++ b/crates/typst-library/src/model/terms.rs @@ -127,7 +127,7 @@ impl Show for Packed { let pad = hanging_indent + indent; let unpad = (!hanging_indent.is_zero()) - .then(|| HElem::new((-hanging_indent).into()).pack()); + .then(|| HElem::new((-hanging_indent).into()).pack().spanned(self.span())); let mut children = vec![]; for child in self.children().iter() { @@ -149,12 +149,16 @@ impl Show for Packed { let mut realized = StackElem::new(children) .with_spacing(Some(gutter.into())) .pack() + .spanned(self.span()) .padded(padding); if self.tight(styles) { let leading = ParElem::leading_in(styles); - let spacing = - VElem::new(leading.into()).with_weak(true).with_attach(true).pack(); + let spacing = VElem::new(leading.into()) + .with_weak(true) + .with_attach(true) + .pack() + .spanned(self.span()); realized = spacing + realized; } diff --git a/crates/typst-macros/src/cast.rs b/crates/typst-macros/src/cast.rs index 9254cdb90..b90b78886 100644 --- a/crates/typst-macros/src/cast.rs +++ b/crates/typst-macros/src/cast.rs @@ -273,7 +273,7 @@ fn create_output_body(input: &CastInput) -> TokenStream { if input.dynamic { quote! { #foundations::CastInfo::Type(#foundations::Type::of::()) } } else { - quote! { Self::input() } + quote! { ::input() } } } From 2b8dc9b14da365cb36345d41fd1ca93894f50c68 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Mon, 2 Dec 2024 13:32:06 +0100 Subject: [PATCH 27/61] Add HTML feature flag --- crates/typst-cli/src/args.rs | 4 +++- crates/typst-cli/src/world.rs | 11 ++++++++--- crates/typst-library/src/lib.rs | 4 +++- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/crates/typst-cli/src/args.rs b/crates/typst-cli/src/args.rs index 9437f848c..bc3d12251 100644 --- a/crates/typst-cli/src/args.rs +++ b/crates/typst-cli/src/args.rs @@ -283,7 +283,9 @@ pub struct SharedArgs { /// An in-development feature that may be changed or removed at any time. #[derive(Debug, Copy, Clone, Eq, PartialEq, ValueEnum)] -pub enum Feature {} +pub enum Feature { + Html, +} /// Arguments related to where packages are stored in the system. #[derive(Debug, Clone, Args)] diff --git a/crates/typst-cli/src/world.rs b/crates/typst-cli/src/world.rs index a04de2c05..6e6de0dcb 100644 --- a/crates/typst-cli/src/world.rs +++ b/crates/typst-cli/src/world.rs @@ -17,7 +17,7 @@ use typst_kit::fonts::{FontSlot, Fonts}; use typst_kit::package::PackageStorage; use typst_timing::timed; -use crate::args::{Input, SharedArgs}; +use crate::args::{Feature, Input, SharedArgs}; use crate::compile::ExportCache; use crate::download::PrintDownload; use crate::package; @@ -112,8 +112,13 @@ impl SystemWorld { .map(|(k, v)| (k.as_str().into(), v.as_str().into_value())) .collect(); - let features = - command.feature.iter().map(|&feature| match feature {}).collect(); + let features = command + .feature + .iter() + .map(|&feature| match feature { + Feature::Html => typst::Feature::Html, + }) + .collect(); Library::builder().with_inputs(inputs).with_features(features).build() }; diff --git a/crates/typst-library/src/lib.rs b/crates/typst-library/src/lib.rs index bd135cdbd..5aec09165 100644 --- a/crates/typst-library/src/lib.rs +++ b/crates/typst-library/src/lib.rs @@ -231,7 +231,9 @@ impl FromIterator for Features { /// An in-development feature that should be enabled. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] #[non_exhaustive] -pub enum Feature {} +pub enum Feature { + Html, +} /// Construct the module with global definitions. fn global(math: Module, inputs: Dict) -> Module { From d00a5d6c9b35e4a340be8df18a11943e3591b86a Mon Sep 17 00:00:00 2001 From: Laurenz Date: Mon, 2 Dec 2024 13:34:02 +0100 Subject: [PATCH 28/61] Add contextual `target` function --- crates/typst-library/src/foundations/mod.rs | 9 ++++- .../typst-library/src/foundations/target.rs | 38 +++++++++++++++++++ crates/typst-library/src/lib.rs | 6 +-- 3 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 crates/typst-library/src/foundations/target.rs diff --git a/crates/typst-library/src/foundations/mod.rs b/crates/typst-library/src/foundations/mod.rs index 9259a7d16..28f983186 100644 --- a/crates/typst-library/src/foundations/mod.rs +++ b/crates/typst-library/src/foundations/mod.rs @@ -31,6 +31,8 @@ mod selector; mod str; mod styles; mod symbol; +#[path = "target.rs"] +mod target_; mod ty; mod value; mod version; @@ -61,6 +63,7 @@ pub use self::selector::*; pub use self::str::*; pub use self::styles::*; pub use self::symbol::*; +pub use self::target_::*; pub use self::ty::*; pub use self::value::*; pub use self::version::*; @@ -79,6 +82,7 @@ use typst_syntax::Spanned; use crate::diag::{bail, SourceResult, StrResult}; use crate::engine::Engine; use crate::routines::EvalMode; +use crate::{Feature, Features}; /// Foundational types and functions. /// @@ -88,7 +92,7 @@ use crate::routines::EvalMode; pub static FOUNDATIONS: Category; /// Hook up all `foundations` definitions. -pub(super) fn define(global: &mut Scope, inputs: Dict) { +pub(super) fn define(global: &mut Scope, inputs: Dict, features: &Features) { global.category(FOUNDATIONS); global.define_type::(); global.define_type::(); @@ -116,6 +120,9 @@ pub(super) fn define(global: &mut Scope, inputs: Dict) { global.define_func::(); global.define_func::(); global.define_func:: + + +
+
Waiting for output ...
+
typst watch {INPUT}
+
+ + +"; + +/// Reloads the page whenever it receives a "reload" server-sent event +/// on the `/events` route. +const LIVE_RELOAD_SCRIPT: &str = "\ +\ +"; diff --git a/crates/typst-cli/src/watch.rs b/crates/typst-cli/src/watch.rs index f5569b466..e62746dfb 100644 --- a/crates/typst-cli/src/watch.rs +++ b/crates/typst-cli/src/watch.rs @@ -21,7 +21,7 @@ use crate::{print_error, terminal}; /// Execute a watching compilation command. pub fn watch(timer: &mut Timer, command: &WatchCommand) -> StrResult<()> { - let mut config = CompileConfig::new(&command.args)?; + let mut config = CompileConfig::watching(command)?; let Output::Path(output) = &config.output else { bail!("cannot write document to stdout in watch mode"); @@ -53,7 +53,7 @@ pub fn watch(timer: &mut Timer, command: &WatchCommand) -> StrResult<()> { }; // Perform initial compilation. - timer.record(&mut world, |world| compile_once(world, &mut config, true))??; + timer.record(&mut world, |world| compile_once(world, &mut config))??; // Watch all dependencies of the initial compilation. watcher.update(world.dependencies())?; @@ -67,7 +67,7 @@ pub fn watch(timer: &mut Timer, command: &WatchCommand) -> StrResult<()> { world.reset(); // Recompile. - timer.record(&mut world, |world| compile_once(world, &mut config, true))??; + timer.record(&mut world, |world| compile_once(world, &mut config))??; // Evict the cache. comemo::evict(10); @@ -293,6 +293,13 @@ impl Status { out.reset()?; writeln!(out, " {}", config.output)?; + if let Some(server) = &config.server { + out.set_color(&color)?; + write!(out, "serving at")?; + out.reset()?; + writeln!(out, " http://{}", server.addr())?; + } + writeln!(out)?; writeln!(out, "[{timestamp}] {}", self.message())?; writeln!(out)?; diff --git a/crates/typst-cli/src/world.rs b/crates/typst-cli/src/world.rs index c39358b9c..af6cf228f 100644 --- a/crates/typst-cli/src/world.rs +++ b/crates/typst-cli/src/world.rs @@ -18,7 +18,6 @@ use typst_kit::package::PackageStorage; use typst_timing::timed; use crate::args::{Feature, Input, ProcessArgs, WorldArgs}; -use crate::compile::ExportCache; use crate::download::PrintDownload; use crate::package; @@ -49,9 +48,6 @@ pub struct SystemWorld { /// always the same within one compilation. /// Reset between compilations if not [`Now::Fixed`]. now: Now, - /// The export cache, used for caching output files in `typst watch` - /// sessions. - export_cache: ExportCache, } impl SystemWorld { @@ -146,7 +142,6 @@ impl SystemWorld { slots: Mutex::new(HashMap::new()), package_storage: package::storage(&world_args.package), now, - export_cache: ExportCache::new(), }) } @@ -191,11 +186,6 @@ impl SystemWorld { pub fn lookup(&self, id: FileId) -> Source { self.source(id).expect("file id does not point to any source file") } - - /// Gets access to the export cache. - pub fn export_cache(&self) -> &ExportCache { - &self.export_cache - } } impl World for SystemWorld { From 8e4f5f21e0e52fb4ac88ab769e585af811d9b56e Mon Sep 17 00:00:00 2001 From: Daniel Drodt <132357467+Drodt@users.noreply.github.com> Date: Thu, 5 Dec 2024 12:21:44 +0100 Subject: [PATCH 39/61] Fix warning for HTML export to reflect renaming of --feature to --features (#5530) --- crates/typst/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/typst/src/lib.rs b/crates/typst/src/lib.rs index b76815b92..358a9ab12 100644 --- a/crates/typst/src/lib.rs +++ b/crates/typst/src/lib.rs @@ -246,7 +246,7 @@ fn warn_or_error_for_html( } else { bail!( Span::detached(), - "html export is only available when `--feature html` is passed"; + "html export is only available when `--features html` is passed"; hint: "html export is under active development and incomplete"; hint: "see {ISSUE} for more information" ); From caa72f4ec2401c275ddd3d8794dcf0bfdf9697a8 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Thu, 5 Dec 2024 16:25:18 +0100 Subject: [PATCH 40/61] Put HTTP server behind on-by-default feature flag (#5532) --- .github/workflows/ci.yml | 1 + crates/typst-cli/Cargo.toml | 7 +++- crates/typst-cli/src/args.rs | 40 ++++++++++++------- crates/typst-cli/src/compile.rs | 70 +++++++++++++++++++-------------- crates/typst-cli/src/main.rs | 1 + crates/typst-cli/src/server.rs | 7 ++-- crates/typst-cli/src/watch.rs | 1 + 7 files changed, 78 insertions(+), 49 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9ddb309bb..268fd93c0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -64,6 +64,7 @@ jobs: components: clippy, rustfmt - uses: Swatinem/rust-cache@v2 - run: cargo clippy --workspace --all-targets --all-features + - run: cargo clippy --workspace --all-targets --no-default-features - run: cargo fmt --check --all - run: cargo doc --workspace --no-deps diff --git a/crates/typst-cli/Cargo.toml b/crates/typst-cli/Cargo.toml index c859f043c..7e9b93f93 100644 --- a/crates/typst-cli/Cargo.toml +++ b/crates/typst-cli/Cargo.toml @@ -50,7 +50,7 @@ shell-escape = { workspace = true } sigpipe = { workspace = true } tar = { workspace = true } tempfile = { workspace = true } -tiny_http = { workspace = true } +tiny_http = { workspace = true, optional = true } toml = { workspace = true } ureq = { workspace = true } xz2 = { workspace = true, optional = true } @@ -65,11 +65,14 @@ color-print = { workspace = true } semver = { workspace = true } [features] -default = ["embed-fonts"] +default = ["embed-fonts", "http-server"] # Embeds some fonts into the binary, see typst-kit embed-fonts = ["typst-kit/embed-fonts"] +# Enables the built-in HTTP server for `typst watch` and HTML export. +http-server = ["dep:tiny_http"] + # Permits the CLI to update itself without a package manager. self-update = ["dep:self-replace", "dep:xz2", "dep:zip"] diff --git a/crates/typst-cli/src/args.rs b/crates/typst-cli/src/args.rs index ead932362..83c4c8f9e 100644 --- a/crates/typst-cli/src/args.rs +++ b/crates/typst-cli/src/args.rs @@ -98,20 +98,10 @@ pub struct WatchCommand { #[clap(flatten)] pub args: CompileArgs, - /// Disables the built-in HTTP server for HTML export. - #[clap(long)] - pub no_serve: bool, - - /// Disables the injected live reload script for HTML export. The HTML that - /// is written to disk isn't affected either way. - #[clap(long)] - pub no_reload: bool, - - /// The port where HTML is served. - /// - /// Defaults to the first free port in the range 3000-3005. - #[clap(long)] - pub port: Option, + /// Arguments for the HTTP server. + #[cfg(feature = "http-server")] + #[clap(flatten)] + pub server: ServerArgs, } /// Initializes a new project from a template. @@ -354,7 +344,7 @@ pub struct PackageArgs { pub package_cache_path: Option, } -/// Common arguments to customize available fonts +/// Common arguments to customize available fonts. #[derive(Debug, Clone, Parser)] pub struct FontArgs { /// Adds additional directories that are recursively searched for fonts. @@ -375,6 +365,26 @@ pub struct FontArgs { pub ignore_system_fonts: bool, } +/// Arguments for the HTTP server. +#[cfg(feature = "http-server")] +#[derive(Debug, Clone, Parser)] +pub struct ServerArgs { + /// Disables the built-in HTTP server for HTML export. + #[clap(long)] + pub no_serve: bool, + + /// Disables the injected live reload script for HTML export. The HTML that + /// is written to disk isn't affected either way. + #[clap(long)] + pub no_reload: bool, + + /// The port where HTML is served. + /// + /// Defaults to the first free port in the range 3000-3005. + #[clap(long)] + pub port: Option, +} + macro_rules! display_possible_values { ($ty:ty) => { impl Display for $ty { diff --git a/crates/typst-cli/src/compile.rs b/crates/typst-cli/src/compile.rs index 01a6de1bc..3aa3aa3b9 100644 --- a/crates/typst-cli/src/compile.rs +++ b/crates/typst-cli/src/compile.rs @@ -1,4 +1,4 @@ -use std::ffi::OsString; +use std::ffi::OsStr; use std::fs::{self, File}; use std::io::{self, Write}; use std::path::{Path, PathBuf}; @@ -23,6 +23,7 @@ use crate::args::{ CompileArgs, CompileCommand, DiagnosticFormat, Input, Output, OutputFormat, PdfStandard, WatchCommand, }; +#[cfg(feature = "http-server")] use crate::server::HtmlServer; use crate::timings::Timer; @@ -72,6 +73,7 @@ pub struct CompileConfig { /// watch` sessions with images. pub export_cache: ExportCache, /// Server for `typst watch` to HTML. + #[cfg(feature = "http-server")] pub server: Option, } @@ -139,17 +141,18 @@ impl CompileConfig { PdfStandards::new(&list)? }; - let mut server = None; - let mut watching = false; - if let Some(command) = watch { - watching = true; - if output_format == OutputFormat::Html && !command.no_serve { - server = Some(HtmlServer::new(&input, command.port, !command.no_reload)?); + #[cfg(feature = "http-server")] + let server = match watch { + Some(command) + if output_format == OutputFormat::Html && !command.server.no_serve => + { + Some(HtmlServer::new(&input, &command.server)?) } - } + _ => None, + }; Ok(Self { - watching, + watching: watch.is_some(), input, output, output_format, @@ -161,6 +164,7 @@ impl CompileConfig { diagnostic_format: args.process.diagnostic_format, open: args.open.clone(), export_cache: ExportCache::new(), + #[cfg(feature = "http-server")] server, }) } @@ -241,6 +245,7 @@ fn export_html(document: &HtmlDocument, config: &CompileConfig) -> SourceResult< let html = typst_html::html(document)?; let result = config.output.write(html.as_bytes()); + #[cfg(feature = "http-server")] if let Some(server) = &config.server { server.update(html); } @@ -556,30 +561,37 @@ fn write_make_deps(world: &mut SystemWorld, config: &CompileConfig) -> StrResult }) } -/// Opens the output if desired, with: -/// - The default file viewer if `open` is `None`. -/// - The given viewer provided by `open` if it is `Some`. -/// -/// If the file could not be opened, an error is returned. +/// Opens the output if desired. fn open_output(config: &mut CompileConfig) -> StrResult<()> { - let Some(open) = config.open.take() else { return Ok(()) }; + let Some(viewer) = config.open.take() else { return Ok(()) }; - let path = if let Some(server) = &config.server { - OsString::from(format!("http://{}", server.addr())) - } else if let Output::Path(path) = &config.output { - // Some resource openers require the path to be canonicalized. - path.canonicalize() - .map_err(|err| eco_format!("failed to canonicalize path ({err})"))? - .into_os_string() - } else { - return Ok(()); - }; + #[cfg(feature = "http-server")] + if let Some(server) = &config.server { + let url = format!("http://{}", server.addr()); + return open_path(OsStr::new(&url), viewer.as_deref()); + } - if let Some(app) = &open { - open::with_detached(&path, app) - .map_err(|err| eco_format!("failed to open file with {} ({})", app, err)) + // Can't open stdout. + let Output::Path(path) = &config.output else { return Ok(()) }; + + // Some resource openers require the path to be canonicalized. + let path = path + .canonicalize() + .map_err(|err| eco_format!("failed to canonicalize path ({err})"))?; + + open_path(path.as_os_str(), viewer.as_deref()) +} + +/// Opens the given file using: +/// +/// - The default file viewer if `app` is `None`. +/// - The given viewer provided by `app` if it is `Some`. +fn open_path(path: &OsStr, viewer: Option<&str>) -> StrResult<()> { + if let Some(viewer) = viewer { + open::with_detached(path, viewer) + .map_err(|err| eco_format!("failed to open file with {} ({})", viewer, err)) } else { - open::that_detached(&path).map_err(|err| { + open::that_detached(path).map_err(|err| { let openers = open::commands(path) .iter() .map(|command| command.get_program().to_string_lossy()) diff --git a/crates/typst-cli/src/main.rs b/crates/typst-cli/src/main.rs index 610f89c03..14f8a665d 100644 --- a/crates/typst-cli/src/main.rs +++ b/crates/typst-cli/src/main.rs @@ -6,6 +6,7 @@ mod greet; mod init; mod package; mod query; +#[cfg(feature = "http-server")] mod server; mod terminal; mod timings; diff --git a/crates/typst-cli/src/server.rs b/crates/typst-cli/src/server.rs index b3ce83f86..8910e0323 100644 --- a/crates/typst-cli/src/server.rs +++ b/crates/typst-cli/src/server.rs @@ -7,7 +7,7 @@ use parking_lot::{Condvar, Mutex, MutexGuard}; use tiny_http::{Header, Request, Response, StatusCode}; use typst::diag::{bail, StrResult}; -use crate::args::Input; +use crate::args::{Input, ServerArgs}; /// Serves HTML with live reload. pub struct HtmlServer { @@ -17,8 +17,9 @@ pub struct HtmlServer { impl HtmlServer { /// Create a new HTTP server that serves live HTML. - pub fn new(input: &Input, port: Option, reload: bool) -> StrResult { - let (addr, server) = start_server(port)?; + pub fn new(input: &Input, args: &ServerArgs) -> StrResult { + let reload = !args.no_reload; + let (addr, server) = start_server(args.port)?; let placeholder = PLACEHOLDER_HTML.replace("{INPUT}", &input.to_string()); let bucket = Arc::new(Bucket::new(placeholder)); diff --git a/crates/typst-cli/src/watch.rs b/crates/typst-cli/src/watch.rs index e62746dfb..91132fc30 100644 --- a/crates/typst-cli/src/watch.rs +++ b/crates/typst-cli/src/watch.rs @@ -293,6 +293,7 @@ impl Status { out.reset()?; writeln!(out, " {}", config.output)?; + #[cfg(feature = "http-server")] if let Some(server) = &config.server { out.set_color(&color)?; write!(out, "serving at")?; From 0228462ba10397468b3c0937e2e9cd1d3118e0fc Mon Sep 17 00:00:00 2001 From: Laurenz Date: Thu, 5 Dec 2024 16:57:01 +0100 Subject: [PATCH 41/61] Bump Rust to 1.83 (#5534) --- .github/workflows/ci.yml | 4 ++-- .github/workflows/release.yml | 2 +- crates/typst-layout/src/grid/repeated.rs | 2 +- crates/typst-layout/src/grid/rowspans.rs | 2 +- crates/typst-layout/src/inline/line.rs | 4 ++-- crates/typst-library/src/engine.rs | 1 + crates/typst-library/src/introspection/locator.rs | 1 + crates/typst-render/src/lib.rs | 2 +- crates/typst-syntax/src/node.rs | 4 ++-- crates/typst-syntax/src/parser.rs | 4 ++-- docs/src/main.rs | 2 +- flake.lock | 6 +++--- flake.nix | 2 +- 13 files changed, 19 insertions(+), 17 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 268fd93c0..01b3e8c3a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,7 +30,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@1.82.0 + - uses: dtolnay/rust-toolchain@1.83.0 - uses: Swatinem/rust-cache@v2 - run: cargo test --workspace --no-run - run: cargo test --workspace --no-fail-fast @@ -59,7 +59,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@1.82.0 + - uses: dtolnay/rust-toolchain@1.83.0 with: components: clippy, rustfmt - uses: Swatinem/rust-cache@v2 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 84c70c2d2..5be6bfa2c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -44,7 +44,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@1.82.0 + - uses: dtolnay/rust-toolchain@1.83.0 with: target: ${{ matrix.target }} diff --git a/crates/typst-layout/src/grid/repeated.rs b/crates/typst-layout/src/grid/repeated.rs index 972179da8..8d08d56db 100644 --- a/crates/typst-layout/src/grid/repeated.rs +++ b/crates/typst-layout/src/grid/repeated.rs @@ -45,7 +45,7 @@ impl Repeatable { } } -impl<'a> GridLayouter<'a> { +impl GridLayouter<'_> { /// Layouts the header's rows. /// Skips regions as necessary. pub fn layout_header( diff --git a/crates/typst-layout/src/grid/rowspans.rs b/crates/typst-layout/src/grid/rowspans.rs index 03b4103fd..93d4c960d 100644 --- a/crates/typst-layout/src/grid/rowspans.rs +++ b/crates/typst-layout/src/grid/rowspans.rs @@ -85,7 +85,7 @@ pub struct CellMeasurementData<'layouter> { pub frames_in_previous_regions: usize, } -impl<'a> GridLayouter<'a> { +impl GridLayouter<'_> { /// Layout a rowspan over the already finished regions, plus the current /// region's frame and resolved rows, if it wasn't finished yet (because /// we're being called from `finish_region`, but note that this function is diff --git a/crates/typst-layout/src/inline/line.rs b/crates/typst-layout/src/inline/line.rs index 6dca95a9d..ef7e26c3c 100644 --- a/crates/typst-layout/src/inline/line.rs +++ b/crates/typst-layout/src/inline/line.rs @@ -38,7 +38,7 @@ pub struct Line<'a> { pub dash: Option, } -impl<'a> Line<'a> { +impl Line<'_> { /// Create an empty line. pub fn empty() -> Self { Self { @@ -685,7 +685,7 @@ impl<'a> Deref for Items<'a> { } } -impl<'a> DerefMut for Items<'a> { +impl DerefMut for Items<'_> { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } diff --git a/crates/typst-library/src/engine.rs b/crates/typst-library/src/engine.rs index cd25ec486..80aaef224 100644 --- a/crates/typst-library/src/engine.rs +++ b/crates/typst-library/src/engine.rs @@ -350,6 +350,7 @@ impl Route<'_> { } #[comemo::track] +#[allow(clippy::needless_lifetimes)] impl<'a> Route<'a> { /// Whether the given id is part of the route. pub fn contains(&self, id: FileId) -> bool { diff --git a/crates/typst-library/src/introspection/locator.rs b/crates/typst-library/src/introspection/locator.rs index 4045aa9ca..a84cf1639 100644 --- a/crates/typst-library/src/introspection/locator.rs +++ b/crates/typst-library/src/introspection/locator.rs @@ -203,6 +203,7 @@ impl<'a> Locator<'a> { } #[comemo::track] +#[allow(clippy::needless_lifetimes)] impl<'a> Locator<'a> { /// Resolves the locator based on its local and the outer information. fn resolve(&self) -> Resolved { diff --git a/crates/typst-render/src/lib.rs b/crates/typst-render/src/lib.rs index 8595e7908..4cd32d39c 100644 --- a/crates/typst-render/src/lib.rs +++ b/crates/typst-render/src/lib.rs @@ -93,7 +93,7 @@ struct State<'a> { size: Size, } -impl<'a> State<'a> { +impl State<'_> { fn new(size: Size, transform: sk::Transform, pixel_per_pt: f32) -> Self { Self { size, diff --git a/crates/typst-syntax/src/node.rs b/crates/typst-syntax/src/node.rs index 14ad018fb..b7e1809d7 100644 --- a/crates/typst-syntax/src/node.rs +++ b/crates/typst-syntax/src/node.rs @@ -767,7 +767,7 @@ impl<'a> LinkedNode<'a> { } /// Access to parents and siblings. -impl<'a> LinkedNode<'a> { +impl LinkedNode<'_> { /// Get this node's parent. pub fn parent(&self) -> Option<&Self> { self.parent.as_deref() @@ -825,7 +825,7 @@ pub enum Side { } /// Access to leaves. -impl<'a> LinkedNode<'a> { +impl LinkedNode<'_> { /// Get the rightmost non-trivia leaf before this node. pub fn prev_leaf(&self) -> Option { let mut node = self.clone(); diff --git a/crates/typst-syntax/src/parser.rs b/crates/typst-syntax/src/parser.rs index ea5b9155d..e087f9dd3 100644 --- a/crates/typst-syntax/src/parser.rs +++ b/crates/typst-syntax/src/parser.rs @@ -1911,7 +1911,7 @@ struct PartialState { } /// The Memoization interface. -impl<'s> Parser<'s> { +impl Parser<'_> { /// Store the already parsed nodes and the parser state into the memo map by /// extending the arena and storing the extended range and a checkpoint. fn memoize_parsed_nodes(&mut self, key: MemoKey, prev_len: usize) { @@ -1967,7 +1967,7 @@ impl<'s> Parser<'s> { /// Functions for eating expected or unexpected tokens and generating errors if /// we don't get what we expect. -impl<'s> Parser<'s> { +impl Parser<'_> { /// Consume the given `kind` or produce an error. fn expect(&mut self, kind: SyntaxKind) -> bool { let at = self.at(kind); diff --git a/docs/src/main.rs b/docs/src/main.rs index f8d7c9345..064bf9a8f 100644 --- a/docs/src/main.rs +++ b/docs/src/main.rs @@ -13,7 +13,7 @@ struct CliResolver<'a> { base: &'a str, } -impl<'a> Resolver for CliResolver<'a> { +impl Resolver for CliResolver<'_> { fn commits(&self, from: &str, to: &str) -> Vec { if self.verbose { eprintln!("commits({from}, {to})"); diff --git a/flake.lock b/flake.lock index 03be65eaa..c02466422 100644 --- a/flake.lock +++ b/flake.lock @@ -112,13 +112,13 @@ "rust-manifest": { "flake": false, "locked": { - "narHash": "sha256-7BORPQOFj1mTsyFBkfImqoE4gwyZfPLYXFFPXPVbo4A=", + "narHash": "sha256-Yqu2/i9170R7pQhvOCR1f5SyFr7PcFbO6xcMr9KWruQ=", "type": "file", - "url": "https://static.rust-lang.org/dist/channel-rust-1.82.0.toml" + "url": "https://static.rust-lang.org/dist/channel-rust-1.83.0.toml" }, "original": { "type": "file", - "url": "https://static.rust-lang.org/dist/channel-rust-1.82.0.toml" + "url": "https://static.rust-lang.org/dist/channel-rust-1.83.0.toml" } }, "systems": { diff --git a/flake.nix b/flake.nix index aec83d896..abdad27aa 100644 --- a/flake.nix +++ b/flake.nix @@ -10,7 +10,7 @@ inputs.nixpkgs.follows = "nixpkgs"; }; rust-manifest = { - url = "https://static.rust-lang.org/dist/channel-rust-1.82.0.toml"; + url = "https://static.rust-lang.org/dist/channel-rust-1.83.0.toml"; flake = false; }; }; From 50dcacea9a3d9284ef1eeb9c20682d9568c91e70 Mon Sep 17 00:00:00 2001 From: Ian Wrzesinski <133046678+wrzian@users.noreply.github.com> Date: Sun, 8 Dec 2024 11:23:14 -0500 Subject: [PATCH 42/61] Convert unopened square-brackets into a hard error (#5414) --- crates/typst-syntax/src/parser.rs | 45 +++++++++++++++-------------- tests/ref/single-right-bracket.png | Bin 118 -> 0 bytes tests/suite/scripting/blocks.typ | 37 ++++++++++++++++++++++++ 3 files changed, 61 insertions(+), 21 deletions(-) delete mode 100644 tests/ref/single-right-bracket.png diff --git a/crates/typst-syntax/src/parser.rs b/crates/typst-syntax/src/parser.rs index e087f9dd3..cb5e2dd85 100644 --- a/crates/typst-syntax/src/parser.rs +++ b/crates/typst-syntax/src/parser.rs @@ -47,14 +47,9 @@ fn markup_exprs(p: &mut Parser, mut at_start: bool, stop_set: SyntaxSet) { debug_assert!(stop_set.contains(SyntaxKind::End)); at_start |= p.had_newline(); let mut nesting: usize = 0; - loop { - match p.current() { - SyntaxKind::LeftBracket => nesting += 1, - SyntaxKind::RightBracket if nesting > 0 => nesting -= 1, - _ if p.at_set(stop_set) => break, - _ => {} - } - markup_expr(p, at_start); + // Keep going if we're at a nested right-bracket regardless of the stop set. + while !p.at_set(stop_set) || (nesting > 0 && p.at(SyntaxKind::RightBracket)) { + markup_expr(p, at_start, &mut nesting); at_start = p.had_newline(); } } @@ -69,15 +64,12 @@ pub(super) fn reparse_markup( ) -> Option> { let mut p = Parser::new(text, range.start, LexMode::Markup); *at_start |= p.had_newline(); - while p.current_start() < range.end { - match p.current() { - SyntaxKind::LeftBracket => *nesting += 1, - SyntaxKind::RightBracket if *nesting > 0 => *nesting -= 1, - SyntaxKind::RightBracket if !top_level => break, - SyntaxKind::End => break, - _ => {} + while !p.end() && p.current_start() < range.end { + // If not top-level and at a new RightBracket, stop the reparse. + if !top_level && *nesting == 0 && p.at(SyntaxKind::RightBracket) { + break; } - markup_expr(&mut p, *at_start); + markup_expr(&mut p, *at_start, nesting); *at_start = p.had_newline(); } (p.balanced && p.current_start() == range.end).then(|| p.finish()) @@ -86,8 +78,21 @@ pub(super) fn reparse_markup( /// Parses a single markup expression. This includes markup elements like text, /// headings, strong/emph, lists/enums, etc. This is also the entry point for /// parsing math equations and embedded code expressions. -fn markup_expr(p: &mut Parser, at_start: bool) { +fn markup_expr(p: &mut Parser, at_start: bool, nesting: &mut usize) { match p.current() { + SyntaxKind::LeftBracket => { + *nesting += 1; + p.convert_and_eat(SyntaxKind::Text); + } + SyntaxKind::RightBracket if *nesting > 0 => { + *nesting -= 1; + p.convert_and_eat(SyntaxKind::Text); + } + SyntaxKind::RightBracket => { + p.unexpected(); + p.hint("try using a backslash escape: \\]"); + } + SyntaxKind::Text | SyntaxKind::Linebreak | SyntaxKind::Escape @@ -108,9 +113,7 @@ fn markup_expr(p: &mut Parser, at_start: bool) { SyntaxKind::RefMarker => reference(p), SyntaxKind::Dollar => equation(p), - SyntaxKind::LeftBracket - | SyntaxKind::RightBracket - | SyntaxKind::HeadingMarker + SyntaxKind::HeadingMarker | SyntaxKind::ListMarker | SyntaxKind::EnumMarker | SyntaxKind::TermMarker @@ -201,7 +204,7 @@ fn equation(p: &mut Parser) { let m = p.marker(); p.enter_modes(LexMode::Math, AtNewline::Continue, |p| { p.assert(SyntaxKind::Dollar); - math(p, syntax_set!(Dollar, RightBracket, End)); + math(p, syntax_set!(Dollar, End)); p.expect_closing_delimiter(m, SyntaxKind::Dollar); }); p.wrap(m, SyntaxKind::Equation); diff --git a/tests/ref/single-right-bracket.png b/tests/ref/single-right-bracket.png deleted file mode 100644 index 9867424ddfa324301c82cc4dde8072d9dfaa899f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 118 zcmeAS@N?(olHy`uVBq!ia0vp^6+kS_0VEhE<%|3RQnsEhjv*Ddl7HAcG$dYm6xi*q zE9WORe@;PCL(QJexeYq|46;71IC}Wpqgv*ak0;k1UYz=M#nHuL{ZT$l3=9kQOnGR} Rb7?8aKu=dcmvv4FO#p!jD Date: Sun, 8 Dec 2024 13:25:47 -0300 Subject: [PATCH 43/61] Ensure par and align interrupt cite groups and lists (#5526) --- crates/typst-realize/src/lib.rs | 6 +++-- ...03-cite-group-interrupted-by-par-align.png | Bin 0 -> 1487 bytes tests/ref/issue-5503-cite-in-align.png | Bin 0 -> 393 bytes ...sue-5503-enum-interrupted-by-par-align.png | Bin 0 -> 1004 bytes ...sue-5503-list-interrupted-by-par-align.png | Bin 0 -> 415 bytes ...ue-5503-terms-interrupted-by-par-align.png | Bin 0 -> 569 bytes tests/suite/model/cite.typ | 22 ++++++++++++++++++ tests/suite/model/enum.typ | 12 ++++++++++ tests/suite/model/list.typ | 15 ++++++++++++ tests/suite/model/terms.typ | 15 ++++++++++++ 10 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 tests/ref/issue-5503-cite-group-interrupted-by-par-align.png create mode 100644 tests/ref/issue-5503-cite-in-align.png create mode 100644 tests/ref/issue-5503-enum-interrupted-by-par-align.png create mode 100644 tests/ref/issue-5503-list-interrupted-by-par-align.png create mode 100644 tests/ref/issue-5503-terms-interrupted-by-par-align.png diff --git a/crates/typst-realize/src/lib.rs b/crates/typst-realize/src/lib.rs index c46a15351..fd43e8304 100644 --- a/crates/typst-realize/src/lib.rs +++ b/crates/typst-realize/src/lib.rs @@ -836,7 +836,9 @@ static CITES: GroupingRule = GroupingRule { tags: false, trigger: |content, _| content.elem() == CiteElem::elem(), inner: |content| content.elem() == SpaceElem::elem(), - interrupt: |elem| elem == CiteGroup::elem(), + interrupt: |elem| { + elem == CiteGroup::elem() || elem == ParElem::elem() || elem == AlignElem::elem() + }, finish: finish_cites, }; @@ -859,7 +861,7 @@ const fn list_like_grouping() -> GroupingRule { let elem = content.elem(); elem == SpaceElem::elem() || elem == ParbreakElem::elem() }, - interrupt: |elem| elem == T::elem(), + interrupt: |elem| elem == T::elem() || elem == AlignElem::elem(), finish: finish_list_like::, } } diff --git a/tests/ref/issue-5503-cite-group-interrupted-by-par-align.png b/tests/ref/issue-5503-cite-group-interrupted-by-par-align.png new file mode 100644 index 0000000000000000000000000000000000000000..166331587009d036a445d95c3478dac114015d58 GIT binary patch literal 1487 zcmV;=1u*)FP)!yuQY8a(c$b#{K>MR8&^^`TG0&{PObh?CtTCmY(OVnAlq|_4Tf+9>Wv%9pkw9L%RFfux7Y;?D` zw}*#^j*gCujEwN`@P>wlv*4n|t%V=tHy1T!ttEanCa>3l$4fQ zT3oNOxp8xQz`(-u^YcJJK%k(Ym6esSu&_KmL0Vj7t*x(QW^Ui#;o#unsHm!vl$>d6 zbDo}_U0-LZsjFOGX1Tk=yS>G|zQkW)X}`hA!^O?T$Ir>j)9UK%>+9{$&(YD-+t%3N z-rwcY)Z9^1Tkh`ei;Ih&pPyb}XsW8Ld3%F}hmW$fyn1|uy}!rS*4W_S;Q9Ia)79Ob zpQoRpsfvu0wz$AfP*_b*S4&M*R8&-TcYjPyR?g7a^YivnQ&-p7;nmmQmzkk|fr+lL zxPE|$@$vEQ@AFSlS>4~~v+#k$0008gNklq~ zTwUuyK+6(9UoH?cvAP5B*3%wHv!}9#O+Z7Bp8!m@ z@Gs1ds5cjD`2oKw$1z`zgfbsGb0A&K(unG8}wY-nnhf&4DjKT$J$w|z^ zR7L6M#KcdOs+Smjc zBM4jFy=$alH!OxPQ;y^633~n7Rce;oxswwyb57rXi%PoAn(0VngmM5yg#dK6o5Vh# zu#E7tvb8ka z{2CLB;gQw{2R~Q7UOA3aM)(}HH1h582vaVh^X3b}H(}S#-HgAxO9B1KWB{dL8#V!6 zCrkiL?8Er@--Q!&%7lXl4eZA}On&@%VC&!kJ89G$KEjtN$8mM7neCswloy04cmVpM z;sSitvA;<)_43SC8D@9)SZ0m pbaN+c-A%PF5dXG(nQ~lx@*8WC00B1gL@odT002ovPDHLkV1mnpGGG7z literal 0 HcmV?d00001 diff --git a/tests/ref/issue-5503-cite-in-align.png b/tests/ref/issue-5503-cite-in-align.png new file mode 100644 index 0000000000000000000000000000000000000000..aeb72aa0d169fee63a9e8db9bef41fbd4427cc4a GIT binary patch literal 393 zcmV;40e1e0P)gwwD_V(D=*xK6KU0q!@H8n;?MvIG!m6er0K}p)&_3000000NkvXXu0mjfe@M+w literal 0 HcmV?d00001 diff --git a/tests/ref/issue-5503-enum-interrupted-by-par-align.png b/tests/ref/issue-5503-enum-interrupted-by-par-align.png new file mode 100644 index 0000000000000000000000000000000000000000..9cc942b4cc09c57c1cce0c1408e01cf2e972699f GIT binary patch literal 1004 zcmVb|=u zb^-ueRHpRzG#~^MOfbPy85Ry-X9)A!D|ykbC}9Yj>+o+d(z=p&>|kJI38L_N!=edh za4>xc;*F77#|%F0>K~tk_jIy?O^dVRLnr$H0F_(s9Rw@bWwZCf!2hi$Z61QSd! z!BY`-H3wyZ>$Q)!$pX81ZGN;>4Jv&Iru;X;xrVe)62o}7q#~N?^??Hhb8AluiZ}E@ zY8d>VT%_~`xW6l?tBQsC5~PO5G5!w*g)p3pa&=+t*aDQ5NDjB1F0vrwWf+bxJrin6 zH6)q$?sl_$kM#xe3<#gRd_)!)9?t}AHNgZE{G;&eR1Z72t7@_l_mLUQZBUAh;PN46 zaFJ1j2To{yWd^sjH;C}=E+jC6Zz+@Gdm^1+0mDZP?J-;$000y#+W%HFgZ(#aKRp@? zchjRML$q836HG9{QxA4J?DD|Nj+Dv*4{9d6_IZ^RXs_@JHP1Ah#Bg7xLLF`8syAzz zp4M6p;#87}mly{4-=l`U0QGzE@M;{?7XgW30m5GhMc{l?=T&O_3vszja@evZrw+;X zFubUAkN-|e)g1G>)zjkR3y!A*WP$m%HXm&@!2}chgK)pj$`I~u6?3Q8zz(jyAf_`J zU0r1L#mpGo z)Q1?c1P-=ryM`F_Ct`U;&Z~jk^c)NgE8Xe;0veVfsgZ!`MiN_ zYwM04kC*rQ*bYcAc63-dA;)rcYnyo5YJv%#Hn82s6n-k!nM{VTko%k14^A+>@w`bm zO|`=<$B)ksR*dEuWr4F$S;h{Y1=s=r08={33z@;Xjdzgq766cah&!GUq~#)*V1fz$ a_Iv?W=#otA7_LeH0000%wxAq))we5$W( zqlC2pa+cDYcP+VTJR*i+Dn*v6&+-Cj;SS8@k7eBpDJ=RStliO^Bmz=m_MHw6*?dl? zVh={Q{h~d8!Ue)G!wfV0U*Pjo89<9qJ9}~jR0>Gm{IU~Z>0x|qB*o?b8 z7ryBfF${f69!Vm)3R*Z0eXE+*#iI^FIMy56H54>4XJRFt8jH}u_sX5}R7q#+S4H5a z!_CDo!wfV0Ggulw1Q2Mep-Tv3008Rhdu=BlUlwa3xRu$UA76~6h&y{rKE43va$|Dl zl@=ZXT*8z;gM06Cju4g%UdNO&;SYJ&n+;2t3xr{Y8D{v~eF5@?n6e%z(Fy=G`P)qI zPjl*XWq^m#t;VPD44m`UXj0{@!Sz(QzjZrWMz=*izgN zJ0*c<@`HhcxiUaCf-I~&!C2;R@!?+#_OEX78&gW)%@EQBcmA$9sSq6w)V^DQwYoFe z%y=TImia3CU&jAIH$w$NFu?>9d;#G@qh)BYhm#(jRr@o#2kNAU_fsff5l^lF!MI=W z30`442t?Tf@qc^_M=GZu>}FRnvzObepZ&?@)+&2jC4rmYb}19;9^e+ZVtnZh-L?vn zz?z=`)HWaso??9cA6_faiViPd|CVsy>^H{pYE!G1mJN5KN|n~!W)U3@xLF=y7r)1` z$q*OvaZ#3wxqQ@I1QSd!!Doca8!L+HU&sLK(4m4(w((`rU_z&{&hg&a@NcZfi%EUE zB(RXTUeZ66v3U6UlW@iBs(>P8kpy-sY}U}}@ue%%CO+IzgY9%4dyW!-vEl}n9>wgN z*`mXNa^(Vkl(p?NJ@!P3n&xvRw1vKxx;`opf(a&=;FBBycoS&Zfmn~t00000NkvXX Hu0mjfmu3tE literal 0 HcmV?d00001 diff --git a/tests/suite/model/cite.typ b/tests/suite/model/cite.typ index 902800818..b328dda49 100644 --- a/tests/suite/model/cite.typ +++ b/tests/suite/model/cite.typ @@ -114,6 +114,28 @@ B #cite() #cite(). #show bibliography: none #bibliography("/assets/bib/works.bib", style: "chicago-author-date") +--- issue-5503-cite-in-align --- +// The two aligned elements should be displayed in separate lines. +#align(right)[@netwok] +#align(right)[b] + +#show bibliography: none +#bibliography("/assets/bib/works.bib") + +--- issue-5503-cite-group-interrupted-by-par-align --- +// `par` and `align` are block-level and should interrupt a cite group +@netwok +@arrgh +#par(leading: 5em)[@netwok] +#par[@arrgh] +@netwok +@arrgh +#align(right)[@netwok] +@arrgh + +#show bibliography: none +#bibliography("/assets/bib/works.bib") + --- cite-type-error-hint --- // Test hint for cast error from str to label // Error: 7-15 expected label, found string diff --git a/tests/suite/model/enum.typ b/tests/suite/model/enum.typ index ed33157e8..c5e562159 100644 --- a/tests/suite/model/enum.typ +++ b/tests/suite/model/enum.typ @@ -163,3 +163,15 @@ a + 0. // Enum item (pre-emptive) #enum.item(none)[Hello] #enum.item(17)[Hello] + +--- issue-5503-enum-interrupted-by-par-align --- +// `align` is block-level and should interrupt an enum +// but not a `par` ++ a ++ b +#par(leading: 5em)[+ par] ++ d +#par[+ par] ++ f +#align(right)[+ align] ++ h diff --git a/tests/suite/model/list.typ b/tests/suite/model/list.typ index aa117672c..138abf70e 100644 --- a/tests/suite/model/list.typ +++ b/tests/suite/model/list.typ @@ -218,3 +218,18 @@ World part($ x $ + parbreak() + list[A]) part($ x $ + parbreak() + parbreak() + list[A]) } + +--- issue-5503-list-interrupted-by-par-align --- +// `align` is block-level and should interrupt a list +// but not a `par` +#show list: [List] +- a +- b +#par(leading: 5em)[- c] +- d +- e +#par[- f] +- g +- h +#align(right)[- i] +- j diff --git a/tests/suite/model/terms.typ b/tests/suite/model/terms.typ index 07aa827dc..61fe20b0d 100644 --- a/tests/suite/model/terms.typ +++ b/tests/suite/model/terms.typ @@ -75,3 +75,18 @@ Not in list --- issue-2530-term-item-panic --- // Term item (pre-emptive) #terms.item[Hello][World!] + +--- issue-5503-terms-interrupted-by-par-align --- +// `align` is block-level and should interrupt a `terms` +// but not a `par` +#show terms: [Terms] +/ a: a +/ b: b +#par(leading: 5em)[/ c: c] +/ d: d +/ e: e +#par[/ f: f] +/ g: g +/ h: h +#align(right)[/ i: i] +/ j: j From fbcd624eebf9763d5cedbbc433e8b5ffee8402d8 Mon Sep 17 00:00:00 2001 From: Borna Punda Date: Sun, 8 Dec 2024 17:29:24 +0100 Subject: [PATCH 44/61] Add support for Croatian quotes (#5539) --- crates/typst-library/src/text/smartquote.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/typst-library/src/text/smartquote.rs b/crates/typst-library/src/text/smartquote.rs index 925e86d65..2f89fe298 100644 --- a/crates/typst-library/src/text/smartquote.rs +++ b/crates/typst-library/src/text/smartquote.rs @@ -211,7 +211,7 @@ impl<'s> SmartQuotes<'s> { /// Swiss / Liechtensteinian German, Estonian, Icelandic, Italian, Latin, /// Lithuanian, Latvian, Slovak, Slovenian, Spanish, Bosnian, Finnish, /// Swedish, French, Swiss French, Hungarian, Polish, Romanian, Japanese, - /// Traditional Chinese, Russian, Norwegian, and Hebrew. + /// Traditional Chinese, Russian, Norwegian, Hebrew and Croatian. /// /// For unknown languages, the English quotes are used as fallback. pub fn get( @@ -250,6 +250,7 @@ impl<'s> SmartQuotes<'s> { "ru" | "no" | "nb" | "nn" | "uk" => ("’", "’", "«", "»"), "el" => ("‘", "’", "«", "»"), "he" => ("’", "’", "”", "”"), + "hr" => ("‘", "’", "„", "”"), _ if lang.dir() == Dir::RTL => ("’", "‘", "”", "“"), _ => default, }; From e6de044b1da1e1c801a69149d757bada7154a83b Mon Sep 17 00:00:00 2001 From: Ian Wrzesinski <133046678+wrzian@users.noreply.github.com> Date: Sun, 8 Dec 2024 11:29:49 -0500 Subject: [PATCH 45/61] Add test for issue #4573 (#5542) --- tests/suite/scripting/destructuring.typ | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/suite/scripting/destructuring.typ b/tests/suite/scripting/destructuring.typ index 3c0c754c8..87bdefa73 100644 --- a/tests/suite/scripting/destructuring.typ +++ b/tests/suite/scripting/destructuring.typ @@ -373,3 +373,23 @@ // Error: 6-12 too many elements to destructure // Hint: 6-12 the provided array has a length of 3, but the pattern expects 2 elements #for (x, y) in ((1,2,3), (4,5,6)) {} + +--- issue-4573-destructuring-unclosed-delimiter --- +// Tests a case where parsing within an incorrectly predicted paren expression +// (the "outer" assignment) would put the parser in an invalid state when +// reloading a stored prediction (the "inner" assignment) and cause a panic when +// generating the unclosed delimiter error. See the comment in the issue for +// more details. +#{ + ( + // Error: 5-7 expected pattern, found keyword `if` + // Hint: 5-7 keyword `if` is not allowed as an identifier; try `if_` instead + // Error: 9 expected comma + // Error: 13-17 unexpected keyword `else` + // Error: 20 expected comma + if x {} else {} + // Error: 5-6 unclosed delimiter + { () = "inner" + ) = "outer" +} + From 62567fc91e2d58d5d12457bbeddc5d7950d7c570 Mon Sep 17 00:00:00 2001 From: PgBiel <9021226+PgBiel@users.noreply.github.com> Date: Sun, 8 Dec 2024 13:35:54 -0300 Subject: [PATCH 46/61] Fix multiple footnotes in footnote entry (#5545) --- crates/typst-layout/src/flow/compose.rs | 15 ++++++++++++++- ...issue-5256-multiple-footnotes-in-footnote.png | Bin 0 -> 796 bytes tests/suite/layout/flow/footnote.typ | 4 ++++ 3 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 tests/ref/issue-5256-multiple-footnotes-in-footnote.png diff --git a/crates/typst-layout/src/flow/compose.rs b/crates/typst-layout/src/flow/compose.rs index 343b47833..9650a7bc1 100644 --- a/crates/typst-layout/src/flow/compose.rs +++ b/crates/typst-layout/src/flow/compose.rs @@ -470,7 +470,20 @@ impl<'a, 'b> Composer<'a, 'b, '_, '_> { // Lay out nested footnotes. for (_, note) in nested { - self.footnote(note, regions, flow_need, migratable)?; + match self.footnote(note, regions, flow_need, migratable) { + // This footnote was already processed or queued. + Ok(_) => {} + // Footnotes always request a relayout when processed for the + // first time, so we ignore a relayout request since we're + // about to do so afterwards. Without this check, the first + // inner footnote interrupts processing of the following ones. + Err(Stop::Relayout(_)) => {} + // Either of + // - A `Stop::Finish` indicating that the frame's origin element + // should migrate to uphold the footnote invariant. + // - A fatal error. + err => return err, + } } // Since we laid out a footnote, we need a relayout. diff --git a/tests/ref/issue-5256-multiple-footnotes-in-footnote.png b/tests/ref/issue-5256-multiple-footnotes-in-footnote.png new file mode 100644 index 0000000000000000000000000000000000000000..f9c1733515950a9a022e784c477d151273be14c6 GIT binary patch literal 796 zcmV+%1LOROP) z_4V%W^WER)_xJhV;OF=E`2GF;>+JB;)!ot4+qAa6@$vNV@bK#E@9pmL>gwu;hlktS z+vn))=;-LAq@-eEV&vrHSXfxKwY8d?pWfcytgNi9ueW-8ft#G7k&~Nxe1xZ|v9q(a z)Yjf_ae2?t*+@!KaB_Oh&(~yTZq3cnl9Zgs$k5*2;*^z{%F4|4_V)Ah^M!|x$;;EE zrKzi|u!e|`%+1l8oS>+8I{ys4?F%F4>h%+x|eOw-fTu&}VGsCb) zO-)HjNpW#;*x1;XmX?o?kMHmAudlB?Jw5I1?SO!QQBhI!^z=?nPF-DH{{H@`sHouJ z;COg=-QC^k>FN9X``_Q+=I8D6^Y;4s`uqF*ii(Qj$FKwd00BrzL_t(|+U?gxR|7E= zhT)H72HN6Mytuo&ySuwvTWFC&U8X;r*|O=XB!_aJWdAO5?mZ_UgphdfeL;%Tvnxo! z8zhD6>MtP$=bXY~Fve)55JLW#XiWG$NiosGBobwCKSI4YQtpb$oJe zc1dh_bc8jvw2n>8&n$`!n~bc{sMe@c(+gt5oasFKs3fVZa3DQFEYY+emL$SSU404e)>>USU>|#;!LaV z==2055H8cw-Sy;twnMZGQ!T7EpBJLBR1>WB#|J4Z(`tkBRYo}fSnl2q-p-Mg4Z+y%cHkXAM8X2`yP%U0#_}4FE+f3MYA!9IIU|IS aAHD(Hcs_t>m%3&E0000, C @fn, D @fn, E @fn. // Test whether an empty footnote would cause infinite loop #show footnote.entry: it => {} #lorem(3) #footnote[A footnote] + +--- issue-5256-multiple-footnotes-in-footnote --- +// Test whether all footnotes inside another footnote are listed. +#footnote[#footnote[A]#footnote[B]#footnote[C]] From d04cc61eee2b9519e82b7759a929314299cb4a34 Mon Sep 17 00:00:00 2001 From: Tetragramm <9815373+Tetragramm@users.noreply.github.com> Date: Sun, 8 Dec 2024 10:43:25 -0600 Subject: [PATCH 47/61] Add missing functions to the gradient object. (#5528) Co-authored-by: PgBiel <9021226+PgBiel@users.noreply.github.com> Co-authored-by: Laurenz --- .../typst-library/src/visualize/gradient.rs | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/crates/typst-library/src/visualize/gradient.rs b/crates/typst-library/src/visualize/gradient.rs index 2be7e3701..e16e5d88a 100644 --- a/crates/typst-library/src/visualize/gradient.rs +++ b/crates/typst-library/src/visualize/gradient.rs @@ -697,6 +697,8 @@ impl Gradient { } /// Returns the angle of this gradient. + /// + /// Returns `{none}` if the gradient is neither linear nor conic. #[func] pub fn angle(&self) -> Option { match self { @@ -706,6 +708,54 @@ impl Gradient { } } + /// Returns the center of this gradient. + /// + /// Returns `{none}` if the gradient is neither radial nor conic. + #[func] + pub fn center(&self) -> Option> { + match self { + Self::Linear(_) => None, + Self::Radial(radial) => Some(radial.center), + Self::Conic(conic) => Some(conic.center), + } + } + + /// Returns the radius of this gradient. + /// + /// Returns `{none}` if the gradient is not radial. + #[func] + pub fn radius(&self) -> Option { + match self { + Self::Linear(_) => None, + Self::Radial(radial) => Some(radial.radius), + Self::Conic(_) => None, + } + } + + /// Returns the focal-center of this gradient. + /// + /// Returns `{none}` if the gradient is not radial. + #[func] + pub fn focal_center(&self) -> Option> { + match self { + Self::Linear(_) => None, + Self::Radial(radial) => Some(radial.focal_center), + Self::Conic(_) => None, + } + } + + /// Returns the focal-radius of this gradient. + /// + /// Returns `{none}` if the gradient is not radial. + #[func] + pub fn focal_radius(&self) -> Option { + match self { + Self::Linear(_) => None, + Self::Radial(radial) => Some(radial.focal_radius), + Self::Conic(_) => None, + } + } + /// Sample the gradient at a given position. /// /// The position is either a position along the gradient (a [ratio] between From 57f7c167d867094660077d3da75a0207497aa36e Mon Sep 17 00:00:00 2001 From: Jakob Peters Date: Sun, 8 Dec 2024 08:52:57 -0800 Subject: [PATCH 48/61] Document integer literal parsing (#5462) Co-authored-by: Laurenz --- crates/typst-library/src/foundations/int.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/crates/typst-library/src/foundations/int.rs b/crates/typst-library/src/foundations/int.rs index e936353cc..bddffada3 100644 --- a/crates/typst-library/src/foundations/int.rs +++ b/crates/typst-library/src/foundations/int.rs @@ -11,7 +11,12 @@ use crate::foundations::{ /// /// The number can be negative, zero, or positive. As Typst uses 64 bits to /// store integers, integers cannot be smaller than `{-9223372036854775808}` or -/// larger than `{9223372036854775807}`. +/// larger than `{9223372036854775807}`. Integer literals are always positive, +/// so a negative integer such as `{-1}` is semantically the negation `-` of the +/// positive literal `1`. A positive integer greater than the maximum value and +/// a negative integer less than or equal to the minimum value cannot be +/// represented as an integer literal, and are instead parsed as a `{float}`. +/// The minimum integer value can still be obtained through integer arithmetic. /// /// The number can also be specified as hexadecimal, octal, or binary by /// starting it with a zero followed by either `x`, `o`, or `b`. From 468a60103dca9c6788be2207c9785d5ba771c800 Mon Sep 17 00:00:00 2001 From: Max Date: Sun, 8 Dec 2024 16:55:34 +0000 Subject: [PATCH 49/61] Fix multiline annotations in over- elems in math changing the baseline (#5459) --- crates/typst-layout/src/math/mat.rs | 4 +++- crates/typst-layout/src/math/shared.rs | 1 - crates/typst-layout/src/math/underover.rs | 4 ++-- tests/ref/math-cases-linebreaks.png | Bin 0 -> 570 bytes tests/ref/math-mat-linebreaks.png | Bin 0 -> 651 bytes .../ref/math-underover-multiline-annotation.png | Bin 0 -> 1672 bytes tests/ref/math-vec-linebreaks.png | Bin 0 -> 856 bytes tests/suite/math/cases.typ | 5 +++++ tests/suite/math/mat.typ | 5 +++++ tests/suite/math/underover.typ | 7 +++++++ tests/suite/math/vec.typ | 5 +++++ 11 files changed, 27 insertions(+), 4 deletions(-) create mode 100644 tests/ref/math-cases-linebreaks.png create mode 100644 tests/ref/math-mat-linebreaks.png create mode 100644 tests/ref/math-underover-multiline-annotation.png create mode 100644 tests/ref/math-vec-linebreaks.png diff --git a/crates/typst-layout/src/math/mat.rs b/crates/typst-layout/src/math/mat.rs index 6c8b04553..24104f4ee 100644 --- a/crates/typst-layout/src/math/mat.rs +++ b/crates/typst-layout/src/math/mat.rs @@ -127,7 +127,9 @@ fn layout_vec_body( let denom_style = style_for_denominator(styles); let mut flat = vec![]; for child in column { - flat.push(ctx.layout_into_run(child, styles.chain(&denom_style))?); + // We allow linebreaks in cases and vectors, which are functionally + // identical to commas. + flat.extend(ctx.layout_into_run(child, styles.chain(&denom_style))?.rows()); } // We pad ascent and descent with the ascent and descent of the paren // to ensure that normal vectors are aligned with others unless they are diff --git a/crates/typst-layout/src/math/shared.rs b/crates/typst-layout/src/math/shared.rs index 13477c10b..74e62e8f0 100644 --- a/crates/typst-layout/src/math/shared.rs +++ b/crates/typst-layout/src/math/shared.rs @@ -121,7 +121,6 @@ pub fn stack( alternator: LeftRightAlternator, minimum_ascent_descent: Option<(Abs, Abs)>, ) -> Frame { - let rows: Vec<_> = rows.into_iter().flat_map(|r| r.rows()).collect(); let AlignmentResult { points, width } = alignments(&rows); let rows: Vec<_> = rows .into_iter() diff --git a/crates/typst-layout/src/math/underover.rs b/crates/typst-layout/src/math/underover.rs index b1d4825b6..1a2c8db66 100644 --- a/crates/typst-layout/src/math/underover.rs +++ b/crates/typst-layout/src/math/underover.rs @@ -297,7 +297,7 @@ fn layout_underoverspreader( if let Some(annotation) = annotation { let under_style = style_for_subscript(styles); let annotation_styles = styles.chain(&under_style); - rows.push(ctx.layout_into_run(annotation, annotation_styles)?); + rows.extend(ctx.layout_into_run(annotation, annotation_styles)?.rows()); } 0 } @@ -305,7 +305,7 @@ fn layout_underoverspreader( if let Some(annotation) = annotation { let over_style = style_for_superscript(styles); let annotation_styles = styles.chain(&over_style); - rows.push(ctx.layout_into_run(annotation, annotation_styles)?); + rows.extend(ctx.layout_into_run(annotation, annotation_styles)?.rows()); } rows.push(stretched.into()); rows.push(MathRun::new(vec![body])); diff --git a/tests/ref/math-cases-linebreaks.png b/tests/ref/math-cases-linebreaks.png new file mode 100644 index 0000000000000000000000000000000000000000..543d5384c11a270a8a56f95e91e4f5ec7ac64d3f GIT binary patch literal 570 zcmV-A0>%A_P)lQAABqA!TMHEz9cRe8qCP#{hjKH8aMcJl}i-<6+$P9wogh3C2 z9$-Qlq}1S~m52`p2z#sqtf$p4{Ie*W;1j*P)1M%KUG3EJzHddF`rhwFq zuOWb2FNlT)?nE^_rf7H3syuj!rM6+>S%~W1hNYHWQX0%!Bp-Yg8enf7IwDfGunL3Y zMi=#7I5NY+by_FYE@NEjPc3Le#1;UFH5nkO(Sdec{YUT# zJ7W;TJ1$!tQGFT&m~jJSNvgAwue8zdeq&V|NR+h#*S(L+V=G2w<)zTV7>r zI0P`vTFV>jEY2Hi_@%07*qo IM6N<$g7^yiXaE2J literal 0 HcmV?d00001 diff --git a/tests/ref/math-mat-linebreaks.png b/tests/ref/math-mat-linebreaks.png new file mode 100644 index 0000000000000000000000000000000000000000..52ff0a8bbc61bba3604655887b7aeb893fc9115d GIT binary patch literal 651 zcmV;60(AX}P)Fp_9-lGDtAGC{c8XR6@&wB3QQ48A}g}S`?Zq2AQdpA-F(8@h}ob zL~bT61Y6V&wl$2HZaQ@>Hn6R^Z-?Juy|=>c@!;=!d7j&c_c^=;Y*9N3wkRFeVIAIP zu>Za`I~Qf)NK1pZG~WFw3ODud(w3dg?FE~Te{P~9ySGiV$^HXAl>k`z4zN%z2Rmy4 z#vY9t04nChV51#~7xUTzU~3kG_a^|ik!JS_5Ga;{1BC$b8{VU~D8SwyLhyJQ05N|& z2tcV*2!41GrL#66*xZVesY3{^d5qF+lMr0pkJ6p%0EAHs=NvAo2OxNQ4FIak0N(lm zV5SN!XCPlMKKA#EQ$1+K&VZIRPw-OB$4nNszX9gwGrY8X2IhQVSjm2QA)7pnmFYhv*5Er3HQ zegV`v#Nc$v24I<;90F1&5QDMW0m4auz7at*X7QFz>CPW%z<5%=m~ltqx>i lmX#p_tLd-~>#z<3e*pLZl?g+Rl0X0e002ovPDHLkV1mb*BP##^ literal 0 HcmV?d00001 diff --git a/tests/ref/math-underover-multiline-annotation.png b/tests/ref/math-underover-multiline-annotation.png new file mode 100644 index 0000000000000000000000000000000000000000..ad8f0c80250910c87ad9ae023ff3cb1c76cd2205 GIT binary patch literal 1672 zcmV;326y?1P)9k`JtF@vwIs|DIqvB(cfUQIcLWN}s zZxGle5!r!^DyX+-}OO8*UtBUMO(l%CZZg?z>R@H9J{7F8V*wpZ>yq!O{ z%80Sb9Ge>Eo&`O6q_)}b!Kyas@X2ZZ7Qzb`thM+s5#g?QA>qy@dvRSQhOS9Krfq5~ zt+bjuhIuBLu4_QkVCR9+ukdhz^HS-}Wc`7zOMuK4XFS!ZJlTBKOPF)cpmBLh@BjP? zYn028Wi4r=5tsnkV%($n-uzRN$y->)>6!Rb-F<$Asje}pDl?l08VxX%piRA(Qky?h zHO#Z%SP&r>qB76;ec zVedP6OAf8s?eD_B$V<8+YUi>tVzUReN9OYVd# z3Gr@*qGY42v^Bjru@Q}mxu`4PY!=~-Y#=rB060?dSWdx<@q-A7d}CUzOV{fum0#90&)(f$$3qPiL0(v5K+ebI^5zP$B^ge%#y# z9ZmJDZLSUmYwegD8l)1R;o)%0gEv{JU&#qb!NZ&f-1+bFCm}HO!(rBz49vcM9%pzP z19iT_VI7dnN@=+EQPK9)Df72BoF7ANFl!sh1`MiKo#X-MeTCzE z;1JCJW|==^LPHKX6>seUb_@aF9uph@BmW{u`YdcJJXMqfG7tFgFP{0K#zv z8wqi8Th7!^ZIE8apX}5}!<}GO*{hy^qe6=9hrwzn5;IOtc@JmG6EE3W!IGYT)c}QB z+@%#QWh#(4-Kp^j*I_6&c8ybJR*9k&Z6R|pAA##0z?qF~AfD6pvK$YG0xxuj5t8_Z zp=2ZBArQ1$5DJ&@qPr16{~35lxq-bJ-{FbH=YAH#7;BqaT4&fD6J1=d?fKgM1qA0% zk4I}+Qp7km z%7L1t7QKJr+jRy-QHHCvs~eDYsu~iC3?t3W-u^9Ss}GH}$GR`u{Ry{!%IUHy#n`^C zG|$hFi#-LQ>I9xeuNYa}e=V5^;->uxk1OWlN}CmyzR2_YEORbypCte9izy3 zlm0t*@h8kctNU)#Ew#193`50zW!;~)SGik^fiDU07)x?@yp%99W?oWw^h{4s;qA7{ zoXrj+BVs20SchtJ!(SXQgc$yzK7O~Nb#ue^8S7Wq!VDI7;I$YC2f~5yi}fWyLVz@{ S>l_mR00006nP)L;@+%=DDnTU^m zbxqq|-Z|}IoD1vXScnIY;p(4-l$SfPq8O6z=AmK-7>JYJLZ0YIt`y1;>cqiQLQEv~ z?9vL|ltN!@+r-23T|)6Aa7J{CxPLcfS~c2(&y75Faoa)2ldULh6vmZ&5f|Qo+&Qn0 z!#GMsU3@tkQc?RNrVK_yw1}gsAu~03X)j@1%b_kl@DP`Ja)^W0trzizFStHidU03A zP!|W+5vSG53dMUF3EZ}Wx_E6LdHeo=)T|)s&>Gren}?AEo7*AV14wH0uu&FUmrar& zNCl}1BI)EZD`m08Q;+-ADF^UXKq`F2>-t@>^_~_+Pu!K^_KEL5C!Uyr_*S{vojCT> zC}VNV>#_zn;%z;dV{F7`*$D1%RB;s#?-IxoZ+r}D2f9Yi+RdowDKo;`>7%O*OBzwDnwhnrIR@4Jm#Qgm)i;4vX#0xqJ}sJ{KQ`U zo4}>}sEg0s5nJ2LK`BGTGxVd_eQmgMQ>lxq;&3T$H_D+)vP7I!fGfKuzZp8Iin@3@ z0MZnMxM>)N-if&7Fr;cfGVw41XQ+!&lv;?+c!bPC^MSlbt=la^-YXJuZv>(*Zagc5 z{q*4DE}`G#I&ofw5Snatkyhv&B?B=EH$!Uhj+}B*V>F~i2RRjqScu=}xn`|=0T(v7 zW=;zeF*-ls7}_KD1!_;2aje#Jh>e&}AI3eOK>TVLQs;4hPq8u*BilGG&O~Aphilv> i{m&(dC9x!4lGh&#Ht1u*xA0;B0000 Date: Sun, 8 Dec 2024 18:06:25 +0100 Subject: [PATCH 50/61] Add support for converting text in SVGs to paths (#5390) --- crates/typst-layout/src/image.rs | 1 + .../typst-library/src/visualize/image/mod.rs | 9 +++++++- .../typst-library/src/visualize/image/svg.rs | 23 +++++++++++++++++-- crates/typst-pdf/src/image.rs | 6 ++++- 4 files changed, 35 insertions(+), 4 deletions(-) diff --git a/crates/typst-layout/src/image.rs b/crates/typst-layout/src/image.rs index 84a602823..628fe10d6 100644 --- a/crates/typst-layout/src/image.rs +++ b/crates/typst-layout/src/image.rs @@ -55,6 +55,7 @@ pub fn layout_image( elem.alt(styles), engine.world, &families(styles).collect::>(), + elem.flatten_text(styles), ) .at(span)?; diff --git a/crates/typst-library/src/visualize/image/mod.rs b/crates/typst-library/src/visualize/image/mod.rs index 868a3c5b4..fddb4acb9 100644 --- a/crates/typst-library/src/visualize/image/mod.rs +++ b/crates/typst-library/src/visualize/image/mod.rs @@ -94,6 +94,12 @@ pub struct ImageElem { /// ``` #[default(ImageFit::Cover)] pub fit: ImageFit, + + /// Whether text in SVG images should be converted into paths before + /// embedding. This will result in the text becoming unselectable in + /// the output. + #[default(false)] + pub flatten_text: bool, } #[scope] @@ -246,13 +252,14 @@ impl Image { alt: Option, world: Tracked, families: &[&str], + flatten_text: bool, ) -> StrResult { let kind = match format { ImageFormat::Raster(format) => { ImageKind::Raster(RasterImage::new(data, format)?) } ImageFormat::Vector(VectorFormat::Svg) => { - ImageKind::Svg(SvgImage::with_fonts(data, world, families)?) + ImageKind::Svg(SvgImage::with_fonts(data, world, flatten_text, families)?) } }; diff --git a/crates/typst-library/src/visualize/image/svg.rs b/crates/typst-library/src/visualize/image/svg.rs index f7a498a83..6b6a1b6b2 100644 --- a/crates/typst-library/src/visualize/image/svg.rs +++ b/crates/typst-library/src/visualize/image/svg.rs @@ -22,6 +22,7 @@ pub struct SvgImage(Arc); struct Repr { data: Bytes, size: Axes, + flatten_text: bool, font_hash: u128, tree: usvg::Tree, } @@ -32,7 +33,13 @@ impl SvgImage { pub fn new(data: Bytes) -> StrResult { let tree = usvg::Tree::from_data(&data, &base_options()).map_err(format_usvg_error)?; - Ok(Self(Arc::new(Repr { data, size: tree_size(&tree), font_hash: 0, tree }))) + Ok(Self(Arc::new(Repr { + data, + size: tree_size(&tree), + font_hash: 0, + flatten_text: false, + tree, + }))) } /// Decode an SVG image with access to fonts. @@ -40,6 +47,7 @@ impl SvgImage { pub fn with_fonts( data: Bytes, world: Tracked, + flatten_text: bool, families: &[&str], ) -> StrResult { let book = world.book(); @@ -60,7 +68,13 @@ impl SvgImage { ) .map_err(format_usvg_error)?; let font_hash = resolver.into_inner().unwrap().finish(); - Ok(Self(Arc::new(Repr { data, size: tree_size(&tree), font_hash, tree }))) + Ok(Self(Arc::new(Repr { + data, + size: tree_size(&tree), + font_hash, + flatten_text, + tree, + }))) } /// The raw image data. @@ -73,6 +87,11 @@ impl SvgImage { self.0.size.x } + /// Whether the SVG's text should be flattened. + pub fn flatten_text(&self) -> bool { + self.0.flatten_text + } + /// The SVG's height in pixels. pub fn height(&self) -> f64 { self.0.size.y diff --git a/crates/typst-pdf/src/image.rs b/crates/typst-pdf/src/image.rs index 9651d31ba..bff7bfefa 100644 --- a/crates/typst-pdf/src/image.rs +++ b/crates/typst-pdf/src/image.rs @@ -208,7 +208,11 @@ fn encode_svg( ) -> Result<(Chunk, Ref), svg2pdf::ConversionError> { svg2pdf::to_chunk( svg.tree(), - svg2pdf::ConversionOptions { pdfa, ..Default::default() }, + svg2pdf::ConversionOptions { + pdfa, + embed_text: !svg.flatten_text(), + ..Default::default() + }, ) } From 4729d3d3bdf52268d143b9fe0ba6b097eae32bf8 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Sun, 8 Dec 2024 19:36:04 +0100 Subject: [PATCH 51/61] Fix language-dependant figure caption separator in outline (#5550) --- crates/typst-library/src/model/figure.rs | 1 + ...sue-5370-figure-caption-separator-outline.png | Bin 0 -> 2078 bytes tests/suite/model/figure.typ | 6 ++++++ 3 files changed, 7 insertions(+) create mode 100644 tests/ref/issue-5370-figure-caption-separator-outline.png diff --git a/crates/typst-library/src/model/figure.rs b/crates/typst-library/src/model/figure.rs index e871fbeb8..fd843ee53 100644 --- a/crates/typst-library/src/model/figure.rs +++ b/crates/typst-library/src/model/figure.rs @@ -308,6 +308,7 @@ impl Synthesize for Packed { // Fill the figure's caption. let mut caption = elem.caption(styles); if let Some(caption) = &mut caption { + caption.synthesize(engine, styles)?; caption.push_kind(kind.clone()); caption.push_supplement(supplement.clone()); caption.push_numbering(numbering.clone()); diff --git a/tests/ref/issue-5370-figure-caption-separator-outline.png b/tests/ref/issue-5370-figure-caption-separator-outline.png new file mode 100644 index 0000000000000000000000000000000000000000..a9b0d06e15ce2ff1785bcf91ef6fd038be1f4a25 GIT binary patch literal 2078 zcmV+(2;ujMP)W@V*iY0>zS1`+Kd+KVy`nnZ(&_AV;gL_~=dlu0F7YGu)$RJ0@7J2l$V z{kivE@At#QckxqsKi*J#mFG{$Ip^Mc?m7Saoafvd`qK=eN|7cOh|yxSNQ@Sv#b}Wj zEk=vP=zo=-o__P@&4h#mgL%kGGb&Fnp92RD%$zwhFE6ig+x_fwT+_({Su-MY17$Btvij%8$I==ZvQ{rZg?H?CZ{ zqLsJ$_3Jli&>$vQ4o2tX7+V88$tHSSE9 zFrj<*?znGiR@t;^Q{B3C`}FBU)!p5_Y15`e$LZ6j@7=pcZ{x?0C#AG*-J1Jy3p+bI z9OvP^efyTQ!wPveN!f zwQAKGF=9mVDltw5B<)fr!!ifj`}gm!nWzpOI*c1Pj+L=!(ISi^ke!{Kv9(^kdi(b6 z>(QeJ=crrjvbMIqeEBjN49jq{Ns}fF!`9Z8U{@LG*|TR65fP(DkJc}F_39OAnr;ac zwG&jlOpj=$#2nZ(^%j;S;WlK*5DM7crcIj{FJ6#RVq;^e1_cF`NFW@dokCQRif>kk z=pzP?A3u&qxkaLdOLzuEYhhs_d4rP~0Egn^<5@jy8$>I2p@r3^)?9XWHieEIJF@Yv zU%wth^>%gwM#v+vva;eTeRuuv;e*P1D(bl@506ErHwFd}4j(>DSTJn|2Z!0SXEQ=N zpEYY1u^bZ<)4O+XH&+)M8*2uD%jBQw)2H)P$mt9~h0e#1A16(kL_+iM@bK~R898z! z?*IJxlc&3N>(*`Cw(Z})pK|l&&FLgPJu5IM`26|vM9IR13!NN|E*C>}QD|uBnl)=w z8AnJ&^1)Ie%FoXyLU|!MdGh4br%&a}P(Hy$Vzd}75~IavFi_s!8HJbk@a4-Wg zjB0MQrfG3zAcj%>jV`mj+7H806IF`DXfaxh7Kzbfv=}WGh|yxSNQ@Sv#b}WjEk=vP zXfaxh7Kzbfv`CB=qs3^E7%fJN#Aq>Ej22ab43#Tz01jp#hEdIb$x;1v^&c0*kY7ZX zUqly)(PFenjQ+1cq5z_(8b&{U{CM!-!O*P$P=RwWm@cp;AZJRq)90{Z!@!pSZi8uJ zyc4)JuxAXW2+Run6rf90CZaJqEG&!yL?rMqCS40ihS%E@Cr*?;1Z)RL6$KzwFlmhU z10M~JlEFkluK{mUW&VbxX3d(g$rNo3j1hPorVVCi*REYa(|CJjc@j7P-AV(yK=DkS zI+e@I>{bHE``pL7}VdcNyu0SE?F3n&?fU=OZaw@yD1xaxFjg0z4OUy!!17gN$VEMRQISy+RvKy&G(b?cG(XK|w zV2E)dW6YQ_>L{XHat;_Y5)@aNY3ksu1AG)Yk(uak3$RJCkGF-@t5;Ka`SRtwdGp{0 zGArIp;I-+O^GG-u(ieWQsLA8{A>e3$+Z2+D7=(YxW57h{Z}>tZTPW9(TPefTRB6Gx zNZf^I!?mOr4ndA%CxFGq3Zn;JHrPejFgS->Gjbq&v=HEoP~M} Date: Mon, 9 Dec 2024 04:33:30 -0500 Subject: [PATCH 52/61] Fix sizing of quadratic shapes (square/circle) (#5451) Co-authored-by: Laurenz Co-authored-by: PgBiel <9021226+PgBiel@users.noreply.github.com> --- crates/typst-layout/src/shapes.rs | 67 ++++++++++++++---- .../circle-beyond-page-width-overflows.png | Bin 0 -> 620 bytes tests/ref/circle-size-beyond-default.png | Bin 0 -> 1158 bytes tests/ref/rect-size-beyond-default.png | Bin 0 -> 185 bytes ...re-overflow.png => square-no-overflow.png} | Bin tests/ref/square-overflow-forced-height.png | Bin 0 -> 468 bytes tests/ref/square-overflow-forced-width.png | Bin 0 -> 457 bytes tests/ref/square-size-beyond-default.png | Bin 0 -> 198 bytes tests/suite/visualize/circle.typ | 12 ++++ tests/suite/visualize/rect.typ | 6 ++ tests/suite/visualize/square.typ | 27 ++++++- 11 files changed, 94 insertions(+), 18 deletions(-) create mode 100644 tests/ref/circle-beyond-page-width-overflows.png create mode 100644 tests/ref/circle-size-beyond-default.png create mode 100644 tests/ref/rect-size-beyond-default.png rename tests/ref/{square-overflow.png => square-no-overflow.png} (100%) create mode 100644 tests/ref/square-overflow-forced-height.png create mode 100644 tests/ref/square-overflow-forced-width.png create mode 100644 tests/ref/square-size-beyond-default.png diff --git a/crates/typst-layout/src/shapes.rs b/crates/typst-layout/src/shapes.rs index a35021721..db9acece3 100644 --- a/crates/typst-layout/src/shapes.rs +++ b/crates/typst-layout/src/shapes.rs @@ -348,29 +348,48 @@ fn layout_shape( pod.size = crate::pad::shrink(region.size, &inset); } - // Layout the child. - frame = crate::layout_frame(engine, child, locator.relayout(), styles, pod)?; - - // If the child is a square or circle, relayout with full expansion into - // square region to make sure the result is really quadratic. + // If the shape is quadratic, we first measure it to determine its size + // and then layout with full expansion to force the aspect ratio and + // make sure it's really quadratic. if kind.is_quadratic() { - let length = frame.size().max_by_side().min(pod.size.min_by_side()); - let quad_pod = Region::new(Size::splat(length), Axes::splat(true)); - frame = crate::layout_frame(engine, child, locator, styles, quad_pod)?; + let length = match quadratic_size(pod) { + Some(length) => length, + None => { + // Take as much as the child wants, but without overflowing. + crate::layout_frame(engine, child, locator.relayout(), styles, pod)? + .size() + .max_by_side() + .min(pod.size.min_by_side()) + } + }; + + pod = Region::new(Size::splat(length), Axes::splat(true)); } + // Layout the child. + frame = crate::layout_frame(engine, child, locator, styles, pod)?; + // Apply the inset. if has_inset { crate::pad::grow(&mut frame, &inset); } } else { - // The default size that a shape takes on if it has no child and - // enough space. - let default = Size::new(Abs::pt(45.0), Abs::pt(30.0)); - let mut size = region.expand.select(region.size, default.min(region.size)); - if kind.is_quadratic() { - size = Size::splat(size.min_by_side()); - } + // The default size that a shape takes on if it has no child and no + // forced sizes. + let default = Size::new(Abs::pt(45.0), Abs::pt(30.0)).min(region.size); + + let size = if kind.is_quadratic() { + Size::splat(match quadratic_size(region) { + Some(length) => length, + None => default.min_by_side(), + }) + } else { + // For each dimension, pick the region size if forced, otherwise + // use the default size (or the region size if the default + // is too large for the region). + region.expand.select(region.size, default) + }; + frame = Frame::soft(size); } @@ -411,6 +430,24 @@ fn layout_shape( Ok(frame) } +/// Determines the forced size of a quadratic shape based on the region, if any. +/// +/// The size is forced if at least one axis is expanded because `expand` is +/// `true` for axes whose size was manually specified by the user. +fn quadratic_size(region: Region) -> Option { + if region.expand.x && region.expand.y { + // If both `width` and `height` are specified, we choose the + // smaller one. + Some(region.size.x.min(region.size.y)) + } else if region.expand.x { + Some(region.size.x) + } else if region.expand.y { + Some(region.size.y) + } else { + None + } +} + /// Creates a new rectangle as a path. pub fn clip_rect( size: Size, diff --git a/tests/ref/circle-beyond-page-width-overflows.png b/tests/ref/circle-beyond-page-width-overflows.png new file mode 100644 index 0000000000000000000000000000000000000000..941cb0092b24a4ea7548e69361bb6f4b0f9a4fc6 GIT binary patch literal 620 zcmV-y0+aoTP)%GI2?lHn00kCJ4m@}U3OsfM3Ov0J3aoB`0w0cp0^fFk0|!nx@NyCq z_;?f?xX0qai)*03=L6ut{sITq6mVd{=D>*^P+-dg4!pF=fo%aCcqKuCJ8wAfdJ-JC z_lg6b4ub>FZIj@xDhEE80tXIfIq>xeIIu30;Q1{Q+;_==E8XD0dov_h(Ma%6g#()+ zIItm+;N4jg97~blP>uw9OC;C@4qTof!9^blHY5_950YSACc&CQg40nFtj3)Nr=w20 zT)OPE2gHz^HYCcEBpB`*3GTn3EqhK|Z!-}RT=2sQpSC*-d>X*|w+_Vs0000~)y>-`na;?nq zk32d$ZxUP6S=}5R9sfIQeK}?JrW&t{qMULrN2E;p)0lZn-Sr}pzcDn)^1PH6n3J15 zxrZ-l1u7TT$A=Dg4j* z;ZBz=`|W4_$ugALINdTvR#xx#w=PERhacSPh4y#<_Er)**vHg=$ z?DxyuZp?GJcWlnD2Rn@MpRQ+ja0w8Y$kdUl>qY}a}IeU>Y|#Mj)n zWb48B?S~g!dVeb+f2VaSJ&NZb$;}pSyuC<_}JPn-~ZXqka(ztb9T#ecQ)tR+Jy%vN^(E^ zA?p5d-m)_P^ySOSe3;~9h=EHBkzyQlZPv74Pt-xFiF@7&d?EQ;F4gUe<)<;;Q;3PJB-1(f7fuh zJ!0|s63(?M!R2*8fob_dKDTY_629lJXc4rHd{DfPd#i!aHk~lt&8DnZ4bu2qH+n5R zJb$4aTh({F2FJ%w86CKL#M5PyPh5VnvDo+mD>G;M=dF=zUN~>q`D@>^=IfJBZ+m?A z@#$k>pI@$|lP#*Frv{Vh7MYA3ptK{NeXE`m#@03aEhbboFyt I=akR{03I?NnE(I) literal 0 HcmV?d00001 diff --git a/tests/ref/rect-size-beyond-default.png b/tests/ref/rect-size-beyond-default.png new file mode 100644 index 0000000000000000000000000000000000000000..1f4d80fe19b377ab56b2f06cce9e8e543971238e GIT binary patch literal 185 zcmeAS@N?(olHy`uVBq!ia0vp^6+pa%i5W;Leq#~^Qak}ZA+G=b|8Hn$sIRYgaByHu zoVFh*p5f`@7*cWT?KMTd1_K_qhqDwd4xX_PZi+g0uy8^O)7g&&zFy90&%W7RxB0)k zN#66U+Qp!=x`ATG6Hh+*w{yi!)mf`|&M*#}=%Y6IWXiv32USY1eb{c%U-7ee(#a?L kcCILH2`%wcTl`%+7@(Ksy;cUHx3vIVCg!0L4s85&!@I literal 0 HcmV?d00001 diff --git a/tests/ref/square-overflow.png b/tests/ref/square-no-overflow.png similarity index 100% rename from tests/ref/square-overflow.png rename to tests/ref/square-no-overflow.png diff --git a/tests/ref/square-overflow-forced-height.png b/tests/ref/square-overflow-forced-height.png new file mode 100644 index 0000000000000000000000000000000000000000..f7cb0ee351bc41d9b56d229cc9c7d2affb412cb4 GIT binary patch literal 468 zcmeAS@N?(olHy`uVBq!ia0vp^6+m3c0VEi%2k%` z*L7{h(rHo6(^?iNDrqcQxWcrf?U3As!>f61rzr$`p6&2%oaC6qH7P+sG*MM4aEnO? z3+L2IW9{|(542=_ZIsymzHHg~vU3Hi0+y}f5LWv$KcSRSlxel7(>=`=rv%2z zJ+|=5Q~@{LhItAM2NoZCAX{{{NE3$3Zcd7k9P_;RXZPMIR+JVYEDv8@8 zFC|Meb58dS582@+a`f4TE3aM`r9C_>nlYX6Or>JqiOcE_?cy)*P5knAGEMLu7GH36#({L(U?B^_e<{;G`4A>&QdV$XBB qiN)76IajIQSe|ny21{`K|Hf literal 0 HcmV?d00001 diff --git a/tests/ref/square-overflow-forced-width.png b/tests/ref/square-overflow-forced-width.png new file mode 100644 index 0000000000000000000000000000000000000000..4667181687ecf6ee0f9081d52fc7e6404e28acd4 GIT binary patch literal 457 zcmeAS@N?(olHy`uVBq!ia0vp^Hb8um14uALo#10(U|^i!>EaktaqI0(TW_aCiDQNK zuGv?Dyw^Hx^z`!laH#YG9RlKTc@T+lVeEB)DD-6mlT9l zT%MiOv59NsY~xdEa*6%_va;=dQNHvc&$@MwSU>CVe)&m0#_`k^FHzW>R-my>TRRctTg|ECtp#US=P>1_FlZ<5Vk z;Vg^U9Lg0we_3Hz(4S*0*U^@=!*9}(bpm^?nf{c1eKtzcqh!T8*WL^B8}6og_U3&# z9%~lMxK7M>7ifJ@#9FMsXL|Hr%^V7_ahY z;z90c3+XdSwqmlsetKBsZaQEn!ukAhh-kyN&)0WF7*!cG&U+BppJT!-Fem?5=!)EM zFUwgm(Z`dWolYIUHnH}{@vxwEALo9#^yTRe&UaVenlwCk(d#yOM(m+yJ;6d(&TsI) nwS!Mc7zREoIQW1-OywWer4uzJAMd+j2#O<5S3j3^P6P_V&($L*m)mX)KgwXoW8Ee|)xc2*ro1Y(HSYbJ$>a>)k_njX Date: Mon, 9 Dec 2024 06:55:58 -0300 Subject: [PATCH 53/61] Forbid footnote migration in pending floats (#5497) Co-authored-by: Laurenz --- crates/typst-layout/src/flow/compose.rs | 32 +++++++++++++++--- crates/typst-layout/src/flow/distribute.rs | 16 ++++++--- crates/typst-layout/src/pages/collect.rs | 2 +- crates/typst-library/src/model/footnote.rs | 2 +- ...ssue-5435-footnote-migration-in-floats.png | Bin 0 -> 448 bytes tests/suite/layout/flow/footnote.typ | 20 +++++++++++ 6 files changed, 61 insertions(+), 11 deletions(-) create mode 100644 tests/ref/issue-5435-footnote-migration-in-floats.png diff --git a/crates/typst-layout/src/flow/compose.rs b/crates/typst-layout/src/flow/compose.rs index 9650a7bc1..326456752 100644 --- a/crates/typst-layout/src/flow/compose.rs +++ b/crates/typst-layout/src/flow/compose.rs @@ -214,6 +214,13 @@ impl<'a, 'b> Composer<'a, 'b, '_, '_> { } /// Lay out the inner contents of a column. + /// + /// Pending floats and footnotes are also laid out at this step. For those, + /// however, we forbid footnote migration (moving the frame containing the + /// footnote reference if the corresponding entry doesn't fit), allowing + /// the footnote invariant to be broken, as it would require handling a + /// [`Stop::Finish`] at this point, but that is exclusively handled by the + /// distributor. fn column_contents(&mut self, regions: Regions) -> FlowResult { // Process pending footnotes. for note in std::mem::take(&mut self.work.footnotes) { @@ -222,7 +229,7 @@ impl<'a, 'b> Composer<'a, 'b, '_, '_> { // Process pending floats. for placed in std::mem::take(&mut self.work.floats) { - self.float(placed, ®ions, false)?; + self.float(placed, ®ions, false, false)?; } distribute(self, regions) @@ -236,13 +243,21 @@ impl<'a, 'b> Composer<'a, 'b, '_, '_> { /// (depending on `placed.scope`). /// /// When the float does not fit, it is queued into `work.floats`. The - /// value of `clearance` that between the float and flow content is needed - /// --- it is set if there are already distributed items. + /// value of `clearance` indicates that between the float and flow content + /// is needed --- it is set if there are already distributed items. + /// + /// The value of `migratable` determines whether footnotes within the float + /// should be allowed to prompt its migration if they don't fit in order to + /// respect the footnote invariant (entries in the same page as the + /// references), triggering [`Stop::Finish`]. This is usually `true` within + /// the distributor, as it can handle that particular flow event, and + /// `false` elsewhere. pub fn float( &mut self, placed: &'b PlacedChild<'a>, regions: &Regions, clearance: bool, + migratable: bool, ) -> FlowResult<()> { // If the float is already processed, skip it. let loc = placed.location(); @@ -291,7 +306,7 @@ impl<'a, 'b> Composer<'a, 'b, '_, '_> { } // Handle footnotes in the float. - self.footnotes(regions, &frame, need, false)?; + self.footnotes(regions, &frame, need, false, migratable)?; // Determine the float's vertical alignment. We can unwrap the inner // `Option` because `Custom(None)` is checked for during collection. @@ -326,12 +341,19 @@ impl<'a, 'b> Composer<'a, 'b, '_, '_> { /// Lays out footnotes in the `frame` if this is the root flow and there are /// any. The value of `breakable` indicates whether the element that /// produced the frame is breakable. If not, the frame is treated as atomic. + /// + /// The value of `migratable` indicates whether footnote migration should be + /// possible (at least for the first footnote found in the frame, as it is + /// forbidden for the second footnote onwards). It is usually `true` within + /// the distributor and `false` elsewhere, as the distributor can handle + /// [`Stop::Finish`] which is returned when migration is requested. pub fn footnotes( &mut self, regions: &Regions, frame: &Frame, flow_need: Abs, breakable: bool, + migratable: bool, ) -> FlowResult<()> { // Footnotes are only supported at the root level. if !self.config.root { @@ -352,7 +374,7 @@ impl<'a, 'b> Composer<'a, 'b, '_, '_> { let mut relayout = false; let mut regions = *regions; - let mut migratable = !breakable && regions.may_progress(); + let mut migratable = migratable && !breakable && regions.may_progress(); for (y, elem) in notes { // The amount of space used by the in-flow content that contains the diff --git a/crates/typst-layout/src/flow/distribute.rs b/crates/typst-layout/src/flow/distribute.rs index 5b293d352..7a1cf4264 100644 --- a/crates/typst-layout/src/flow/distribute.rs +++ b/crates/typst-layout/src/flow/distribute.rs @@ -240,7 +240,8 @@ impl<'a, 'b> Distributor<'a, 'b, '_, '_, '_> { // Handle fractionally sized blocks. if let Some(fr) = single.fr { - self.composer.footnotes(&self.regions, &frame, Abs::zero(), false)?; + self.composer + .footnotes(&self.regions, &frame, Abs::zero(), false, true)?; self.flush_tags(); self.items.push(Item::Fr(fr, Some(single))); return Ok(()); @@ -323,8 +324,13 @@ impl<'a, 'b> Distributor<'a, 'b, '_, '_, '_> { } // Handle footnotes. - self.composer - .footnotes(&self.regions, &frame, frame.height(), breakable)?; + self.composer.footnotes( + &self.regions, + &frame, + frame.height(), + breakable, + true, + )?; // Push an item for the frame. self.regions.size.y -= frame.height(); @@ -347,11 +353,13 @@ impl<'a, 'b> Distributor<'a, 'b, '_, '_, '_> { placed, &self.regions, self.items.iter().any(|item| matches!(item, Item::Frame(..))), + true, )?; self.regions.size.y -= weak_spacing; } else { let frame = placed.layout(self.composer.engine, self.regions.base())?; - self.composer.footnotes(&self.regions, &frame, Abs::zero(), true)?; + self.composer + .footnotes(&self.regions, &frame, Abs::zero(), true, true)?; self.flush_tags(); self.items.push(Item::Placed(frame, placed)); } diff --git a/crates/typst-layout/src/pages/collect.rs b/crates/typst-layout/src/pages/collect.rs index 1903d6ac5..0bbae9f4c 100644 --- a/crates/typst-layout/src/pages/collect.rs +++ b/crates/typst-layout/src/pages/collect.rs @@ -53,7 +53,7 @@ pub fn collect<'a>( // The initial styles for the next page are ours unless this is a // "boundary" pagebreak. Such a pagebreak is generated at the end of - // the scope of a page set rule to ensure a page boundary. It's + // the scope of a page set rule to ensure a page boundary. Its // styles correspond to the styles _before_ the page set rule, so we // don't want to apply it to a potential empty page. if !pagebreak.boundary(styles) { diff --git a/crates/typst-library/src/model/footnote.rs b/crates/typst-library/src/model/footnote.rs index d9971dd11..ffc78ea05 100644 --- a/crates/typst-library/src/model/footnote.rs +++ b/crates/typst-library/src/model/footnote.rs @@ -194,7 +194,7 @@ cast! { /// before any page content, typically at the very start of the document. #[elem(name = "entry", title = "Footnote Entry", Show, ShowSet)] pub struct FootnoteEntry { - /// The footnote for this entry. It's location can be used to determine + /// The footnote for this entry. Its location can be used to determine /// the footnote counter state. /// /// ```example diff --git a/tests/ref/issue-5435-footnote-migration-in-floats.png b/tests/ref/issue-5435-footnote-migration-in-floats.png new file mode 100644 index 0000000000000000000000000000000000000000..672a5af8354377b57d01f583670e3fe56ccfc6e9 GIT binary patch literal 448 zcmV;x0YCnUP)0{{R382;U20002GP)t-s|Ns90 z007kCU#jxWdE7zro3yoS>YZrkI(XaB_Nje1yWp%!-VZa&&z4_4T~I#>dCU zXlQ8G*49^7S3EpCsi~>7w6xRH(|~}0PEJnZ;^Iq7OH@=;Nl8hptgMldk%fhYm6esg zzP`c1!NkPG>FMe8^z`-h_xt<%i;Ii%^Ye!-V1EDr0KrK_K~#9!?b%hX0x%Rs(eqlK zyW{Te&itpv4EO>LBzsL@CvDn~dj$Xh000000010rOgMP}$M9Jx$>O72ltn1yW$}>B z$s&Y6_&j2a{3!r%;dTe|NW)mXzZELKWehD qgr78>Tf)yTuL(IMZh74RF4i6h93@VhU)O{H0000, C @fn, D @fn, E @fn. --- issue-5256-multiple-footnotes-in-footnote --- // Test whether all footnotes inside another footnote are listed. #footnote[#footnote[A]#footnote[B]#footnote[C]] + +--- issue-5435-footnote-migration-in-floats --- +// Test that a footnote should not prompt migration when in a float that was +// queued to the next page (due to the float being too large), even if the +// footnote does not fit, breaking the footnote invariant. +#set page(height: 50pt) + +#place( + top, + float: true, + { + v(100pt) + footnote[a] + } +) +#place( + top, + float: true, + footnote[b] +) From f960fe60121ff7d03dfc5db429b756d7efd0cc1f Mon Sep 17 00:00:00 2001 From: Laurenz Date: Mon, 9 Dec 2024 11:43:48 +0100 Subject: [PATCH 54/61] Bump hashbrown (#5552) --- Cargo.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0d9d80b57..94ce026e9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -890,9 +890,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.0" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" [[package]] name = "hayagriva" @@ -1169,7 +1169,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", - "hashbrown 0.15.0", + "hashbrown 0.15.2", "rayon", "serde", ] From bb0c8140950b3eec020a4f0147bbc4ea65f3952a Mon Sep 17 00:00:00 2001 From: wznmickey Date: Mon, 9 Dec 2024 05:56:42 -0500 Subject: [PATCH 55/61] Forbid base prefix for numbers with a unit (#5548) Co-authored-by: Laurenz --- crates/typst-syntax/src/lexer.rs | 6 ++++++ tests/suite/layout/length.typ | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/crates/typst-syntax/src/lexer.rs b/crates/typst-syntax/src/lexer.rs index 1314016fa..358c25b20 100644 --- a/crates/typst-syntax/src/lexer.rs +++ b/crates/typst-syntax/src/lexer.rs @@ -766,6 +766,12 @@ impl Lexer<'_> { return self.error(eco_format!("invalid number suffix: {}", suffix)); } + if base != 10 { + let kind = self.error(eco_format!("invalid base-{base} prefix")); + self.hint("numbers with a unit cannot have a base prefix"); + return kind; + } + SyntaxKind::Numeric } diff --git a/tests/suite/layout/length.typ b/tests/suite/layout/length.typ index 5ba70072d..71d79da9a 100644 --- a/tests/suite/layout/length.typ +++ b/tests/suite/layout/length.typ @@ -74,3 +74,8 @@ // Hint: 2-24 use `length.to-absolute()` to resolve its em component (requires context) // Hint: 2-24 or use `length.abs.inches()` instead to ignore its em component #(4.5em + 6in).inches() + +--- issue-5519-length-base --- +// Error: 2-9 invalid base-2 prefix +// Hint: 2-9 numbers with a unit cannot have a base prefix +#0b100pt From 17f20c6944d569d5f0bb57caee37d9f208d87d0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20F=C3=A4rber?= <01mf02@gmail.com> Date: Tue, 10 Dec 2024 10:57:22 +0100 Subject: [PATCH 56/61] Basic HTML pretty-printing (#5533) Co-authored-by: Laurenz --- crates/typst-html/src/encode.rs | 48 ++++++++++++++++++++++-- crates/typst-library/src/html/dom.rs | 55 +++++++++++++++++++++++++++- crates/typst-realize/src/lib.rs | 2 +- 3 files changed, 100 insertions(+), 5 deletions(-) diff --git a/crates/typst-html/src/encode.rs b/crates/typst-html/src/encode.rs index d4ff83d67..b87b0e1d6 100644 --- a/crates/typst-html/src/encode.rs +++ b/crates/typst-html/src/encode.rs @@ -8,14 +8,30 @@ use typst_syntax::Span; /// Encodes an HTML document into a string. pub fn html(document: &HtmlDocument) -> SourceResult { - let mut w = Writer { buf: String::new() }; + let mut w = Writer { pretty: true, ..Writer::default() }; w.buf.push_str(""); + write_indent(&mut w); write_element(&mut w, &document.root)?; Ok(w.buf) } +#[derive(Default)] struct Writer { buf: String, + /// current indentation level + level: usize, + /// pretty printing enabled? + pretty: bool, +} + +/// Write a newline and indent, if pretty printing is enabled. +fn write_indent(w: &mut Writer) { + if w.pretty { + w.buf.push('\n'); + for _ in 0..w.level { + w.buf.push_str(" "); + } + } } /// Encode an HTML node into the writer. @@ -67,9 +83,30 @@ fn write_element(w: &mut Writer, element: &HtmlElement) -> SourceResult<()> { return Ok(()); } - for node in &element.children { - write_node(w, node)?; + let pretty = w.pretty; + if !element.children.is_empty() { + w.pretty &= is_pretty(element); + let mut indent = w.pretty; + + w.level += 1; + for c in &element.children { + let pretty_child = match c { + HtmlNode::Tag(_) => continue, + HtmlNode::Element(element) => is_pretty(element), + HtmlNode::Text(..) | HtmlNode::Frame(_) => false, + }; + + if core::mem::take(&mut indent) || pretty_child { + write_indent(w); + } + write_node(w, c)?; + indent = pretty_child; + } + w.level -= 1; + + write_indent(w) } + w.pretty = pretty; w.buf.push_str(" SourceResult<()> { Ok(()) } +/// Whether the element should be pretty-printed. +fn is_pretty(element: &HtmlElement) -> bool { + tag::is_block_by_default(element.tag) || matches!(element.tag, tag::meta) +} + /// Escape a character. fn write_escape(w: &mut Writer, c: char) -> StrResult<()> { // See diff --git a/crates/typst-library/src/html/dom.rs b/crates/typst-library/src/html/dom.rs index ee94279f2..3d558fb0f 100644 --- a/crates/typst-library/src/html/dom.rs +++ b/crates/typst-library/src/html/dom.rs @@ -470,6 +470,59 @@ pub mod tag { wbr } + /// Whether nodes with the tag have the CSS property `display: block` by + /// default. + /// + /// If this is true, then pretty-printing can insert spaces around such + /// nodes and around the contents of such nodes. + /// + /// However, when users change the properties of such tags via CSS, the + /// insertion of whitespace may actually impact the visual output; for + /// example, shows how + /// adding CSS rules to `

` can make it sensitive to whitespace. In such + /// cases, users should disable pretty-printing. + pub fn is_block_by_default(tag: HtmlTag) -> bool { + matches!( + tag, + self::html + | self::head + | self::body + | self::article + | self::aside + | self::h1 + | self::h2 + | self::h3 + | self::h4 + | self::h5 + | self::h6 + | self::hgroup + | self::nav + | self::section + | self::dd + | self::dl + | self::dt + | self::menu + | self::ol + | self::ul + | self::address + | self::blockquote + | self::dialog + | self::div + | self::fieldset + | self::figure + | self::figcaption + | self::footer + | self::form + | self::header + | self::hr + | self::legend + | self::main + | self::p + | self::pre + | self::search + ) + } + /// Whether the element is inline-level as opposed to being block-level. /// /// Not sure whether this distinction really makes sense. But we somehow @@ -480,7 +533,7 @@ pub mod tag { /// /// /// - pub fn is_inline(tag: HtmlTag) -> bool { + pub fn is_inline_by_default(tag: HtmlTag) -> bool { matches!( tag, self::abbr diff --git a/crates/typst-realize/src/lib.rs b/crates/typst-realize/src/lib.rs index fd43e8304..6ab6d81c5 100644 --- a/crates/typst-realize/src/lib.rs +++ b/crates/typst-realize/src/lib.rs @@ -823,7 +823,7 @@ static PAR: GroupingRule = GroupingRule { RealizationKind::HtmlDocument(_) | RealizationKind::HtmlFragment ) && content .to_packed::() - .is_some_and(|elem| tag::is_inline(elem.tag))) + .is_some_and(|elem| tag::is_inline_by_default(elem.tag))) }, inner: |content| content.elem() == SpaceElem::elem(), interrupt: |elem| elem == ParElem::elem() || elem == AlignElem::elem(), From a5ade167dd24fe3efdb3391dcb72a09b4dd1a268 Mon Sep 17 00:00:00 2001 From: Ian Wrzesinski <133046678+wrzian@users.noreply.github.com> Date: Wed, 11 Dec 2024 06:28:24 -0500 Subject: [PATCH 57/61] More `CapturesVisitor` tests (#5506) --- crates/typst-eval/src/call.rs | 113 ++++++++++++++++++++++---------- crates/typst-ide/src/tooltip.rs | 15 +++++ 2 files changed, 94 insertions(+), 34 deletions(-) diff --git a/crates/typst-eval/src/call.rs b/crates/typst-eval/src/call.rs index 513d1dd2c..fc934cef5 100644 --- a/crates/typst-eval/src/call.rs +++ b/crates/typst-eval/src/call.rs @@ -593,14 +593,8 @@ mod tests { use super::*; #[track_caller] - fn test(text: &str, result: &[&str]) { - let mut scopes = Scopes::new(None); - scopes.top.define("f", 0); - scopes.top.define("x", 0); - scopes.top.define("y", 0); - scopes.top.define("z", 0); - - let mut visitor = CapturesVisitor::new(Some(&scopes), Capturer::Function); + fn test(scopes: &Scopes, text: &str, result: &[&str]) { + let mut visitor = CapturesVisitor::new(Some(scopes), Capturer::Function); let root = parse(text); visitor.visit(&root); @@ -613,44 +607,95 @@ mod tests { #[test] fn test_captures() { + let mut scopes = Scopes::new(None); + scopes.top.define("f", 0); + scopes.top.define("x", 0); + scopes.top.define("y", 0); + scopes.top.define("z", 0); + let s = &scopes; + // Let binding and function definition. - test("#let x = x", &["x"]); - test("#let x; #(x + y)", &["y"]); - test("#let f(x, y) = x + y", &[]); - test("#let f(x, y) = f", &[]); - test("#let f = (x, y) => f", &["f"]); + test(s, "#let x = x", &["x"]); + test(s, "#let x; #(x + y)", &["y"]); + test(s, "#let f(x, y) = x + y", &[]); + test(s, "#let f(x, y) = f", &[]); + test(s, "#let f = (x, y) => f", &["f"]); // Closure with different kinds of params. - test("#((x, y) => x + z)", &["z"]); - test("#((x: y, z) => x + z)", &["y"]); - test("#((..x) => x + y)", &["y"]); - test("#((x, y: x + z) => x + y)", &["x", "z"]); - test("#{x => x; x}", &["x"]); + test(s, "#((x, y) => x + z)", &["z"]); + test(s, "#((x: y, z) => x + z)", &["y"]); + test(s, "#((..x) => x + y)", &["y"]); + test(s, "#((x, y: x + z) => x + y)", &["x", "z"]); + test(s, "#{x => x; x}", &["x"]); // Show rule. - test("#show y: x => x", &["y"]); - test("#show y: x => x + z", &["y", "z"]); - test("#show x: x => x", &["x"]); + test(s, "#show y: x => x", &["y"]); + test(s, "#show y: x => x + z", &["y", "z"]); + test(s, "#show x: x => x", &["x"]); // For loop. - test("#for x in y { x + z }", &["y", "z"]); - test("#for (x, y) in y { x + y }", &["y"]); - test("#for x in y {} #x", &["x", "y"]); + test(s, "#for x in y { x + z }", &["y", "z"]); + test(s, "#for (x, y) in y { x + y }", &["y"]); + test(s, "#for x in y {} #x", &["x", "y"]); // Import. - test("#import z: x, y", &["z"]); - test("#import x + y: x, y, z", &["x", "y"]); + test(s, "#import z: x, y", &["z"]); + test(s, "#import x + y: x, y, z", &["x", "y"]); // Blocks. - test("#{ let x = 1; { let y = 2; y }; x + y }", &["y"]); - test("#[#let x = 1]#x", &["x"]); + test(s, "#{ let x = 1; { let y = 2; y }; x + y }", &["y"]); + test(s, "#[#let x = 1]#x", &["x"]); // Field access. - test("#foo(body: 1)", &[]); - test("#(body: 1)", &[]); - test("#(body = 1)", &[]); - test("#(body += y)", &["y"]); - test("#{ (body, a) = (y, 1) }", &["y"]); - test("#(x.at(y) = 5)", &["x", "y"]) + test(s, "#x.y.f(z)", &["x", "z"]); + + // Parenthesized expressions. + test(s, "#f(x: 1)", &["f"]); + test(s, "#(x: 1)", &[]); + test(s, "#(x = 1)", &["x"]); + test(s, "#(x += y)", &["x", "y"]); + test(s, "#{ (x, z) = (y, 1) }", &["x", "y", "z"]); + test(s, "#(x.at(y) = 5)", &["x", "y"]); + } + + #[test] + fn test_captures_in_math() { + let mut scopes = Scopes::new(None); + scopes.top.define("f", 0); + scopes.top.define("x", 0); + scopes.top.define("y", 0); + scopes.top.define("z", 0); + // Multi-letter variables are required for math. + scopes.top.define("foo", 0); + scopes.top.define("bar", 0); + scopes.top.define("x-bar", 0); + scopes.top.define("x_bar", 0); + let s = &scopes; + + // Basic math identifier differences. + test(s, "$ x f(z) $", &[]); // single letters not captured. + test(s, "$ #x #f(z) $", &["f", "x", "z"]); + test(s, "$ foo f(bar) $", &["bar", "foo"]); + test(s, "$ #foo[#$bar$] $", &["bar", "foo"]); + test(s, "$ #let foo = x; foo $", &["x"]); + + // Math idents don't have dashes/underscores + test(s, "$ x-y x_y foo-x x_bar $", &["bar", "foo"]); + test(s, "$ #x-bar #x_bar $", &["x-bar", "x_bar"]); + + // Named-params. + test(s, "$ foo(bar: y) $", &["foo"]); + // This should be updated when we improve named-param parsing: + test(s, "$ foo(x-y: 1, bar-z: 2) $", &["bar", "foo"]); + + // Field access in math. + test(s, "$ foo.bar $", &["foo"]); + test(s, "$ foo.x $", &["foo"]); + test(s, "$ x.foo $", &["foo"]); + test(s, "$ foo . bar $", &["bar", "foo"]); + test(s, "$ foo.x.y.bar(z) $", &["foo"]); + test(s, "$ foo.x-bar $", &["bar", "foo"]); + test(s, "$ foo.x_bar $", &["bar", "foo"]); + test(s, "$ #x_bar.x-bar $", &["x_bar"]); } } diff --git a/crates/typst-ide/src/tooltip.rs b/crates/typst-ide/src/tooltip.rs index adfbeda50..4eaaeda1f 100644 --- a/crates/typst-ide/src/tooltip.rs +++ b/crates/typst-ide/src/tooltip.rs @@ -337,6 +337,21 @@ mod tests { fn test_tooltip_closure() { test("#let f(x) = x + y", 11, Side::Before) .must_be_text("This closure captures `y`"); + // Same tooltip if `y` is defined first. + test("#let y = 10; #let f(x) = x + y", 24, Side::Before) + .must_be_text("This closure captures `y`"); + // Names are sorted. + test("#let f(x) = x + y + z + a", 11, Side::Before) + .must_be_text("This closure captures `a`, `y`, and `z`"); + // Names are de-duplicated. + test("#let f(x) = x + y + z + y", 11, Side::Before) + .must_be_text("This closure captures `y` and `z`"); + // With arrow syntax. + test("#let f = (x) => x + y", 15, Side::Before) + .must_be_text("This closure captures `y`"); + // No recursion with arrow syntax. + test("#let f = (x) => x + y + f", 13, Side::After) + .must_be_text("This closure captures `f` and `y`"); } #[test] From ef4fc040b279104f6c95a5ea2f9a9d10fb0e9019 Mon Sep 17 00:00:00 2001 From: Ian Wrzesinski <133046678+wrzian@users.noreply.github.com> Date: Wed, 11 Dec 2024 06:31:04 -0500 Subject: [PATCH 58/61] Improve raw trimming (#5541) --- crates/typst-syntax/src/lexer.rs | 109 +++++++++++++++++++++++-------- tests/ref/raw-empty-lines.png | Bin 0 -> 92 bytes tests/suite/text/raw.typ | 55 +++++++++++++++- 3 files changed, 137 insertions(+), 27 deletions(-) create mode 100644 tests/ref/raw-empty-lines.png diff --git a/crates/typst-syntax/src/lexer.rs b/crates/typst-syntax/src/lexer.rs index 358c25b20..b0cb5c464 100644 --- a/crates/typst-syntax/src/lexer.rs +++ b/crates/typst-syntax/src/lexer.rs @@ -251,8 +251,9 @@ impl Lexer<'_> { } } - /// Lex an entire raw segment at once. This is a convenience to avoid going - /// to and from the parser for each raw section. + /// We parse entire raw segments in the lexer as a convenience to avoid + /// going to and from the parser for each raw section. See comments in + /// [`Self::blocky_raw`] and [`Self::inline_raw`] for specific details. fn raw(&mut self) -> (SyntaxKind, SyntaxNode) { let start = self.s.cursor() - 1; @@ -313,6 +314,35 @@ impl Lexer<'_> { (SyntaxKind::Raw, SyntaxNode::inner(SyntaxKind::Raw, nodes)) } + /// Raw blocks parse a language tag, have smart behavior for trimming + /// whitespace in the start/end lines, and trim common leading whitespace + /// from all other lines as the "dedent". The exact behavior is described + /// below. + /// + /// ### The initial line: + /// - A valid Typst identifier immediately following the opening delimiter + /// is parsed as the language tag. + /// - We check the rest of the line and if all characters are whitespace, + /// trim it. Otherwise we trim a single leading space if present. + /// - If more trimmed characters follow on future lines, they will be + /// merged into the same trimmed element. + /// - If we didn't trim the entire line, the rest is kept as text. + /// + /// ### Inner lines: + /// - We determine the "dedent" by iterating over the lines. The dedent is + /// the minimum number of leading whitespace characters (not bytes) before + /// each line that has any non-whitespace characters. + /// - The opening delimiter's line does not contribute to the dedent, but + /// the closing delimiter's line does (even if that line is entirely + /// whitespace up to the delimiter). + /// - We then trim the newline and dedent characters of each line, and add a + /// (potentially empty) text element of all remaining characters. + /// + /// ### The final line: + /// - If the last line is entirely whitespace, it is trimmed. + /// - Otherwise its text is kept like an inner line. However, if the last + /// non-whitespace character of the final line is a backtick, then one + /// ascii space (if present) is trimmed from the end. fn blocky_raw(&mut self, inner_end: usize, mut push_raw: F) where F: FnMut(SyntaxKind, &Scanner), @@ -323,12 +353,10 @@ impl Lexer<'_> { push_raw(SyntaxKind::RawLang, &self.s); } - // Determine inner content between backticks. - self.s.eat_if(' '); - let inner = self.s.to(inner_end); + // The rest of the function operates on the lines between the backticks. + let mut lines = split_newlines(self.s.to(inner_end)); // Determine dedent level. - let mut lines = split_newlines(inner); let dedent = lines .iter() .skip(1) @@ -339,35 +367,61 @@ impl Lexer<'_> { .min() .unwrap_or(0); - // Trim single space in last line if text ends with a backtick. The last - // line is the one directly before the closing backticks and if it is - // just whitespace, it will be completely trimmed below. - if inner.trim_end().ends_with('`') { - if let Some(last) = lines.last_mut() { + // Trim whitespace from the last line. Will be added as a `RawTrimmed` + // kind by the check for `self.s.cursor() != inner_end` below. + if lines.last().is_some_and(|last| last.chars().all(char::is_whitespace)) { + lines.pop(); + } else if let Some(last) = lines.last_mut() { + // If last line ends in a backtick, try to trim a single space. This + // check must happen before we add the first line since the last and + // first lines might be the same. + if last.trim_end().ends_with('`') { *last = last.strip_suffix(' ').unwrap_or(last); } } - let is_whitespace = |line: &&str| line.chars().all(char::is_whitespace); - let starts_whitespace = lines.first().is_some_and(is_whitespace); - let ends_whitespace = lines.last().is_some_and(is_whitespace); - let mut lines = lines.into_iter(); - let mut skipped = false; - // Trim whitespace + newline at start. - if starts_whitespace { - self.s.advance(lines.next().unwrap().len()); - skipped = true; - } - // Trim whitespace + newline at end. - if ends_whitespace { - lines.next_back(); + // Handle the first line: trim if all whitespace, or trim a single space + // at the start. Note that the first line does not affect the dedent + // value. + if let Some(first_line) = lines.next() { + if first_line.chars().all(char::is_whitespace) { + self.s.advance(first_line.len()); + // This is the only spot we advance the scanner, but don't + // immediately call `push_raw`. But the rest of the function + // ensures we will always add this text to a `RawTrimmed` later. + debug_assert!(self.s.cursor() != inner_end); + // A proof by cases follows: + // # First case: The loop runs + // If the loop runs, there must be a newline following, so + // `cursor != inner_end`. And if the loop runs, the first thing + // it does is add a trimmed element. + // # Second case: The final if-statement runs. + // To _not_ reach the loop from here, we must have only one or + // two lines: + // 1. If one line, we cannot be here, because the first and last + // lines are the same, so this line will have been removed by + // the check for the last line being all whitespace. + // 2. If two lines, the loop will run unless the last is fully + // whitespace, but if it is, it will have been popped, then + // the final if-statement will run because the text removed + // by the last line must include at least a newline, so + // `cursor != inner_end` here. + } else { + let line_end = self.s.cursor() + first_line.len(); + if self.s.eat_if(' ') { + // Trim a single space after the lang tag on the first line. + push_raw(SyntaxKind::RawTrimmed, &self.s); + } + // We know here that the rest of the line is non-empty. + self.s.jump(line_end); + push_raw(SyntaxKind::Text, &self.s); + } } // Add lines. - for (i, line) in lines.enumerate() { - let dedent = if i == 0 && !skipped { 0 } else { dedent }; + for line in lines { let offset: usize = line.chars().take(dedent).map(char::len_utf8).sum(); self.s.eat_newline(); self.s.advance(offset); @@ -383,6 +437,9 @@ impl Lexer<'_> { } } + /// Inline raw text is split on lines with non-newlines as `Text` kinds and + /// newlines as `RawTrimmed`. Inline raw text does not dedent the text, all + /// non-newline whitespace is kept. fn inline_raw(&mut self, inner_end: usize, mut push_raw: F) where F: FnMut(SyntaxKind, &Scanner), diff --git a/tests/ref/raw-empty-lines.png b/tests/ref/raw-empty-lines.png new file mode 100644 index 0000000000000000000000000000000000000000..dcf0d926142a1089d82c8dedb3803e8686c522e8 GIT binary patch literal 92 zcmeAS@N?(olHy`uVBq!ia0vp^6+mpr1SA;hUTARxDP>O=$B>F!$v^tV4SKvDPx!Z$ oi#@_#%N7WIqhDT})M=B+V1NDiu_LRjOF-H@UHx3vIVCg!02g{2TmS$7 literal 0 HcmV?d00001 diff --git a/tests/suite/text/raw.typ b/tests/suite/text/raw.typ index fa9e630fa..1ba216302 100644 --- a/tests/suite/text/raw.typ +++ b/tests/suite/text/raw.typ @@ -282,10 +282,40 @@ int main() { --- raw-blocky --- // Test various raw parsing edge cases. + #let empty = ( name: "empty", input: ``, text: "", + block: false, +) + +#let empty-spaces = ( + name: "empty-spaces", + input: ``` ```, + text: "", + block: false, +) + +#let empty-newlines = ( + name: "empty-newlines", + input: ``` + + +```, + text: "\n", + block: true, +) + +#let newlines-backtick = ( + name: "newlines-backtick", + input: ``` + +` + +```, + text: "\n`\n", + block: true, ) #let backtick = ( @@ -423,8 +453,18 @@ test block: true, ) +#let extra-first-line-ws = ( + name: "extra-first-line-ws", + input: eval("``` \n```"), + text: "", + block: true, +) + #let cases = ( empty, + empty-spaces, + empty-newlines, + newlines-backtick, backtick, lang-backtick, lang-space, @@ -438,10 +478,11 @@ test blocky-dedent-lastline2, blocky-tab, blocky-tab-dedent, + extra-first-line-ws, ) #for c in cases { - let block = c.at("block", default: false) + let block = c.block assert.eq(c.text, c.input.text, message: "in point " + c.name + ", expect " + repr(c.text) + ", got " + repr(c.input.text) + "") assert.eq(block, c.input.block, message: "in point " + c.name + ", expect " + repr(block) + ", got " + repr(c.input.block) + "") } @@ -556,6 +597,18 @@ print(y) --- issue-3601-empty-raw --- // Test that empty raw block with `typ` language doesn't cause a crash. ```typ +``` + +--- raw-empty-lines --- +// Test raw with multiple empty lines. + +#show raw: block.with(width: 100%, fill: gray) + +``` + + + + ``` --- issue-3841-tabs-in-raw-type-code --- From 5e0e58d26ef656836aae8d16477bb059e97883a3 Mon Sep 17 00:00:00 2001 From: Ian Wrzesinski <133046678+wrzian@users.noreply.github.com> Date: Wed, 11 Dec 2024 06:33:59 -0500 Subject: [PATCH 59/61] Add number-syntax edge case tests (#5560) --- tests/ref/double-percent.png | Bin 0 -> 496 bytes tests/suite/foundations/float.typ | 20 ++++++++++++++++++++ tests/suite/layout/length.typ | 27 +++++++++++++++++++++++++++ tests/suite/layout/relative.typ | 8 ++++++++ 4 files changed, 55 insertions(+) create mode 100644 tests/ref/double-percent.png diff --git a/tests/ref/double-percent.png b/tests/ref/double-percent.png new file mode 100644 index 0000000000000000000000000000000000000000..61a0d6143cd1615b0fa0051d0442b32be6fd2491 GIT binary patch literal 496 zcmV+=2f`SbJhrlzKag@yFj3(}_~!2H?CkmK@#wC} z)^MQTk+#!kn$&BZ+=Z^zaiQj?#p$ujwY9a5j*hIXtmfwC)^eiSeW~fQ&FJXp>gwu| zk&(y8$MMbHoSdBU^75mjqwT@g;g-6}%F6cO>gu-7`||eg?(V$2yr7_<{{H@khK6)> zbh5It#KgqGDRSzy&iUx@&|Q?$VwdpA+xFh+_xJby`~2XNx8aw%>a@-K@b&-x{>PGM zX#fBKrb$FWRCwC$(?t%$KoCUHa+sN!nVFdxY~TMVk>bQRm`IWOt-g9ws|F#2{4FP1O>0000 Date: Wed, 11 Dec 2024 16:46:10 +0100 Subject: [PATCH 60/61] Fix crash due to consecutive weak spacing (#5562) --- crates/typst-layout/src/inline/collect.rs | 37 +++++++++--------- crates/typst-layout/src/inline/linebreak.rs | 2 + ...ue-5244-consecutive-weak-space-heading.png | Bin 0 -> 346 bytes .../ref/issue-5244-consecutive-weak-space.png | Bin 0 -> 194 bytes ...issue-5253-consecutive-weak-space-math.png | Bin 0 -> 138 bytes tests/suite/layout/spacing.typ | 18 +++++++++ 6 files changed, 38 insertions(+), 19 deletions(-) create mode 100644 tests/ref/issue-5244-consecutive-weak-space-heading.png create mode 100644 tests/ref/issue-5244-consecutive-weak-space.png create mode 100644 tests/ref/issue-5253-consecutive-weak-space-math.png diff --git a/crates/typst-layout/src/inline/collect.rs b/crates/typst-layout/src/inline/collect.rs index fbcddee5c..23e82c417 100644 --- a/crates/typst-layout/src/inline/collect.rs +++ b/crates/typst-layout/src/inline/collect.rs @@ -256,8 +256,7 @@ impl<'a> Collector<'a> { } fn push_text(&mut self, text: &str, styles: StyleChain<'a>) { - self.full.push_str(text); - self.push_segment(Segment::Text(text.len(), styles)); + self.build_text(styles, |full| full.push_str(text)); } fn build_text(&mut self, styles: StyleChain<'a>, f: F) @@ -266,33 +265,33 @@ impl<'a> Collector<'a> { { let prev = self.full.len(); f(&mut self.full); - let len = self.full.len() - prev; - self.push_segment(Segment::Text(len, styles)); + let segment_len = self.full.len() - prev; + + // Merge adjacent text segments with the same styles. + if let Some(Segment::Text(last_len, last_styles)) = self.segments.last_mut() { + if *last_styles == styles { + *last_len += segment_len; + return; + } + } + + self.segments.push(Segment::Text(segment_len, styles)); } fn push_item(&mut self, item: Item<'a>) { - self.full.push_str(item.textual()); - self.push_segment(Segment::Item(item)); - } - - fn push_segment(&mut self, segment: Segment<'a>) { - match (self.segments.last_mut(), &segment) { - // Merge adjacent text segments with the same styles. - (Some(Segment::Text(last_len, last_styles)), Segment::Text(len, styles)) - if *last_styles == *styles => - { - *last_len += *len; - } - + match (self.segments.last_mut(), &item) { // Merge adjacent weak spacing by taking the maximum. ( Some(Segment::Item(Item::Absolute(prev_amount, true))), - Segment::Item(Item::Absolute(amount, true)), + Item::Absolute(amount, true), ) => { *prev_amount = (*prev_amount).max(*amount); } - _ => self.segments.push(segment), + _ => { + self.full.push_str(item.textual()); + self.segments.push(Segment::Item(item)); + } } } } diff --git a/crates/typst-layout/src/inline/linebreak.rs b/crates/typst-layout/src/inline/linebreak.rs index 236d68921..7b66fcdb4 100644 --- a/crates/typst-layout/src/inline/linebreak.rs +++ b/crates/typst-layout/src/inline/linebreak.rs @@ -971,11 +971,13 @@ where } /// Estimates the metrics for the line spanned by the range. + #[track_caller] fn estimate(&self, range: Range) -> T { self.get(range.end) - self.get(range.start) } /// Get the metric at the given byte position. + #[track_caller] fn get(&self, index: usize) -> T { match index.checked_sub(1) { None => T::default(), diff --git a/tests/ref/issue-5244-consecutive-weak-space-heading.png b/tests/ref/issue-5244-consecutive-weak-space-heading.png new file mode 100644 index 0000000000000000000000000000000000000000..c1ef792494783488b66a9fbf529e0fe999faf73f GIT binary patch literal 346 zcmV-g0j2(lP)oHuy~s?qsV*&_bGyBj{1L_kE8wX|LctLbg`Jx z7Mx;$Hb^|?thC$huoh3|IL|gp3%hOpI?7``~LqUEX%wAF+M*5 sF+be<11>;DD~VByM=c(;cu-gj0Q#vf!c~}!1^@s607*qoM6N<$g0-fy;s5{u literal 0 HcmV?d00001 diff --git a/tests/ref/issue-5244-consecutive-weak-space.png b/tests/ref/issue-5244-consecutive-weak-space.png new file mode 100644 index 0000000000000000000000000000000000000000..7a102ddfb66053fd1a3e0f94a88e52bcea4856b1 GIT binary patch literal 194 zcmeAS@N?(olHy`uVBq!ia0vp^6+kS_0VEhE<%|3RQk|YIjv*Ddl7HAcG$dYm6xi*q zE4Ouqm00y2reNc(Vq4xWUw(ACv3m2%`d9zuqc2{5bg#cmpzYP?liV*dKECd0PTPJ! zXQ%Ae!2K`N3*T>CefiY^o!jv*Ddl7HAcG$dYm6xi*q zE4Q`cN)nguzuncJ)OSVJ?3U))*L1_$?|q(>fc*bBThlCunmaq**Bxp2QQ`X1X?ykJ fo0%|;608gidztzjdZ$;N0a@bd>gTe~DWM4fGX65$ literal 0 HcmV?d00001 diff --git a/tests/suite/layout/spacing.typ b/tests/suite/layout/spacing.typ index f59389956..d5cd122c4 100644 --- a/tests/suite/layout/spacing.typ +++ b/tests/suite/layout/spacing.typ @@ -58,3 +58,21 @@ This is the first line \ #h(2cm, weak: true) A new line // Non-weak-spacing, on the other hand, is not removed. This is the first line \ #h(2cm, weak: false) A new line + +--- issue-5244-consecutive-weak-space --- +#set par(linebreaks: "optimized") +#{ + [A] + h(0.3em, weak: true) + h(0.3em, weak: true) + [B] +} + +--- issue-5244-consecutive-weak-space-heading --- +#set par(justify: true) +#set heading(numbering: "I.") + += #h(0.3em, weak: true) test + +--- issue-5253-consecutive-weak-space-math --- +$= thin thin$ a From a3ad0a0bba3ce3abb1d8ed84656f54cc2d74be25 Mon Sep 17 00:00:00 2001 From: Joshua Gawley <16921823+joshuagawley@users.noreply.github.com> Date: Fri, 13 Dec 2024 11:26:52 +0000 Subject: [PATCH 61/61] Document new counting symbols (#5568) --- crates/typst-library/src/model/numbering.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/typst-library/src/model/numbering.rs b/crates/typst-library/src/model/numbering.rs index b7f27bb9b..4e2fe4579 100644 --- a/crates/typst-library/src/model/numbering.rs +++ b/crates/typst-library/src/model/numbering.rs @@ -60,8 +60,9 @@ pub fn numbering( /// Defines how the numbering works. /// /// **Counting symbols** are `1`, `a`, `A`, `i`, `I`, `α`, `Α`, `一`, `壹`, - /// `あ`, `い`, `ア`, `イ`, `א`, `가`, `ㄱ`, `*`, `①`, and `⓵`. They are - /// replaced by the number in the sequence, preserving the original case. + /// `あ`, `い`, `ア`, `イ`, `א`, `가`, `ㄱ`, `*`, `١`, `۱`, `१`, `১`, `ক`, + /// `①`, and `⓵`. They are replaced by the number in the sequence, + /// preserving the original case. /// /// The `*` character means that symbols should be used to count, in the /// order of `*`, `†`, `‡`, `§`, `¶`, `‖`. If there are more than six