Compare commits

..

5 Commits

Author SHA1 Message Date
Max
aee99408e1
Apply short fall consistently in math when stretching (#6377) 2025-06-04 10:14:24 +00:00
Linus Unnebäck
1de2095f67
Add support for WebP images (#6311) 2025-06-04 09:54:03 +00:00
Max
5f776c7372
Bump New CM fonts to version 7.0.2 (#6376) 2025-06-04 09:41:08 +00:00
Max
128c40d839
Apply script-style to numbers consistently in math (#6320) 2025-06-04 08:20:54 +00:00
Nazar Serhiichuk
4a8367e90a
Fix Ukrainian secondary smart quotes (#6372) 2025-06-04 08:13:39 +00:00
62 changed files with 44 additions and 42 deletions

3
Cargo.lock generated
View File

@ -1215,6 +1215,7 @@ dependencies = [
"byteorder-lite",
"color_quant",
"gif",
"image-webp",
"num-traits",
"png",
"zune-core",
@ -2863,7 +2864,7 @@ dependencies = [
[[package]]
name = "typst-assets"
version = "0.13.1"
source = "git+https://github.com/typst/typst-assets?rev=ab1295f#ab1295ff896444e51902e03c2669955e1d73604a"
source = "git+https://github.com/typst/typst-assets?rev=c74e539#c74e539b090070a0c66fd007c550f5b6d3b724bd"
[[package]]
name = "typst-cli"

View File

@ -32,7 +32,7 @@ typst-svg = { path = "crates/typst-svg", version = "0.13.1" }
typst-syntax = { path = "crates/typst-syntax", version = "0.13.1" }
typst-timing = { path = "crates/typst-timing", version = "0.13.1" }
typst-utils = { path = "crates/typst-utils", version = "0.13.1" }
typst-assets = { git = "https://github.com/typst/typst-assets", rev = "ab1295f" }
typst-assets = { git = "https://github.com/typst/typst-assets", rev = "c74e539" }
typst-dev-assets = { git = "https://github.com/typst/typst-dev-assets", rev = "fddbf8b" }
arrayvec = "0.7.4"
az = "1.2"
@ -69,7 +69,7 @@ icu_provider_adapters = "1.4"
icu_provider_blob = "1.4"
icu_segmenter = { version = "1.4", features = ["serde"] }
if_chain = "1"
image = { version = "0.25.5", default-features = false, features = ["png", "jpeg", "gif"] }
image = { version = "0.25.5", default-features = false, features = ["png", "jpeg", "gif", "webp"] }
indexmap = { version = "2", features = ["serde"] }
infer = { version = "0.19.0", default-features = false }
kamadak-exif = "0.6"

View File

@ -841,7 +841,9 @@ fn param_value_completions<'a>(
/// Returns which file extensions to complete for the given parameter if any.
fn path_completion(func: &Func, param: &ParamInfo) -> Option<&'static [&'static str]> {
Some(match (func.name(), param.name) {
(Some("image"), "source") => &["png", "jpg", "jpeg", "gif", "svg", "svgz"],
(Some("image"), "source") => {
&["png", "jpg", "jpeg", "gif", "svg", "svgz", "webp"]
}
(Some("csv"), "source") => &["csv"],
(Some("plugin"), "source") => &["wasm"],
(Some("cbor"), "source") => &["cbor"],

View File

@ -147,6 +147,7 @@ fn determine_format(source: &DataSource, data: &Bytes) -> StrResult<ImageFormat>
"jpg" | "jpeg" => return Ok(ExchangeFormat::Jpg.into()),
"gif" => return Ok(ExchangeFormat::Gif.into()),
"svg" | "svgz" => return Ok(VectorFormat::Svg.into()),
"webp" => return Ok(ExchangeFormat::Webp.into()),
_ => {}
}
}

View File

@ -46,7 +46,7 @@ pub fn layout_accent(
// wide in many case.
let width = elem.size(styles).relative_to(base.width());
let short_fall = ACCENT_SHORT_FALL.at(glyph.font_size);
let variant = glyph.stretch_horizontal(ctx, width, short_fall);
let variant = glyph.stretch_horizontal(ctx, width - short_fall);
let accent = variant.frame;
let accent_attach = variant.accent_attach.0;

View File

@ -110,12 +110,12 @@ fn layout_frac_like(
if binom {
let mut left = GlyphFragment::new(ctx, styles, '(', span)
.stretch_vertical(ctx, height, short_fall);
.stretch_vertical(ctx, height - short_fall);
left.center_on_axis(ctx);
ctx.push(left);
ctx.push(FrameFragment::new(styles, frame));
let mut right = GlyphFragment::new(ctx, styles, ')', span)
.stretch_vertical(ctx, height, short_fall);
.stretch_vertical(ctx, height - short_fall);
right.center_on_axis(ctx);
ctx.push(right);
} else {

View File

@ -435,13 +435,8 @@ impl GlyphFragment {
}
/// Try to stretch a glyph to a desired height.
pub fn stretch_vertical(
self,
ctx: &mut MathContext,
height: Abs,
short_fall: Abs,
) -> VariantFragment {
stretch_glyph(ctx, self, height, short_fall, Axis::Y)
pub fn stretch_vertical(self, ctx: &mut MathContext, height: Abs) -> VariantFragment {
stretch_glyph(ctx, self, height, Axis::Y)
}
/// Try to stretch a glyph to a desired width.
@ -449,9 +444,8 @@ impl GlyphFragment {
self,
ctx: &mut MathContext,
width: Abs,
short_fall: Abs,
) -> VariantFragment {
stretch_glyph(ctx, self, width, short_fall, Axis::X)
stretch_glyph(ctx, self, width, Axis::X)
}
}

View File

@ -314,7 +314,7 @@ fn layout_delimiters(
if let Some(left) = left {
let mut left = GlyphFragment::new(ctx, styles, left, span)
.stretch_vertical(ctx, target, short_fall);
.stretch_vertical(ctx, target - short_fall);
left.align_on_axis(ctx, delimiter_alignment(left.c));
ctx.push(left);
}
@ -323,7 +323,7 @@ fn layout_delimiters(
if let Some(right) = right {
let mut right = GlyphFragment::new(ctx, styles, right, span)
.stretch_vertical(ctx, target, short_fall);
.stretch_vertical(ctx, target - short_fall);
right.align_on_axis(ctx, delimiter_alignment(right.c));
ctx.push(right);
}

View File

@ -50,7 +50,7 @@ pub fn layout_root(
// Layout root symbol.
let target = radicand.height() + thickness + gap;
let sqrt = GlyphFragment::new(ctx, styles, '√', span)
.stretch_vertical(ctx, target, Abs::zero())
.stretch_vertical(ctx, target)
.frame;
// Layout the index.

View File

@ -67,8 +67,7 @@ pub fn stretch_fragment(
let mut variant = stretch_glyph(
ctx,
glyph,
stretch.relative_to(relative_to_size),
short_fall,
stretch.relative_to(relative_to_size) - short_fall,
axis,
);
@ -120,7 +119,6 @@ pub fn stretch_glyph(
ctx: &mut MathContext,
mut base: GlyphFragment,
target: Abs,
short_fall: Abs,
axis: Axis,
) -> VariantFragment {
// If the base glyph is good enough, use it.
@ -128,8 +126,7 @@ pub fn stretch_glyph(
Axis::X => base.width,
Axis::Y => base.height(),
};
let short_target = target - short_fall;
if short_target <= advance {
if target <= advance {
return base.into_variant();
}
@ -153,13 +150,13 @@ pub fn stretch_glyph(
for variant in construction.variants {
best_id = variant.variant_glyph;
best_advance = base.font.to_em(variant.advance_measurement).at(base.font_size);
if short_target <= best_advance {
if target <= best_advance {
break;
}
}
// This is either good or the best we've got.
if short_target <= best_advance || construction.assembly.is_none() {
if target <= best_advance || construction.assembly.is_none() {
base.set_id(ctx, best_id);
return base.into_variant();
}

View File

@ -65,18 +65,13 @@ fn layout_inline_text(
// Small optimization for numbers. Note that this lays out slightly
// differently to normal text and is worth re-evaluating in the future.
let mut fragments = vec![];
let is_single = text.chars().count() == 1;
for unstyled_c in text.chars() {
let c = styled_char(styles, unstyled_c, false);
let mut glyph = GlyphFragment::new(ctx, styles, c, span);
if is_single {
// Duplicate what `layout_glyph` does exactly even if it's
// probably incorrect here.
match EquationElem::size_in(styles) {
MathSize::Script => glyph.make_script_size(ctx),
MathSize::ScriptScript => glyph.make_script_script_size(ctx),
_ => {}
}
match EquationElem::size_in(styles) {
MathSize::Script => glyph.make_script_size(ctx),
MathSize::ScriptScript => glyph.make_script_script_size(ctx),
_ => {}
}
fragments.push(glyph.into());
}
@ -164,7 +159,7 @@ fn layout_glyph(
let mut variant = if math_size == MathSize::Display {
let height = scaled!(ctx, styles, display_operator_min_height)
.max(SQRT_2 * glyph.height());
glyph.stretch_vertical(ctx, height, Abs::zero())
glyph.stretch_vertical(ctx, height)
} else {
glyph.into_variant()
};

View File

@ -286,7 +286,7 @@ fn layout_underoverspreader(
let body_class = body.class();
let body = body.into_fragment(styles);
let glyph = GlyphFragment::new(ctx, styles, c, span);
let stretched = glyph.stretch_horizontal(ctx, body.width(), Abs::zero());
let stretched = glyph.stretch_horizontal(ctx, body.width());
let mut rows = vec![];
let baseline = match position {

View File

@ -247,8 +247,9 @@ impl<'s> SmartQuotes<'s> {
"es" if matches!(region, Some("ES") | None) => ("", "", "«", "»"),
"hu" | "pl" | "ro" => ("", "", "", ""),
"no" | "nb" | "nn" if alternative => low_high,
"no" | "nb" | "nn" | "uk" => ("", "", "«", "»"),
"no" | "nb" | "nn" => ("", "", "«", "»"),
"ru" => ("", "", "«", "»"),
"uk" => ("", "", "«", "»"),
"el" => ("", "", "«", "»"),
"he" => ("", "", "", ""),
"hr" => ("", "", "", ""),

View File

@ -77,8 +77,8 @@ pub struct ImageElem {
/// [`source`]($image.source) (even then, Typst will try to figure out the
/// format automatically, but that's not always possible).
///
/// Supported formats are `{"png"}`, `{"jpg"}`, `{"gif"}`, `{"svg"}` as well
/// as raw pixel data. Embedding PDFs as images is
/// Supported formats are `{"png"}`, `{"jpg"}`, `{"gif"}`, `{"svg"}`,
/// `{"webp"}` as well as raw pixel data. Embedding PDFs as images is
/// [not currently supported](https://github.com/typst/typst/issues/145).
///
/// When providing raw pixel data as the `source`, you must specify a

View File

@ -9,6 +9,7 @@ use ecow::{eco_format, EcoString};
use image::codecs::gif::GifDecoder;
use image::codecs::jpeg::JpegDecoder;
use image::codecs::png::PngDecoder;
use image::codecs::webp::WebPDecoder;
use image::{
guess_format, DynamicImage, ImageBuffer, ImageDecoder, ImageResult, Limits, Pixel,
};
@ -77,6 +78,7 @@ impl RasterImage {
ExchangeFormat::Jpg => decode(JpegDecoder::new(cursor), icc),
ExchangeFormat::Png => decode(PngDecoder::new(cursor), icc),
ExchangeFormat::Gif => decode(GifDecoder::new(cursor), icc),
ExchangeFormat::Webp => decode(WebPDecoder::new(cursor), icc),
}
.map_err(format_image_error)?;
@ -242,6 +244,8 @@ pub enum ExchangeFormat {
/// Raster format that is typically used for short animated clips. Typst can
/// load GIFs, but they will become static.
Gif,
/// Raster format that supports both lossy and lossless compression.
Webp,
}
impl ExchangeFormat {
@ -257,6 +261,7 @@ impl From<ExchangeFormat> for image::ImageFormat {
ExchangeFormat::Png => image::ImageFormat::Png,
ExchangeFormat::Jpg => image::ImageFormat::Jpeg,
ExchangeFormat::Gif => image::ImageFormat::Gif,
ExchangeFormat::Webp => image::ImageFormat::WebP,
}
}
}
@ -269,6 +274,7 @@ impl TryFrom<image::ImageFormat> for ExchangeFormat {
image::ImageFormat::Png => ExchangeFormat::Png,
image::ImageFormat::Jpeg => ExchangeFormat::Jpg,
image::ImageFormat::Gif => ExchangeFormat::Gif,
image::ImageFormat::WebP => ExchangeFormat::Webp,
_ => bail!("format not yet supported"),
})
}

View File

@ -45,6 +45,7 @@ pub fn convert_image_to_base64_url(image: &Image) -> EcoString {
ExchangeFormat::Png => "png",
ExchangeFormat::Jpg => "jpeg",
ExchangeFormat::Gif => "gif",
ExchangeFormat::Webp => "webp",
},
raster.data(),
),

View File

@ -69,7 +69,7 @@ the first item of the list above by indenting it.
## Adding a figure { #figure }
You think that your report would benefit from a figure. Let's add one. Typst
supports images in the formats PNG, JPEG, GIF, and SVG. To add an image file to
supports images in the formats PNG, JPEG, GIF, SVG, and WebP. To add an image file to
your project, first open the _file panel_ by clicking the box icon in the left
sidebar. Here, you can see a list of all files in your project. Currently, there
is only one: The main Typst file you are writing in. To upload another file,

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 465 B

After

Width:  |  Height:  |  Size: 461 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 644 B

After

Width:  |  Height:  |  Size: 716 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 572 B

After

Width:  |  Height:  |  Size: 567 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 359 B

After

Width:  |  Height:  |  Size: 351 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 510 B

After

Width:  |  Height:  |  Size: 506 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 670 B

After

Width:  |  Height:  |  Size: 687 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 340 B

After

Width:  |  Height:  |  Size: 354 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 506 B

After

Width:  |  Height:  |  Size: 492 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 927 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 989 B

After

Width:  |  Height:  |  Size: 903 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 976 B

After

Width:  |  Height:  |  Size: 875 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 954 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 818 B

After

Width:  |  Height:  |  Size: 816 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 496 B

After

Width:  |  Height:  |  Size: 526 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 651 B

After

Width:  |  Height:  |  Size: 648 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 882 B

After

Width:  |  Height:  |  Size: 956 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 902 B

After

Width:  |  Height:  |  Size: 897 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 648 B

After

Width:  |  Height:  |  Size: 640 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 927 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 420 B

After

Width:  |  Height:  |  Size: 436 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 651 B

After

Width:  |  Height:  |  Size: 648 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 620 B

After

Width:  |  Height:  |  Size: 630 B

BIN
tests/ref/smartquote-uk.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -46,6 +46,10 @@
#set text(lang: "ru")
"Лошадь не ест салат из огурцов" - это была первая фраза, сказанная по 'телефону'.
--- smartquote-uk ---
#set text(lang: "uk")
"Кінь не їсть огірковий салат" перше речення, коли-небудь вимовлене по 'телефону'.
--- smartquote-it ---
#set text(lang: "it")
"Il cavallo non mangia insalata di cetrioli" è stata la prima frase pronunciata al 'telefono'.

View File

@ -243,7 +243,7 @@ A #box(image("/assets/images/tiger.jpg", height: 1cm, width: 80%)) B
--- image-png-but-pixmap-format ---
#image(
read("/assets/images/tiger.jpg", encoding: none),
// Error: 11-18 expected "png", "jpg", "gif", dictionary, "svg", or auto
// Error: 11-18 expected "png", "jpg", "gif", "webp", dictionary, "svg", or auto
format: "rgba8",
)