Merge branch 'main' into pdf-accessibility

This commit is contained in:
Tobias Schmitz 2025-07-30 15:27:00 +02:00
commit 7c8a4e4243
No known key found for this signature in database
42 changed files with 417 additions and 96 deletions

1
Cargo.lock generated
View File

@ -3286,7 +3286,6 @@ dependencies = [
name = "typst-syntax" name = "typst-syntax"
version = "0.13.1" version = "0.13.1"
dependencies = [ dependencies = [
"comemo",
"ecow", "ecow",
"serde", "serde",
"toml", "toml",

View File

@ -160,6 +160,7 @@ strip = true
[workspace.lints.clippy] [workspace.lints.clippy]
blocks_in_conditions = "allow" blocks_in_conditions = "allow"
comparison_chain = "allow" comparison_chain = "allow"
iter_over_hash_type = "warn"
manual_range_contains = "allow" manual_range_contains = "allow"
mutable_key_type = "allow" mutable_key_type = "allow"
uninlined_format_args = "warn" uninlined_format_args = "warn"

View File

@ -139,6 +139,7 @@ impl Watcher {
fn update(&mut self, iter: impl IntoIterator<Item = PathBuf>) -> StrResult<()> { fn update(&mut self, iter: impl IntoIterator<Item = PathBuf>) -> StrResult<()> {
// Mark all files as not "seen" so that we may unwatch them if they // Mark all files as not "seen" so that we may unwatch them if they
// aren't in the dependency list. // aren't in the dependency list.
#[allow(clippy::iter_over_hash_type, reason = "order does not matter")]
for seen in self.watched.values_mut() { for seen in self.watched.values_mut() {
*seen = false; *seen = false;
} }

View File

@ -173,6 +173,7 @@ impl SystemWorld {
/// Reset the compilation state in preparation of a new compilation. /// Reset the compilation state in preparation of a new compilation.
pub fn reset(&mut self) { pub fn reset(&mut self) {
#[allow(clippy::iter_over_hash_type, reason = "order does not matter")]
for slot in self.slots.get_mut().values_mut() { for slot in self.slots.get_mut().values_mut() {
slot.reset(); slot.reset();
} }

View File

@ -709,9 +709,11 @@ fn complete_params(ctx: &mut CompletionContext) -> bool {
return true; return true;
} }
// Parameters: "func(|)", "func(hi|)", "func(12,|)". // Parameters: "func(|)", "func(hi|)", "func(12, |)", "func(12,|)" [explicit mode only]
if let SyntaxKind::LeftParen | SyntaxKind::Comma = deciding.kind() if let SyntaxKind::LeftParen | SyntaxKind::Comma = deciding.kind()
&& (deciding.kind() != SyntaxKind::Comma || deciding.range().end < ctx.cursor) && (deciding.kind() != SyntaxKind::Comma
|| deciding.range().end < ctx.cursor
|| ctx.explicit)
{ {
if let Some(next) = deciding.next_leaf() { if let Some(next) = deciding.next_leaf() {
ctx.from = ctx.cursor.min(next.offset()); ctx.from = ctx.cursor.min(next.offset());
@ -891,7 +893,10 @@ fn complete_code(ctx: &mut CompletionContext) -> bool {
} }
// An existing identifier: "{ pa| }". // An existing identifier: "{ pa| }".
if ctx.leaf.kind() == SyntaxKind::Ident { // Ignores named pair keys as they are not variables (as in "(pa|: 23)").
if ctx.leaf.kind() == SyntaxKind::Ident
&& (ctx.leaf.index() > 0 || ctx.leaf.parent_kind() != Some(SyntaxKind::Named))
{
ctx.from = ctx.leaf.offset(); ctx.from = ctx.leaf.offset();
code_completions(ctx, false); code_completions(ctx, false);
return true; return true;
@ -904,11 +909,19 @@ fn complete_code(ctx: &mut CompletionContext) -> bool {
return true; return true;
} }
// Anywhere: "{ | }". // Anywhere: "{ | }", "(|)", "(1,|)", "(a:|)".
// But not within or after an expression. // But not within or after an expression, and also not part of a dictionary
// key (as in "(pa: |,)")
if ctx.explicit if ctx.explicit
&& ctx.leaf.parent_kind() != Some(SyntaxKind::Dict)
&& (ctx.leaf.kind().is_trivia() && (ctx.leaf.kind().is_trivia()
|| matches!(ctx.leaf.kind(), SyntaxKind::LeftParen | SyntaxKind::LeftBrace)) || matches!(
ctx.leaf.kind(),
SyntaxKind::LeftParen
| SyntaxKind::LeftBrace
| SyntaxKind::Comma
| SyntaxKind::Colon
))
{ {
ctx.from = ctx.cursor; ctx.from = ctx.cursor;
code_completions(ctx, false); code_completions(ctx, false);
@ -1560,6 +1573,7 @@ mod tests {
trait ResponseExt { trait ResponseExt {
fn completions(&self) -> &[Completion]; fn completions(&self) -> &[Completion];
fn labels(&self) -> BTreeSet<&str>; fn labels(&self) -> BTreeSet<&str>;
fn must_be_empty(&self) -> &Self;
fn must_include<'a>(&self, includes: impl IntoIterator<Item = &'a str>) -> &Self; fn must_include<'a>(&self, includes: impl IntoIterator<Item = &'a str>) -> &Self;
fn must_exclude<'a>(&self, excludes: impl IntoIterator<Item = &'a str>) -> &Self; fn must_exclude<'a>(&self, excludes: impl IntoIterator<Item = &'a str>) -> &Self;
fn must_apply<'a>(&self, label: &str, apply: impl Into<Option<&'a str>>) fn must_apply<'a>(&self, label: &str, apply: impl Into<Option<&'a str>>)
@ -1578,6 +1592,16 @@ mod tests {
self.completions().iter().map(|c| c.label.as_str()).collect() self.completions().iter().map(|c| c.label.as_str()).collect()
} }
#[track_caller]
fn must_be_empty(&self) -> &Self {
let labels = self.labels();
assert!(
labels.is_empty(),
"expected no suggestions (got {labels:?} instead)"
);
self
}
#[track_caller] #[track_caller]
fn must_include<'a>(&self, includes: impl IntoIterator<Item = &'a str>) -> &Self { fn must_include<'a>(&self, includes: impl IntoIterator<Item = &'a str>) -> &Self {
let labels = self.labels(); let labels = self.labels();
@ -1622,7 +1646,15 @@ mod tests {
let world = world.acquire(); let world = world.acquire();
let world = world.borrow(); let world = world.borrow();
let doc = typst::compile(world).output.ok(); let doc = typst::compile(world).output.ok();
test_with_doc(world, pos, doc.as_ref()) test_with_doc(world, pos, doc.as_ref(), true)
}
#[track_caller]
fn test_implicit(world: impl WorldLike, pos: impl FilePos) -> Response {
let world = world.acquire();
let world = world.borrow();
let doc = typst::compile(world).output.ok();
test_with_doc(world, pos, doc.as_ref(), false)
} }
#[track_caller] #[track_caller]
@ -1635,7 +1667,7 @@ mod tests {
let doc = typst::compile(&world).output.ok(); let doc = typst::compile(&world).output.ok();
let end = world.main.text().len(); let end = world.main.text().len();
world.main.edit(end..end, addition); world.main.edit(end..end, addition);
test_with_doc(&world, pos, doc.as_ref()) test_with_doc(&world, pos, doc.as_ref(), true)
} }
#[track_caller] #[track_caller]
@ -1643,11 +1675,12 @@ mod tests {
world: impl WorldLike, world: impl WorldLike,
pos: impl FilePos, pos: impl FilePos,
doc: Option<&PagedDocument>, doc: Option<&PagedDocument>,
explicit: bool,
) -> Response { ) -> Response {
let world = world.acquire(); let world = world.acquire();
let world = world.borrow(); let world = world.borrow();
let (source, cursor) = pos.resolve(world); let (source, cursor) = pos.resolve(world);
autocomplete(world, doc, &source, cursor, true) autocomplete(world, doc, &source, cursor, explicit)
} }
#[test] #[test]
@ -1698,7 +1731,7 @@ mod tests {
let end = world.main.text().len(); let end = world.main.text().len();
world.main.edit(end..end, " #cite()"); world.main.edit(end..end, " #cite()");
test_with_doc(&world, -2, doc.as_ref()) test_with_doc(&world, -2, doc.as_ref(), true)
.must_include(["netwok", "glacier-melt", "supplement"]) .must_include(["netwok", "glacier-melt", "supplement"])
.must_exclude(["bib"]); .must_exclude(["bib"]);
} }
@ -1853,26 +1886,105 @@ mod tests {
#[test] #[test]
fn test_autocomplete_fonts() { fn test_autocomplete_fonts() {
test("#text(font:)", -2) test("#text(font:)", -2)
.must_include(["\"Libertinus Serif\"", "\"New Computer Modern Math\""]); .must_include([q!("Libertinus Serif"), q!("New Computer Modern Math")]);
test("#show link: set text(font: )", -2) test("#show link: set text(font: )", -2)
.must_include(["\"Libertinus Serif\"", "\"New Computer Modern Math\""]); .must_include([q!("Libertinus Serif"), q!("New Computer Modern Math")]);
test("#show math.equation: set text(font: )", -2) test("#show math.equation: set text(font: )", -2)
.must_include(["\"New Computer Modern Math\""]) .must_include([q!("New Computer Modern Math")])
.must_exclude(["\"Libertinus Serif\""]); .must_exclude([q!("Libertinus Serif")]);
test("#show math.equation: it => { set text(font: )\nit }", -7) test("#show math.equation: it => { set text(font: )\nit }", -7)
.must_include(["\"New Computer Modern Math\""]) .must_include([q!("New Computer Modern Math")])
.must_exclude(["\"Libertinus Serif\""]); .must_exclude([q!("Libertinus Serif")]);
} }
#[test] #[test]
fn test_autocomplete_typed_html() { fn test_autocomplete_typed_html() {
test("#html.div(translate: )", -2) test("#html.div(translate: )", -2)
.must_include(["true", "false"]) .must_include(["true", "false"])
.must_exclude(["\"yes\"", "\"no\""]); .must_exclude([q!("yes"), q!("no")]);
test("#html.input(value: )", -2).must_include(["float", "string", "red", "blue"]); test("#html.input(value: )", -2).must_include(["float", "string", "red", "blue"]);
test("#html.div(role: )", -2).must_include(["\"alertdialog\""]); test("#html.div(role: )", -2).must_include([q!("alertdialog")]);
}
#[test]
fn test_autocomplete_in_function_params_after_comma_and_colon() {
let document = "#text(size: 12pt, [])";
// After colon
test(document, 11).must_include(["length"]);
test_implicit(document, 11).must_include(["length"]);
test(document, 12).must_include(["length"]);
test_implicit(document, 12).must_include(["length"]);
// After comma
test(document, 17).must_include(["font"]);
test_implicit(document, 17).must_be_empty();
test(document, 18).must_include(["font"]);
test_implicit(document, 18).must_include(["font"]);
}
#[test]
fn test_autocomplete_in_list_literal() {
let document = "#let val = 0\n#(1, \"one\")";
// After opening paren
test(document, 15).must_include(["color", "val"]);
test_implicit(document, 15).must_be_empty();
// After first element
test(document, 16).must_be_empty();
test_implicit(document, 16).must_be_empty();
// After comma
test(document, 17).must_include(["color", "val"]);
test_implicit(document, 17).must_be_empty();
test(document, 18).must_include(["color", "val"]);
test_implicit(document, 18).must_be_empty();
}
#[test]
fn test_autocomplete_in_dict_literal() {
let document = "#let first = 0\n#(first: 1, second: one)";
// After opening paren
test(document, 17).must_be_empty();
test_implicit(document, 17).must_be_empty();
// After first key
test(document, 22).must_be_empty();
test_implicit(document, 22).must_be_empty();
// After colon
test(document, 23).must_include(["align", "first"]);
test_implicit(document, 23).must_be_empty();
test(document, 24).must_include(["align", "first"]);
test_implicit(document, 24).must_be_empty();
// After first value
test(document, 25).must_be_empty();
test_implicit(document, 25).must_be_empty();
// After comma
test(document, 26).must_be_empty();
test_implicit(document, 26).must_be_empty();
test(document, 27).must_be_empty();
test_implicit(document, 27).must_be_empty();
}
#[test]
fn test_autocomplete_in_destructuring() {
let document = "#let value = 20\n#let (va: value) = (va: 10)";
// At destructuring rename pattern source
test(document, 24).must_be_empty();
test_implicit(document, 24).must_be_empty();
} }
} }

View File

@ -234,18 +234,23 @@ impl From<SyntaxError> for SourceDiagnostic {
/// Destination for a deprecation message when accessing a deprecated value. /// Destination for a deprecation message when accessing a deprecated value.
pub trait DeprecationSink { pub trait DeprecationSink {
/// Emits the given deprecation message into this sink. /// Emits the given deprecation message into this sink alongside a version
fn emit(self, message: &str); /// in which the deprecated item is planned to be removed.
fn emit(self, message: &str, until: Option<&str>);
} }
impl DeprecationSink for () { impl DeprecationSink for () {
fn emit(self, _: &str) {} fn emit(self, _: &str, _: Option<&str>) {}
} }
impl DeprecationSink for (&mut Engine<'_>, Span) { impl DeprecationSink for (&mut Engine<'_>, Span) {
/// Emits the deprecation message as a warning. /// Emits the deprecation message as a warning.
fn emit(self, message: &str) { fn emit(self, message: &str, version: Option<&str>) {
self.0.sink.warn(SourceDiagnostic::warning(self.1, message)); self.0
.sink
.warn(SourceDiagnostic::warning(self.1, message).with_hints(
version.map(|v| eco_format!("it will be removed in Typst {}", v)),
));
} }
} }

View File

@ -253,8 +253,8 @@ pub struct Binding {
span: Span, span: Span,
/// The category of the binding. /// The category of the binding.
category: Option<Category>, category: Option<Category>,
/// A deprecation message for the definition. /// The deprecation information if this item is deprecated.
deprecation: Option<&'static str>, deprecation: Option<Box<Deprecation>>,
} }
/// The different kinds of slots. /// The different kinds of slots.
@ -284,8 +284,8 @@ impl Binding {
} }
/// Marks this binding as deprecated, with the given `message`. /// Marks this binding as deprecated, with the given `message`.
pub fn deprecated(&mut self, message: &'static str) -> &mut Self { pub fn deprecated(&mut self, deprecation: Deprecation) -> &mut Self {
self.deprecation = Some(message); self.deprecation = Some(Box::new(deprecation));
self self
} }
@ -300,8 +300,8 @@ impl Binding {
/// - pass `()` to ignore the message. /// - pass `()` to ignore the message.
/// - pass `(&mut engine, span)` to emit a warning into the engine. /// - pass `(&mut engine, span)` to emit a warning into the engine.
pub fn read_checked(&self, sink: impl DeprecationSink) -> &Value { pub fn read_checked(&self, sink: impl DeprecationSink) -> &Value {
if let Some(message) = self.deprecation { if let Some(info) = &self.deprecation {
sink.emit(message); sink.emit(info.message, info.until);
} }
&self.value &self.value
} }
@ -337,8 +337,8 @@ impl Binding {
} }
/// A deprecation message for the value, if any. /// A deprecation message for the value, if any.
pub fn deprecation(&self) -> Option<&'static str> { pub fn deprecation(&self) -> Option<&Deprecation> {
self.deprecation self.deprecation.as_deref()
} }
/// The category of the value, if any. /// The category of the value, if any.
@ -356,6 +356,51 @@ pub enum Capturer {
Context, Context,
} }
/// Information about a deprecated binding.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct Deprecation {
/// A deprecation message for the definition.
message: &'static str,
/// A version in which the deprecated binding is planned to be removed.
until: Option<&'static str>,
}
impl Deprecation {
/// Creates new deprecation info with a default message to display when
/// emitting the deprecation warning.
pub fn new() -> Self {
Self { message: "item is deprecated", until: None }
}
/// Set the message to display when emitting the deprecation warning.
pub fn with_message(mut self, message: &'static str) -> Self {
self.message = message;
self
}
/// Set the version in which the binding is planned to be removed.
pub fn with_until(mut self, version: &'static str) -> Self {
self.until = Some(version);
self
}
/// The message to display when emitting the deprecation warning.
pub fn message(&self) -> &'static str {
self.message
}
/// The version in which the binding is planned to be removed.
pub fn until(&self) -> Option<&'static str> {
self.until
}
}
impl Default for Deprecation {
fn default() -> Self {
Self::new()
}
}
/// The error message when trying to mutate a variable from the standard /// The error message when trying to mutate a variable from the standard
/// library. /// library.
#[cold] #[cold]

