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"
version = "0.13.1"
dependencies = [
"comemo",
"ecow",
"serde",
"toml",

View File

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

View File

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

View File

@ -173,6 +173,7 @@ impl SystemWorld {
/// Reset the compilation state in preparation of a new compilation.
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() {
slot.reset();
}

View File

@ -709,9 +709,11 @@ fn complete_params(ctx: &mut CompletionContext) -> bool {
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()
&& (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() {
ctx.from = ctx.cursor.min(next.offset());
@ -891,7 +893,10 @@ fn complete_code(ctx: &mut CompletionContext) -> bool {
}
// 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();
code_completions(ctx, false);
return true;
@ -904,11 +909,19 @@ fn complete_code(ctx: &mut CompletionContext) -> bool {
return true;
}
// Anywhere: "{ | }".
// But not within or after an expression.
// Anywhere: "{ | }", "(|)", "(1,|)", "(a:|)".
// But not within or after an expression, and also not part of a dictionary
// key (as in "(pa: |,)")
if ctx.explicit
&& ctx.leaf.parent_kind() != Some(SyntaxKind::Dict)
&& (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;
code_completions(ctx, false);
@ -1560,6 +1573,7 @@ mod tests {
trait ResponseExt {
fn completions(&self) -> &[Completion];
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_exclude<'a>(&self, excludes: impl IntoIterator<Item = &'a str>) -> &Self;
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()
}
#[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]
fn must_include<'a>(&self, includes: impl IntoIterator<Item = &'a str>) -> &Self {
let labels = self.labels();
@ -1622,7 +1646,15 @@ mod tests {
let world = world.acquire();
let world = world.borrow();
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]
@ -1635,7 +1667,7 @@ mod tests {
let doc = typst::compile(&world).output.ok();
let end = world.main.text().len();
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]
@ -1643,11 +1675,12 @@ mod tests {
world: impl WorldLike,
pos: impl FilePos,
doc: Option<&PagedDocument>,
explicit: bool,
) -> Response {
let world = world.acquire();
let world = world.borrow();
let (source, cursor) = pos.resolve(world);
autocomplete(world, doc, &source, cursor, true)
autocomplete(world, doc, &source, cursor, explicit)
}
#[test]
@ -1698,7 +1731,7 @@ mod tests {
let end = world.main.text().len();
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_exclude(["bib"]);
}
@ -1853,26 +1886,105 @@ mod tests {
#[test]
fn test_autocomplete_fonts() {
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)
.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)
.must_include(["\"New Computer Modern Math\""])
.must_exclude(["\"Libertinus Serif\""]);
.must_include([q!("New Computer Modern Math")])
.must_exclude([q!("Libertinus Serif")]);
test("#show math.equation: it => { set text(font: )\nit }", -7)
.must_include(["\"New Computer Modern Math\""])
.must_exclude(["\"Libertinus Serif\""]);
.must_include([q!("New Computer Modern Math")])
.must_exclude([q!("Libertinus Serif")]);
}
#[test]
fn test_autocomplete_typed_html() {
test("#html.div(translate: )", -2)
.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.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.
pub trait DeprecationSink {
/// Emits the given deprecation message into this sink.
fn emit(self, message: &str);
/// Emits the given deprecation message into this sink alongside a version
/// in which the deprecated item is planned to be removed.
fn emit(self, message: &str, until: Option<&str>);
}
impl DeprecationSink for () {
fn emit(self, _: &str) {}
fn emit(self, _: &str, _: Option<&str>) {}
}
impl DeprecationSink for (&mut Engine<'_>, Span) {
/// Emits the deprecation message as a warning.
fn emit(self, message: &str) {
self.0.sink.warn(SourceDiagnostic::warning(self.1, message));
fn emit(self, message: &str, version: Option<&str>) {
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,
/// The category of the binding.
category: Option<Category>,
/// A deprecation message for the definition.
deprecation: Option<&'static str>,
/// The deprecation information if this item is deprecated.
deprecation: Option<Box<Deprecation>>,
}
/// The different kinds of slots.
@ -284,8 +284,8 @@ impl Binding {
}
/// Marks this binding as deprecated, with the given `message`.
pub fn deprecated(&mut self, message: &'static str) -> &mut Self {
self.deprecation = Some(message);
pub fn deprecated(&mut self, deprecation: Deprecation) -> &mut Self {
self.deprecation = Some(Box::new(deprecation));
self
}
@ -300,8 +300,8 @@ impl Binding {
/// - pass `()` to ignore the message.
/// - pass `(&mut engine, span)` to emit a warning into the engine.
pub fn read_checked(&self, sink: impl DeprecationSink) -> &Value {
if let Some(message) = self.deprecation {
sink.emit(message);
if let Some(info) = &self.deprecation {
sink.emit(info.message, info.until);
}
&self.value
}
@ -337,8 +337,8 @@ impl Binding {
}
/// A deprecation message for the value, if any.
pub fn deprecation(&self) -> Option<&'static str> {
self.deprecation
pub fn deprecation(&self) -> Option<&Deprecation> {
self.deprecation.as_deref()
}
/// The category of the value, if any.
@ -356,6 +356,51 @@ pub enum Capturer {
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
/// library.
#[cold]

View File

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

View File

@ -412,9 +412,11 @@ impl Counter {
/// - If it is a string, creates a custom counter that is only affected
/// by manual updates,
/// - 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,
/// - 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.
key: CounterKey,
) -> Counter {

View File

@ -6,7 +6,7 @@ use crate::foundations::{Array, Context, LocatableSelector, Value, func};
/// 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
/// ensure that [context] is available.
///

View File

@ -549,7 +549,7 @@ pub struct GridHLine {
/// the grid's `row-gutter` option.
#[elem(name = "vline", title = "Grid Vertical Line")]
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
/// column with the given index instead (see that field's docs for
/// details).

View File

@ -340,7 +340,7 @@ pub struct PageElem {
/// This content will overlay the page's body.
///
/// ```example
/// #set page(foreground: text(24pt)[🥸])
/// #set page(foreground: text(24pt)[🤓])
///
/// Reviewer 2 has marked our paper
/// "Weak Reject" because they did
@ -397,6 +397,15 @@ impl LocalName for PageElem {
/// == Compound Theory
/// 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")]
pub struct PagebreakElem {
/// If `{true}`, the page break is skipped if the current page is already

View File

@ -33,7 +33,10 @@ pub fn cbor(
impl cbor {
/// Reads structured data from CBOR bytes.
#[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(
engine: &mut Engine,
/// CBOR data.

View File

@ -95,7 +95,10 @@ pub fn csv(
impl csv {
/// Reads structured data from a CSV string/bytes.
#[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(
engine: &mut Engine,
/// CSV data.

View File

@ -67,7 +67,10 @@ pub fn json(
impl json {
/// Reads structured data from a JSON string/bytes.
#[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(
engine: &mut Engine,
/// JSON data.

View File

@ -41,7 +41,10 @@ pub fn toml(
impl toml {
/// Reads structured data from a TOML string/bytes.
#[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(
engine: &mut Engine,
/// TOML data.

View File

@ -75,7 +75,10 @@ pub fn xml(
impl xml {
/// Reads structured data from an XML string/bytes.
#[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(
engine: &mut Engine,
/// XML data.

View File

@ -54,7 +54,10 @@ pub fn yaml(
impl yaml {
/// Reads structured data from a YAML string/bytes.
#[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(
engine: &mut Engine,
/// YAML data.

View File

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

View File

@ -192,6 +192,39 @@ pub struct FigureElem {
/// 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>,
/// The figure's supplement.
@ -231,8 +264,8 @@ pub struct FigureElem {
/// Convenience field to get access to the counter for this figure.
///
/// The counter only depends on the `kind`:
/// - For (tables)[@table]: `{counter(figure.where(kind: table))}`
/// - For (images)[@image]: `{counter(figure.where(kind: image))}`
/// - 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))}`
///
/// 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.
#[elem(name = "vline", title = "Table Vertical Line")]
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).
pub x: Smart<usize>,
@ -631,7 +631,7 @@ pub struct TableVLine {
/// cell(align: left)[🌴🚗],
/// cell(
/// inset: 0.06em,
/// text(1.62em)[🛖🌅🌊],
/// text(1.62em)[🏝️🌅🌊],
/// ),
/// )
/// ```

View File

@ -1,6 +1,6 @@
//! Modifiable symbols.
use crate::foundations::{Module, Scope, Symbol, Value};
use crate::foundations::{Deprecation, Module, Scope, Symbol, Value};
/// Hook up all `symbol` definitions.
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);
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
/// In diesem Dokument, ...
/// ```
///
/// The language code is case-insensitive, and will be lowercased when
/// accessed through [context]($context).
#[default(Lang::ENGLISH)]
#[ghost]
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)
///
/// 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]
pub region: Option<Region>,

View File

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

View File

@ -179,7 +179,10 @@ pub struct ImageElem {
impl ImageElem {
/// Decode a raster or vector graphic from bytes or a string.
#[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(
span: Span,
/// 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::tiling::*;
use crate::foundations::Deprecation;
use crate::foundations::{Element, Scope, Type};
/// Hook up all visualize definitions.
@ -41,11 +42,14 @@ pub(super) fn define(global: &mut Scope) {
global.define_elem::<CircleElem>();
global.define_elem::<PolygonElem>();
global.define_elem::<CurveElem>();
global
.define("path", Element::of::<PathElem>())
.deprecated("the `path` function is deprecated, use `curve` instead");
global
.define("pattern", Type::of::<Tiling>())
.deprecated("the name `pattern` is deprecated, use `tiling` instead");
global.define("path", Element::of::<PathElem>()).deprecated(
Deprecation::new()
.with_message("the `path` function is deprecated, use `curve` instead"),
);
global.define("pattern", Type::of::<Tiling>()).deprecated(
Deprecation::new()
.with_message("the name `pattern` is deprecated, use `tiling` instead")
.with_until("0.15.0"),
);
global.reset_category();
}

View File

@ -1,7 +1,8 @@
use heck::ToKebabCase;
use proc_macro2::TokenStream;
use quote::quote;
use syn::{Result, parse_quote};
use syn::punctuated::Punctuated;
use syn::{MetaNameValue, Result, Token, parse_quote};
use crate::util::{BareType, foundations};
@ -52,14 +53,37 @@ pub fn scope(_: TokenStream, item: syn::Item) -> Result<TokenStream> {
_ => 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")) {
match &attr.meta {
syn::Meta::NameValue(pair) if pair.path.is_ident("deprecated") => {
Some(&pair.value)
}
_ => None,
}) {
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) }
}
_ => {}
}
}
definitions.push(def);
}

View File

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

View File

@ -143,6 +143,16 @@ pub struct PackageInfo {
}
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.
pub fn validate(&self, spec: &PackageSpec) -> Result<(), EcoString> {
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.
#[derive(Clone, Eq, PartialEq, Hash)]
pub struct PackageSpec {
@ -535,22 +583,11 @@ mod tests {
"#
),
Ok(PackageManifest {
package: PackageInfo {
name: "package".into(),
version: PackageVersion { major: 0, minor: 1, patch: 0 },
entrypoint: "src/lib.typ".into(),
authors: vec![],
license: None,
description: None,
homepage: None,
repository: None,
keywords: vec![],
categories: vec![],
disciplines: vec![],
compiler: None,
exclude: vec![],
unknown_fields: BTreeMap::new(),
},
package: PackageInfo::new(
"package",
PackageVersion { major: 0, minor: 1, patch: 0 },
"src/lib.typ"
),
template: None,
tool: ToolInfo { sections: 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
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
then cached locally, keeping your Typst installation lean. You can use your own
editor and decide where to store your files with the local compiler.
required. Unlike popular LaTeX distributions such as TeX Live, packages are
downloaded when you first use them and then cached locally, keeping your Typst
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 }
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 |
|:--------------------------------|:-------------------------------------------|
| graphicx, svg | [`image`] function |
| tabularx | [`table`], [`grid`] functions |
| tabularx, tabularray | [`table`], [`grid`] functions |
| fontenc, inputenc, unicode-math | Just start writing! |
| babel, polyglossia | [`text`]($text.lang) function: `[#set text(lang: "zh")]` |
| 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
in parentheses: `{$x_(a -> epsilon)$}`.
Since variables in math mode do not need to be prepended with a `#` or a `/`,
you can also call functions without these special characters:
Since variables in math mode do not need to be prepended with a `#` (or a `\`
like in LaTeX), you can also call functions without these special characters:
```example
$ 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:
```example
$ sum^10_(🥸=1)
#rect(width: 4mm, height: 2mm)/🥸
$ sum^10_(🤓=1)
#rect(width: 4mm, height: 2mm)/🤓
= 🧠 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
below, math mode defines `dif` and `Dif`. These are not normal symbol values
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 std::sync::LazyLock;
use typst::diag::{StrResult, bail};
use typst::foundations::Deprecation;
use typst::foundations::{
AutoValue, Binding, Bytes, CastInfo, Func, Module, NoneValue, ParamInfo, Repr, Scope,
Smart, Type, Value,
@ -381,7 +382,7 @@ fn func_page(
parent: &str,
func: &Func,
path: &[&str],
deprecation: Option<&'static str>,
deprecation: Option<&Deprecation>,
) -> PageModel {
let model = func_model(resolver, func, path, false, deprecation);
let name = func.name().unwrap();
@ -402,7 +403,7 @@ fn func_model(
func: &Func,
path: &[&str],
nested: bool,
deprecation: Option<&'static str>,
deprecation: Option<&Deprecation>,
) -> FuncModel {
let name = func.name().unwrap();
let scope = func.scope().unwrap();
@ -438,7 +439,8 @@ fn func_model(
oneliner: oneliner(details),
element: func.element().is_some(),
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),
example: example.map(|md| Html::markdown(resolver, md, None)),
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)]| {
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)
.map(|(other, _, _)| complete(other))
.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 element: bool,
pub contextual: bool,
pub deprecation: Option<&'static str>,
pub deprecation_message: Option<&'static str>,
pub deprecation_until: Option<&'static str>,
pub details: Html,
/// This example is only for nested function models. Others can have
/// their example directly in their details.
@ -165,7 +166,8 @@ pub struct SymbolModel {
pub markup_shorthand: Option<&'static str>,
pub math_shorthand: 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.

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 ---
// 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

View File

@ -32,4 +32,5 @@
--- csv-decode-deprecated ---
// 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

View File

@ -11,6 +11,7 @@
--- json-decode-deprecated ---
// 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
--- issue-3363-json-large-number ---

View File

@ -42,4 +42,5 @@
--- toml-decode-deprecated ---
// 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

View File

@ -29,4 +29,5 @@
--- xml-decode-deprecated ---
// 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

View File

@ -18,4 +18,5 @@
--- yaml-decode-deprecated ---
// 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

View File

@ -188,26 +188,31 @@ A #box(image("/assets/images/tiger.jpg", height: 1cm, width: 80%)) B
--- image-decode-svg ---
// Test parsing from svg data
// 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-bad-svg ---
// 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
// 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-detect-format ---
// Test format auto detect
// 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-specify-format ---
// Test format manual
// 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-specify-wrong-format ---
// 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
// 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-pixmap-empty ---

View File

@ -161,5 +161,6 @@
#set page(width: auto, height: auto, margin: 0pt)
// 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%)))
#rect(width: 50pt, height: 50pt, fill: t)