Compare commits

...

13 Commits

Author SHA1 Message Date
Laurenz Stampfl
e4cdaadcfb
Merge 77c25aed746e106b034406435c702d4a875952af into b790c6d59ceaf7a809cc24b60c1f1509807470e2 2025-07-20 13:23:42 +00:00
Laurenz Stampfl
77c25aed74 Update dependencies 2025-07-20 15:23:34 +02:00
Laurenz Stampfl
e71057536b Rename test 2025-07-20 15:22:08 +02:00
Laurenz Stampfl
2f72a1f197 Add tests 2025-07-20 15:13:34 +02:00
Laurenz Stampfl
62eab1dc9f Remove outdated comment 2025-07-20 14:50:02 +02:00
Laurenz Stampfl
7791162455 Group imports 2025-07-20 14:49:34 +02:00
Laurenz Stampfl
24d68ba61c Update documentation 2025-07-20 14:48:50 +02:00
Laurenz Stampfl
34d26dc64b
Merge branch 'main' into pdf_embed 2025-07-20 14:41:00 +02:00
Laurenz Stampfl
62daf7d3ef Add proper errors for PDFs that failed to load 2025-07-20 14:40:14 +02:00
Laurenz Stampfl
d9fb17edb3 Embed all fonts 2025-07-20 14:30:08 +02:00
Erik
b790c6d59c
Add rust-analyzer to flake devShell (#6618) 2025-07-18 14:36:10 +00:00
Malo
b1c79b50d4
Fix documentation oneliners (#6608) 2025-07-18 13:25:17 +00:00
Patrick Massot
4629ede020
Mention Tinymist in README.md (#6601) 2025-07-18 13:21:36 +00:00
15 changed files with 157 additions and 121 deletions

17
Cargo.lock generated
View File

@ -967,7 +967,7 @@ dependencies = [
[[package]] [[package]]
name = "hayro" name = "hayro"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/LaurenzV/hayro?rev=2b63dc8#2b63dc85b9447a815cc5b40c16338841c3780f7e" source = "git+https://github.com/LaurenzV/hayro?rev=d651e18#d651e188a747c188db2e206733eaf17e8f622a5d"
dependencies = [ dependencies = [
"bytemuck", "bytemuck",
"hayro-interpret", "hayro-interpret",
@ -980,7 +980,7 @@ dependencies = [
[[package]] [[package]]
name = "hayro-font" name = "hayro-font"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/LaurenzV/hayro?rev=2b63dc8#2b63dc85b9447a815cc5b40c16338841c3780f7e" source = "git+https://github.com/LaurenzV/hayro?rev=d651e18#d651e188a747c188db2e206733eaf17e8f622a5d"
dependencies = [ dependencies = [
"log", "log",
"phf", "phf",
@ -989,7 +989,7 @@ dependencies = [
[[package]] [[package]]
name = "hayro-interpret" name = "hayro-interpret"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/LaurenzV/hayro?rev=2b63dc8#2b63dc85b9447a815cc5b40c16338841c3780f7e" source = "git+https://github.com/LaurenzV/hayro?rev=d651e18#d651e188a747c188db2e206733eaf17e8f622a5d"
dependencies = [ dependencies = [
"bitflags 2.9.1", "bitflags 2.9.1",
"hayro-font", "hayro-font",
@ -1006,7 +1006,7 @@ dependencies = [
[[package]] [[package]]
name = "hayro-syntax" name = "hayro-syntax"
version = "0.0.1" version = "0.0.1"
source = "git+https://github.com/LaurenzV/hayro?rev=2b63dc8#2b63dc85b9447a815cc5b40c16338841c3780f7e" source = "git+https://github.com/LaurenzV/hayro?rev=d651e18#d651e188a747c188db2e206733eaf17e8f622a5d"
dependencies = [ dependencies = [
"flate2", "flate2",
"kurbo", "kurbo",
@ -1019,7 +1019,7 @@ dependencies = [
[[package]] [[package]]
name = "hayro-write" name = "hayro-write"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/LaurenzV/hayro?rev=2b63dc8#2b63dc85b9447a815cc5b40c16338841c3780f7e" source = "git+https://github.com/LaurenzV/hayro?rev=d651e18#d651e188a747c188db2e206733eaf17e8f622a5d"
dependencies = [ dependencies = [
"flate2", "flate2",
"hayro-syntax", "hayro-syntax",
@ -1430,7 +1430,7 @@ dependencies = [
[[package]] [[package]]
name = "krilla" name = "krilla"
version = "0.4.0" version = "0.4.0"
source = "git+https://github.com/LaurenzV/krilla?rev=1668ac2#1668ac2e64dc85572e6c62a2399e85acd39b619d" source = "git+https://github.com/LaurenzV/krilla/?rev=2da9d6c#2da9d6c6cb6a6baa6379592c72ae4eb7e16aa7b4"
dependencies = [ dependencies = [
"base64", "base64",
"bumpalo", "bumpalo",
@ -1460,7 +1460,7 @@ dependencies = [
[[package]] [[package]]
name = "krilla-svg" name = "krilla-svg"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/LaurenzV/krilla?rev=1668ac2#1668ac2e64dc85572e6c62a2399e85acd39b619d" source = "git+https://github.com/LaurenzV/krilla/?rev=2da9d6c#2da9d6c6cb6a6baa6379592c72ae4eb7e16aa7b4"
dependencies = [ dependencies = [
"flate2", "flate2",
"fontdb", "fontdb",
@ -2926,6 +2926,7 @@ dependencies = [
[[package]] [[package]]
name = "typst-assets" name = "typst-assets"
version = "0.13.1" version = "0.13.1"
source = "git+https://github.com/LaurenzV/typst-assets?rev=d89cc82#d89cc821f84a5667714491019c0b64087b2608bd"
[[package]] [[package]]
name = "typst-cli" name = "typst-cli"
@ -2975,7 +2976,7 @@ dependencies = [
[[package]] [[package]]
name = "typst-dev-assets" name = "typst-dev-assets"
version = "0.13.1" version = "0.13.1"
source = "git+https://github.com/typst/typst-dev-assets?rev=bfa947f#bfa947f3433d7d13a995168c40ae788a2ebfe648" source = "git+https://github.com/LaurenzV/typst-dev-assets?rev=180c145#180c145cf810c8b2a7ed77355b351f3aed07d0c6"
[[package]] [[package]]
name = "typst-docs" name = "typst-docs"

View File

@ -32,8 +32,8 @@ typst-svg = { path = "crates/typst-svg", version = "0.13.1" }
typst-syntax = { path = "crates/typst-syntax", 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-timing = { path = "crates/typst-timing", version = "0.13.1" }
typst-utils = { path = "crates/typst-utils", version = "0.13.1" } typst-utils = { path = "crates/typst-utils", version = "0.13.1" }
typst-assets = { path = "../typst-assets" } typst-assets = { git = "https://github.com/LaurenzV/typst-assets", rev = "d89cc82" }
typst-dev-assets = { git = "https://github.com/typst/typst-dev-assets", rev = "bfa947f" } typst-dev-assets = { git = "https://github.com/LaurenzV/typst-dev-assets", rev = "180c145" }
arrayvec = "0.7.4" arrayvec = "0.7.4"
az = "1.2" az = "1.2"
base64 = "0.22" base64 = "0.22"
@ -61,8 +61,8 @@ fontdb = { version = "0.23", default-features = false }
fs_extra = "1.3" fs_extra = "1.3"
glidesort = "0.1.2" glidesort = "0.1.2"
hayagriva = "0.8.1" hayagriva = "0.8.1"
hayro-syntax = { git = "https://github.com/LaurenzV/hayro", rev = "2b63dc8" } hayro-syntax = { git = "https://github.com/LaurenzV/hayro", rev = "d651e18" }
hayro = { git = "https://github.com/LaurenzV/hayro", rev = "2b63dc8" } hayro = { git = "https://github.com/LaurenzV/hayro", rev = "d651e18" }
heck = "0.5" heck = "0.5"
hypher = "0.1.4" hypher = "0.1.4"
icu_properties = { version = "1.4", features = ["serde"] } icu_properties = { version = "1.4", features = ["serde"] }
@ -75,8 +75,8 @@ image = { version = "0.25.5", default-features = false, features = ["png", "jpeg
indexmap = { version = "2", features = ["serde"] } indexmap = { version = "2", features = ["serde"] }
infer = { version = "0.19.0", default-features = false } infer = { version = "0.19.0", default-features = false }
kamadak-exif = "0.6" kamadak-exif = "0.6"
krilla = { git = "https://github.com/LaurenzV/krilla", rev = "1668ac2", default-features = false, features = ["raster-images", "comemo", "rayon", "pdf"] } krilla = { git = "https://github.com/LaurenzV/krilla/", rev = "2da9d6c", default-features = false, features = ["raster-images", "comemo", "rayon", "pdf"] }
krilla-svg = { git = "https://github.com/LaurenzV/krilla", rev = "1668ac2" } krilla-svg = { git = "https://github.com/LaurenzV/krilla/", rev = "2da9d6c"}
kurbo = "0.11" kurbo = "0.11"
libfuzzer-sys = "0.4" libfuzzer-sys = "0.4"
lipsum = "0.9" lipsum = "0.9"

View File

@ -173,8 +173,11 @@ typst help
typst help watch typst help watch
``` ```
If you prefer an integrated IDE-like experience with autocompletion and instant If you prefer an integrated IDE-like experience with autocompletion and instant
preview, you can also check out [Typst's free web app][app]. preview, you can also check out our [free web app][app]. Alternatively, there is
a community-created language server called
[Tinymist](https://myriad-dreamin.github.io/tinymist/) which is integrated into
various editor extensions.
## Community ## Community
The main places where the community gathers are our [Forum][forum] and our The main places where the community gathers are our [Forum][forum] and our

View File

@ -797,7 +797,9 @@ impl Color {
components components
} }
/// Returns the constructor function for this color's space: /// Returns the constructor function for this color's space.
///
/// Returns one of:
/// - [`luma`]($color.luma) /// - [`luma`]($color.luma)
/// - [`oklab`]($color.oklab) /// - [`oklab`]($color.oklab)
/// - [`oklch`]($color.oklch) /// - [`oklch`]($color.oklch)

View File

@ -15,6 +15,7 @@ use std::fmt::{self, Debug, Formatter};
use std::sync::Arc; use std::sync::Arc;
use ecow::EcoString; use ecow::EcoString;
use hayro_syntax::LoadPdfError;
use typst_library::{Feature, World}; use typst_library::{Feature, World};
use typst_syntax::{Span, Spanned}; use typst_syntax::{Span, Spanned};
use typst_utils::LazyHash; use typst_utils::LazyHash;
@ -272,9 +273,27 @@ impl Packed<ImageElem> {
), ),
ImageFormat::Vector(VectorFormat::Pdf) => { ImageFormat::Vector(VectorFormat::Pdf) => {
if engine.world.library().features.is_enabled(Feature::PdfEmbedding) { if engine.world.library().features.is_enabled(Feature::PdfEmbedding) {
let document = let document = match PdfDocument::new(loaded.data.clone()) {
PdfDocument::new(loaded.data.clone(), engine.world.clone()) Ok(doc) => doc,
.within(loaded)?; Err(e) => match e {
LoadPdfError::Encryption => {
bail!(
span,
"the PDF is encrypted or password-protected";
hint: "such PDFs are currently not supported";
hint: "preprocess the PDF to remove the encryption"
);
}
LoadPdfError::Invalid => {
bail!(
span,
"the PDF could not be loaded";
hint: "perhaps the PDF file is malformed"
);
}
},
};
let page_num = self.page.get(styles); let page_num = self.page.get(styles);
if page_num == 0 { if page_num == 0 {
@ -285,16 +304,18 @@ impl Packed<ImageElem> {
) )
}; };
// The user provides the page number start from 1, further down the pipeline, // The user provides the page number start from 1, but further down the pipeline,
// page numbers are 0-based. // page numbers are 0-based.
let page_idx = page_num - 1; let page_idx = page_num - 1;
let num_pages = document.len(); let num_pages = document.len();
let Some(pdf_image) = PdfImage::new(document, page_idx) else { let Some(pdf_image) = PdfImage::new(document, page_idx) else {
let pages = if num_pages == 1 { "page" } else { "pages" };
bail!( bail!(
span, span,
"page {page_num} doesn't exist"; "page {page_num} doesn't exist";
hint: "the document only has {num_pages} pages" hint: "the document only has {num_pages} {pages}"
); );
}; };

View File

@ -1,13 +1,10 @@
use crate::diag::LoadResult;
use crate::foundations::Bytes;
use crate::text::{FontStretch, FontStyle, FontVariant, FontWeight};
use crate::World;
use comemo::Tracked;
use hayro_syntax::page::Page;
use hayro_syntax::Pdf;
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use std::sync::Arc; use std::sync::Arc;
use typst_library::text::FontInfo;
use hayro_syntax::page::Page;
use hayro_syntax::{LoadPdfError, Pdf};
use crate::foundations::Bytes;
struct DocumentRepr { struct DocumentRepr {
pdf: Arc<Pdf>, pdf: Arc<Pdf>,
@ -29,24 +26,24 @@ impl PdfDocument {
/// Load a PDF document. /// Load a PDF document.
#[comemo::memoize] #[comemo::memoize]
#[typst_macros::time(name = "load pdf document")] #[typst_macros::time(name = "load pdf document")]
pub fn new(data: Bytes, world: Tracked<dyn World + '_>) -> LoadResult<PdfDocument> { pub fn new(data: Bytes) -> Result<PdfDocument, LoadPdfError> {
// TODO: Remove unwraps let pdf = Arc::new(Pdf::new(Arc::new(data.clone()))?);
let pdf = Arc::new(Pdf::new(Arc::new(data.clone())).unwrap()); let standard_fonts = get_standard_fonts();
let standard_fonts = get_standard_fonts(world.clone());
Ok(Self(Arc::new(DocumentRepr { data, pdf, standard_fonts }))) Ok(Self(Arc::new(DocumentRepr { data, pdf, standard_fonts })))
} }
/// Return the number of pages in the PDF.
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
self.0.pdf.pages().len() self.0.pdf.pages().len()
} }
} }
struct ImageRepr { struct ImageRepr {
pub document: PdfDocument, document: PdfDocument,
pub page_index: usize, page_index: usize,
pub width: f32, width: f32,
pub height: f32, height: f32,
} }
impl Hash for ImageRepr { impl Hash for ImageRepr {
@ -56,16 +53,16 @@ impl Hash for ImageRepr {
} }
} }
/// A page of a PDF file. /// A specific page of a PDF acting as an image.
#[derive(Clone, Hash)] #[derive(Clone, Hash)]
pub struct PdfImage(Arc<ImageRepr>); pub struct PdfImage(Arc<ImageRepr>);
impl PdfImage { impl PdfImage {
/// Create a new PDF image. Returns `None` if the page index is not valid. /// Create a new PDF image.
///
/// Returns `None` if the page index is not valid.
#[comemo::memoize] #[comemo::memoize]
pub fn new(document: PdfDocument, page: usize) -> Option<PdfImage> { pub fn new(document: PdfDocument, page: usize) -> Option<PdfImage> {
// TODO: Don't allow loading if pdf-embedding feature is disabled.
// TODO: Remove Unwrap
let dimensions = document.0.pdf.pages().get(page)?.render_dimensions(); let dimensions = document.0.pdf.pages().get(page)?.render_dimensions();
Some(Self(Arc::new(ImageRepr { Some(Self(Arc::new(ImageRepr {
@ -76,110 +73,93 @@ impl PdfImage {
}))) })))
} }
/// Returns the PDF page of the image.
pub fn page(&self) -> &Page { pub fn page(&self) -> &Page {
&self.0.document.0.pdf.pages()[self.0.page_index] &self.pdf().pages()[self.0.page_index]
} }
/// Returns the underlying PDF document.
pub fn pdf(&self) -> &Arc<Pdf> { pub fn pdf(&self) -> &Arc<Pdf> {
&self.0.document.0.pdf &self.0.document.0.pdf
} }
/// Returns the width of the image.
pub fn width(&self) -> f32 { pub fn width(&self) -> f32 {
self.0.width self.0.width
} }
/// Returns the embedded standard fonts of the image.
pub fn standard_fonts(&self) -> &Arc<StandardFonts> { pub fn standard_fonts(&self) -> &Arc<StandardFonts> {
&self.0.document.0.standard_fonts &self.0.document.0.standard_fonts
} }
/// Returns the height of the image.
pub fn height(&self) -> f32 { pub fn height(&self) -> f32 {
self.0.height self.0.height
} }
pub fn data(&self) -> &Bytes { /// Returns the page index of the image.
&self.0.document.0.data
}
pub fn page_index(&self) -> usize { pub fn page_index(&self) -> usize {
self.0.page_index self.0.page_index
} }
/// Returns the underlying Typst PDF document.
pub fn document(&self) -> &PdfDocument { pub fn document(&self) -> &PdfDocument {
&self.0.document &self.0.document
} }
} }
#[comemo::memoize] #[comemo::memoize]
fn get_standard_fonts(world: Tracked<dyn World + '_>) -> Arc<StandardFonts> { fn get_standard_fonts() -> Arc<StandardFonts> {
let book = world.book();
let get_font = |name: &str, fallback_name: Option<&str>, variant: FontVariant| {
book.select(name, variant)
.or_else(|| {
if let Some(fallback_name) = fallback_name {
book.select(fallback_name, variant)
} else {
None
}
})
.or_else(|| book.select_fallback(None, variant, "A"))
.and_then(|i| world.font(i))
.map(|font| (font.data().clone(), font.index()))
};
let normal_variant = FontVariant::new(
FontStyle::Normal,
FontWeight::default(),
FontStretch::default(),
);
let bold_variant =
FontVariant::new(FontStyle::Normal, FontWeight::BOLD, FontStretch::default());
let italic_variant = FontVariant::new(
FontStyle::Italic,
FontWeight::default(),
FontStretch::default(),
);
let bold_italic_variant =
FontVariant::new(FontStyle::Italic, FontWeight::BOLD, FontStretch::default());
let helvetica = VariantFont { let helvetica = VariantFont {
normal: get_font("helvetica", Some("liberation sans"), normal_variant), normal: Bytes::new(typst_assets::pdf::SANS),
bold: get_font("helvetica", Some("liberation sans"), bold_variant), bold: Bytes::new(typst_assets::pdf::SANS_BOLD),
italic: get_font("helvetica", Some("liberation sans"), italic_variant), italic: Bytes::new(typst_assets::pdf::SANS_ITALIC),
bold_italic: get_font("helvetica", Some("liberation sans"), bold_italic_variant), bold_italic: Bytes::new(typst_assets::pdf::SANS_BOLD_ITALIC),
}; };
let courier = VariantFont { let courier = VariantFont {
normal: get_font("courier", Some("liberation mono"), normal_variant), normal: Bytes::new(typst_assets::pdf::FIXED),
bold: get_font("courier", Some("liberation mono"), bold_variant), bold: Bytes::new(typst_assets::pdf::FIXED_BOLD),
italic: get_font("courier", Some("liberation mono"), italic_variant), italic: Bytes::new(typst_assets::pdf::FIXED_ITALIC),
bold_italic: get_font("courier", Some("liberation mono"), bold_italic_variant), bold_italic: Bytes::new(typst_assets::pdf::FIXED_BOLD_ITALIC),
}; };
let times = VariantFont { let times = VariantFont {
normal: get_font("times", Some("liberation serif"), normal_variant), normal: Bytes::new(typst_assets::pdf::SERIF),
bold: get_font("times", Some("liberation serif"), bold_variant), bold: Bytes::new(typst_assets::pdf::SERIF_BOLD),
italic: get_font("times", Some("liberation serif"), italic_variant), italic: Bytes::new(typst_assets::pdf::SERIF_ITALIC),
bold_italic: get_font("times", Some("liberation serif"), bold_italic_variant), bold_italic: Bytes::new(typst_assets::pdf::SERIF_BOLD_ITALIC),
}; };
let symbol = Some(Bytes::new(typst_assets::pdf::SYMBOL)); let symbol = Bytes::new(typst_assets::pdf::SYMBOL);
let zapf_dingbats = Some(Bytes::new(typst_assets::pdf::DING_BATS)); let zapf_dingbats = Bytes::new(typst_assets::pdf::DING_BATS);
Arc::new(StandardFonts { helvetica, courier, times, symbol, zapf_dingbats }) Arc::new(StandardFonts { helvetica, courier, times, symbol, zapf_dingbats })
} }
/// A PDF font with multiple variants.
pub struct VariantFont { pub struct VariantFont {
pub normal: Option<(Bytes, u32)>, /// The normal variant.
pub bold: Option<(Bytes, u32)>, pub normal: Bytes,
pub italic: Option<(Bytes, u32)>, /// The bold variant.
pub bold_italic: Option<(Bytes, u32)>, pub bold: Bytes,
/// The italic variant.
pub italic: Bytes,
/// The bold-italic variant.
pub bold_italic: Bytes,
} }
/// A structure holding the raw data of all PDF standard fonts.
pub struct StandardFonts { pub struct StandardFonts {
/// The data for the `Helvetica` font family.
pub helvetica: VariantFont, pub helvetica: VariantFont,
/// The data for the `Courier` font family.
pub courier: VariantFont, pub courier: VariantFont,
/// The data for the `Times` font family.
pub times: VariantFont, pub times: VariantFont,
pub symbol: Option<Bytes>, /// The data for the `Symbol` font family.
pub zapf_dingbats: Option<Bytes>, pub symbol: Bytes,
/// The data for the `Zapf Dingbats` font family.
pub zapf_dingbats: Bytes,
} }

View File

@ -124,15 +124,13 @@ fn build_pdf_texture(pdf: &PdfImage, w: u32, h: u32) -> Option<sk::Pixmap> {
StandardFont::TimesBold => sf.times.bold.clone(), StandardFont::TimesBold => sf.times.bold.clone(),
StandardFont::TimesItalic => sf.times.italic.clone(), StandardFont::TimesItalic => sf.times.italic.clone(),
StandardFont::TimesBoldItalic => sf.times.bold_italic.clone(), StandardFont::TimesBoldItalic => sf.times.bold_italic.clone(),
StandardFont::ZapfDingBats => sf.zapf_dingbats.clone().map(|d| (d, 0)), StandardFont::ZapfDingBats => sf.zapf_dingbats.clone(),
StandardFont::Symbol => sf.symbol.clone().map(|d| (d, 0)), StandardFont::Symbol => sf.symbol.clone(),
}; };
bytes.map(|d| { let font_data: Arc<dyn AsRef<[u8]> + Send + Sync> = Arc::new(bytes.clone());
let font_data: Arc<dyn AsRef<[u8]> + Send + Sync> = Arc::new(d.0.clone());
(font_data, d.1) Some((font_data, 0))
})
}; };
let interpreter_settings = InterpreterSettings { let interpreter_settings = InterpreterSettings {

View File

@ -116,15 +116,13 @@ fn pdf_to_png(pdf: &PdfImage, w: u32, h: u32) -> Vec<u8> {
StandardFont::TimesBold => sf.times.bold.clone(), StandardFont::TimesBold => sf.times.bold.clone(),
StandardFont::TimesItalic => sf.times.italic.clone(), StandardFont::TimesItalic => sf.times.italic.clone(),
StandardFont::TimesBoldItalic => sf.times.bold_italic.clone(), StandardFont::TimesBoldItalic => sf.times.bold_italic.clone(),
StandardFont::ZapfDingBats => sf.zapf_dingbats.clone().map(|d| (d, 0)), StandardFont::ZapfDingBats => sf.zapf_dingbats.clone(),
StandardFont::Symbol => sf.symbol.clone().map(|d| (d, 0)), StandardFont::Symbol => sf.symbol.clone(),
}; };
bytes.map(|d| { let font_data: Arc<dyn AsRef<[u8]> + Send + Sync> = Arc::new(bytes.clone());
let font_data: Arc<dyn AsRef<[u8]> + Send + Sync> = Arc::new(d.0.clone());
(font_data, d.1) Some((font_data, 0))
})
}; };
let interpreter_settings = InterpreterSettings { let interpreter_settings = InterpreterSettings {

View File

@ -242,7 +242,7 @@ fn category_page(resolver: &dyn Resolver, category: Category) -> PageModel {
items.push(CategoryItem { items.push(CategoryItem {
name: group.name.clone(), name: group.name.clone(),
route: subpage.route.clone(), route: subpage.route.clone(),
oneliner: oneliner(docs).into(), oneliner: oneliner(docs),
code: true, code: true,
}); });
children.push(subpage); children.push(subpage);
@ -296,7 +296,7 @@ fn category_page(resolver: &dyn Resolver, category: Category) -> PageModel {
items.push(CategoryItem { items.push(CategoryItem {
name: name.into(), name: name.into(),
route: subpage.route.clone(), route: subpage.route.clone(),
oneliner: oneliner(func.docs().unwrap_or_default()).into(), oneliner: oneliner(func.docs().unwrap_or_default()),
code: true, code: true,
}); });
children.push(subpage); children.push(subpage);
@ -306,7 +306,7 @@ fn category_page(resolver: &dyn Resolver, category: Category) -> PageModel {
items.push(CategoryItem { items.push(CategoryItem {
name: ty.short_name().into(), name: ty.short_name().into(),
route: subpage.route.clone(), route: subpage.route.clone(),
oneliner: oneliner(ty.docs()).into(), oneliner: oneliner(ty.docs()),
code: true, code: true,
}); });
children.push(subpage); children.push(subpage);
@ -637,7 +637,7 @@ fn group_page(
let item = CategoryItem { let item = CategoryItem {
name: group.name.clone(), name: group.name.clone(),
route: model.route.clone(), route: model.route.clone(),
oneliner: oneliner(&group.details).into(), oneliner: oneliner(&group.details),
code: false, code: false,
}; };
@ -772,8 +772,24 @@ pub fn urlify(title: &str) -> EcoString {
} }
/// Extract the first line of documentation. /// Extract the first line of documentation.
fn oneliner(docs: &str) -> &str { fn oneliner(docs: &str) -> EcoString {
docs.lines().next().unwrap_or_default() let paragraph = docs.split("\n\n").next().unwrap_or_default();
let mut depth = 0;
let mut period = false;
let mut end = paragraph.len();
for (i, c) in paragraph.char_indices() {
match c {
'(' | '[' | '{' => depth += 1,
')' | ']' | '}' => depth -= 1,
'.' if depth == 0 => period = true,
c if period && c.is_whitespace() && !docs[..i].ends_with("e.g.") => {
end = i;
break;
}
_ => period = false,
}
}
EcoString::from(&docs[..end]).replace("\r\n", " ").replace("\n", " ")
} }
/// The order of types in the documentation. /// The order of types in the documentation.

View File

@ -86,7 +86,7 @@ pub struct FuncModel {
pub name: EcoString, pub name: EcoString,
pub title: &'static str, pub title: &'static str,
pub keywords: &'static [&'static str], pub keywords: &'static [&'static str],
pub oneliner: &'static str, pub oneliner: EcoString,
pub element: bool, pub element: bool,
pub contextual: bool, pub contextual: bool,
pub deprecation: Option<&'static str>, pub deprecation: Option<&'static str>,
@ -139,7 +139,7 @@ pub struct TypeModel {
pub name: &'static str, pub name: &'static str,
pub title: &'static str, pub title: &'static str,
pub keywords: &'static [&'static str], pub keywords: &'static [&'static str],
pub oneliner: &'static str, pub oneliner: EcoString,
pub details: Html, pub details: Html,
pub constructor: Option<FuncModel>, pub constructor: Option<FuncModel>,
pub scope: Vec<FuncModel>, pub scope: Vec<FuncModel>,

View File

@ -127,6 +127,10 @@
checks = self'.checks; checks = self'.checks;
inputsFrom = [ typst ]; inputsFrom = [ typst ];
buildInputs = with pkgs; [
rust-analyzer
];
packages = [ packages = [
# A script for quickly running tests. # A script for quickly running tests.
# See https://github.com/typst/typst/blob/main/tests/README.md#making-an-alias # See https://github.com/typst/typst/blob/main/tests/README.md#making-an-alias

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

BIN
tests/ref/image-pdf.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@ -198,7 +198,7 @@ fn library() -> Library {
// exactly 100pt wide. Page height is unbounded and font size is 10pt so // exactly 100pt wide. Page height is unbounded and font size is 10pt so
// that it multiplies to nice round numbers. // that it multiplies to nice round numbers.
let mut lib = Library::builder() let mut lib = Library::builder()
.with_features([Feature::Html].into_iter().collect()) .with_features([Feature::Html, Feature::PdfEmbedding].into_iter().collect())
.build(); .build();
// Hook up helpers into the global scope. // Hook up helpers into the global scope.

View File

@ -258,7 +258,7 @@ A #box(image("/assets/images/tiger.jpg", height: 1cm, width: 80%)) B
--- image-png-but-pixmap-format --- --- image-png-but-pixmap-format ---
#image( #image(
read("/assets/images/tiger.jpg", encoding: none), read("/assets/images/tiger.jpg", encoding: none),
// Error: 11-18 expected "png", "jpg", "gif", "webp", dictionary, "svg", or auto // Error: 11-18 expected "png", "jpg", "gif", "webp", dictionary, "svg", "pdf", or auto
format: "rgba8", format: "rgba8",
) )
@ -289,3 +289,16 @@ A #box(image("/assets/images/tiger.jpg", height: 1cm, width: 80%)) B
..rotations.map(v => raw(str(v), lang: "typc")), ..rotations.map(v => raw(str(v), lang: "typc")),
..rotations.map(rotated) ..rotations.map(rotated)
) )
--- image-pdf ---
#image("/assets/images/matplotlib.pdf")
--- image-pdf-invalid-page ---
// Error: 2-49 page 2 doesn't exist
// Hint: 2-49 the document only has 1 page
#image("/assets/images/matplotlib.pdf", page: 2)
--- image-pdf-multiple-pages ---
#image("/assets/images/diagrams.pdf", page: 1)
#image("/assets/images/diagrams.pdf", page: 3)
#image("/assets/images/diagrams.pdf", page: 2)