diff --git a/Cargo.lock b/Cargo.lock index 4ccaffde..56e2aafa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3835,7 +3835,7 @@ checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" [[package]] name = "typst" -version = "0.13.0-rc1" +version = "0.13.0" dependencies = [ "comemo", "ecow", @@ -3852,12 +3852,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", @@ -3902,12 +3903,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", @@ -3930,7 +3931,7 @@ dependencies = [ [[package]] name = "typst-eval" -version = "0.13.0-rc1" +version = "0.13.0" dependencies = [ "comemo", "ecow", @@ -3948,7 +3949,7 @@ dependencies = [ [[package]] name = "typst-fuzz" -version = "0.13.0-rc1" +version = "0.13.0" dependencies = [ "comemo", "libfuzzer-sys", @@ -3960,7 +3961,7 @@ dependencies = [ [[package]] name = "typst-html" -version = "0.13.0-rc1" +version = "0.13.0" dependencies = [ "comemo", "ecow", @@ -3974,7 +3975,7 @@ dependencies = [ [[package]] name = "typst-ide" -version = "0.13.0-rc1" +version = "0.13.0" dependencies = [ "comemo", "ecow", @@ -3991,7 +3992,7 @@ dependencies = [ [[package]] name = "typst-kit" -version = "0.13.0-rc1" +version = "0.13.0" dependencies = [ "dirs", "ecow", @@ -4015,7 +4016,7 @@ dependencies = [ [[package]] name = "typst-layout" -version = "0.13.0-rc1" +version = "0.13.0" dependencies = [ "az", "bumpalo", @@ -4045,7 +4046,7 @@ dependencies = [ [[package]] name = "typst-library" -version = "0.13.0-rc1" +version = "0.13.0" dependencies = [ "az", "bitflags 2.8.0", @@ -4067,6 +4068,7 @@ dependencies = [ "kamadak-exif", "kurbo", "lipsum", + "memchr", "palette", "phf", "png", @@ -4104,7 +4106,7 @@ dependencies = [ [[package]] name = "typst-macros" -version = "0.13.0-rc1" +version = "0.13.0" dependencies = [ "heck", "proc-macro2", @@ -4114,7 +4116,7 @@ dependencies = [ [[package]] name = "typst-pdf" -version = "0.13.0-rc1" +version = "0.13.0" dependencies = [ "arrayvec", "base64", @@ -4140,7 +4142,7 @@ dependencies = [ [[package]] name = "typst-realize" -version = "0.13.0-rc1" +version = "0.13.0" dependencies = [ "arrayvec", "bumpalo", @@ -4156,7 +4158,7 @@ dependencies = [ [[package]] name = "typst-render" -version = "0.13.0-rc1" +version = "0.13.0" dependencies = [ "bytemuck", "comemo", @@ -4172,7 +4174,7 @@ dependencies = [ [[package]] name = "typst-svg" -version = "0.13.0-rc1" +version = "0.13.0" dependencies = [ "base64", "comemo", @@ -4190,7 +4192,7 @@ dependencies = [ [[package]] name = "typst-syntax" -version = "0.13.0-rc1" +version = "0.13.0" dependencies = [ "ecow", "serde", @@ -4206,7 +4208,7 @@ dependencies = [ [[package]] name = "typst-tests" -version = "0.13.0-rc1" +version = "0.13.0" dependencies = [ "clap", "comemo", @@ -4231,7 +4233,7 @@ dependencies = [ [[package]] name = "typst-timing" -version = "0.13.0-rc1" +version = "0.13.0" dependencies = [ "parking_lot", "serde", @@ -4241,7 +4243,7 @@ dependencies = [ [[package]] name = "typst-utils" -version = "0.13.0-rc1" +version = "0.13.0" dependencies = [ "once_cell", "portable-atomic", diff --git a/Cargo.toml b/Cargo.toml index 729e1d55..7c07b407 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" @@ -74,6 +74,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" diff --git a/crates/typst-cli/src/compile.rs b/crates/typst-cli/src/compile.rs index 515a777a..2b6a7d82 100644 --- a/crates/typst-cli/src/compile.rs +++ b/crates/typst-cli/src/compile.rs @@ -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> { +) -> Warned>> { match config.output_format { OutputFormat::Html => { let Warned { output, warnings } = typst::compile::(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::(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> { 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> { // 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::, EcoString>>()?; - - Ok(()) + .collect::>>() } 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, +) -> 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::, _>>() + 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, root: PathBuf, dependencies: impl Iterator, ) -> io::Result<()> { let mut file = File::create(make_deps_path)?; + let current_dir = std::env::current_dir()?; + let relative_root = diff_paths(&root, ¤t_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})") }) diff --git a/crates/typst-cli/src/watch.rs b/crates/typst-cli/src/watch.rs index 91132fc3..cc727f0f 100644 --- a/crates/typst-cli/src/watch.rs +++ b/crates/typst-cli/src/watch.rs @@ -204,6 +204,10 @@ impl Watcher { let event = event .map_err(|err| eco_format!("failed to watch dependencies ({err})"))?; + if !is_relevant_event_kind(&event.kind) { + continue; + } + // Workaround for notify-rs' implicit unwatch on remove/rename // (triggered by some editors when saving files) with the // inotify backend. By keeping track of the potentially @@ -224,7 +228,17 @@ impl Watcher { } } - relevant |= self.is_event_relevant(&event); + // Don't recompile because the output file changed. + // FIXME: This doesn't work properly for multifile image export. + if event + .paths + .iter() + .all(|path| is_same_file(path, &self.output).unwrap_or(false)) + { + continue; + } + + relevant = true; } // If we found a relevant event or if any of the missing files now @@ -234,32 +248,23 @@ impl Watcher { } } } +} - /// Whether a watch event is relevant for compilation. - fn is_event_relevant(&self, event: ¬ify::Event) -> bool { - // Never recompile because the output file changed. - if event - .paths - .iter() - .all(|path| is_same_file(path, &self.output).unwrap_or(false)) - { - return false; - } - - match &event.kind { - notify::EventKind::Any => true, - notify::EventKind::Access(_) => false, - notify::EventKind::Create(_) => true, - notify::EventKind::Modify(kind) => match kind { - notify::event::ModifyKind::Any => true, - notify::event::ModifyKind::Data(_) => true, - notify::event::ModifyKind::Metadata(_) => false, - notify::event::ModifyKind::Name(_) => true, - notify::event::ModifyKind::Other => false, - }, - notify::EventKind::Remove(_) => true, - notify::EventKind::Other => false, - } +/// Whether a kind of watch event is relevant for compilation. +fn is_relevant_event_kind(kind: ¬ify::EventKind) -> bool { + match kind { + notify::EventKind::Any => true, + notify::EventKind::Access(_) => false, + notify::EventKind::Create(_) => true, + notify::EventKind::Modify(kind) => match kind { + notify::event::ModifyKind::Any => true, + notify::event::ModifyKind::Data(_) => true, + notify::event::ModifyKind::Metadata(_) => false, + notify::event::ModifyKind::Name(_) => true, + notify::event::ModifyKind::Other => false, + }, + notify::EventKind::Remove(_) => true, + notify::EventKind::Other => false, } } diff --git a/crates/typst-eval/src/code.rs b/crates/typst-eval/src/code.rs index a7b6b6f9..6768ccdc 100644 --- a/crates/typst-eval/src/code.rs +++ b/crates/typst-eval/src/code.rs @@ -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); diff --git a/crates/typst-eval/src/flow.rs b/crates/typst-eval/src/flow.rs index b5ba487f..0e7d3e63 100644 --- a/crates/typst-eval/src/flow.rs +++ b/crates/typst-eval/src/flow.rs @@ -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(_)) => { diff --git a/crates/typst-eval/src/ops.rs b/crates/typst-eval/src/ops.rs index ebbd6743..f2a8f6c6 100644 --- a/crates/typst-eval/src/ops.rs +++ b/crates/typst-eval/src/ops.rs @@ -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 { 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, +) -> SourceResult { + 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, +) -> SourceResult { + 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) +} diff --git a/crates/typst-html/src/lib.rs b/crates/typst-html/src/lib.rs index 25d0cd5d..aa769976 100644 --- a/crates/typst-html/src/lib.rs +++ b/crates/typst-html/src/lib.rs @@ -83,8 +83,8 @@ fn html_document_impl( )?; let output = handle_list(&mut engine, &mut locator, children.iter().copied())?; + let introspector = Introspector::html(&output); let root = root_element(output, &info)?; - let introspector = Introspector::html(&root); Ok(HtmlDocument { info, root, introspector }) } @@ -307,18 +307,18 @@ fn head_element(info: &DocumentInfo) -> HtmlElement { /// Determine which kind of output the user generated. fn classify_output(mut output: Vec) -> SourceResult { - let len = output.len(); + let count = output.iter().filter(|node| !matches!(node, HtmlNode::Tag(_))).count(); for node in &mut output { let HtmlNode::Element(elem) = node else { continue }; let tag = elem.tag; let mut take = || std::mem::replace(elem, HtmlElement::new(tag::html)); - match (tag, len) { + match (tag, count) { (tag::html, 1) => return Ok(OutputKind::Html(take())), (tag::body, 1) => return Ok(OutputKind::Body(take())), (tag::html | tag::body, _) => bail!( elem.span, "`{}` element must be the only element in the document", - elem.tag + elem.tag, ), _ => {} } diff --git a/crates/typst-library/Cargo.toml b/crates/typst-library/Cargo.toml index cc5e2671..fb45ec86 100644 --- a/crates/typst-library/Cargo.toml +++ b/crates/typst-library/Cargo.toml @@ -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 } diff --git a/crates/typst-library/src/diag.rs b/crates/typst-library/src/diag.rs index 49cbd02c..bf8dc0e4 100644 --- a/crates/typst-library/src/diag.rs +++ b/crates/typst-library/src/diag.rs @@ -232,18 +232,42 @@ impl From 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, 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). diff --git a/crates/typst-library/src/foundations/array.rs b/crates/typst-library/src/foundations/array.rs index aad7266b..8c921a13 100644 --- a/crates/typst-library/src/foundations/array.rs +++ b/crates/typst-library/src/foundations/array.rs @@ -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, @@ -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, + span: Span, /// If given, applies this function to the elements in the array to /// determine the keys to deduplicate by. #[named] key: Option, ) -> SourceResult { 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; } } diff --git a/crates/typst-library/src/foundations/func.rs b/crates/typst-library/src/foundations/func.rs index 3ed1562f..66c6b70a 100644 --- a/crates/typst-library/src/foundations/func.rs +++ b/crates/typst-library/src/foundations/func.rs @@ -437,10 +437,10 @@ impl PartialEq for Func { } } -impl PartialEq<&NativeFuncData> for Func { - fn eq(&self, other: &&NativeFuncData) -> bool { +impl PartialEq<&'static NativeFuncData> for Func { + fn eq(&self, other: &&'static NativeFuncData) -> bool { match &self.repr { - Repr::Native(native) => native.function == other.function, + Repr::Native(native) => *native == Static(*other), _ => false, } } diff --git a/crates/typst-library/src/foundations/ops.rs b/crates/typst-library/src/foundations/ops.rs index 6c240844..c2cd5f5a 100644 --- a/crates/typst-library/src/foundations/ops.rs +++ b/crates/typst-library/src/foundations/ops.rs @@ -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 { +pub fn join(lhs: Value, rhs: Value, sink: &mut dyn DeprecationSink) -> StrResult { use Value::*; Ok(match (lhs, rhs) { (a, None) => a, @@ -39,6 +39,17 @@ pub fn join(lhs: Value, rhs: Value) -> StrResult { (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 { } /// Compute the sum of two values. -pub fn add(lhs: Value, rhs: Value) -> HintedStrResult { +pub fn add( + lhs: Value, + rhs: Value, + sink: &mut dyn DeprecationSink, +) -> HintedStrResult { use Value::*; Ok(match (lhs, rhs) { (a, None) => a, @@ -156,6 +171,16 @@ pub fn add(lhs: Value, rhs: Value) -> HintedStrResult { (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 { } /// Compute whether two values are equal. -pub fn eq(lhs: Value, rhs: Value) -> HintedStrResult { - Ok(Value::Bool(equal(&lhs, &rhs))) +pub fn eq( + lhs: Value, + rhs: Value, + sink: &mut dyn DeprecationSink, +) -> HintedStrResult { + Ok(Value::Bool(equal(&lhs, &rhs, sink))) } /// Compute whether two values are unequal. -pub fn neq(lhs: Value, rhs: Value) -> HintedStrResult { - Ok(Value::Bool(!equal(&lhs, &rhs))) +pub fn neq( + lhs: Value, + rhs: Value, + sink: &mut dyn DeprecationSink, +) -> HintedStrResult { + 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, str); + ty.compat_name() == str.as_str() + } + _ => false, } } @@ -534,8 +573,12 @@ fn try_cmp_arrays(a: &[Value], b: &[Value]) -> StrResult { } /// Test whether one value is "in" another one. -pub fn in_(lhs: Value, rhs: Value) -> HintedStrResult { - if let Some(b) = contains(&lhs, &rhs) { +pub fn in_( + lhs: Value, + rhs: Value, + sink: &mut dyn DeprecationSink, +) -> HintedStrResult { + 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 { } /// Test whether one value is "not in" another one. -pub fn not_in(lhs: Value, rhs: Value) -> HintedStrResult { - if let Some(b) = contains(&lhs, &rhs) { +pub fn not_in( + lhs: Value, + rhs: Value, + sink: &mut dyn DeprecationSink, +) -> HintedStrResult { + 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 { } /// Test for containment. -pub fn contains(lhs: &Value, rhs: &Value) -> Option { +pub fn contains( + lhs: &Value, + rhs: &Value, + sink: &mut dyn DeprecationSink, +) -> Option { use Value::*; match (lhs, rhs) { (Str(a), Str(b)) => Some(b.as_str().contains(a.as_str())), (Dyn(a), Str(b)) => a.downcast::().map(|regex| regex.is_match(b)), (Str(a), Dict(b)) => Some(b.contains(a)), - (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,90 @@ pub fn contains(lhs: &Value, rhs: &Value) -> Option { 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, s: &str) { + // Only warn if `s` looks like a type name to prevent false positives. + if is_compat_type_name(s) { + 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"], + ); +} + +fn is_compat_type_name(s: &str) -> bool { + matches!( + s, + "boolean" + | "alignment" + | "angle" + | "arguments" + | "array" + | "bytes" + | "color" + | "content" + | "counter" + | "datetime" + | "decimal" + | "dictionary" + | "direction" + | "duration" + | "float" + | "fraction" + | "function" + | "gradient" + | "integer" + | "label" + | "length" + | "location" + | "module" + | "pattern" + | "ratio" + | "regex" + | "relative length" + | "selector" + | "state" + | "string" + | "stroke" + | "symbol" + | "tiling" + | "type" + | "version" + ) +} diff --git a/crates/typst-library/src/foundations/scope.rs b/crates/typst-library/src/foundations/scope.rs index e1ce61b8..ad6da323 100644 --- a/crates/typst-library/src/foundations/scope.rs +++ b/crates/typst-library/src/foundations/scope.rs @@ -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); } diff --git a/crates/typst-library/src/foundations/ty.rs b/crates/typst-library/src/foundations/ty.rs index 40f7003c..4747775a 100644 --- a/crates/typst-library/src/foundations/ty.rs +++ b/crates/typst-library/src/foundations/ty.rs @@ -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); @@ -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. diff --git a/crates/typst-library/src/foundations/value.rs b/crates/typst-library/src/foundations/value.rs index 854c2486..ba5b1473 100644 --- a/crates/typst-library/src/foundations/value.rs +++ b/crates/typst-library/src/foundations/value.rs @@ -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 ()) } } diff --git a/crates/typst-library/src/introspection/introspector.rs b/crates/typst-library/src/introspection/introspector.rs index 8cbaea89..9751dfcb 100644 --- a/crates/typst-library/src/introspection/introspector.rs +++ b/crates/typst-library/src/introspection/introspector.rs @@ -10,7 +10,7 @@ use typst_utils::NonZeroExt; use crate::diag::{bail, StrResult}; use crate::foundations::{Content, Label, Repr, Selector}; -use crate::html::{HtmlElement, HtmlNode}; +use crate::html::HtmlNode; use crate::introspection::{Location, Tag}; use crate::layout::{Frame, FrameItem, Page, Point, Position, Transform}; use crate::model::Numbering; @@ -55,8 +55,8 @@ impl Introspector { /// Creates an introspector for HTML. #[typst_macros::time(name = "introspect html")] - pub fn html(root: &HtmlElement) -> Self { - IntrospectorBuilder::new().build_html(root) + pub fn html(output: &[HtmlNode]) -> Self { + IntrospectorBuilder::new().build_html(output) } /// Iterates over all locatable elements. @@ -392,9 +392,9 @@ impl IntrospectorBuilder { } /// Build an introspector for an HTML document. - fn build_html(mut self, root: &HtmlElement) -> Introspector { + fn build_html(mut self, output: &[HtmlNode]) -> Introspector { let mut elems = Vec::new(); - self.discover_in_html(&mut elems, root); + self.discover_in_html(&mut elems, output); self.finalize(elems) } @@ -434,16 +434,16 @@ impl IntrospectorBuilder { } /// Processes the tags in the HTML element. - fn discover_in_html(&mut self, sink: &mut Vec, elem: &HtmlElement) { - for child in &elem.children { - match child { + fn discover_in_html(&mut self, sink: &mut Vec, nodes: &[HtmlNode]) { + for node in nodes { + match node { HtmlNode::Tag(tag) => self.discover_in_tag( sink, tag, Position { page: NonZeroUsize::ONE, point: Point::zero() }, ), HtmlNode::Text(_, _) => {} - HtmlNode::Element(elem) => self.discover_in_html(sink, elem), + HtmlNode::Element(elem) => self.discover_in_html(sink, &elem.children), HtmlNode::Frame(frame) => self.discover_in_frame( sink, frame, diff --git a/crates/typst-library/src/layout/grid/resolve.rs b/crates/typst-library/src/layout/grid/resolve.rs index f6df57a3..762f94ed 100644 --- a/crates/typst-library/src/layout/grid/resolve.rs +++ b/crates/typst-library/src/layout/grid/resolve.rs @@ -1526,11 +1526,7 @@ impl<'a> CellGrid<'a> { self.entry(x, y).map(|entry| match entry { Entry::Cell(_) => Axes::new(x, y), Entry::Merged { parent } => { - let c = if self.has_gutter { - 1 + self.cols.len() / 2 - } else { - self.cols.len() - }; + let c = self.non_gutter_column_count(); let factor = if self.has_gutter { 2 } else { 1 }; Axes::new(factor * (*parent % c), factor * (*parent / c)) } @@ -1602,6 +1598,21 @@ impl<'a> CellGrid<'a> { cell.rowspan.get() } } + + #[inline] + pub fn non_gutter_column_count(&self) -> usize { + if self.has_gutter { + // Calculation: With gutters, we have + // 'cols = 2 * (non-gutter cols) - 1', since there is a gutter + // column between each regular column. Therefore, + // 'floor(cols / 2)' will be equal to + // 'floor(non-gutter cols - 1/2) = non-gutter-cols - 1', + // so 'non-gutter cols = 1 + floor(cols / 2)'. + 1 + self.cols.len() / 2 + } else { + self.cols.len() + } + } } /// Given a cell's requested x and y, the vector with the resolved cell diff --git a/crates/typst-library/src/model/table.rs b/crates/typst-library/src/model/table.rs index 82c1cc08..6f4461bd 100644 --- a/crates/typst-library/src/model/table.rs +++ b/crates/typst-library/src/model/table.rs @@ -282,7 +282,7 @@ fn show_cell_html(tag: HtmlTag, cell: &Cell, styles: StyleChain) -> Content { fn show_cellgrid_html(grid: CellGrid, styles: StyleChain) -> Content { let elem = |tag, body| HtmlElem::new(tag).with_body(Some(body)).pack(); - let mut rows: Vec<_> = grid.entries.chunks(grid.cols.len()).collect(); + let mut rows: Vec<_> = grid.entries.chunks(grid.non_gutter_column_count()).collect(); let tr = |tag, row: &[Entry]| { let row = row diff --git a/crates/typst-library/src/text/mod.rs b/crates/typst-library/src/text/mod.rs index 30c2ea1d..3aac15ba 100644 --- a/crates/typst-library/src/text/mod.rs +++ b/crates/typst-library/src/text/mod.rs @@ -1380,24 +1380,7 @@ pub fn is_default_ignorable(c: char) -> bool { fn check_font_list(engine: &mut Engine, list: &Spanned) { 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: {}", diff --git a/crates/typst-library/src/text/raw.rs b/crates/typst-library/src/text/raw.rs index b330c01e..1ce8bfc6 100644 --- a/crates/typst-library/src/text/raw.rs +++ b/crates/typst-library/src/text/raw.rs @@ -446,10 +446,14 @@ impl Show for Packed { 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) { diff --git a/crates/typst-library/src/visualize/image/mod.rs b/crates/typst-library/src/visualize/image/mod.rs index 97189e22..258eb96f 100644 --- a/crates/typst-library/src/visualize/image/mod.rs +++ b/crates/typst-library/src/visualize/image/mod.rs @@ -398,8 +398,7 @@ impl ImageFormat { return Some(Self::Raster(RasterFormat::Exchange(format))); } - // SVG or compressed SVG. - if data.starts_with(b" 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 { diff --git a/docs/changelog/0.13.0.md b/docs/changelog/0.13.0.md index 78a8f897..6c2fe427 100644 --- a/docs/changelog/0.13.0.md +++ b/docs/changelog/0.13.0.md @@ -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 - + diff --git a/docs/changelog/welcome.md b/docs/changelog/welcome.md index eca5c254..8fb85f87 100644 --- a/docs/changelog/welcome.md +++ b/docs/changelog/welcome.md @@ -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) diff --git a/tests/ref/html/col-gutter-table.html b/tests/ref/html/col-gutter-table.html new file mode 100644 index 00000000..54170f53 --- /dev/null +++ b/tests/ref/html/col-gutter-table.html @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + +
abc
def
ghi
+ + diff --git a/tests/ref/html/col-row-gutter-table.html b/tests/ref/html/col-row-gutter-table.html new file mode 100644 index 00000000..54170f53 --- /dev/null +++ b/tests/ref/html/col-row-gutter-table.html @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + +
abc
def
ghi
+ + diff --git a/tests/ref/html/html-elem-alone-context.html b/tests/ref/html/html-elem-alone-context.html new file mode 100644 index 00000000..69e9da41 --- /dev/null +++ b/tests/ref/html/html-elem-alone-context.html @@ -0,0 +1,2 @@ + + diff --git a/tests/ref/html/html-elem-metadata.html b/tests/ref/html/html-elem-metadata.html new file mode 100644 index 00000000..c37a7d2e --- /dev/null +++ b/tests/ref/html/html-elem-metadata.html @@ -0,0 +1,2 @@ + +Hi diff --git a/tests/ref/html/row-gutter-table.html b/tests/ref/html/row-gutter-table.html new file mode 100644 index 00000000..54170f53 --- /dev/null +++ b/tests/ref/html/row-gutter-table.html @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + +
abc
def
ghi
+ + diff --git a/tests/ref/image-svg-auto-detection.png b/tests/ref/image-svg-auto-detection.png new file mode 100644 index 00000000..0240f8f5 Binary files /dev/null and b/tests/ref/image-svg-auto-detection.png differ diff --git a/tests/src/world.rs b/tests/src/world.rs index 5c267832..56ebc868 100644 --- a/tests/src/world.rs +++ b/tests/src/world.rs @@ -251,6 +251,6 @@ fn lines( (1..=count) .map(|n| numbering.apply(engine, context, &[n])) .collect::>()? - .join(Some('\n'.into_value()), None) + .join(engine, span, Some('\n'.into_value()), None) .at(span) } diff --git a/tests/suite/foundations/type.typ b/tests/suite/foundations/type.typ index 068b858d..8f3dbea7 100644 --- a/tests/suite/foundations/type.typ +++ b/tests/suite/foundations/type.typ @@ -2,6 +2,62 @@ #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) +// This is not a warning. +#test(type(10) in ("any", str, int), 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. diff --git a/tests/suite/html/elem.typ b/tests/suite/html/elem.typ new file mode 100644 index 00000000..b416fdf9 --- /dev/null +++ b/tests/suite/html/elem.typ @@ -0,0 +1,15 @@ +--- html-elem-alone-context html --- +#context html.elem("html") + +--- html-elem-not-alone html --- +// Error: 2-19 `` element must be the only element in the document +#html.elem("html") +Text + +--- html-elem-metadata html --- +#html.elem("html", context { + let val = query().first().value + test(val, "Hi") + val +}) +#metadata("Hi") diff --git a/tests/suite/layout/grid/html.typ b/tests/suite/layout/grid/html.typ index 2a7dfc2c..10345cb0 100644 --- a/tests/suite/layout/grid/html.typ +++ b/tests/suite/layout/grid/html.typ @@ -30,3 +30,30 @@ [row], ), ) + +--- col-gutter-table html --- +#table( + columns: 3, + column-gutter: 3pt, + [a], [b], [c], + [d], [e], [f], + [g], [h], [i] +) + +--- row-gutter-table html --- +#table( + columns: 3, + row-gutter: 3pt, + [a], [b], [c], + [d], [e], [f], + [g], [h], [i] +) + +--- col-row-gutter-table html --- +#table( + columns: 3, + gutter: 3pt, + [a], [b], [c], + [d], [e], [f], + [g], [h], [i] +) diff --git a/tests/suite/text/font.typ b/tests/suite/text/font.typ index 9e5c0150..60a1cd94 100644 --- a/tests/suite/text/font.typ +++ b/tests/suite/text/font.typ @@ -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( diff --git a/tests/suite/visualize/image.typ b/tests/suite/visualize/image.typ index e37932f2..7ce0c8c0 100644 --- a/tests/suite/visualize/image.typ +++ b/tests/suite/visualize/image.typ @@ -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( + ``` + + + + + + ```.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)