use crate::content::graphics_state::GraphicsState;
use crate::document::PdfDocument;
use crate::error::Result;
use crate::object::Object;
#[derive(Clone, Debug, Default)]
pub(crate) struct ParsedExtGState {
pub(crate) fill_alpha: Option<f32>,
pub(crate) stroke_alpha: Option<f32>,
pub(crate) blend_mode: Option<String>,
pub(crate) stroke_overprint: Option<bool>,
pub(crate) fill_overprint: Option<bool>,
pub(crate) overprint_mode: Option<u8>,
}
impl ParsedExtGState {
pub(crate) fn apply(&self, gs: &mut GraphicsState) {
if let Some(a) = self.fill_alpha {
gs.fill_alpha = a;
}
if let Some(a) = self.stroke_alpha {
gs.stroke_alpha = a;
}
if let Some(ref m) = self.blend_mode {
gs.blend_mode = m.clone();
}
if let Some(v) = self.fill_overprint {
gs.fill_overprint = v;
}
if let Some(v) = self.stroke_overprint {
gs.stroke_overprint = v;
}
if let Some(v) = self.overprint_mode {
gs.overprint_mode = v;
}
}
}
pub(crate) fn parse_ext_g_state_inner(
state_obj: &Object,
doc: &PdfDocument,
) -> Result<ParsedExtGState> {
let mut out = ParsedExtGState::default();
let state_resolved = doc.resolve_object(state_obj)?;
let state_dict = match state_resolved.as_dict() {
Some(d) => d,
None => return Ok(out),
};
if let Some(ca) = state_dict.get("ca") {
out.fill_alpha = ca
.as_real()
.map(|v| v as f32)
.or_else(|| ca.as_integer().map(|v| v as f32));
}
if let Some(ca_upper) = state_dict.get("CA") {
out.stroke_alpha = ca_upper
.as_real()
.map(|v| v as f32)
.or_else(|| ca_upper.as_integer().map(|v| v as f32));
}
if let Some(bm) = state_dict.get("BM") {
let mode = match bm {
Object::Name(n) => n.clone(),
Object::Array(arr) => arr
.first()
.and_then(|o| o.as_name())
.unwrap_or("Normal")
.to_string(),
_ => "Normal".to_string(),
};
out.blend_mode = Some(mode);
}
let op_stroke = state_dict.get("OP").and_then(Object::as_bool);
let op_fill = state_dict.get("op").and_then(Object::as_bool);
out.stroke_overprint = op_stroke;
out.fill_overprint = op_fill.or(op_stroke);
if let Some(opm) = state_dict.get("OPM").and_then(Object::as_integer) {
out.overprint_mode = Some(if opm == 1 { 1 } else { 0 });
}
Ok(out)
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
fn fixture_doc() -> PdfDocument {
let mut buf: Vec<u8> = Vec::new();
buf.extend_from_slice(b"%PDF-1.4\n");
let cat_off = buf.len();
buf.extend_from_slice(b"1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n");
let pages_off = buf.len();
buf.extend_from_slice(b"2 0 obj\n<< /Type /Pages /Kids [] /Count 0 >>\nendobj\n");
let xref_off = buf.len();
buf.extend_from_slice(b"xref\n0 3\n0000000000 65535 f \n");
buf.extend_from_slice(format!("{:010} 00000 n \n", cat_off).as_bytes());
buf.extend_from_slice(format!("{:010} 00000 n \n", pages_off).as_bytes());
buf.extend_from_slice(
format!("trailer\n<< /Size 3 /Root 1 0 R >>\nstartxref\n{}\n%%EOF\n", xref_off)
.as_bytes(),
);
PdfDocument::from_bytes(buf).expect("fixture PDF parses")
}
fn dict(entries: &[(&str, Object)]) -> Object {
let mut m = HashMap::new();
for (k, v) in entries {
m.insert((*k).to_string(), v.clone());
}
Object::Dictionary(m)
}
#[test]
fn parses_op_op_opm_from_extgstate_dict() {
let obj = dict(&[
("OP", Object::Boolean(true)),
("op", Object::Boolean(false)),
("OPM", Object::Integer(1)),
]);
let doc = fixture_doc();
let parsed = parse_ext_g_state_inner(&obj, &doc).expect("parses");
assert_eq!(parsed.stroke_overprint, Some(true));
assert_eq!(parsed.fill_overprint, Some(false));
assert_eq!(parsed.overprint_mode, Some(1));
}
#[test]
fn op_without_op_sets_both_overprints() {
let obj = dict(&[("OP", Object::Boolean(true))]);
let doc = fixture_doc();
let parsed = parse_ext_g_state_inner(&obj, &doc).expect("parses");
assert_eq!(parsed.stroke_overprint, Some(true));
assert_eq!(parsed.fill_overprint, Some(true));
}
#[test]
fn op_without_op_uppercase_only_does_not_affect_stroke() {
let obj = dict(&[("op", Object::Boolean(true))]);
let doc = fixture_doc();
let parsed = parse_ext_g_state_inner(&obj, &doc).expect("parses");
assert_eq!(parsed.stroke_overprint, None);
assert_eq!(parsed.fill_overprint, Some(true));
}
#[test]
fn opm_clamps_unknown_values_to_zero() {
let obj = dict(&[("OPM", Object::Integer(42))]);
let doc = fixture_doc();
let parsed = parse_ext_g_state_inner(&obj, &doc).expect("parses");
assert_eq!(parsed.overprint_mode, Some(0));
}
#[test]
fn missing_overprint_keys_leave_options_none() {
let obj = dict(&[]);
let doc = fixture_doc();
let parsed = parse_ext_g_state_inner(&obj, &doc).expect("parses");
assert_eq!(parsed.stroke_overprint, None);
assert_eq!(parsed.fill_overprint, None);
assert_eq!(parsed.overprint_mode, None);
}
}