mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-28 20:16:52 -06:00
Refactor pdf for strict Clippy checks
This commit is contained in:
+127
-107
@@ -89,7 +89,7 @@ impl OutlinePen for Pen {
|
||||
|
||||
/// A bundled stand-in for a font that is not embedded, by its PostScript name.
|
||||
fn fallback(base: &str) -> &'static [u8] {
|
||||
use effectcraft_text::fonts::*;
|
||||
use effectcraft_text::fonts::{INTER_BOLD, INTER_ITALIC, INTER_REGULAR, INTER_SEMIBOLD, JETBRAINS_MONO_REGULAR, NOTO_SERIF_REGULAR};
|
||||
let b = base.to_ascii_lowercase();
|
||||
if b.contains("courier") || b.contains("mono") || b.contains("consol") {
|
||||
return JETBRAINS_MONO_REGULAR;
|
||||
@@ -393,89 +393,10 @@ impl Font {
|
||||
}
|
||||
// Widths.
|
||||
if f.composite {
|
||||
f.default_width = file.get_num(&desc_dict, "DW").unwrap_or(1000.0) / 1000.0;
|
||||
if let Some(w) = file.get(&desc_dict, "W").and_then(Obj::array) {
|
||||
let mut i = 0;
|
||||
while i < w.len() {
|
||||
let first = file.resolve(&w[i]).num().unwrap_or(0.0) as u32;
|
||||
match w.get(i + 1).map(|x| file.resolve(x)) {
|
||||
Some(Obj::Array(list)) => {
|
||||
for (k, v) in list.iter().enumerate() {
|
||||
if let Some(cid) = first.checked_add(k as u32) {
|
||||
f.cid_widths.insert(cid, file.resolve(v).num().unwrap_or(0.0) / 1000.0);
|
||||
}
|
||||
}
|
||||
i += 2;
|
||||
}
|
||||
Some(Obj::Num(last)) => {
|
||||
let wv = w.get(i + 2).and_then(|x| file.resolve(x).num()).unwrap_or(0.0) / 1000.0;
|
||||
for c in first..=(*last as u32).min(first.saturating_add(65535)) {
|
||||
f.cid_widths.insert(c, wv);
|
||||
}
|
||||
i += 3;
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
// Vertical metrics (§9.7.4.3): /DW2 [vy w1y] and /W2 entries
|
||||
// `c [w1y vx vy …]` or `cfirst clast w1y vx vy`.
|
||||
let dw2 = file.get(&desc_dict, "DW2").map(|x| file.nums(x)).unwrap_or_default();
|
||||
if dw2.len() == 2 {
|
||||
f.dw2 = (dw2[0] / 1000.0, dw2[1] / 1000.0);
|
||||
}
|
||||
if let Some(w) = file.get(&desc_dict, "W2").and_then(Obj::array) {
|
||||
let num = |i: usize| w.get(i).and_then(|x| file.resolve(x).num());
|
||||
let mut i = 0;
|
||||
while i < w.len() {
|
||||
let Some(first) = num(i) else { break };
|
||||
let first = first.max(0.0) as u32;
|
||||
match w.get(i + 1).map(|x| file.resolve(x)) {
|
||||
Some(Obj::Array(list)) => {
|
||||
let v: Vec<f64> = list.iter().filter_map(|x| file.resolve(x).num()).collect();
|
||||
for (k, t) in v.as_chunks::<3>().0.iter().enumerate() {
|
||||
if let Some(cid) = first.checked_add(k as u32) {
|
||||
f.cid_vmetrics.insert(cid, (t[0] / 1000.0, t[1] / 1000.0, t[2] / 1000.0));
|
||||
}
|
||||
}
|
||||
i += 2;
|
||||
}
|
||||
Some(Obj::Num(last)) => {
|
||||
let (Some(w1), Some(vx), Some(vy)) = (num(i + 2), num(i + 3), num(i + 4)) else { break };
|
||||
for c in first..=(last.max(0.0) as u32).min(first.saturating_add(65535)) {
|
||||
f.cid_vmetrics.insert(c, (w1 / 1000.0, vx / 1000.0, vy / 1000.0));
|
||||
}
|
||||
i += 5;
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
f.load_cid_widths(file, &desc_dict);
|
||||
f.load_vertical_metrics(file, &desc_dict);
|
||||
f.has_widths = true;
|
||||
// Encoding CMap.
|
||||
match file.get(d, "Encoding") {
|
||||
Some(Obj::Name(n)) => {
|
||||
f.vertical = n.ends_with("-V");
|
||||
if !n.starts_with("Identity") {
|
||||
skipped.push(format!("CMap {n}"));
|
||||
}
|
||||
}
|
||||
Some(Obj::Stream(sd, raw)) => {
|
||||
if let Some(data) = decode_stream(file, sd, raw) {
|
||||
let cm = parse_cmap(&data);
|
||||
f.vertical = cm.wmode || file.get_num(sd, "WMode") == Some(1.0);
|
||||
f.codespace = cm.codespace;
|
||||
f.cid_ranges = cm.cids;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
match file.get(&desc_dict, "CIDToGIDMap") {
|
||||
Some(Obj::Stream(sd, raw)) => {
|
||||
f.cid_to_gid = decode_stream(file, sd, raw).map(|b| b.as_chunks::<2>().0.iter().map(|c| u16::from_be_bytes([c[0], c[1]])).collect());
|
||||
}
|
||||
_ => f.cid_to_gid = None,
|
||||
}
|
||||
f.load_cid_encoding(file, d, &desc_dict, skipped);
|
||||
} else {
|
||||
f.first_char = file.get_num(d, "FirstChar").unwrap_or(0.0) as u32;
|
||||
if let Some(w) = file.get(d, "Widths") {
|
||||
@@ -486,16 +407,113 @@ impl Font {
|
||||
f.default_width = file.get_num(fd, "MissingWidth").unwrap_or(0.0);
|
||||
}
|
||||
}
|
||||
f.load_program(file, d, fd.as_ref(), &subtype, skipped);
|
||||
f.load_simple_encoding(file, d);
|
||||
f
|
||||
}
|
||||
|
||||
fn load_cid_widths(&mut self, file: &File, desc_dict: &Dict) {
|
||||
self.default_width = file.get_num(desc_dict, "DW").unwrap_or(1000.0) / 1000.0;
|
||||
if let Some(w) = file.get(desc_dict, "W").and_then(Obj::array) {
|
||||
let mut i = 0;
|
||||
while i < w.len() {
|
||||
let first = file.resolve(&w[i]).num().unwrap_or(0.0) as u32;
|
||||
match w.get(i + 1).map(|x| file.resolve(x)) {
|
||||
Some(Obj::Array(list)) => {
|
||||
for (k, v) in list.iter().enumerate() {
|
||||
if let Some(cid) = first.checked_add(k as u32) {
|
||||
self.cid_widths.insert(cid, file.resolve(v).num().unwrap_or(0.0) / 1000.0);
|
||||
}
|
||||
}
|
||||
i += 2;
|
||||
}
|
||||
Some(Obj::Num(last)) => {
|
||||
let wv = w.get(i + 2).and_then(|x| file.resolve(x).num()).unwrap_or(0.0) / 1000.0;
|
||||
for c in first..=(*last as u32).min(first.saturating_add(65535)) {
|
||||
self.cid_widths.insert(c, wv);
|
||||
}
|
||||
i += 3;
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn load_vertical_metrics(&mut self, file: &File, desc_dict: &Dict) {
|
||||
// Vertical metrics (§9.7.4.3): /DW2 [vy w1y] and /W2 entries
|
||||
// `c [w1y vx vy …]` or `cfirst clast w1y vx vy`.
|
||||
let dw2 = file.get(desc_dict, "DW2").map(|x| file.nums(x)).unwrap_or_default();
|
||||
if dw2.len() == 2 {
|
||||
self.dw2 = (dw2[0] / 1000.0, dw2[1] / 1000.0);
|
||||
}
|
||||
if let Some(w) = file.get(desc_dict, "W2").and_then(Obj::array) {
|
||||
let num = |i: usize| w.get(i).and_then(|x| file.resolve(x).num());
|
||||
let mut i = 0;
|
||||
while i < w.len() {
|
||||
let Some(first) = num(i) else { break };
|
||||
let first = first.max(0.0) as u32;
|
||||
match w.get(i + 1).map(|x| file.resolve(x)) {
|
||||
Some(Obj::Array(list)) => {
|
||||
let v: Vec<f64> = list.iter().filter_map(|x| file.resolve(x).num()).collect();
|
||||
for (k, t) in v.as_chunks::<3>().0.iter().enumerate() {
|
||||
if let Some(cid) = first.checked_add(k as u32) {
|
||||
self.cid_vmetrics.insert(cid, (t[0] / 1000.0, t[1] / 1000.0, t[2] / 1000.0));
|
||||
}
|
||||
}
|
||||
i += 2;
|
||||
}
|
||||
Some(Obj::Num(last)) => {
|
||||
let (Some(w1), Some(vx), Some(vy)) = (num(i + 2), num(i + 3), num(i + 4)) else { break };
|
||||
for c in first..=(last.max(0.0) as u32).min(first.saturating_add(65535)) {
|
||||
self.cid_vmetrics.insert(c, (w1 / 1000.0, vx / 1000.0, vy / 1000.0));
|
||||
}
|
||||
i += 5;
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn load_cid_encoding(&mut self, file: &File, d: &Dict, desc_dict: &Dict, skipped: &mut Vec<String>) {
|
||||
// Encoding CMap.
|
||||
match file.get(d, "Encoding") {
|
||||
Some(Obj::Name(n)) => {
|
||||
self.vertical = n.ends_with("-V");
|
||||
if !n.starts_with("Identity") {
|
||||
skipped.push(format!("CMap {n}"));
|
||||
}
|
||||
}
|
||||
Some(Obj::Stream(sd, raw)) => {
|
||||
if let Some(data) = decode_stream(file, sd, raw) {
|
||||
let cm = parse_cmap(&data);
|
||||
self.vertical = cm.wmode || file.get_num(sd, "WMode") == Some(1.0);
|
||||
self.codespace = cm.codespace;
|
||||
self.cid_ranges = cm.cids;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
match file.get(desc_dict, "CIDToGIDMap") {
|
||||
Some(Obj::Stream(sd, raw)) => {
|
||||
self.cid_to_gid = decode_stream(file, sd, raw).map(|b| b.as_chunks::<2>().0.iter().map(|c| u16::from_be_bytes([c[0], c[1]])).collect());
|
||||
}
|
||||
_ => self.cid_to_gid = None,
|
||||
}
|
||||
}
|
||||
|
||||
fn load_program(&mut self, file: &File, d: &Dict, fd: Option<&Dict>, subtype: &str, skipped: &mut Vec<String>) {
|
||||
// The program.
|
||||
let font_file = |key: &str| -> Option<(Dict, Vec<u8>)> {
|
||||
let fd = fd.as_ref()?;
|
||||
let fd = fd?;
|
||||
match file.get(fd, key) {
|
||||
Some(Obj::Stream(sd, raw)) => Some((sd.clone(), decode_stream(file, sd, raw)?)),
|
||||
_ => None,
|
||||
}
|
||||
};
|
||||
if subtype == "Type3" {
|
||||
f.matrix = file
|
||||
self.matrix = file
|
||||
.get(d, "FontMatrix")
|
||||
.map(|m| file.nums(m))
|
||||
.filter(|m| m.len() == 6)
|
||||
@@ -511,76 +529,78 @@ impl Font {
|
||||
}
|
||||
}
|
||||
}
|
||||
f.program = Program::Type3 { procs, resources: file.get_dict(d, "Resources").cloned() };
|
||||
self.program = Program::Type3 { procs, resources: file.get_dict(d, "Resources").cloned() };
|
||||
// Type 3 widths are in glyph space.
|
||||
let m = f.matrix.as_coeffs()[0];
|
||||
for w in &mut f.widths {
|
||||
let m = self.matrix.as_coeffs()[0];
|
||||
for w in &mut self.widths {
|
||||
*w *= m * 1000.0;
|
||||
}
|
||||
} else if let Some((_, data)) = font_file("FontFile2") {
|
||||
f.program = Program::TrueType(Arc::new(data));
|
||||
self.program = Program::TrueType(Arc::new(data));
|
||||
} else if let Some((sd, data)) = font_file("FontFile3") {
|
||||
match file.get(&sd, "Subtype").and_then(Obj::name) {
|
||||
Some("OpenType") => {
|
||||
// An OpenType wrapper: TrueType outlines through skrifa, CFF through ours.
|
||||
let cff = FontRef::new(&data).ok().and_then(|fr| fr.table_data(skrifa::Tag::new(b"CFF ")).map(|t| t.as_bytes().to_vec()));
|
||||
match cff.and_then(|c| Cff::parse(&c)) {
|
||||
Some(c) => f.program = Program::Cff(Box::new(c)),
|
||||
None => f.program = Program::TrueType(Arc::new(data)),
|
||||
Some(c) => self.program = Program::Cff(Box::new(c)),
|
||||
None => self.program = Program::TrueType(Arc::new(data)),
|
||||
}
|
||||
}
|
||||
_ => match Cff::parse(&data) {
|
||||
Some(c) => f.program = Program::Cff(Box::new(c)),
|
||||
Some(c) => self.program = Program::Cff(Box::new(c)),
|
||||
None => skipped.push("font program (CFF)".into()),
|
||||
},
|
||||
}
|
||||
} else if let Some((_, data)) = font_file("FontFile") {
|
||||
match Type1::parse(&data) {
|
||||
Some(t) => f.program = Program::Type1(Box::new(t)),
|
||||
Some(t) => self.program = Program::Type1(Box::new(t)),
|
||||
None => skipped.push("font program (Type 1)".into()),
|
||||
}
|
||||
} else {
|
||||
f.program = Program::Fallback(fallback(&base_name));
|
||||
self.program = Program::Fallback(fallback(&self.base_name));
|
||||
}
|
||||
if let Program::Cff(c) = &f.program {
|
||||
if let Program::Cff(c) = &self.program {
|
||||
let m = c.font_matrix;
|
||||
f.matrix = Affine::new(m);
|
||||
self.matrix = Affine::new(m);
|
||||
}
|
||||
if let Program::Type1(t) = &f.program
|
||||
if let Program::Type1(t) = &self.program
|
||||
&& let Some(m) = t.font_matrix
|
||||
{
|
||||
f.matrix = Affine::new(m);
|
||||
self.matrix = Affine::new(m);
|
||||
}
|
||||
}
|
||||
|
||||
fn load_simple_encoding(&mut self, file: &File, d: &Dict) {
|
||||
// Simple-font encoding: base encoding, built-in encoding, Differences.
|
||||
if !f.composite {
|
||||
if !self.composite {
|
||||
let enc = file.get(d, "Encoding").map(|e| file.resolve(e));
|
||||
let base = match enc {
|
||||
Some(Obj::Name(n)) => Base::from_name(n),
|
||||
Some(Obj::Dict(e)) => file.get(e, "BaseEncoding").and_then(Obj::name).and_then(Base::from_name),
|
||||
_ => None,
|
||||
};
|
||||
let builtin: Option<Vec<Option<String>>> = match &f.program {
|
||||
let builtin: Option<Vec<Option<String>>> = match &self.program {
|
||||
Program::Type1(t) if !t.encoding.is_empty() => Some((0..256).map(|c| t.encoding.get(&(c as u8)).cloned()).collect()),
|
||||
_ => None,
|
||||
};
|
||||
let fallback_base = if f.symbolic { None } else { Some(Base::Standard) };
|
||||
let fallback_base = if self.symbolic { None } else { Some(Base::Standard) };
|
||||
for c in 0..256usize {
|
||||
f.names[c] = match (base, &builtin) {
|
||||
self.names[c] = match (base, &builtin) {
|
||||
(Some(b), _) => b.name(c as u8),
|
||||
(None, Some(bi)) => bi[c].clone(),
|
||||
(None, None) => match &f.program {
|
||||
(None, None) => match &self.program {
|
||||
// TrueType symbolic fonts map codes directly; others use Standard.
|
||||
Program::TrueType(_) if f.symbolic => None,
|
||||
Program::Fallback(_) if base_name.contains("Symbol") || base_name.contains("Dingbats") => None,
|
||||
Program::TrueType(_) if self.symbolic => None,
|
||||
Program::Fallback(_) if self.base_name.contains("Symbol") || self.base_name.contains("Dingbats") => None,
|
||||
_ => fallback_base.or(Some(Base::Standard)).and_then(|b| b.name(c as u8)),
|
||||
},
|
||||
};
|
||||
}
|
||||
if let Some(Obj::Dict(e)) = enc {
|
||||
differences(file, e, &mut f.names);
|
||||
differences(file, e, &mut self.names);
|
||||
}
|
||||
}
|
||||
f
|
||||
}
|
||||
|
||||
/// Split a string into (code, byte length) pairs.
|
||||
|
||||
+253
-143
@@ -2,10 +2,166 @@ 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::*;
|
||||
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" => {
|
||||
@@ -21,25 +177,27 @@ impl Interp {
|
||||
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;
|
||||
}
|
||||
"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);
|
||||
}
|
||||
drop(d);
|
||||
self.push(V::Array(dst));
|
||||
}
|
||||
other => self.push(other),
|
||||
},
|
||||
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 };
|
||||
@@ -84,6 +242,13 @@ impl Interp {
|
||||
}
|
||||
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();
|
||||
@@ -131,6 +296,13 @@ impl Interp {
|
||||
};
|
||||
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();
|
||||
@@ -181,6 +353,13 @@ impl Interp {
|
||||
"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();
|
||||
@@ -199,82 +378,10 @@ impl Interp {
|
||||
self.exec_obj(if c { t } else { f }, src)?;
|
||||
}
|
||||
// Each pass of a loop counts against the budget.
|
||||
"repeat" => {
|
||||
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?,
|
||||
}
|
||||
}
|
||||
}
|
||||
"for" => {
|
||||
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;
|
||||
}
|
||||
}
|
||||
"loop" => {
|
||||
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?,
|
||||
}
|
||||
}
|
||||
}
|
||||
"forall" => {
|
||||
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?,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"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" => {
|
||||
@@ -288,6 +395,13 @@ impl Interp {
|
||||
}
|
||||
"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();
|
||||
@@ -385,6 +499,13 @@ impl Interp {
|
||||
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;
|
||||
@@ -521,6 +642,13 @@ impl Interp {
|
||||
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" => {
|
||||
@@ -644,6 +772,13 @@ impl Interp {
|
||||
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())?;
|
||||
@@ -733,6 +868,13 @@ impl Interp {
|
||||
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" => {
|
||||
@@ -805,6 +947,13 @@ impl Interp {
|
||||
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();
|
||||
@@ -869,6 +1018,13 @@ impl Interp {
|
||||
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),
|
||||
@@ -903,54 +1059,8 @@ impl Interp {
|
||||
self.pop();
|
||||
self.b.skip("smooth shading (shfill)");
|
||||
}
|
||||
// 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());
|
||||
}
|
||||
}
|
||||
_ => return Ok(false),
|
||||
}
|
||||
Ok(())
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{HashMap, Rc, is_white};
|
||||
|
||||
/// The PostScript section of an EPS file (DOS EPS binary header handled) and its bounding box
|
||||
/// (`%%HiResBoundingBox` preferred).
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use super::eps::embedded_fonts;
|
||||
use super::scanner::Src;
|
||||
use super::value::{Flow, GState, V, new_dict};
|
||||
use super::*;
|
||||
use super::{Affine, BezPath, Builder, Cap, HashMap, Interp, Join};
|
||||
|
||||
/// The deepest procedure nesting run (deeper recursion stops the program).
|
||||
const MAX_CALL_DEPTH: usize = 256;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::value::{Flow, V};
|
||||
use super::*;
|
||||
use super::{Affine, BezPath, FillRule, Interp, MAX_PATH, Paint, PathEl, Point, Stroke};
|
||||
|
||||
/// The angle from `a` counterclockwise to `b` (degrees, 0 to 360), worked out from what is left
|
||||
/// of each after whole turns.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::value::V;
|
||||
use super::*;
|
||||
use super::{Rc, RefCell, ascii85, is_white, parse_num};
|
||||
|
||||
/// Source tokenizer (the data source for `currentfile`).
|
||||
pub(super) struct Src<'a> {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::scanner::Src;
|
||||
use super::value::{DictRef, Flow, V, array, matrix_of, matrix_val, new_dict};
|
||||
use super::*;
|
||||
use super::{Affine, BezPath, FillRule, Font, HashMap, Interp, Paint, Point, Rc, RefCell};
|
||||
|
||||
// ------------------------------------------------------------------ text
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{Affine, BezPath, Cap, HashMap, Join, Point, Rc, RefCell};
|
||||
|
||||
pub(crate) type DictRef = Rc<RefCell<HashMap<String, V>>>;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user