diff --git a/Cargo.lock b/Cargo.lock index 1a1c3f55b..72d97bc45 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -93,12 +93,6 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" -[[package]] -name = "arrayvec" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" - [[package]] name = "arrayvec" version = "0.7.2" @@ -128,6 +122,12 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +[[package]] +name = "base64" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" + [[package]] name = "biblatex" version = "0.8.0" @@ -519,12 +519,9 @@ dependencies = [ [[package]] name = "data-url" -version = "0.1.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a30bfce702bcfa94e906ef82421f2c0e61c076ad76030c16ee5d2e9a32fe193" -dependencies = [ - "matches", -] +checksum = "8d7439c3735f405729d52c3fbbe4de140eaf938a1fe47d227c27f8254d4302a5" [[package]] name = "dirs" @@ -686,13 +683,14 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "fontdb" -version = "0.9.3" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52186a39c335aa6f79fc0bf1c3cf854870b6ad4e50a7bb8a59b4ba1331f478a" +checksum = "237ff9f0813bbfc9de836016472e0c9ae7802f174a51594607e5f4ff334cb2f5" dependencies = [ "log", "memmap2", - "ttf-parser 0.17.1", + "slotmap", + "ttf-parser", ] [[package]] @@ -739,16 +737,6 @@ dependencies = [ "wasi", ] -[[package]] -name = "gif" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3edd93c6756b4dfaf2709eafcc345ba2636565295c198a9cfbf75fa5e3e00b06" -dependencies = [ - "color_quant", - "weezl", -] - [[package]] name = "gif" version = "0.12.0" @@ -885,13 +873,19 @@ dependencies = [ "bytemuck", "byteorder", "color_quant", - "gif 0.12.0", - "jpeg-decoder 0.3.0", + "gif", + "jpeg-decoder", "num-rational", "num-traits", "png", ] +[[package]] +name = "imagesize" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b72ad49b554c1728b1e83254a1b1565aea4161e28dabbfa171fc15fe62299caf" + [[package]] name = "include_dir" version = "0.7.3" @@ -1040,12 +1034,6 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" -[[package]] -name = "jpeg-decoder" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9478aa10f73e7528198d75109c8be5cd7d15fb530238040148d5f9a22d4c5b3b" - [[package]] name = "jpeg-decoder" version = "0.3.0" @@ -1083,11 +1071,11 @@ dependencies = [ [[package]] name = "kurbo" -version = "0.8.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a53776d271cfb873b17c618af0298445c88afc52837f3e948fa3fafd131f449" +checksum = "d676038719d1c892f91e6e85121550143c75880b42f7feff6d413a078cf91fb3" dependencies = [ - "arrayvec 0.7.2", + "arrayvec", ] [[package]] @@ -1170,12 +1158,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "matches" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" - [[package]] name = "memchr" version = "2.5.0" @@ -1200,15 +1182,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "miniz_oxide" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" -dependencies = [ - "adler", -] - [[package]] name = "miniz_oxide" version = "0.6.2" @@ -1285,7 +1258,7 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3" dependencies = [ - "arrayvec 0.7.2", + "arrayvec", "itoa", ] @@ -1414,9 +1387,9 @@ checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" [[package]] name = "pdf-writer" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "249f9b33a3192626f2cd9f4b0cd66c1ec32d65968d58cf4d8239977feddddead" +checksum = "63f45f7c7538e67c58cb4977e4f97bbd75fbd3990d827d28d597ec746291f644" dependencies = [ "bitflags 1.3.2", "itoa", @@ -1449,9 +1422,9 @@ dependencies = [ [[package]] name = "pico-args" -version = "0.4.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db8bcd96cb740d03149cbad5518db9fd87126a10ab519c011893b1754134c468" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" [[package]] name = "pin-project-lite" @@ -1465,7 +1438,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9eefadd393715fe315c8cdcd587f893b818a6dfe4f6f9faeb44b764c7c38fd8b" dependencies = [ - "ttf-parser 0.18.1", + "ttf-parser", ] [[package]] @@ -1590,9 +1563,9 @@ dependencies = [ [[package]] name = "rctree" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ae028b272a6e99d9f8260ceefa3caa09300a8d6c8d2b2001316474bc52122e9" +checksum = "3b42e27ef78c35d3998403c1d26f3efd9e135d3e5121b0a4845cc5cc27547f4f" [[package]] name = "redox_syscall" @@ -1648,15 +1621,12 @@ checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c" [[package]] name = "resvg" -version = "0.22.0" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e702d1e8e00a3a0717b96244cba840f34f542d8f23097c8903266c4e2975658" +checksum = "142e83d8ae8c8c639f304698a5567b229ba65caba867bf4387bbc0ae158827cf" dependencies = [ - "gif 0.11.4", - "jpeg-decoder 0.2.6", "log", "pico-args", - "png", "rgb", "svgtypes", "tiny-skia", @@ -1694,10 +1664,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b833d8d034ea094b1ea68aa6d5c740e0d04bad9d16568d08ba6f76823a114316" [[package]] -name = "roxmltree" -version = "0.14.1" +name = "rosvgtree" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "921904a62e410e37e215c40381b7117f830d9d89ba60ab5236170541dd25646b" +checksum = "ad747e7384940e7bf33b15ba433b7bad9f44c0c6d5287a67c2cb22cd1743d497" +dependencies = [ + "log", + "roxmltree", + "simplecss", + "siphasher", + "svgtypes", +] + +[[package]] +name = "roxmltree" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8f595a457b6b8c6cda66a48503e92ee8d19342f905948f29c383200ec9eb1d8" dependencies = [ "xmlparser", ] @@ -1751,14 +1734,14 @@ checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" [[package]] name = "rustybuzz" -version = "0.5.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a617c811f5c9a7060fe511d35d13bf5b9f0463ce36d63ce666d05779df2b4eba" +checksum = "162bdf42e261bee271b3957691018634488084ef577dddeb6420a9684cab2a6a" dependencies = [ "bitflags 1.3.2", "bytemuck", "smallvec", - "ttf-parser 0.15.2", + "ttf-parser", "unicode-bidi-mirroring", "unicode-ccc", "unicode-general-category", @@ -1771,15 +1754,6 @@ version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" -[[package]] -name = "safe_arch" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1ff3d6d9696af502cc3110dacce942840fb06ff4514cad92236ecc455f2ce05" -dependencies = [ - "bytemuck", -] - [[package]] name = "same-file" version = "1.0.6" @@ -1899,6 +1873,15 @@ version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" +[[package]] +name = "slotmap" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1e08e261d0e8f5c43123b7adf3e4ca1690d655377ac93a03b2c9d3e98de1342" +dependencies = [ + "version_check", +] + [[package]] name = "smallvec" version = "1.10.0" @@ -1948,6 +1931,15 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9091b6114800a5f2141aee1d1b9d6ca3592ac062dc5decb3764ec5895a47b4eb" +[[package]] +name = "strict-num" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9df65f20698aeed245efdde3628a6b559ea1239bbb871af1b6e3b58c413b2bd1" +dependencies = [ + "float-cmp", +] + [[package]] name = "strsim" version = "0.10.0" @@ -1985,21 +1977,21 @@ checksum = "09eab8a83bff89ba2200bd4c59be45c7c787f988431b936099a5a266c957f2f9" [[package]] name = "svg2pdf" version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd5736713f3850a24060c3cdd7ac9efdc0c5138779386c6c8975b46d54d2d3d5" +source = "git+https://github.com/typst/svg2pdf#fec20fb9e2ab42f76060f349688005af1d182444" dependencies = [ "image", - "miniz_oxide 0.5.4", + "miniz_oxide 0.7.1", "pdf-writer", "usvg", ] [[package]] name = "svgtypes" -version = "0.8.2" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22975e8a2bac6a76bb54f898a6b18764633b00e780330f0b689f65afb3975564" +checksum = "ed4b0611e7f3277f68c0fa18e385d9e2d26923691379690039548f867cef02a7" dependencies = [ + "kurbo", "siphasher", ] @@ -2112,16 +2104,28 @@ dependencies = [ [[package]] name = "tiny-skia" -version = "0.6.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d049bfef0eaa2521e75d9ffb5ce86ad54480932ae19b85f78bec6f52c4d30d78" +checksum = "5b610cd8b9a29feb9029c30f1e7bff634651b6e4e925388ee6cff4c68d901a3e" dependencies = [ "arrayref", - "arrayvec 0.5.2", + "arrayvec", "bytemuck", "cfg-if", + "log", "png", - "safe_arch", + "tiny-skia-path", +] + +[[package]] +name = "tiny-skia-path" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7acb0ccda1ac91084353a56d0b69b0e29c311fd809d2088b1ed2f9ae1841c47" +dependencies = [ + "arrayref", + "bytemuck", + "strict-num", ] [[package]] @@ -2260,18 +2264,6 @@ dependencies = [ "tracing-log", ] -[[package]] -name = "ttf-parser" -version = "0.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b3e06c9b9d80ed6b745c7159c40b311ad2916abb34a49e9be2653b90db0d8dd" - -[[package]] -name = "ttf-parser" -version = "0.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "375812fa44dab6df41c195cd2f7fecb488f6c09fbaafb62807488cefab642bff" - [[package]] name = "ttf-parser" version = "0.18.1" @@ -2313,7 +2305,7 @@ dependencies = [ "svg2pdf", "tiny-skia", "tracing", - "ttf-parser 0.18.1", + "ttf-parser", "typst-macros", "unicode-math-class", "unicode-segmentation", @@ -2393,7 +2385,7 @@ dependencies = [ "syntect", "toml", "tracing", - "ttf-parser 0.18.1", + "ttf-parser", "typed-arena", "typst", "unicode-bidi", @@ -2425,7 +2417,7 @@ dependencies = [ "oxipng", "rayon", "tiny-skia", - "ttf-parser 0.18.1", + "ttf-parser", "typst", "typst-library", "unscanny", @@ -2479,9 +2471,9 @@ checksum = "cc2520efa644f8268dce4dcd3050eaa7fc044fca03961e9998ac7e2e92b77cf1" [[package]] name = "unicode-general-category" -version = "0.4.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07547e3ee45e28326cc23faac56d44f58f16ab23e413db526debce3b0bfd2742" +checksum = "2281c8c1d221438e373249e065ca4989c4c36952c211ff21a0ee91c44a3869e7" [[package]] name = "unicode-ident" @@ -2558,7 +2550,7 @@ version = "2.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "338b31dd1314f68f3aabf3ed57ab922df95ffcd902476ca7ba3c4ce7b908c46d" dependencies = [ - "base64", + "base64 0.13.1", "flate2", "log", "once_cell", @@ -2583,28 +2575,62 @@ dependencies = [ [[package]] name = "usvg" -version = "0.22.0" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a261d60a7215fa339482047cc3dafd4e22e2bf34396aaebef2b707355bbb39c0" +checksum = "4b44e14b7678bcc5947b397991432d0c4e02a103958a0ed5e1b9b961ddd08b21" +dependencies = [ + "base64 0.21.0", + "log", + "pico-args", + "usvg-parser", + "usvg-text-layout", + "usvg-tree", + "xmlwriter", +] + +[[package]] +name = "usvg-parser" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90c8251d965c2882a636ffcc054340b1f13a6bce68779cb5b2084d8ffc2535be" dependencies = [ - "base64", "data-url", "flate2", - "float-cmp", + "imagesize", + "kurbo", + "log", + "rosvgtree", + "strict-num", + "svgtypes", + "usvg-tree", +] + +[[package]] +name = "usvg-text-layout" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c4fed019d1af07bfe0f3bac13d120d7b51bc65b38cb24809cf4ed0b8b631138" +dependencies = [ "fontdb", "kurbo", "log", - "pico-args", - "rctree", - "roxmltree", "rustybuzz", - "simplecss", - "siphasher", - "svgtypes", - "ttf-parser 0.15.2", "unicode-bidi", "unicode-script", "unicode-vo", + "usvg-tree", +] + +[[package]] +name = "usvg-tree" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7371265c467cdae0ccc3655e2e3f310c695fb9f717c0d25187bf3b333f7b5159" +dependencies = [ + "kurbo", + "rctree", + "strict-num", + "svgtypes", ] [[package]] @@ -2956,6 +2982,12 @@ version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d25c75bf9ea12c4040a97f829154768bbbce366287e2dc044af160cd79a13fd" +[[package]] +name = "xmlwriter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" + [[package]] name = "xmp-writer" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index bb313c77a..a0e51002d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,31 +26,31 @@ bytemuck = "1" comemo = "0.2.2" ecow = "0.1" flate2 = "1" -fontdb = "0.9" +fontdb = "0.13" if_chain = "1" image = { version = "0.24", default-features = false, features = ["png", "jpeg", "gif"] } indexmap = "1.9.3" log = "0.4" miniz_oxide = "0.7" once_cell = "1" -pdf-writer = "0.6" +pdf-writer = "0.7" pixglyph = "0.1" regex = "1" -resvg = { version = "0.22", default-features = false } -roxmltree = "0.14" -rustybuzz = "0.5" +resvg = { version = "0.32", default-features = false } +roxmltree = "0.18" +rustybuzz = "0.7" serde = { version = "1", features = ["derive"] } siphasher = "0.3" subsetter = "0.1.1" -svg2pdf = "0.4" -tiny-skia = "0.6.6" +svg2pdf = { git = "https://github.com/typst/svg2pdf" } +tiny-skia = "0.9.0" tracing = "0.1.37" ttf-parser = "0.18.1" unicode-math-class = "0.1" unicode-segmentation = "1" unicode-xid = "0.2" unscanny = "0.1" -usvg = { version = "0.22", default-features = false, features = ["text"] } +usvg = { version = "0.32", default-features = false, features = ["text"] } xmp-writer = "0.1" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] diff --git a/library/Cargo.toml b/library/Cargo.toml index 630b44038..033058f3d 100644 --- a/library/Cargo.toml +++ b/library/Cargo.toml @@ -22,12 +22,12 @@ csv = "1" ecow = "0.1" hayagriva = "0.3" hypher = "0.1" -kurbo = "0.8" +kurbo = "0.9" lipsum = "0.9" log = "0.4" once_cell = "1" -roxmltree = "0.14" -rustybuzz = "0.5" +roxmltree = "0.18" +rustybuzz = "0.7" serde_json = "1" serde_yaml = "0.8" smallvec = "1.10" diff --git a/library/src/compute/data.rs b/library/src/compute/data.rs index 14ae304bf..d104cf279 100644 --- a/library/src/compute/data.rs +++ b/library/src/compute/data.rs @@ -448,7 +448,6 @@ fn convert_xml(node: roxmltree::Node) -> Value { let tag: Str = node.tag_name().name().into(); let attrs: Dict = node .attributes() - .iter() .map(|attr| (attr.name().into(), attr.value().into())) .collect(); diff --git a/src/diag.rs b/src/diag.rs index 88141062a..556d32557 100644 --- a/src/diag.rs +++ b/src/diag.rs @@ -273,7 +273,6 @@ pub fn format_xml_like_error(format: &str, error: roxmltree::Error) -> EcoString roxmltree::Error::NoRootNode => { eco_format!("failed to parse {format}: missing root node") } - roxmltree::Error::SizeLimit => "file is too large".into(), _ => eco_format!("failed to parse {format}"), } } diff --git a/src/export/pdf/image.rs b/src/export/pdf/image.rs index 04d4dcc3a..dcd5a45a2 100644 --- a/src/export/pdf/image.rs +++ b/src/export/pdf/image.rs @@ -11,6 +11,7 @@ use crate::image::{DecodedImage, RasterFormat}; pub fn write_images(ctx: &mut PdfContext) { for image in ctx.image_map.items() { let image_ref = ctx.alloc.bump(); + let icc_ref = ctx.alloc.bump(); ctx.image_refs.push(image_ref); let width = image.width(); @@ -19,7 +20,7 @@ pub fn write_images(ctx: &mut PdfContext) { // Add the primary image. // TODO: Error if image could not be encoded. match image.decoded() { - DecodedImage::Raster(dynamic, format) => { + DecodedImage::Raster(dynamic, icc, format) => { // TODO: Error if image could not be encoded. let (data, filter, has_color) = encode_image(*format, dynamic).unwrap(); let mut image = ctx.writer.image_xobject(image_ref, &data); @@ -29,7 +30,9 @@ pub fn write_images(ctx: &mut PdfContext) { image.bits_per_component(8); let space = image.color_space(); - if has_color { + if icc.is_some() { + space.icc_based(icc_ref); + } else if has_color { space.device_rgb(); } else { space.device_gray(); @@ -49,6 +52,21 @@ pub fn write_images(ctx: &mut PdfContext) { mask.height(height as i32); mask.color_space().device_gray(); mask.bits_per_component(8); + } else { + image.finish(); + } + + if let Some(icc) = icc { + let compressed = deflate(&icc.0); + let mut stream = ctx.writer.icc_profile(icc_ref, &compressed); + stream.filter(Filter::FlateDecode); + if has_color { + stream.n(3); + stream.alternate().srgb(); + } else { + stream.n(1); + stream.alternate().d65_gray(); + } } } DecodedImage::Svg(svg) => { diff --git a/src/export/pdf/outline.rs b/src/export/pdf/outline.rs index f8f12d71c..c156ecafb 100644 --- a/src/export/pdf/outline.rs +++ b/src/export/pdf/outline.rs @@ -118,7 +118,7 @@ fn write_outline_item( let index = pos.page.get() - 1; if let Some(&height) = ctx.page_heights.get(index) { let y = (pos.point.y - Abs::pt(10.0)).max(Abs::zero()); - outline.dest_direct().page(ctx.page_refs[index]).xyz( + outline.dest().page(ctx.page_refs[index]).xyz( pos.point.x.to_f32(), height - y.to_f32(), None, diff --git a/src/export/pdf/page.rs b/src/export/pdf/page.rs index acf5062ec..35a4f5dcd 100644 --- a/src/export/pdf/page.rs +++ b/src/export/pdf/page.rs @@ -139,7 +139,7 @@ fn write_page(ctx: &mut PdfContext, page: Page) { annotation .action() .action_type(ActionType::GoTo) - .destination_direct() + .destination() .page(ctx.page_refs[index]) .xyz(pos.point.x.to_f32(), height - y.to_f32(), None); } @@ -499,7 +499,7 @@ fn write_image(ctx: &mut PageContext, x: f32, y: f32, image: &Image, size: Size) if let Some(alt) = image.alt() { let mut image_span = ctx.content.begin_marked_content_with_properties(Name(b"Span")); - let mut image_alt = image_span.properties_direct(); + let mut image_alt = image_span.properties(); image_alt.pair(Name(b"Alt"), pdf_writer::Str(alt.as_bytes())); image_alt.finish(); image_span.finish(); diff --git a/src/export/render.rs b/src/export/render.rs index fa3dc4b51..31e440d1f 100644 --- a/src/export/render.rs +++ b/src/export/render.rs @@ -5,9 +5,10 @@ use std::sync::Arc; use image::imageops::FilterType; use image::{GenericImageView, Rgba}; +use resvg::FitTo; use tiny_skia as sk; use ttf_parser::{GlyphId, OutlineBuilder}; -use usvg::{FitTo, NodeExt}; +use usvg::{NodeExt, TreeParsing}; use crate::doc::{Frame, FrameItem, GroupItem, Meta, TextItem}; use crate::geom::{ @@ -38,7 +39,7 @@ pub fn render(frame: &Frame, pixel_per_pt: f32, fill: Color) -> sk::Pixmap { fn render_frame( canvas: &mut sk::Pixmap, ts: sk::Transform, - mask: Option<&sk::ClipMask>, + mask: Option<&sk::Mask>, frame: &Frame, ) { for (pos, item) in frame.items() { @@ -73,13 +74,13 @@ fn render_frame( fn render_group( canvas: &mut sk::Pixmap, ts: sk::Transform, - mask: Option<&sk::ClipMask>, + mask: Option<&sk::Mask>, group: &GroupItem, ) { let ts = ts.pre_concat(group.transform.into()); let mut mask = mask; - let mut storage; + let storage; if group.clips { let size = group.frame.size(); let w = size.x.to_f32(); @@ -88,21 +89,32 @@ fn render_group( .map(sk::PathBuilder::from_rect) .and_then(|path| path.transform(ts)) { - let result = if let Some(mask) = mask { - storage = mask.clone(); - storage.intersect_path(&path, sk::FillRule::default(), false) + if let Some(mask) = mask { + let mut mask = mask.clone(); + mask.intersect_path( + &path, + sk::FillRule::default(), + false, + sk::Transform::default(), + ); + storage = mask; } else { let pxw = canvas.width(); let pxh = canvas.height(); - storage = sk::ClipMask::new(); - storage.set_path(pxw, pxh, &path, sk::FillRule::default(), false) - }; + let Some(mut mask) = sk::Mask::new(pxw, pxh) else { + // Fails if clipping rect is empty. In that case we just + // clip everything by returning. + return; + }; - // Clipping fails if clipping rect is empty. In that case we just - // clip everything by returning. - if result.is_none() { - return; - } + mask.fill_path( + &path, + sk::FillRule::default(), + false, + sk::Transform::default(), + ); + storage = mask; + }; mask = Some(&storage); } @@ -115,7 +127,7 @@ fn render_group( fn render_text( canvas: &mut sk::Pixmap, ts: sk::Transform, - mask: Option<&sk::ClipMask>, + mask: Option<&sk::Mask>, text: &TextItem, ) { let mut x = 0.0; @@ -136,7 +148,7 @@ fn render_text( fn render_svg_glyph( canvas: &mut sk::Pixmap, ts: sk::Transform, - mask: Option<&sk::ClipMask>, + mask: Option<&sk::Mask>, text: &TextItem, id: GlyphId, ) -> Option<()> { @@ -157,8 +169,8 @@ fn render_svg_glyph( // Parse SVG. let opts = usvg::Options::default(); - let tree = usvg::Tree::from_xmltree(&document, &opts.to_ref()).ok()?; - let view_box = tree.svg_node().view_box.rect; + let tree = usvg::Tree::from_xmltree(&document, &opts).ok()?; + let view_box = tree.view_box.rect; // If there's no viewbox defined, use the em square for our scale // transformation ... @@ -182,7 +194,7 @@ fn render_svg_glyph( // See https://github.com/RazrFalcon/resvg/issues/602 for why // using the svg size is problematic here. let mut bbox = usvg::Rect::new_bbox(); - for node in tree.root().descendants() { + for node in tree.root.descendants() { if let Some(rect) = node.calculate_bbox().and_then(|b| b.to_rect()) { bbox = bbox.expand(rect); } @@ -224,14 +236,16 @@ fn render_svg_glyph( &sk::PixmapPaint::default(), sk::Transform::identity(), mask, - ) + ); + + Some(()) } /// Render a bitmap glyph into the canvas. fn render_bitmap_glyph( canvas: &mut sk::Pixmap, ts: sk::Transform, - mask: Option<&sk::ClipMask>, + mask: Option<&sk::Mask>, text: &TextItem, id: GlyphId, ) -> Option<()> { @@ -255,7 +269,7 @@ fn render_bitmap_glyph( fn render_outline_glyph( canvas: &mut sk::Pixmap, ts: sk::Transform, - mask: Option<&sk::ClipMask>, + mask: Option<&sk::Mask>, text: &TextItem, id: GlyphId, ) -> Option<()> { @@ -278,7 +292,7 @@ fn render_outline_glyph( // system is Y-up. let scale = text.size.to_f32() / text.font.units_per_em() as f32; let ts = ts.pre_scale(scale, -scale); - canvas.fill_path(&path, &paint, rule, ts, mask)?; + canvas.fill_path(&path, &paint, rule, ts, mask); return Some(()); } @@ -318,7 +332,9 @@ fn render_outline_glyph( &sk::PixmapPaint::default(), sk::Transform::identity(), mask, - ) + ); + + Some(()) } else { let cw = canvas.width() as i32; let ch = canvas.height() as i32; @@ -365,7 +381,7 @@ fn render_outline_glyph( fn render_shape( canvas: &mut sk::Pixmap, ts: sk::Transform, - mask: Option<&sk::ClipMask>, + mask: Option<&sk::Mask>, shape: &Shape, ) -> Option<()> { let path = match shape.geometry { @@ -465,7 +481,7 @@ fn convert_path(path: &geom::Path) -> Option { fn render_image( canvas: &mut sk::Pixmap, ts: sk::Transform, - mask: Option<&sk::ClipMask>, + mask: Option<&sk::Mask>, image: &Image, size: Size, ) -> Option<()> { @@ -503,7 +519,7 @@ fn render_image( fn scaled_texture(image: &Image, w: u32, h: u32) -> Option> { let mut pixmap = sk::Pixmap::new(w, h)?; match image.decoded() { - DecodedImage::Raster(dynamic, _) => { + DecodedImage::Raster(dynamic, _, _) => { let downscale = w < image.width(); let filter = if downscale { FilterType::Lanczos3 } else { FilterType::CatmullRom }; diff --git a/src/image.rs b/src/image.rs index 49d919082..d78c74285 100644 --- a/src/image.rs +++ b/src/image.rs @@ -1,6 +1,6 @@ //! Image handling. -use std::collections::BTreeSet; +use std::collections::BTreeMap; use std::fmt::{self, Debug, Formatter}; use std::hash::{Hash, Hasher}; use std::io; @@ -8,6 +8,12 @@ use std::sync::Arc; use comemo::Tracked; use ecow::EcoString; +use image::codecs::gif::GifDecoder; +use image::codecs::jpeg::JpegDecoder; +use image::codecs::png::PngDecoder; +use image::io::Limits; +use image::{ImageDecoder, ImageResult}; +use usvg::{TreeParsing, TreeTextToPath}; use crate::diag::{format_xml_like_error, StrResult}; use crate::util::Buffer; @@ -171,8 +177,8 @@ impl From for ImageFormat { /// A decoded image. pub enum DecodedImage { - /// A decoded pixel raster. - Raster(image::DynamicImage, RasterFormat), + /// A decoded pixel raster with its ICC profile. + Raster(image::DynamicImage, Option, RasterFormat), /// An decoded SVG tree. Svg(usvg::Tree), } @@ -181,34 +187,52 @@ impl DecodedImage { /// The width of the image in pixels. pub fn width(&self) -> u32 { match self { - Self::Raster(dynamic, _) => dynamic.width(), - Self::Svg(tree) => tree.svg_node().size.width().ceil() as u32, + Self::Raster(dynamic, _, _) => dynamic.width(), + Self::Svg(tree) => tree.size.width().ceil() as u32, } } /// The height of the image in pixels. pub fn height(&self) -> u32 { match self { - Self::Raster(dynamic, _) => dynamic.height(), - Self::Svg(tree) => tree.svg_node().size.height().ceil() as u32, + Self::Raster(dynamic, _, _) => dynamic.height(), + Self::Svg(tree) => tree.size.height().ceil() as u32, } } } +/// Raw data for of an ICC profile. +pub struct IccProfile(pub Vec); + /// Decode a raster image. #[comemo::memoize] fn decode_raster(data: &Buffer, format: RasterFormat) -> StrResult> { - let cursor = io::Cursor::new(&data); - let reader = image::io::Reader::with_format(cursor, format.into()); - let dynamic = reader.decode().map_err(format_image_error)?; - Ok(Arc::new(DecodedImage::Raster(dynamic, format))) + fn decode_with<'a, T: ImageDecoder<'a>>( + decoder: ImageResult, + ) -> ImageResult<(image::DynamicImage, Option)> { + let mut decoder = decoder?; + let icc = decoder.icc_profile().map(IccProfile); + decoder.set_limits(Limits::default())?; + let dynamic = image::DynamicImage::from_decoder(decoder)?; + Ok((dynamic, icc)) + } + + let cursor = io::Cursor::new(data); + let (dynamic, icc) = match format { + RasterFormat::Jpg => decode_with(JpegDecoder::new(cursor)), + RasterFormat::Png => decode_with(PngDecoder::new(cursor)), + RasterFormat::Gif => decode_with(GifDecoder::new(cursor)), + } + .map_err(format_image_error)?; + + Ok(Arc::new(DecodedImage::Raster(dynamic, icc, format))) } /// Decode an SVG image. #[comemo::memoize] fn decode_svg(data: &Buffer) -> StrResult> { let opts = usvg::Options::default(); - let tree = usvg::Tree::from_data(data, &opts.to_ref()).map_err(format_usvg_error)?; + let tree = usvg::Tree::from_data(data, &opts).map_err(format_usvg_error)?; Ok(Arc::new(DecodedImage::Svg(tree))) } @@ -219,79 +243,89 @@ fn decode_svg_with_fonts( world: Tracked, fallback_family: Option<&str>, ) -> StrResult> { - // Parse XML. - let xml = std::str::from_utf8(data) - .map_err(|_| format_usvg_error(usvg::Error::NotAnUtf8Str))?; - let document = roxmltree::Document::parse(xml) - .map_err(|err| format_xml_like_error("svg", err))?; - - // Parse SVG. - let mut opts = usvg::Options { - fontdb: load_svg_fonts(&document, world, fallback_family), - ..Default::default() - }; + let mut opts = usvg::Options::default(); // Recover the non-lowercased version of the family because // usvg is case sensitive. let book = world.book(); - if let Some(family) = fallback_family + let fallback_family = fallback_family .and_then(|lowercase| book.select_family(lowercase).next()) .and_then(|index| book.info(index)) - .map(|info| info.family.clone()) - { - opts.font_family = family; + .map(|info| info.family.clone()); + + if let Some(family) = &fallback_family { + opts.font_family = family.clone(); } - let tree = - usvg::Tree::from_xmltree(&document, &opts.to_ref()).map_err(format_usvg_error)?; + let mut tree = usvg::Tree::from_data(data, &opts).map_err(format_usvg_error)?; + if tree.has_text_nodes() { + let fontdb = load_svg_fonts(&tree, world, fallback_family.as_deref()); + tree.convert_text(&fontdb); + } Ok(Arc::new(DecodedImage::Svg(tree))) } /// Discover and load the fonts referenced by an SVG. fn load_svg_fonts( - document: &roxmltree::Document, + tree: &usvg::Tree, world: Tracked, fallback_family: Option<&str>, ) -> fontdb::Database { - // Find out which font families are referenced by the SVG. We simply do a - // search for `font-family` attributes. This won't help with CSS, but usvg - // 22.0 doesn't seem to support it anyway. Once we bump to the latest usvg, - // this can be replaced by a scan for text elements in the SVG: - // https://github.com/RazrFalcon/resvg/issues/555 - let mut referenced = BTreeSet::::new(); - traverse_xml(&document.root(), &mut |node| { - if let Some(list) = node.attribute("font-family") { - for family in list.split(',') { - referenced.insert(EcoString::from(family.trim()).to_lowercase()); - } - } - }); - - // Prepare font database. + let mut referenced = BTreeMap::::new(); let mut fontdb = fontdb::Database::new(); - for family in referenced.iter().map(|family| family.as_str()).chain(fallback_family) { + let mut load = |family: &str| { + let lower = EcoString::from(family.trim()).to_lowercase(); + if let Some(&success) = referenced.get(&lower) { + return success; + } + // We load all variants for the family, since we don't know which will // be used. - for id in world.book().select_family(family) { + let mut success = false; + for id in world.book().select_family(&lower) { if let Some(font) = world.font(id) { let source = Arc::new(font.data().clone()); fontdb.load_font_source(fontdb::Source::Binary(source)); + success = true; } } + + referenced.insert(lower, success); + success + }; + + // Load fallback family. + if let Some(family) = fallback_family { + load(family); } + // Find out which font families are referenced by the SVG. + traverse_svg(&tree.root, &mut |node| { + let usvg::NodeKind::Text(text) = &mut *node.borrow_mut() else { return }; + for chunk in &mut text.chunks { + for span in &mut chunk.spans { + for family in &mut span.font.families { + if !load(family) { + let Some(fallback) = fallback_family else { continue }; + *family = fallback.into(); + } + } + } + } + }); + fontdb } /// Search for all font families referenced by an SVG. -fn traverse_xml(node: &roxmltree::Node, f: &mut F) +fn traverse_svg(node: &usvg::Node, f: &mut F) where - F: FnMut(&roxmltree::Node), + F: FnMut(&usvg::Node), { f(node); for child in node.children() { - traverse_xml(&child, f); + traverse_svg(&child, f); } } diff --git a/tests/Cargo.toml b/tests/Cargo.toml index e0b3a24a8..0b704ec29 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -14,7 +14,7 @@ iai = { git = "https://github.com/reknih/iai" } once_cell = "1" oxipng = "8.0.0" rayon = "1.7.0" -tiny-skia = "0.6.6" +tiny-skia = "0.9.0" ttf-parser = "0.18.1" unscanny = "0.1" walkdir = "2" diff --git a/tests/ref/bugs/math-realize.png b/tests/ref/bugs/math-realize.png index 941f965b1..c48244039 100644 Binary files a/tests/ref/bugs/math-realize.png and b/tests/ref/bugs/math-realize.png differ diff --git a/tests/ref/bugs/smartquotes-in-outline.png b/tests/ref/bugs/smartquotes-in-outline.png index c8d763b4f..8a2cbc6ae 100644 Binary files a/tests/ref/bugs/smartquotes-in-outline.png and b/tests/ref/bugs/smartquotes-in-outline.png differ diff --git a/tests/ref/layout/clip.png b/tests/ref/layout/clip.png index f6ec0ba9f..11c486819 100644 Binary files a/tests/ref/layout/clip.png and b/tests/ref/layout/clip.png differ diff --git a/tests/ref/layout/grid-3.png b/tests/ref/layout/grid-3.png index abae5c7b7..0f54f2ccd 100644 Binary files a/tests/ref/layout/grid-3.png and b/tests/ref/layout/grid-3.png differ diff --git a/tests/ref/layout/pad.png b/tests/ref/layout/pad.png index c33b97361..d228f07fb 100644 Binary files a/tests/ref/layout/pad.png and b/tests/ref/layout/pad.png differ diff --git a/tests/ref/layout/par-bidi.png b/tests/ref/layout/par-bidi.png index 8751d93ea..8b626b2a7 100644 Binary files a/tests/ref/layout/par-bidi.png and b/tests/ref/layout/par-bidi.png differ diff --git a/tests/ref/layout/par-indent.png b/tests/ref/layout/par-indent.png index 233529256..cceaa3b91 100644 Binary files a/tests/ref/layout/par-indent.png and b/tests/ref/layout/par-indent.png differ diff --git a/tests/ref/layout/place.png b/tests/ref/layout/place.png index 968e613cd..2ef85a4de 100644 Binary files a/tests/ref/layout/place.png and b/tests/ref/layout/place.png differ diff --git a/tests/ref/layout/transform.png b/tests/ref/layout/transform.png index cb4b0af28..0b26a7f7d 100644 Binary files a/tests/ref/layout/transform.png and b/tests/ref/layout/transform.png differ diff --git a/tests/ref/math/content.png b/tests/ref/math/content.png index 433c8ddcf..f7af513b0 100644 Binary files a/tests/ref/math/content.png and b/tests/ref/math/content.png differ diff --git a/tests/ref/math/numbering.png b/tests/ref/math/numbering.png index 72abfed6b..8ccb3ac61 100644 Binary files a/tests/ref/math/numbering.png and b/tests/ref/math/numbering.png differ diff --git a/tests/ref/meta/bibliography-ordering.png b/tests/ref/meta/bibliography-ordering.png index e59e792b2..a03236cd8 100644 Binary files a/tests/ref/meta/bibliography-ordering.png and b/tests/ref/meta/bibliography-ordering.png differ diff --git a/tests/ref/meta/bibliography.png b/tests/ref/meta/bibliography.png index 8e5e18ecd..22d7355c2 100644 Binary files a/tests/ref/meta/bibliography.png and b/tests/ref/meta/bibliography.png differ diff --git a/tests/ref/meta/figure.png b/tests/ref/meta/figure.png index a86b4d6d6..35d7b4a07 100644 Binary files a/tests/ref/meta/figure.png and b/tests/ref/meta/figure.png differ diff --git a/tests/ref/meta/link.png b/tests/ref/meta/link.png index 075ca6e1a..1232f413d 100644 Binary files a/tests/ref/meta/link.png and b/tests/ref/meta/link.png differ diff --git a/tests/ref/meta/outline.png b/tests/ref/meta/outline.png index c485ca9e2..bf9c4abeb 100644 Binary files a/tests/ref/meta/outline.png and b/tests/ref/meta/outline.png differ diff --git a/tests/ref/meta/query-before-after.png b/tests/ref/meta/query-before-after.png index 8757ce8e1..80f8fe1f7 100644 Binary files a/tests/ref/meta/query-before-after.png and b/tests/ref/meta/query-before-after.png differ diff --git a/tests/ref/meta/query-figure.png b/tests/ref/meta/query-figure.png index 3b8d3398e..2537ebf00 100644 Binary files a/tests/ref/meta/query-figure.png and b/tests/ref/meta/query-figure.png differ diff --git a/tests/ref/meta/query-header.png b/tests/ref/meta/query-header.png index 809812020..c2dc46892 100644 Binary files a/tests/ref/meta/query-header.png and b/tests/ref/meta/query-header.png differ diff --git a/tests/ref/meta/ref.png b/tests/ref/meta/ref.png index dc25a0ab9..c904fc998 100644 Binary files a/tests/ref/meta/ref.png and b/tests/ref/meta/ref.png differ diff --git a/tests/ref/text/baseline.png b/tests/ref/text/baseline.png index 4ad82e1f0..dcd6eb121 100644 Binary files a/tests/ref/text/baseline.png and b/tests/ref/text/baseline.png differ diff --git a/tests/ref/text/emoji.png b/tests/ref/text/emoji.png index 2036a6fc9..715cc30f8 100644 Binary files a/tests/ref/text/emoji.png and b/tests/ref/text/emoji.png differ diff --git a/tests/ref/text/fallback.png b/tests/ref/text/fallback.png index d5d79ee29..544a04340 100644 Binary files a/tests/ref/text/fallback.png and b/tests/ref/text/fallback.png differ diff --git a/tests/ref/text/font.png b/tests/ref/text/font.png index 856d556b9..cb5ad984f 100644 Binary files a/tests/ref/text/font.png and b/tests/ref/text/font.png differ diff --git a/tests/ref/text/linebreak-obj.png b/tests/ref/text/linebreak-obj.png index 60231290f..7159aae6c 100644 Binary files a/tests/ref/text/linebreak-obj.png and b/tests/ref/text/linebreak-obj.png differ diff --git a/tests/ref/text/symbol.png b/tests/ref/text/symbol.png index 9c09732f2..8848a89ed 100644 Binary files a/tests/ref/text/symbol.png and b/tests/ref/text/symbol.png differ diff --git a/tests/ref/visualize/image.png b/tests/ref/visualize/image.png index 0d1a0b8a6..060b4b8af 100644 Binary files a/tests/ref/visualize/image.png and b/tests/ref/visualize/image.png differ diff --git a/tests/ref/visualize/stroke.png b/tests/ref/visualize/stroke.png index 1c8870703..3aa87c4b4 100644 Binary files a/tests/ref/visualize/stroke.png and b/tests/ref/visualize/stroke.png differ diff --git a/tests/ref/visualize/svg-text.png b/tests/ref/visualize/svg-text.png index fbaa85766..e9f02aeac 100644 Binary files a/tests/ref/visualize/svg-text.png and b/tests/ref/visualize/svg-text.png differ