Compare commits

...

14 Commits

Author SHA1 Message Date
stelzo 8bef52630c
1.85 2025-02-20 23:40:08 +01:00
stelzo 710c7dedfe
disable fuzzing 2025-02-20 23:20:43 +01:00
stelzo 8403097443
fix clippy 2025-02-20 23:10:31 +01:00
stelzo d3a3ddd1c4
fix clippy 2025-02-20 23:07:20 +01:00
stelzo a8e94784b4
fix transmute 2025-02-20 23:00:22 +01:00
stelzo 89e0a84ac8
update ci 2025-02-20 22:52:07 +01:00
stelzo 0cde01c1d1
merge 0.13 updates 2025-02-20 22:33:27 +01:00
Laurenz 8dce676dcd Version bump 2025-02-19 11:13:25 +01:00
Laurenz c02cb70f27 Update changelog (#5894) 2025-02-19 11:07:46 +01:00
Matthew Toohey 0a534f2c0e --make-deps fixes (#5873) 2025-02-18 19:19:16 +01:00
ᡥᠠᡳᡤᡳᠶᠠ ᡥᠠᠯᠠ·ᠨᡝᡴᠣ 猫 de16a2ced1 HTML export: Use `<code>` for inline `RawElem` (#5884) 2025-02-18 11:25:46 +01:00
Laurenz d48708c5d5 More robust SVG auto-detection (#5878) 2025-02-17 12:49:15 +01:00
Laurenz e294fe85a5
Bring back type/str compatibility for 0.13, with warnings and hints (#5877) 2025-02-17 11:52:11 +01:00
Laurenz 2f1a5ab914 Remove Linux Libertine warning (#5876) 2025-02-16 15:13:42 +01:00
30 changed files with 477 additions and 170 deletions

View File

@ -30,7 +30,7 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@1.83.0
- uses: dtolnay/rust-toolchain@1.85.0
- uses: Swatinem/rust-cache@v2
- run: cargo test --workspace --no-run
- run: cargo test --workspace --no-fail-fast
@ -59,7 +59,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@1.83.0
- uses: dtolnay/rust-toolchain@1.85.0
with:
components: clippy, rustfmt
- uses: Swatinem/rust-cache@v2
@ -73,18 +73,18 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@1.80.0
- uses: dtolnay/rust-toolchain@1.85.0
- uses: Swatinem/rust-cache@v2
- run: cargo check --workspace
fuzz:
name: Check fuzzers
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly-2024-10-29
- uses: Swatinem/rust-cache@v2
- run: cargo install --locked cargo-fuzz@0.12.0
- run: cd tests/fuzz && cargo fuzz build --dev
#fuzz:
# name: Check fuzzers
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v4
# - uses: dtolnay/rust-toolchain@master
# with:
# toolchain: nightly-2024-10-29
# - uses: Swatinem/rust-cache@v2
# - run: cargo install --locked cargo-fuzz@0.12.0
# - run: cd tests/fuzz && cargo fuzz build --dev

View File

@ -44,7 +44,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@1.83.0
- uses: dtolnay/rust-toolchain@1.85.0
with:
target: ${{ matrix.target }}

48
Cargo.lock generated
View File

@ -2735,7 +2735,7 @@ checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a"
[[package]]
name = "typst"
version = "0.13.0-rc1"
version = "0.13.0"
dependencies = [
"comemo",
"ecow",
@ -2752,12 +2752,13 @@ dependencies = [
[[package]]
name = "typst-assets"
version = "0.13.0-rc1"
source = "git+https://github.com/typst/typst-assets?rev=7eb87f5#7eb87f5496aff556ace09cf574d11d90d90543ca"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1051c56bbbf74d31ea6c6b1661e62fa0ebb8104403ee53f6dcd321600426e0b6"
[[package]]
name = "typst-cli"
version = "0.13.0-rc1"
version = "0.13.0"
dependencies = [
"chrono",
"clap",
@ -2802,12 +2803,12 @@ dependencies = [
[[package]]
name = "typst-dev-assets"
version = "0.12.0"
source = "git+https://github.com/typst/typst-dev-assets?rev=7f8999d#7f8999d19907cd6e1148b295efbc844921c0761c"
version = "0.13.0"
source = "git+https://github.com/typst/typst-dev-assets?tag=v0.13.0#61aebe9575a5abff889f76d73c7b01dc8e17e340"
[[package]]
name = "typst-docs"
version = "0.13.0-rc1"
version = "0.13.0"
dependencies = [
"clap",
"ecow",
@ -2830,7 +2831,7 @@ dependencies = [
[[package]]
name = "typst-eval"
version = "0.13.0-rc1"
version = "0.13.0"
dependencies = [
"comemo",
"ecow",
@ -2848,7 +2849,7 @@ dependencies = [
[[package]]
name = "typst-fuzz"
version = "0.13.0-rc1"
version = "0.13.0"
dependencies = [
"comemo",
"libfuzzer-sys",
@ -2860,7 +2861,7 @@ dependencies = [
[[package]]
name = "typst-html"
version = "0.13.0-rc1"
version = "0.13.0"
dependencies = [
"comemo",
"ecow",
@ -2874,7 +2875,7 @@ dependencies = [
[[package]]
name = "typst-ide"
version = "0.13.0-rc1"
version = "0.13.0"
dependencies = [
"comemo",
"ecow",
@ -2891,7 +2892,7 @@ dependencies = [
[[package]]
name = "typst-kit"
version = "0.13.0-rc1"
version = "0.13.0"
dependencies = [
"dirs",
"ecow",
@ -2914,7 +2915,7 @@ dependencies = [
[[package]]
name = "typst-layout"
version = "0.13.0-rc1"
version = "0.13.0"
dependencies = [
"az",
"bumpalo",
@ -2944,7 +2945,7 @@ dependencies = [
[[package]]
name = "typst-library"
version = "0.13.0-rc1"
version = "0.13.0"
dependencies = [
"az",
"bitflags 2.8.0",
@ -2966,6 +2967,7 @@ dependencies = [
"kamadak-exif",
"kurbo",
"lipsum",
"memchr",
"palette",
"phf",
"png",
@ -3003,7 +3005,7 @@ dependencies = [
[[package]]
name = "typst-macros"
version = "0.13.0-rc1"
version = "0.13.0"
dependencies = [
"heck",
"proc-macro2",
@ -3013,7 +3015,7 @@ dependencies = [
[[package]]
name = "typst-pdf"
version = "0.13.0-rc1"
version = "0.13.0"
dependencies = [
"arrayvec",
"base64",
@ -3039,7 +3041,7 @@ dependencies = [
[[package]]
name = "typst-realize"
version = "0.13.0-rc1"
version = "0.13.0"
dependencies = [
"arrayvec",
"bumpalo",
@ -3055,7 +3057,7 @@ dependencies = [
[[package]]
name = "typst-render"
version = "0.13.0-rc1"
version = "0.13.0"
dependencies = [
"bytemuck",
"comemo",
@ -3071,7 +3073,7 @@ dependencies = [
[[package]]
name = "typst-svg"
version = "0.13.0-rc1"
version = "0.13.0"
dependencies = [
"base64",
"comemo",
@ -3089,7 +3091,7 @@ dependencies = [
[[package]]
name = "typst-syntax"
version = "0.13.0-rc1"
version = "0.13.0"
dependencies = [
"ecow",
"serde",
@ -3105,7 +3107,7 @@ dependencies = [
[[package]]
name = "typst-tests"
version = "0.13.0-rc1"
version = "0.13.0"
dependencies = [
"clap",
"comemo",
@ -3130,7 +3132,7 @@ dependencies = [
[[package]]
name = "typst-timing"
version = "0.13.0-rc1"
version = "0.13.0"
dependencies = [
"parking_lot",
"serde",
@ -3140,7 +3142,7 @@ dependencies = [
[[package]]
name = "typst-utils"
version = "0.13.0-rc1"
version = "0.13.0"
dependencies = [
"once_cell",
"portable-atomic",

View File

@ -4,7 +4,7 @@ default-members = ["crates/typst-cli"]
resolver = "2"
[workspace.package]
version = "0.13.0-rc1"
version = "0.13.0"
rust-version = "1.80" # also change in ci.yml
authors = ["The Typst Project Developers"]
edition = "2021"
@ -16,24 +16,24 @@ keywords = ["typst"]
readme = "README.md"
[workspace.dependencies]
typst = { path = "crates/typst", version = "0.13.0-rc1" }
typst-cli = { path = "crates/typst-cli", version = "0.13.0-rc1" }
typst-eval = { path = "crates/typst-eval", version = "0.13.0-rc1" }
typst-html = { path = "crates/typst-html", version = "0.13.0-rc1" }
typst-ide = { path = "crates/typst-ide", version = "0.13.0-rc1" }
typst-kit = { path = "crates/typst-kit", version = "0.13.0-rc1" }
typst-layout = { path = "crates/typst-layout", version = "0.13.0-rc1" }
typst-library = { path = "crates/typst-library", version = "0.13.0-rc1" }
typst-macros = { path = "crates/typst-macros", version = "0.13.0-rc1" }
typst-pdf = { path = "crates/typst-pdf", version = "0.13.0-rc1" }
typst-realize = { path = "crates/typst-realize", version = "0.13.0-rc1" }
typst-render = { path = "crates/typst-render", version = "0.13.0-rc1" }
typst-svg = { path = "crates/typst-svg", version = "0.13.0-rc1" }
typst-syntax = { path = "crates/typst-syntax", version = "0.13.0-rc1" }
typst-timing = { path = "crates/typst-timing", version = "0.13.0-rc1" }
typst-utils = { path = "crates/typst-utils", version = "0.13.0-rc1" }
typst-assets = { git = "https://github.com/typst/typst-assets", rev = "7eb87f5" }
typst-dev-assets = { git = "https://github.com/typst/typst-dev-assets", rev = "7f8999d" }
typst = { path = "crates/typst", version = "0.13.0" }
typst-cli = { path = "crates/typst-cli", version = "0.13.0" }
typst-eval = { path = "crates/typst-eval", version = "0.13.0" }
typst-html = { path = "crates/typst-html", version = "0.13.0" }
typst-ide = { path = "crates/typst-ide", version = "0.13.0" }
typst-kit = { path = "crates/typst-kit", version = "0.13.0" }
typst-layout = { path = "crates/typst-layout", version = "0.13.0" }
typst-library = { path = "crates/typst-library", version = "0.13.0" }
typst-macros = { path = "crates/typst-macros", version = "0.13.0" }
typst-pdf = { path = "crates/typst-pdf", version = "0.13.0" }
typst-realize = { path = "crates/typst-realize", version = "0.13.0" }
typst-render = { path = "crates/typst-render", version = "0.13.0" }
typst-svg = { path = "crates/typst-svg", version = "0.13.0" }
typst-syntax = { path = "crates/typst-syntax", version = "0.13.0" }
typst-timing = { path = "crates/typst-timing", version = "0.13.0" }
typst-utils = { path = "crates/typst-utils", version = "0.13.0" }
typst-assets = "0.13.0"
typst-dev-assets = { git = "https://github.com/typst/typst-dev-assets", tag = "v0.13.0" }
arrayvec = "0.7.4"
az = "1.2"
base64 = "0.22"
@ -73,6 +73,7 @@ kamadak-exif = "0.6"
kurbo = "0.11"
libfuzzer-sys = "0.4"
lipsum = "0.9"
memchr = "2"
miniz_oxide = "0.8"
native-tls = "0.2"
notify = "8"

View File

@ -6,8 +6,9 @@ use std::path::{Path, PathBuf};
use chrono::{DateTime, Datelike, Timelike, Utc};
use codespan_reporting::diagnostic::{Diagnostic, Label};
use codespan_reporting::term;
use ecow::{eco_format, EcoString};
use ecow::eco_format;
use parking_lot::RwLock;
use pathdiff::diff_paths;
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
use typst::diag::{
bail, At, Severity, SourceDiagnostic, SourceResult, StrResult, Warned,
@ -188,7 +189,7 @@ pub fn compile_once(
match output {
// Export the PDF / PNG.
Ok(()) => {
Ok(outputs) => {
let duration = start.elapsed();
if config.watching {
@ -202,7 +203,7 @@ pub fn compile_once(
print_diagnostics(world, &[], &warnings, config.diagnostic_format)
.map_err(|err| eco_format!("failed to print diagnostics ({err})"))?;
write_make_deps(world, config)?;
write_make_deps(world, config, outputs)?;
open_output(config)?;
}
@ -226,12 +227,15 @@ pub fn compile_once(
fn compile_and_export(
world: &mut SystemWorld,
config: &mut CompileConfig,
) -> Warned<SourceResult<()>> {
) -> Warned<SourceResult<Vec<Output>>> {
match config.output_format {
OutputFormat::Html => {
let Warned { output, warnings } = typst::compile::<HtmlDocument>(world);
let result = output.and_then(|document| export_html(&document, config));
Warned { output: result, warnings }
Warned {
output: result.map(|()| vec![config.output.clone()]),
warnings,
}
}
_ => {
let Warned { output, warnings } = typst::compile::<PagedDocument>(world);
@ -257,9 +261,14 @@ fn export_html(document: &HtmlDocument, config: &CompileConfig) -> SourceResult<
}
/// Export to a paged target format.
fn export_paged(document: &PagedDocument, config: &CompileConfig) -> SourceResult<()> {
fn export_paged(
document: &PagedDocument,
config: &CompileConfig,
) -> SourceResult<Vec<Output>> {
match config.output_format {
OutputFormat::Pdf => export_pdf(document, config),
OutputFormat::Pdf => {
export_pdf(document, config).map(|()| vec![config.output.clone()])
}
OutputFormat::Png => {
export_image(document, config, ImageExportFormat::Png).at(Span::detached())
}
@ -327,7 +336,7 @@ fn export_image(
document: &PagedDocument,
config: &CompileConfig,
fmt: ImageExportFormat,
) -> StrResult<()> {
) -> StrResult<Vec<Output>> {
// Determine whether we have indexable templates in output
let can_handle_multiple = match config.output {
Output::Stdout => false,
@ -383,7 +392,7 @@ fn export_image(
&& config.export_cache.is_cached(*i, &page.frame)
&& path.exists()
{
return Ok(());
return Ok(Output::Path(path.to_path_buf()));
}
Output::Path(path.to_owned())
@ -392,11 +401,9 @@ fn export_image(
};
export_image_page(config, page, &output, fmt)?;
Ok(())
Ok(output)
})
.collect::<Result<Vec<()>, EcoString>>()?;
Ok(())
.collect::<StrResult<Vec<Output>>>()
}
mod output_template {
@ -501,14 +508,25 @@ impl ExportCache {
/// Writes a Makefile rule describing the relationship between the output and
/// its dependencies to the path specified by the --make-deps argument, if it
/// was provided.
fn write_make_deps(world: &mut SystemWorld, config: &CompileConfig) -> StrResult<()> {
fn write_make_deps(
world: &mut SystemWorld,
config: &CompileConfig,
outputs: Vec<Output>,
) -> StrResult<()> {
let Some(ref make_deps_path) = config.make_deps else { return Ok(()) };
let Output::Path(output_path) = &config.output else {
bail!("failed to create make dependencies file because output was stdout")
};
let Some(output_path) = output_path.as_os_str().to_str() else {
let Ok(output_paths) = outputs
.into_iter()
.filter_map(|o| match o {
Output::Path(path) => Some(path.into_os_string().into_string()),
Output::Stdout => None,
})
.collect::<Result<Vec<_>, _>>()
else {
bail!("failed to create make dependencies file because output path was not valid unicode")
};
if output_paths.is_empty() {
bail!("failed to create make dependencies file because output was stdout")
}
// Based on `munge` in libcpp/mkdeps.cc from the GCC source code. This isn't
// perfect as some special characters can't be escaped.
@ -522,6 +540,10 @@ fn write_make_deps(world: &mut SystemWorld, config: &CompileConfig) -> StrResult
res.push('$');
slashes = 0;
}
':' => {
res.push('\\');
slashes = 0;
}
' ' | '\t' => {
// `munge`'s source contains a comment here that says: "A
// space or tab preceded by 2N+1 backslashes represents N
@ -544,18 +566,29 @@ fn write_make_deps(world: &mut SystemWorld, config: &CompileConfig) -> StrResult
fn write(
make_deps_path: &Path,
output_path: &str,
output_paths: Vec<String>,
root: PathBuf,
dependencies: impl Iterator<Item = PathBuf>,
) -> io::Result<()> {
let mut file = File::create(make_deps_path)?;
let current_dir = std::env::current_dir()?;
let relative_root = diff_paths(&root, &current_dir).unwrap_or(root.clone());
file.write_all(munge(output_path).as_bytes())?;
for (i, output_path) in output_paths.into_iter().enumerate() {
if i != 0 {
file.write_all(b" ")?;
}
file.write_all(munge(&output_path).as_bytes())?;
}
file.write_all(b":")?;
for dependency in dependencies {
let Some(dependency) =
dependency.strip_prefix(&root).unwrap_or(&dependency).to_str()
else {
let relative_dependency = match dependency.strip_prefix(&root) {
Ok(root_relative_dependency) => {
relative_root.join(root_relative_dependency)
}
Err(_) => dependency,
};
let Some(relative_dependency) = relative_dependency.to_str() else {
// Silently skip paths that aren't valid unicode so we still
// produce a rule that will work for the other paths that can be
// processed.
@ -563,14 +596,14 @@ fn write_make_deps(world: &mut SystemWorld, config: &CompileConfig) -> StrResult
};
file.write_all(b" ")?;
file.write_all(munge(dependency).as_bytes())?;
file.write_all(munge(relative_dependency).as_bytes())?;
}
file.write_all(b"\n")?;
Ok(())
}
write(make_deps_path, output_path, world.root().to_owned(), world.dependencies())
write(make_deps_path, output_paths, world.root().to_owned(), world.dependencies())
.map_err(|err| {
eco_format!("failed to create make dependencies file due to IO error ({err})")
})

View File

@ -55,7 +55,7 @@ fn eval_code<'a>(
_ => expr.eval(vm)?,
};
output = ops::join(output, value).at(span)?;
output = ops::join(output, value, &mut (&mut vm.engine, span)).at(span)?;
if let Some(event) = &vm.flow {
warn_for_discarded_content(&mut vm.engine, event, &output);

View File

@ -83,7 +83,8 @@ impl Eval for ast::WhileLoop<'_> {
}
let value = body.eval(vm)?;
output = ops::join(output, value).at(body.span())?;
let span = body.span();
output = ops::join(output, value, &mut (&mut vm.engine, span)).at(span)?;
match vm.flow {
Some(FlowEvent::Break(_)) => {
@ -129,7 +130,9 @@ impl Eval for ast::ForLoop<'_> {
let body = self.body();
let value = body.eval(vm)?;
output = ops::join(output, value).at(body.span())?;
let span = body.span();
output =
ops::join(output, value, &mut (&mut vm.engine, span)).at(span)?;
match vm.flow {
Some(FlowEvent::Break(_)) => {

View File

@ -1,4 +1,4 @@
use typst_library::diag::{At, HintedStrResult, SourceResult};
use typst_library::diag::{At, DeprecationSink, HintedStrResult, SourceResult};
use typst_library::foundations::{ops, IntoValue, Value};
use typst_syntax::ast::{self, AstNode};
@ -23,22 +23,22 @@ impl Eval for ast::Binary<'_> {
fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
match self.op() {
ast::BinOp::Add => apply_binary(self, vm, ops::add),
ast::BinOp::Add => apply_binary_with_sink(self, vm, ops::add),
ast::BinOp::Sub => apply_binary(self, vm, ops::sub),
ast::BinOp::Mul => apply_binary(self, vm, ops::mul),
ast::BinOp::Div => apply_binary(self, vm, ops::div),
ast::BinOp::And => apply_binary(self, vm, ops::and),
ast::BinOp::Or => apply_binary(self, vm, ops::or),
ast::BinOp::Eq => apply_binary(self, vm, ops::eq),
ast::BinOp::Neq => apply_binary(self, vm, ops::neq),
ast::BinOp::Eq => apply_binary_with_sink(self, vm, ops::eq),
ast::BinOp::Neq => apply_binary_with_sink(self, vm, ops::neq),
ast::BinOp::Lt => apply_binary(self, vm, ops::lt),
ast::BinOp::Leq => apply_binary(self, vm, ops::leq),
ast::BinOp::Gt => apply_binary(self, vm, ops::gt),
ast::BinOp::Geq => apply_binary(self, vm, ops::geq),
ast::BinOp::In => apply_binary(self, vm, ops::in_),
ast::BinOp::NotIn => apply_binary(self, vm, ops::not_in),
ast::BinOp::In => apply_binary_with_sink(self, vm, ops::in_),
ast::BinOp::NotIn => apply_binary_with_sink(self, vm, ops::not_in),
ast::BinOp::Assign => apply_assignment(self, vm, |_, b| Ok(b)),
ast::BinOp::AddAssign => apply_assignment(self, vm, ops::add),
ast::BinOp::AddAssign => apply_assignment_with_sink(self, vm, ops::add),
ast::BinOp::SubAssign => apply_assignment(self, vm, ops::sub),
ast::BinOp::MulAssign => apply_assignment(self, vm, ops::mul),
ast::BinOp::DivAssign => apply_assignment(self, vm, ops::div),
@ -65,6 +65,18 @@ fn apply_binary(
op(lhs, rhs).at(binary.span())
}
/// Apply a basic binary operation, with the possiblity of deprecations.
fn apply_binary_with_sink(
binary: ast::Binary,
vm: &mut Vm,
op: impl Fn(Value, Value, &mut dyn DeprecationSink) -> HintedStrResult<Value>,
) -> SourceResult<Value> {
let span = binary.span();
let lhs = binary.lhs().eval(vm)?;
let rhs = binary.rhs().eval(vm)?;
op(lhs, rhs, &mut (&mut vm.engine, span)).at(span)
}
/// Apply an assignment operation.
fn apply_assignment(
binary: ast::Binary,
@ -89,3 +101,23 @@ fn apply_assignment(
*location = op(lhs, rhs).at(binary.span())?;
Ok(Value::None)
}
/// Apply an assignment operation, with the possiblity of deprecations.
fn apply_assignment_with_sink(
binary: ast::Binary,
vm: &mut Vm,
op: fn(Value, Value, &mut dyn DeprecationSink) -> HintedStrResult<Value>,
) -> SourceResult<Value> {
let rhs = binary.rhs().eval(vm)?;
let location = binary.lhs().access(vm)?;
let lhs = std::mem::take(&mut *location);
let mut sink = vec![];
let span = binary.span();
*location = op(lhs, rhs, &mut (&mut sink, span)).at(span)?;
if !sink.is_empty() {
for warning in sink {
vm.engine.sink.warn(warning);
}
}
Ok(Value::None)
}

View File

@ -154,7 +154,7 @@ pub fn line<'a>(
let mut items = collect_items(engine, p, range, trim);
// Add a hyphen at the line start, if a previous dash should be repeated.
if pred.map_or(false, |pred| should_repeat_hyphen(pred, full)) {
if pred.is_some_and(|pred| should_repeat_hyphen(pred, full)) {
if let Some(shaped) = items.first_text_mut() {
shaped.prepend_hyphen(engine, p.config.fallback);
}
@ -406,7 +406,7 @@ fn should_repeat_hyphen(pred_line: &Line, text: &str) -> bool {
//
// See § 4.1.1.1.2.e on the "Ortografía de la lengua española"
// https://www.rae.es/ortografía/como-signo-de-división-de-palabras-a-final-de-línea
Lang::SPANISH => text.chars().next().map_or(false, |c| !c.is_uppercase()),
Lang::SPANISH => text.chars().next().is_some_and(|c| !c.is_uppercase()),
_ => false,
}

View File

@ -38,6 +38,7 @@ indexmap = { workspace = true }
kamadak-exif = { workspace = true }
kurbo = { workspace = true }
lipsum = { workspace = true }
memchr = { workspace = true }
palette = { workspace = true }
phf = { workspace = true }
png = { workspace = true }

View File

@ -232,18 +232,42 @@ 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);
fn emit(&mut self, message: &str);
/// Emits the given deprecation message into this sink, with the given
/// hints.
fn emit_with_hints(&mut self, message: &str, hints: &[&str]);
}
impl DeprecationSink for () {
fn emit(self, _: &str) {}
fn emit(&mut self, _: &str) {}
fn emit_with_hints(&mut self, _: &str, _: &[&str]) {}
}
impl DeprecationSink for (&mut Vec<SourceDiagnostic>, Span) {
fn emit(&mut self, message: &str) {
self.0.push(SourceDiagnostic::warning(self.1, message));
}
fn emit_with_hints(&mut self, message: &str, hints: &[&str]) {
self.0.push(
SourceDiagnostic::warning(self.1, message)
.with_hints(hints.iter().copied().map(Into::into)),
);
}
}
impl DeprecationSink for (&mut Engine<'_>, Span) {
/// Emits the deprecation message as a warning.
fn emit(self, message: &str) {
fn emit(&mut self, message: &str) {
self.0.sink.warn(SourceDiagnostic::warning(self.1, message));
}
fn emit_with_hints(&mut self, message: &str, hints: &[&str]) {
self.0.sink.warn(
SourceDiagnostic::warning(self.1, message)
.with_hints(hints.iter().copied().map(Into::into)),
);
}
}
/// A part of a diagnostic's [trace](SourceDiagnostic::trace).

View File

@ -9,7 +9,9 @@ use serde::{Deserialize, Serialize};
use smallvec::SmallVec;
use typst_syntax::{Span, Spanned};
use crate::diag::{bail, At, HintedStrResult, SourceDiagnostic, SourceResult, StrResult};
use crate::diag::{
bail, At, DeprecationSink, HintedStrResult, SourceDiagnostic, SourceResult, StrResult,
};
use crate::engine::Engine;
use crate::foundations::{
cast, func, ops, repr, scope, ty, Args, Bytes, CastInfo, Context, Dict, FromValue,
@ -143,6 +145,11 @@ impl Array {
Ok(self.iter().cloned().cycle().take(count).collect())
}
/// The internal implementation of [`Array::contains`].
pub fn contains_impl(&self, value: &Value, sink: &mut dyn DeprecationSink) -> bool {
self.0.iter().any(|v| ops::equal(v, value, sink))
}
}
#[scope]
@ -290,10 +297,12 @@ impl Array {
#[func]
pub fn contains(
&self,
engine: &mut Engine,
span: Span,
/// The value to search for.
value: Value,
) -> bool {
self.0.contains(&value)
self.contains_impl(&value, &mut (engine, span))
}
/// Searches for an item for which the given function returns `{true}` and
@ -576,6 +585,8 @@ impl Array {
#[func]
pub fn sum(
self,
engine: &mut Engine,
span: Span,
/// What to return if the array is empty. Must be set if the array can
/// be empty.
#[named]
@ -587,7 +598,7 @@ impl Array {
.or(default)
.ok_or("cannot calculate sum of empty array with no default")?;
for item in iter {
acc = ops::add(acc, item)?;
acc = ops::add(acc, item, &mut (&mut *engine, span))?;
}
Ok(acc)
}
@ -686,6 +697,8 @@ impl Array {
#[func]
pub fn join(
self,
engine: &mut Engine,
span: Span,
/// A value to insert between each item of the array.
#[default]
separator: Option<Value>,
@ -701,13 +714,18 @@ impl Array {
for (i, value) in self.into_iter().enumerate() {
if i > 0 {
if i + 1 == len && last.is_some() {
result = ops::join(result, last.take().unwrap())?;
result = ops::join(
result,
last.take().unwrap(),
&mut (&mut *engine, span),
)?;
} else {
result = ops::join(result, separator.clone())?;
result =
ops::join(result, separator.clone(), &mut (&mut *engine, span))?;
}
}
result = ops::join(result, value)?;
result = ops::join(result, value, &mut (&mut *engine, span))?;
}
Ok(result)
@ -862,13 +880,14 @@ impl Array {
self,
engine: &mut Engine,
context: Tracked<Context>,
span: Span,
/// If given, applies this function to the elements in the array to
/// determine the keys to deduplicate by.
#[named]
key: Option<Func>,
) -> SourceResult<Array> {
let mut out = EcoVec::with_capacity(self.0.len());
let mut key_of = |x: Value| match &key {
let key_of = |engine: &mut Engine, x: Value| match &key {
// NOTE: We are relying on `comemo`'s memoization of function
// evaluation to not excessively reevaluate the `key`.
Some(f) => f.call(engine, context, [x]),
@ -879,14 +898,18 @@ impl Array {
// 1. We would like to preserve the order of the elements.
// 2. We cannot hash arbitrary `Value`.
'outer: for value in self {
let key = key_of(value.clone())?;
let key = key_of(&mut *engine, value.clone())?;
if out.is_empty() {
out.push(value);
continue;
}
for second in out.iter() {
if ops::equal(&key, &key_of(second.clone())?) {
if ops::equal(
&key,
&key_of(&mut *engine, second.clone())?,
&mut (&mut *engine, span),
) {
continue 'outer;
}
}

View File

@ -440,7 +440,7 @@ impl PartialEq for Func {
impl PartialEq<&NativeFuncData> for Func {
fn eq(&self, other: &&NativeFuncData) -> bool {
match &self.repr {
Repr::Native(native) => native.function == other.function,
Repr::Native(native) => std::ptr::fn_addr_eq(native.function, other.function),
_ => false,
}
}

View File

@ -5,7 +5,7 @@ use std::cmp::Ordering;
use ecow::eco_format;
use typst_utils::Numeric;
use crate::diag::{bail, HintedStrResult, StrResult};
use crate::diag::{bail, DeprecationSink, HintedStrResult, StrResult};
use crate::foundations::{
format_str, Datetime, IntoValue, Regex, Repr, SymbolElem, Value,
};
@ -21,7 +21,7 @@ macro_rules! mismatch {
}
/// Join a value with another value.
pub fn join(lhs: Value, rhs: Value) -> StrResult<Value> {
pub fn join(lhs: Value, rhs: Value, sink: &mut dyn DeprecationSink) -> StrResult<Value> {
use Value::*;
Ok(match (lhs, rhs) {
(a, None) => a,
@ -39,6 +39,17 @@ pub fn join(lhs: Value, rhs: Value) -> StrResult<Value> {
(Array(a), Array(b)) => Array(a + b),
(Dict(a), Dict(b)) => Dict(a + b),
(Args(a), Args(b)) => Args(a + b),
// Type compatibility.
(Type(a), Str(b)) => {
warn_type_str_join(sink);
Str(format_str!("{a}{b}"))
}
(Str(a), Type(b)) => {
warn_type_str_join(sink);
Str(format_str!("{a}{b}"))
}
(a, b) => mismatch!("cannot join {} with {}", a, b),
})
}
@ -88,7 +99,11 @@ pub fn neg(value: Value) -> HintedStrResult<Value> {
}
/// Compute the sum of two values.
pub fn add(lhs: Value, rhs: Value) -> HintedStrResult<Value> {
pub fn add(
lhs: Value,
rhs: Value,
sink: &mut dyn DeprecationSink,
) -> HintedStrResult<Value> {
use Value::*;
Ok(match (lhs, rhs) {
(a, None) => a,
@ -156,6 +171,16 @@ pub fn add(lhs: Value, rhs: Value) -> HintedStrResult<Value> {
(Datetime(a), Duration(b)) => Datetime(a + b),
(Duration(a), Datetime(b)) => Datetime(b + a),
// Type compatibility.
(Type(a), Str(b)) => {
warn_type_str_add(sink);
Str(format_str!("{a}{b}"))
}
(Str(a), Type(b)) => {
warn_type_str_add(sink);
Str(format_str!("{a}{b}"))
}
(Dyn(a), Dyn(b)) => {
// Alignments can be summed.
if let (Some(&a), Some(&b)) =
@ -394,13 +419,21 @@ pub fn or(lhs: Value, rhs: Value) -> HintedStrResult<Value> {
}
/// Compute whether two values are equal.
pub fn eq(lhs: Value, rhs: Value) -> HintedStrResult<Value> {
Ok(Value::Bool(equal(&lhs, &rhs)))
pub fn eq(
lhs: Value,
rhs: Value,
sink: &mut dyn DeprecationSink,
) -> HintedStrResult<Value> {
Ok(Value::Bool(equal(&lhs, &rhs, sink)))
}
/// Compute whether two values are unequal.
pub fn neq(lhs: Value, rhs: Value) -> HintedStrResult<Value> {
Ok(Value::Bool(!equal(&lhs, &rhs)))
pub fn neq(
lhs: Value,
rhs: Value,
sink: &mut dyn DeprecationSink,
) -> HintedStrResult<Value> {
Ok(Value::Bool(!equal(&lhs, &rhs, sink)))
}
macro_rules! comparison {
@ -419,7 +452,7 @@ comparison!(gt, ">", Ordering::Greater);
comparison!(geq, ">=", Ordering::Greater | Ordering::Equal);
/// Determine whether two values are equal.
pub fn equal(lhs: &Value, rhs: &Value) -> bool {
pub fn equal(lhs: &Value, rhs: &Value, sink: &mut dyn DeprecationSink) -> bool {
use Value::*;
match (lhs, rhs) {
// Compare reflexively.
@ -463,6 +496,12 @@ pub fn equal(lhs: &Value, rhs: &Value) -> bool {
rat == rel.rel && rel.abs.is_zero()
}
// Type compatibility.
(Type(ty), Str(str)) | (Str(str), Type(ty)) => {
warn_type_str_equal(sink);
ty.compat_name() == str.as_str()
}
_ => false,
}
}
@ -534,8 +573,12 @@ fn try_cmp_arrays(a: &[Value], b: &[Value]) -> StrResult<Ordering> {
}
/// Test whether one value is "in" another one.
pub fn in_(lhs: Value, rhs: Value) -> HintedStrResult<Value> {
if let Some(b) = contains(&lhs, &rhs) {
pub fn in_(
lhs: Value,
rhs: Value,
sink: &mut dyn DeprecationSink,
) -> HintedStrResult<Value> {
if let Some(b) = contains(&lhs, &rhs, sink) {
Ok(Value::Bool(b))
} else {
mismatch!("cannot apply 'in' to {} and {}", lhs, rhs)
@ -543,8 +586,12 @@ pub fn in_(lhs: Value, rhs: Value) -> HintedStrResult<Value> {
}
/// Test whether one value is "not in" another one.
pub fn not_in(lhs: Value, rhs: Value) -> HintedStrResult<Value> {
if let Some(b) = contains(&lhs, &rhs) {
pub fn not_in(
lhs: Value,
rhs: Value,
sink: &mut dyn DeprecationSink,
) -> HintedStrResult<Value> {
if let Some(b) = contains(&lhs, &rhs, sink) {
Ok(Value::Bool(!b))
} else {
mismatch!("cannot apply 'not in' to {} and {}", lhs, rhs)
@ -552,13 +599,27 @@ pub fn not_in(lhs: Value, rhs: Value) -> HintedStrResult<Value> {
}
/// Test for containment.
pub fn contains(lhs: &Value, rhs: &Value) -> Option<bool> {
pub fn contains(
lhs: &Value,
rhs: &Value,
sink: &mut dyn DeprecationSink,
) -> Option<bool> {
use Value::*;
match (lhs, rhs) {
(Str(a), Str(b)) => Some(b.as_str().contains(a.as_str())),
(Dyn(a), Str(b)) => a.downcast::<Regex>().map(|regex| regex.is_match(b)),
(Str(a), Dict(b)) => Some(b.contains(a)),
(a, Array(b)) => Some(b.contains(a.clone())),
(a, Array(b)) => Some(b.contains_impl(a, sink)),
// Type compatibility.
(Type(a), Str(b)) => {
warn_type_in_str(sink);
Some(b.as_str().contains(a.compat_name()))
}
(Type(a), Dict(b)) => {
warn_type_in_dict(sink);
Some(b.contains(a.compat_name()))
}
_ => Option::None,
}
@ -568,3 +629,46 @@ pub fn contains(lhs: &Value, rhs: &Value) -> Option<bool> {
fn too_large() -> &'static str {
"value is too large"
}
#[cold]
fn warn_type_str_add(sink: &mut dyn DeprecationSink) {
sink.emit_with_hints(
"adding strings and types is deprecated",
&["convert the type to a string with `str` first"],
);
}
#[cold]
fn warn_type_str_join(sink: &mut dyn DeprecationSink) {
sink.emit_with_hints(
"joining strings and types is deprecated",
&["convert the type to a string with `str` first"],
);
}
#[cold]
fn warn_type_str_equal(sink: &mut dyn DeprecationSink) {
sink.emit_with_hints(
"comparing strings with types is deprecated",
&[
"compare with the literal type instead",
"this comparison will always return `false` in future Typst releases",
],
);
}
#[cold]
fn warn_type_in_str(sink: &mut dyn DeprecationSink) {
sink.emit_with_hints(
"checking whether a type is contained in a string is deprecated",
&["this compatibility behavior only exists because `type` used to return a string"],
);
}
#[cold]
fn warn_type_in_dict(sink: &mut dyn DeprecationSink) {
sink.emit_with_hints(
"checking whether a type is contained in a dictionary is deprecated",
&["this compatibility behavior only exists because `type` used to return a string"],
);
}

View File

@ -300,7 +300,7 @@ impl Binding {
/// As the `sink`
/// - 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 {
pub fn read_checked(&self, mut sink: impl DeprecationSink) -> &Value {
if let Some(message) = self.deprecation {
sink.emit(message);
}

View File

@ -44,6 +44,16 @@ use crate::foundations::{
/// #type(int) \
/// #type(type)
/// ```
///
/// # Compatibility
/// In Typst 0.7 and lower, the `type` function returned a string instead of a
/// type. Compatibility with the old way will remain until Typst 0.14 to give
/// package authors time to upgrade.
///
/// - Checks like `{int == "integer"}` evaluate to `{true}`
/// - Adding/joining a type and string will yield a string
/// - The `{in}` operator on a type and a dictionary will evaluate to `{true}`
/// if the dictionary has a string key matching the type's name
#[ty(scope, cast)]
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub struct Type(Static<NativeTypeData>);
@ -106,6 +116,14 @@ impl Type {
}
}
// Type compatibility.
impl Type {
/// The type's backward-compatible name.
pub fn compat_name(&self) -> &str {
self.long_name()
}
}
#[scope]
impl Type {
/// Determines a value's type.

View File

@ -292,7 +292,8 @@ impl Repr for Value {
impl PartialEq for Value {
fn eq(&self, other: &Self) -> bool {
ops::equal(self, other)
// No way to emit deprecation warnings here :(
ops::equal(self, other, &mut ())
}
}

View File

@ -1387,7 +1387,7 @@ impl<'a> CellGrid<'a> {
// Include the gutter right before the footer, unless there is
// none, or the gutter is already included in the header (no
// rows between the header and the footer).
if header_end.map_or(true, |header_end| header_end != footer.start) {
if header_end != Some(footer.start) {
footer.start = footer.start.saturating_sub(1);
}
}

View File

@ -1380,24 +1380,7 @@ pub fn is_default_ignorable(c: char) -> bool {
fn check_font_list(engine: &mut Engine, list: &Spanned<FontList>) {
let book = engine.world.book();
for family in &list.v {
let found = book.contains_family(family.as_str());
if family.as_str() == "linux libertine" {
let mut warning = warning!(
list.span,
"Typst's default font has changed from Linux Libertine to its successor Libertinus Serif";
hint: "please set the font to `\"Libertinus Serif\"` instead"
);
if found {
warning.hint(
"Linux Libertine is available on your system - \
you can ignore this warning if you are sure you want to use it",
);
warning.hint("this warning will be removed in Typst 0.13");
}
engine.sink.warn(warning);
} else if !found {
if !book.contains_family(family.as_str()) {
engine.sink.warn(warning!(
list.span,
"unknown font family: {}",

View File

@ -446,10 +446,14 @@ impl Show for Packed<RawElem> {
let mut realized = Content::sequence(seq);
if TargetElem::target_in(styles).is_html() {
return Ok(HtmlElem::new(tag::pre)
.with_body(Some(realized))
.pack()
.spanned(self.span()));
return Ok(HtmlElem::new(if self.block(styles) {
tag::pre
} else {
tag::code
})
.with_body(Some(realized))
.pack()
.spanned(self.span()));
}
if self.block(styles) {

View File

@ -398,8 +398,7 @@ impl ImageFormat {
return Some(Self::Raster(RasterFormat::Exchange(format)));
}
// SVG or compressed SVG.
if data.starts_with(b"<svg") || data.starts_with(&[0x1f, 0x8b]) {
if is_svg(data) {
return Some(Self::Vector(VectorFormat::Svg));
}
@ -407,6 +406,21 @@ impl ImageFormat {
}
}
/// Checks whether the data looks like an SVG or a compressed SVG.
fn is_svg(data: &[u8]) -> bool {
// Check for the gzip magic bytes. This check is perhaps a bit too
// permissive as other formats than SVGZ could use gzip.
if data.starts_with(&[0x1f, 0x8b]) {
return true;
}
// If the first 2048 bytes contain the SVG namespace declaration, we assume
// that it's an SVG. Note that, if the SVG does not contain a namespace
// declaration, usvg will reject it.
let head = &data[..data.len().min(2048)];
memchr::memmem::find(head, b"http://www.w3.org/2000/svg").is_some()
}
/// A vector graphics format.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)]
pub enum VectorFormat {

View File

@ -44,7 +44,7 @@ impl Scalar {
const fn is_nan(x: f64) -> bool {
// Safety: all bit patterns are valid for u64, and f64 has no padding bits.
// We cannot use `f64::to_bits` because it is not const.
let x_bits = unsafe { std::mem::transmute::<f64, u64>(x) };
let x_bits = x.to_bits();
(x_bits << 1 >> (64 - 12 + 1)) == 0b0_111_1111_1111 && (x_bits << 12) != 0
}

View File

@ -1,9 +1,9 @@
---
title: 0.13.0
description: Changes slated to appear in Typst 0.13.0
description: Changes in Typst 0.13.0
---
# Version 0.13.0, Release Candidate 1 (February 5, 2025) { #v0.13.0-rc1 }
# Version 0.13.0 (February 19, 2025)
## Highlights
- There is now a distinction between [proper paragraphs]($par) and just
@ -99,8 +99,9 @@ description: Changes slated to appear in Typst 0.13.0
- Fixed interaction of clipping and outset on [`box`] and [`block`]
- Fixed panic with [`path`] of infinite length
- Fixed non-solid (e.g. tiling) text fills in clipped blocks
- Auto-detection of image formats from a raw buffer now has basic support for
SVGs
- Fixed a crash for images with a DPI value of zero
- Fixed floating-point error in [`gradient.repeat`]
- Auto-detection of image formats from a raw buffer now has support for SVGs
## Scripting
- Functions that accept [file paths]($syntax/#paths) now also accept raw
@ -187,12 +188,12 @@ description: Changes slated to appear in Typst 0.13.0
- [CJK-Latin-spacing]($text.cjk-latin-spacing) does not affect [raw] text
anymore
- Fixed wrong language codes being used for Greek and Ukrainian
- Fixed default quotes for Croatian
- Fixed default quotes for Croatian and Bulgarian
- Fixed crash in RTL text handling
- Added support for [`raw`] syntax highlighting for a few new languages: CFML,
NSIS, and WGSL
- New font metadata exception for New Computer Modern Sans Math
- Updated bundled New Computer Modern fonts to version 7.0
- Updated bundled New Computer Modern fonts to version 7.0.1
## Layout
- Fixed various bugs with footnotes
@ -271,6 +272,9 @@ feature flag.
- Added a live reloading HTTP server to `typst watch` when targeting HTML
- Fixed self-update not being aware about certain target architectures
- Fixed crash when piping `typst fonts` output to another command
- Fixed handling of relative paths in `--make-deps` output
- Fixed handling of multipage SVG and PNG export in `--make-deps` output
- Colons in filenames are now correctly escaped in `--make-deps` output
## Symbols
- New
@ -313,6 +317,9 @@ feature flag.
functions directly accepting both paths and bytes
- The `sect` and its variants in favor of `inter`, and `integral.sect` in favor
of `integral.inter`
- The compatibility behavior of type/str comparisons (e.g. `{int == "integer"}`)
which was temporarily introduced in Typst 0.8 now emits warnings. It will be
removed in Typst 0.14.
## Removals
- Removed `style` function and `styles` argument of [`measure`], use a [context]
@ -324,9 +331,6 @@ feature flag.
- Removed compatibility behavior where [`counter.display`] worked without
[context] **(Breaking change)**
- Removed compatibility behavior of [`locate`] **(Breaking change)**
- Removed compatibility behavior of type/str comparisons
(e.g. `{int == "integer"}`) which was temporarily introduced in Typst 0.8
**(Breaking change)**
## Development
- The `typst::compile` function is now generic and can return either a
@ -337,4 +341,4 @@ feature flag.
- Fixed linux/arm64 Docker image
## Contributors
<contributors from="v0.12.0" to="v0.13.0-rc1" />
<contributors from="v0.12.0" to="v0.13.0" />

View File

@ -10,7 +10,7 @@ forward. This section documents all changes to Typst since its initial public
release.
## Versions
- [Typst 0.13.0 (Release Candidate 1)]($changelog/0.13.0)
- [Typst 0.13.0]($changelog/0.13.0)
- [Typst 0.12.0]($changelog/0.12.0)
- [Typst 0.11.1]($changelog/0.11.1)
- [Typst 0.11.0]($changelog/0.11.0)

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 B

View File

@ -161,7 +161,7 @@ impl<'a> Runner<'a> {
// Compare against reference output if available.
// Test that is ok doesn't need to be updated.
if ref_data.as_ref().map_or(false, |r| D::matches(&live, r)) {
if ref_data.as_ref().is_ok_and(|r| D::matches(&live, r)) {
return;
}

View File

@ -251,6 +251,6 @@ fn lines(
(1..=count)
.map(|n| numbering.apply(engine, context, &[n]))
.collect::<SourceResult<Array>>()?
.join(Some('\n'.into_value()), None)
.join(engine, span, Some('\n'.into_value()), None)
.at(span)
}

View File

@ -2,6 +2,60 @@
#test(type(1), int)
#test(type(ltr), direction)
#test(type(10 / 3), float)
#test(type(10) == int, true)
#test(type(10) != int, false)
--- type-string-compatibility-add ---
// Warning: 7-23 adding strings and types is deprecated
// Hint: 7-23 convert the type to a string with `str` first
#test("is " + type(10), "is integer")
// Warning: 7-23 adding strings and types is deprecated
// Hint: 7-23 convert the type to a string with `str` first
#test(type(10) + " is", "integer is")
--- type-string-compatibility-join ---
// Warning: 16-24 joining strings and types is deprecated
// Hint: 16-24 convert the type to a string with `str` first
#test({ "is "; type(10) }, "is integer")
// Warning: 19-24 joining strings and types is deprecated
// Hint: 19-24 convert the type to a string with `str` first
#test({ type(10); " is" }, "integer is")
--- type-string-compatibility-equal ---
// Warning: 7-28 comparing strings with types is deprecated
// Hint: 7-28 compare with the literal type instead
// Hint: 7-28 this comparison will always return `false` in future Typst releases
#test(type(10) == "integer", true)
// Warning: 7-26 comparing strings with types is deprecated
// Hint: 7-26 compare with the literal type instead
// Hint: 7-26 this comparison will always return `false` in future Typst releases
#test(type(10) != "float", true)
--- type-string-compatibility-in-array ---
// Warning: 7-35 comparing strings with types is deprecated
// Hint: 7-35 compare with the literal type instead
// Hint: 7-35 this comparison will always return `false` in future Typst releases
#test(int in ("integer", "string"), true)
// Warning: 7-37 comparing strings with types is deprecated
// Hint: 7-37 compare with the literal type instead
// Hint: 7-37 this comparison will always return `false` in future Typst releases
#test(float in ("integer", "string"), false)
--- type-string-compatibility-in-str ---
// Warning: 7-35 checking whether a type is contained in a string is deprecated
// Hint: 7-35 this compatibility behavior only exists because `type` used to return a string
#test(int in "integers or strings", true)
// Warning: 7-35 checking whether a type is contained in a string is deprecated
// Hint: 7-35 this compatibility behavior only exists because `type` used to return a string
#test(str in "integers or strings", true)
// Warning: 7-37 checking whether a type is contained in a string is deprecated
// Hint: 7-37 this compatibility behavior only exists because `type` used to return a string
#test(float in "integers or strings", false)
--- type-string-compatibility-in-dict ---
// Warning: 7-37 checking whether a type is contained in a dictionary is deprecated
// Hint: 7-37 this compatibility behavior only exists because `type` used to return a string
#test(int in (integer: 1, string: 2), true)
--- issue-3110-type-constructor ---
// Let the error message report the type name.

View File

@ -77,11 +77,6 @@ I
#let var = text(font: ("list-of", "nonexistent-fonts"))[don't]
#var
--- text-font-linux-libertine ---
// Warning: 17-34 Typst's default font has changed from Linux Libertine to its successor Libertinus Serif
// Hint: 17-34 please set the font to `"Libertinus Serif"` instead
#set text(font: "Linux Libertine")
--- issue-5499-text-fill-in-clip-block ---
#let t = tiling(

View File

@ -65,6 +65,17 @@ A #box(image("/assets/images/tiger.jpg", height: 1cm, width: 80%)) B
caption: [Bilingual text]
)
--- image-svg-auto-detection ---
#image(bytes(
```
<?xml version="1.0" encoding="utf-8"?>
<!-- An SVG -->
<svg width="200" height="150" xmlns="http://www.w3.org/2000/svg">
<rect fill="red" stroke="black" x="25" y="25" width="150" height="100"/>
</svg>
```.text
))
--- image-pixmap-rgb8 ---
#image(
bytes((
@ -152,8 +163,8 @@ A #box(image("/assets/images/tiger.jpg", height: 1cm, width: 80%)) B
#image("path/does/not/exist")
--- image-bad-format ---
// Error: 2-22 unknown image format
#image("./image.typ")
// Error: 2-37 unknown image format
#image("/assets/plugins/hello.wasm")
--- image-bad-svg ---
// Error: 2-33 failed to parse SVG (found closing tag 'g' instead of 'style' in line 4)