mirror of
https://github.com/storytold/craft-libs.git
synced 2026-11-12 08:52:32 +00:00
313 lines
12 KiB
Diff
Generated
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,
|
|
}
|