mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-24 10:08:00 -06:00
The PDF/A device-colour scan split content streams only on spaces and line feeds, so `0 0 0 1 k` written with carriage returns, tabs, form feeds or NULs between the tokens went unseen: verify reported no DeviceCMYK, and convert gave the CMYK page an sRGB output intent. Split on all six whitespace characters of ISO 32000-2 §7.2.3, using the lexer's own `is_whitespace` (now exported from pdfcraft-cos) instead of a second list. Adds tests::device_colour_is_found_after_any_whitespace, which fails before this change; its gray and RGB controls check that those pages still get the sRGB output intent.
This commit is contained in:
@@ -24,7 +24,7 @@ mod writer;
|
||||
pub use bytes::Bytes;
|
||||
pub use document::{Document, Revision, SourceIdentity, XrefEntry};
|
||||
pub use object::{Dict, MAX_DECODED, Name, ObjRef, Object, PdfString, Stream};
|
||||
pub use parser::{Lexer, parse_indirect};
|
||||
pub use parser::{Lexer, is_whitespace, parse_indirect};
|
||||
pub use pdfcraft_crypt::{
|
||||
Algorithm, Auth, Method as CryptMethod, NewEncryption, Permissions, SecurityHandler, effective_password, pdfdoc_byte, pdfdoc_char,
|
||||
};
|
||||
|
||||
@@ -11,7 +11,8 @@ use crate::{Bytes, CosError};
|
||||
|
||||
const MAX_DEPTH: usize = 128;
|
||||
|
||||
pub(crate) fn is_whitespace(b: u8) -> bool {
|
||||
/// One of the six PDF whitespace characters (ISO 32000-2 §7.2.3, Table 1).
|
||||
pub fn is_whitespace(b: u8) -> bool {
|
||||
matches!(b, b'\0' | b'\t' | b'\n' | b'\x0C' | b'\r' | b' ')
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
pub mod icc;
|
||||
mod xmp;
|
||||
|
||||
use pdfcraft_cos::{Dict, Document, ObjRef, Object, PdfString, Stream};
|
||||
use pdfcraft_cos::{Dict, Document, ObjRef, Object, PdfString, Stream, is_whitespace};
|
||||
|
||||
/// The PDF/A part and conformance level.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
@@ -287,8 +287,8 @@ fn uses_device_colour(doc: &Document, objs: &[(ObjRef, std::sync::Arc<Object>)])
|
||||
&& let Ok(data) = s.decoded()
|
||||
{
|
||||
// Content streams: rg/RG/g/G/k/K operators.
|
||||
for line in data.split(|b| *b == b'\n' || *b == b' ') {
|
||||
match line {
|
||||
for token in data.split(|b| is_whitespace(*b)) {
|
||||
match token {
|
||||
b"g" | b"G" => used.gray = true,
|
||||
b"rg" | b"RG" => used.rgb = true,
|
||||
b"k" | b"K" => used.cmyk = true,
|
||||
|
||||
@@ -214,3 +214,39 @@ fn verify_reports_an_output_intent_that_does_not_match_device_colour() {
|
||||
let headed = format!("<< /Length 128 >>\nstream\n{header}\nendstream");
|
||||
assert!(!messages(&verify(&cmyk_page(intent, "/Im0 Do", &[&headed]), Level::A2b)).contains(&cmyk_msg));
|
||||
}
|
||||
|
||||
/// Issue #1334: a colour operator is found whichever of the six PDF whitespace characters
|
||||
/// separates the tokens, not only after a space or a line feed.
|
||||
#[test]
|
||||
fn device_colour_is_found_after_any_whitespace() {
|
||||
let page = |content: &[u8]| {
|
||||
let content = String::from_utf8(content.to_vec()).unwrap();
|
||||
let stream = format!("<< /Length {} >>\nstream\n{content}\nendstream", content.len());
|
||||
build(
|
||||
&[
|
||||
"<< /Type /Catalog /Pages 2 0 R >>",
|
||||
"<< /Type /Pages /Kids [3 0 R] /Count 1 /MediaBox [0 0 200 200] >>",
|
||||
"<< /Type /Page /Parent 2 0 R /Contents 4 0 R /Resources << >> >>",
|
||||
stream.as_str(),
|
||||
],
|
||||
"",
|
||||
)
|
||||
};
|
||||
let cmyk_msg = "DeviceCMYK is used: it needs a CMYK output intent (not added automatically)";
|
||||
for separator in [b" ".as_slice(), b"\n", b"\r", b"\t", b"\x0C", b"\0"] {
|
||||
let cmyk: &[&[u8]] = &[b"0", b"0", b"0", b"1", b"k", b"10", b"10", b"50", b"50", b"re", b"f"];
|
||||
let mut doc = page(&cmyk.join(separator));
|
||||
assert!(messages(&verify(&doc, Level::A2b)).contains(&cmyk_msg), "separator {separator:?}");
|
||||
let r = convert(&mut doc, Level::A2b).unwrap();
|
||||
assert!(declared(&doc).output_intents.is_empty(), "separator {separator:?}");
|
||||
assert!(messages(&r.remaining).contains(&cmyk_msg), "separator {separator:?}");
|
||||
|
||||
// Gray and RGB still get the sRGB output intent.
|
||||
for colour in [[b"0".as_slice(), b"g"].as_slice(), &[b"1", b"0", b"0", b"rg"]] {
|
||||
let mut doc = page(&colour.join(separator));
|
||||
let r = convert(&mut doc, Level::A2b).unwrap();
|
||||
assert!(r.fixed.iter().any(|f| f.contains("output intent")), "separator {separator:?}: {:?}", r.fixed);
|
||||
assert_eq!(declared(&doc).output_intents, ["sRGB IEC61966-2.1"], "separator {separator:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user