mirror of
https://github.com/storytold/craft-libs.git
synced 2026-11-08 02:30:11 +00:00
krilla: CMYK, gray and spot colors keep their exact inks (patch 17) (#417)
* krilla: CMYK, gray and spot colors keep their exact inks (patch 17) Gray, CMYK, Separation and DeviceN colors stored each component as a u8, so every ink was written as a multiple of 1/255: C75 became 0.7490196 and C4 M4 Y4 K4 became 0.039215688. - cmyk, luma, separation and device_n Color::new_f32 take components from 0 to 1 and write them as given. The u8 constructors are unchanged. - Gradient stops rewritten into one DeviceN, Separation or CMYK space keep their components. - Mesh gradients write corner colors at 16 bits a component. * Tests: the 75% K mesh corner comment gives the asserted 0.749996
This commit is contained in:
@@ -933,8 +933,8 @@ fn mesh(patches: Vec<MeshPatch>) -> backend::paint::Paint {
|
||||
}
|
||||
|
||||
/// Each Type 6 shading in the file: color space family and colorant names, components a
|
||||
/// corner (from `/Decode`), and the corner color bytes of each patch.
|
||||
type MeshSummary = ((String, Vec<String>), usize, Vec<Vec<u8>>);
|
||||
/// corner (from `/Decode`), and the 16-bit corner color components of each patch.
|
||||
type MeshSummary = ((String, Vec<String>), usize, Vec<Vec<u16>>);
|
||||
|
||||
fn mesh_shadings(bytes: &[u8]) -> Vec<MeshSummary> {
|
||||
let pdf = hayro_syntax::Pdf::new(Arc::new(bytes.to_vec())).unwrap();
|
||||
@@ -946,21 +946,27 @@ fn mesh_shadings(bytes: &[u8]) -> Vec<MeshSummary> {
|
||||
continue;
|
||||
}
|
||||
assert_eq!(dict.get::<i32>(b"BitsPerCoordinate".as_slice()), Some(32));
|
||||
assert_eq!(dict.get::<i32>(b"BitsPerComponent".as_slice()), Some(8));
|
||||
assert_eq!(dict.get::<i32>(b"BitsPerComponent".as_slice()), Some(16));
|
||||
assert_eq!(dict.get::<i32>(b"BitsPerFlag".as_slice()), Some(8));
|
||||
let cs = dict.get::<Object<'_>>(b"ColorSpace".as_slice()).unwrap();
|
||||
let decode = dict.get::<Array<'_>>(b"Decode".as_slice()).unwrap().iter::<f32>().count();
|
||||
let components = (decode - 4) / 2;
|
||||
let data = stream.decoded().unwrap();
|
||||
let patch_len = 1 + 12 * 8 + 4 * components;
|
||||
let patch_len = 1 + 12 * 8 + 4 * 2 * components;
|
||||
assert_eq!(data.len() % patch_len, 0, "whole patches of {components} components a corner");
|
||||
let corners = data.chunks(patch_len).map(|patch| patch[1 + 12 * 8..].to_vec()).collect();
|
||||
let samples = |bytes: &[u8]| -> Vec<u16> { bytes.chunks(2).map(|c| u16::from_be_bytes([c[0], c[1]])).collect() };
|
||||
let corners = data.chunks(patch_len).map(|patch| samples(&patch[1 + 12 * 8..])).collect();
|
||||
out.push((colorant_names(&cs), components, corners));
|
||||
}
|
||||
out.sort_by(|a, b| a.0.cmp(&b.0));
|
||||
out
|
||||
}
|
||||
|
||||
/// 8-bit components as the 16-bit components that carry the same value (`v * 257`).
|
||||
fn wide(components: Vec<u16>) -> Vec<u16> {
|
||||
components.into_iter().map(|v| v * 257).collect()
|
||||
}
|
||||
|
||||
fn gold_spot(tint: u8) -> backend::color::Color {
|
||||
use backend::color::separation::{self, SeparationColorant, SeparationSpace};
|
||||
separation::Color::new(tint, SeparationSpace::new(SeparationColorant::Custom("Gold".to_owned()), gold().into())).into()
|
||||
@@ -981,7 +987,7 @@ fn mesh_mixing_process_and_spot_corners_is_a_device_n_shading() {
|
||||
]
|
||||
};
|
||||
let names = |v: &[&str]| v.iter().map(|s| (*s).to_owned()).collect::<Vec<_>>();
|
||||
let expected = vec![vec![255, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 0], vec![0, 0, 255, 0, 0, 128, 255, 0, 0, 255, 0, 0]];
|
||||
let expected = vec![wide(vec![255, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 0]), wide(vec![0, 0, 255, 0, 0, 128, 255, 0, 0, 255, 0, 0])];
|
||||
|
||||
let opaque = page_with(|surface| fill_rect(surface, 0.0, mesh(patches(1.0))));
|
||||
assert_eq!(mesh_shadings(&opaque), vec![(("DeviceN".to_owned(), names(&["Cyan", "Black", "Gold"])), 3, expected.clone())]);
|
||||
@@ -989,7 +995,7 @@ fn mesh_mixing_process_and_spot_corners_is_a_device_n_shading() {
|
||||
let translucent = page_with(|surface| fill_rect(surface, 0.0, mesh(patches(0.5))));
|
||||
let found = mesh_shadings(&translucent);
|
||||
assert_eq!(found.len(), 2, "{found:?}");
|
||||
assert_eq!(found[0], (("DeviceGray".to_owned(), vec![]), 1, vec![vec![255; 4], vec![255, 255, 255, 128]]));
|
||||
assert_eq!(found[0], (("DeviceGray".to_owned(), vec![]), 1, vec![wide(vec![255; 4]), vec![65535, 65535, 65535, 32768]]));
|
||||
assert_eq!(found[1], (("DeviceN".to_owned(), names(&["Cyan", "Black", "Gold"])), 3, expected));
|
||||
assert_eq!(soft_masks(&translucent).len(), 1);
|
||||
}
|
||||
@@ -1022,10 +1028,90 @@ fn rgb_mesh_is_a_device_rgb_shading() {
|
||||
let rgb = |r, g, b| -> backend::color::Color { backend::color::rgb::Color::new(r, g, b).into() };
|
||||
let patch = square(10.0, 10.0, 40.0, [rgb(255, 0, 0), rgb(0, 255, 0), rgb(0, 0, 255), rgb(255, 255, 255)], [1.0; 4]);
|
||||
let bytes = page_with(|surface| fill_rect(surface, 0.0, mesh(vec![patch])));
|
||||
let corners = vec![vec![255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255]];
|
||||
let corners = vec![wide(vec![255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255])];
|
||||
assert_eq!(mesh_shadings(&bytes), vec![(("DeviceRGB".to_owned(), vec![]), 3, corners)]);
|
||||
}
|
||||
|
||||
// Color precision (the `new_f32` constructors).
|
||||
|
||||
/// The first page's content stream as text.
|
||||
fn content(bytes: &[u8]) -> String {
|
||||
let pdf = hayro_syntax::Pdf::new(Arc::new(bytes.to_vec())).unwrap();
|
||||
let page = pdf.pages().iter().next().unwrap();
|
||||
String::from_utf8_lossy(page.page_stream().unwrap()).into_owned()
|
||||
}
|
||||
|
||||
/// CMYK inks, gray, spot tints and DeviceN tints made with `new_f32` are written as given, so
|
||||
/// C75 is 0.75 and not 191/255; the 8-bit constructors still write `value / 255`.
|
||||
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), test)]
|
||||
fn f32_colors_are_written_without_rounding() {
|
||||
use backend::color::separation::{self, SeparationColorant, SeparationSpace};
|
||||
let gold_space = SeparationSpace::new(SeparationColorant::Custom("Gold".to_owned()), gold().into());
|
||||
let space = DeviceNSpace::new([Colorant::Process(Process::Cyan), spot("Gold", gold())]).unwrap();
|
||||
let colors: Vec<backend::color::Color> = vec![
|
||||
cmyk::Color::new_f32(0.75, 0.0, 1.0, 0.0).into(),
|
||||
cmyk::Color::new_f32(0.04, 0.04, 0.04, 0.04).into(),
|
||||
luma::Color::new_f32(0.96).into(),
|
||||
separation::Color::new_f32(0.75, gold_space).into(),
|
||||
device_n::Color::new_f32(vec![0.45, 0.04], space).unwrap().into(),
|
||||
cmyk::Color::new(191, 0, 255, 0).into(),
|
||||
];
|
||||
let text = content(&device_n_sample(cmyk_settings(), colors).unwrap());
|
||||
for op in ["0.75 0 1 0 k", "0.04 0.04 0.04 0.04 k", "0.96 g", "0.75 scn", "0.45 0.04 scn", "0.7490196 0 1 0 k"] {
|
||||
assert!(text.contains(op), "{op:?} in {text:?}");
|
||||
}
|
||||
}
|
||||
|
||||
/// `new_f32` clamps to 0 to 1 and reads NaN and -0 as 0, so a color equals (and hashes like)
|
||||
/// the 8-bit color with the same components.
|
||||
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), test)]
|
||||
fn f32_colors_clamp_and_compare_by_value() {
|
||||
use std::collections::HashSet;
|
||||
let odd = cmyk::Color::new_f32(1.5, -0.2, f32::NAN, -0.0);
|
||||
assert_eq!(odd, cmyk::Color::new(255, 0, 0, 0));
|
||||
assert_eq!(HashSet::from([odd, cmyk::Color::new(255, 0, 0, 0)]).len(), 1);
|
||||
assert_eq!(luma::Color::new_f32(2.0), luma::Color::white());
|
||||
let text = content(&page_with(|surface| fill_rect(surface, 0.0, odd.into())));
|
||||
assert!(text.contains("1 0 0 0 k"), "{text:?}");
|
||||
}
|
||||
|
||||
/// The colors of a shading's function (`/C0` and `/C1` of the first shading's exponential
|
||||
/// function).
|
||||
fn shading_ends(bytes: &[u8]) -> (Vec<f32>, Vec<f32>) {
|
||||
let pdf = hayro_syntax::Pdf::new(Arc::new(bytes.to_vec())).unwrap();
|
||||
let page = pdf.pages().iter().next().unwrap();
|
||||
let patterns = &page.resources().patterns;
|
||||
let key = patterns.keys().next().unwrap();
|
||||
let pattern = patterns.get::<Dict<'_>>(key.as_ref()).unwrap();
|
||||
let shading = pattern.get::<Dict<'_>>(b"Shading".as_slice()).unwrap();
|
||||
let function = shading.get::<Dict<'_>>(b"Function".as_slice()).unwrap();
|
||||
let floats = |key: &[u8]| -> Vec<f32> { function.get::<Array<'_>>(key).unwrap().iter::<f32>().collect() };
|
||||
(floats(b"C0"), floats(b"C1"))
|
||||
}
|
||||
|
||||
/// Gradient stops keep their inks when a spot and process colors are rewritten into one
|
||||
/// DeviceN space, and mesh corners are written at 16 bits, so K 75% reads 0.749996 rather than
|
||||
/// 191/255.
|
||||
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), test)]
|
||||
fn f32_colors_keep_their_inks_in_shadings() {
|
||||
use backend::color::separation::{self, SeparationColorant, SeparationSpace};
|
||||
let gold_spot: backend::color::Color =
|
||||
separation::Color::new_f32(0.75, SeparationSpace::new(SeparationColorant::Custom("Gold".to_owned()), gold().into())).into();
|
||||
let cyan: backend::color::Color = cmyk::Color::new_f32(0.04, 0.0, 0.0, 0.0).into();
|
||||
let bytes = page_with(|surface| fill_rect(surface, 0.0, linear([gold_spot, cyan])));
|
||||
assert_eq!(shadings(&bytes)[0].0, ("DeviceN".to_owned(), vec!["Cyan".to_owned(), "Gold".to_owned()]));
|
||||
assert_eq!(shading_ends(&bytes), (vec![0.0, 0.75], vec![0.04, 0.0]));
|
||||
|
||||
let k75 = || -> backend::color::Color { cmyk::Color::new_f32(0.0, 0.0, 0.0, 0.75).into() };
|
||||
let patch = square(10.0, 10.0, 40.0, [k75(), k75(), k75(), k75()], [1.0; 4]);
|
||||
let bytes = page_with(|surface| fill_rect(surface, 0.0, mesh(vec![patch])));
|
||||
let corner = [0, 0, 0, 49151];
|
||||
assert_eq!(mesh_shadings(&bytes), vec![(("DeviceCMYK".to_owned(), vec![]), 4, vec![corner.repeat(4)])]);
|
||||
}
|
||||
|
||||
const FOGRA39: &[u8] = include_bytes!("../../cms/profiles/craft-coated-fogra39.icc");
|
||||
|
||||
fn text(dict: &Dict<'_>, key: &[u8]) -> Option<String> {
|
||||
|
||||
Vendored
+20
@@ -26,6 +26,7 @@ itself omits those texts. Its examples and four CC0 ICC profiles are retained wi
|
||||
| `patches/14-devicen-pdf-version.patch` | DeviceN attributes by PDF version | `graphics/device_n.rs` |
|
||||
| `patches/15-pdfx-conformance.patch` | PDF/X conformance key | `serialize.rs` |
|
||||
| `patches/16-modification-date.patch` | Modification date | `interchange/metadata.rs` |
|
||||
| `patches/17-color-precision.patch` | Color precision | `graphics/{color,shading_function}.rs` |
|
||||
|
||||
Each patch applies with `patch -p1` from this directory, in numeric order, to the pristine
|
||||
crate. `SOURCE.json` records, for every retained file, the upstream hash (`upstream_sha256`)
|
||||
@@ -410,3 +411,22 @@ Tests: `crates/pdf/tests/writing.rs` (`writes_pages_metadata_and_selected_versio
|
||||
`the_creation_date_is_the_modification_date_when_none_is_given`) and
|
||||
`crates/prepress/tests/flatten_document.rs`
|
||||
(`a_document_flattens_page_by_page_and_keeps_boxes_metadata_and_shared_fonts`).
|
||||
|
||||
## 17: color precision
|
||||
|
||||
Upstream stores gray, CMYK, Separation and DeviceN components as `u8`, so every ink is written
|
||||
as a multiple of 1/255: C75 becomes 0.7490196 and C4 M4 Y4 K4 becomes 0.039215688.
|
||||
|
||||
- `cmyk::Color::new_f32`, `luma::Color::new_f32`, `separation::Color::new_f32` and
|
||||
`device_n::Color::new_f32` take components from 0 to 1 and write them as given. Values
|
||||
outside 0 to 1 are clamped and NaN is 0.
|
||||
- The `u8` constructors keep their signatures and write `value / 255`, as upstream does.
|
||||
- The colors store each component as an `f32` whose bits `Eq` and `Hash` compare; -0 is
|
||||
stored as 0, so equal components compare equal. `RegularColor::as_rgb` rounds gray to the
|
||||
nearest 8-bit value.
|
||||
- Gradient stops that are rewritten into one DeviceCMYK, Separation or DeviceN space keep
|
||||
their components, and a DeviceN color's CMYK alternate (annotations, tags) is no longer
|
||||
rounded.
|
||||
- Mesh gradients write their corner colors and opacities at 16 bits a component instead of 8.
|
||||
|
||||
Tests: `crates/pdf/tests/print_color.rs` (`f32_colors_*`, `mesh_*`, `rgb_mesh_is_a_device_rgb_shading`).
|
||||
|
||||
Vendored
+2
-2
@@ -152,7 +152,7 @@
|
||||
{
|
||||
"path": "src/graphics/color.rs",
|
||||
"upstream_sha256": "e5843d33ef184ab5c458025d1b1dc53df6ee6aaf085595ea14604d080b98f8a4",
|
||||
"sha256": "553c2c1ce7a7c26dc11a227d70db0e30933e0b0e69d2d6c550e1384c0a3037ec"
|
||||
"sha256": "5b0de85e0f3eac2905f975f676f1ef5d0eebb99a9a35670fefb419ed36fed6cf"
|
||||
},
|
||||
{
|
||||
"path": "src/graphics/device_n.rs",
|
||||
@@ -207,7 +207,7 @@
|
||||
{
|
||||
"path": "src/graphics/shading_function.rs",
|
||||
"upstream_sha256": "0e0fc8bb1a32272457ef560fa80933972d0f5ef5fb7046424c6de48093cfd978",
|
||||
"sha256": "2831ec0d78a755db9311a16d01afd5c9f714e4d43bdf69b42c0cb291387ca743"
|
||||
"sha256": "6da45a96a98d32d4ef878fa03ac1fddbccccea2e2d6333d3a0e90ca009061f81"
|
||||
},
|
||||
{
|
||||
"path": "src/graphics/shading_pattern.rs",
|
||||
|
||||
+411
@@ -0,0 +1,411 @@
|
||||
diff --git a/src/graphics/color.rs b/src/graphics/color.rs
|
||||
index 6fb0618..06a3098 100644
|
||||
--- a/src/graphics/color.rs
|
||||
+++ b/src/graphics/color.rs
|
||||
@@ -69,7 +69,7 @@
|
||||
//! [`SerializeSettings`]: crate::SerializeSettings
|
||||
|
||||
use std::fmt::Debug;
|
||||
-use std::hash::Hash;
|
||||
+use std::hash::{Hash, Hasher};
|
||||
|
||||
use crate::configure::ValidationError;
|
||||
use crate::graphics::icc::ICCBasedColorSpace;
|
||||
@@ -82,6 +82,48 @@ pub(crate) const DEVICE_GRAY: &str = "DeviceGray";
|
||||
/// The PDF name for the device CMYK color space.
|
||||
pub(crate) const DEVICE_CMYK: &str = "DeviceCMYK";
|
||||
|
||||
+/// A color component from 0 to 1: a gray level, a process ink or a spot tint.
|
||||
+///
|
||||
+/// `new` clamps the value to 0 to 1 and turns NaN and -0 into 0, so equal components have equal
|
||||
+/// bits; `Eq` and `Hash` compare the bits.
|
||||
+#[derive(Debug, Clone, Copy)]
|
||||
+pub(crate) struct Tint(f32);
|
||||
+
|
||||
+impl Tint {
|
||||
+ pub(crate) fn new(value: f32) -> Self {
|
||||
+ // `max` returns the other operand for NaN; adding 0 turns -0 into 0.
|
||||
+ Tint(value.max(0.0).min(1.0) + 0.0)
|
||||
+ }
|
||||
+
|
||||
+ /// The component `value / 255`, as the 8-bit constructors give it.
|
||||
+ pub(crate) fn from_u8(value: u8) -> Self {
|
||||
+ Tint(f32::from(value) / 255.0)
|
||||
+ }
|
||||
+
|
||||
+ pub(crate) fn get(self) -> f32 {
|
||||
+ self.0
|
||||
+ }
|
||||
+
|
||||
+ /// The nearest 8-bit value.
|
||||
+ pub(crate) fn to_u8(self) -> u8 {
|
||||
+ (self.0 * 255.0).round() as u8
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+impl PartialEq for Tint {
|
||||
+ fn eq(&self, other: &Self) -> bool {
|
||||
+ self.0.to_bits() == other.0.to_bits()
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+impl Eq for Tint {}
|
||||
+
|
||||
+impl Hash for Tint {
|
||||
+ fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
+ self.0.to_bits().hash(state);
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
/// A wrapper for storing colors from different color spaces.
|
||||
#[derive(Debug, Hash, Eq, PartialEq, Clone)]
|
||||
pub enum Color {
|
||||
@@ -160,7 +202,10 @@ impl RegularColor {
|
||||
pub(crate) fn as_rgb(self) -> Option<rgb::Color> {
|
||||
Some(match self {
|
||||
Self::Rgb(r) => r,
|
||||
- Self::Luma(l) => rgb::Color::new(l.0, l.0, l.0),
|
||||
+ Self::Luma(l) => {
|
||||
+ let v = l.0.to_u8();
|
||||
+ rgb::Color::new(v, v, v)
|
||||
+ }
|
||||
Self::Cmyk(_) => return None,
|
||||
})
|
||||
}
|
||||
@@ -183,16 +228,24 @@ impl SpecialColor {
|
||||
|
||||
/// Gray-scale colors.
|
||||
pub mod luma {
|
||||
- use crate::color::{CieBasedColorSpace, DeviceColorSpace, RegularColor, RegularColorSpace};
|
||||
+ use crate::color::{
|
||||
+ CieBasedColorSpace, DeviceColorSpace, RegularColor, RegularColorSpace, Tint,
|
||||
+ };
|
||||
|
||||
/// A luma color.
|
||||
#[derive(Debug, Hash, Eq, PartialEq, Clone, Copy)]
|
||||
- pub struct Color(pub(crate) u8);
|
||||
+ pub struct Color(pub(crate) Tint);
|
||||
|
||||
impl Color {
|
||||
/// Create a new luma color.
|
||||
pub fn new(lightness: u8) -> Color {
|
||||
- Color(lightness)
|
||||
+ Color(Tint::from_u8(lightness))
|
||||
+ }
|
||||
+
|
||||
+ /// Create a luma color from a lightness from 0 (black) to 1 (white), written to the
|
||||
+ /// PDF without rounding. Values outside 0 to 1 are clamped, and NaN is 0.
|
||||
+ pub fn new_f32(lightness: f32) -> Color {
|
||||
+ Color(Tint::new(lightness))
|
||||
}
|
||||
|
||||
/// Create a black luma color.
|
||||
@@ -206,7 +259,7 @@ pub mod luma {
|
||||
}
|
||||
|
||||
pub(crate) fn to_pdf_color(self) -> f32 {
|
||||
- self.0 as f32 / 255.0
|
||||
+ self.0.get()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,27 +304,43 @@ impl From<SpecialColor> for Color {
|
||||
|
||||
/// CMYK colors.
|
||||
pub mod cmyk {
|
||||
- use crate::color::{CieBasedColorSpace, DeviceColorSpace, RegularColorSpace};
|
||||
+ use crate::color::{CieBasedColorSpace, DeviceColorSpace, RegularColorSpace, Tint};
|
||||
use crate::graphics::icc::ICCBasedColorSpace;
|
||||
use crate::SerializeSettings;
|
||||
|
||||
/// A CMYK color.
|
||||
#[derive(Debug, Hash, Eq, PartialEq, Clone, Copy)]
|
||||
- pub struct Color(pub(crate) u8, pub(crate) u8, pub(crate) u8, pub(crate) u8);
|
||||
+ pub struct Color(
|
||||
+ pub(crate) Tint,
|
||||
+ pub(crate) Tint,
|
||||
+ pub(crate) Tint,
|
||||
+ pub(crate) Tint,
|
||||
+ );
|
||||
|
||||
impl Color {
|
||||
/// Create a new CMYK color.
|
||||
pub fn new(cyan: u8, magenta: u8, yellow: u8, black: u8) -> Color {
|
||||
- Color(cyan, magenta, yellow, black)
|
||||
+ Color(
|
||||
+ Tint::from_u8(cyan),
|
||||
+ Tint::from_u8(magenta),
|
||||
+ Tint::from_u8(yellow),
|
||||
+ Tint::from_u8(black),
|
||||
+ )
|
||||
+ }
|
||||
+
|
||||
+ /// Create a CMYK color from inks from 0 to 1, written to the PDF without rounding.
|
||||
+ /// Values outside 0 to 1 are clamped, and NaN is 0.
|
||||
+ pub fn new_f32(cyan: f32, magenta: f32, yellow: f32, black: f32) -> Color {
|
||||
+ Color(
|
||||
+ Tint::new(cyan),
|
||||
+ Tint::new(magenta),
|
||||
+ Tint::new(yellow),
|
||||
+ Tint::new(black),
|
||||
+ )
|
||||
}
|
||||
|
||||
pub(crate) fn to_pdf_color(self) -> [f32; 4] {
|
||||
- [
|
||||
- self.0 as f32 / 255.0,
|
||||
- self.1 as f32 / 255.0,
|
||||
- self.2 as f32 / 255.0,
|
||||
- self.3 as f32 / 255.0,
|
||||
- ]
|
||||
+ [self.0.get(), self.1.get(), self.2.get(), self.3.get()]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -390,23 +459,35 @@ pub mod rgb {
|
||||
|
||||
/// Separation (spot) colors.
|
||||
pub mod separation {
|
||||
- use crate::color::RegularColor;
|
||||
+ use crate::color::{RegularColor, Tint};
|
||||
|
||||
/// A spot color.
|
||||
#[derive(Debug, Hash, Eq, PartialEq, Clone)]
|
||||
pub struct Color {
|
||||
- pub(crate) tint: u8,
|
||||
+ pub(crate) tint: Tint,
|
||||
pub(crate) space: SeparationSpace,
|
||||
}
|
||||
|
||||
impl Color {
|
||||
/// Create a new spot color.
|
||||
pub fn new(tint: u8, space: SeparationSpace) -> Self {
|
||||
- Self { tint, space }
|
||||
+ Self {
|
||||
+ tint: Tint::from_u8(tint),
|
||||
+ space,
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ /// Create a spot color with a tint from 0 to 1, written to the PDF without rounding.
|
||||
+ /// A tint outside 0 to 1 is clamped, and NaN is 0.
|
||||
+ pub fn new_f32(tint: f32, space: SeparationSpace) -> Self {
|
||||
+ Self {
|
||||
+ tint: Tint::new(tint),
|
||||
+ space,
|
||||
+ }
|
||||
}
|
||||
|
||||
pub(crate) fn to_pdf_color(&self) -> f32 {
|
||||
- self.tint as f32 / 255.0
|
||||
+ self.tint.get()
|
||||
}
|
||||
|
||||
pub(crate) fn color_space(&self) -> SeparationSpace {
|
||||
@@ -498,7 +579,7 @@ pub mod separation {
|
||||
pub mod device_n {
|
||||
use std::sync::Arc;
|
||||
|
||||
- use crate::color::cmyk;
|
||||
+ use crate::color::{cmyk, Tint};
|
||||
|
||||
/// A process colorant.
|
||||
#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)]
|
||||
@@ -656,7 +737,7 @@ pub mod device_n {
|
||||
/// A DeviceN color: one tint per colorant of its space.
|
||||
#[derive(Debug, Hash, Eq, PartialEq, Clone)]
|
||||
pub struct Color {
|
||||
- pub(crate) tints: Vec<u8>,
|
||||
+ pub(crate) tints: Vec<Tint>,
|
||||
pub(crate) space: DeviceNSpace,
|
||||
}
|
||||
|
||||
@@ -666,20 +747,30 @@ pub mod device_n {
|
||||
///
|
||||
/// Returns `None` unless there is one tint per colorant.
|
||||
pub fn new(tints: Vec<u8>, space: DeviceNSpace) -> Option<Self> {
|
||||
+ Self::new_f32(
|
||||
+ tints.into_iter().map(|t| Tint::from_u8(t).get()).collect(),
|
||||
+ space,
|
||||
+ )
|
||||
+ }
|
||||
+
|
||||
+ /// Create a DeviceN color with tints from 0 (no ink) to 1 (full ink), in the order
|
||||
+ /// of the space's colorants, written to the PDF without rounding. Tints outside 0
|
||||
+ /// to 1 are clamped, and NaN is 0.
|
||||
+ ///
|
||||
+ /// Returns `None` unless there is one tint per colorant.
|
||||
+ pub fn new_f32(tints: Vec<f32>, space: DeviceNSpace) -> Option<Self> {
|
||||
+ let tints: Vec<Tint> = tints.into_iter().map(Tint::new).collect();
|
||||
(tints.len() == space.colorants.len()).then_some(Self { tints, space })
|
||||
}
|
||||
|
||||
pub(crate) fn to_pdf_color(&self) -> Vec<f32> {
|
||||
- self.tints.iter().map(|t| *t as f32 / 255.0).collect()
|
||||
+ self.tints.iter().map(|t| t.get()).collect()
|
||||
}
|
||||
|
||||
/// The color in the alternate CMYK space.
|
||||
pub(crate) fn alternate(&self) -> cmyk::Color {
|
||||
- let [c, m, y, k] = self
|
||||
- .space
|
||||
- .alternate(&self.to_pdf_color())
|
||||
- .map(|v| (v * 255.0).round().clamp(0.0, 255.0) as u8);
|
||||
- cmyk::Color::new(c, m, y, k)
|
||||
+ let [c, m, y, k] = self.space.alternate(&self.to_pdf_color());
|
||||
+ cmyk::Color::new_f32(c, m, y, k)
|
||||
}
|
||||
}
|
||||
|
||||
diff --git a/src/graphics/shading_function.rs b/src/graphics/shading_function.rs
|
||||
index 74c56f3..8aae81a 100644
|
||||
--- a/src/graphics/shading_function.rs
|
||||
+++ b/src/graphics/shading_function.rs
|
||||
@@ -277,12 +277,12 @@ impl GradientProperties {
|
||||
}
|
||||
}
|
||||
|
||||
-/// The inks of one gradient color: process tints and spot tints (0–255).
|
||||
+/// The inks of one gradient color: process tints and spot tints (0–1).
|
||||
struct Inks {
|
||||
- process: [u8; 4],
|
||||
+ process: [f32; 4],
|
||||
/// Process colorants a DeviceN color names, even at zero tint.
|
||||
named_process: [bool; 4],
|
||||
- spots: Vec<(String, u8)>,
|
||||
+ spots: Vec<(String, f32)>,
|
||||
}
|
||||
|
||||
/// A spot named by a gradient color: the Separation space a stop used, and the CMYK
|
||||
@@ -353,13 +353,13 @@ pub(crate) fn common_color_space(colors: &[Color]) -> Option<Vec<Color>> {
|
||||
let mut all_inks = Vec::with_capacity(colors.len());
|
||||
for color in colors {
|
||||
let mut inks = Inks {
|
||||
- process: [0; 4],
|
||||
+ process: [0.0; 4],
|
||||
named_process: [false; 4],
|
||||
spots: Vec::new(),
|
||||
};
|
||||
match color {
|
||||
- Color::Regular(RegularColor::Cmyk(c)) => inks.process = [c.0, c.1, c.2, c.3],
|
||||
- Color::Regular(RegularColor::Luma(l)) => inks.process[3] = 255 - l.0,
|
||||
+ Color::Regular(RegularColor::Cmyk(c)) => inks.process = c.to_pdf_color(),
|
||||
+ Color::Regular(RegularColor::Luma(l)) => inks.process[3] = 1.0 - l.to_pdf_color(),
|
||||
Color::Regular(RegularColor::Rgb(_)) => return None,
|
||||
Color::Special(SpecialColor::Separation(s)) => {
|
||||
let SeparationColorant::Custom(name) = &s.space.colorant else {
|
||||
@@ -370,19 +370,19 @@ pub(crate) fn common_color_space(colors: &[Color]) -> Option<Vec<Color>> {
|
||||
_ => None,
|
||||
};
|
||||
add_spot(&mut spot_inks, name, Some(&s.space), alternate);
|
||||
- inks.spots.push((name.clone(), s.tint));
|
||||
+ inks.spots.push((name.clone(), s.tint.get()));
|
||||
}
|
||||
Color::Special(SpecialColor::DeviceN(c)) => {
|
||||
for (colorant, tint) in c.space.colorants().iter().zip(&c.tints) {
|
||||
match colorant {
|
||||
Colorant::Process(p) => {
|
||||
let i = Process::ALL.iter().position(|q| q == p).unwrap_or(0);
|
||||
- inks.process[i] = *tint;
|
||||
+ inks.process[i] = tint.get();
|
||||
inks.named_process[i] = true;
|
||||
}
|
||||
Colorant::Spot { name, alternate } => {
|
||||
add_spot(&mut spot_inks, name, None, Some(*alternate));
|
||||
- inks.spots.push((name.clone(), *tint));
|
||||
+ inks.spots.push((name.clone(), tint.get()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -395,14 +395,14 @@ pub(crate) fn common_color_space(colors: &[Color]) -> Option<Vec<Color>> {
|
||||
.filter(|&i| {
|
||||
all_inks
|
||||
.iter()
|
||||
- .any(|inks| inks.process[i] > 0 || inks.named_process[i])
|
||||
+ .any(|inks| inks.process[i] > 0.0 || inks.named_process[i])
|
||||
})
|
||||
.collect();
|
||||
let spot_tint = |inks: &Inks, name: &str| {
|
||||
inks.spots
|
||||
.iter()
|
||||
.find(|(n, _)| n == name)
|
||||
- .map_or(0, |(_, t)| *t)
|
||||
+ .map_or(0.0, |(_, t)| *t)
|
||||
};
|
||||
|
||||
match (process.as_slice(), spot_inks.as_slice()) {
|
||||
@@ -411,7 +411,7 @@ pub(crate) fn common_color_space(colors: &[Color]) -> Option<Vec<Color>> {
|
||||
.iter()
|
||||
.map(|inks| {
|
||||
let [c, m, y, k] = inks.process;
|
||||
- cmyk::Color::new(c, m, y, k).into()
|
||||
+ cmyk::Color::new_f32(c, m, y, k).into()
|
||||
})
|
||||
.collect(),
|
||||
),
|
||||
@@ -428,7 +428,8 @@ pub(crate) fn common_color_space(colors: &[Color]) -> Option<Vec<Color>> {
|
||||
all_inks
|
||||
.iter()
|
||||
.map(|inks| {
|
||||
- separation::Color::new(spot_tint(inks, &spot.name), space.clone()).into()
|
||||
+ separation::Color::new_f32(spot_tint(inks, &spot.name), space.clone())
|
||||
+ .into()
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
@@ -448,9 +449,9 @@ pub(crate) fn common_color_space(colors: &[Color]) -> Option<Vec<Color>> {
|
||||
all_inks
|
||||
.iter()
|
||||
.map(|inks| {
|
||||
- let mut tints: Vec<u8> = process.iter().map(|&i| inks.process[i]).collect();
|
||||
+ let mut tints: Vec<f32> = process.iter().map(|&i| inks.process[i]).collect();
|
||||
tints.extend(spot_inks.iter().map(|s| spot_tint(inks, &s.name)));
|
||||
- device_n::Color::new(tints, space.clone()).map(Color::from)
|
||||
+ device_n::Color::new_f32(tints, space.clone()).map(Color::from)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
@@ -547,7 +548,7 @@ fn shading_color_space(sc: &mut SerializeContext, color: Color, use_opacities: b
|
||||
|
||||
/// A mesh gradient as a Type 6 (Coons patch mesh) shading: per patch an 8-bit edge flag (0, no
|
||||
/// shared edge), the twelve points as 32-bit coordinates over the mesh's bounds (`Decode`), and
|
||||
-/// the four corner colors (with `use_opacities`, the opacities as gray) at 8 bits a component,
|
||||
+/// the four corner colors (with `use_opacities`, the opacities as gray) at 16 bits a component,
|
||||
/// Flate-compressed. 32-bit coordinates keep the patches where they are to well under a printer
|
||||
/// dot, as Illustrator writes its meshes.
|
||||
fn serialize_mesh_shading(
|
||||
@@ -584,8 +585,8 @@ fn serialize_mesh_shading(
|
||||
* f64::from(u32::MAX))
|
||||
.round() as u32
|
||||
};
|
||||
- let byte = |v: f32| (v.clamp(0.0, 1.0) * 255.0).round() as u8;
|
||||
- let mut data = Vec::with_capacity(mesh.patches.len() * (1 + 12 * 8 + 4 * components));
|
||||
+ let component = |v: f32| ((v.clamp(0.0, 1.0) * 65535.0).round() as u16).to_be_bytes();
|
||||
+ let mut data = Vec::with_capacity(mesh.patches.len() * (1 + 12 * 8 + 4 * 2 * components));
|
||||
for patch in &mesh.patches {
|
||||
data.push(0u8);
|
||||
for (x, y) in &patch.points {
|
||||
@@ -594,12 +595,12 @@ fn serialize_mesh_shading(
|
||||
}
|
||||
for (color, opacity) in patch.colors.iter().zip(patch.opacities.iter()) {
|
||||
if use_opacities {
|
||||
- data.push(byte(opacity.get()));
|
||||
+ data.extend(component(opacity.get()));
|
||||
} else {
|
||||
let values = color.to_pdf_color();
|
||||
// Mixed spaces fail the document (`mixed_mesh_colors`); still never misread bytes.
|
||||
for i in 0..components {
|
||||
- data.push(byte(values.get(i).copied().unwrap_or(0.0)));
|
||||
+ data.extend(component(values.get(i).copied().unwrap_or(0.0)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -611,7 +612,7 @@ fn serialize_mesh_shading(
|
||||
shading.shading_type(StreamShadingType::CoonsPatch);
|
||||
set_colorspace(cs, shading.deref_mut().deref_mut());
|
||||
shading.bits_per_coordinate(32);
|
||||
- shading.bits_per_component(8);
|
||||
+ shading.bits_per_component(16);
|
||||
shading.bits_per_flag(8);
|
||||
let mut decode = vec![x0, x1, y0, y1];
|
||||
for _ in 0..components {
|
||||
Vendored
+118
-27
@@ -69,7 +69,7 @@
|
||||
//! [`SerializeSettings`]: crate::SerializeSettings
|
||||
|
||||
use std::fmt::Debug;
|
||||
use std::hash::Hash;
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
use crate::configure::ValidationError;
|
||||
use crate::graphics::icc::ICCBasedColorSpace;
|
||||
@@ -82,6 +82,48 @@ pub(crate) const DEVICE_GRAY: &str = "DeviceGray";
|
||||
/// The PDF name for the device CMYK color space.
|
||||
pub(crate) const DEVICE_CMYK: &str = "DeviceCMYK";
|
||||
|
||||
/// A color component from 0 to 1: a gray level, a process ink or a spot tint.
|
||||
///
|
||||
/// `new` clamps the value to 0 to 1 and turns NaN and -0 into 0, so equal components have equal
|
||||
/// bits; `Eq` and `Hash` compare the bits.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(crate) struct Tint(f32);
|
||||
|
||||
impl Tint {
|
||||
pub(crate) fn new(value: f32) -> Self {
|
||||
// `max` returns the other operand for NaN; adding 0 turns -0 into 0.
|
||||
Tint(value.max(0.0).min(1.0) + 0.0)
|
||||
}
|
||||
|
||||
/// The component `value / 255`, as the 8-bit constructors give it.
|
||||
pub(crate) fn from_u8(value: u8) -> Self {
|
||||
Tint(f32::from(value) / 255.0)
|
||||
}
|
||||
|
||||
pub(crate) fn get(self) -> f32 {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// The nearest 8-bit value.
|
||||
pub(crate) fn to_u8(self) -> u8 {
|
||||
(self.0 * 255.0).round() as u8
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Tint {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.0.to_bits() == other.0.to_bits()
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Tint {}
|
||||
|
||||
impl Hash for Tint {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.0.to_bits().hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper for storing colors from different color spaces.
|
||||
#[derive(Debug, Hash, Eq, PartialEq, Clone)]
|
||||
pub enum Color {
|
||||
@@ -160,7 +202,10 @@ impl RegularColor {
|
||||
pub(crate) fn as_rgb(self) -> Option<rgb::Color> {
|
||||
Some(match self {
|
||||
Self::Rgb(r) => r,
|
||||
Self::Luma(l) => rgb::Color::new(l.0, l.0, l.0),
|
||||
Self::Luma(l) => {
|
||||
let v = l.0.to_u8();
|
||||
rgb::Color::new(v, v, v)
|
||||
}
|
||||
Self::Cmyk(_) => return None,
|
||||
})
|
||||
}
|
||||
@@ -183,16 +228,24 @@ impl SpecialColor {
|
||||
|
||||
/// Gray-scale colors.
|
||||
pub mod luma {
|
||||
use crate::color::{CieBasedColorSpace, DeviceColorSpace, RegularColor, RegularColorSpace};
|
||||
use crate::color::{
|
||||
CieBasedColorSpace, DeviceColorSpace, RegularColor, RegularColorSpace, Tint,
|
||||
};
|
||||
|
||||
/// A luma color.
|
||||
#[derive(Debug, Hash, Eq, PartialEq, Clone, Copy)]
|
||||
pub struct Color(pub(crate) u8);
|
||||
pub struct Color(pub(crate) Tint);
|
||||
|
||||
impl Color {
|
||||
/// Create a new luma color.
|
||||
pub fn new(lightness: u8) -> Color {
|
||||
Color(lightness)
|
||||
Color(Tint::from_u8(lightness))
|
||||
}
|
||||
|
||||
/// Create a luma color from a lightness from 0 (black) to 1 (white), written to the
|
||||
/// PDF without rounding. Values outside 0 to 1 are clamped, and NaN is 0.
|
||||
pub fn new_f32(lightness: f32) -> Color {
|
||||
Color(Tint::new(lightness))
|
||||
}
|
||||
|
||||
/// Create a black luma color.
|
||||
@@ -206,7 +259,7 @@ pub mod luma {
|
||||
}
|
||||
|
||||
pub(crate) fn to_pdf_color(self) -> f32 {
|
||||
self.0 as f32 / 255.0
|
||||
self.0.get()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,27 +304,43 @@ impl From<SpecialColor> for Color {
|
||||
|
||||
/// CMYK colors.
|
||||
pub mod cmyk {
|
||||
use crate::color::{CieBasedColorSpace, DeviceColorSpace, RegularColorSpace};
|
||||
use crate::color::{CieBasedColorSpace, DeviceColorSpace, RegularColorSpace, Tint};
|
||||
use crate::graphics::icc::ICCBasedColorSpace;
|
||||
use crate::SerializeSettings;
|
||||
|
||||
/// A CMYK color.
|
||||
#[derive(Debug, Hash, Eq, PartialEq, Clone, Copy)]
|
||||
pub struct Color(pub(crate) u8, pub(crate) u8, pub(crate) u8, pub(crate) u8);
|
||||
pub struct Color(
|
||||
pub(crate) Tint,
|
||||
pub(crate) Tint,
|
||||
pub(crate) Tint,
|
||||
pub(crate) Tint,
|
||||
);
|
||||
|
||||
impl Color {
|
||||
/// Create a new CMYK color.
|
||||
pub fn new(cyan: u8, magenta: u8, yellow: u8, black: u8) -> Color {
|
||||
Color(cyan, magenta, yellow, black)
|
||||
Color(
|
||||
Tint::from_u8(cyan),
|
||||
Tint::from_u8(magenta),
|
||||
Tint::from_u8(yellow),
|
||||
Tint::from_u8(black),
|
||||
)
|
||||
}
|
||||
|
||||
/// Create a CMYK color from inks from 0 to 1, written to the PDF without rounding.
|
||||
/// Values outside 0 to 1 are clamped, and NaN is 0.
|
||||
pub fn new_f32(cyan: f32, magenta: f32, yellow: f32, black: f32) -> Color {
|
||||
Color(
|
||||
Tint::new(cyan),
|
||||
Tint::new(magenta),
|
||||
Tint::new(yellow),
|
||||
Tint::new(black),
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn to_pdf_color(self) -> [f32; 4] {
|
||||
[
|
||||
self.0 as f32 / 255.0,
|
||||
self.1 as f32 / 255.0,
|
||||
self.2 as f32 / 255.0,
|
||||
self.3 as f32 / 255.0,
|
||||
]
|
||||
[self.0.get(), self.1.get(), self.2.get(), self.3.get()]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -390,23 +459,35 @@ pub mod rgb {
|
||||
|
||||
/// Separation (spot) colors.
|
||||
pub mod separation {
|
||||
use crate::color::RegularColor;
|
||||
use crate::color::{RegularColor, Tint};
|
||||
|
||||
/// A spot color.
|
||||
#[derive(Debug, Hash, Eq, PartialEq, Clone)]
|
||||
pub struct Color {
|
||||
pub(crate) tint: u8,
|
||||
pub(crate) tint: Tint,
|
||||
pub(crate) space: SeparationSpace,
|
||||
}
|
||||
|
||||
impl Color {
|
||||
/// Create a new spot color.
|
||||
pub fn new(tint: u8, space: SeparationSpace) -> Self {
|
||||
Self { tint, space }
|
||||
Self {
|
||||
tint: Tint::from_u8(tint),
|
||||
space,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a spot color with a tint from 0 to 1, written to the PDF without rounding.
|
||||
/// A tint outside 0 to 1 is clamped, and NaN is 0.
|
||||
pub fn new_f32(tint: f32, space: SeparationSpace) -> Self {
|
||||
Self {
|
||||
tint: Tint::new(tint),
|
||||
space,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn to_pdf_color(&self) -> f32 {
|
||||
self.tint as f32 / 255.0
|
||||
self.tint.get()
|
||||
}
|
||||
|
||||
pub(crate) fn color_space(&self) -> SeparationSpace {
|
||||
@@ -498,7 +579,7 @@ pub mod separation {
|
||||
pub mod device_n {
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::color::cmyk;
|
||||
use crate::color::{cmyk, Tint};
|
||||
|
||||
/// A process colorant.
|
||||
#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)]
|
||||
@@ -656,7 +737,7 @@ pub mod device_n {
|
||||
/// A DeviceN color: one tint per colorant of its space.
|
||||
#[derive(Debug, Hash, Eq, PartialEq, Clone)]
|
||||
pub struct Color {
|
||||
pub(crate) tints: Vec<u8>,
|
||||
pub(crate) tints: Vec<Tint>,
|
||||
pub(crate) space: DeviceNSpace,
|
||||
}
|
||||
|
||||
@@ -666,20 +747,30 @@ pub mod device_n {
|
||||
///
|
||||
/// Returns `None` unless there is one tint per colorant.
|
||||
pub fn new(tints: Vec<u8>, space: DeviceNSpace) -> Option<Self> {
|
||||
Self::new_f32(
|
||||
tints.into_iter().map(|t| Tint::from_u8(t).get()).collect(),
|
||||
space,
|
||||
)
|
||||
}
|
||||
|
||||
/// Create a DeviceN color with tints from 0 (no ink) to 1 (full ink), in the order
|
||||
/// of the space's colorants, written to the PDF without rounding. Tints outside 0
|
||||
/// to 1 are clamped, and NaN is 0.
|
||||
///
|
||||
/// Returns `None` unless there is one tint per colorant.
|
||||
pub fn new_f32(tints: Vec<f32>, space: DeviceNSpace) -> Option<Self> {
|
||||
let tints: Vec<Tint> = tints.into_iter().map(Tint::new).collect();
|
||||
(tints.len() == space.colorants.len()).then_some(Self { tints, space })
|
||||
}
|
||||
|
||||
pub(crate) fn to_pdf_color(&self) -> Vec<f32> {
|
||||
self.tints.iter().map(|t| *t as f32 / 255.0).collect()
|
||||
self.tints.iter().map(|t| t.get()).collect()
|
||||
}
|
||||
|
||||
/// The color in the alternate CMYK space.
|
||||
pub(crate) fn alternate(&self) -> cmyk::Color {
|
||||
let [c, m, y, k] = self
|
||||
.space
|
||||
.alternate(&self.to_pdf_color())
|
||||
.map(|v| (v * 255.0).round().clamp(0.0, 255.0) as u8);
|
||||
cmyk::Color::new(c, m, y, k)
|
||||
let [c, m, y, k] = self.space.alternate(&self.to_pdf_color());
|
||||
cmyk::Color::new_f32(c, m, y, k)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+22
-21
@@ -277,12 +277,12 @@ impl GradientProperties {
|
||||
}
|
||||
}
|
||||
|
||||
/// The inks of one gradient color: process tints and spot tints (0–255).
|
||||
/// The inks of one gradient color: process tints and spot tints (0–1).
|
||||
struct Inks {
|
||||
process: [u8; 4],
|
||||
process: [f32; 4],
|
||||
/// Process colorants a DeviceN color names, even at zero tint.
|
||||
named_process: [bool; 4],
|
||||
spots: Vec<(String, u8)>,
|
||||
spots: Vec<(String, f32)>,
|
||||
}
|
||||
|
||||
/// A spot named by a gradient color: the Separation space a stop used, and the CMYK
|
||||
@@ -353,13 +353,13 @@ pub(crate) fn common_color_space(colors: &[Color]) -> Option<Vec<Color>> {
|
||||
let mut all_inks = Vec::with_capacity(colors.len());
|
||||
for color in colors {
|
||||
let mut inks = Inks {
|
||||
process: [0; 4],
|
||||
process: [0.0; 4],
|
||||
named_process: [false; 4],
|
||||
spots: Vec::new(),
|
||||
};
|
||||
match color {
|
||||
Color::Regular(RegularColor::Cmyk(c)) => inks.process = [c.0, c.1, c.2, c.3],
|
||||
Color::Regular(RegularColor::Luma(l)) => inks.process[3] = 255 - l.0,
|
||||
Color::Regular(RegularColor::Cmyk(c)) => inks.process = c.to_pdf_color(),
|
||||
Color::Regular(RegularColor::Luma(l)) => inks.process[3] = 1.0 - l.to_pdf_color(),
|
||||
Color::Regular(RegularColor::Rgb(_)) => return None,
|
||||
Color::Special(SpecialColor::Separation(s)) => {
|
||||
let SeparationColorant::Custom(name) = &s.space.colorant else {
|
||||
@@ -370,19 +370,19 @@ pub(crate) fn common_color_space(colors: &[Color]) -> Option<Vec<Color>> {
|
||||
_ => None,
|
||||
};
|
||||
add_spot(&mut spot_inks, name, Some(&s.space), alternate);
|
||||
inks.spots.push((name.clone(), s.tint));
|
||||
inks.spots.push((name.clone(), s.tint.get()));
|
||||
}
|
||||
Color::Special(SpecialColor::DeviceN(c)) => {
|
||||
for (colorant, tint) in c.space.colorants().iter().zip(&c.tints) {
|
||||
match colorant {
|
||||
Colorant::Process(p) => {
|
||||
let i = Process::ALL.iter().position(|q| q == p).unwrap_or(0);
|
||||
inks.process[i] = *tint;
|
||||
inks.process[i] = tint.get();
|
||||
inks.named_process[i] = true;
|
||||
}
|
||||
Colorant::Spot { name, alternate } => {
|
||||
add_spot(&mut spot_inks, name, None, Some(*alternate));
|
||||
inks.spots.push((name.clone(), *tint));
|
||||
inks.spots.push((name.clone(), tint.get()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -395,14 +395,14 @@ pub(crate) fn common_color_space(colors: &[Color]) -> Option<Vec<Color>> {
|
||||
.filter(|&i| {
|
||||
all_inks
|
||||
.iter()
|
||||
.any(|inks| inks.process[i] > 0 || inks.named_process[i])
|
||||
.any(|inks| inks.process[i] > 0.0 || inks.named_process[i])
|
||||
})
|
||||
.collect();
|
||||
let spot_tint = |inks: &Inks, name: &str| {
|
||||
inks.spots
|
||||
.iter()
|
||||
.find(|(n, _)| n == name)
|
||||
.map_or(0, |(_, t)| *t)
|
||||
.map_or(0.0, |(_, t)| *t)
|
||||
};
|
||||
|
||||
match (process.as_slice(), spot_inks.as_slice()) {
|
||||
@@ -411,7 +411,7 @@ pub(crate) fn common_color_space(colors: &[Color]) -> Option<Vec<Color>> {
|
||||
.iter()
|
||||
.map(|inks| {
|
||||
let [c, m, y, k] = inks.process;
|
||||
cmyk::Color::new(c, m, y, k).into()
|
||||
cmyk::Color::new_f32(c, m, y, k).into()
|
||||
})
|
||||
.collect(),
|
||||
),
|
||||
@@ -428,7 +428,8 @@ pub(crate) fn common_color_space(colors: &[Color]) -> Option<Vec<Color>> {
|
||||
all_inks
|
||||
.iter()
|
||||
.map(|inks| {
|
||||
separation::Color::new(spot_tint(inks, &spot.name), space.clone()).into()
|
||||
separation::Color::new_f32(spot_tint(inks, &spot.name), space.clone())
|
||||
.into()
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
@@ -448,9 +449,9 @@ pub(crate) fn common_color_space(colors: &[Color]) -> Option<Vec<Color>> {
|
||||
all_inks
|
||||
.iter()
|
||||
.map(|inks| {
|
||||
let mut tints: Vec<u8> = process.iter().map(|&i| inks.process[i]).collect();
|
||||
let mut tints: Vec<f32> = process.iter().map(|&i| inks.process[i]).collect();
|
||||
tints.extend(spot_inks.iter().map(|s| spot_tint(inks, &s.name)));
|
||||
device_n::Color::new(tints, space.clone()).map(Color::from)
|
||||
device_n::Color::new_f32(tints, space.clone()).map(Color::from)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
@@ -547,7 +548,7 @@ fn shading_color_space(sc: &mut SerializeContext, color: Color, use_opacities: b
|
||||
|
||||
/// A mesh gradient as a Type 6 (Coons patch mesh) shading: per patch an 8-bit edge flag (0, no
|
||||
/// shared edge), the twelve points as 32-bit coordinates over the mesh's bounds (`Decode`), and
|
||||
/// the four corner colors (with `use_opacities`, the opacities as gray) at 8 bits a component,
|
||||
/// the four corner colors (with `use_opacities`, the opacities as gray) at 16 bits a component,
|
||||
/// Flate-compressed. 32-bit coordinates keep the patches where they are to well under a printer
|
||||
/// dot, as Illustrator writes its meshes.
|
||||
fn serialize_mesh_shading(
|
||||
@@ -584,8 +585,8 @@ fn serialize_mesh_shading(
|
||||
* f64::from(u32::MAX))
|
||||
.round() as u32
|
||||
};
|
||||
let byte = |v: f32| (v.clamp(0.0, 1.0) * 255.0).round() as u8;
|
||||
let mut data = Vec::with_capacity(mesh.patches.len() * (1 + 12 * 8 + 4 * components));
|
||||
let component = |v: f32| ((v.clamp(0.0, 1.0) * 65535.0).round() as u16).to_be_bytes();
|
||||
let mut data = Vec::with_capacity(mesh.patches.len() * (1 + 12 * 8 + 4 * 2 * components));
|
||||
for patch in &mesh.patches {
|
||||
data.push(0u8);
|
||||
for (x, y) in &patch.points {
|
||||
@@ -594,12 +595,12 @@ fn serialize_mesh_shading(
|
||||
}
|
||||
for (color, opacity) in patch.colors.iter().zip(patch.opacities.iter()) {
|
||||
if use_opacities {
|
||||
data.push(byte(opacity.get()));
|
||||
data.extend(component(opacity.get()));
|
||||
} else {
|
||||
let values = color.to_pdf_color();
|
||||
// Mixed spaces fail the document (`mixed_mesh_colors`); still never misread bytes.
|
||||
for i in 0..components {
|
||||
data.push(byte(values.get(i).copied().unwrap_or(0.0)));
|
||||
data.extend(component(values.get(i).copied().unwrap_or(0.0)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -611,7 +612,7 @@ fn serialize_mesh_shading(
|
||||
shading.shading_type(StreamShadingType::CoonsPatch);
|
||||
set_colorspace(cs, shading.deref_mut().deref_mut());
|
||||
shading.bits_per_coordinate(32);
|
||||
shading.bits_per_component(8);
|
||||
shading.bits_per_component(16);
|
||||
shading.bits_per_flag(8);
|
||||
let mut decode = vec![x0, x1, y0, y1];
|
||||
for _ in 0..components {
|
||||
|
||||
Reference in New Issue
Block a user