mirror of
https://github.com/typst/typst
synced 2025-07-28 14:57:54 +08:00
Compare commits
6 Commits
b0416a4cc8
...
bf045ce8d1
Author | SHA1 | Date | |
---|---|---|---|
|
bf045ce8d1 | ||
|
a46f1c7250 | ||
|
41d4f9d403 | ||
|
7ea0ed59ac | ||
|
9fa41da5e8 | ||
|
7399513bfc |
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
@ -40,7 +40,7 @@ jobs:
|
|||||||
sudo dpkg --add-architecture i386
|
sudo dpkg --add-architecture i386
|
||||||
sudo apt update
|
sudo apt update
|
||||||
sudo apt install -y gcc-multilib libssl-dev:i386 pkg-config:i386
|
sudo apt install -y gcc-multilib libssl-dev:i386 pkg-config:i386
|
||||||
- uses: dtolnay/rust-toolchain@1.88.0
|
- uses: dtolnay/rust-toolchain@1.87.0
|
||||||
with:
|
with:
|
||||||
targets: ${{ matrix.bits == 32 && 'i686-unknown-linux-gnu' || '' }}
|
targets: ${{ matrix.bits == 32 && 'i686-unknown-linux-gnu' || '' }}
|
||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
@ -73,7 +73,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: dtolnay/rust-toolchain@1.88.0
|
- uses: dtolnay/rust-toolchain@1.87.0
|
||||||
with:
|
with:
|
||||||
components: clippy, rustfmt
|
components: clippy, rustfmt
|
||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
62
Cargo.lock
generated
62
Cargo.lock
generated
@ -415,6 +415,15 @@ name = "codex"
|
|||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
source = "git+https://github.com/typst/codex?rev=9ac86f9#9ac86f96af5b89fce555e6bba8b6d1ac7b44ef00"
|
source = "git+https://github.com/typst/codex?rev=9ac86f9#9ac86f96af5b89fce555e6bba8b6d1ac7b44ef00"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "color"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7ae467d04a8a8aea5d9a49018a6ade2e4221d92968e8ce55a48c0b1164e5f698"
|
||||||
|
dependencies = [
|
||||||
|
"bytemuck",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "color-print"
|
name = "color-print"
|
||||||
version = "0.3.7"
|
version = "0.3.7"
|
||||||
@ -964,23 +973,9 @@ dependencies = [
|
|||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hayro"
|
|
||||||
version = "0.1.0"
|
|
||||||
source = "git+https://github.com/LaurenzV/hayro?rev=d385360#d38536089c95b521b0b64080302d7a305344edbf"
|
|
||||||
dependencies = [
|
|
||||||
"bytemuck",
|
|
||||||
"hayro-interpret",
|
|
||||||
"image",
|
|
||||||
"kurbo",
|
|
||||||
"rustc-hash",
|
|
||||||
"smallvec",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hayro-font"
|
name = "hayro-font"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/LaurenzV/hayro?rev=d385360#d38536089c95b521b0b64080302d7a305344edbf"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"phf",
|
"phf",
|
||||||
@ -989,13 +984,13 @@ 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=d385360#d38536089c95b521b0b64080302d7a305344edbf"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.1",
|
"bitflags 2.9.1",
|
||||||
"hayro-font",
|
"hayro-font",
|
||||||
"hayro-syntax",
|
"hayro-syntax",
|
||||||
"kurbo",
|
"kurbo",
|
||||||
"log",
|
"log",
|
||||||
|
"peniko",
|
||||||
"phf",
|
"phf",
|
||||||
"qcms",
|
"qcms",
|
||||||
"skrifa",
|
"skrifa",
|
||||||
@ -1003,10 +998,23 @@ dependencies = [
|
|||||||
"yoke 0.8.0",
|
"yoke 0.8.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hayro-render"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"bytemuck",
|
||||||
|
"hayro-interpret",
|
||||||
|
"hayro-syntax",
|
||||||
|
"image",
|
||||||
|
"kurbo",
|
||||||
|
"peniko",
|
||||||
|
"rustc-hash",
|
||||||
|
"smallvec",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hayro-syntax"
|
name = "hayro-syntax"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
source = "git+https://github.com/LaurenzV/hayro?rev=d385360#d38536089c95b521b0b64080302d7a305344edbf"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"flate2",
|
"flate2",
|
||||||
"kurbo",
|
"kurbo",
|
||||||
@ -1019,7 +1027,6 @@ 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=d385360#d38536089c95b521b0b64080302d7a305344edbf"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"flate2",
|
"flate2",
|
||||||
"hayro-syntax",
|
"hayro-syntax",
|
||||||
@ -1269,9 +1276,9 @@ checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "image"
|
name = "image"
|
||||||
version = "0.25.6"
|
version = "0.25.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "db35664ce6b9810857a38a906215e75a9c879f0696556a39f59c62829710251a"
|
checksum = "cd6f44aed642f18953a158afeb30206f4d50da59fbc66ecb53c66488de73563b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"byteorder-lite",
|
"byteorder-lite",
|
||||||
@ -1430,7 +1437,6 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "krilla"
|
name = "krilla"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
source = "git+https://github.com/LaurenzV/krilla?rev=f7d753c3#f7d753c39e1fb7118fb1b774243a0720771f229f"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
@ -1460,7 +1466,6 @@ 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=f7d753c3#f7d753c39e1fb7118fb1b774243a0720771f229f"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"flate2",
|
"flate2",
|
||||||
"fontdb",
|
"fontdb",
|
||||||
@ -1918,6 +1923,18 @@ dependencies = [
|
|||||||
"ryu",
|
"ryu",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "peniko"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1f9529efd019889b2a205193c14ffb6e2839b54ed9d2720674f10f4b04d87ac9"
|
||||||
|
dependencies = [
|
||||||
|
"bytemuck",
|
||||||
|
"color",
|
||||||
|
"kurbo",
|
||||||
|
"smallvec",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "percent-encoding"
|
name = "percent-encoding"
|
||||||
version = "2.3.1"
|
version = "2.3.1"
|
||||||
@ -3239,7 +3256,7 @@ version = "0.13.1"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"comemo",
|
"comemo",
|
||||||
"hayro",
|
"hayro-render",
|
||||||
"image",
|
"image",
|
||||||
"pixglyph",
|
"pixglyph",
|
||||||
"resvg",
|
"resvg",
|
||||||
@ -3258,7 +3275,6 @@ dependencies = [
|
|||||||
"comemo",
|
"comemo",
|
||||||
"ecow",
|
"ecow",
|
||||||
"flate2",
|
"flate2",
|
||||||
"hayro",
|
|
||||||
"image",
|
"image",
|
||||||
"ttf-parser",
|
"ttf-parser",
|
||||||
"typst-library",
|
"typst-library",
|
||||||
|
@ -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 = "d385360" }
|
hayro-syntax = { path = "../hayro/hayro-syntax" }
|
||||||
hayro = { git = "https://github.com/LaurenzV/hayro", rev = "d385360" }
|
hayro-render = { path = "../hayro/hayro-render" }
|
||||||
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 = "f7d753c3", default-features = false, features = ["raster-images", "comemo", "rayon", "pdf"] }
|
krilla = { path = "../krilla/crates/krilla", default-features = false, features = ["raster-images", "comemo", "rayon", "pdf"] }
|
||||||
krilla-svg = { git = "https://github.com/LaurenzV/krilla", rev = "f7d753c3" }
|
krilla-svg = { path = "../krilla/crates/krilla-svg" }
|
||||||
kurbo = "0.11"
|
kurbo = "0.11"
|
||||||
libfuzzer-sys = "0.4"
|
libfuzzer-sys = "0.4"
|
||||||
lipsum = "0.9"
|
lipsum = "0.9"
|
||||||
|
@ -4,7 +4,6 @@ mod pdf;
|
|||||||
mod raster;
|
mod raster;
|
||||||
mod svg;
|
mod svg;
|
||||||
|
|
||||||
pub use self::pdf::PdfImage;
|
|
||||||
pub use self::raster::{
|
pub use self::raster::{
|
||||||
ExchangeFormat, PixelEncoding, PixelFormat, RasterFormat, RasterImage,
|
ExchangeFormat, PixelEncoding, PixelFormat, RasterFormat, RasterImage,
|
||||||
};
|
};
|
||||||
@ -15,11 +14,10 @@ use std::fmt::{self, Debug, Formatter};
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use ecow::EcoString;
|
use ecow::EcoString;
|
||||||
use typst_library::{Feature, World};
|
|
||||||
use typst_syntax::{Span, Spanned};
|
use typst_syntax::{Span, Spanned};
|
||||||
use typst_utils::LazyHash;
|
use typst_utils::LazyHash;
|
||||||
|
|
||||||
use crate::diag::{bail, warning, At, LoadedWithin, SourceResult, StrResult};
|
use crate::diag::{warning, At, LoadedWithin, SourceResult, StrResult};
|
||||||
use crate::engine::Engine;
|
use crate::engine::Engine;
|
||||||
use crate::foundations::{
|
use crate::foundations::{
|
||||||
cast, elem, func, scope, Bytes, Cast, Content, Derived, NativeElement, Packed, Smart,
|
cast, elem, func, scope, Bytes, Cast, Content, Derived, NativeElement, Packed, Smart,
|
||||||
@ -29,7 +27,7 @@ use crate::layout::{Length, Rel, Sizing};
|
|||||||
use crate::loading::{DataSource, Load, LoadSource, Loaded, Readable};
|
use crate::loading::{DataSource, Load, LoadSource, Loaded, Readable};
|
||||||
use crate::model::Figurable;
|
use crate::model::Figurable;
|
||||||
use crate::text::{families, LocalName};
|
use crate::text::{families, LocalName};
|
||||||
use crate::visualize::image::pdf::PdfDocument;
|
use crate::visualize::image::pdf::{PdfDocument, PdfImage};
|
||||||
|
|
||||||
/// A raster or vector graphic.
|
/// A raster or vector graphic.
|
||||||
///
|
///
|
||||||
@ -271,42 +269,13 @@ impl Packed<ImageElem> {
|
|||||||
.within(loaded)?,
|
.within(loaded)?,
|
||||||
),
|
),
|
||||||
ImageFormat::Vector(VectorFormat::Pdf) => {
|
ImageFormat::Vector(VectorFormat::Pdf) => {
|
||||||
if engine.world.library().features.is_enabled(Feature::PdfEmbedding) {
|
let document = PdfDocument::new(loaded.data.clone()).within(loaded)?;
|
||||||
let document =
|
// The user provides the page number staring from 1, further down the pipeline they page
|
||||||
PdfDocument::new(loaded.data.clone(), engine.world.clone())
|
// numbers are 0-based.
|
||||||
.within(loaded)?;
|
let pdf_image =
|
||||||
let page_num = self.page.get(styles);
|
PdfImage::new(document, self.page.get(styles) - 1).within(loaded)?;
|
||||||
|
|
||||||
if page_num == 0 {
|
ImageKind::Pdf(pdf_image)
|
||||||
bail!(
|
|
||||||
span,
|
|
||||||
"{page_num} is not a valid page number";
|
|
||||||
hint: "page numbers for PDF start at 1"
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
// The user provides the page number start from 1, further down the pipeline,
|
|
||||||
// page numbers are 0-based.
|
|
||||||
let page_idx = page_num - 1;
|
|
||||||
let num_pages = document.len();
|
|
||||||
|
|
||||||
let Some(pdf_image) = PdfImage::new(document, page_idx) else {
|
|
||||||
bail!(
|
|
||||||
span,
|
|
||||||
"page {page_num} doesn't exist";
|
|
||||||
hint: "the document only has {num_pages} pages"
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
ImageKind::Pdf(pdf_image)
|
|
||||||
} else {
|
|
||||||
bail!(
|
|
||||||
span,
|
|
||||||
"embedding PDFs is currently an experimental, opt-in feature";
|
|
||||||
hint: "enable the corresponding feature to try it out";
|
|
||||||
hint: "convert your PDF to SVG instead"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,17 +1,15 @@
|
|||||||
use crate::diag::LoadResult;
|
use crate::diag::LoadResult;
|
||||||
use crate::foundations::Bytes;
|
use crate::foundations::Bytes;
|
||||||
use crate::text::{FontStretch, FontStyle, FontVariant, FontWeight};
|
use hayro_syntax::pdf::Pdf;
|
||||||
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 hayro_syntax::document::page::Page;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
struct DocumentRepr {
|
struct DocumentRepr {
|
||||||
pdf: Arc<Pdf>,
|
pdf: Arc<Pdf>,
|
||||||
data: Bytes,
|
data: Bytes,
|
||||||
standard_fonts: Arc<StandardFonts>,
|
page_sizes: Vec<(f32, f32)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Hash for DocumentRepr {
|
impl Hash for DocumentRepr {
|
||||||
@ -28,16 +26,14 @@ 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) -> LoadResult<PdfDocument> {
|
||||||
// TODO: Remove unwraps
|
// TODO: Remove unwraps
|
||||||
let pdf = Arc::new(Pdf::new(Arc::new(data.clone())).unwrap());
|
let pdf = Arc::new(Pdf::new(Arc::new(data.clone())).unwrap());
|
||||||
let standard_fonts = get_standard_fonts(world.clone());
|
let pages = pdf.pages().unwrap();
|
||||||
|
|
||||||
Ok(Self(Arc::new(DocumentRepr { data, pdf, standard_fonts })))
|
let page_sizes = pages.get().iter().map(|p| p.render_dimensions()).collect();
|
||||||
}
|
|
||||||
|
|
||||||
pub fn len(&self) -> usize {
|
Ok(Self(Arc::new(DocumentRepr { data, pdf, page_sizes })))
|
||||||
self.0.pdf.pages().len()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,14 +56,13 @@ impl Hash for ImageRepr {
|
|||||||
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.
|
|
||||||
#[comemo::memoize]
|
#[comemo::memoize]
|
||||||
pub fn new(document: PdfDocument, page: usize) -> Option<PdfImage> {
|
pub fn new(document: PdfDocument, page: usize) -> LoadResult<PdfImage> {
|
||||||
// TODO: Don't allow loading if pdf-embedding feature is disabled.
|
// TODO: Don't allow loading if pdf-embedding feature is disabled.
|
||||||
// TODO: Remove Unwrap
|
// TODO: Remove Unwrap
|
||||||
let dimensions = document.0.pdf.pages().get(page)?.render_dimensions();
|
let dimensions = *(&document.0).page_sizes.get(page).unwrap();
|
||||||
|
|
||||||
Some(Self(Arc::new(ImageRepr {
|
Ok(Self(Arc::new(ImageRepr {
|
||||||
document,
|
document,
|
||||||
page_index: page,
|
page_index: page,
|
||||||
width: dimensions.0,
|
width: dimensions.0,
|
||||||
@ -75,22 +70,18 @@ impl PdfImage {
|
|||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn page(&self) -> &Page {
|
pub fn with_page<F, R>(&self, f: F) -> R
|
||||||
&self.0.document.0.pdf.pages()[self.0.page_index]
|
where
|
||||||
}
|
F: FnOnce(&Page) -> R,
|
||||||
|
{
|
||||||
pub fn pdf(&self) -> &Arc<Pdf> {
|
let pages = self.0.document.0.pdf.pages().unwrap();
|
||||||
&self.0.document.0.pdf
|
f(&pages.get().get(self.0.page_index).unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn width(&self) -> f32 {
|
pub fn width(&self) -> f32 {
|
||||||
self.0.width
|
self.0.width
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn standard_fonts(&self) -> &Arc<StandardFonts> {
|
|
||||||
&self.0.document.0.standard_fonts
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn height(&self) -> f32 {
|
pub fn height(&self) -> f32 {
|
||||||
self.0.height
|
self.0.height
|
||||||
}
|
}
|
||||||
@ -102,83 +93,4 @@ impl PdfImage {
|
|||||||
pub fn page_index(&self) -> usize {
|
pub fn page_index(&self) -> usize {
|
||||||
self.0.page_index
|
self.0.page_index
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn document(&self) -> &PdfDocument {
|
|
||||||
&self.0.document
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[comemo::memoize]
|
|
||||||
fn get_standard_fonts(world: Tracked<dyn World + '_>) -> 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
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.and_then(|i| world.font(i))
|
|
||||||
.map(|font| font.data().clone())
|
|
||||||
};
|
|
||||||
|
|
||||||
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 {
|
|
||||||
normal: get_font("helvetica", Some("liberation sans"), normal_variant),
|
|
||||||
bold: get_font("helvetica", Some("liberation sans"), bold_variant),
|
|
||||||
italic: get_font("helvetica", Some("liberation sans"), italic_variant),
|
|
||||||
bold_italic: get_font("helvetica", Some("liberation sans"), bold_italic_variant),
|
|
||||||
};
|
|
||||||
|
|
||||||
let courier = VariantFont {
|
|
||||||
normal: get_font("courier", Some("liberation mono"), normal_variant),
|
|
||||||
bold: get_font("courier", Some("liberation mono"), bold_variant),
|
|
||||||
italic: get_font("courier", Some("liberation mono"), italic_variant),
|
|
||||||
bold_italic: get_font("courier", Some("liberation mono"), bold_italic_variant),
|
|
||||||
};
|
|
||||||
|
|
||||||
let times = VariantFont {
|
|
||||||
normal: get_font("times", Some("liberation serif"), normal_variant),
|
|
||||||
bold: get_font("times", Some("liberation serif"), bold_variant),
|
|
||||||
italic: get_font("times", Some("liberation serif"), italic_variant),
|
|
||||||
bold_italic: get_font("times", Some("liberation serif"), bold_italic_variant),
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: Use Foxit fonts as fallback
|
|
||||||
let symbol = get_font("symbol", None, normal_variant);
|
|
||||||
let zapf_dingbats = get_font("zapf dingbats", None, normal_variant);
|
|
||||||
|
|
||||||
Arc::new(StandardFonts { helvetica, courier, times, symbol, zapf_dingbats })
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct VariantFont {
|
|
||||||
pub normal: Option<Bytes>,
|
|
||||||
pub bold: Option<Bytes>,
|
|
||||||
pub italic: Option<Bytes>,
|
|
||||||
pub bold_italic: Option<Bytes>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct StandardFonts {
|
|
||||||
pub helvetica: VariantFont,
|
|
||||||
pub courier: VariantFont,
|
|
||||||
pub times: VariantFont,
|
|
||||||
pub symbol: Option<Bytes>,
|
|
||||||
pub zapf_dingbats: Option<Bytes>,
|
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,6 @@ use krilla::embed::EmbedError;
|
|||||||
use krilla::error::KrillaError;
|
use krilla::error::KrillaError;
|
||||||
use krilla::geom::PathBuilder;
|
use krilla::geom::PathBuilder;
|
||||||
use krilla::page::{PageLabel, PageSettings};
|
use krilla::page::{PageLabel, PageSettings};
|
||||||
use krilla::pdf::PdfError;
|
|
||||||
use krilla::surface::Surface;
|
use krilla::surface::Surface;
|
||||||
use krilla::{Document, SerializeSettings};
|
use krilla::{Document, SerializeSettings};
|
||||||
use krilla_svg::render_svg_glyph;
|
use krilla_svg::render_svg_glyph;
|
||||||
@ -365,37 +364,9 @@ fn finish(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
KrillaError::Pdf(_, e, loc) => {
|
KrillaError::Pdf(_, e, loc) => {
|
||||||
|
// TODO: Better errors
|
||||||
let span = to_span(loc);
|
let span = to_span(loc);
|
||||||
match e {
|
bail!(span, "failed to process PDF");
|
||||||
// We already validated in `typst-library` that the page index is valid.
|
|
||||||
PdfError::InvalidPage(_) => unreachable!(),
|
|
||||||
PdfError::VersionMismatch(v) => {
|
|
||||||
let pdf_ver = v.as_str();
|
|
||||||
let config_ver = configuration.version();
|
|
||||||
let cur_ver = config_ver.as_str();
|
|
||||||
|
|
||||||
bail!(span,
|
|
||||||
"the version of the PDF file is too high";
|
|
||||||
hint: "the current export target is {cur_ver}, while the PDF has version {pdf_ver}";
|
|
||||||
hint: "raise the export target to {pdf_ver} or higher";
|
|
||||||
hint: "preprocess the PDF to convert it to a lower version"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KrillaError::DuplicateTagId(_, loc) => {
|
|
||||||
let span = to_span(loc);
|
|
||||||
bail!(span,
|
|
||||||
"duplicate tag id";
|
|
||||||
hint: "this is a bug in typst, please report it"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
KrillaError::UnknownTagId(_, loc) => {
|
|
||||||
let span = to_span(loc);
|
|
||||||
bail!(span,
|
|
||||||
"unknown tag id";
|
|
||||||
hint: "this is a bug in typst, please report it"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -569,12 +540,12 @@ fn convert_error(
|
|||||||
}
|
}
|
||||||
// The below errors cannot occur yet, only once Typst supports full PDF/A
|
// The below errors cannot occur yet, only once Typst supports full PDF/A
|
||||||
// and PDF/UA. But let's still add a message just to be on the safe side.
|
// and PDF/UA. But let's still add a message just to be on the safe side.
|
||||||
ValidationError::MissingAnnotationAltText(_) => error!(
|
ValidationError::MissingAnnotationAltText => error!(
|
||||||
Span::detached(),
|
Span::detached(),
|
||||||
"{prefix} missing annotation alt text";
|
"{prefix} missing annotation alt text";
|
||||||
hint: "please report this as a bug"
|
hint: "please report this as a bug"
|
||||||
),
|
),
|
||||||
ValidationError::MissingAltText(_) => error!(
|
ValidationError::MissingAltText => error!(
|
||||||
Span::detached(),
|
Span::detached(),
|
||||||
"{prefix} missing alt text";
|
"{prefix} missing alt text";
|
||||||
hint: "make sure your images and equations have alt text"
|
hint: "make sure your images and equations have alt text"
|
||||||
@ -610,13 +581,17 @@ fn convert_error(
|
|||||||
"{prefix} missing document date";
|
"{prefix} missing document date";
|
||||||
hint: "set the date of the document"
|
hint: "set the date of the document"
|
||||||
),
|
),
|
||||||
ValidationError::EmbeddedPDF(loc) => {
|
ValidationError::DuplicateTagId(_, loc) => error!(
|
||||||
error!(
|
to_span(*loc),
|
||||||
to_span(*loc),
|
"{prefix} duplicate tag id";
|
||||||
"embedding PDFs is currently not supported in this export mode";
|
hint: "please report this as a bug"
|
||||||
hint: "try converting the PDF to SVG before embedding it"
|
),
|
||||||
)
|
ValidationError::UnknownTagId(_, loc) => error!(
|
||||||
}
|
to_span(*loc),
|
||||||
|
"{prefix} unknown tag id";
|
||||||
|
hint: "please report this as a bug"
|
||||||
|
),
|
||||||
|
ValidationError::EmbeddedPDF(loc) => error!(to_span(*loc), "TODO"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ use typst_library::diag::{bail, SourceResult};
|
|||||||
use typst_library::foundations::Smart;
|
use typst_library::foundations::Smart;
|
||||||
use typst_library::layout::{Abs, Angle, Ratio, Size, Transform};
|
use typst_library::layout::{Abs, Angle, Ratio, Size, Transform};
|
||||||
use typst_library::visualize::{
|
use typst_library::visualize::{
|
||||||
ExchangeFormat, Image, ImageKind, ImageScaling, PdfImage, RasterFormat, RasterImage,
|
ExchangeFormat, Image, ImageKind, ImageScaling, RasterFormat, RasterImage,
|
||||||
};
|
};
|
||||||
use typst_syntax::Span;
|
use typst_syntax::Span;
|
||||||
|
|
||||||
@ -62,7 +62,13 @@ pub(crate) fn handle_image(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
ImageKind::Pdf(pdf) => {
|
ImageKind::Pdf(pdf) => {
|
||||||
surface.draw_pdf_page(&convert_pdf(pdf), size.to_krilla(), pdf.page_index())
|
let pdf_data: Arc<dyn AsRef<[u8]> + Send + Sync> =
|
||||||
|
Arc::new(pdf.data().clone());
|
||||||
|
surface.draw_pdf_page(
|
||||||
|
&PdfDocument::new(pdf_data.into()).unwrap(),
|
||||||
|
size.to_krilla(),
|
||||||
|
pdf.page_index(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,9 +95,9 @@ struct Repr {
|
|||||||
|
|
||||||
/// A wrapper around `RasterImage` so that we can implement `CustomImage`.
|
/// A wrapper around `RasterImage` so that we can implement `CustomImage`.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct PdfRasterImage(Arc<Repr>);
|
struct PdfImage(Arc<Repr>);
|
||||||
|
|
||||||
impl PdfRasterImage {
|
impl PdfImage {
|
||||||
pub fn new(raster: RasterImage) -> Self {
|
pub fn new(raster: RasterImage) -> Self {
|
||||||
Self(Arc::new(Repr {
|
Self(Arc::new(Repr {
|
||||||
raster,
|
raster,
|
||||||
@ -101,7 +107,7 @@ impl PdfRasterImage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Hash for PdfRasterImage {
|
impl Hash for PdfImage {
|
||||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
// `alpha_channel` and `actual_dynamic` are generated from the underlying `RasterImage`,
|
// `alpha_channel` and `actual_dynamic` are generated from the underlying `RasterImage`,
|
||||||
// so this is enough. Since `raster` is prehashed, this is also very cheap.
|
// so this is enough. Since `raster` is prehashed, this is also very cheap.
|
||||||
@ -109,7 +115,7 @@ impl Hash for PdfRasterImage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CustomImage for PdfRasterImage {
|
impl CustomImage for PdfImage {
|
||||||
fn color_channel(&self) -> &[u8] {
|
fn color_channel(&self) -> &[u8] {
|
||||||
self.0
|
self.0
|
||||||
.actual_dynamic
|
.actual_dynamic
|
||||||
@ -200,15 +206,10 @@ fn convert_raster(
|
|||||||
interpolate,
|
interpolate,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
krilla::image::Image::from_custom(PdfRasterImage::new(raster), interpolate)
|
krilla::image::Image::from_custom(PdfImage::new(raster), interpolate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[comemo::memoize]
|
|
||||||
fn convert_pdf(pdf: &PdfImage) -> PdfDocument {
|
|
||||||
PdfDocument::new(pdf.pdf().clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn exif_transform(image: &RasterImage, size: Size) -> (Transform, Size) {
|
fn exif_transform(image: &RasterImage, size: Size) -> (Transform, Size) {
|
||||||
let base = |hp: bool, vp: bool, mut base_ts: Transform, size: Size| {
|
let base = |hp: bool, vp: bool, mut base_ts: Transform, size: Size| {
|
||||||
if hp {
|
if hp {
|
||||||
|
@ -49,6 +49,7 @@ pub(crate) fn handle_link(
|
|||||||
fc.push_annotation(
|
fc.push_annotation(
|
||||||
LinkAnnotation::new(
|
LinkAnnotation::new(
|
||||||
rect,
|
rect,
|
||||||
|
None,
|
||||||
Target::Action(Action::Link(LinkAction::new(u.to_string()))),
|
Target::Action(Action::Link(LinkAction::new(u.to_string()))),
|
||||||
)
|
)
|
||||||
.into(),
|
.into(),
|
||||||
@ -63,6 +64,7 @@ pub(crate) fn handle_link(
|
|||||||
fc.push_annotation(
|
fc.push_annotation(
|
||||||
LinkAnnotation::new(
|
LinkAnnotation::new(
|
||||||
rect,
|
rect,
|
||||||
|
None,
|
||||||
Target::Destination(krilla::destination::Destination::Named(
|
Target::Destination(krilla::destination::Destination::Named(
|
||||||
nd.clone(),
|
nd.clone(),
|
||||||
)),
|
)),
|
||||||
@ -81,6 +83,7 @@ pub(crate) fn handle_link(
|
|||||||
fc.push_annotation(
|
fc.push_annotation(
|
||||||
LinkAnnotation::new(
|
LinkAnnotation::new(
|
||||||
rect,
|
rect,
|
||||||
|
None,
|
||||||
Target::Destination(krilla::destination::Destination::Xyz(
|
Target::Destination(krilla::destination::Destination::Xyz(
|
||||||
XyzDestination::new(index, pos.point.to_krilla()),
|
XyzDestination::new(index, pos.point.to_krilla()),
|
||||||
)),
|
)),
|
||||||
|
@ -18,7 +18,7 @@ typst-macros = { workspace = true }
|
|||||||
typst-timing = { workspace = true }
|
typst-timing = { workspace = true }
|
||||||
bytemuck = { workspace = true }
|
bytemuck = { workspace = true }
|
||||||
comemo = { workspace = true }
|
comemo = { workspace = true }
|
||||||
hayro = { workspace = true }
|
hayro-render = { workspace = true }
|
||||||
image = { workspace = true }
|
image = { workspace = true }
|
||||||
pixglyph = { workspace = true }
|
pixglyph = { workspace = true }
|
||||||
resvg = { workspace = true }
|
resvg = { workspace = true }
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
use hayro::{FontData, FontQuery, InterpreterSettings, RenderSettings, StandardFont};
|
use std::sync::Arc;
|
||||||
|
use hayro_render::{InterpreterSettings, RenderSettings};
|
||||||
use image::imageops::FilterType;
|
use image::imageops::FilterType;
|
||||||
use image::{GenericImageView, Rgba};
|
use image::{GenericImageView, Rgba};
|
||||||
use std::sync::Arc;
|
|
||||||
use tiny_skia as sk;
|
use tiny_skia as sk;
|
||||||
use tiny_skia::IntSize;
|
use tiny_skia::IntSize;
|
||||||
use typst_library::foundations::Smart;
|
use typst_library::foundations::Smart;
|
||||||
use typst_library::layout::Size;
|
use typst_library::layout::Size;
|
||||||
use typst_library::visualize::{Image, ImageKind, ImageScaling, PdfImage};
|
use typst_library::visualize::{Image, ImageKind, ImageScaling};
|
||||||
|
|
||||||
use crate::{AbsExt, State};
|
use crate::{AbsExt, State};
|
||||||
|
|
||||||
@ -86,7 +86,7 @@ fn build_texture(image: &Image, w: u32, h: u32) -> Option<Arc<sk::Pixmap>> {
|
|||||||
let Rgba([r, g, b, a]) = src;
|
let Rgba([r, g, b, a]) = src;
|
||||||
*dest = sk::ColorU8::from_rgba(r, g, b, a).premultiply();
|
*dest = sk::ColorU8::from_rgba(r, g, b, a).premultiply();
|
||||||
}
|
}
|
||||||
|
|
||||||
texture
|
texture
|
||||||
}
|
}
|
||||||
ImageKind::Svg(svg) => {
|
ImageKind::Svg(svg) => {
|
||||||
@ -97,61 +97,26 @@ fn build_texture(image: &Image, w: u32, h: u32) -> Option<Arc<sk::Pixmap>> {
|
|||||||
h as f32 / tree.size().height(),
|
h as f32 / tree.size().height(),
|
||||||
);
|
);
|
||||||
resvg::render(tree, ts, &mut texture.as_mut());
|
resvg::render(tree, ts, &mut texture.as_mut());
|
||||||
|
|
||||||
texture
|
texture
|
||||||
}
|
}
|
||||||
ImageKind::Pdf(pdf) => build_pdf_texture(pdf, w, h)?,
|
ImageKind::Pdf(pdf) => {
|
||||||
};
|
let hayro_pix = pdf.with_page(|page| {
|
||||||
|
// TODO: Configure so that PDF standard fonts can be resolved
|
||||||
|
let interpreter_settings = InterpreterSettings::default();
|
||||||
|
let render_settings = RenderSettings {
|
||||||
|
x_scale: w as f32 / pdf.width(),
|
||||||
|
y_scale: h as f32 / pdf.height(),
|
||||||
|
width: Some(w as u16),
|
||||||
|
height: Some(h as u16)
|
||||||
|
};
|
||||||
|
hayro_render::render(page, &interpreter_settings, &render_settings)
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
sk::Pixmap::from_vec(hayro_pix.data_as_u8_slice().to_vec(), IntSize::from_wh(w, h)?)?
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
Some(Arc::new(texture))
|
Some(Arc::new(texture))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keep this in sync with `typst-svg`!
|
|
||||||
fn build_pdf_texture(pdf: &PdfImage, w: u32, h: u32) -> Option<sk::Pixmap> {
|
|
||||||
let sf = pdf.standard_fonts().clone();
|
|
||||||
|
|
||||||
let select_standard_font = move |font: StandardFont| -> Option<FontData> {
|
|
||||||
let bytes = match font {
|
|
||||||
StandardFont::Helvetica => sf.helvetica.normal.clone(),
|
|
||||||
StandardFont::HelveticaBold => sf.helvetica.bold.clone(),
|
|
||||||
StandardFont::HelveticaOblique => sf.helvetica.italic.clone(),
|
|
||||||
StandardFont::HelveticaBoldOblique => sf.helvetica.bold_italic.clone(),
|
|
||||||
StandardFont::Courier => sf.courier.normal.clone(),
|
|
||||||
StandardFont::CourierBold => sf.courier.bold.clone(),
|
|
||||||
StandardFont::CourierOblique => sf.courier.italic.clone(),
|
|
||||||
StandardFont::CourierBoldOblique => sf.courier.bold_italic.clone(),
|
|
||||||
StandardFont::TimesRoman => sf.times.normal.clone(),
|
|
||||||
StandardFont::TimesBold => sf.times.bold.clone(),
|
|
||||||
StandardFont::TimesItalic => sf.times.italic.clone(),
|
|
||||||
StandardFont::TimesBoldItalic => sf.times.bold_italic.clone(),
|
|
||||||
StandardFont::ZapfDingBats => sf.zapf_dingbats.clone(),
|
|
||||||
StandardFont::Symbol => sf.symbol.clone(),
|
|
||||||
};
|
|
||||||
|
|
||||||
bytes.map(|d| {
|
|
||||||
let font_data: Arc<dyn AsRef<[u8]> + Send + Sync> = Arc::new(d.clone());
|
|
||||||
|
|
||||||
font_data
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
let interpreter_settings = InterpreterSettings {
|
|
||||||
font_resolver: Arc::new(move |query| match query {
|
|
||||||
FontQuery::Standard(s) => select_standard_font(*s),
|
|
||||||
FontQuery::Fallback(f) => select_standard_font(f.pick_standard_font()),
|
|
||||||
}),
|
|
||||||
warning_sink: Arc::new(|_| {}),
|
|
||||||
};
|
|
||||||
let page = pdf.page();
|
|
||||||
|
|
||||||
let render_settings = RenderSettings {
|
|
||||||
x_scale: w as f32 / pdf.width(),
|
|
||||||
y_scale: h as f32 / pdf.height(),
|
|
||||||
width: Some(w as u16),
|
|
||||||
height: Some(h as u16),
|
|
||||||
};
|
|
||||||
|
|
||||||
let hayro_pix = hayro::render(page, &interpreter_settings, &render_settings);
|
|
||||||
|
|
||||||
sk::Pixmap::from_vec(hayro_pix.take_u8(), IntSize::from_wh(w, h)?)
|
|
||||||
}
|
|
||||||
|
@ -21,7 +21,6 @@ base64 = { workspace = true }
|
|||||||
comemo = { workspace = true }
|
comemo = { workspace = true }
|
||||||
ecow = { workspace = true }
|
ecow = { workspace = true }
|
||||||
flate2 = { workspace = true }
|
flate2 = { workspace = true }
|
||||||
hayro = { workspace = true }
|
|
||||||
image = { workspace = true }
|
image = { workspace = true }
|
||||||
ttf-parser = { workspace = true }
|
ttf-parser = { workspace = true }
|
||||||
xmlparser = { workspace = true }
|
xmlparser = { workspace = true }
|
||||||
|
@ -1,14 +1,10 @@
|
|||||||
use std::borrow::Cow;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use base64::Engine;
|
use base64::Engine;
|
||||||
use ecow::{eco_format, EcoString};
|
use ecow::{eco_format, EcoString};
|
||||||
use hayro::{FontData, FontQuery, InterpreterSettings, RenderSettings, StandardFont};
|
|
||||||
use image::{codecs::png::PngEncoder, ImageEncoder};
|
use image::{codecs::png::PngEncoder, ImageEncoder};
|
||||||
use typst_library::foundations::Smart;
|
use typst_library::foundations::Smart;
|
||||||
use typst_library::layout::{Abs, Axes};
|
use typst_library::layout::{Abs, Axes};
|
||||||
use typst_library::visualize::{
|
use typst_library::visualize::{
|
||||||
ExchangeFormat, Image, ImageKind, ImageScaling, PdfImage, RasterFormat,
|
ExchangeFormat, Image, ImageKind, ImageScaling, RasterFormat,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::SVGRenderer;
|
use crate::SVGRenderer;
|
||||||
@ -48,7 +44,7 @@ pub fn convert_image_scaling(scaling: Smart<ImageScaling>) -> Option<&'static st
|
|||||||
#[comemo::memoize]
|
#[comemo::memoize]
|
||||||
pub fn convert_image_to_base64_url(image: &Image) -> EcoString {
|
pub fn convert_image_to_base64_url(image: &Image) -> EcoString {
|
||||||
let mut buf;
|
let mut buf;
|
||||||
let (format, data): (&str, Cow<[u8]>) = match image.kind() {
|
let (format, data): (&str, &[u8]) = match image.kind() {
|
||||||
ImageKind::Raster(raster) => match raster.format() {
|
ImageKind::Raster(raster) => match raster.format() {
|
||||||
RasterFormat::Exchange(format) => (
|
RasterFormat::Exchange(format) => (
|
||||||
match format {
|
match format {
|
||||||
@ -57,7 +53,7 @@ pub fn convert_image_to_base64_url(image: &Image) -> EcoString {
|
|||||||
ExchangeFormat::Gif => "gif",
|
ExchangeFormat::Gif => "gif",
|
||||||
ExchangeFormat::Webp => "webp",
|
ExchangeFormat::Webp => "webp",
|
||||||
},
|
},
|
||||||
Cow::Borrowed(raster.data()),
|
raster.data(),
|
||||||
),
|
),
|
||||||
RasterFormat::Pixel(_) => ("png", {
|
RasterFormat::Pixel(_) => ("png", {
|
||||||
buf = vec![];
|
buf = vec![];
|
||||||
@ -66,29 +62,11 @@ pub fn convert_image_to_base64_url(image: &Image) -> EcoString {
|
|||||||
encoder.set_icc_profile(icc_profile.to_vec()).ok();
|
encoder.set_icc_profile(icc_profile.to_vec()).ok();
|
||||||
}
|
}
|
||||||
raster.dynamic().write_with_encoder(encoder).unwrap();
|
raster.dynamic().write_with_encoder(encoder).unwrap();
|
||||||
Cow::Borrowed(buf.as_slice())
|
buf.as_slice()
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
ImageKind::Svg(svg) => ("svg+xml", Cow::Borrowed(svg.data())),
|
ImageKind::Svg(svg) => ("svg+xml", svg.data()),
|
||||||
ImageKind::Pdf(pdf) => {
|
ImageKind::Pdf(_) => todo!(),
|
||||||
// To make sure the image isn't pixelated, we always scale up so the lowest
|
|
||||||
// dimension has at least 1000 pixels. However, we only scale up as much so that the
|
|
||||||
// largest dimension doesn't exceed 3000 pixels.
|
|
||||||
const MIN_RES: f32 = 1000.0;
|
|
||||||
const MAX_RES: f32 = 3000.0;
|
|
||||||
|
|
||||||
let base_width = pdf.width();
|
|
||||||
let w_scale = (MIN_RES / base_width).max(MAX_RES / base_width);
|
|
||||||
let base_height = pdf.height();
|
|
||||||
let h_scale = (MIN_RES / base_height).min(MAX_RES / base_height);
|
|
||||||
|
|
||||||
let total_scale = w_scale.min(h_scale);
|
|
||||||
|
|
||||||
let width = (base_width * total_scale).ceil() as u32;
|
|
||||||
let height = (base_height * total_scale).ceil() as u32;
|
|
||||||
|
|
||||||
("png", Cow::Owned(pdf_to_png(pdf, width, height)))
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut url = eco_format!("data:image/{format};base64,");
|
let mut url = eco_format!("data:image/{format};base64,");
|
||||||
@ -96,54 +74,3 @@ pub fn convert_image_to_base64_url(image: &Image) -> EcoString {
|
|||||||
url.push_str(&data);
|
url.push_str(&data);
|
||||||
url
|
url
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keep this in sync with `typst-png`!
|
|
||||||
#[comemo::memoize]
|
|
||||||
fn pdf_to_png(pdf: &PdfImage, w: u32, h: u32) -> Vec<u8> {
|
|
||||||
let sf = pdf.standard_fonts().clone();
|
|
||||||
|
|
||||||
let select_standard_font = move |font: StandardFont| -> Option<FontData> {
|
|
||||||
let bytes = match font {
|
|
||||||
StandardFont::Helvetica => sf.helvetica.normal.clone(),
|
|
||||||
StandardFont::HelveticaBold => sf.helvetica.bold.clone(),
|
|
||||||
StandardFont::HelveticaOblique => sf.helvetica.italic.clone(),
|
|
||||||
StandardFont::HelveticaBoldOblique => sf.helvetica.bold_italic.clone(),
|
|
||||||
StandardFont::Courier => sf.courier.normal.clone(),
|
|
||||||
StandardFont::CourierBold => sf.courier.bold.clone(),
|
|
||||||
StandardFont::CourierOblique => sf.courier.italic.clone(),
|
|
||||||
StandardFont::CourierBoldOblique => sf.courier.bold_italic.clone(),
|
|
||||||
StandardFont::TimesRoman => sf.times.normal.clone(),
|
|
||||||
StandardFont::TimesBold => sf.times.bold.clone(),
|
|
||||||
StandardFont::TimesItalic => sf.times.italic.clone(),
|
|
||||||
StandardFont::TimesBoldItalic => sf.times.bold_italic.clone(),
|
|
||||||
StandardFont::ZapfDingBats => sf.zapf_dingbats.clone(),
|
|
||||||
StandardFont::Symbol => sf.symbol.clone(),
|
|
||||||
};
|
|
||||||
|
|
||||||
bytes.map(|d| {
|
|
||||||
let font_data: Arc<dyn AsRef<[u8]> + Send + Sync> = Arc::new(d.clone());
|
|
||||||
|
|
||||||
font_data
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
let interpreter_settings = InterpreterSettings {
|
|
||||||
font_resolver: Arc::new(move |query| match query {
|
|
||||||
FontQuery::Standard(s) => select_standard_font(*s),
|
|
||||||
FontQuery::Fallback(f) => select_standard_font(f.pick_standard_font()),
|
|
||||||
}),
|
|
||||||
warning_sink: Arc::new(|_| {}),
|
|
||||||
};
|
|
||||||
let page = pdf.page();
|
|
||||||
|
|
||||||
let render_settings = RenderSettings {
|
|
||||||
x_scale: w as f32 / pdf.width(),
|
|
||||||
y_scale: h as f32 / pdf.height(),
|
|
||||||
width: Some(w as u16),
|
|
||||||
height: Some(h as u16),
|
|
||||||
};
|
|
||||||
|
|
||||||
let hayro_pix = hayro::render(page, &interpreter_settings, &render_settings);
|
|
||||||
|
|
||||||
hayro_pix.take_png()
|
|
||||||
}
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user