mirror of
https://github.com/storytold/designcraft.git
synced 2026-11-14 14:22:39 +00:00
Merge remote-tracking branch 'upstream/main' into image-perf
# Conflicts: # crates/render/src/decode.rs
This commit is contained in:
commit
829e5b86c3
7 files changed
+127
-68
No files matched your search
Generated
+64
-17
@@ -161,13 +161,28 @@ version = "2.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3"
|
||||
|
||||
[[package]]
|
||||
name = "alloc-no-stdlib"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2fb6cfd47bf496ff64095c20eaba0c201404ee38714d4142fcfa1dc334fcc7a"
|
||||
|
||||
[[package]]
|
||||
name = "alloc-stdlib"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e76a019e91224d279006ff972f1e984179a6e9feb050adba6ce8274aef23195"
|
||||
dependencies = [
|
||||
"alloc-no-stdlib",
|
||||
"alloc-no-stdlib 2.0.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "alloc-stdlib"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b5c1865780388bfa186411ab5f247819487fc4864c6e9c3106611fa347586e1"
|
||||
dependencies = [
|
||||
"alloc-no-stdlib 3.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -644,9 +659,9 @@ version = "8.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5cc91aac060a7a1e25823bdccbfb6af1875b88f17c6daac97894eed8207166b3"
|
||||
dependencies = [
|
||||
"alloc-no-stdlib",
|
||||
"alloc-stdlib",
|
||||
"brotli-decompressor",
|
||||
"alloc-no-stdlib 2.0.4",
|
||||
"alloc-stdlib 0.2.4",
|
||||
"brotli-decompressor 5.0.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -655,8 +670,18 @@ version = "5.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a32acac15fe1967bc3986b2a6347dffc965602354ea6f450ad07e8bfd253583"
|
||||
dependencies = [
|
||||
"alloc-no-stdlib",
|
||||
"alloc-stdlib",
|
||||
"alloc-no-stdlib 2.0.4",
|
||||
"alloc-stdlib 0.2.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "brotli-decompressor"
|
||||
version = "6.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75c74c9c51b9140663e196b5fb88bc94c334d7a497c8311cb759d8557874747f"
|
||||
dependencies = [
|
||||
"alloc-no-stdlib 3.0.0",
|
||||
"alloc-stdlib 0.3.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1273,9 +1298,9 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "craft-image-codecs"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/dylanmtaylor/craft-libs?rev=2e670cf9377b523112f3fe4b409399772c745895#2e670cf9377b523112f3fe4b409399772c745895"
|
||||
source = "git+https://github.com/storytold/craft-libs?rev=cc23ffd4d26f3074808e2187d332564817624f8f#cc23ffd4d26f3074808e2187d332564817624f8f"
|
||||
dependencies = [
|
||||
"craft-jpeg 0.1.0 (git+https://github.com/dylanmtaylor/craft-libs?rev=2e670cf9377b523112f3fe4b409399772c745895)",
|
||||
"craft-jpeg 0.1.0 (git+https://github.com/storytold/craft-libs?rev=cc23ffd4d26f3074808e2187d332564817624f8f)",
|
||||
"exr",
|
||||
"flate2",
|
||||
"half",
|
||||
@@ -1295,9 +1320,10 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "craft-image-codecs"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/storytold/craft-libs?rev=cc23ffd4d26f3074808e2187d332564817624f8f#cc23ffd4d26f3074808e2187d332564817624f8f"
|
||||
source = "git+https://github.com/storytold/craft-libs?rev=ea1c84290dadec636900b4d1bce107e59ca3f0d6#ea1c84290dadec636900b4d1bce107e59ca3f0d6"
|
||||
dependencies = [
|
||||
"craft-jpeg 0.1.0 (git+https://github.com/storytold/craft-libs?rev=cc23ffd4d26f3074808e2187d332564817624f8f)",
|
||||
"craft-jpeg 0.1.0 (git+https://github.com/storytold/craft-libs?rev=ea1c84290dadec636900b4d1bce107e59ca3f0d6)",
|
||||
"craft-metadata",
|
||||
"exr",
|
||||
"flate2",
|
||||
"half",
|
||||
@@ -1347,7 +1373,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "craft-jpeg"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/dylanmtaylor/craft-libs?rev=2e670cf9377b523112f3fe4b409399772c745895#2e670cf9377b523112f3fe4b409399772c745895"
|
||||
source = "git+https://github.com/storytold/craft-libs?rev=cc23ffd4d26f3074808e2187d332564817624f8f#cc23ffd4d26f3074808e2187d332564817624f8f"
|
||||
dependencies = [
|
||||
"thiserror 2.0.21",
|
||||
]
|
||||
@@ -1355,7 +1381,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "craft-jpeg"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/storytold/craft-libs?rev=cc23ffd4d26f3074808e2187d332564817624f8f#cc23ffd4d26f3074808e2187d332564817624f8f"
|
||||
source = "git+https://github.com/storytold/craft-libs?rev=ea1c84290dadec636900b4d1bce107e59ca3f0d6#ea1c84290dadec636900b4d1bce107e59ca3f0d6"
|
||||
dependencies = [
|
||||
"thiserror 2.0.21",
|
||||
]
|
||||
@@ -1389,6 +1415,18 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "craft-metadata"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/storytold/craft-libs?rev=ea1c84290dadec636900b4d1bce107e59ca3f0d6#ea1c84290dadec636900b4d1bce107e59ca3f0d6"
|
||||
dependencies = [
|
||||
"brotli-decompressor 6.3.1",
|
||||
"chrono",
|
||||
"craft-tiff",
|
||||
"quick-xml 0.41.0",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "craft-offcrypto"
|
||||
version = "0.1.0"
|
||||
@@ -1573,6 +1611,15 @@ dependencies = [
|
||||
"thiserror 2.0.21",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "craft-tiff"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/storytold/craft-libs?rev=ea1c84290dadec636900b4d1bce107e59ca3f0d6#ea1c84290dadec636900b4d1bce107e59ca3f0d6"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"thiserror 2.0.21",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "craft-ui"
|
||||
version = "0.1.0"
|
||||
@@ -1990,7 +2037,7 @@ name = "designcraft-cli"
|
||||
version = "0.20.1"
|
||||
dependencies = [
|
||||
"craft-file-formats",
|
||||
"craft-image-codecs 0.1.0 (git+https://github.com/dylanmtaylor/craft-libs?rev=2e670cf9377b523112f3fe4b409399772c745895)",
|
||||
"craft-image-codecs 0.1.0 (git+https://github.com/storytold/craft-libs?rev=ea1c84290dadec636900b4d1bce107e59ca3f0d6)",
|
||||
"designcraft-color",
|
||||
"designcraft-compose",
|
||||
"designcraft-doc",
|
||||
@@ -2165,7 +2212,7 @@ dependencies = [
|
||||
"craft-cms",
|
||||
"craft-eps 0.1.0 (git+https://github.com/storytold/craft-libs?rev=ba24fe2435adb3c81ad6057faf36365fbba736cf)",
|
||||
"craft-file-formats",
|
||||
"craft-image-codecs 0.1.0 (git+https://github.com/dylanmtaylor/craft-libs?rev=2e670cf9377b523112f3fe4b409399772c745895)",
|
||||
"craft-image-codecs 0.1.0 (git+https://github.com/storytold/craft-libs?rev=ea1c84290dadec636900b4d1bce107e59ca3f0d6)",
|
||||
"craft-psd",
|
||||
"craft-svg 0.1.0 (git+https://github.com/storytold/craft-libs?rev=cc23ffd4d26f3074808e2187d332564817624f8f)",
|
||||
"designcraft-color",
|
||||
@@ -2199,7 +2246,7 @@ version = "0.20.1"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"craft-cms",
|
||||
"craft-image-codecs 0.1.0 (git+https://github.com/dylanmtaylor/craft-libs?rev=2e670cf9377b523112f3fe4b409399772c745895)",
|
||||
"craft-image-codecs 0.1.0 (git+https://github.com/storytold/craft-libs?rev=ea1c84290dadec636900b4d1bce107e59ca3f0d6)",
|
||||
"craft-pdf",
|
||||
"craft-prepress",
|
||||
"designcraft-color",
|
||||
@@ -2224,7 +2271,7 @@ name = "designcraft-pptx"
|
||||
version = "0.20.1"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"craft-image-codecs 0.1.0 (git+https://github.com/dylanmtaylor/craft-libs?rev=2e670cf9377b523112f3fe4b409399772c745895)",
|
||||
"craft-image-codecs 0.1.0 (git+https://github.com/storytold/craft-libs?rev=ea1c84290dadec636900b4d1bce107e59ca3f0d6)",
|
||||
"craft-opc 0.1.0 (git+https://github.com/storytold/craft-libs?rev=8abfa9d0f14bafbfc3449fc552ffeafb9ca6a551)",
|
||||
"craft-pptx",
|
||||
"designcraft-color",
|
||||
@@ -2245,7 +2292,7 @@ name = "designcraft-render"
|
||||
version = "0.20.1"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"craft-image-codecs 0.1.0 (git+https://github.com/dylanmtaylor/craft-libs?rev=2e670cf9377b523112f3fe4b409399772c745895)",
|
||||
"craft-image-codecs 0.1.0 (git+https://github.com/storytold/craft-libs?rev=ea1c84290dadec636900b4d1bce107e59ca3f0d6)",
|
||||
"craft-pdf",
|
||||
"designcraft-color",
|
||||
"designcraft-compose",
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ craft-xlsx = { git = "https://github.com/storytold/craft-libs", rev = "2b9a325f5
|
||||
craft-docx = { git = "https://github.com/storytold/craft-libs", rev = "ba24fe2435adb3c81ad6057faf36365fbba736cf", version = "=0.1.0" }
|
||||
craft-wordmodel = { git = "https://github.com/storytold/craft-libs", rev = "ba24fe2435adb3c81ad6057faf36365fbba736cf", version = "=0.1.0" }
|
||||
craft-csv = { git = "https://github.com/storytold/craft-libs", rev = "387c64892b30fdad491b263cc3d9eac660d88dcc", version = "=0.1.0" }
|
||||
craft-image-codecs = { git = "https://github.com/dylanmtaylor/craft-libs", rev = "2e670cf9377b523112f3fe4b409399772c745895", version = "=0.1.0" }
|
||||
craft-image-codecs = { git = "https://github.com/storytold/craft-libs", rev = "ea1c84290dadec636900b4d1bce107e59ca3f0d6", version = "=0.1.0" }
|
||||
craft-psd = { git = "https://github.com/dylanmtaylor/craft-libs", rev = "2e670cf9377b523112f3fe4b409399772c745895", version = "=0.1.0" }
|
||||
craft-file-formats = { git = "https://github.com/storytold/craft-libs", rev = "eed3c28cf016723d004d6f2c79c61424c15d4558", version = "=0.1.0" }
|
||||
craft-opc = { git = "https://github.com/storytold/craft-libs", rev = "8abfa9d0f14bafbfc3449fc552ffeafb9ca6a551", version = "=0.1.0" }
|
||||
|
||||
@@ -59,6 +59,16 @@ pub fn write<W: std::io::Write + std::io::Seek>(out: &mut W, doc: &Document, pre
|
||||
craft_designcraft_format::write_to(out, &compact, concat!("DesignCraft ", env!("CARGO_PKG_VERSION")), preview, &assets, &proxies)
|
||||
}
|
||||
|
||||
/// [`save_with_preview`] into `out`, in chunks, so a writer that stops on cancellation stops the
|
||||
/// write at its next chunk.
|
||||
pub fn write<W: std::io::Write>(out: &mut W, doc: &Document, preview: Option<&[u8]>) -> Result<(), FormatError> {
|
||||
let bytes = save_with_preview(doc, preview)?;
|
||||
for chunk in bytes.chunks(1 << 20) {
|
||||
out.write_all(chunk).map_err(|e| FormatError::Io(e.to_string()))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Required native format version for a document. Kept for app-side compatibility; inference is
|
||||
/// performed by the shared codec over the serialized neutral document schema.
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -583,22 +583,31 @@ pub struct CmykRaster {
|
||||
pub alpha: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
/// The TIFF page that decoding opens; `None` for other formats.
|
||||
fn default_tiff_page(bytes: &[u8]) -> Option<craft_image_codecs::TiffPage> {
|
||||
let mut info = craft_image_codecs::tiff_info(bytes).ok()?;
|
||||
let page = info.default_page()?;
|
||||
(page < info.pages.len()).then(|| info.pages.swap_remove(page))
|
||||
}
|
||||
|
||||
/// A TIFF whose pixels are CMYK (PhotometricInterpretation Separated, four inks).
|
||||
pub fn is_cmyk_tiff(bytes: &[u8]) -> bool {
|
||||
craft_image_codecs::tiff_info(bytes)
|
||||
.ok()
|
||||
.and_then(|info| info.default_page().and_then(|i| info.pages.get(i).map(|page| (page.photometric, page.samples_per_pixel))))
|
||||
.is_some_and(|(photometric, samples)| photometric == Some(5) && matches!(samples, 4 | 5))
|
||||
default_tiff_page(bytes).is_some_and(|page| page.photometric == Some(5) && matches!(page.samples_per_pixel, 4 | 5))
|
||||
}
|
||||
|
||||
/// The ink values of a CMYK TIFF, which [`decode_rgba`] converts to RGB. 8- or 16-bit CMYK,
|
||||
/// pixels interleaved, opaque or with unassociated alpha. `None` for anything else — another
|
||||
/// format, another ink set (InkSet 2), planar storage or premultiplied alpha — and for files
|
||||
/// over the decoder's 256 MiB limit; [`is_cmyk_tiff`] tells those apart from RGB files.
|
||||
/// opaque or with alpha (associated alpha is made straight). `None` for anything else —
|
||||
/// another format or another ink set (InkSet 2) — and for files over the decoder's 256 MiB
|
||||
/// limit; [`is_cmyk_tiff`] tells those apart from RGB files.
|
||||
pub fn decode_cmyk_tiff(bytes: &[u8]) -> Option<CmykRaster> {
|
||||
if too_large_to_decode(bytes) {
|
||||
return None;
|
||||
}
|
||||
// InkSet (tag 332): absent or 1 = CMYK; 2 = the inks named in InkNames; other values are
|
||||
// undefined. Only CMYK samples are C, M, Y and K; reading others as such prints wrong colours.
|
||||
if default_tiff_page(bytes).is_some_and(|page| page.ink_set.is_some_and(|set| set != craft_image_codecs::InkSet::Cmyk)) {
|
||||
return None;
|
||||
}
|
||||
let image = craft_image_codecs::decode_with(bytes, &decode_options()).ok()?;
|
||||
image.layout().is_cmyk().then(|| cmyk_raster(&image, bytes)).flatten()
|
||||
}
|
||||
|
||||
@@ -17,8 +17,7 @@ pub(super) struct ConvertedImage {
|
||||
pub(super) warning: Option<&'static str>,
|
||||
}
|
||||
|
||||
pub(super) const CMYK_FALLBACK_WARNING: &str =
|
||||
"converted to RGB (a CMYK TIFF that is planar, has premultiplied alpha, uses other inks or is over 256 MiB)";
|
||||
pub(super) const CMYK_FALLBACK_WARNING: &str = "converted to RGB (a CMYK TIFF that uses other inks or is over 256 MiB)";
|
||||
|
||||
#[cfg(not(test))]
|
||||
pub(super) type ImageContentCache = HashMap<Arc<Vec<u8>>, ConvertedImage>;
|
||||
@@ -84,13 +83,10 @@ pub(super) fn jpeg(data: krilla::Data, icc: impl FnOnce() -> Option<Vec<u8>>, mo
|
||||
}
|
||||
|
||||
/// Whether krilla would write this PNG, GIF or WebP as untagged RGB: not grey, and no profile of
|
||||
/// its own.
|
||||
/// its own. The header inspection doesn't read WebP, which has no grey form, so a file it can't
|
||||
/// read counts as colour.
|
||||
fn untagged_rgb(data: &[u8]) -> bool {
|
||||
let Some(info) = craft_image_codecs::inspect_raster(data).ok().flatten() else { return false };
|
||||
!matches!(
|
||||
info.color_type,
|
||||
craft_image_codecs::RasterColorType::Bitmap | craft_image_codecs::RasterColorType::Gray | craft_image_codecs::RasterColorType::GrayAlpha
|
||||
) && embedded_icc(data).is_none()
|
||||
!is_grey(data) && embedded_icc(data).is_none()
|
||||
}
|
||||
|
||||
pub(super) fn lossless(data: &Arc<Vec<u8>>, fmt: Option<craft_image_codecs::Format>, mode: RasterMode) -> Option<Image> {
|
||||
@@ -401,28 +397,18 @@ mod image_content_cache_tests {
|
||||
let opts = PdfOptions { standard, ..Default::default() };
|
||||
let mut ex = exporter(&d, &cache, &opts);
|
||||
ex.rgb_only = standard == Standard::PdfA2b;
|
||||
// Unassociated alpha (2) decodes as inks, and as RGB for RGB-only exports.
|
||||
// Associated alpha is rejected by both paths: retain both failure names.
|
||||
let succeeds = alpha.is_none() || alpha == Some(2);
|
||||
let first = ex.load_image(a);
|
||||
assert_eq!(first.is_some(), succeeds, "{alpha:?} {standard:?}");
|
||||
if alpha.is_none() {
|
||||
let (space, samples) = image_color_samples(first.unwrap());
|
||||
assert_eq!(space, if ex.rgb_only { "DeviceRGB" } else { "DeviceCMYK" });
|
||||
assert_eq!(samples, if ex.rgb_only { screen_rgb.clone() } else { crate::image_fixtures::INKS.to_vec() });
|
||||
}
|
||||
assert_eq!(ex.load_image(b).is_some(), succeeds, "{alpha:?} {standard:?}");
|
||||
assert_eq!(ex.load_image(a).is_some(), succeeds);
|
||||
assert_eq!(ex.image_conversions, if succeeds { 1 } else { 2 });
|
||||
assert_eq!(ex.images_by_content.len(), usize::from(succeeds));
|
||||
if succeeds {
|
||||
assert!(ex.warnings.is_empty());
|
||||
} else {
|
||||
for name in ["a.tif", "b.tif"] {
|
||||
assert!(ex.warnings.contains(&format!("image `{name}` could not be decoded and was skipped")));
|
||||
assert_eq!(ex.warnings.contains(&format!("{name}: {CMYK_FALLBACK_WARNING}")), !ex.rgb_only);
|
||||
}
|
||||
}
|
||||
// Every alpha kind decodes as inks, and as RGB for RGB-only exports; associated
|
||||
// alpha (1) is made straight, so it gives the same inks as unassociated (2).
|
||||
let first = ex.load_image(a).unwrap();
|
||||
let (space, samples) = image_color_samples(first);
|
||||
assert_eq!(space, if ex.rgb_only { "DeviceRGB" } else { "DeviceCMYK" }, "{alpha:?} {standard:?}");
|
||||
let want = if ex.rgb_only { screen_rgb.clone() } else { crate::image_fixtures::INKS.to_vec() };
|
||||
assert_eq!(samples, want, "{alpha:?} {standard:?}");
|
||||
assert!(ex.load_image(b).is_some(), "{alpha:?} {standard:?}");
|
||||
assert!(ex.load_image(a).is_some());
|
||||
assert_eq!(ex.image_conversions, 1);
|
||||
assert_eq!(ex.images_by_content.len(), 1);
|
||||
assert!(ex.warnings.is_empty());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -430,7 +416,7 @@ mod image_content_cache_tests {
|
||||
// Serialize the converted raster without an archival validator so the RGB-only pixel
|
||||
// contract stays independent of main's existing PDF/A interpolation validation failure.
|
||||
fn image_color_samples(image: Image) -> (String, Vec<u8>) {
|
||||
use craft_pdf::syntax::object::Name;
|
||||
use craft_pdf::syntax::object::{Name, Stream};
|
||||
let mut pdf = krilla::Document::new();
|
||||
let size = Size::from_wh(20.0, 20.0).unwrap();
|
||||
let mut page = pdf.start_page_with(PageSettings::new(size));
|
||||
@@ -444,7 +430,9 @@ mod image_content_cache_tests {
|
||||
.objects()
|
||||
.into_iter()
|
||||
.filter_map(|o| o.into_stream())
|
||||
.find(|s| s.dict().get::<Name>("Subtype").is_some_and(|n| n.as_str() == "Image"))
|
||||
.filter(|s| s.dict().get::<Name>("Subtype").is_some_and(|n| n.as_str() == "Image"))
|
||||
// An image with alpha comes with its soft mask, another image; take the masked one.
|
||||
.max_by_key(|s| s.dict().get::<Stream<'_>>("SMask").is_some())
|
||||
.unwrap();
|
||||
(stream.dict().get::<Name>("ColorSpace").unwrap().as_str().to_owned(), stream.decoded().unwrap().into_owned())
|
||||
}
|
||||
|
||||
@@ -240,20 +240,22 @@ fn successful_fallback_warns_for_both_names_and_preserves_rgb_samples() {
|
||||
for (name, x) in [("other-inks-a.tif", 36.0), ("other-inks-b.tif", 66.0)] {
|
||||
place_named(&mut d, bytes.clone(), x, name, "image/tiff", 0);
|
||||
}
|
||||
let reason = "converted to RGB (a CMYK TIFF that is planar, has premultiplied alpha, uses other inks or is over 256 MiB)";
|
||||
let reason = "converted to RGB (a CMYK TIFF that uses other inks or is over 256 MiB)";
|
||||
let expected_warnings = [format!("other-inks-a.tif: {reason}"), format!("other-inks-b.tif: {reason}")];
|
||||
// The fallback is the RGB the screen shows: converted through the working CMYK space.
|
||||
let rgb: Vec<u8> = designcraft_images::decode_rgba(&bytes).unwrap().pixels().flat_map(|p| [p[0], p[1], p[2]]).collect();
|
||||
for compress_images in [false, true] {
|
||||
let report = export_pdf_with_report(&d, &Cache::new(), &PdfOptions { compress_images, ..Default::default() }).unwrap();
|
||||
assert_eq!(report.warnings, expected_warnings);
|
||||
let images = image_objects(&report.bytes);
|
||||
assert_eq!(images.len(), 1);
|
||||
assert_eq!(images[0].space, "DeviceRGB");
|
||||
assert_eq!(images[0].samples, [0, 0, 0, 255, 0, 0]);
|
||||
assert_eq!(images[0].samples, rgb);
|
||||
assert_eq!(images[0].size, (2, 1));
|
||||
}
|
||||
let booklet = export_booklet(&d, &Cache::new(), &BookletOptions::default()).unwrap();
|
||||
assert_eq!(booklet.warnings, expected_warnings);
|
||||
assert_eq!(image_objects(&booklet.bytes)[0].samples, [0, 0, 0, 255, 0, 0]);
|
||||
assert_eq!(image_objects(&booklet.bytes)[0].samples, rgb);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -282,19 +284,20 @@ fn pdfa_rgb_conversion_does_not_poison_later_cmyk_exports() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unsupported_associated_alpha_keeps_each_asset_failure_warning() {
|
||||
fn associated_alpha_exports_straight_inks_once() {
|
||||
// The fixture stores the inks multiplied by alpha; the export carries them made straight.
|
||||
let bytes = fixtures::cmyk_tiff(Some(1), false);
|
||||
let mut d = Document::new(&Default::default());
|
||||
for (name, x) in [("alpha-a.tif", 36.0), ("alpha-b.tif", 66.0)] {
|
||||
place_named(&mut d, bytes.clone(), x, name, "image/tiff", 0);
|
||||
}
|
||||
let report = export_pdf_with_report(&d, &Cache::new(), &PdfOptions::default()).unwrap();
|
||||
assert!(image_objects(&report.bytes).is_empty());
|
||||
assert_eq!(report.warnings.len(), 4);
|
||||
for name in ["alpha-a.tif", "alpha-b.tif"] {
|
||||
assert!(report.warnings.iter().any(|w| w.starts_with(&format!("{name}: converted to RGB"))));
|
||||
assert!(report.warnings.contains(&format!("image `{name}` could not be decoded and was skipped")));
|
||||
}
|
||||
assert!(report.warnings.is_empty(), "{:?}", report.warnings);
|
||||
let images = image_objects(&report.bytes);
|
||||
let cmyk: Vec<_> = images.iter().filter(|i| i.space == "DeviceCMYK").collect();
|
||||
assert_eq!(cmyk.len(), 1, "{images:?}");
|
||||
assert_eq!(cmyk[0].samples, fixtures::INKS);
|
||||
assert_eq!(cmyk[0].alpha.as_deref(), Some(&fixtures::ALPHA[..]));
|
||||
}
|
||||
|
||||
fn two_page_pdf() -> Vec<u8> {
|
||||
|
||||
@@ -11,7 +11,9 @@ pub fn cmyk_tiff(alpha: Option<u16>, other_inks: bool) -> Vec<u8> {
|
||||
if let Some(kind) = alpha {
|
||||
let mut image = encoder.new_image::<CMYKA8>(2, 1).unwrap();
|
||||
image.encoder().write_tag(Tag::ExtraSamples, &[kind][..]).unwrap();
|
||||
image.write_data(&[0, 0, 0, 255, ALPHA[0], 0, 255, 255, 0, ALPHA[1]]).unwrap();
|
||||
// Associated alpha (1) stores each ink multiplied by alpha: 255 at alpha 128 is 128.
|
||||
let ink = if kind == 1 { 128 } else { 255 };
|
||||
image.write_data(&[0, 0, 0, 255, ALPHA[0], 0, ink, ink, 0, ALPHA[1]]).unwrap();
|
||||
} else {
|
||||
let mut image = encoder.new_image::<CMYK8>(2, 1).unwrap();
|
||||
if other_inks {
|
||||
|
||||
Reference in new issue
Block a user