mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-26 19:19:10 -06:00
render: draw device shadings in the resources' default colour spaces
Patch 38 now also replaces the /ColorSpace of a shading that is DeviceGray, DeviceRGB or DeviceCMYK with the default for it, whether the shading is painted by sh or as a shading pattern. Poppler and MuPDF draw these in the default too. The shading's cache key includes the default, as the image's does. The forms test also covers a form whose /ColorSpace has no default: it draws in its page's, as in Poppler and MuPDF.
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
//! #1201: the `/DefaultGray`, `/DefaultRGB` and `/DefaultCMYK` colour spaces of a resource
|
||||
//! dictionary stand in for the device spaces its content selects, with colour operators and
|
||||
//! with images (ISO 32000-2 §8.6.5.6, vendored hayro-interpret patch 38). hayro drew device
|
||||
//! colour as it was, so pages that calibrate their device colour this way (common in PDF/A)
|
||||
//! came out in the wrong colours.
|
||||
//! dictionary stand in for the device spaces its content selects, with colour operators, with
|
||||
//! images and with shadings (ISO 32000-2 §8.6.5.6, vendored hayro-interpret patch 38). hayro
|
||||
//! drew device colour as it was, so pages that calibrate their device colour this way (common
|
||||
//! in PDF/A) came out in the wrong colours.
|
||||
//!
|
||||
//! The defaults are synthetic ICC profiles built here, each sending device colour somewhere it
|
||||
//! cannot go uncalibrated: red to green (or blue), black to mid grey, black ink to blue.
|
||||
@@ -250,6 +250,61 @@ fn device_images_draw_in_the_default_colour_spaces() {
|
||||
assert_eq!(device[5], BLACK, "inline /G image without DefaultGray");
|
||||
}
|
||||
|
||||
/// Shadings whose `/ColorSpace` is a device space are drawn in the default for it too: painted
|
||||
/// by `sh` or as a shading pattern, with their colours from a function (axial) or from their
|
||||
/// vertices (a Type 4 mesh).
|
||||
#[test]
|
||||
fn device_shadings_draw_in_the_default_colour_spaces() {
|
||||
let axial = |space: &str, colour: &str| {
|
||||
format!(
|
||||
"<< /ShadingType 2 /ColorSpace /{space} /Coords [0 0 130 0] /Extend [true true] \
|
||||
/Function << /FunctionType 2 /Domain [0 1] /C0 [{colour}] /C1 [{colour}] /N 1 >> >>"
|
||||
)
|
||||
};
|
||||
// Two red triangles over the page: a flag, x, y and red, green and blue, a byte each.
|
||||
let mut mesh: Vec<u8> = Vec::new();
|
||||
for (x, y) in [(0, 0), (255, 0), (0, 255), (255, 0), (255, 255), (0, 255)] {
|
||||
mesh.extend_from_slice(&[0, x, y, 255, 0, 0]);
|
||||
}
|
||||
let objects = [
|
||||
profile_stream(3, rgb_profile([1, 2, 0])),
|
||||
profile_stream(1, gray_profile()),
|
||||
(
|
||||
format!(
|
||||
"<< /ShadingType 4 /ColorSpace /DeviceRGB /BitsPerCoordinate 8 /BitsPerComponent 8 /BitsPerFlag 8 \
|
||||
/Decode [0 255 0 255 0 1 0 1 0 1] /Length {} >>",
|
||||
mesh.len()
|
||||
),
|
||||
mesh,
|
||||
),
|
||||
];
|
||||
let content = "q 0 0 30 70 re W n /A sh Q /Pattern cs /P scn 32 0 30 70 re f \
|
||||
q 64 0 30 70 re W n /M sh Q q 96 0 30 70 re W n /G sh Q";
|
||||
let points = [(15, 35), (47, 35), (79, 35), (111, 35)];
|
||||
let shadings = format!(
|
||||
"/Shading << /A {} /M 7 0 R /G {} >> /Pattern << /P << /PatternType 2 /Shading {} >> >>",
|
||||
axial("DeviceRGB", "1 0 0"),
|
||||
axial("DeviceGray", "0"),
|
||||
axial("DeviceRGB", "1 0 0")
|
||||
);
|
||||
let calibrated = colours(
|
||||
content,
|
||||
&format!("<< /ColorSpace << /DefaultRGB [/ICCBased 5 0 R] /DefaultGray [/ICCBased 6 0 R] >> {shadings} >>"),
|
||||
&objects,
|
||||
&points,
|
||||
);
|
||||
assert_near(calibrated[0], GREEN, "sh, an axial shading in DeviceRGB");
|
||||
assert_near(calibrated[1], GREEN, "a shading pattern in DeviceRGB");
|
||||
assert_near(calibrated[2], GREEN, "sh, a Type 4 mesh in DeviceRGB");
|
||||
assert_near(calibrated[3], MID_GREY, "sh, an axial shading in DeviceGray");
|
||||
|
||||
let device = colours(content, &format!("<< {shadings} >>"), &objects, &points);
|
||||
assert_near(device[0], RED, "sh, an axial shading without DefaultRGB");
|
||||
assert_near(device[1], RED, "a shading pattern without DefaultRGB");
|
||||
assert_near(device[2], RED, "sh, a Type 4 mesh without DefaultRGB");
|
||||
assert_near(device[3], BLACK, "sh, an axial shading without DefaultGray");
|
||||
}
|
||||
|
||||
/// One image drawn by a page with DefaultRGB and by a form that switches it off is decoded
|
||||
/// for each: the decoded-image cache keeps the two apart (hayro-interpret patch 32 shares a
|
||||
/// decode between every draw of an image).
|
||||
@@ -271,9 +326,11 @@ fn an_image_drawn_with_and_without_a_default_keeps_both_colours() {
|
||||
}
|
||||
|
||||
/// A form XObject draws device colour in the default of the resources it is drawn with: its
|
||||
/// own when it has one, else the page's (with or without a `/Resources` of its own, and
|
||||
/// through a form nested in it). A form whose own default is a device space or not a usable
|
||||
/// default draws device colour, not the page's default.
|
||||
/// own when it has one, else the page's (with or without a `/Resources` of its own, with a
|
||||
/// `/ColorSpace` of its own that has no default, and through a form nested in it). Poppler and
|
||||
/// MuPDF draw these forms in the page's default too, though §8.6.5.6 names only the current
|
||||
/// resource dictionary. A form whose own default is a device space or not a usable default
|
||||
/// draws device colour, not the page's default.
|
||||
#[test]
|
||||
fn forms_inherit_the_default_colour_spaces_they_do_not_override() {
|
||||
let blue = rgb_profile([2, 0, 1]);
|
||||
@@ -288,12 +345,13 @@ fn forms_inherit_the_default_colour_spaces_they_do_not_override() {
|
||||
form("/Resources << /ColorSpace << /DefaultRGB /DeviceRGB >> >>", "1 0 0 rg 88 37 20 33 re f"),
|
||||
form("/Resources << /ColorSpace << /DefaultRGB [/Lab << /WhitePoint [0.9642 1 0.8249] >>] >> >>", "1 0 0 rg 110 37 20 33 re f"),
|
||||
form("", "1 0 0 rg 0 0 20 33 re f"),
|
||||
form("/Resources << /ColorSpace << /CS1 /DeviceGray >> >>", "1 0 0 rg 22 0 20 33 re f"),
|
||||
];
|
||||
let got = colours(
|
||||
"1 0 0 rg 0 37 20 33 re f /A Do /B Do /C Do /D Do /E Do",
|
||||
"<< /ColorSpace << /DefaultRGB [/ICCBased 5 0 R] >> /XObject << /A 7 0 R /B 8 0 R /C 9 0 R /D 10 0 R /E 11 0 R >> >>",
|
||||
"1 0 0 rg 0 37 20 33 re f /A Do /B Do /C Do /D Do /E Do /F Do",
|
||||
"<< /ColorSpace << /DefaultRGB [/ICCBased 5 0 R] >> /XObject << /A 7 0 R /B 8 0 R /C 9 0 R /D 10 0 R /E 11 0 R /F 13 0 R >> >>",
|
||||
&objects,
|
||||
&[(10, 17), (32, 17), (54, 17), (76, 17), (98, 17), (120, 17), (10, 53)],
|
||||
&[(10, 17), (32, 17), (54, 17), (76, 17), (98, 17), (120, 17), (10, 53), (32, 53)],
|
||||
);
|
||||
assert_near(got[0], GREEN, "the page");
|
||||
assert_near(got[1], GREEN, "a form without /Resources");
|
||||
@@ -302,6 +360,7 @@ fn forms_inherit_the_default_colour_spaces_they_do_not_override() {
|
||||
assert_eq!(got[4], RED, "a form whose DefaultRGB is /DeviceRGB");
|
||||
assert_eq!(got[5], RED, "a form whose DefaultRGB is a Lab space");
|
||||
assert_near(got[6], BLUE, "a form nested in the one with its own DefaultRGB");
|
||||
assert_near(got[7], GREEN, "a form whose /ColorSpace has no DefaultRGB");
|
||||
}
|
||||
|
||||
/// A default that cannot stand in for its device space (ISO 32000-2 §8.6.5.6: a Lab, Indexed
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+9
-3
@@ -599,7 +599,7 @@ fn interpret_window<'a>(
|
||||
context.get_mut().graphics_state.non_stroke_pattern = n.1.and_then(|name| {
|
||||
resources
|
||||
.get_pattern(name)
|
||||
.and_then(|d| Pattern::new(d, context, resources))
|
||||
.and_then(|d| Pattern::new(d, context, resources, &defaults))
|
||||
});
|
||||
}
|
||||
TypedInstruction::StrokeColorNamed(n) => {
|
||||
@@ -608,7 +608,7 @@ fn interpret_window<'a>(
|
||||
context.get_mut().graphics_state.stroke_pattern = n.1.and_then(|name| {
|
||||
resources
|
||||
.get_pattern(name)
|
||||
.and_then(|d| Pattern::new(d, context, resources))
|
||||
.and_then(|d| Pattern::new(d, context, resources, &defaults))
|
||||
});
|
||||
}
|
||||
TypedInstruction::BeginMarkedContentWithProperties(bdc) => {
|
||||
@@ -860,7 +860,13 @@ fn interpret_window<'a>(
|
||||
.get_shading(s.0)
|
||||
.and_then(|o| {
|
||||
let (dict, stream) = dict_or_stream(&o)?;
|
||||
Shading::new(dict, stream, &context.interpreter_cache.object_cache)
|
||||
// PdfCraft patch (38): a shading in a device space draws in its default.
|
||||
Shading::new(
|
||||
dict,
|
||||
stream,
|
||||
&context.interpreter_cache.object_cache,
|
||||
&defaults,
|
||||
)
|
||||
})
|
||||
.map(|s| {
|
||||
Pattern::Shading(ShadingPattern {
|
||||
|
||||
+12
-3
@@ -1,7 +1,7 @@
|
||||
//! PDF patterns.
|
||||
|
||||
use crate::cache::Cache;
|
||||
use crate::color::{Color, ColorSpace};
|
||||
use crate::color::{Color, ColorSpace, DefaultColorSpaces};
|
||||
use crate::context::{Context, InterpreterCache};
|
||||
use crate::device::Device;
|
||||
use crate::font::Glyph;
|
||||
@@ -34,16 +34,20 @@ pub enum Pattern<'a> {
|
||||
}
|
||||
|
||||
impl<'a> Pattern<'a> {
|
||||
/// PdfCraft patch (38): `defaults` are those of the resources the pattern is selected with,
|
||||
/// for a shading pattern's shading in a device space.
|
||||
pub(crate) fn new(
|
||||
object: Object<'a>,
|
||||
ctx: &Context<'a>,
|
||||
resources: &Resources<'a>,
|
||||
defaults: &DefaultColorSpaces<'_, '_>,
|
||||
) -> Option<Self> {
|
||||
match object {
|
||||
Object::Dict(dict) => Some(Self::Shading(ShadingPattern::new(
|
||||
&dict,
|
||||
&ctx.interpreter_cache.object_cache,
|
||||
ctx.get().graphics_state.non_stroke_alpha,
|
||||
defaults,
|
||||
)?)),
|
||||
Object::Stream(stream) => Some(Self::Tiling(Box::new(TilingPattern::new(
|
||||
stream, ctx, resources,
|
||||
@@ -93,11 +97,16 @@ pub struct ShadingPattern {
|
||||
}
|
||||
|
||||
impl ShadingPattern {
|
||||
pub(crate) fn new(dict: &Dict<'_>, cache: &Cache, opacity: f32) -> Option<Self> {
|
||||
pub(crate) fn new(
|
||||
dict: &Dict<'_>,
|
||||
cache: &Cache,
|
||||
opacity: f32,
|
||||
defaults: &DefaultColorSpaces<'_, '_>,
|
||||
) -> Option<Self> {
|
||||
let shading = dict.get::<Object<'_>>(SHADING).and_then(|o| {
|
||||
let (dict, stream) = dict_or_stream(&o)?;
|
||||
|
||||
Shading::new(dict, stream, cache)
|
||||
Shading::new(dict, stream, cache, defaults)
|
||||
})?;
|
||||
let matrix = dict
|
||||
.get::<[f64; 6]>(MATRIX)
|
||||
|
||||
+18
-6
@@ -4,9 +4,9 @@
|
||||
|
||||
use crate::CacheKey;
|
||||
use crate::cache::Cache;
|
||||
use crate::color::{ColorComponents, ColorSpace};
|
||||
use crate::color::{ColorComponents, ColorSpace, DefaultColorSpaces, DeviceFamily};
|
||||
use crate::function::{Function, StitchingBounds, Values, interpolate};
|
||||
use crate::util::{Float32Ext, PointExt, RectExt};
|
||||
use crate::util::{Float32Ext, PointExt, RectExt, hash128};
|
||||
use hayro_syntax::bit_reader::BitReader;
|
||||
use hayro_syntax::object::Array;
|
||||
use hayro_syntax::object::Dict;
|
||||
@@ -139,12 +139,24 @@ pub struct Shading {
|
||||
}
|
||||
|
||||
impl Shading {
|
||||
pub(crate) fn new(dict: &Dict<'_>, stream: Option<&Stream<'_>>, cache: &Cache) -> Option<Self> {
|
||||
let cache_key = dict.cache_key();
|
||||
|
||||
/// PdfCraft patch (38): a shading in a device space is drawn in its default colour space
|
||||
/// from `defaults`, the resources it is painted with (ISO 32000-2 §8.6.5.6), and keyed by
|
||||
/// it as an image is.
|
||||
pub(crate) fn new(
|
||||
dict: &Dict<'_>,
|
||||
stream: Option<&Stream<'_>>,
|
||||
cache: &Cache,
|
||||
defaults: &DefaultColorSpaces<'_, '_>,
|
||||
) -> Option<Self> {
|
||||
let shading_num = dict.get::<u8>(SHADING_TYPE)?;
|
||||
|
||||
let color_space = ColorSpace::new(dict.get(COLORSPACE)?, cache)?;
|
||||
let color_space_obj = dict.get::<Object<'_>>(COLORSPACE)?;
|
||||
let default =
|
||||
DeviceFamily::of_object(&color_space_obj).and_then(|family| defaults.get(family));
|
||||
let (color_space, cache_key) = match default {
|
||||
Some((space, key)) => (space, hash128(&(dict.cache_key(), key))),
|
||||
None => (ColorSpace::new(color_space_obj, cache)?, dict.cache_key()),
|
||||
};
|
||||
|
||||
let shading_type = match shading_num {
|
||||
1 => {
|
||||
|
||||
Reference in New Issue
Block a user