View File

@ -151,7 +151,7 @@ impl Symbol {
modifiers.best_match_in(list.variants().map(|(m, _, d)| (m, d))) modifiers.best_match_in(list.variants().map(|(m, _, d)| (m, d)))
{ {
if let Some(message) = deprecation { if let Some(message) = deprecation {
sink.emit(message) sink.emit(message, None)
} }
return Ok(self); return Ok(self);
} }

View File

@ -412,9 +412,11 @@ impl Counter {
/// - If it is a string, creates a custom counter that is only affected /// - If it is a string, creates a custom counter that is only affected
/// by manual updates, /// by manual updates,
/// - If it is the [`page`] function, counts through pages, /// - If it is the [`page`] function, counts through pages,
/// - If it is a [selector], counts through elements that matches with the /// - If it is a [selector], counts through elements that match the
/// selector. For example, /// selector. For example,
/// - provide an element function: counts elements of that type, /// - provide an element function: counts elements of that type,
/// - provide a [`where`]($function.where) selector:
/// counts a type of element with specific fields,
/// - provide a [`{<label>}`]($label): counts elements with that label. /// - provide a [`{<label>}`]($label): counts elements with that label.
key: CounterKey, key: CounterKey,
) -> Counter { ) -> Counter {

View File

@ -6,7 +6,7 @@ use crate::foundations::{Array, Context, LocatableSelector, Value, func};
/// Finds elements in the document. /// Finds elements in the document.
/// ///
/// The `query` functions lets you search your document for elements of a /// The `query` function lets you search your document for elements of a
/// particular type or with a particular label. To use it, you first need to /// particular type or with a particular label. To use it, you first need to
/// ensure that [context] is available. /// ensure that [context] is available.
/// ///

View File

@ -549,7 +549,7 @@ pub struct GridHLine {
/// the grid's `row-gutter` option. /// the grid's `row-gutter` option.
#[elem(name = "vline", title = "Grid Vertical Line")] #[elem(name = "vline", title = "Grid Vertical Line")]
pub struct GridVLine { pub struct GridVLine {
/// The column before which the horizontal line is placed (zero-indexed). /// The column before which the vertical line is placed (zero-indexed).
/// If the `position` field is set to `{end}`, the line is placed after the /// If the `position` field is set to `{end}`, the line is placed after the
/// column with the given index instead (see that field's docs for /// column with the given index instead (see that field's docs for
/// details). /// details).

View File

@ -340,7 +340,7 @@ pub struct PageElem {
/// This content will overlay the page's body. /// This content will overlay the page's body.
/// ///
/// ```example /// ```example
/// #set page(foreground: text(24pt)[🥸]) /// #set page(foreground: text(24pt)[🤓])
/// ///
/// Reviewer 2 has marked our paper /// Reviewer 2 has marked our paper
/// "Weak Reject" because they did /// "Weak Reject" because they did
@ -397,6 +397,15 @@ impl LocalName for PageElem {
/// == Compound Theory /// == Compound Theory
/// In 1984, the first ... /// In 1984, the first ...
/// ``` /// ```
///
/// Even without manual page breaks, content will be automatically paginated
/// based on the configured page size. You can set [the page height]($page.height)
/// to `{auto}` to let the page grow dynamically until a manual page break
/// occurs.
///
/// Pagination tries to avoid single lines of text at the top or bottom of a
/// page (these are called _widows_ and _orphans_). You can adjust the
/// [`text.costs`]($text.costs) parameter to disable this behavior.
#[elem(title = "Page Break")] #[elem(title = "Page Break")]
pub struct PagebreakElem { pub struct PagebreakElem {
/// If `{true}`, the page break is skipped if the current page is already /// If `{true}`, the page break is skipped if the current page is already

View File

@ -33,7 +33,10 @@ pub fn cbor(
impl cbor { impl cbor {
/// Reads structured data from CBOR bytes. /// Reads structured data from CBOR bytes.
#[func(title = "Decode CBOR")] #[func(title = "Decode CBOR")]
#[deprecated = "`cbor.decode` is deprecated, directly pass bytes to `cbor` instead"] #[deprecated(
message = "`cbor.decode` is deprecated, directly pass bytes to `cbor` instead",
until = "0.15.0"
)]
pub fn decode( pub fn decode(
engine: &mut Engine, engine: &mut Engine,
/// CBOR data. /// CBOR data.

View File

@ -95,7 +95,10 @@ pub fn csv(
impl csv { impl csv {
/// Reads structured data from a CSV string/bytes. /// Reads structured data from a CSV string/bytes.
#[func(title = "Decode CSV")] #[func(title = "Decode CSV")]
#[deprecated = "`csv.decode` is deprecated, directly pass bytes to `csv` instead"] #[deprecated(
message = "`csv.decode` is deprecated, directly pass bytes to `csv` instead",
until = "0.15.0"
)]
pub fn decode( pub fn decode(
engine: &mut Engine, engine: &mut Engine,
/// CSV data. /// CSV data.

View File

@ -67,7 +67,10 @@ pub fn json(
impl json { impl json {
/// Reads structured data from a JSON string/bytes. /// Reads structured data from a JSON string/bytes.
#[func(title = "Decode JSON")] #[func(title = "Decode JSON")]
#[deprecated = "`json.decode` is deprecated, directly pass bytes to `json` instead"] #[deprecated(
message = "`json.decode` is deprecated, directly pass bytes to `json` instead",
until = "0.15.0"
)]
pub fn decode( pub fn decode(
engine: &mut Engine, engine: &mut Engine,
/// JSON data. /// JSON data.

View File

@ -41,7 +41,10 @@ pub fn toml(
impl toml { impl toml {
/// Reads structured data from a TOML string/bytes. /// Reads structured data from a TOML string/bytes.
#[func(title = "Decode TOML")] #[func(title = "Decode TOML")]
#[deprecated = "`toml.decode` is deprecated, directly pass bytes to `toml` instead"] #[deprecated(
message = "`toml.decode` is deprecated, directly pass bytes to `toml` instead",
until = "0.15.0"
)]
pub fn decode( pub fn decode(
engine: &mut Engine, engine: &mut Engine,
/// TOML data. /// TOML data.

View File

@ -75,7 +75,10 @@ pub fn xml(
impl xml { impl xml {
/// Reads structured data from an XML string/bytes. /// Reads structured data from an XML string/bytes.
#[func(title = "Decode XML")] #[func(title = "Decode XML")]
#[deprecated = "`xml.decode` is deprecated, directly pass bytes to `xml` instead"] #[deprecated(
message = "`xml.decode` is deprecated, directly pass bytes to `xml` instead",
until = "0.15.0"
)]
pub fn decode( pub fn decode(
engine: &mut Engine, engine: &mut Engine,
/// XML data. /// XML data.

View File

@ -54,7 +54,10 @@ pub fn yaml(
impl yaml { impl yaml {
/// Reads structured data from a YAML string/bytes. /// Reads structured data from a YAML string/bytes.
#[func(title = "Decode YAML")] #[func(title = "Decode YAML")]
#[deprecated = "`yaml.decode` is deprecated, directly pass bytes to `yaml` instead"] #[deprecated(
message = "`yaml.decode` is deprecated, directly pass bytes to `yaml` instead",
until = "0.15.0"
)]
pub fn decode( pub fn decode(
engine: &mut Engine, engine: &mut Engine,
/// YAML data. /// YAML data.

View File

@ -172,7 +172,6 @@ impl ShowSet for Packed<EquationElem> {
fn show_set(&self, styles: StyleChain) -> Styles { fn show_set(&self, styles: StyleChain) -> Styles {
let mut out = Styles::new(); let mut out = Styles::new();
if self.block.get(styles) { if self.block.get(styles) {
out.set(AlignElem::alignment, Alignment::CENTER);
out.set(AlignElem::alignment, Alignment::CENTER); out.set(AlignElem::alignment, Alignment::CENTER);
out.set(BlockElem::breakable, false); out.set(BlockElem::breakable, false);
out.set(ParLine::numbering, None); out.set(ParLine::numbering, None);

View File

@ -192,6 +192,39 @@ pub struct FigureElem {
/// supplement: [Atom], /// supplement: [Atom],
/// ) /// )
/// ``` /// ```
///
/// If you want to modify a counter to skip a number or reset the counter,
/// you can access the [counter] of each kind of figure with a
/// [`where`]($function.where) selector:
///
/// - For [tables]($table): `{counter(figure.where(kind: table))}`
/// - For [images]($image): `{counter(figure.where(kind: image))}`
/// - For a custom kind: `{counter(figure.where(kind: kind))}`
///
/// ```example
/// #figure(
/// table(columns: 2, $n$, $1$),
/// caption: [The first table.],
/// )
///
/// #counter(
/// figure.where(kind: table)
/// ).update(41)
///
/// #figure(
/// table(columns: 2, $n$, $42$),
/// caption: [The 42nd table],
/// )
///
/// #figure(
/// rect[Image],
/// caption: [Does not affect images],
/// )
/// ```
///
/// To conveniently use the correct counter in a show rule, you can access
/// the `counter` field. There is an example of this in the documentation
/// [of the `figure.caption` element's `body` field]($figure.caption.body).
pub kind: Smart<FigureKind>, pub kind: Smart<FigureKind>,
/// The figure's supplement. /// The figure's supplement.
@ -231,8 +264,8 @@ pub struct FigureElem {
/// Convenience field to get access to the counter for this figure. /// Convenience field to get access to the counter for this figure.
/// ///
/// The counter only depends on the `kind`: /// The counter only depends on the `kind`:
/// - For (tables)[@table]: `{counter(figure.where(kind: table))}` /// - For [tables]($table): `{counter(figure.where(kind: table))}`
/// - For (images)[@image]: `{counter(figure.where(kind: image))}` /// - For [images]($image): `{counter(figure.where(kind: image))}`
/// - For a custom kind: `{counter(figure.where(kind: kind))}` /// - For a custom kind: `{counter(figure.where(kind: kind))}`
/// ///
/// These are the counters you'll need to modify if you want to skip a /// These are the counters you'll need to modify if you want to skip a

View File

@ -534,7 +534,7 @@ pub struct TableHLine {
/// part of all your tables' designs. /// part of all your tables' designs.
#[elem(name = "vline", title = "Table Vertical Line")] #[elem(name = "vline", title = "Table Vertical Line")]
pub struct TableVLine { pub struct TableVLine {
/// The column before which the horizontal line is placed (zero-indexed). /// The column before which the vertical line is placed (zero-indexed).
/// Functions identically to the `x` field in [`grid.vline`]($grid.vline). /// Functions identically to the `x` field in [`grid.vline`]($grid.vline).
pub x: Smart<usize>, pub x: Smart<usize>,
@ -631,7 +631,7 @@ pub struct TableVLine {
/// cell(align: left)[🌴🚗], /// cell(align: left)[🌴🚗],
/// cell( /// cell(
/// inset: 0.06em, /// inset: 0.06em,
/// text(1.62em)[🛖🌅🌊], /// text(1.62em)[🏝️🌅🌊],
/// ), /// ),
/// ) /// )
/// ``` /// ```

View File

@ -1,6 +1,6 @@
//! Modifiable symbols. //! Modifiable symbols.
use crate::foundations::{Module, Scope, Symbol, Value}; use crate::foundations::{Deprecation, Module, Scope, Symbol, Value};
/// Hook up all `symbol` definitions. /// Hook up all `symbol` definitions.
pub(super) fn define(global: &mut Scope) { pub(super) fn define(global: &mut Scope) {
@ -23,7 +23,7 @@ fn extend_scope_from_codex_module(scope: &mut Scope, module: codex::Module) {
let scope_binding = scope.define(name, value); let scope_binding = scope.define(name, value);
if let Some(message) = binding.deprecation { if let Some(message) = binding.deprecation {
scope_binding.deprecated(message); scope_binding.deprecated(Deprecation::new().with_message(message));
} }
} }
} }

View File

@ -411,6 +411,9 @@ pub struct TextElem {
/// = Einleitung /// = Einleitung
/// In diesem Dokument, ... /// In diesem Dokument, ...
/// ``` /// ```
///
/// The language code is case-insensitive, and will be lowercased when
/// accessed through [context]($context).
#[default(Lang::ENGLISH)] #[default(Lang::ENGLISH)]
#[ghost] #[ghost]
pub lang: Lang, pub lang: Lang,
@ -418,6 +421,9 @@ pub struct TextElem {
/// An [ISO 3166-1 alpha-2 region code.](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) /// An [ISO 3166-1 alpha-2 region code.](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2)
/// ///
/// This lets the text processing pipeline make more informed choices. /// This lets the text processing pipeline make more informed choices.
///
/// The region code is case-insensitive, and will be uppercased when
/// accessed through [context]($context).
#[ghost] #[ghost]
pub region: Option<Region>, pub region: Option<Region>,

View File

@ -264,6 +264,7 @@ impl<'s> SmartQuotes<'s> {
"he" => ("", "", "", ""), "he" => ("", "", "", ""),
"hr" => ("", "", "", ""), "hr" => ("", "", "", ""),
"bg" => ("", "", "", ""), "bg" => ("", "", "", ""),
"ar" if !alternative => ("", "", "«", "»"),
_ if lang.dir() == Dir::RTL => ("", "", "", ""), _ if lang.dir() == Dir::RTL => ("", "", "", ""),
_ => default, _ => default,
}; };

View File

@ -179,7 +179,10 @@ pub struct ImageElem {
impl ImageElem { impl ImageElem {
/// Decode a raster or vector graphic from bytes or a string. /// Decode a raster or vector graphic from bytes or a string.
#[func(title = "Decode Image")] #[func(title = "Decode Image")]
#[deprecated = "`image.decode` is deprecated, directly pass bytes to `image` instead"] #[deprecated(
message = "`image.decode` is deprecated, directly pass bytes to `image` instead",
until = "0.15.0"
)]
pub fn decode( pub fn decode(
span: Span, span: Span,
/// The data to decode as an image. Can be a string for SVGs. /// The data to decode as an image. Can be a string for SVGs.

View File

@ -24,6 +24,7 @@ pub use self::shape::*;
pub use self::stroke::*; pub use self::stroke::*;
pub use self::tiling::*; pub use self::tiling::*;
use crate::foundations::Deprecation;
use crate::foundations::{Element, Scope, Type}; use crate::foundations::{Element, Scope, Type};
/// Hook up all visualize definitions. /// Hook up all visualize definitions.
@ -41,11 +42,14 @@ pub(super) fn define(global: &mut Scope) {
global.define_elem::<CircleElem>(); global.define_elem::<CircleElem>();
global.define_elem::<PolygonElem>(); global.define_elem::<PolygonElem>();
global.define_elem::<CurveElem>(); global.define_elem::<CurveElem>();
global global.define("path", Element::of::<PathElem>()).deprecated(
.define("path", Element::of::<PathElem>()) Deprecation::new()
.deprecated("the `path` function is deprecated, use `curve` instead"); .with_message("the `path` function is deprecated, use `curve` instead"),
global );
.define("pattern", Type::of::<Tiling>()) global.define("pattern", Type::of::<Tiling>()).deprecated(
.deprecated("the name `pattern` is deprecated, use `tiling` instead"); Deprecation::new()
.with_message("the name `pattern` is deprecated, use `tiling` instead")
.with_until("0.15.0"),
);
global.reset_category(); global.reset_category();
} }

View File

@ -1,7 +1,8 @@
use heck::ToKebabCase; use heck::ToKebabCase;
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use quote::quote; use quote::quote;
use syn::{Result, parse_quote}; use syn::punctuated::Punctuated;
use syn::{MetaNameValue, Result, Token, parse_quote};
use crate::util::{BareType, foundations}; use crate::util::{BareType, foundations};
@ -52,13 +53,36 @@ pub fn scope(_: TokenStream, item: syn::Item) -> Result<TokenStream> {
_ => bail!(child, "unexpected item in scope"), _ => bail!(child, "unexpected item in scope"),
}; };
if let Some(message) = attrs.iter().find_map(|attr| match &attr.meta { if let Some(attr) = attrs.iter().find(|attr| attr.path().is_ident("deprecated")) {
syn::Meta::NameValue(pair) if pair.path.is_ident("deprecated") => { match &attr.meta {
Some(&pair.value) syn::Meta::NameValue(pair) if pair.path.is_ident("deprecated") => {
let message = &pair.value;
def = quote! { #def.deprecated(#message) }
}
syn::Meta::List(list) if list.path.is_ident("deprecated") => {
let args = list.parse_args_with(
Punctuated::<MetaNameValue, Token![,]>::parse_separated_nonempty,
)?;
let mut deprecation =
quote! { crate::foundations::Deprecation::new() };
if let Some(message) = args.iter().find_map(|pair| {
pair.path.is_ident("message").then_some(&pair.value)
}) {
deprecation = quote! { #deprecation.with_message(#message) }
}
if let Some(version) = args.iter().find_map(|pair| {
pair.path.is_ident("until").then_some(&pair.value)
}) {
deprecation = quote! { #deprecation.with_until(#version) }
}
def = quote! { #def.deprecated(#deprecation) }
}
_ => {}
} }
_ => None,
}) {
def = quote! { #def.deprecated(#message) }
} }
definitions.push(def); definitions.push(def);

View File

@ -15,7 +15,6 @@ readme = { workspace = true }
[dependencies] [dependencies]
typst-timing = { workspace = true } typst-timing = { workspace = true }
typst-utils = { workspace = true } typst-utils = { workspace = true }
comemo = { workspace = true }
ecow = { workspace = true } ecow = { workspace = true }
serde = { workspace = true } serde = { workspace = true }
toml = { workspace = true } toml = { workspace = true }

View File

@ -143,6 +143,16 @@ pub struct PackageInfo {
} }
impl PackageManifest { impl PackageManifest {
/// Create a new package manifest with the given package info.
pub fn new(package: PackageInfo) -> Self {
PackageManifest {
package,
template: None,
tool: ToolInfo::default(),
unknown_fields: UnknownFields::new(),
}
}
/// Ensure that this manifest is indeed for the specified package. /// Ensure that this manifest is indeed for the specified package.
pub fn validate(&self, spec: &PackageSpec) -> Result<(), EcoString> { pub fn validate(&self, spec: &PackageSpec) -> Result<(), EcoString> {
if self.package.name != spec.name { if self.package.name != spec.name {
@ -173,6 +183,44 @@ impl PackageManifest {
} }
} }
impl TemplateInfo {
/// Create a new template info with only required fields.
pub fn new(path: impl Into<EcoString>, entrypoint: impl Into<EcoString>) -> Self {
TemplateInfo {
path: path.into(),
entrypoint: entrypoint.into(),
thumbnail: None,
unknown_fields: UnknownFields::new(),
}
}
}
impl PackageInfo {
/// Create a new package info with only required fields.
pub fn new(
name: impl Into<EcoString>,
version: PackageVersion,
entrypoint: impl Into<EcoString>,
) -> Self {
PackageInfo {
name: name.into(),
version,
entrypoint: entrypoint.into(),
authors: vec![],
categories: vec![],
compiler: None,
description: None,
disciplines: vec![],
exclude: vec![],
homepage: None,
keywords: vec![],
license: None,
repository: None,
unknown_fields: BTreeMap::new(),
}
}
}
/// Identifies a package. /// Identifies a package.
#[derive(Clone, Eq, PartialEq, Hash)] #[derive(Clone, Eq, PartialEq, Hash)]
pub struct PackageSpec { pub struct PackageSpec {
@ -535,22 +583,11 @@ mod tests {
"# "#
), ),
Ok(PackageManifest { Ok(PackageManifest {
package: PackageInfo { package: PackageInfo::new(
name: "package".into(), "package",
version: PackageVersion { major: 0, minor: 1, patch: 0 }, PackageVersion { major: 0, minor: 1, patch: 0 },
entrypoint: "src/lib.typ".into(), "src/lib.typ"
authors: vec![], ),
license: None,
description: None,
homepage: None,
repository: None,
keywords: vec![],
categories: vec![],
disciplines: vec![],
compiler: None,
exclude: vec![],
unknown_fields: BTreeMap::new(),
},
template: None, template: None,
tool: ToolInfo { sections: BTreeMap::new() }, tool: ToolInfo { sections: BTreeMap::new() },
unknown_fields: BTreeMap::new(), unknown_fields: BTreeMap::new(),

View File

@ -33,9 +33,10 @@ collaborative editor and run Typst in your browser, no installation required.
If you choose to use Typst on your computer instead, you can download the If you choose to use Typst on your computer instead, you can download the
compiler as a single, small binary which any user can run, no root privileges compiler as a single, small binary which any user can run, no root privileges
required. Unlike LaTeX, packages are downloaded when you first use them and required. Unlike popular LaTeX distributions such as TeX Live, packages are
then cached locally, keeping your Typst installation lean. You can use your own downloaded when you first use them and then cached locally, keeping your Typst
editor and decide where to store your files with the local compiler. installation lean. You can use your own editor and decide where to store your
files with the local compiler.
## How do I create a new, empty document? { #getting-started } ## How do I create a new, empty document? { #getting-started }
That's easy. You just create a new, empty text file (the file extension is That's easy. You just create a new, empty text file (the file extension is
@ -459,7 +460,7 @@ and their corresponding Typst functions.
| LaTeX Package | Typst Alternative | | LaTeX Package | Typst Alternative |
|:--------------------------------|:-------------------------------------------| |:--------------------------------|:-------------------------------------------|
| graphicx, svg | [`image`] function | | graphicx, svg | [`image`] function |
| tabularx | [`table`], [`grid`] functions | | tabularx, tabularray | [`table`], [`grid`] functions |
| fontenc, inputenc, unicode-math | Just start writing! | | fontenc, inputenc, unicode-math | Just start writing! |
| babel, polyglossia | [`text`]($text.lang) function: `[#set text(lang: "zh")]` | | babel, polyglossia | [`text`]($text.lang) function: `[#set text(lang: "zh")]` |
| amsmath | [Math mode]($category/math) | | amsmath | [Math mode]($category/math) |
@ -550,8 +551,8 @@ $ f(x) = (x + 1) / x $
to include more than one value in a sub- or superscript, enclose their contents to include more than one value in a sub- or superscript, enclose their contents
in parentheses: `{$x_(a -> epsilon)$}`. in parentheses: `{$x_(a -> epsilon)$}`.
Since variables in math mode do not need to be prepended with a `#` or a `/`, Since variables in math mode do not need to be prepended with a `#` (or a `\`
you can also call functions without these special characters: like in LaTeX), you can also call functions without these special characters:
```example ```example
$ f(x, y) := cases( $ f(x, y) := cases(
@ -580,8 +581,8 @@ their call with a `#`. Nobody can stop you from using rectangles or emoji as
your variables anymore: your variables anymore:
```example ```example
$ sum^10_(🥸=1) $ sum^10_(🤓=1)
#rect(width: 4mm, height: 2mm)/🥸 #rect(width: 4mm, height: 2mm)/🤓
= 🧠 maltese $ = 🧠 maltese $
``` ```

View File

@ -3,3 +3,6 @@ with a normal keyboard. Alternatively, you can also always directly enter
Unicode symbols into your text and formulas. In addition to the symbols listed 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 below, math mode defines `dif` and `Dif`. These are not normal symbol values
because they also affect spacing and font style. because they also affect spacing and font style.
You can define custom symbols with the constructor function of the
[symbol]($symbol) type.

View File

@ -17,6 +17,7 @@ use serde::Deserialize;
use serde_yaml as yaml; use serde_yaml as yaml;
use std::sync::LazyLock; use std::sync::LazyLock;
use typst::diag::{StrResult, bail}; use typst::diag::{StrResult, bail};
use typst::foundations::Deprecation;
use typst::foundations::{ use typst::foundations::{
AutoValue, Binding, Bytes, CastInfo, Func, Module, NoneValue, ParamInfo, Repr, Scope, AutoValue, Binding, Bytes, CastInfo, Func, Module, NoneValue, ParamInfo, Repr, Scope,
Smart, Type, Value, Smart, Type, Value,
@ -381,7 +382,7 @@ fn func_page(
parent: &str, parent: &str,
func: &Func, func: &Func,
path: &[&str], path: &[&str],
deprecation: Option<&'static str>, deprecation: Option<&Deprecation>,
) -> PageModel { ) -> PageModel {
let model = func_model(resolver, func, path, false, deprecation); let model = func_model(resolver, func, path, false, deprecation);
let name = func.name().unwrap(); let name = func.name().unwrap();
@ -402,7 +403,7 @@ fn func_model(
func: &Func, func: &Func,
path: &[&str], path: &[&str],
nested: bool, nested: bool,
deprecation: Option<&'static str>, deprecation: Option<&Deprecation>,
) -> FuncModel { ) -> FuncModel {
let name = func.name().unwrap(); let name = func.name().unwrap();
let scope = func.scope().unwrap(); let scope = func.scope().unwrap();
@ -438,7 +439,8 @@ fn func_model(
oneliner: oneliner(details), oneliner: oneliner(details),
element: func.element().is_some(), element: func.element().is_some(),
contextual: func.contextual().unwrap_or(false), contextual: func.contextual().unwrap_or(false),
deprecation, deprecation_message: deprecation.map(Deprecation::message),
deprecation_until: deprecation.and_then(Deprecation::until),
details: Html::markdown(resolver, details, nesting), details: Html::markdown(resolver, details, nesting),
example: example.map(|md| Html::markdown(resolver, md, None)), example: example.map(|md| Html::markdown(resolver, md, None)),
self_, self_,
@ -718,7 +720,7 @@ fn symbols_model(resolver: &dyn Resolver, group: &GroupData) -> SymbolsModel {
} }
}; };
for (variant, c, deprecation) in symbol.variants() { for (variant, c, deprecation_message) in symbol.variants() {
let shorthand = |list: &[(&'static str, char)]| { let shorthand = |list: &[(&'static str, char)]| {
list.iter().copied().find(|&(_, x)| x == c).map(|(s, _)| s) list.iter().copied().find(|&(_, x)| x == c).map(|(s, _)| s)
}; };
@ -737,7 +739,9 @@ fn symbols_model(resolver: &dyn Resolver, group: &GroupData) -> SymbolsModel {
.filter(|(other, _, _)| other != &variant) .filter(|(other, _, _)| other != &variant)
.map(|(other, _, _)| complete(other)) .map(|(other, _, _)| complete(other))
.collect(), .collect(),
deprecation: deprecation.or_else(|| binding.deprecation()), deprecation_message: deprecation_message
.or_else(|| binding.deprecation().map(Deprecation::message)),
deprecation_until: binding.deprecation().and_then(Deprecation::until),
}); });
} }
} }

View File

@ -89,7 +89,8 @@ pub struct FuncModel {
pub oneliner: EcoString, pub oneliner: EcoString,
pub element: bool, pub element: bool,
pub contextual: bool, pub contextual: bool,
pub deprecation: Option<&'static str>, pub deprecation_message: Option<&'static str>,
pub deprecation_until: Option<&'static str>,
pub details: Html, pub details: Html,
/// This example is only for nested function models. Others can have /// This example is only for nested function models. Others can have
/// their example directly in their details. /// their example directly in their details.
@ -165,7 +166,8 @@ pub struct SymbolModel {
pub markup_shorthand: Option<&'static str>, pub markup_shorthand: Option<&'static str>,
pub math_shorthand: Option<&'static str>, pub math_shorthand: Option<&'static str>,
pub math_class: Option<&'static str>, pub math_class: Option<&'static str>,
pub deprecation: Option<&'static str>, pub deprecation_message: Option<&'static str>,
pub deprecation_until: Option<&'static str>,
} }
/// Shorthands listed on a category page. /// Shorthands listed on a category page.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 959 B

After

Width:  |  Height:  |  Size: 969 B

View File

@ -1,3 +1,4 @@
--- cbor-decode-deprecated --- --- cbor-decode-deprecated ---
// Warning: 15-21 `cbor.decode` is deprecated, directly pass bytes to `cbor` instead // Warning: 15-21 `cbor.decode` is deprecated, directly pass bytes to `cbor` instead
// Hint: 15-21 it will be removed in Typst 0.15.0
#let _ = cbor.decode #let _ = cbor.decode

View File

@ -32,4 +32,5 @@
--- csv-decode-deprecated --- --- csv-decode-deprecated ---
// Warning: 14-20 `csv.decode` is deprecated, directly pass bytes to `csv` instead // Warning: 14-20 `csv.decode` is deprecated, directly pass bytes to `csv` instead
// Hint: 14-20 it will be removed in Typst 0.15.0
#let _ = csv.decode #let _ = csv.decode

View File

@ -11,6 +11,7 @@
--- json-decode-deprecated --- --- json-decode-deprecated ---
// Warning: 15-21 `json.decode` is deprecated, directly pass bytes to `json` instead // Warning: 15-21 `json.decode` is deprecated, directly pass bytes to `json` instead
// Hint: 15-21 it will be removed in Typst 0.15.0
#let _ = json.decode #let _ = json.decode
--- issue-3363-json-large-number --- --- issue-3363-json-large-number ---

View File

@ -42,4 +42,5 @@
--- toml-decode-deprecated --- --- toml-decode-deprecated ---
// Warning: 15-21 `toml.decode` is deprecated, directly pass bytes to `toml` instead // Warning: 15-21 `toml.decode` is deprecated, directly pass bytes to `toml` instead
// Hint: 15-21 it will be removed in Typst 0.15.0
#let _ = toml.decode #let _ = toml.decode

View File

@ -29,4 +29,5 @@
--- xml-decode-deprecated --- --- xml-decode-deprecated ---
// Warning: 14-20 `xml.decode` is deprecated, directly pass bytes to `xml` instead // Warning: 14-20 `xml.decode` is deprecated, directly pass bytes to `xml` instead
// Hint: 14-20 it will be removed in Typst 0.15.0
#let _ = xml.decode #let _ = xml.decode

View File

@ -18,4 +18,5 @@
--- yaml-decode-deprecated --- --- yaml-decode-deprecated ---
// Warning: 15-21 `yaml.decode` is deprecated, directly pass bytes to `yaml` instead // Warning: 15-21 `yaml.decode` is deprecated, directly pass bytes to `yaml` instead
// Hint: 15-21 it will be removed in Typst 0.15.0
#let _ = yaml.decode #let _ = yaml.decode

View File

@ -188,26 +188,31 @@ A #box(image("/assets/images/tiger.jpg", height: 1cm, width: 80%)) B
--- image-decode-svg --- --- image-decode-svg ---
// Test parsing from svg data // Test parsing from svg data
// Warning: 8-14 `image.decode` is deprecated, directly pass bytes to `image` instead // Warning: 8-14 `image.decode` is deprecated, directly pass bytes to `image` instead
// Hint: 8-14 it will be removed in Typst 0.15.0
#image.decode(`<svg xmlns="http://www.w3.org/2000/svg" height="140" width="500"><ellipse cx="200" cy="80" rx="100" ry="50" style="fill:yellow;stroke:purple;stroke-width:2" /></svg>`.text, format: "svg") #image.decode(`<svg xmlns="http://www.w3.org/2000/svg" height="140" width="500"><ellipse cx="200" cy="80" rx="100" ry="50" style="fill:yellow;stroke:purple;stroke-width:2" /></svg>`.text, format: "svg")
--- image-decode-bad-svg --- --- image-decode-bad-svg ---
// Error: 15-152 failed to parse SVG (missing root node at 1:1) // Error: 15-152 failed to parse SVG (missing root node at 1:1)
// Warning: 8-14 `image.decode` is deprecated, directly pass bytes to `image` instead // Warning: 8-14 `image.decode` is deprecated, directly pass bytes to `image` instead
// Hint: 8-14 it will be removed in Typst 0.15.0
#image.decode(`<svg height="140" width="500"><ellipse cx="200" cy="80" rx="100" ry="50" style="fill:yellow;stroke:purple;stroke-width:2" /></svg>`.text, format: "svg") #image.decode(`<svg height="140" width="500"><ellipse cx="200" cy="80" rx="100" ry="50" style="fill:yellow;stroke:purple;stroke-width:2" /></svg>`.text, format: "svg")
--- image-decode-detect-format --- --- image-decode-detect-format ---
// Test format auto detect // Test format auto detect
// Warning: 8-14 `image.decode` is deprecated, directly pass bytes to `image` instead // Warning: 8-14 `image.decode` is deprecated, directly pass bytes to `image` instead
// Hint: 8-14 it will be removed in Typst 0.15.0
#image.decode(read("/assets/images/tiger.jpg", encoding: none), width: 80%) #image.decode(read("/assets/images/tiger.jpg", encoding: none), width: 80%)
--- image-decode-specify-format --- --- image-decode-specify-format ---
// Test format manual // Test format manual
// Warning: 8-14 `image.decode` is deprecated, directly pass bytes to `image` instead // Warning: 8-14 `image.decode` is deprecated, directly pass bytes to `image` instead
// Hint: 8-14 it will be removed in Typst 0.15.0
#image.decode(read("/assets/images/tiger.jpg", encoding: none), format: "jpg", width: 80%) #image.decode(read("/assets/images/tiger.jpg", encoding: none), format: "jpg", width: 80%)
--- image-decode-specify-wrong-format --- --- image-decode-specify-wrong-format ---
// Error: 2-91 failed to decode image (Format error decoding Png: Invalid PNG signature.) // Error: 2-91 failed to decode image (Format error decoding Png: Invalid PNG signature.)
// Warning: 8-14 `image.decode` is deprecated, directly pass bytes to `image` instead // Warning: 8-14 `image.decode` is deprecated, directly pass bytes to `image` instead
// Hint: 8-14 it will be removed in Typst 0.15.0
#image.decode(read("/assets/images/tiger.jpg", encoding: none), format: "png", width: 80%) #image.decode(read("/assets/images/tiger.jpg", encoding: none), format: "png", width: 80%)
--- image-pixmap-empty --- --- image-pixmap-empty ---

View File

@ -161,5 +161,6 @@
#set page(width: auto, height: auto, margin: 0pt) #set page(width: auto, height: auto, margin: 0pt)
// Warning: 10-17 the name `pattern` is deprecated, use `tiling` instead // Warning: 10-17 the name `pattern` is deprecated, use `tiling` instead
// Hint: 10-17 it will be removed in Typst 0.15.0
#let t = pattern(size: (10pt, 10pt), line(stroke: 4pt, start: (0%, 0%), end: (100%, 100%))) #let t = pattern(size: (10pt, 10pt), line(stroke: 4pt, start: (0%, 0%), end: (100%, 100%)))
#rect(width: 50pt, height: 50pt, fill: t) #rect(width: 50pt, height: 50pt, fill: t)