From 3650859ae8823f47c9f50db6ad5ed52a0477bf15 Mon Sep 17 00:00:00 2001 From: evie <50974538+mi2ebi@users.noreply.github.com> Date: Tue, 11 Mar 2025 03:00:53 -0700 Subject: [PATCH 1/7] Fix `cargo clippy` warnings (mostly about `.repeat.take` and `.next_back`) (#6038) --- crates/typst-ide/src/analyze.rs | 2 +- crates/typst-layout/src/flow/compose.rs | 2 +- crates/typst-layout/src/grid/layouter.rs | 2 +- crates/typst-layout/src/math/stretch.rs | 2 +- crates/typst-library/src/foundations/cast.rs | 2 +- crates/typst-library/src/model/numbering.rs | 2 +- crates/typst-library/src/visualize/gradient.rs | 3 +-- crates/typst-render/src/shape.rs | 8 +++++--- 8 files changed, 12 insertions(+), 11 deletions(-) diff --git a/crates/typst-ide/src/analyze.rs b/crates/typst-ide/src/analyze.rs index 7ee83e709..c493da81a 100644 --- a/crates/typst-ide/src/analyze.rs +++ b/crates/typst-ide/src/analyze.rs @@ -26,7 +26,7 @@ pub fn analyze_expr( ast::Expr::Str(v) => Value::Str(v.get().into()), _ => { if node.kind() == SyntaxKind::Contextual { - if let Some(child) = node.children().last() { + if let Some(child) = node.children().next_back() { return analyze_expr(world, &child); } } diff --git a/crates/typst-layout/src/flow/compose.rs b/crates/typst-layout/src/flow/compose.rs index 76af8f650..54dc487a3 100644 --- a/crates/typst-layout/src/flow/compose.rs +++ b/crates/typst-layout/src/flow/compose.rs @@ -115,7 +115,7 @@ impl<'a, 'b> Composer<'a, 'b, '_, '_> { let column_height = regions.size.y; let backlog: Vec<_> = std::iter::once(&column_height) .chain(regions.backlog) - .flat_map(|&h| std::iter::repeat(h).take(self.config.columns.count)) + .flat_map(|&h| std::iter::repeat_n(h, self.config.columns.count)) .skip(1) .collect(); diff --git a/crates/typst-layout/src/grid/layouter.rs b/crates/typst-layout/src/grid/layouter.rs index af47ff72f..dc9e2238d 100644 --- a/crates/typst-layout/src/grid/layouter.rs +++ b/crates/typst-layout/src/grid/layouter.rs @@ -1469,7 +1469,7 @@ impl<'a> GridLayouter<'a> { // last height is the one for the current region. rowspan .heights - .extend(std::iter::repeat(Abs::zero()).take(amount_missing_heights)); + .extend(std::iter::repeat_n(Abs::zero(), amount_missing_heights)); // Ensure that, in this region, the rowspan will span at least // this row. diff --git a/crates/typst-layout/src/math/stretch.rs b/crates/typst-layout/src/math/stretch.rs index dafa8cbe8..f45035e27 100644 --- a/crates/typst-layout/src/math/stretch.rs +++ b/crates/typst-layout/src/math/stretch.rs @@ -302,6 +302,6 @@ fn assemble( fn parts(assembly: GlyphAssembly, repeat: usize) -> impl Iterator + '_ { assembly.parts.into_iter().flat_map(move |part| { let count = if part.part_flags.extender() { repeat } else { 1 }; - std::iter::repeat(part).take(count) + std::iter::repeat_n(part, count) }) } diff --git a/crates/typst-library/src/foundations/cast.rs b/crates/typst-library/src/foundations/cast.rs index 38f409c67..73645491f 100644 --- a/crates/typst-library/src/foundations/cast.rs +++ b/crates/typst-library/src/foundations/cast.rs @@ -21,7 +21,7 @@ use crate::foundations::{ /// /// Type casting works as follows: /// - [`Reflect for T`](Reflect) describes the possible Typst values for `T` -/// (for documentation and autocomplete). +/// (for documentation and autocomplete). /// - [`IntoValue for T`](IntoValue) is for conversion from `T -> Value` /// (infallible) /// - [`FromValue for T`](FromValue) is for conversion from `Value -> T` diff --git a/crates/typst-library/src/model/numbering.rs b/crates/typst-library/src/model/numbering.rs index 150506758..ada8a3965 100644 --- a/crates/typst-library/src/model/numbering.rs +++ b/crates/typst-library/src/model/numbering.rs @@ -394,7 +394,7 @@ impl NumberingKind { const SYMBOLS: &[char] = &['*', '†', '‡', '§', '¶', '‖']; let symbol = SYMBOLS[(n - 1) % SYMBOLS.len()]; let amount = ((n - 1) / SYMBOLS.len()) + 1; - std::iter::repeat(symbol).take(amount).collect() + std::iter::repeat_n(symbol, amount).collect() } Self::Hebrew => hebrew_numeral(n), diff --git a/crates/typst-library/src/visualize/gradient.rs b/crates/typst-library/src/visualize/gradient.rs index 1a723a9f5..d59175a4e 100644 --- a/crates/typst-library/src/visualize/gradient.rs +++ b/crates/typst-library/src/visualize/gradient.rs @@ -574,8 +574,7 @@ impl Gradient { } let n = repetitions.v; - let mut stops = std::iter::repeat(self.stops_ref()) - .take(n) + let mut stops = std::iter::repeat_n(self.stops_ref(), n) .enumerate() .flat_map(|(i, stops)| { let mut stops = stops diff --git a/crates/typst-render/src/shape.rs b/crates/typst-render/src/shape.rs index ba7ed6d89..9b50d5f1f 100644 --- a/crates/typst-render/src/shape.rs +++ b/crates/typst-render/src/shape.rs @@ -69,9 +69,11 @@ pub fn render_shape(canvas: &mut sk::Pixmap, state: State, shape: &Shape) -> Opt let dash = dash.as_ref().and_then(to_sk_dash_pattern); let bbox = shape.geometry.bbox_size(); - let offset_bbox = (!matches!(shape.geometry, Geometry::Line(..))) - .then(|| offset_bounding_box(bbox, *thickness)) - .unwrap_or(bbox); + let offset_bbox = if !matches!(shape.geometry, Geometry::Line(..)) { + offset_bounding_box(bbox, *thickness) + } else { + bbox + }; let fill_transform = (!matches!(shape.geometry, Geometry::Line(..))).then(|| { From 96f695737174449cbd9efbf4954b676b9bb35056 Mon Sep 17 00:00:00 2001 From: Max Date: Tue, 11 Mar 2025 10:18:15 +0000 Subject: [PATCH 2/7] Fix `math.root` frame size (#6021) --- crates/typst-layout/src/math/root.rs | 8 +++++--- tests/ref/math-root-frame-size-index.png | Bin 0 -> 902 bytes tests/suite/math/root.typ | 6 ++++++ 3 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 tests/ref/math-root-frame-size-index.png diff --git a/crates/typst-layout/src/math/root.rs b/crates/typst-layout/src/math/root.rs index a6b5c03d0..c7f41488e 100644 --- a/crates/typst-layout/src/math/root.rs +++ b/crates/typst-layout/src/math/root.rs @@ -85,14 +85,15 @@ pub fn layout_root( ascent.set_max(shift_up + index.ascent()); } - let radicand_x = sqrt_offset + sqrt.width(); + let sqrt_x = sqrt_offset.max(Abs::zero()); + let radicand_x = sqrt_x + sqrt.width(); let radicand_y = ascent - radicand.ascent(); let width = radicand_x + radicand.width(); let size = Size::new(width, ascent + descent); // The extra "- thickness" comes from the fact that the sqrt is placed // in `push_frame` with respect to its top, not its baseline. - let sqrt_pos = Point::new(sqrt_offset, radicand_y - gap - thickness); + let sqrt_pos = Point::new(sqrt_x, radicand_y - gap - thickness); let line_pos = Point::new(radicand_x, radicand_y - gap - (thickness / 2.0)); let radicand_pos = Point::new(radicand_x, radicand_y); @@ -100,7 +101,8 @@ pub fn layout_root( frame.set_baseline(ascent); if let Some(index) = index { - let index_pos = Point::new(kern_before, ascent - index.ascent() - shift_up); + let index_x = -sqrt_offset.min(Abs::zero()) + kern_before; + let index_pos = Point::new(index_x, ascent - index.ascent() - shift_up); frame.push_frame(index_pos, index); } diff --git a/tests/ref/math-root-frame-size-index.png b/tests/ref/math-root-frame-size-index.png new file mode 100644 index 0000000000000000000000000000000000000000..41d4df2e9ea40429c2bc33bccec9afeb927374d9 GIT binary patch literal 902 zcmV;119|+3P)1i8`-j-*}=qYVapPY5}A+<9-Iye>Z(xM zl~(B}0~RTzPEs)|upmOI7*X&?#8TVRhm;nvwDxJ=wudKah$qu8UY_^u`?4HSYGnS{;D#1a#Z13Z08!n*r88 zzas*c%d^rT<0UE1JOsQ4dode$m!(q&;Aa3^<$?&f*+4}cYR##7;Cd##Ew+7a`3q3q zj5{YFd5g}kMZmv%;$_3I^tSmvu*09)y0jaMei%+R)&L`)V)qa>e1fc z2Z!?`SuiZcDeUolI|Cz#zv{>_#s+nI)O@E#*-!AVuF8y9xPF=HO9c;s;l5ed5Cal# zJQM-XSS{1c+2N4{WZU#$vzgMwTYy6V(_bL~j+7(0m@G-64*|Zzc8n(8gW26auJ!|q zjdfW8u&$koRrtk}k^rxg-nLTuSpE{Y{0+6h-6$Up34j|tu`)MAwi_3KcNUXdBOhTt z?FM%K+YRhf{N`?fyIRXy5#7cX6OH$!!PEDMC|S1D}D$A#-5Jy-q_ zeAVIOc$~Tus7*}XKYL?T;y`w#qi1hI9Qy({-ZS8ZI`&n|DVVXxIZ z#cU_6HUNsg4Xn1-Gyn++s8)cR6%-i`??Jl2bjdn@gn^kO=VV3^sPiheeB%&fcht}5a3%&k>Ms!v}_2LOAUTtXCV2KT#oUK z`sUa(U;t_?d}b;#EZbcz<3Qz)0OO7-Cm!eENp(AWUQGZ|`{~+@pszX@ Date: Tue, 11 Mar 2025 23:20:41 +0300 Subject: [PATCH 3/7] Fix parallel package installation (#5979) Co-authored-by: Laurenz --- Cargo.lock | 1 + Cargo.toml | 1 + crates/typst-kit/Cargo.toml | 3 +- crates/typst-kit/src/package.rs | 84 ++++++++++++++++++++++++++++++--- 4 files changed, 82 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ac08b57ee..06dd4ab80 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2896,6 +2896,7 @@ dependencies = [ "dirs", "ecow", "env_proxy", + "fastrand", "flate2", "fontdb", "native-tls", diff --git a/Cargo.toml b/Cargo.toml index 40abaaca7..4e0d3a26c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,6 +55,7 @@ ctrlc = "3.4.1" dirs = "6" ecow = { version = "0.2", features = ["serde"] } env_proxy = "0.4" +fastrand = "2.3" flate2 = "1" fontdb = { version = "0.23", default-features = false } fs_extra = "1.3" diff --git a/crates/typst-kit/Cargo.toml b/crates/typst-kit/Cargo.toml index 52aa407c3..e59127d71 100644 --- a/crates/typst-kit/Cargo.toml +++ b/crates/typst-kit/Cargo.toml @@ -19,6 +19,7 @@ typst-utils = { workspace = true } dirs = { workspace = true, optional = true } ecow = { workspace = true } env_proxy = { workspace = true, optional = true } +fastrand = { workspace = true, optional = true } flate2 = { workspace = true, optional = true } fontdb = { workspace = true, optional = true } native-tls = { workspace = true, optional = true } @@ -43,7 +44,7 @@ fonts = ["dep:fontdb", "fontdb/memmap", "fontdb/fontconfig"] downloads = ["dep:env_proxy", "dep:native-tls", "dep:ureq", "dep:openssl"] # Add package downloading utilities, implies `downloads` -packages = ["downloads", "dep:dirs", "dep:flate2", "dep:tar"] +packages = ["downloads", "dep:dirs", "dep:flate2", "dep:tar", "dep:fastrand"] # Embeds some fonts into the binary: # - For text: Libertinus Serif, New Computer Modern diff --git a/crates/typst-kit/src/package.rs b/crates/typst-kit/src/package.rs index 172d8740a..1a1abd607 100644 --- a/crates/typst-kit/src/package.rs +++ b/crates/typst-kit/src/package.rs @@ -1,6 +1,7 @@ //! Download and unpack packages and package indices. use std::fs; +use std::io; use std::path::{Path, PathBuf}; use ecow::eco_format; @@ -100,7 +101,7 @@ impl PackageStorage { // Download from network if it doesn't exist yet. if spec.namespace == DEFAULT_NAMESPACE { - self.download_package(spec, &dir, progress)?; + self.download_package(spec, cache_dir, progress)?; if dir.exists() { return Ok(dir); } @@ -167,7 +168,7 @@ impl PackageStorage { pub fn download_package( &self, spec: &PackageSpec, - package_dir: &Path, + cache_dir: &Path, progress: &mut dyn Progress, ) -> PackageResult<()> { assert_eq!(spec.namespace, DEFAULT_NAMESPACE); @@ -191,11 +192,52 @@ impl PackageStorage { } }; + // The directory in which the package's version lives. + let base_dir = cache_dir.join(format!("{}/{}", spec.namespace, spec.name)); + + // The place at which the specific package version will live in the end. + let package_dir = base_dir.join(format!("{}", spec.version)); + + // To prevent multiple Typst instances from interferring, we download + // into a temporary directory first and then move this directory to + // its final destination. + // + // In the `rename` function's documentation it is stated: + // > This will not work if the new name is on a different mount point. + // + // By locating the temporary directory directly next to where the + // package directory will live, we are (trying our best) making sure + // that `tempdir` and `package_dir` are on the same mount point. + let tempdir = Tempdir::create(base_dir.join(format!( + ".tmp-{}-{}", + spec.version, + fastrand::u32(..), + ))) + .map_err(|err| error("failed to create temporary package directory", err))?; + + // Decompress the archive into the temporary directory. let decompressed = flate2::read::GzDecoder::new(data.as_slice()); - tar::Archive::new(decompressed).unpack(package_dir).map_err(|err| { - fs::remove_dir_all(package_dir).ok(); - PackageError::MalformedArchive(Some(eco_format!("{err}"))) - }) + tar::Archive::new(decompressed) + .unpack(&tempdir) + .map_err(|err| PackageError::MalformedArchive(Some(eco_format!("{err}"))))?; + + // When trying to move (i.e., `rename`) the directory from one place to + // another and the target/destination directory is empty, then the + // operation will succeed (if it's atomic, or hardware doesn't fail, or + // power doesn't go off, etc.). If however the target directory is not + // empty, i.e., another instance already successfully moved the package, + // then we can safely ignore the `DirectoryNotEmpty` error. + // + // This means that we do not check the integrity of an existing moved + // package, just like we don't check the integrity if the package + // directory already existed in the first place. If situations with + // broken packages still occur even with the rename safeguard, we might + // consider more complex solutions like file locking or checksums. + match fs::rename(&tempdir, &package_dir) { + Ok(()) => Ok(()), + Err(err) if err.kind() == io::ErrorKind::DirectoryNotEmpty => Ok(()), + Err(err) => Err(error("failed to move downloaded package directory", err)), + } } } @@ -207,6 +249,36 @@ struct MinimalPackageInfo { version: PackageVersion, } +/// A temporary directory that is a automatically cleaned up. +struct Tempdir(PathBuf); + +impl Tempdir { + /// Creates a directory at the path and auto-cleans it. + fn create(path: PathBuf) -> io::Result { + std::fs::create_dir_all(&path)?; + Ok(Self(path)) + } +} + +impl Drop for Tempdir { + fn drop(&mut self) { + _ = fs::remove_dir_all(&self.0); + } +} + +impl AsRef for Tempdir { + fn as_ref(&self) -> &Path { + &self.0 + } +} + +/// Enriches an I/O error with a message and turns it into a +/// `PackageError::Other`. +#[cold] +fn error(message: &str, err: io::Error) -> PackageError { + PackageError::Other(Some(eco_format!("{message}: {err}"))) +} + #[cfg(test)] mod tests { use super::*; From 24b2f98bf9b8d7a8d8ad9f5f0585ab4317cd3666 Mon Sep 17 00:00:00 2001 From: Michael Fortunato Date: Wed, 12 Mar 2025 07:45:22 -0500 Subject: [PATCH 4/7] Fix typo in 4-template.md (#6047) --- docs/tutorial/4-template.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/4-template.md b/docs/tutorial/4-template.md index 209fa5546..7542cd6e4 100644 --- a/docs/tutorial/4-template.md +++ b/docs/tutorial/4-template.md @@ -44,7 +44,7 @@ I am #amazed(color: purple)[amazed]! Templates now work by wrapping our whole document in a custom function like `amazed`. But wrapping a whole document in a giant function call would be cumbersome! Instead, we can use an "everything" show rule to achieve the same -with cleaner code. To write such a show rule, put a colon directly behind the +with cleaner code. To write such a show rule, put a colon directly after the show keyword and then provide a function. This function is given the rest of the document as a parameter. The function can then do anything with this content. Since the `amazed` function can be called with a single content argument, we can From 37bb632d2e9f1f779e15dd5c21ff1ceeadea4a17 Mon Sep 17 00:00:00 2001 From: "Kevin K." Date: Wed, 12 Mar 2025 13:45:57 +0100 Subject: [PATCH 5/7] Fix missing words and paren in docs (#6046) --- crates/typst-library/src/model/bibliography.rs | 2 +- crates/typst-library/src/model/outline.rs | 2 +- crates/typst-library/src/text/raw.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/typst-library/src/model/bibliography.rs b/crates/typst-library/src/model/bibliography.rs index a391e5804..b11c61789 100644 --- a/crates/typst-library/src/model/bibliography.rs +++ b/crates/typst-library/src/model/bibliography.rs @@ -94,7 +94,7 @@ pub struct BibliographyElem { /// - A path string to load a bibliography file from the given path. For /// more details about paths, see the [Paths section]($syntax/#paths). /// - Raw bytes from which the bibliography should be decoded. - /// - An array where each item is one the above. + /// - An array where each item is one of the above. #[required] #[parse( let sources = args.expect("sources")?; diff --git a/crates/typst-library/src/model/outline.rs b/crates/typst-library/src/model/outline.rs index 7ceb530f8..489c375e6 100644 --- a/crates/typst-library/src/model/outline.rs +++ b/crates/typst-library/src/model/outline.rs @@ -388,7 +388,7 @@ pub struct OutlineEntry { /// space between the entry's body and the page number. When using show /// rules to override outline entries, it is thus recommended to wrap the /// fill in a [`box`] with fractional width, i.e. - /// `{box(width: 1fr, it.fill}`. + /// `{box(width: 1fr, it.fill)}`. /// /// When using [`repeat`], the [`gap`]($repeat.gap) property can be useful /// to tweak the visual weight of the fill. diff --git a/crates/typst-library/src/text/raw.rs b/crates/typst-library/src/text/raw.rs index 1ce8bfc61..d5c07424d 100644 --- a/crates/typst-library/src/text/raw.rs +++ b/crates/typst-library/src/text/raw.rs @@ -188,7 +188,7 @@ pub struct RawElem { /// - A path string to load a syntax file from the given path. For more /// details about paths, see the [Paths section]($syntax/#paths). /// - Raw bytes from which the syntax should be decoded. - /// - An array where each item is one the above. + /// - An array where each item is one of the above. /// /// ````example /// #set raw(syntaxes: "SExpressions.sublime-syntax") From 95a7e28e25be8374f8574244cc46cf42e97b937e Mon Sep 17 00:00:00 2001 From: Laurenz Date: Wed, 12 Mar 2025 13:46:03 +0100 Subject: [PATCH 6/7] Make two typst-kit functions private (#6045) --- crates/typst-kit/src/package.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/crates/typst-kit/src/package.rs b/crates/typst-kit/src/package.rs index 1a1abd607..584ec83c0 100644 --- a/crates/typst-kit/src/package.rs +++ b/crates/typst-kit/src/package.rs @@ -78,7 +78,8 @@ impl PackageStorage { self.package_path.as_deref() } - /// Make a package available in the on-disk. + /// Makes a package available on-disk and returns the path at which it is + /// located (will be either in the cache or package directory). pub fn prepare_package( &self, spec: &PackageSpec, @@ -111,7 +112,7 @@ impl PackageStorage { Err(PackageError::NotFound(spec.clone())) } - /// Try to determine the latest version of a package. + /// Tries to determine the latest version of a package. pub fn determine_latest_version( &self, spec: &VersionlessPackageSpec, @@ -144,7 +145,7 @@ impl PackageStorage { } /// Download the package index. The result of this is cached for efficiency. - pub fn download_index(&self) -> StrResult<&[serde_json::Value]> { + fn download_index(&self) -> StrResult<&[serde_json::Value]> { self.index .get_or_try_init(|| { let url = format!("{DEFAULT_REGISTRY}/{DEFAULT_NAMESPACE}/index.json"); @@ -165,7 +166,7 @@ impl PackageStorage { /// /// # Panics /// Panics if the package spec namespace isn't `DEFAULT_NAMESPACE`. - pub fn download_package( + fn download_package( &self, spec: &PackageSpec, cache_dir: &Path, From 1b2714e1a758d6ee0f9471fd1e49cb02f6d8cde4 Mon Sep 17 00:00:00 2001 From: Wolf-SO Date: Wed, 12 Mar 2025 19:29:35 +0100 Subject: [PATCH 7/7] Update 1-writing.md to improve readability (#6040) --- docs/tutorial/1-writing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/1-writing.md b/docs/tutorial/1-writing.md index 5a9fdd4f7..acc257830 100644 --- a/docs/tutorial/1-writing.md +++ b/docs/tutorial/1-writing.md @@ -172,7 +172,7 @@ nothing else. For example, the image function expects a path to an image file. It would not make sense to pass, e.g., a paragraph of text or another image as the image's path parameter. That's why only strings are allowed here. -On the contrary, strings work wherever content is expected because text is a +In contrast, strings work wherever content is expected because text is a valid kind of content.