From d54544297beba0a762bee9bc731baab96e4d7250 Mon Sep 17 00:00:00 2001 From: +merlan #flirora Date: Wed, 25 Jun 2025 12:58:40 -0400 Subject: [PATCH 01/49] Minor fixes to doc comments (#6500) --- crates/typst-layout/src/inline/line.rs | 6 +++++- crates/typst-library/src/model/bibliography.rs | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/crates/typst-layout/src/inline/line.rs b/crates/typst-layout/src/inline/line.rs index 659d33f4a..f05189275 100644 --- a/crates/typst-layout/src/inline/line.rs +++ b/crates/typst-layout/src/inline/line.rs @@ -640,7 +640,7 @@ impl<'a> Items<'a> { self.0.push(entry.into()); } - /// Iterate over the items + /// Iterate over the items. pub fn iter(&self) -> impl Iterator> { self.0.iter().map(|item| &**item) } @@ -698,6 +698,10 @@ impl Debug for Items<'_> { } /// A reference to or a boxed item. +/// +/// This is conceptually similar to a [`Cow<'a, Item<'a>>`][std::borrow::Cow], +/// but we box owned items since an [`Item`] is much bigger than +/// a box. pub enum ItemEntry<'a> { Ref(&'a Item<'a>), Box(Box>), diff --git a/crates/typst-library/src/model/bibliography.rs b/crates/typst-library/src/model/bibliography.rs index 8056d4ab3..e1a073594 100644 --- a/crates/typst-library/src/model/bibliography.rs +++ b/crates/typst-library/src/model/bibliography.rs @@ -592,7 +592,7 @@ impl Works { /// Context for generating the bibliography. struct Generator<'a> { - /// The routines that is used to evaluate mathematical material in citations. + /// The routines that are used to evaluate mathematical material in citations. routines: &'a Routines, /// The world that is used to evaluate mathematical material in citations. world: Tracked<'a, dyn World + 'a>, @@ -609,7 +609,7 @@ struct Generator<'a> { /// Details about a group of merged citations. All citations are put into groups /// of adjacent ones (e.g., `@foo @bar` will merge into a group of length two). -/// Even single citations will be put into groups of length ones. +/// Even single citations will be put into groups of length one. struct GroupInfo { /// The group's location. location: Location, From d3caedd813b1ca4379a71eb1b4aa636096d53a04 Mon Sep 17 00:00:00 2001 From: Connor K Date: Wed, 25 Jun 2025 12:59:19 -0400 Subject: [PATCH 02/49] Fix typos in page-setup.md (#6499) --- docs/guides/page-setup.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/guides/page-setup.md b/docs/guides/page-setup.md index 36ed0fa23..1682c1220 100644 --- a/docs/guides/page-setup.md +++ b/docs/guides/page-setup.md @@ -206,7 +206,6 @@ label exists on the current page: ```typ >>> #set page("a5", margin: (x: 2.5cm, y: 3cm)) #set page(header: context { - let page-counter = let matches = query() let current = counter(page).get() let has-table = matches.any(m => @@ -218,7 +217,7 @@ label exists on the current page: #h(1fr) National Academy of Sciences ] -})) +}) #lorem(100) #pagebreak() From 35809387f88483bfa3d0978cfc3303eba0de632b Mon Sep 17 00:00:00 2001 From: Laurenz Date: Thu, 26 Jun 2025 10:06:22 +0200 Subject: [PATCH 03/49] Support `in` operator on strings and modules (#6498) --- .../typst-library/src/foundations/module.rs | 19 +++++++++++++++---- crates/typst-library/src/foundations/ops.rs | 1 + docs/reference/groups.yml | 6 +----- tests/suite/scripting/ops.typ | 2 ++ 4 files changed, 19 insertions(+), 9 deletions(-) diff --git a/crates/typst-library/src/foundations/module.rs b/crates/typst-library/src/foundations/module.rs index 55d8bab63..14eefca39 100644 --- a/crates/typst-library/src/foundations/module.rs +++ b/crates/typst-library/src/foundations/module.rs @@ -19,11 +19,8 @@ use crate::foundations::{repr, ty, Content, Scope, Value}; /// /// You can access definitions from the module using [field access /// notation]($scripting/#fields) and interact with it using the [import and -/// include syntaxes]($scripting/#modules). Alternatively, it is possible to -/// convert a module to a dictionary, and therefore access its contents -/// dynamically, using the [dictionary constructor]($dictionary/#constructor). +/// include syntaxes]($scripting/#modules). /// -/// # Example /// ```example /// <<< #import "utils.typ" /// <<< #utils.add(2, 5) @@ -34,6 +31,20 @@ use crate::foundations::{repr, ty, Content, Scope, Value}; /// >>> /// >>> #(-3) /// ``` +/// +/// You can check whether a definition is present in a module using the `{in}` +/// operator, with a string on the left-hand side. This can be useful to +/// [conditionally access]($category/foundations/std/#conditional-access) +/// definitions in a module. +/// +/// ```example +/// #("table" in std) \ +/// #("nope" in std) +/// ``` +/// +/// Alternatively, it is possible to convert a module to a dictionary, and +/// therefore access its contents dynamically, using the [dictionary +/// constructor]($dictionary/#constructor). #[ty(cast)] #[derive(Clone, Hash)] #[allow(clippy::derived_hash_with_manual_eq)] diff --git a/crates/typst-library/src/foundations/ops.rs b/crates/typst-library/src/foundations/ops.rs index 6c2408446..3c6a5e6cf 100644 --- a/crates/typst-library/src/foundations/ops.rs +++ b/crates/typst-library/src/foundations/ops.rs @@ -558,6 +558,7 @@ pub fn contains(lhs: &Value, rhs: &Value) -> Option { (Str(a), Str(b)) => Some(b.as_str().contains(a.as_str())), (Dyn(a), Str(b)) => a.downcast::().map(|regex| regex.is_match(b)), (Str(a), Dict(b)) => Some(b.contains(a)), + (Str(a), Module(b)) => Some(b.scope().get(a).is_some()), (a, Array(b)) => Some(b.contains(a.clone())), _ => Option::None, diff --git a/docs/reference/groups.yml b/docs/reference/groups.yml index c7e3d9964..e01d99dc4 100644 --- a/docs/reference/groups.yml +++ b/docs/reference/groups.yml @@ -181,11 +181,7 @@ [`sys.version`]($category/foundations/sys) can also be very useful. ```typ - #let tiling = if "tiling" in dictionary(std) { - tiling - } else { - pattern - } + #let tiling = if "tiling" in std { tiling } else { pattern } ... ``` diff --git a/tests/suite/scripting/ops.typ b/tests/suite/scripting/ops.typ index d17c0117f..561682f05 100644 --- a/tests/suite/scripting/ops.typ +++ b/tests/suite/scripting/ops.typ @@ -264,6 +264,8 @@ #test("Hey" not in "abheyCd", true) #test("a" not /* fun comment? */ in "abc", false) +#test("sys" in std, true) +#test("system" in std, false) --- ops-not-trailing --- // Error: 10 expected keyword `in` From 6a1d6c08e2d6e4c184c6d177e67796b23ccbe4c7 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Thu, 26 Jun 2025 10:07:41 +0200 Subject: [PATCH 04/49] Consistent sizing for `html.frame` (#6505) --- crates/typst-html/src/encode.rs | 15 ++++++++++----- crates/typst-html/src/lib.rs | 7 +++++-- crates/typst-library/src/html/dom.rs | 17 ++++++++++++++--- .../src/introspection/introspector.rs | 2 +- 4 files changed, 30 insertions(+), 11 deletions(-) diff --git a/crates/typst-html/src/encode.rs b/crates/typst-html/src/encode.rs index 9c7938360..84860dbe9 100644 --- a/crates/typst-html/src/encode.rs +++ b/crates/typst-html/src/encode.rs @@ -3,9 +3,8 @@ use std::fmt::Write; use typst_library::diag::{bail, At, SourceResult, StrResult}; use typst_library::foundations::Repr; use typst_library::html::{ - attr, charsets, tag, HtmlDocument, HtmlElement, HtmlNode, HtmlTag, + attr, charsets, tag, HtmlDocument, HtmlElement, HtmlFrame, HtmlNode, HtmlTag, }; -use typst_library::layout::Frame; use typst_syntax::Span; /// Encodes an HTML document into a string. @@ -304,9 +303,15 @@ fn write_escape(w: &mut Writer, c: char) -> StrResult<()> { } /// Encode a laid out frame into the writer. -fn write_frame(w: &mut Writer, frame: &Frame) { +fn write_frame(w: &mut Writer, frame: &HtmlFrame) { // FIXME: This string replacement is obviously a hack. - let svg = typst_svg::svg_frame(frame) - .replace(" Self::intern(&v)?, } +/// Layouted content that will be embedded into HTML as an SVG. +#[derive(Debug, Clone, Hash)] +pub struct HtmlFrame { + /// The frame that will be displayed as an SVG. + pub inner: Frame, + /// The text size where the frame was defined. This is used to size the + /// frame with em units to make text in and outside of the frame sized + /// consistently. + pub text_size: Abs, +} + /// Defines syntactical properties of HTML tags, attributes, and text. pub mod charsets { /// Check whether a character is in a tag name. diff --git a/crates/typst-library/src/introspection/introspector.rs b/crates/typst-library/src/introspection/introspector.rs index 9751dfcb8..d2ad0525b 100644 --- a/crates/typst-library/src/introspection/introspector.rs +++ b/crates/typst-library/src/introspection/introspector.rs @@ -446,7 +446,7 @@ impl IntrospectorBuilder { HtmlNode::Element(elem) => self.discover_in_html(sink, &elem.children), HtmlNode::Frame(frame) => self.discover_in_frame( sink, - frame, + &frame.inner, NonZeroUsize::ONE, Transform::identity(), ), From 04fd0acacab8cf2e82268da9c18ef4bcf37507dc Mon Sep 17 00:00:00 2001 From: Malo <57839069+MDLC01@users.noreply.github.com> Date: Thu, 26 Jun 2025 09:24:21 +0100 Subject: [PATCH 05/49] Allow deprecating symbol variants (#6441) --- Cargo.lock | 2 +- Cargo.toml | 2 +- crates/typst-ide/src/complete.rs | 2 +- .../typst-library/src/foundations/symbol.rs | 59 ++++++++++++------- crates/typst-library/src/foundations/value.rs | 4 +- docs/src/lib.rs | 14 ++--- tests/suite/math/attach.typ | 6 +- 7 files changed, 51 insertions(+), 38 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3ea423f5f..91ff48432 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -413,7 +413,7 @@ dependencies = [ [[package]] name = "codex" version = "0.1.1" -source = "git+https://github.com/typst/codex?rev=56eb217#56eb2172fc0670f4c1c8b79a63d11f9354e5babe" +source = "git+https://github.com/typst/codex?rev=a5428cb#a5428cb9c81a41354d44b44dbd5a16a710bbd928" [[package]] name = "color-print" diff --git a/Cargo.toml b/Cargo.toml index 3cfb72008..76d83995f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,7 +47,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 = "56eb217" } +codex = { git = "https://github.com/typst/codex", rev = "a5428cb" } color-print = "0.3.6" comemo = "0.4" csv = "1" diff --git a/crates/typst-ide/src/complete.rs b/crates/typst-ide/src/complete.rs index 536423318..bc5b3e10e 100644 --- a/crates/typst-ide/src/complete.rs +++ b/crates/typst-ide/src/complete.rs @@ -448,7 +448,7 @@ fn field_access_completions( match value { Value::Symbol(symbol) => { for modifier in symbol.modifiers() { - if let Ok(modified) = symbol.clone().modified(modifier) { + if let Ok(modified) = symbol.clone().modified((), modifier) { ctx.completions.push(Completion { kind: CompletionKind::Symbol(modified.get()), label: modifier.into(), diff --git a/crates/typst-library/src/foundations/symbol.rs b/crates/typst-library/src/foundations/symbol.rs index 0f503edd0..f57bb0c2a 100644 --- a/crates/typst-library/src/foundations/symbol.rs +++ b/crates/typst-library/src/foundations/symbol.rs @@ -8,7 +8,7 @@ use serde::{Serialize, Serializer}; use typst_syntax::{is_ident, Span, Spanned}; use typst_utils::hash128; -use crate::diag::{bail, SourceResult, StrResult}; +use crate::diag::{bail, DeprecationSink, SourceResult, StrResult}; use crate::foundations::{ cast, elem, func, scope, ty, Array, Content, Func, NativeElement, NativeFunc, Packed, PlainText, Repr as _, @@ -54,18 +54,22 @@ enum Repr { /// A native symbol that has no named variant. Single(char), /// A native symbol with multiple named variants. - Complex(&'static [(ModifierSet<&'static str>, char)]), + Complex(&'static [Variant<&'static str>]), /// 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, ModifierSet)>), } +/// A symbol variant, consisting of a set of modifiers, a character, and an +/// optional deprecation message. +type Variant = (ModifierSet, char, Option); + /// A collection of symbols. #[derive(Clone, Eq, PartialEq, Hash)] enum List { - Static(&'static [(ModifierSet<&'static str>, char)]), - Runtime(Box<[(ModifierSet, char)]>), + Static(&'static [Variant<&'static str>]), + Runtime(Box<[Variant]>), } impl Symbol { @@ -76,14 +80,14 @@ impl Symbol { /// Create a symbol with a static variant list. #[track_caller] - pub const fn list(list: &'static [(ModifierSet<&'static str>, char)]) -> Self { + pub const fn list(list: &'static [Variant<&'static str>]) -> Self { debug_assert!(!list.is_empty()); Self(Repr::Complex(list)) } /// Create a symbol with a runtime variant list. #[track_caller] - pub fn runtime(list: Box<[(ModifierSet, char)]>) -> Self { + pub fn runtime(list: Box<[Variant]>) -> Self { debug_assert!(!list.is_empty()); Self(Repr::Modified(Arc::new((List::Runtime(list), ModifierSet::default())))) } @@ -93,9 +97,11 @@ impl Symbol { match &self.0 { Repr::Single(c) => *c, Repr::Complex(_) => ModifierSet::<&'static str>::default() - .best_match_in(self.variants()) + .best_match_in(self.variants().map(|(m, c, _)| (m, c))) .unwrap(), - Repr::Modified(arc) => arc.1.best_match_in(self.variants()).unwrap(), + Repr::Modified(arc) => { + arc.1.best_match_in(self.variants().map(|(m, c, _)| (m, c))).unwrap() + } } } @@ -128,7 +134,11 @@ impl Symbol { } /// Apply a modifier to the symbol. - pub fn modified(mut self, modifier: &str) -> StrResult { + pub fn modified( + mut self, + sink: impl DeprecationSink, + modifier: &str, + ) -> StrResult { if let Repr::Complex(list) = self.0 { self.0 = Repr::Modified(Arc::new((List::Static(list), ModifierSet::default()))); @@ -137,7 +147,12 @@ impl Symbol { if let Repr::Modified(arc) = &mut self.0 { let (list, modifiers) = Arc::make_mut(arc); modifiers.insert_raw(modifier); - if modifiers.best_match_in(list.variants()).is_some() { + if let Some(deprecation) = + modifiers.best_match_in(list.variants().map(|(m, _, d)| (m, d))) + { + if let Some(message) = deprecation { + sink.emit(message) + } return Ok(self); } } @@ -146,7 +161,7 @@ impl Symbol { } /// The characters that are covered by this symbol. - pub fn variants(&self) -> impl Iterator, char)> { + pub fn variants(&self) -> impl Iterator> { match &self.0 { Repr::Single(c) => Variants::Single(Some(*c).into_iter()), Repr::Complex(list) => Variants::Static(list.iter()), @@ -161,7 +176,7 @@ impl Symbol { _ => ModifierSet::default(), }; self.variants() - .flat_map(|(m, _)| m) + .flat_map(|(m, _, _)| m) .filter(|modifier| !modifier.is_empty() && !modifiers.contains(modifier)) .collect::>() .into_iter() @@ -256,7 +271,7 @@ impl Symbol { let list = variants .into_iter() - .map(|s| (ModifierSet::from_raw_dotted(s.v.0), s.v.1)) + .map(|s| (ModifierSet::from_raw_dotted(s.v.0), s.v.1, None)) .collect(); Ok(Symbol::runtime(list)) } @@ -316,17 +331,17 @@ impl crate::foundations::Repr for Symbol { } fn repr_variants<'a>( - variants: impl Iterator, char)>, + variants: impl Iterator>, applied_modifiers: ModifierSet<&str>, ) -> String { crate::foundations::repr::pretty_array_like( &variants - .filter(|(modifiers, _)| { + .filter(|(modifiers, _, _)| { // Only keep variants that can still be accessed, i.e., variants // that contain all applied modifiers. applied_modifiers.iter().all(|am| modifiers.contains(am)) }) - .map(|(modifiers, c)| { + .map(|(modifiers, c, _)| { let trimmed_modifiers = modifiers.into_iter().filter(|&m| !applied_modifiers.contains(m)); if trimmed_modifiers.clone().all(|m| m.is_empty()) { @@ -379,18 +394,20 @@ cast! { /// Iterator over variants. enum Variants<'a> { Single(std::option::IntoIter), - Static(std::slice::Iter<'static, (ModifierSet<&'static str>, char)>), - Runtime(std::slice::Iter<'a, (ModifierSet, char)>), + Static(std::slice::Iter<'static, Variant<&'static str>>), + Runtime(std::slice::Iter<'a, Variant>), } impl<'a> Iterator for Variants<'a> { - type Item = (ModifierSet<&'a str>, char); + type Item = Variant<&'a str>; fn next(&mut self) -> Option { match self { - Self::Single(iter) => Some((ModifierSet::default(), iter.next()?)), + Self::Single(iter) => Some((ModifierSet::default(), iter.next()?, None)), Self::Static(list) => list.next().copied(), - Self::Runtime(list) => list.next().map(|(m, c)| (m.as_deref(), *c)), + Self::Runtime(list) => { + list.next().map(|(m, c, d)| (m.as_deref(), *c, d.as_deref())) + } } } } diff --git a/crates/typst-library/src/foundations/value.rs b/crates/typst-library/src/foundations/value.rs index 854c2486e..4bcf2d4e3 100644 --- a/crates/typst-library/src/foundations/value.rs +++ b/crates/typst-library/src/foundations/value.rs @@ -157,7 +157,9 @@ impl Value { /// Try to access a field on the value. pub fn field(&self, field: &str, sink: impl DeprecationSink) -> StrResult { match self { - Self::Symbol(symbol) => symbol.clone().modified(field).map(Self::Symbol), + Self::Symbol(symbol) => { + symbol.clone().modified(sink, field).map(Self::Symbol) + } Self::Version(version) => version.component(field).map(Self::Int), Self::Dict(dict) => dict.get(field).cloned(), Self::Content(content) => content.field_by_name(field), diff --git a/docs/src/lib.rs b/docs/src/lib.rs index 9bd21c2e8..dc6b62c72 100644 --- a/docs/src/lib.rs +++ b/docs/src/lib.rs @@ -720,18 +720,12 @@ fn symbols_model(resolver: &dyn Resolver, group: &GroupData) -> SymbolsModel { } }; - for (variant, c) in symbol.variants() { + for (variant, c, deprecation) in symbol.variants() { let shorthand = |list: &[(&'static str, char)]| { list.iter().copied().find(|&(_, x)| x == c).map(|(s, _)| s) }; let name = complete(variant); - let deprecation = match name.as_str() { - "integral.sect" => { - Some("`integral.sect` is deprecated, use `integral.inter` instead") - } - _ => binding.deprecation(), - }; list.push(SymbolModel { name, @@ -742,10 +736,10 @@ fn symbols_model(resolver: &dyn Resolver, group: &GroupData) -> SymbolsModel { accent: typst::math::Accent::combine(c).is_some(), alternates: symbol .variants() - .filter(|(other, _)| other != &variant) - .map(|(other, _)| complete(other)) + .filter(|(other, _, _)| other != &variant) + .map(|(other, _, _)| complete(other)) .collect(), - deprecation, + deprecation: deprecation.or_else(|| binding.deprecation()), }); } } diff --git a/tests/suite/math/attach.typ b/tests/suite/math/attach.typ index cedc3a4ab..979018478 100644 --- a/tests/suite/math/attach.typ +++ b/tests/suite/math/attach.typ @@ -121,8 +121,8 @@ $a scripts(=)^"def" b quad a scripts(lt.eq)_"really" b quad a scripts(arrow.r.lo --- math-attach-integral --- // Test default of scripts attachments on integrals at display size. -$ integral.sect_a^b quad \u{2a1b}_a^b quad limits(\u{2a1b})_a^b $ -$integral.sect_a^b quad \u{2a1b}_a^b quad limits(\u{2a1b})_a^b$ +$ integral.inter_a^b quad \u{2a1b}_a^b quad limits(\u{2a1b})_a^b $ +$integral.inter_a^b quad \u{2a1b}_a^b quad limits(\u{2a1b})_a^b$ --- math-attach-large-operator --- // Test default of limit attachments on large operators at display size only. @@ -179,7 +179,7 @@ $ a0 + a1 + a0_2 \ #{ let var = $x^1$ for i in range(24) { - var = $var$ + var = $var$ } $var_2$ } From 5dd5771df03a666fe17930b0b071b06266e5937f Mon Sep 17 00:00:00 2001 From: "Said A." <47973576+Daaiid@users.noreply.github.com> Date: Thu, 26 Jun 2025 11:18:51 +0200 Subject: [PATCH 06/49] Disallow empty labels and references (#5776) (#6332) Co-authored-by: Laurenz --- crates/typst-eval/src/markup.rs | 7 ++++-- crates/typst-ide/src/definition.rs | 3 ++- crates/typst-library/src/foundations/label.rs | 23 ++++++++++++------ .../typst-library/src/model/bibliography.rs | 6 ++++- crates/typst-syntax/src/ast.rs | 2 ++ crates/typst-syntax/src/lexer.rs | 2 +- tests/ref/ref-to-empty-label-not-possible.png | Bin 0 -> 182 bytes tests/suite/foundations/label.typ | 4 +++ tests/suite/model/bibliography.typ | 8 ++++++ tests/suite/model/ref.typ | 11 +++++++++ 10 files changed, 54 insertions(+), 12 deletions(-) create mode 100644 tests/ref/ref-to-empty-label-not-possible.png diff --git a/crates/typst-eval/src/markup.rs b/crates/typst-eval/src/markup.rs index 5beefa912..9118ded56 100644 --- a/crates/typst-eval/src/markup.rs +++ b/crates/typst-eval/src/markup.rs @@ -205,7 +205,9 @@ impl Eval for ast::Label<'_> { type Output = Value; fn eval(self, _: &mut Vm) -> SourceResult { - Ok(Value::Label(Label::new(PicoStr::intern(self.get())))) + Ok(Value::Label( + Label::new(PicoStr::intern(self.get())).expect("unexpected empty label"), + )) } } @@ -213,7 +215,8 @@ impl Eval for ast::Ref<'_> { type Output = Content; fn eval(self, vm: &mut Vm) -> SourceResult { - let target = Label::new(PicoStr::intern(self.target())); + let target = Label::new(PicoStr::intern(self.target())) + .expect("unexpected empty reference"); 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/definition.rs b/crates/typst-ide/src/definition.rs index 69d702b3b..ae1ba287b 100644 --- a/crates/typst-ide/src/definition.rs +++ b/crates/typst-ide/src/definition.rs @@ -72,7 +72,8 @@ pub fn definition( // Try to jump to the referenced content. DerefTarget::Ref(node) => { - let label = Label::new(PicoStr::intern(node.cast::()?.target())); + let label = Label::new(PicoStr::intern(node.cast::()?.target())) + .expect("unexpected empty reference"); let selector = Selector::Label(label); let elem = document?.introspector.query_first(&selector)?; return Some(Definition::Span(elem.span())); diff --git a/crates/typst-library/src/foundations/label.rs b/crates/typst-library/src/foundations/label.rs index 3b9b010c5..b1ac58bf2 100644 --- a/crates/typst-library/src/foundations/label.rs +++ b/crates/typst-library/src/foundations/label.rs @@ -1,7 +1,8 @@ use ecow::{eco_format, EcoString}; use typst_utils::{PicoStr, ResolvedPicoStr}; -use crate::foundations::{func, scope, ty, Repr, Str}; +use crate::diag::StrResult; +use crate::foundations::{bail, func, scope, ty, Repr, Str}; /// A label for an element. /// @@ -27,7 +28,8 @@ use crate::foundations::{func, scope, ty, Repr, Str}; /// # Syntax /// This function also has dedicated syntax: You can create a label by enclosing /// its name in angle brackets. This works both in markup and code. A label's -/// name can contain letters, numbers, `_`, `-`, `:`, and `.`. +/// name can contain letters, numbers, `_`, `-`, `:`, and `.`. A label cannot +/// be empty. /// /// Note that there is a syntactical difference when using the dedicated syntax /// for this function. In the code below, the `[]` terminates the heading and @@ -50,8 +52,11 @@ pub struct Label(PicoStr); impl Label { /// Creates a label from an interned string. - pub fn new(name: PicoStr) -> Self { - Self(name) + /// + /// Returns `None` if the given string is empty. + pub fn new(name: PicoStr) -> Option { + const EMPTY: PicoStr = PicoStr::constant(""); + (name != EMPTY).then_some(Self(name)) } /// Resolves the label to a string. @@ -70,10 +75,14 @@ impl Label { /// Creates a label from a string. #[func(constructor)] pub fn construct( - /// The name of the label. + /// The name of the label. Must not be empty. name: Str, - ) -> Label { - Self(PicoStr::intern(name.as_str())) + ) -> StrResult

${title}