Files
effectcraft/crates/pdf/src/ps/builtin.rs
T

1067 lines
41 KiB
Rust

use super::eps::find_from;
use super::scanner::Src;
use super::text::encoding_array;
use super::value::{Flow, V, array, matrix_of, matrix_val, new_dict};
use super::{Affine, BezPath, Cap, FillRule, Interp, Join, PathEl, Point, Rc, RefCell, parse_num};
impl Interp {
pub(super) fn builtin(&mut self, n: &str, src: &mut Src) -> Result<(), Flow> {
// Every handler leaves the stacks untouched when the operator is outside its family.
if self.try_stack_op(n)
|| self.try_arithmetic_op(n)
|| self.try_logic_op(n)
|| self.try_control_op(n, src)?
|| self.try_dictionary_op(n)
|| self.try_collection_op(n)
|| self.try_environment_op(n, src)?
|| self.try_graphics_op(n)?
|| self.try_coordinate_op(n)
|| self.try_path_op(n)?
|| self.try_paint_op(n)?
{
return Ok(());
}
match n {
// Text (see `text_op`).
"scalefont" | "makefont" | "selectfont" | "setfont" | "show" | "charpath" | "glyphshow" | "rootfont" | "currentfont" | "setcachedevice"
| "setcharwidth" | "ashow" | "widthshow" | "awidthshow" | "xshow" | "yshow" | "xyshow" | "kshow" | "cshow" | "stringwidth" | "definefont"
| "StandardEncoding" | "ISOLatin1Encoding" | "eexec" | "StartData" | "undefinefont" => self.text_op(n, src)?,
// Images: operands popped, inline data skipped.
"image" | "imagemask" | "colorimage" => {
let mut reads_file = false;
let check = |v: &V, rf: &mut bool| {
if matches!(v, V::File) || matches!(v, V::Proc(p) if p.iter().any(|x| matches!(x, V::Name(n) if &**n == "currentfile"))) {
*rf = true;
}
};
if n == "colorimage" {
let ncomp = self.popn().max(1.0) as usize;
let multi = matches!(self.pop(), V::Bool(true));
for _ in 0..if multi { ncomp.min(4) } else { 1 } {
let v = self.pop();
check(&v, &mut reads_file);
}
for _ in 0..4 {
self.pop();
}
} else {
match self.pop() {
V::Dict(d) => {
if let Some(ds) = d.borrow().get("DataSource") {
check(ds, &mut reads_file);
}
}
top => {
check(&top, &mut reads_file);
for _ in 0..4 {
self.pop();
}
}
}
}
if reads_file {
src.skip_image_data();
}
self.b.skip("image");
}
_ => {
if self.unknown.len() < 32 && !self.unknown.iter().any(|u| u == n) {
self.unknown.push(n.to_string());
}
}
}
Ok(())
}
fn repeat_proc(&mut self, src: &mut Src) -> Result<(), Flow> {
let p = self.pop();
let k = self.popn().clamp(0.0, 1e6) as usize;
for _ in 0..k {
self.spend(1)?;
match self.exec_obj(p.clone(), src) {
Err(Flow::Exit) => break,
r => r?,
}
}
Ok(())
}
fn for_proc(&mut self, src: &mut Src) -> Result<(), Flow> {
let p = self.pop();
let lim = self.popn();
let inc = self.popn();
let mut i = self.popn();
let mut guard = 0;
while inc != 0.0 && ((inc > 0.0 && i <= lim) || (inc < 0.0 && i >= lim)) && guard < 1_000_000 {
self.spend(1)?;
self.push(V::Num(i));
match self.exec_obj(p.clone(), src) {
Err(Flow::Exit) => break,
r => r?,
}
i += inc;
guard += 1;
}
Ok(())
}
fn loop_proc(&mut self, src: &mut Src) -> Result<(), Flow> {
let p = self.pop();
for _ in 0..1_000_000 {
self.spend(1)?;
match self.exec_obj(p.clone(), src) {
Err(Flow::Exit) => break,
r => r?,
}
}
Ok(())
}
fn forall_proc(&mut self, src: &mut Src) -> Result<(), Flow> {
let p = self.pop();
match self.pop() {
V::Dict(d) => {
let items: Vec<(String, V)> = d.borrow().iter().map(|(k, v)| (k.clone(), v.clone())).collect();
for (k, v) in items {
self.spend(1)?;
self.push(V::Lit(k.into()));
self.push(v);
match self.exec_obj(p.clone(), src) {
Err(Flow::Exit) => break,
r => r?,
}
}
}
V::Str(s) => {
let bytes = s.borrow().clone();
for c in bytes {
self.spend(1)?;
self.push(V::Num(c as f64));
match self.exec_obj(p.clone(), src) {
Err(Flow::Exit) => break,
r => r?,
}
}
}
a => {
for v in a.items().unwrap_or_default() {
self.spend(1)?;
self.push(v);
match self.exec_obj(p.clone(), src) {
Err(Flow::Exit) => break,
r => r?,
}
}
}
}
Ok(())
}
fn try_stack_op(&mut self, n: &str) -> bool {
match n {
// Stack.
"pop" => {
self.pop();
}
"exch" => {
let b = self.pop();
let a = self.pop();
self.push(b);
self.push(a);
}
"dup" => {
let a = self.st.last().cloned().unwrap_or(V::Null);
self.push(a);
}
"copy" => {
match self.pop() {
V::Num(k) => {
let k = (k.max(0.0) as usize).min(self.st.len());
let tail: Vec<V> = self.st[self.st.len() - k..].to_vec();
self.st.extend(tail);
}
V::Array(dst) => {
let s = self.pop().items().unwrap_or_default();
let mut d = dst.borrow_mut();
for (i, v) in s.into_iter().enumerate() {
if i < d.len() {
d[i] = v;
}
}
drop(d);
self.push(V::Array(dst));
}
other => self.push(other),
};
}
"index" => {
let k = self.popn().max(0.0) as usize;
let v = if k < self.st.len() { self.st[self.st.len() - 1 - k].clone() } else { V::Null };
self.push(v);
}
"roll" => {
let j = self.popn() as i64;
let k = (self.popn().max(0.0) as usize).min(self.st.len());
if k > 0 {
let s = self.st.len() - k;
let r = j.rem_euclid(k as i64) as usize;
self.st[s..].rotate_right(r);
}
}
"clear" => self.st.clear(),
"count" => self.push(V::Num(self.st.len() as f64)),
"mark" | "[" | "<<" => self.push(V::Mark),
"cleartomark" => {
while let Some(v) = self.st.pop() {
if matches!(v, V::Mark) {
break;
}
}
}
"counttomark" => {
let k = self.st.iter().rev().position(|v| matches!(v, V::Mark)).unwrap_or(self.st.len());
self.push(V::Num(k as f64));
}
"]" => {
let k = self.st.iter().rev().position(|v| matches!(v, V::Mark)).unwrap_or(self.st.len());
let items = self.st.split_off(self.st.len() - k);
self.st.pop();
self.push(array(items));
}
">>" => {
let k = self.st.iter().rev().position(|v| matches!(v, V::Mark)).unwrap_or(self.st.len());
let items = self.st.split_off(self.st.len() - k);
self.st.pop();
let d = new_dict();
for kv in items.as_chunks::<2>().0 {
d.borrow_mut().insert(kv[0].key(), kv[1].clone());
}
self.push(V::Dict(d));
}
_ => return false,
}
true
}
fn try_arithmetic_op(&mut self, n: &str) -> bool {
match n {
// Arithmetic.
"add" | "sub" | "mul" | "div" | "idiv" | "mod" | "atan" | "exp" | "max" | "min" => {
let b = self.popn();
let a = self.popn();
let v = match n {
"add" => a + b,
"sub" => a - b,
"mul" => a * b,
"div" => {
if b == 0.0 {
0.0
} else {
a / b
}
}
// `checked_*` give 0 both for a zero divisor and for the one overflowing
// case, `i64::MIN / -1` (`/` and `%` panic on it in every build).
"idiv" => (a as i64).checked_div(b as i64).unwrap_or(0) as f64,
"mod" => (a as i64).checked_rem(b as i64).unwrap_or(0) as f64,
"atan" => a.atan2(b).to_degrees().rem_euclid(360.0),
"exp" => a.powf(b),
"max" => a.max(b),
_ => a.min(b),
};
self.push(V::Num(if v.is_finite() { v } else { 0.0 }));
}
"neg" | "abs" | "sqrt" | "sin" | "cos" | "ln" | "log" | "cvi" | "cvr" | "round" | "floor" | "ceiling" | "truncate" => {
let a = match self.pop() {
V::Str(s) => parse_num(&s.borrow()).unwrap_or(0.0),
v => v.num().unwrap_or(0.0),
};
let v = match n {
"neg" => -a,
"abs" => a.abs(),
"sqrt" => a.max(0.0).sqrt(),
"sin" => a.to_radians().sin(),
"cos" => a.to_radians().cos(),
"ln" => a.ln(),
"log" => a.log10(),
"cvi" | "truncate" => a.trunc(),
"round" => (a + 0.5).floor(),
"floor" => a.floor(),
"ceiling" => a.ceil(),
_ => a,
};
self.push(V::Num(if v.is_finite() { v } else { 0.0 }));
}
_ => return false,
}
true
}
fn try_logic_op(&mut self, n: &str) -> bool {
match n {
// Comparison and logic.
"eq" | "ne" => {
let b = self.pop();
let a = self.pop();
let eq = match (&a, &b) {
(V::Num(x), V::Num(y)) => x == y,
(V::Bool(x), V::Bool(y)) => x == y,
(V::Null, V::Null) => true,
(V::Array(x), V::Array(y)) => Rc::ptr_eq(x, y),
(V::Dict(x), V::Dict(y)) => Rc::ptr_eq(x, y),
(x, y) => !x.key().is_empty() && x.key() == y.key(),
};
self.push(V::Bool(eq == (n == "eq")));
}
"gt" | "ge" | "lt" | "le" => {
let b = self.popn();
let a = self.popn();
self.push(V::Bool(match n {
"gt" => a > b,
"ge" => a >= b,
"lt" => a < b,
_ => a <= b,
}));
}
"not" => match self.pop() {
V::Bool(b) => self.push(V::Bool(!b)),
v => self.push(V::Num(!(v.num().unwrap_or(0.0) as i64) as f64)),
},
"and" | "or" | "xor" => {
let b = self.pop();
let a = self.pop();
match (a, b) {
(V::Bool(x), V::Bool(y)) => self.push(V::Bool(match n {
"and" => x && y,
"or" => x || y,
_ => x ^ y,
})),
(x, y) => {
let (x, y) = (x.num().unwrap_or(0.0) as i64, y.num().unwrap_or(0.0) as i64);
self.push(V::Num(match n {
"and" => x & y,
"or" => x | y,
_ => x ^ y,
} as f64));
}
}
}
"true" => self.push(V::Bool(true)),
"false" => self.push(V::Bool(false)),
"null" => self.push(V::Null),
_ => return false,
}
true
}
fn try_control_op(&mut self, n: &str, src: &mut Src) -> Result<bool, Flow> {
match n {
// Control.
"exec" => {
let v = self.pop();
self.exec_obj(v, src)?;
}
"if" => {
let p = self.pop();
if let V::Bool(true) = self.pop() {
self.exec_obj(p, src)?;
}
}
"ifelse" => {
let f = self.pop();
let t = self.pop();
let c = matches!(self.pop(), V::Bool(true));
self.exec_obj(if c { t } else { f }, src)?;
}
// Each pass of a loop counts against the budget.
"repeat" => self.repeat_proc(src)?,
"for" => self.for_proc(src)?,
"loop" => self.loop_proc(src)?,
"forall" => self.forall_proc(src)?,
"exit" => return Err(Flow::Exit),
"stop" => return Err(Flow::Stop),
"stopped" => {
let p = self.pop();
let stopped = match self.exec_obj(p, src) {
Err(Flow::Quit) => return Err(Flow::Quit),
Err(_) => true,
Ok(()) => false,
};
self.push(V::Bool(stopped));
}
"quit" => return Err(Flow::Quit),
"showpage" | "copypage" => {}
_ => return Ok(false),
}
Ok(true)
}
fn try_dictionary_op(&mut self, n: &str) -> bool {
match n {
// Dictionaries.
"def" => {
let v = self.pop();
let k = self.pop().key();
if let Some(d) = self.dicts.last() {
d.borrow_mut().insert(k, v);
}
}
"store" => {
let v = self.pop();
let k = self.pop().key();
let d = self.dicts.iter().rev().find(|d| d.borrow().contains_key(&k)).or(self.dicts.last()).cloned();
if let Some(d) = d {
d.borrow_mut().insert(k, v);
}
}
"load" => {
let k = self.pop().key();
let v = self.lookup(&k).unwrap_or(V::Op(k.into()));
self.push(v);
}
"undef" => {
let k = self.pop().key();
if let V::Dict(d) = self.pop() {
d.borrow_mut().remove(&k);
}
}
"dict" => {
self.pop();
self.push(V::Dict(new_dict()));
}
"begin" => {
if let V::Dict(d) = self.pop()
&& self.dicts.len() < 64
{
self.dicts.push(d);
}
}
"end" => {
if self.dicts.len() > 1 {
self.dicts.pop();
}
}
"currentdict" => {
let d = self.dicts.last().cloned().unwrap_or_else(new_dict);
self.push(V::Dict(d));
}
"userdict" | "globaldict" | "systemdict" | "statusdict" | "errordict" | "$error" => {
let d = self.dicts[0].clone();
self.push(V::Dict(d));
}
"where" => {
let k = self.pop().key();
match self.dicts.iter().rev().find(|d| d.borrow().contains_key(&k)).cloned() {
Some(d) => {
self.push(V::Dict(d));
self.push(V::Bool(true));
}
None => self.push(V::Bool(false)),
}
}
"known" => {
let k = self.pop().key();
let v = match self.pop() {
V::Dict(d) => d.borrow().contains_key(&k),
_ => false,
};
self.push(V::Bool(v));
}
"countdictstack" => self.push(V::Num(self.dicts.len() as f64)),
"cleardictstack" => self.dicts.truncate(1),
"findresource" | "findfont" | "findencoding" => {
let category = if n == "findresource" { self.pop().key() } else { String::new() };
let key = self.pop().key();
match (n, category.as_str()) {
("findfont", _) | (_, "Font") => {
let f = self.find_font(&key);
self.push(V::Dict(f));
}
("findencoding", _) | (_, "Encoding") => self.push(encoding_array(&key)),
_ => self.push(V::Dict(new_dict())),
}
}
"defineresource" => {
let category = self.pop().key();
let v = self.pop();
let key = self.pop().key();
if let (V::Dict(d), "Font") = (&v, category.as_str()) {
self.font_dir.insert(key, d.clone());
}
self.push(v);
}
"resourcestatus" => {
self.pop();
self.pop();
self.push(V::Bool(false));
}
_ => return false,
}
true
}
fn try_collection_op(&mut self, n: &str) -> bool {
match n {
// Arrays and strings.
"array" => {
let k = self.popn().clamp(0.0, 1e6) as usize;
self.push(array(vec![V::Null; k]));
}
"string" => {
let k = self.popn().clamp(0.0, 1e7) as usize;
self.push(V::Str(Rc::new(RefCell::new(vec![0; k]))));
}
"length" => {
let l = match self.pop() {
V::Str(s) => s.borrow().len(),
V::Dict(d) => d.borrow().len(),
V::Name(s) | V::Lit(s) => s.len(),
v => v.items().map_or(0, |i| i.len()),
};
self.push(V::Num(l as f64));
}
"get" => {
let k = self.pop();
let v = match self.pop() {
V::Dict(d) => d.borrow().get(&k.key()).cloned().unwrap_or(V::Null),
V::Str(s) => V::Num(s.borrow().get(k.num().unwrap_or(0.0) as usize).copied().unwrap_or(0) as f64),
a => a.items().and_then(|i| i.get(k.num().unwrap_or(0.0) as usize).cloned()).unwrap_or(V::Null),
};
self.push(v);
}
"put" => {
let v = self.pop();
let k = self.pop();
match self.pop() {
V::Dict(d) => {
d.borrow_mut().insert(k.key(), v);
}
V::Array(a) => {
let i = k.num().unwrap_or(0.0) as usize;
if let Some(slot) = a.borrow_mut().get_mut(i) {
*slot = v;
}
}
V::Str(s) => {
let i = k.num().unwrap_or(0.0) as usize;
if let Some(slot) = s.borrow_mut().get_mut(i) {
*slot = v.num().unwrap_or(0.0) as u8;
}
}
_ => {}
}
}
"getinterval" => {
let c = self.popn().max(0.0) as usize;
let i = self.popn().max(0.0) as usize;
match self.pop() {
V::Str(s) => {
let b = s.borrow();
let part = b.get(i.min(b.len())..(i + c).min(b.len())).unwrap_or(&[]).to_vec();
drop(b);
self.push(V::Str(Rc::new(RefCell::new(part))));
}
a => {
let items = a.items().unwrap_or_default();
let part = items.get(i.min(items.len())..(i + c).min(items.len())).unwrap_or(&[]).to_vec();
self.push(array(part));
}
}
}
"putinterval" => {
self.pop();
self.pop();
self.pop();
}
"aload" => {
let a = self.pop();
for v in a.items().unwrap_or_default() {
self.push(v);
}
self.push(a);
}
"astore" => {
let a = self.pop();
if let V::Array(arr) = &a {
let k = arr.borrow().len().min(self.st.len());
let items = self.st.split_off(self.st.len() - k);
*arr.borrow_mut() = items;
}
self.push(a);
}
"cvx" => match self.pop() {
V::Array(a) => self.push(V::Proc(Rc::new(a.borrow().clone()))),
V::Lit(s) => self.push(V::Name(s)),
V::Str(s) => {
// Executable string: tokenize it as a procedure.
let bytes = s.borrow().clone();
let mut sub = Src { d: &bytes, pos: 0 };
let mut body = vec![];
while let Some(t) = sub.token(0) {
body.push(t);
}
self.push(V::Proc(Rc::new(body)));
}
v => self.push(v),
},
"cvlit" => match self.pop() {
V::Proc(p) => self.push(array(p.as_ref().clone())),
V::Name(s) => self.push(V::Lit(s)),
v => self.push(v),
},
"cvn" => {
let k = self.pop().key();
self.push(V::Lit(k.into()));
}
"cvs" => {
self.pop();
let k = self.pop().key();
self.push(V::Str(Rc::new(RefCell::new(k.into_bytes()))));
}
"xcheck" => {
let v = self.pop();
self.push(V::Bool(matches!(v, V::Proc(_) | V::Name(_) | V::Op(_))));
}
"type" => {
let t = match self.pop() {
V::Num(_) => "realtype",
V::Bool(_) => "booleantype",
V::Name(_) | V::Lit(_) => "nametype",
V::Op(_) => "operatortype",
V::Str(_) => "stringtype",
V::Proc(_) | V::Array(_) => "arraytype",
V::Dict(_) => "dicttype",
V::Mark => "marktype",
V::Null => "nulltype",
V::File => "filetype",
};
self.push(V::Name(t.into()));
}
"bind" | "readonly" | "executeonly" | "noaccess" => {}
_ => return false,
}
true
}
fn try_environment_op(&mut self, n: &str, src: &mut Src) -> Result<bool, Flow> {
match n {
"languagelevel" => self.push(V::Num(3.0)),
"version" | "product" => self.push(V::Str(Rc::new(RefCell::new(b"3010".to_vec())))),
"save" => {
self.spend(self.gs.path.elements().len())?;
self.saved.push(self.gs.clone());
self.gs.depth = self.b.depth();
self.push(V::Null);
}
"restore" => {
self.pop();
self.grestore();
}
"currentfile" => self.push(V::File),
"readstring" | "readhexstring" | "readline" => {
self.pop();
self.pop();
self.push(V::Str(Rc::new(RefCell::new(vec![]))));
self.push(V::Bool(false));
}
"flushfile" | "closefile" | "print" | "==" | "=" | "pstack" | "setpagedevice" | "setglobal" | "setpacking" | "setshared" | "setobjectformat"
| "setsmoothness" => {
if !matches!(n, "pstack") {
self.pop();
}
}
"currentglobal" | "currentpacking" | "currentshared" => self.push(V::Bool(false)),
// Operators that return values must push them, or the rest of the program reads a
// shifted stack: Illustrator's EPS prolog queries the device this way and then drew
// nothing (#955). Values are a plain device's defaults (PostScript Language
// Reference, 3rd ed., chapter 8).
"currentflat" => self.push(V::Num(1.0)),
"currentscreen" => {
self.push(V::Num(60.0));
self.push(V::Num(45.0));
self.push(V::Proc(Rc::new(vec![])));
}
"currentblackgeneration" | "currentundercolorremoval" | "currenttransfer" => self.push(V::Proc(Rc::new(vec![]))),
"currentcolortransfer" => {
for _ in 0..4 {
self.push(V::Proc(Rc::new(vec![])));
}
}
"currentpagedevice" | "currenthalftone" | "currentsystemparams" | "currentuserparams" | "currentdevparams" => {
if n == "currentdevparams" {
self.pop();
}
self.push(V::Dict(new_dict()));
}
"setuserparams" | "setsystemparams" | "setcolortransfer" | "setblackgeneration" | "setundercolorremoval" => {
let k = if n == "setcolortransfer" { 4 } else { 1 };
for _ in 0..k {
self.pop();
}
}
// `file status` → false; `string status` → false (no such file).
"status" => {
self.pop();
self.push(V::Bool(false));
}
"dictstack" => {
self.pop();
let ds: Vec<V> = self.dicts.iter().map(|d| V::Dict(d.clone())).collect();
self.push(array(ds));
}
"maxlength" => {
let k = match self.pop() {
V::Dict(d) => d.borrow().len(),
_ => 0,
};
self.push(V::Num(k.saturating_add(16) as f64));
}
"internaldict" => {
self.pop();
let d = self.dicts[0].clone();
self.push(V::Dict(d));
}
"undefineresource" => {
self.pop();
self.pop();
}
// `[ … /Name pdfmark`: a Distiller annotation; other interpreters discard it.
"pdfmark" => {
while let Some(v) = self.st.pop() {
if matches!(v, V::Mark) {
break;
}
}
}
// `source [params] /Name filter`. A SubFileDecode of the current file with a count of
// 0 reads to its end-of-data string: skip that data (an embedded XMP packet, which
// otherwise ran as PostScript). Other filters read what their source reads.
"filter" => {
let name = self.pop().key();
let mut skipped = false;
let source = if name == "SubFileDecode" && !matches!(self.st.last(), Some(V::Dict(_))) {
let eod = match self.pop() {
V::Str(s) => s.borrow().clone(),
_ => vec![],
};
let count = self.popn();
let source = self.pop();
if matches!(source, V::File) && count == 0.0 && !eod.is_empty() {
src.pos = match find_from(src.d, &eod, src.pos) {
Some(p) => p.saturating_add(eod.len()),
None => src.d.len(),
};
skipped = true;
}
source
} else {
if matches!(self.st.last(), Some(V::Dict(_))) {
self.pop();
}
self.pop()
};
// Data already stepped over leaves nothing for `image` to skip.
self.push(if skipped { V::Null } else { source });
}
"vmstatus" => {
for _ in 0..3 {
self.push(V::Num(0.0));
}
}
_ => return Ok(false),
}
Ok(true)
}
fn try_graphics_op(&mut self, n: &str) -> Result<bool, Flow> {
match n {
// Graphics state.
"gsave" => {
self.spend(self.gs.path.elements().len())?;
let mut g = self.gs.clone();
g.depth = self.b.depth();
self.saved.push(g);
}
"grestore" => self.grestore(),
"grestoreall" => {
while !self.saved.is_empty() {
self.grestore();
}
}
"initgraphics" => {
self.gs.ctm = Affine::IDENTITY;
self.gs.lw = 1.0;
self.gs.color = [0.0; 3];
}
"setlinewidth" => self.gs.lw = self.popn(),
"currentlinewidth" => self.push(V::Num(self.gs.lw)),
"setlinecap" => self.gs.cap = [Cap::Butt, Cap::Round, Cap::Square][(self.popn().max(0.0) as usize).min(2)],
"setlinejoin" => self.gs.join = [Join::Miter, Join::Round, Join::Bevel][(self.popn().max(0.0) as usize).min(2)],
"setmiterlimit" => self.gs.miter = self.popn(),
"setdash" => {
let off = self.popn();
let a: Vec<f64> = self.pop().items().unwrap_or_default().iter().filter_map(V::num).collect();
self.gs.dash = (!a.is_empty() && a.iter().any(|v| *v > 0.0)).then_some((a, off));
}
"setflat" | "setstrokeadjust" | "setoverprint" | "setrenderingintent" | "setcolorrendering" | "sethalftone" | "settransfer" => {
self.pop();
}
// frequency angle proc
"setscreen" => {
for _ in 0..3 {
self.pop();
}
}
"setgray" => {
let g = self.popn().clamp(0.0, 1.0);
self.gs.color = [g; 3];
}
"setrgbcolor" => {
let b = self.popn();
let g = self.popn();
let r = self.popn();
self.gs.color = [r, g, b].map(|v| v.clamp(0.0, 1.0));
}
"sethsbcolor" => {
let v = self.popn().clamp(0.0, 1.0);
let s = self.popn().clamp(0.0, 1.0);
let h = self.popn().rem_euclid(1.0) * 6.0;
let (i, f) = (h.floor() as i32, h.fract());
let (p, q, t) = (v * (1.0 - s), v * (1.0 - s * f), v * (1.0 - s * (1.0 - f)));
self.gs.color = match i {
0 => [v, t, p],
1 => [q, v, p],
2 => [p, v, t],
3 => [p, q, v],
4 => [t, p, v],
_ => [v, p, q],
};
}
"setcmykcolor" => {
let k = self.popn().clamp(0.0, 1.0);
let y = self.popn().clamp(0.0, 1.0);
let m = self.popn().clamp(0.0, 1.0);
let c = self.popn().clamp(0.0, 1.0);
self.gs.color = [(1.0 - c) * (1.0 - k), (1.0 - m) * (1.0 - k), (1.0 - y) * (1.0 - k)];
}
"setcolorspace" => {
self.pop();
}
"setcolor" => {
// Device colour by operand count (patterns are not drawn).
let k = self.st.iter().rev().take_while(|v| matches!(v, V::Num(_))).count().min(4);
let c: Vec<f64> = self.st.split_off(self.st.len() - k).iter().filter_map(V::num).collect();
self.gs.color = match c.len() {
1 => [c[0]; 3],
3 => [c[0], c[1], c[2]],
4 => [(1.0 - c[0]) * (1.0 - c[3]), (1.0 - c[1]) * (1.0 - c[3]), (1.0 - c[2]) * (1.0 - c[3])],
_ => self.gs.color,
};
}
"currentgray" => self.push(V::Num((self.gs.color[0] + self.gs.color[1] + self.gs.color[2]) / 3.0)),
"currentrgbcolor" => {
for c in self.gs.color {
self.push(V::Num(c));
}
}
_ => return Ok(false),
}
Ok(true)
}
fn try_coordinate_op(&mut self, n: &str) -> bool {
match n {
// Coordinates.
"matrix" => self.push(matrix_val(Affine::IDENTITY)),
"identmatrix" | "defaultmatrix" => {
self.pop();
self.push(matrix_val(Affine::IDENTITY));
}
"currentmatrix" => {
self.pop();
self.push(matrix_val(self.gs.ctm));
}
"setmatrix" => {
let m = self.pop();
self.gs.ctm = matrix_of(&m).unwrap_or(Affine::IDENTITY);
}
"concat" => {
let m = self.pop();
if let Some(m) = matrix_of(&m) {
self.gs.ctm *= m;
}
}
"concatmatrix" => {
self.pop();
let b = self.pop();
let a = self.pop();
let (a, b) = (matrix_of(&a).unwrap_or(Affine::IDENTITY), matrix_of(&b).unwrap_or(Affine::IDENTITY));
self.push(matrix_val(b * a));
}
"invertmatrix" => {
self.pop();
let a = self.pop();
self.push(matrix_val(matrix_of(&a).unwrap_or(Affine::IDENTITY).inverse()));
}
"translate" | "scale" => {
let top = self.pop();
let (m_out, y) = match top {
V::Array(_) => (Some(top), self.popn()),
v => (None, v.num().unwrap_or(0.0)),
};
let x = self.popn();
let t = if n == "translate" { Affine::translate((x, y)) } else { Affine::scale_non_uniform(x, y) };
match m_out {
Some(_) => self.push(matrix_val(t)),
None => self.gs.ctm *= t,
}
}
"rotate" => {
let top = self.pop();
match top {
V::Array(_) => {
let a = self.popn();
self.push(matrix_val(Affine::rotate(a.to_radians())));
}
v => self.gs.ctm *= Affine::rotate(v.num().unwrap_or(0.0).to_radians()),
}
}
"transform" | "itransform" | "dtransform" | "idtransform" => {
let top = self.pop();
let (m, y) = match top {
V::Array(_) | V::Proc(_) => (matrix_of(&top).unwrap_or(Affine::IDENTITY), self.popn()),
v => (self.gs.ctm, v.num().unwrap_or(0.0)),
};
let x = self.popn();
let m = if n.starts_with('i') { m.inverse() } else { m };
let p = if n.contains('d') {
let c = m.as_coeffs();
Point::new(c[0] * x + c[2] * y, c[1] * x + c[3] * y)
} else {
m * Point::new(x, y)
};
self.push(V::Num(p.x));
self.push(V::Num(p.y));
}
_ => return false,
}
true
}
fn try_path_op(&mut self, n: &str) -> Result<bool, Flow> {
match n {
// Paths.
"newpath" => {
self.gs.path = BezPath::new();
self.gs.cur = None;
self.gs.start = None;
}
"moveto" => {
let p = self.user_point();
self.move_to(p)?;
}
"rmoveto" => {
let d = self.user_point();
let c = self.current_user();
self.move_to(Point::new(c.x + d.x, c.y + d.y))?;
}
"lineto" => {
let p = self.user_point();
self.line_to(p)?;
}
"rlineto" => {
let d = self.user_point();
let c = self.current_user();
self.line_to(Point::new(c.x + d.x, c.y + d.y))?;
}
"curveto" | "rcurveto" => {
let p3 = self.user_point();
let p2 = self.user_point();
let p1 = self.user_point();
if n == "rcurveto" {
let c = self.current_user();
let o = |p: Point| Point::new(c.x + p.x, c.y + p.y);
self.curve_to(o(p1), o(p2), o(p3))?;
} else {
self.curve_to(p1, p2, p3)?;
}
}
"closepath" => {
if self.gs.cur.is_some() {
self.room(1)?;
self.gs.path.close_path();
self.gs.cur = self.gs.start;
}
}
"arc" => self.arc(false)?,
"arcn" => self.arc(true)?,
"currentpoint" => {
let c = self.current_user();
self.push(V::Num(c.x));
self.push(V::Num(c.y));
}
"pathbbox" => {
self.spend(self.gs.path.elements().len())?;
let r = kurbo::Shape::bounding_box(&self.gs.path);
let inv = self.gs.ctm.inverse();
let (a, b) = (inv * Point::new(r.x0, r.y0), inv * Point::new(r.x1, r.y1));
for v in [a.x.min(b.x), a.y.min(b.y), a.x.max(b.x), a.y.max(b.y)] {
self.push(V::Num(v));
}
}
"flattenpath" | "reversepath" | "strokepath" | "initclip" => {}
"clippath" => {
let p = self.page;
self.gs.path = kurbo::Shape::to_path(&kurbo::Rect::new(p[0], p[1], p[2], p[3]), 0.1);
}
_ => return Ok(false),
}
Ok(true)
}
fn try_paint_op(&mut self, n: &str) -> Result<bool, Flow> {
match n {
// Painting.
"fill" => self.paint(Some(FillRule::NonZero), false),
"eofill" => self.paint(Some(FillRule::EvenOdd), false),
"stroke" => self.paint(None, true),
"clip" | "eoclip" => {
let rule = if n == "clip" { FillRule::NonZero } else { FillRule::EvenOdd };
self.spend(self.gs.path.elements().len())?;
let p = self.gs.path.clone();
if !p.elements().iter().all(|e| matches!(e, PathEl::MoveTo(_))) {
self.b.clip(p, rule);
}
}
"rectfill" | "rectstroke" | "rectclip" => {
let paths = self.rect_path()?;
if n == "rectstroke" && matches!(self.st.last(), Some(V::Array(_))) {
self.pop();
}
let mut all = BezPath::new();
for p in paths {
all.extend(p);
}
match n {
"rectclip" => self.b.clip(all, FillRule::NonZero),
_ => {
let saved = std::mem::replace(&mut self.gs.path, all);
self.paint(if n == "rectfill" { Some(FillRule::NonZero) } else { None }, n == "rectstroke");
self.gs.path = saved;
}
}
}
"shfill" => {
self.pop();
self.b.skip("smooth shading (shfill)");
}
_ => return Ok(false),
}
Ok(true)
}
}