Files
craft-libs/vendor/krilla/patches/03-overprint.patch

313 lines
12 KiB
Diff
Generated

diff --git a/src/configure/PDF_A2.md b/src/configure/PDF_A2.md
index a4e4377..9609ea2 100644
--- a/src/configure/PDF_A2.md
+++ b/src/configure/PDF_A2.md
@@ -67,7 +67,8 @@ in case CMYK is used but no profile was provided, export fails.
6.2.4.2:
- sRGB/sGrey ICC profiles conform to ICC v4 specification. 🟢
-- krilla does not support overprinting. 🔵
+- krilla fails export if overprint mode 1 is combined with fill or stroke overprint, since
+ all CMYK is ICCBased in this mode. 🟢
6.2.4.3:
- krilla always uses sRGB for RGB, and in addition also embeds an sRGB output intent. 🟢
diff --git a/src/configure/PDF_A4.md b/src/configure/PDF_A4.md
index 44a294f..d5e4374 100644
--- a/src/configure/PDF_A4.md
+++ b/src/configure/PDF_A4.md
@@ -66,7 +66,8 @@ See `README.md` for the meaning of each color.
### 6.2.4 Colour spaces
- krilla only uses device-independent colors in this export mode. 🟢
- krilla uses compatible ICC profiles. 🟢
-- krilla does not use the OPM entry. 🟢
+- krilla fails export if overprint mode 1 is combined with fill or stroke overprint, since
+ all CMYK is ICCBased in this mode. 🟢
- krilla never uses CMYK profiles as the destination profile. 🟢
- krilla does not use device color spaces in this mode. 🟢
- krilla ensures the Alternate space in Separation color spaces obeys the restrictions
diff --git a/src/configure/validate.rs b/src/configure/validate.rs
index e92da05..91996ee 100644
--- a/src/configure/validate.rs
+++ b/src/configure/validate.rs
@@ -131,6 +131,16 @@ pub enum ValidationError {
Transparency(Option<Location>),
/// The PDF contains an image with `interpolate` set to `true`.
ImageInterpolation(Option<Location>),
+ /// Overprint mode 1 (`/OPM 1`) was combined with fill or stroke overprint while CMYK
+ /// colors are ICCBased.
+ ///
+ /// PDF/A-2 and later forbid overprint mode 1 when an ICCBased CMYK color space is used
+ /// for a side that overprints. Validated export always writes CMYK as ICCBased, so any
+ /// [`Overprint`] with `nonzero` and `fill` or `stroke` set triggers it; use
+ /// `nonzero: false` there.
+ ///
+ /// [`Overprint`]: crate::blend::Overprint
+ OverprintModeIccCmyk(Option<Location>),
/// The PDF contains an embedded file.
EmbeddedFile(EmbedError, Option<Location>),
/// The PDF contains no tagging.
@@ -554,6 +564,7 @@ impl Archival {
(
Self::A1_A | Self::A1_B,
ValidationError::InconsistentSeparationFallback(_)
+ | ValidationError::OverprintModeIccCmyk(_)
| ValidationError::InvalidCodepointMapping(_, _, _, _)
| ValidationError::UnicodePrivateArea(_, _, _, _)
| ValidationError::NoDocumentTitle
@@ -592,6 +603,7 @@ impl Archival {
| ValidationError::RestrictedLicense(_)
| ValidationError::MissingDocumentDate
| ValidationError::ImageInterpolation(_)
+ | ValidationError::OverprintModeIccCmyk(_)
| ValidationError::EmbeddedPDF(_),
) => true,
// Allowed under all PDF/A-2 and PDF/A-3 profiles.
@@ -656,6 +668,7 @@ impl Archival {
| ValidationError::RestrictedLicense(_)
| ValidationError::MissingDocumentDate
| ValidationError::ImageInterpolation(_)
+ | ValidationError::OverprintModeIccCmyk(_)
| ValidationError::EmbeddedPDF(_),
) => true,
// Allowed under all PDF/A-4 profiles.
@@ -1130,6 +1143,7 @@ impl Accessibility {
| ValidationError::NoDocumentLanguage
| ValidationError::Transparency(_)
| ValidationError::ImageInterpolation(_)
+ | ValidationError::OverprintModeIccCmyk(_)
| ValidationError::EmbeddedFile(
EmbedError::Existence | EmbedError::MissingDate | EmbedError::MissingMimeType,
_,
diff --git a/src/content.rs b/src/content.rs
index dd5b1cb..751cb2f 100644
--- a/src/content.rs
+++ b/src/content.rs
@@ -16,6 +16,7 @@ use crate::configure::ValidationError;
#[cfg(feature = "raster-images")]
use crate::geom::Size;
use crate::geom::{Point, Rect, Transform};
+use crate::graphics::blend::Overprint;
use crate::graphics::color::{Color, ColorSpace};
use crate::graphics::graphics_state::{ExtGState, GraphicsStates};
#[cfg(feature = "raster-images")]
@@ -183,6 +184,12 @@ impl ContentBuilder {
}
}
+ pub(crate) fn set_overprint(&mut self, overprint: Overprint) {
+ let state =
+ ExtGState::new().overprint(overprint.fill, overprint.stroke, overprint.to_pdf_mode());
+ self.graphics_states.combine(&state);
+ }
+
pub(crate) fn expand_bbox(&mut self, new_bbox: Rect) {
let new_bbox = self.graphics_states.transform_bbox(new_bbox);
if let Some(bbox) = &mut self.bbox {
diff --git a/src/graphics/blend.rs b/src/graphics/blend.rs
index 9d0e98d..8d7c5c7 100644
--- a/src/graphics/blend.rs
+++ b/src/graphics/blend.rs
@@ -1,4 +1,4 @@
-//! Blending.
+//! Blending and overprint.
/// How to blend source and backdrop.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
@@ -44,3 +44,38 @@ impl BlendMode {
}
}
}
+
+/// Overprint settings for the graphics state (`/op`, `/OP`, `/OPM`).
+///
+/// With overprint on, painting in one colorant leaves the other colorants of the backdrop
+/// in place on separated output instead of knocking them out. Viewers that don't simulate
+/// overprint ignore it.
+#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Hash)]
+pub struct Overprint {
+ /// Overprint for fills, including filled text (`/op`).
+ pub fill: bool,
+ /// Overprint for strokes, including stroked text (`/OP`).
+ pub stroke: bool,
+ /// Nonzero overprint mode (`/OPM 1`): a DeviceCMYK component of 0 leaves that
+ /// colorant of the backdrop unchanged. With `false` (`/OPM 0`), every DeviceCMYK
+ /// component paints, zero included.
+ ///
+ /// ISO 32000-1 (8.6.7) applies the mode to DeviceCMYK only. With
+ /// [`SerializeSettings::no_device_cs`] all CMYK is ICCBased, so a PDF 1.7 reader paints
+ /// zero components as with `false`, and PDF/A-2 and later reject the combination
+ /// ([`ValidationError::OverprintModeIccCmyk`]).
+ ///
+ /// [`SerializeSettings::no_device_cs`]: crate::SerializeSettings::no_device_cs
+ /// [`ValidationError::OverprintModeIccCmyk`]: crate::configure::ValidationError::OverprintModeIccCmyk
+ pub nonzero: bool,
+}
+
+impl Overprint {
+ pub(crate) fn to_pdf_mode(self) -> pdf_writer::types::OverprintMode {
+ if self.nonzero {
+ pdf_writer::types::OverprintMode::IgnoreZeroChannel
+ } else {
+ pdf_writer::types::OverprintMode::OverrideAllColorants
+ }
+ }
+}
diff --git a/src/graphics/graphics_state.rs b/src/graphics/graphics_state.rs
index 9fc913c..93f3e25 100644
--- a/src/graphics/graphics_state.rs
+++ b/src/graphics/graphics_state.rs
@@ -1,7 +1,7 @@
use std::hash::Hash;
use std::sync::Arc;
-use pdf_writer::types::BlendMode;
+use pdf_writer::types::{BlendMode, OverprintMode};
use pdf_writer::{Finish, Name, Ref};
use crate::chunk_container::ChunkContainer;
@@ -24,6 +24,12 @@ struct Repr {
blend_mode: Option<BlendMode>,
/// An active mask.
mask: Option<Ref>,
+ /// Fill overprint (`/op`).
+ overprint_fill: Option<bool>,
+ /// Stroke overprint (`/OP`).
+ overprint_stroke: Option<bool>,
+ /// Overprint mode (`/OPM`).
+ overprint_mode: Option<OverprintMode>,
}
/// A graphics state containing information about
@@ -31,6 +37,7 @@ struct Repr {
/// - The current non-stroking alpha.
/// - The current blend mode.
/// - The current mask.
+/// - The current overprint settings.
///
/// This struct provides exposes a builder pattern for setting the various properties
/// individually.
@@ -66,6 +73,16 @@ impl ExtGState {
self
}
+ /// Create a new graphics state with fill and stroke overprint and an overprint mode.
+ #[must_use]
+ pub(crate) fn overprint(mut self, fill: bool, stroke: bool, mode: OverprintMode) -> Self {
+ let repr = Arc::make_mut(&mut self.0);
+ repr.overprint_fill = Some(fill);
+ repr.overprint_stroke = Some(stroke);
+ repr.overprint_mode = Some(mode);
+ self
+ }
+
/// Create a new graphics state with a mask.
#[must_use]
pub(crate) fn mask(
@@ -85,6 +102,9 @@ impl ExtGState {
&& self.0.stroking_alpha.is_none()
&& self.0.non_stroking_alpha.is_none()
&& self.0.blend_mode.is_none()
+ && self.0.overprint_fill.is_none()
+ && self.0.overprint_stroke.is_none()
+ && self.0.overprint_mode.is_none()
}
/// Integrate another graphics state into the current one. This is done by replacing
@@ -106,6 +126,18 @@ impl ExtGState {
if let Some(mask) = other.0.mask {
Arc::make_mut(&mut self.0).mask = Some(mask);
}
+
+ if let Some(fill) = other.0.overprint_fill {
+ Arc::make_mut(&mut self.0).overprint_fill = Some(fill);
+ }
+
+ if let Some(stroke) = other.0.overprint_stroke {
+ Arc::make_mut(&mut self.0).overprint_stroke = Some(stroke);
+ }
+
+ if let Some(mode) = other.0.overprint_mode {
+ Arc::make_mut(&mut self.0).overprint_mode = Some(mode);
+ }
}
}
@@ -153,6 +185,30 @@ impl Cacheable for ExtGState {
ext_st.pair(Name(b"SMask"), mask_ref);
}
+ if let Some(fill) = self.0.overprint_fill {
+ ext_st.overprint_fill(fill);
+ }
+
+ if let Some(stroke) = self.0.overprint_stroke {
+ ext_st.overprint(stroke);
+ }
+
+ if let Some(mode) = self.0.overprint_mode {
+ // Under `no_device_cs` all CMYK is ICCBased, which PDF/A-2 and later forbid with
+ // OPM 1 when that side overprints. The colors painted under this state aren't
+ // known here, so any overprinting side counts.
+ let overprints =
+ self.0.overprint_fill == Some(true) || self.0.overprint_stroke == Some(true);
+ if mode == OverprintMode::IgnoreZeroChannel
+ && overprints
+ && sc.serialize_settings().no_device_cs
+ {
+ sc.register_validation_error(ValidationError::OverprintModeIccCmyk(sc.location));
+ }
+
+ ext_st.overprint_mode(mode);
+ }
+
ext_st.finish();
}
}
diff --git a/src/surface.rs b/src/surface.rs
index 990527f..09b1b40 100644
--- a/src/surface.rs
+++ b/src/surface.rs
@@ -16,7 +16,7 @@ use crate::geom::Path;
use crate::geom::Size;
use crate::geom::{Point, Transform};
use crate::graphic::Graphic;
-use crate::graphics::blend::BlendMode;
+use crate::graphics::blend::{BlendMode, Overprint};
use crate::graphics::graphics_state::ExtGState;
#[cfg(feature = "raster-images")]
use crate::graphics::image::Image;
@@ -422,6 +422,16 @@ impl<'a> Surface<'a> {
self.bd.get_mut().set_blend_mode(blend_mode.to_pdf());
}
+ /// Push overprint settings. Paths, text and images drawn until the matching
+ /// [`Surface::pop`] carry them, and so do groups pushed inside. The `/op`, `/OP` and
+ /// `/OPM` entries are always written, so `Overprint::default()` turns overprint off
+ /// inside an outer `push_overprint`.
+ pub fn push_overprint(&mut self, overprint: Overprint) {
+ self.push_instructions.push(PushInstruction::Overprint);
+ self.bd.get_mut().save_graphics_state();
+ self.bd.get_mut().set_overprint(overprint);
+ }
+
/// Push a new clip path.
pub fn push_clip_path(&mut self, path: &Path, clip_rule: &FillRule) {
self.push_instructions.push(PushInstruction::ClipPath);
@@ -509,6 +519,7 @@ impl<'a> Surface<'a> {
}
PushInstruction::ClipPath => self.bd.get_mut().pop_clip_path(),
PushInstruction::BlendMode => self.bd.get_mut().restore_graphics_state(),
+ PushInstruction::Overprint => self.bd.get_mut().restore_graphics_state(),
PushInstruction::Mask(mask) => {
let stream = self.bd.sub_builders.pop().unwrap().finish(self.sc);
self.bd
@@ -670,6 +681,7 @@ pub(crate) enum PushInstruction {
Opacity(NormalizedF32),
ClipPath,
BlendMode,
+ Overprint,
Mask(Box<Mask>),
Isolated,
}