use crate::content::{Instruction, OPERANDS_THRESHOLD, OperatorTrait, Stack};
use crate::object;
use crate::object::Array;
use crate::object::Name;
use crate::object::Number;
use crate::object::Object;
use crate::object::Stream;
use smallvec::{SmallVec, smallvec};
use crate::content::macros::{op_all, op_impl, op0, op1, op2, op3, op4, op6};
include!("ops_generated.rs");
fn parse_named_color<'b, 'a>(
objects: &'b [Object<'a>],
) -> Option<(SmallVec<[Number; OPERANDS_THRESHOLD]>, Option<&'b Name<'a>>)> {
let mut nums = smallvec![];
let mut name = None;
for o in objects {
match o {
Object::Number(n) => nums.push(*n),
Object::Name(n) => name = Some(n),
_ => {
warn!("encountered unknown object {o:?} when parsing scn/SCN");
return None;
}
}
}
Some((nums, name))
}
#[derive(Debug, Clone, PartialEq)]
pub struct StrokeColorNamed<'b, 'a>(
pub SmallVec<[Number; OPERANDS_THRESHOLD]>,
pub Option<&'b Name<'a>>,
);
op_impl!(
StrokeColorNamed<'b, 'a>,
"SCN",
u8::MAX as usize,
|stack: &'b Stack<'a>| {
let (nums, name) = parse_named_color(stack.as_slice())?;
Some(StrokeColorNamed(nums, name))
}
);
#[derive(Debug, PartialEq, Clone)]
pub struct NonStrokeColorNamed<'b, 'a>(
pub SmallVec<[Number; OPERANDS_THRESHOLD]>,
pub Option<&'b Name<'a>>,
);
op_impl!(
NonStrokeColorNamed<'b, 'a>,
"scn",
u8::MAX as usize,
|stack: &'b Stack<'a>| {
let (nums, name) = parse_named_color(stack.as_slice())?;
Some(NonStrokeColorNamed(nums, name))
}
);
#[cfg(test)]
mod tests {
use crate::content::TypedIter;
use crate::content::ops::{
BeginMarkedContentWithProperties, ClosePath, EndMarkedContent, FillPathNonZero, LineTo,
MarkedContentPointWithProperties, MoveTo, NonStrokeColorDeviceRgb, NonStrokeColorNamed,
SetGraphicsState, StrokeColorNamed, Transform, TypedInstruction,
};
use crate::object::Name;
use crate::object::Number;
use crate::object::Object;
use crate::object::{Dict, FromBytes};
fn n(num: i32) -> Number {
Number::from_i32(num)
}
#[test]
fn basic_ops_1() {
let input = b"
1 0 0 -1 0 200 cm
/g0 gs
1 0 0 rg
";
let mut iter = TypedIter::new(input);
assert!(matches!(
iter.next(),
Some(TypedInstruction::Transform(Transform(a, b, c, d, e, f)))
if [a, b, c, d, e, f] == [n(1), n(0), n(0), n(-1), n(0), n(200)]
));
assert!(matches!(
iter.next(),
Some(TypedInstruction::SetGraphicsState(SetGraphicsState(name)))
if name.as_ref() == b"g0"
));
assert!(matches!(
iter.next(),
Some(TypedInstruction::NonStrokeColorDeviceRgb(NonStrokeColorDeviceRgb(r, g, b)))
if [r, g, b] == [n(1), n(0), n(0)]
));
assert!(iter.next().is_none());
}
#[test]
fn basic_ops_2() {
let input = b"
20 20 m
180 20 l
180 180 l
20 180 l
h
f
";
let mut iter = TypedIter::new(input);
assert!(matches!(
iter.next(),
Some(TypedInstruction::MoveTo(MoveTo(x, y))) if [x, y] == [n(20), n(20)]
));
assert!(matches!(
iter.next(),
Some(TypedInstruction::LineTo(LineTo(x, y))) if [x, y] == [n(180), n(20)]
));
assert!(matches!(
iter.next(),
Some(TypedInstruction::LineTo(LineTo(x, y))) if [x, y] == [n(180), n(180)]
));
assert!(matches!(
iter.next(),
Some(TypedInstruction::LineTo(LineTo(x, y))) if [x, y] == [n(20), n(180)]
));
assert!(matches!(
iter.next(),
Some(TypedInstruction::ClosePath(ClosePath))
));
assert!(matches!(
iter.next(),
Some(TypedInstruction::FillPathNonZero(FillPathNonZero))
));
assert!(iter.next().is_none());
}
#[test]
fn scn() {
let input = b"
0.0 scn
1.0 1.0 1.0 /DeviceRgb SCN
";
let mut iter = TypedIter::new(input);
match iter.next() {
Some(TypedInstruction::NonStrokeColorNamed(NonStrokeColorNamed(nums, None))) => {
assert_eq!(nums.as_slice(), &[Number::from_f32(0.0)]);
}
other => panic!("unexpected instruction: {other:?}"),
}
match iter.next() {
Some(TypedInstruction::StrokeColorNamed(StrokeColorNamed(nums, Some(name)))) => {
assert_eq!(
nums.as_slice(),
&[
Number::from_f32(1.0),
Number::from_f32(1.0),
Number::from_f32(1.0)
]
);
assert_eq!(name.as_ref(), b"DeviceRgb");
}
other => panic!("unexpected instruction: {other:?}"),
}
assert!(iter.next().is_none());
}
#[test]
fn dp() {
let input = b"/Attribute<</ShowCenterPoint false >> DP";
let mut iter = TypedIter::new(input);
match iter.next() {
Some(TypedInstruction::MarkedContentPointWithProperties(
MarkedContentPointWithProperties(name, object),
)) => {
assert_eq!(name.as_ref(), b"Attribute");
assert_eq!(
object,
&Object::Dict(Dict::from_bytes(b"<</ShowCenterPoint false >>").unwrap())
);
}
other => panic!("unexpected instruction: {other:?}"),
}
assert!(iter.next().is_none());
}
#[test]
fn bdc_with_dict() {
let input = b"/Span << /MCID 0 /Alt (Alt)>> BDC EMC";
let mut iter = TypedIter::new(input);
match iter.next() {
Some(TypedInstruction::BeginMarkedContentWithProperties(
BeginMarkedContentWithProperties(name, object),
)) => {
assert_eq!(name.as_ref(), b"Span");
assert_eq!(
object,
&Object::Dict(Dict::from_bytes(b"<< /MCID 0 /Alt (Alt)>>").unwrap())
);
}
other => panic!("unexpected instruction: {other:?}"),
}
assert!(matches!(
iter.next(),
Some(TypedInstruction::EndMarkedContent(EndMarkedContent))
));
assert!(iter.next().is_none());
}
#[test]
fn bdc_with_name() {
let input = b"/Span /Name BDC EMC";
let mut iter = TypedIter::new(input);
match iter.next() {
Some(TypedInstruction::BeginMarkedContentWithProperties(
BeginMarkedContentWithProperties(name, object),
)) => {
assert_eq!(name.as_ref(), b"Span");
assert_eq!(object, &Object::Name(Name::new_unescaped(b"Name")));
}
other => panic!("unexpected instruction: {other:?}"),
}
assert!(matches!(
iter.next(),
Some(TypedInstruction::EndMarkedContent(EndMarkedContent))
));
assert!(iter.next().is_none());
}
}