mirror of
https://github.com/typst/typst
synced 2025-08-15 15:38:33 +08:00
Merge branch 'main' into krilla-port
This commit is contained in:
commit
8b63d09c24
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -2967,6 +2967,7 @@ dependencies = [
|
|||||||
"dirs",
|
"dirs",
|
||||||
"ecow",
|
"ecow",
|
||||||
"env_proxy",
|
"env_proxy",
|
||||||
|
"fastrand",
|
||||||
"flate2",
|
"flate2",
|
||||||
"fontdb",
|
"fontdb",
|
||||||
"native-tls",
|
"native-tls",
|
||||||
|
@ -55,6 +55,7 @@ ctrlc = "3.4.1"
|
|||||||
dirs = "6"
|
dirs = "6"
|
||||||
ecow = { version = "0.2", features = ["serde"] }
|
ecow = { version = "0.2", features = ["serde"] }
|
||||||
env_proxy = "0.4"
|
env_proxy = "0.4"
|
||||||
|
fastrand = "2.3"
|
||||||
flate2 = "1"
|
flate2 = "1"
|
||||||
fontdb = { version = "0.23", default-features = false }
|
fontdb = { version = "0.23", default-features = false }
|
||||||
fs_extra = "1.3"
|
fs_extra = "1.3"
|
||||||
|
@ -26,7 +26,7 @@ pub fn analyze_expr(
|
|||||||
ast::Expr::Str(v) => Value::Str(v.get().into()),
|
ast::Expr::Str(v) => Value::Str(v.get().into()),
|
||||||
_ => {
|
_ => {
|
||||||
if node.kind() == SyntaxKind::Contextual {
|
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);
|
return analyze_expr(world, &child);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ typst-utils = { workspace = true }
|
|||||||
dirs = { workspace = true, optional = true }
|
dirs = { workspace = true, optional = true }
|
||||||
ecow = { workspace = true }
|
ecow = { workspace = true }
|
||||||
env_proxy = { workspace = true, optional = true }
|
env_proxy = { workspace = true, optional = true }
|
||||||
|
fastrand = { workspace = true, optional = true }
|
||||||
flate2 = { workspace = true, optional = true }
|
flate2 = { workspace = true, optional = true }
|
||||||
fontdb = { workspace = true, optional = true }
|
fontdb = { workspace = true, optional = true }
|
||||||
native-tls = { 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"]
|
downloads = ["dep:env_proxy", "dep:native-tls", "dep:ureq", "dep:openssl"]
|
||||||
|
|
||||||
# Add package downloading utilities, implies `downloads`
|
# 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:
|
# Embeds some fonts into the binary:
|
||||||
# - For text: Libertinus Serif, New Computer Modern
|
# - For text: Libertinus Serif, New Computer Modern
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
//! Download and unpack packages and package indices.
|
//! Download and unpack packages and package indices.
|
||||||
|
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
use std::io;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use ecow::eco_format;
|
use ecow::eco_format;
|
||||||
@ -77,7 +78,8 @@ impl PackageStorage {
|
|||||||
self.package_path.as_deref()
|
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(
|
pub fn prepare_package(
|
||||||
&self,
|
&self,
|
||||||
spec: &PackageSpec,
|
spec: &PackageSpec,
|
||||||
@ -100,7 +102,7 @@ impl PackageStorage {
|
|||||||
|
|
||||||
// Download from network if it doesn't exist yet.
|
// Download from network if it doesn't exist yet.
|
||||||
if spec.namespace == DEFAULT_NAMESPACE {
|
if spec.namespace == DEFAULT_NAMESPACE {
|
||||||
self.download_package(spec, &dir, progress)?;
|
self.download_package(spec, cache_dir, progress)?;
|
||||||
if dir.exists() {
|
if dir.exists() {
|
||||||
return Ok(dir);
|
return Ok(dir);
|
||||||
}
|
}
|
||||||
@ -110,7 +112,7 @@ impl PackageStorage {
|
|||||||
Err(PackageError::NotFound(spec.clone()))
|
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(
|
pub fn determine_latest_version(
|
||||||
&self,
|
&self,
|
||||||
spec: &VersionlessPackageSpec,
|
spec: &VersionlessPackageSpec,
|
||||||
@ -143,7 +145,7 @@ impl PackageStorage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Download the package index. The result of this is cached for efficiency.
|
/// 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
|
self.index
|
||||||
.get_or_try_init(|| {
|
.get_or_try_init(|| {
|
||||||
let url = format!("{DEFAULT_REGISTRY}/{DEFAULT_NAMESPACE}/index.json");
|
let url = format!("{DEFAULT_REGISTRY}/{DEFAULT_NAMESPACE}/index.json");
|
||||||
@ -164,10 +166,10 @@ impl PackageStorage {
|
|||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
/// Panics if the package spec namespace isn't `DEFAULT_NAMESPACE`.
|
/// Panics if the package spec namespace isn't `DEFAULT_NAMESPACE`.
|
||||||
pub fn download_package(
|
fn download_package(
|
||||||
&self,
|
&self,
|
||||||
spec: &PackageSpec,
|
spec: &PackageSpec,
|
||||||
package_dir: &Path,
|
cache_dir: &Path,
|
||||||
progress: &mut dyn Progress,
|
progress: &mut dyn Progress,
|
||||||
) -> PackageResult<()> {
|
) -> PackageResult<()> {
|
||||||
assert_eq!(spec.namespace, DEFAULT_NAMESPACE);
|
assert_eq!(spec.namespace, DEFAULT_NAMESPACE);
|
||||||
@ -191,11 +193,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());
|
let decompressed = flate2::read::GzDecoder::new(data.as_slice());
|
||||||
tar::Archive::new(decompressed).unpack(package_dir).map_err(|err| {
|
tar::Archive::new(decompressed)
|
||||||
fs::remove_dir_all(package_dir).ok();
|
.unpack(&tempdir)
|
||||||
PackageError::MalformedArchive(Some(eco_format!("{err}")))
|
.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 +250,36 @@ struct MinimalPackageInfo {
|
|||||||
version: PackageVersion,
|
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<Self> {
|
||||||
|
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<Path> 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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -115,7 +115,7 @@ impl<'a, 'b> Composer<'a, 'b, '_, '_> {
|
|||||||
let column_height = regions.size.y;
|
let column_height = regions.size.y;
|
||||||
let backlog: Vec<_> = std::iter::once(&column_height)
|
let backlog: Vec<_> = std::iter::once(&column_height)
|
||||||
.chain(regions.backlog)
|
.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)
|
.skip(1)
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
@ -1469,7 +1469,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
// last height is the one for the current region.
|
// last height is the one for the current region.
|
||||||
rowspan
|
rowspan
|
||||||
.heights
|
.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
|
// Ensure that, in this region, the rowspan will span at least
|
||||||
// this row.
|
// this row.
|
||||||
|
@ -85,14 +85,15 @@ pub fn layout_root(
|
|||||||
ascent.set_max(shift_up + index.ascent());
|
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 radicand_y = ascent - radicand.ascent();
|
||||||
let width = radicand_x + radicand.width();
|
let width = radicand_x + radicand.width();
|
||||||
let size = Size::new(width, ascent + descent);
|
let size = Size::new(width, ascent + descent);
|
||||||
|
|
||||||
// The extra "- thickness" comes from the fact that the sqrt is placed
|
// The extra "- thickness" comes from the fact that the sqrt is placed
|
||||||
// in `push_frame` with respect to its top, not its baseline.
|
// 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 line_pos = Point::new(radicand_x, radicand_y - gap - (thickness / 2.0));
|
||||||
let radicand_pos = Point::new(radicand_x, radicand_y);
|
let radicand_pos = Point::new(radicand_x, radicand_y);
|
||||||
|
|
||||||
@ -100,7 +101,8 @@ pub fn layout_root(
|
|||||||
frame.set_baseline(ascent);
|
frame.set_baseline(ascent);
|
||||||
|
|
||||||
if let Some(index) = index {
|
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);
|
frame.push_frame(index_pos, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -302,6 +302,6 @@ fn assemble(
|
|||||||
fn parts(assembly: GlyphAssembly, repeat: usize) -> impl Iterator<Item = GlyphPart> + '_ {
|
fn parts(assembly: GlyphAssembly, repeat: usize) -> impl Iterator<Item = GlyphPart> + '_ {
|
||||||
assembly.parts.into_iter().flat_map(move |part| {
|
assembly.parts.into_iter().flat_map(move |part| {
|
||||||
let count = if part.part_flags.extender() { repeat } else { 1 };
|
let count = if part.part_flags.extender() { repeat } else { 1 };
|
||||||
std::iter::repeat(part).take(count)
|
std::iter::repeat_n(part, count)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -94,7 +94,7 @@ pub struct BibliographyElem {
|
|||||||
/// - A path string to load a bibliography file from the given path. For
|
/// - A path string to load a bibliography file from the given path. For
|
||||||
/// more details about paths, see the [Paths section]($syntax/#paths).
|
/// more details about paths, see the [Paths section]($syntax/#paths).
|
||||||
/// - Raw bytes from which the bibliography should be decoded.
|
/// - 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]
|
#[required]
|
||||||
#[parse(
|
#[parse(
|
||||||
let sources = args.expect("sources")?;
|
let sources = args.expect("sources")?;
|
||||||
|
@ -394,7 +394,7 @@ impl NumberingKind {
|
|||||||
const SYMBOLS: &[char] = &['*', '†', '‡', '§', '¶', '‖'];
|
const SYMBOLS: &[char] = &['*', '†', '‡', '§', '¶', '‖'];
|
||||||
let symbol = SYMBOLS[(n - 1) % SYMBOLS.len()];
|
let symbol = SYMBOLS[(n - 1) % SYMBOLS.len()];
|
||||||
let amount = ((n - 1) / SYMBOLS.len()) + 1;
|
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),
|
Self::Hebrew => hebrew_numeral(n),
|
||||||
|
|
||||||
|
@ -388,7 +388,7 @@ pub struct OutlineEntry {
|
|||||||
/// space between the entry's body and the page number. When using show
|
/// 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
|
/// rules to override outline entries, it is thus recommended to wrap the
|
||||||
/// fill in a [`box`] with fractional width, i.e.
|
/// 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
|
/// When using [`repeat`], the [`gap`]($repeat.gap) property can be useful
|
||||||
/// to tweak the visual weight of the fill.
|
/// to tweak the visual weight of the fill.
|
||||||
|
@ -188,7 +188,7 @@ pub struct RawElem {
|
|||||||
/// - A path string to load a syntax file from the given path. For more
|
/// - A path string to load a syntax file from the given path. For more
|
||||||
/// details about paths, see the [Paths section]($syntax/#paths).
|
/// details about paths, see the [Paths section]($syntax/#paths).
|
||||||
/// - Raw bytes from which the syntax should be decoded.
|
/// - 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
|
/// ````example
|
||||||
/// #set raw(syntaxes: "SExpressions.sublime-syntax")
|
/// #set raw(syntaxes: "SExpressions.sublime-syntax")
|
||||||
|
@ -574,8 +574,7 @@ impl Gradient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let n = repetitions.v;
|
let n = repetitions.v;
|
||||||
let mut stops = std::iter::repeat(self.stops_ref())
|
let mut stops = std::iter::repeat_n(self.stops_ref(), n)
|
||||||
.take(n)
|
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.flat_map(|(i, stops)| {
|
.flat_map(|(i, stops)| {
|
||||||
let mut stops = stops
|
let mut stops = stops
|
||||||
|
@ -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 dash = dash.as_ref().and_then(to_sk_dash_pattern);
|
||||||
|
|
||||||
let bbox = shape.geometry.bbox_size();
|
let bbox = shape.geometry.bbox_size();
|
||||||
let offset_bbox = (!matches!(shape.geometry, Geometry::Line(..)))
|
let offset_bbox = if !matches!(shape.geometry, Geometry::Line(..)) {
|
||||||
.then(|| offset_bounding_box(bbox, *thickness))
|
offset_bounding_box(bbox, *thickness)
|
||||||
.unwrap_or(bbox);
|
} else {
|
||||||
|
bbox
|
||||||
|
};
|
||||||
|
|
||||||
let fill_transform =
|
let fill_transform =
|
||||||
(!matches!(shape.geometry, Geometry::Line(..))).then(|| {
|
(!matches!(shape.geometry, Geometry::Line(..))).then(|| {
|
||||||
|
@ -172,7 +172,7 @@ nothing else.
|
|||||||
For example, the image function expects a path to an image file.
|
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
|
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.
|
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.
|
valid kind of content.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ I am #amazed(color: purple)[amazed]!
|
|||||||
Templates now work by wrapping our whole document in a custom function like
|
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
|
`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
|
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
|
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.
|
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
|
Since the `amazed` function can be called with a single content argument, we can
|
||||||
|
BIN
tests/ref/math-root-frame-size-index.png
Normal file
BIN
tests/ref/math-root-frame-size-index.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 902 B |
@ -44,3 +44,9 @@ $ root(2, x) quad
|
|||||||
$ √2^3 = sqrt(2^3) $
|
$ √2^3 = sqrt(2^3) $
|
||||||
$ √(x+y) quad ∛x quad ∜x $
|
$ √(x+y) quad ∛x quad ∜x $
|
||||||
$ (√2+3) = (sqrt(2)+3) $
|
$ (√2+3) = (sqrt(2)+3) $
|
||||||
|
|
||||||
|
--- math-root-frame-size-index ---
|
||||||
|
// Test size of final frame when there is an index.
|
||||||
|
$ a root(, 3) & a root(., 3) \
|
||||||
|
a sqrt(3) & a root(2, 3) \
|
||||||
|
a root(#h(-1em), 3) & a root(123, 3) $
|
||||||
|
Loading…
x
Reference in New Issue
Block a user