mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-24 09:46:59 -06:00
The PostScript interpreter that reads EPS and PostScript files had no CCITTFaxDecode filter. Reading a file whose image data went through it stopped at an error naming the filter, and the file opened from a fallback, such as its preview or the art drawn before the image, or did not open. CCITTFaxDecode now decodes CCITT Group 4 (ITU-T T.6) and Group 3 one-dimensional and mixed (ITU-T T.4) data to rows of 1-bit samples. It reads K, Columns, Rows, EndOfBlock, EndOfLine, EncodedByteAlign and BlackIs1 (PLRM 3rd ed., §3.13; ISO 32000-2, §7.4.6). With EndOfBlock the data ends at its EOFB or RTC regardless of Rows, and the RTC of mixed Group 3 data, six EOLs each with a tag bit, is found by the filter. Damaged data keeps the rows decoded before it, and damage in the first row is an ioerror. Columns outside 1 to 65,535 and Rows below 0 are a rangecheck, and data that would decode to more than 256 MiB is refused. hayro-ccitt 0.3.0, already in Cargo.lock through hayro-syntax, becomes a dependency of vectorcraft-eps. The tests encode their images in the test, with the fax crate's Group 4 encoder and T.4 run codes (a dev-dependency, already in Cargo.lock through tiff), and fail without the change. The import fuzz tests read a Group 4 and a Group 3 image coded by hand from the T.4 and T.6 tables, read the same file with random damage, and add the filter and its parameters to the hostile programs. Docs: docs/file-format-parity.md, docs/target-app-parity.md.
1289 lines
55 KiB
Rust
1289 lines
55 KiB
Rust
//! Data the program reads: from itself (`currentfile`) through decoding filters, and images
|
||
//! sampled from it, from strings or from procedures.
|
||
|
||
use std::cell::RefCell;
|
||
use std::collections::HashMap;
|
||
use std::rc::Rc;
|
||
|
||
use hayro_ccitt::{DecodeSettings, Decoder, DecoderContext, EncodingMode};
|
||
use vectorcraft_color::{Color, Paint};
|
||
use vectorcraft_doc::cmyk::{Inks, jpeg_components};
|
||
use vectorcraft_doc::{ImageBlob, ImageObject, Node, NodeId, NodeKind, OpacityMask};
|
||
use vectorcraft_geom::Affine;
|
||
|
||
use super::graphics::{Space, process};
|
||
use super::interp::{Interp, matrix_of};
|
||
use super::lex::{Lexer, find, hex_decode};
|
||
use super::obj::{DictRef, Key, Obj, Op, PsError, Res, Shared, ps_err};
|
||
use crate::ps;
|
||
|
||
/// Most bytes one filter or image reads.
|
||
const MAX_DATA: usize = 1 << 28;
|
||
/// Most pixels an image may have.
|
||
const MAX_PIXELS: usize = 1 << 26;
|
||
/// Why an image is missing.
|
||
const TOO_LARGE: &str = "images too large to read were left out";
|
||
/// Most distinct colours an image converts through a tint transform.
|
||
const MAX_TINTS: usize = 1 << 16;
|
||
|
||
/// A file object: the program itself, or data a filter decoded.
|
||
#[derive(Debug)]
|
||
pub(crate) enum Stream {
|
||
Current,
|
||
/// Filters over the program's own data, decoded when first read.
|
||
Pending(Vec<Filter>),
|
||
Buf {
|
||
data: Vec<u8>,
|
||
pos: usize,
|
||
/// The samples are 8-bit gray, RGB or CMYK (1, 3 or 4 channels) whatever the image's
|
||
/// color space: a decoded JPEG.
|
||
rgb: Option<u8>,
|
||
},
|
||
}
|
||
|
||
fn io<T>(at: &str) -> Res<T> {
|
||
ps_err("ioerror", at)
|
||
}
|
||
|
||
/// The bytes `RunLengthDecode` makes of `raw`, and how many it read.
|
||
fn run_length(raw: &[u8]) -> Res<(Vec<u8>, usize)> {
|
||
let mut out = vec![];
|
||
let mut i = 0;
|
||
while let Some(&n) = raw.get(i) {
|
||
i += 1;
|
||
match n {
|
||
128 => break,
|
||
0..=127 => {
|
||
let len = usize::from(n) + 1;
|
||
out.extend_from_slice(raw.get(i..i + len).ok_or(PsError::Ps("ioerror", "RunLengthDecode".into()))?);
|
||
i += len;
|
||
}
|
||
_ => {
|
||
let b = *raw.get(i).ok_or(PsError::Ps("ioerror", "RunLengthDecode".into()))?;
|
||
i += 1;
|
||
out.extend(std::iter::repeat_n(b, 257 - usize::from(n)));
|
||
}
|
||
}
|
||
if out.len() > MAX_DATA {
|
||
return Err(PsError::Limit("decoded data is too large"));
|
||
}
|
||
}
|
||
Ok((out, i))
|
||
}
|
||
|
||
/// zlib data inflated, and how many bytes of `raw` it took.
|
||
fn flate(raw: &[u8]) -> Res<(Vec<u8>, usize)> {
|
||
let mut z = flate2::Decompress::new(true);
|
||
let mut out = Vec::with_capacity(raw.len().saturating_mul(3).min(MAX_DATA));
|
||
loop {
|
||
let before = (z.total_in(), z.total_out());
|
||
if out.capacity() - out.len() < 64 * 1024 {
|
||
out.reserve(256 * 1024);
|
||
}
|
||
let input = raw.get(z.total_in() as usize..).unwrap_or_default();
|
||
let status = z.decompress_vec(input, &mut out, flate2::FlushDecompress::None).map_err(|_| PsError::Ps("ioerror", "FlateDecode".into()))?;
|
||
if out.len() > MAX_DATA {
|
||
return Err(PsError::Limit("decoded data is too large"));
|
||
}
|
||
match status {
|
||
flate2::Status::StreamEnd => break,
|
||
_ if (z.total_in(), z.total_out()) == before => break,
|
||
_ => {}
|
||
}
|
||
}
|
||
Ok((out, z.total_in() as usize))
|
||
}
|
||
|
||
/// LZW data (`EarlyChange` 1) decoded, and how many bytes of `raw` it took.
|
||
fn lzw(raw: &[u8]) -> Res<(Vec<u8>, usize)> {
|
||
let mut table: Vec<Vec<u8>> = (0..=255u8).map(|b| vec![b]).collect();
|
||
table.push(vec![]);
|
||
table.push(vec![]);
|
||
let (mut out, mut prev): (Vec<u8>, Option<Vec<u8>>) = (vec![], None);
|
||
let (mut acc, mut bits, mut width, mut i) = (0u32, 0u32, 9u32, 0usize);
|
||
loop {
|
||
while bits < width {
|
||
let Some(&b) = raw.get(i) else { return Ok((out, i)) };
|
||
i += 1;
|
||
acc = (acc << 8) | u32::from(b);
|
||
bits += 8;
|
||
}
|
||
let code = ((acc >> (bits - width)) & ((1 << width) - 1)) as usize;
|
||
bits -= width;
|
||
match code {
|
||
256 => {
|
||
table.truncate(258);
|
||
width = 9;
|
||
prev = None;
|
||
}
|
||
257 => return Ok((out, i)),
|
||
_ => {
|
||
let entry = match (table.get(code), &prev) {
|
||
(Some(e), _) if !e.is_empty() => e.clone(),
|
||
(_, Some(p)) if code == table.len() => {
|
||
let mut e = p.clone();
|
||
e.extend(p.first());
|
||
e
|
||
}
|
||
_ => return io("LZWDecode"),
|
||
};
|
||
out.extend_from_slice(&entry);
|
||
if let Some(mut p) = prev.take()
|
||
&& table.len() < 4096
|
||
{
|
||
p.extend(entry.first());
|
||
table.push(p);
|
||
}
|
||
width = match table.len() + 1 {
|
||
n if n >= 2048 => 12,
|
||
n if n >= 1024 => 11,
|
||
n if n >= 512 => 10,
|
||
_ => 9,
|
||
};
|
||
prev = Some(entry);
|
||
if out.len() > MAX_DATA {
|
||
return Err(PsError::Limit("decoded data is too large"));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// A JPEG's pixels (gray, RGB or CMYK, 8 bits) with its channel count, and how many bytes of
|
||
/// `raw` it took. A CMYK JPEG gives its samples as stored, for the image's Decode array to read
|
||
/// (Adobe's CMYK JPEGs store them inverted, which a Decode array of `[1 0 1 0 1 0 1 0]` undoes).
|
||
fn dct(raw: &[u8]) -> Res<(Vec<u8>, usize, u8)> {
|
||
let end = find(raw, &[0xFF, 0xD9]).map_or(raw.len(), |e| e + 2);
|
||
let jpeg = raw.get(..end).unwrap_or_default();
|
||
// `cmyk` gives the ink amounts of inverted samples: inverted back, they are the samples.
|
||
if jpeg_components(jpeg) == Some(4)
|
||
&& let Some(inks) = ImageBlob::new("image/jpeg", jpeg.to_vec()).cmyk()
|
||
{
|
||
return Ok((inks.data.into_iter().map(|v| 255 - v).collect(), end, 4));
|
||
}
|
||
let img = image::load_from_memory_with_format(jpeg, image::ImageFormat::Jpeg).map_err(|_| PsError::Ps("ioerror", "DCTDecode".into()))?;
|
||
if img.color().channel_count() == 1 { Ok((img.to_luma8().into_raw(), end, 1)) } else { Ok((img.to_rgb8().into_raw(), end, 3)) }
|
||
}
|
||
|
||
/// CCITT fax data (ITU-T T.4 Group 3, one- or two-dimensional, and T.6 Group 4) decoded to rows
|
||
/// of 1-bit samples, each padded to a whole byte, and how many bytes of `raw` it took. With
|
||
/// `EndOfBlock` the rows end at the end-of-block code (EOFB, or RTC in Group 3) regardless of
|
||
/// `Rows`; without it they end after `Rows` rows, or at the end of the data when `Rows` is 0.
|
||
/// Damaged data ends the rows: those decoded before it are kept, and damage in the first row is an
|
||
/// `ioerror`. Rows that would take more than `MAX_DATA` bytes are refused.
|
||
fn fax(raw: &[u8], settings: DecodeSettings) -> Res<(Vec<u8>, usize)> {
|
||
let row = (settings.columns as usize).div_ceil(8).max(1);
|
||
let most = u32::try_from(MAX_DATA / row).unwrap_or(u32::MAX);
|
||
if !settings.end_of_block && settings.rows > most {
|
||
return Err(PsError::Limit("decoded data is too large"));
|
||
}
|
||
// Without a row count that ends the data, a row past `most` shows that the data is too large.
|
||
let rows = if settings.end_of_block || settings.rows == 0 { most.saturating_add(1) } else { settings.rows };
|
||
// The decoder reads the RTC of Group 3 two-dimensional data as rows: it gets the data before it.
|
||
let rtc = match settings.encoding {
|
||
EncodingMode::Group3_2D { .. } if settings.end_of_block => rtc(raw),
|
||
_ => None,
|
||
};
|
||
let data = rtc.and_then(|(cut, _)| raw.get(..cut)).unwrap_or(raw);
|
||
let mut out = FaxRows::default();
|
||
let read = hayro_ccitt::decode(data, &mut out, &mut DecoderContext::new(DecodeSettings { rows, ..settings }));
|
||
if out.rows > most {
|
||
return Err(PsError::Limit("decoded data is too large"));
|
||
}
|
||
let used = match read {
|
||
Ok(used) => used,
|
||
Err(_) if out.rows > 0 => {
|
||
out.data.truncate(out.rows as usize * row);
|
||
// The whole rows decoded again, for how many bytes they took.
|
||
let used = hayro_ccitt::decode(data, &mut Dropped, &mut DecoderContext::new(DecodeSettings { rows: out.rows, ..settings }));
|
||
used.unwrap_or(data.len())
|
||
}
|
||
Err(_) => return io("CCITTFaxDecode"),
|
||
};
|
||
Ok((out.data, rtc.map_or(used, |(_, end)| end)))
|
||
}
|
||
|
||
/// The RTC that ends Group 3 two-dimensional data (six EOLs, each followed by a 1 tag bit, ITU-T
|
||
/// T.4 §4.2.4): the byte that holds the 1 bit of its first EOL, and the byte after its last tag
|
||
/// bit. The first EOL with a 1 tag bit that another EOL follows starts it.
|
||
fn rtc(raw: &[u8]) -> Option<(usize, usize)> {
|
||
let bits = raw.len().saturating_mul(8);
|
||
let bit = |i: usize| raw.get(i / 8).is_some_and(|b| b & (0x80 >> (i % 8)) != 0);
|
||
let zeros = |i: usize| (i..bits).take_while(|j| !bit(*j)).count();
|
||
// An EOL (its fill bits first) and a 1 tag bit from `i`: the EOL's 1 bit and where they end.
|
||
let eol = |i: usize| {
|
||
let one = i + zeros(i);
|
||
(one - i >= 11 && bit(one) && bit(one + 1)).then_some((one, one + 2))
|
||
};
|
||
let mut i = 0;
|
||
while i < bits {
|
||
if let Some((one, next)) = eol(i)
|
||
&& let Some((_, mut end)) = eol(next)
|
||
{
|
||
for _ in 2..6 {
|
||
match eol(end) {
|
||
Some((_, e)) => end = e,
|
||
None => break,
|
||
}
|
||
}
|
||
return Some((one / 8, end.div_ceil(8)));
|
||
}
|
||
i += zeros(i) + 1;
|
||
}
|
||
None
|
||
}
|
||
|
||
/// Rows of 1-bit samples from a CCITT decoder, each padded to a whole byte: 1 is white, or black
|
||
/// with `BlackIs1`.
|
||
#[derive(Default)]
|
||
struct FaxRows {
|
||
data: Vec<u8>,
|
||
/// Samples in the row being decoded.
|
||
x: u32,
|
||
/// Whole rows decoded.
|
||
rows: u32,
|
||
}
|
||
|
||
impl Decoder for FaxRows {
|
||
fn push_pixel(&mut self, white: bool) {
|
||
if self.x.is_multiple_of(8) {
|
||
self.data.push(0);
|
||
}
|
||
if white && let Some(b) = self.data.last_mut() {
|
||
*b |= 0x80 >> (self.x % 8);
|
||
}
|
||
self.x = self.x.saturating_add(1);
|
||
}
|
||
|
||
fn push_pixel_chunk(&mut self, white: bool, chunk_count: u32) {
|
||
self.data.extend(std::iter::repeat_n(if white { 0xff } else { 0 }, chunk_count as usize));
|
||
self.x = self.x.saturating_add(chunk_count.saturating_mul(8));
|
||
}
|
||
|
||
fn next_line(&mut self) {
|
||
self.x = 0;
|
||
self.rows = self.rows.saturating_add(1);
|
||
}
|
||
}
|
||
|
||
/// A CCITT decoder that drops what it decodes.
|
||
struct Dropped;
|
||
|
||
impl Decoder for Dropped {
|
||
fn push_pixel(&mut self, _: bool) {}
|
||
|
||
fn push_pixel_chunk(&mut self, _: bool, _: u32) {}
|
||
|
||
fn next_line(&mut self) {}
|
||
}
|
||
|
||
/// A decoding filter and its parameters.
|
||
#[derive(Clone, Debug)]
|
||
pub(crate) struct Filter {
|
||
name: Rc<str>,
|
||
/// `SubFileDecode`'s count and end-of-data string.
|
||
sub: (usize, Vec<u8>),
|
||
/// `FlateDecode`'s and `LZWDecode`'s `Predictor`: what it undoes.
|
||
predictor: Option<Predictor>,
|
||
/// `CCITTFaxDecode`'s parameters.
|
||
fax: Option<DecodeSettings>,
|
||
}
|
||
|
||
/// A `Predictor` (2: TIFF, 10–15: PNG) with its `Colors`, `BitsPerComponent` and `Columns`.
|
||
#[derive(Clone, Copy, Debug)]
|
||
struct Predictor {
|
||
kind: u8,
|
||
colors: usize,
|
||
bpc: usize,
|
||
columns: usize,
|
||
}
|
||
|
||
impl Predictor {
|
||
/// The predictor of a filter's parameters, if it has one.
|
||
fn of(d: &DictRef) -> Res<Option<Self>> {
|
||
let num = |k: &str, default: f64| d.borrow().get(&Key::name(k)).and_then(Obj::as_num).unwrap_or(default);
|
||
let kind = num("Predictor", 1.0);
|
||
if kind <= 1.0 {
|
||
return Ok(None);
|
||
}
|
||
let (colors, bpc, columns) = (num("Colors", 1.0), num("BitsPerComponent", 8.0), num("Columns", 1.0));
|
||
if !(kind == 2.0 || (10.0..=15.0).contains(&kind))
|
||
|| !(1.0..=32.0).contains(&colors)
|
||
|| ![1.0, 2.0, 4.0, 8.0, 16.0].contains(&bpc)
|
||
|| !(1.0..=1e6).contains(&columns)
|
||
{
|
||
return ps_err("rangecheck", "Predictor");
|
||
}
|
||
Ok(Some(Self { kind: kind as u8, colors: colors as usize, bpc: bpc as usize, columns: columns as usize }))
|
||
}
|
||
|
||
/// The data the predictor made `data` from.
|
||
fn undo(self, data: &[u8]) -> Vec<u8> {
|
||
// A row longer than the data is the data.
|
||
let row = (self.colors * self.bpc * self.columns).div_ceil(8).min(data.len().max(1));
|
||
// Bytes per pixel (at least one) is how far back the left neighbour is.
|
||
let bpp = (self.colors * self.bpc).div_ceil(8).max(1);
|
||
if self.kind == 2 {
|
||
let mut out = data.to_vec();
|
||
// Horizontal differencing, for 8-bit components (the common case).
|
||
if self.bpc == 8 {
|
||
for line in out.chunks_mut(row.max(1)) {
|
||
for i in bpp..line.len() {
|
||
let left = line.get(i - bpp).copied().unwrap_or(0);
|
||
if let Some(b) = line.get_mut(i) {
|
||
*b = b.wrapping_add(left);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return out;
|
||
}
|
||
// PNG: each row starts with its filter type.
|
||
let mut out: Vec<u8> = Vec::with_capacity(data.len());
|
||
let mut prev = vec![0u8; row];
|
||
for chunk in data.chunks(row + 1) {
|
||
let Some((&kind, line)) = chunk.split_first() else { break };
|
||
let mut cur = line.to_vec();
|
||
cur.resize(row, 0);
|
||
for i in 0..row {
|
||
let a = if i >= bpp { cur.get(i - bpp).copied().unwrap_or(0) } else { 0 };
|
||
let b = prev.get(i).copied().unwrap_or(0);
|
||
let c = if i >= bpp { prev.get(i - bpp).copied().unwrap_or(0) } else { 0 };
|
||
let add = match kind {
|
||
1 => a,
|
||
2 => b,
|
||
3 => ((u16::from(a) + u16::from(b)) / 2) as u8,
|
||
4 => paeth(a, b, c),
|
||
_ => 0,
|
||
};
|
||
if let Some(v) = cur.get_mut(i) {
|
||
*v = v.wrapping_add(add);
|
||
}
|
||
}
|
||
out.extend_from_slice(cur.get(..line.len().min(row)).unwrap_or_default());
|
||
prev = cur;
|
||
}
|
||
out
|
||
}
|
||
}
|
||
|
||
/// The PNG Paeth predictor.
|
||
fn paeth(a: u8, b: u8, c: u8) -> u8 {
|
||
let (ia, ib, ic) = (i16::from(a), i16::from(b), i16::from(c));
|
||
let p = ia + ib - ic;
|
||
let (pa, pb, pc) = ((p - ia).abs(), (p - ib).abs(), (p - ic).abs());
|
||
if pa <= pb && pa <= pc {
|
||
a
|
||
} else if pb <= pc {
|
||
b
|
||
} else {
|
||
c
|
||
}
|
||
}
|
||
|
||
/// `CCITTFaxDecode`'s parameters (PLRM 3rd ed., §3.13, `CCITTFaxDecode` parameters; ISO 32000-2,
|
||
/// §7.4.6): `K` (below 0 Group 4, 0 Group 3 one-dimensional, above 0 Group 3 mixed), `Columns`,
|
||
/// `Rows`, `EndOfBlock`, `EndOfLine`, `EncodedByteAlign` and `BlackIs1`, with their defaults where
|
||
/// `d` leaves them out. `Columns` outside 1 to 65,535 and `Rows` below 0 are a `rangecheck`.
|
||
fn fax_settings(d: Option<&DictRef>) -> Res<DecodeSettings> {
|
||
let get = |k: &str| d.and_then(|d| d.borrow().get(&Key::name(k)).cloned());
|
||
let num = |k: &str, default: f64| get(k).and_then(|o| o.as_num()).unwrap_or(default);
|
||
let flag = |k: &str, default: bool| match get(k) {
|
||
Some(Obj::Bool(b)) => b,
|
||
_ => default,
|
||
};
|
||
let (k, columns, rows) = (num("K", 0.0), num("Columns", 1728.0), num("Rows", 0.0));
|
||
if !k.is_finite() || !(1.0..=65_535.0).contains(&columns) || !(0.0..=f64::from(u32::MAX)).contains(&rows) {
|
||
return ps_err("rangecheck", "CCITTFaxDecode");
|
||
}
|
||
let encoding = if k < 0.0 {
|
||
EncodingMode::Group4
|
||
} else if k == 0.0 {
|
||
EncodingMode::Group3_1D
|
||
} else {
|
||
EncodingMode::Group3_2D { k: k.min(f64::from(u32::MAX)) as u32 }
|
||
};
|
||
Ok(DecodeSettings {
|
||
columns: columns as u32,
|
||
rows: rows as u32,
|
||
end_of_block: flag("EndOfBlock", true),
|
||
end_of_line: flag("EndOfLine", false),
|
||
rows_are_byte_aligned: flag("EncodedByteAlign", false),
|
||
encoding,
|
||
invert_black: flag("BlackIs1", false),
|
||
})
|
||
}
|
||
|
||
/// The bytes `f` decodes from `raw`, how many it read, and the channels of a decoded JPEG.
|
||
fn decode(f: &Filter, raw: &[u8]) -> Res<(Vec<u8>, usize, Option<u8>)> {
|
||
let (data, used) = match &*f.name {
|
||
"ASCIIHexDecode" => {
|
||
let end = raw.iter().position(|b| *b == b'>');
|
||
let body = raw.get(..end.unwrap_or(raw.len())).unwrap_or_default();
|
||
(hex_decode(body).ok_or(PsError::Ps("ioerror", "ASCIIHexDecode".into()))?, end.map_or(raw.len(), |e| e + 1))
|
||
}
|
||
"ASCII85Decode" => {
|
||
let end = find(raw, b"~>");
|
||
let mut text = String::from_utf8_lossy(raw.get(..end.unwrap_or(raw.len())).unwrap_or_default()).into_owned();
|
||
text.push_str("~>");
|
||
(ps::ascii85_decode(&text).ok_or(PsError::Ps("ioerror", "ASCII85Decode".into()))?, end.map_or(raw.len(), |e| e + 2))
|
||
}
|
||
"RunLengthDecode" => run_length(raw)?,
|
||
"FlateDecode" | "LZWDecode" => {
|
||
let (data, used) = if &*f.name == "FlateDecode" { flate(raw)? } else { lzw(raw)? };
|
||
match f.predictor {
|
||
Some(p) => (p.undo(&data), used),
|
||
None => (data, used),
|
||
}
|
||
}
|
||
"DCTDecode" => {
|
||
let (d, used, channels) = dct(raw)?;
|
||
return Ok((d, used, Some(channels)));
|
||
}
|
||
"CCITTFaxDecode" => match f.fax {
|
||
Some(settings) => fax(raw, settings)?,
|
||
None => return io("CCITTFaxDecode"),
|
||
},
|
||
"SubFileDecode" => {
|
||
let (count, eod) = &f.sub;
|
||
if eod.is_empty() {
|
||
// A count of 0 without an end string sets no end: the data passes through whole.
|
||
let n = if *count == 0 { raw.len() } else { (*count).min(raw.len()) };
|
||
(raw.get(..n).unwrap_or_default().to_vec(), n)
|
||
} else {
|
||
let (mut at, mut seen) = (0, 0);
|
||
loop {
|
||
match find(raw.get(at..).unwrap_or_default(), eod) {
|
||
Some(i) if seen < *count => {
|
||
seen += 1;
|
||
at += i + eod.len();
|
||
}
|
||
Some(i) => break (raw.get(..at + i).unwrap_or_default().to_vec(), at + i + eod.len()),
|
||
None => break (raw.to_vec(), raw.len()),
|
||
}
|
||
}
|
||
}
|
||
}
|
||
// Reads all of its source (up to the end of the filters below it) and keeps it.
|
||
"NullEncode" | "ReusableStreamDecode" => (raw.to_vec(), raw.len()),
|
||
other => return ps_err("undefined", other),
|
||
};
|
||
Ok((data, used, None))
|
||
}
|
||
|
||
/// What a source of image data gives.
|
||
#[derive(Clone)]
|
||
enum Source {
|
||
File(Rc<RefCell<Stream>>),
|
||
Str(Vec<u8>),
|
||
Proc(Obj),
|
||
}
|
||
|
||
impl Interp<'_> {
|
||
pub fn data_op(&mut self, op: Op) -> Res {
|
||
use Op::*;
|
||
match op {
|
||
CurrentFile => self.push(Obj::File { stream: Rc::new(RefCell::new(Stream::Current)), exec: false })?,
|
||
Filter => self.filter()?,
|
||
ReadHexString | ReadString => {
|
||
let s = self.pop_str()?;
|
||
let Obj::File { stream: f, .. } = self.pop()? else { return ps_err("typecheck", "") };
|
||
let n = s.len();
|
||
let got = if op == ReadString { self.read(&f, n)? } else { self.read_hex(&f, n)? };
|
||
self.fill(s, &got)?;
|
||
self.push(Obj::Bool(got.len() == n))?;
|
||
}
|
||
ReadLine => {
|
||
let s = self.pop_str()?;
|
||
let Obj::File { stream: f, .. } = self.pop()? else { return ps_err("typecheck", "") };
|
||
let n = s.len();
|
||
let mut line = vec![];
|
||
let mut ended = false;
|
||
while line.len() < n {
|
||
match self.read(&f, 1)?.first() {
|
||
Some(b'\n') | None => {
|
||
ended = true;
|
||
break;
|
||
}
|
||
Some(b'\r') => {
|
||
ended = true;
|
||
break;
|
||
}
|
||
Some(b) => line.push(*b),
|
||
}
|
||
}
|
||
self.fill(s, &line)?;
|
||
self.push(Obj::Bool(ended))?;
|
||
}
|
||
FlushFile => {
|
||
// An input file is read to its end: a filter over the program's data skips it
|
||
// (metadata read with `flushfile`). The program's own file goes on being run.
|
||
if let Obj::File { stream, .. } = self.pop()? {
|
||
self.materialize(&stream)?;
|
||
}
|
||
}
|
||
CloseFile => {
|
||
self.pop()?;
|
||
}
|
||
File => {
|
||
self.pop()?;
|
||
let name = self.pop()?.text().unwrap_or_else(|| Rc::from(""));
|
||
// The standard files take what programs print (and give nothing); there is no
|
||
// file system to open others in.
|
||
if !matches!(&*name, "%stdout" | "%stderr" | "%stdin" | "%lineedit" | "%statementedit") {
|
||
return ps_err("invalidfileaccess", "file");
|
||
}
|
||
self.push(Obj::File { stream: Rc::new(RefCell::new(Stream::Buf { data: vec![], pos: 0, rgb: None })), exec: false })?;
|
||
}
|
||
Status => match self.pop()? {
|
||
Obj::File { .. } => self.push(Obj::Bool(true))?,
|
||
// A file name: there are no files.
|
||
Obj::Str(_) => self.push(Obj::Bool(false))?,
|
||
_ => return ps_err("typecheck", ""),
|
||
},
|
||
BytesAvailable => {
|
||
let Obj::File { stream: f, .. } = self.pop()? else { return ps_err("typecheck", "") };
|
||
self.materialize(&f)?;
|
||
let n = match &*f.borrow() {
|
||
Stream::Current => self.lex.rest().len(),
|
||
Stream::Buf { data, pos, .. } => data.len().saturating_sub(*pos),
|
||
Stream::Pending(_) => 0,
|
||
};
|
||
self.push(Obj::Int(n as i64))?;
|
||
}
|
||
ResetFile => {
|
||
// A decoded (reusable) stream reads again from its start.
|
||
if let Obj::File { stream: f, .. } = self.pop()?
|
||
&& let Stream::Buf { pos, .. } = &mut *f.borrow_mut()
|
||
{
|
||
*pos = 0;
|
||
}
|
||
}
|
||
Write | WriteString | WriteHexString => {
|
||
self.pop()?;
|
||
self.pop()?;
|
||
}
|
||
Read => {
|
||
let Obj::File { stream: f, .. } = self.pop()? else { return ps_err("typecheck", "") };
|
||
match self.read(&f, 1)?.first() {
|
||
Some(b) => {
|
||
self.push(Obj::Int(i64::from(*b)))?;
|
||
self.push(Obj::Bool(true))?;
|
||
}
|
||
None => self.push(Obj::Bool(false))?,
|
||
}
|
||
}
|
||
FilePosition => {
|
||
let Obj::File { stream: f, .. } = self.pop()? else { return ps_err("typecheck", "") };
|
||
let Stream::Buf { pos, .. } = &*f.borrow() else { return ps_err("ioerror", "") };
|
||
self.push(Obj::Int(*pos as i64))?;
|
||
}
|
||
SetFilePosition => {
|
||
let at = self.pop_count()?;
|
||
let Obj::File { stream: f, .. } = self.pop()? else { return ps_err("typecheck", "") };
|
||
let Stream::Buf { data, pos, .. } = &mut *f.borrow_mut() else { return ps_err("ioerror", "") };
|
||
*pos = at.min(data.len());
|
||
}
|
||
// There are no files to delete, rename or run.
|
||
DeleteFile | Run => {
|
||
self.pop_str()?;
|
||
return ps_err("undefinedfilename", "");
|
||
}
|
||
RenameFile => {
|
||
self.pop_str()?;
|
||
self.pop_str()?;
|
||
return ps_err("undefinedfilename", "");
|
||
}
|
||
Token => self.token()?,
|
||
Eexec => self.eexec()?,
|
||
Image | ImageMask | ColorImage => self.image(op)?,
|
||
_ => self.text_op(op)?,
|
||
}
|
||
Ok(())
|
||
}
|
||
|
||
/// `token`: the next object read from a string (and the rest of it) or a file.
|
||
fn token(&mut self) -> Res {
|
||
match self.pop()? {
|
||
Obj::Str(s) => {
|
||
let bytes = s.to_vec();
|
||
let mut lex = Lexer::new(&bytes);
|
||
let Some(o) = lex.next()? else { return self.push(Obj::Bool(false)) };
|
||
let o = self.scanned(o, lex.immediate)?;
|
||
// The whitespace after the token is read with it.
|
||
let used = bytes.len() - lex.data().len();
|
||
self.push_interval(Obj::Str(s), used, bytes.len() - used)?;
|
||
self.push(o)?;
|
||
}
|
||
Obj::File { stream: f, .. } => {
|
||
self.materialize(&f)?;
|
||
let (o, immediate) = match &mut *f.borrow_mut() {
|
||
Stream::Current => (self.lex.next()?, self.lex.immediate),
|
||
Stream::Buf { data, pos, .. } => {
|
||
let rest = data.get(*pos..).unwrap_or_default();
|
||
let mut lex = Lexer::new(rest);
|
||
let o = lex.next()?;
|
||
*pos += rest.len() - lex.data().len();
|
||
(o, lex.immediate)
|
||
}
|
||
Stream::Pending(_) => (None, false),
|
||
};
|
||
let Some(o) = o else { return self.push(Obj::Bool(false)) };
|
||
let o = self.scanned(o, immediate)?;
|
||
self.push(o)?;
|
||
}
|
||
_ => return ps_err("typecheck", "token"),
|
||
}
|
||
self.push(Obj::Bool(true))
|
||
}
|
||
|
||
/// Up to `n` bytes of file `f`.
|
||
fn read(&mut self, f: &Rc<RefCell<Stream>>, n: usize) -> Res<Vec<u8>> {
|
||
self.materialize(f)?;
|
||
Ok(match &mut *f.borrow_mut() {
|
||
Stream::Current => {
|
||
let rest = self.lex.data();
|
||
let k = n.min(rest.len());
|
||
let v = rest.get(..k).unwrap_or_default().to_vec();
|
||
self.lex.advance(k);
|
||
v
|
||
}
|
||
Stream::Buf { data, pos, .. } => {
|
||
let start = (*pos).min(data.len());
|
||
let end = start.saturating_add(n).min(data.len());
|
||
*pos = end;
|
||
data.get(start..end).unwrap_or_default().to_vec()
|
||
}
|
||
Stream::Pending(_) => vec![],
|
||
})
|
||
}
|
||
|
||
/// Up to `n` bytes read as hexadecimal digits from `f` (other characters skipped).
|
||
fn read_hex(&mut self, f: &Rc<RefCell<Stream>>, n: usize) -> Res<Vec<u8>> {
|
||
let mut out = Vec::with_capacity(n);
|
||
let mut high: Option<u8> = None;
|
||
while out.len() < n {
|
||
let Some(&b) = self.read(f, 1)?.first() else { break };
|
||
let Some(v) = char::from(b).to_digit(16) else { continue };
|
||
match high.take() {
|
||
Some(h) => out.push(h << 4 | v as u8),
|
||
None => high = Some(v as u8),
|
||
}
|
||
}
|
||
Ok(out)
|
||
}
|
||
|
||
/// Read bytes `got` into string `s` and push the part of `s` they fill.
|
||
fn fill(&mut self, s: Shared<u8>, got: &[u8]) -> Res {
|
||
if !s.write(0, got) {
|
||
return ps_err("rangecheck", "");
|
||
}
|
||
self.push_interval(Obj::Str(s), 0, got.len())
|
||
}
|
||
|
||
/// `filter`: a file that decodes its source. Over the program's own data it decodes when
|
||
/// first read (the data follows the operator that reads it); over other data, at once.
|
||
fn filter(&mut self) -> Res {
|
||
let name = self.pop()?.text().ok_or(PsError::Ps("typecheck", "filter".into()))?;
|
||
let sub = if &*name == "SubFileDecode" {
|
||
let eod = self.pop_str()?.to_vec();
|
||
let count = self.pop_count()?;
|
||
(count, eod)
|
||
} else {
|
||
(0, vec![])
|
||
};
|
||
let (mut predictor, mut params) = (None, None);
|
||
if let Some(Obj::Dict(d)) = self.stack.last().cloned() {
|
||
self.pop()?;
|
||
predictor = Predictor::of(&d)?;
|
||
params = Some(d);
|
||
}
|
||
let fax = if &*name == "CCITTFaxDecode" { Some(fax_settings(params.as_ref())?) } else { None };
|
||
let spec_name = name.clone();
|
||
let spec = Filter { name, sub, predictor, fax };
|
||
let stream = match self.pop()? {
|
||
Obj::File { stream: f, .. } => {
|
||
if let Stream::Buf { data, pos, .. } = &*f.borrow() {
|
||
self.alloc(data.len().saturating_sub(*pos))?;
|
||
}
|
||
let chain = match &*f.borrow() {
|
||
Stream::Current => Some(vec![]),
|
||
Stream::Pending(c) => Some(c.clone()),
|
||
Stream::Buf { .. } => None,
|
||
};
|
||
match chain {
|
||
Some(mut c) => {
|
||
// The new filter reads a pending one's data: it has none left of its own
|
||
// (`flushfile` on it skips nothing more).
|
||
if !c.is_empty() {
|
||
*f.borrow_mut() = Stream::Buf { data: vec![], pos: 0, rgb: None };
|
||
}
|
||
c.push(spec);
|
||
Stream::Pending(c)
|
||
}
|
||
None => {
|
||
let mut b = f.borrow_mut();
|
||
let Stream::Buf { data, pos, .. } = &mut *b else { return io("filter") };
|
||
let (out, used, rgb) = decode(&spec, data.get(*pos..).unwrap_or_default())?;
|
||
*pos = pos.saturating_add(used);
|
||
Stream::Buf { data: out, pos: 0, rgb }
|
||
}
|
||
}
|
||
}
|
||
Obj::Str(s) => {
|
||
self.alloc(s.borrow().len())?;
|
||
let (data, _, rgb) = decode(&spec, &s.borrow())?;
|
||
Stream::Buf { data, pos: 0, rgb }
|
||
}
|
||
p @ Obj::Array { .. } => {
|
||
let raw = self.gather(&p, MAX_DATA)?;
|
||
let (data, _, rgb) = decode(&spec, &raw)?;
|
||
Stream::Buf { data, pos: 0, rgb }
|
||
}
|
||
_ => return ps_err("typecheck", "filter"),
|
||
};
|
||
let stream = Rc::new(RefCell::new(stream));
|
||
// A reusable stream reads its source when it is made: the data follows the operator.
|
||
if &*spec_name == "ReusableStreamDecode" {
|
||
self.materialize(&stream)?;
|
||
}
|
||
self.push(Obj::File { stream, exec: false })
|
||
}
|
||
|
||
/// The data an executable file runs as a program: what is left of it. `None` for the
|
||
/// program's own file, which is being run already.
|
||
pub(super) fn program_of(&mut self, f: &Rc<RefCell<Stream>>) -> Res<Option<Vec<u8>>> {
|
||
if matches!(&*f.borrow(), Stream::Current) {
|
||
return Ok(None);
|
||
}
|
||
self.read(f, MAX_DATA).map(Some)
|
||
}
|
||
|
||
/// The decoded data left in file `f`, without reading it (a shading's or function's data).
|
||
pub(super) fn peek_all(&mut self, f: &Rc<RefCell<Stream>>) -> Res<Vec<u8>> {
|
||
self.materialize(f)?;
|
||
Ok(match &*f.borrow() {
|
||
Stream::Buf { data, pos, .. } => data.get(*pos..).unwrap_or_default().to_vec(),
|
||
_ => vec![],
|
||
})
|
||
}
|
||
|
||
/// Decode the filters a file waits to run over the program's data, from where it is now.
|
||
fn materialize(&mut self, f: &Rc<RefCell<Stream>>) -> Res {
|
||
let chain = match &*f.borrow() {
|
||
Stream::Pending(c) => c.clone(),
|
||
_ => return Ok(()),
|
||
};
|
||
let mut buf: Option<(Vec<u8>, Option<u8>)> = None;
|
||
for spec in &chain {
|
||
buf = Some(match buf.take() {
|
||
None => {
|
||
let raw = self.lex.data();
|
||
let (data, used, rgb) = decode(spec, raw)?;
|
||
self.alloc(data.len())?;
|
||
self.lex.advance(used);
|
||
(data, rgb)
|
||
}
|
||
Some((prev, was)) => {
|
||
let (data, _, rgb) = decode(spec, &prev)?;
|
||
self.alloc(data.len())?;
|
||
(data, rgb.or(was))
|
||
}
|
||
});
|
||
}
|
||
let (data, rgb) = buf.unwrap_or_default();
|
||
*f.borrow_mut() = Stream::Buf { data, pos: 0, rgb };
|
||
Ok(())
|
||
}
|
||
|
||
/// The bytes a data procedure gives, called until it gives an empty string or `n` bytes.
|
||
fn gather(&mut self, p: &Obj, n: usize) -> Res<Vec<u8>> {
|
||
Ok(self.read_sources(&[Source::Proc(p.clone())], n, n)?.pop().unwrap_or_default())
|
||
}
|
||
|
||
/// `n` bytes of image data from `src` (zeros past its end).
|
||
fn source_bytes(&mut self, src: &Source, n: usize) -> Res<Vec<u8>> {
|
||
let mut v = self.read_sources(std::slice::from_ref(src), n, n)?.pop().unwrap_or_default();
|
||
v.resize(n, 0);
|
||
Ok(v)
|
||
}
|
||
|
||
/// Up to `n` bytes from each of `sources`, read in turn as an image reads several data sources
|
||
/// (PLRM 3rd ed., §4.10 "Images" and `image`/`colorimage` in chapter 8: the sources are read
|
||
/// in turn, the same amount from each): each procedure called once a round (its string,
|
||
/// whatever its length), each file and string giving `step` bytes a round, until each has
|
||
/// given `n` bytes or ended. Procedures that read one file get their own rows that way.
|
||
fn read_sources(&mut self, sources: &[Source], n: usize, step: usize) -> Res<Vec<Vec<u8>>> {
|
||
let mut out: Vec<Vec<u8>> = vec![vec![]; sources.len()];
|
||
let mut ended = vec![false; sources.len()];
|
||
while out.iter().zip(&ended).any(|(o, e)| o.len() < n && !e) {
|
||
for ((src, o), e) in sources.iter().zip(out.iter_mut()).zip(ended.iter_mut()) {
|
||
if *e || o.len() >= n {
|
||
continue;
|
||
}
|
||
let want = step.min(n - o.len());
|
||
let chunk = match src {
|
||
Source::File(f) => self.read(f, want)?,
|
||
// A string gives its bytes over and over.
|
||
Source::Str(s) => {
|
||
let at = o.len() % s.len().max(1);
|
||
s.get(at..).unwrap_or_default().iter().chain(s.iter().cycle()).take(want).copied().collect()
|
||
}
|
||
Source::Proc(p) => {
|
||
self.call(p.clone())?;
|
||
let s = self.pop_str()?.to_vec();
|
||
self.alloc(s.len())?;
|
||
s
|
||
}
|
||
};
|
||
*e = chunk.is_empty();
|
||
o.extend_from_slice(chunk.get(..chunk.len().min(n - o.len())).unwrap_or_default());
|
||
}
|
||
}
|
||
Ok(out)
|
||
}
|
||
|
||
/// `eexec`: an encrypted font program follows. It is skipped (type is drawn in the app's
|
||
/// fonts, by name): the font dictionary it would complete is defined as it is.
|
||
fn eexec(&mut self) -> Res {
|
||
self.pop()?;
|
||
if let Some(Obj::Dict(d)) = self.stack.last().cloned() {
|
||
let name = d.borrow().get(&Key::name("FontName")).cloned();
|
||
if let Some(k) = name.and_then(|n| n.key()) {
|
||
self.pop()?;
|
||
self.fonts.borrow_mut().insert(k, Obj::Dict(d));
|
||
}
|
||
}
|
||
let rest = self.lex.data();
|
||
let at = find(rest, b"cleartomark").ok_or(PsError::Ps("ioerror", "eexec".into()))?;
|
||
self.lex.advance(at);
|
||
self.push(Obj::Mark)
|
||
}
|
||
|
||
// ---------- images ----------
|
||
|
||
fn image(&mut self, op: Op) -> Res {
|
||
let mask = op == Op::ImageMask;
|
||
let img = match self.stack.last() {
|
||
Some(Obj::Dict(_)) if op != Op::ColorImage => {
|
||
let d = self.pop_dict()?;
|
||
let kind = d.borrow().get(&Key::name("ImageType")).and_then(Obj::as_num);
|
||
if kind == Some(3.0) && !mask {
|
||
self.masked_image(&d)?
|
||
} else {
|
||
let spec = self.image_dict(&d, mask)?;
|
||
self.decode_image(spec)?
|
||
}
|
||
}
|
||
_ => {
|
||
let spec = self.image_operands(op)?;
|
||
self.decode_image(spec)?
|
||
}
|
||
};
|
||
let Some(img) = img else { return Ok(()) };
|
||
self.place_image(img)
|
||
}
|
||
|
||
/// ImageType 3: an image and the mask that says where it paints (where the mask's samples
|
||
/// read below one half through its `Decode`, as an image mask paints its 0 bits), in separate
|
||
/// sources (`InterleaveType` 3) or interleaved with the image's by row (2) or by sample (1).
|
||
fn masked_image(&mut self, d: &DictRef) -> Res<Option<Pixels>> {
|
||
let get = |k: &str| d.borrow().get(&Key::name(k)).cloned();
|
||
let (Some(Obj::Dict(data)), Some(Obj::Dict(mask))) = (get("DataDict"), get("MaskDict")) else {
|
||
return ps_err("typecheck", "ImageType 3");
|
||
};
|
||
let interleave = get("InterleaveType").and_then(|o| o.as_num()).unwrap_or(1.0);
|
||
let mut image = self.image_dict(&data, false)?;
|
||
let mut stencil = self.image_dict(&mask, false)?;
|
||
stencil.space = Space::Gray;
|
||
let n = image.space.n();
|
||
if image.w.saturating_mul(image.h) > MAX_PIXELS || stencil.w.saturating_mul(stencil.h) > MAX_PIXELS {
|
||
self.out.warn(TOO_LARGE);
|
||
return Ok(None);
|
||
}
|
||
if interleave != 3.0 {
|
||
let src = image.sources.first().cloned().ok_or(PsError::Ps("undefined", "DataSource".into()))?;
|
||
let (pixels, samples) = if interleave == 2.0 {
|
||
let (mrow, drow) = (stencil.row_bytes(1), image.row_bytes(n));
|
||
let total = mrow.saturating_mul(stencil.h).saturating_add(drow.saturating_mul(image.h));
|
||
self.alloc(total)?;
|
||
let raw = self.source_bytes(&src, total.min(MAX_DATA))?;
|
||
by_rows(&raw, (mrow, stencil.h), (drow, image.h))
|
||
} else {
|
||
// Each sample: the mask's value, then the colour's, as many bits each.
|
||
stencil.bpc = image.bpc;
|
||
(stencil.w, stencil.h) = (image.w, image.h);
|
||
let row = (image.w * (n + 1) * usize::from(image.bpc)).div_ceil(8);
|
||
self.alloc(row.saturating_mul(image.h))?;
|
||
let raw = self.source_bytes(&src, row.saturating_mul(image.h).min(MAX_DATA))?;
|
||
by_samples(&raw, image.w, image.h, n, image.bpc)
|
||
};
|
||
stencil.sources = vec![Source::Str(pixels)];
|
||
image.sources = vec![Source::Str(samples)];
|
||
}
|
||
let Some(mut img) = self.decode_image(image)? else { return Ok(None) };
|
||
let Some(m) = self.decode_image(stencil)? else { return Ok(Some(img)) };
|
||
let (w, h, mw, mh) = (img.w as usize, img.h as usize, m.w as usize, m.h as usize);
|
||
for (i, px) in img.rgba.as_chunks_mut::<4>().0.iter_mut().enumerate() {
|
||
let (x, y) = (i % w, i / w);
|
||
// The mask's pixel over this one (in 64 bits: rows times rows overflow 32).
|
||
let at = (y as u64 * mh as u64 / h.max(1) as u64) * mw as u64 + x as u64 * mw as u64 / w.max(1) as u64;
|
||
if usize::try_from(at * 4).ok().and_then(|i| m.rgba.get(i)).is_none_or(|v| *v >= 128)
|
||
&& let Some(a) = px.get_mut(3)
|
||
{
|
||
*a = 0;
|
||
}
|
||
}
|
||
Ok(Some(img))
|
||
}
|
||
|
||
/// The dictionary form of `image` and `imagemask`.
|
||
fn image_dict(&mut self, d: &DictRef, mask: bool) -> Res<Spec> {
|
||
let get = |k: &str| d.borrow().get(&Key::name(k)).cloned();
|
||
let num = |k: &str| get(k).and_then(|o| o.as_num());
|
||
let kind = num("ImageType").unwrap_or(1.0);
|
||
if kind != 1.0 && kind != 4.0 {
|
||
return ps_err("rangecheck", "ImageType");
|
||
}
|
||
let size = |k: &str| num(k).filter(|v| *v >= 1.0 && *v <= 65_535.0).map(|v| v as usize).ok_or(PsError::Ps("rangecheck", k.to_string()));
|
||
let (w, h) = (size("Width")?, size("Height")?);
|
||
let bpc = if mask { 1 } else { num("BitsPerComponent").unwrap_or(8.0) as u8 };
|
||
let matrix =
|
||
get("ImageMatrix").and_then(|o| o.items().and_then(|i| matrix_of(&i.borrow()))).ok_or(PsError::Ps("typecheck", "ImageMatrix".into()))?;
|
||
let decode: Vec<f64> = get("Decode").and_then(|o| o.items().map(|i| i.borrow().iter().filter_map(Obj::as_num).collect())).unwrap_or_default();
|
||
let space = if mask { Space::Gray } else { (*self.g.space).clone() };
|
||
let multi = get("MultipleDataSources").is_some_and(|o| matches!(o, Obj::Bool(true)));
|
||
let sources = match (get("DataSource"), multi) {
|
||
(None, _) => vec![],
|
||
(Some(Obj::Array { items, exec: false }), true) => items.borrow().iter().map(source).collect::<Res<Vec<_>>>()?,
|
||
(Some(src), _) => vec![source(&src)?],
|
||
};
|
||
let mask_color: Option<Vec<u32>> = (kind == 4.0)
|
||
.then(|| {
|
||
get("MaskColor").and_then(|o| o.items().map(|i| i.borrow().iter().filter_map(|v| v.as_num().map(|v| v.max(0.0) as u32)).collect()))
|
||
})
|
||
.flatten();
|
||
// An image mask paints its 0 bits with Decode [0 1], its 1 bits with [1 0].
|
||
let stencil = mask.then(|| decode.first().is_some_and(|v| *v > 0.5));
|
||
Ok(Spec { w, h, bpc, space, decode, matrix, sources, mask_color, stencil })
|
||
}
|
||
|
||
/// The operand forms: `w h bpc matrix src image`, `w h polarity matrix src imagemask`, and
|
||
/// `w h bpc matrix src… multi ncomp colorimage`.
|
||
fn image_operands(&mut self, op: Op) -> Res<Spec> {
|
||
let (n, multi) = if op == Op::ColorImage {
|
||
let n = self.pop_count()?;
|
||
(n, self.pop_bool()?)
|
||
} else {
|
||
(1, false)
|
||
};
|
||
let space = match n {
|
||
1 => Space::Gray,
|
||
3 => Space::Rgb,
|
||
4 => Space::Cmyk,
|
||
_ => return ps_err("rangecheck", "colorimage"),
|
||
};
|
||
let mut sources = vec![];
|
||
for _ in 0..if multi { n } else { 1 } {
|
||
let o = self.pop()?;
|
||
sources.push(source(&o)?);
|
||
}
|
||
sources.reverse();
|
||
let matrix = self.pop_matrix()?;
|
||
let (bpc, stencil) = if op == Op::ImageMask { (1, Some(self.pop_bool()?)) } else { (self.pop_int()?.clamp(1, 16) as u8, None) };
|
||
let size = |v: i64| usize::try_from(v).ok().filter(|v| (1..=65_535).contains(v)).ok_or(PsError::Ps("rangecheck", "image".into()));
|
||
let h = size(self.pop_int()?)?;
|
||
let w = size(self.pop_int()?)?;
|
||
Ok(Spec { w, h, bpc, space, decode: vec![], matrix, sources, mask_color: None, stencil })
|
||
}
|
||
|
||
/// The image's pixels, straight RGBA, rows from the first one read, and a CMYK image's ink
|
||
/// amounts.
|
||
fn decode_image(&mut self, s: Spec) -> Res<Option<Pixels>> {
|
||
if !matches!(s.bpc, 1 | 2 | 4 | 8 | 12 | 16) {
|
||
return ps_err("rangecheck", "BitsPerComponent");
|
||
}
|
||
if s.w.saturating_mul(s.h) > MAX_PIXELS {
|
||
self.out.warn(TOO_LARGE);
|
||
return Ok(None);
|
||
}
|
||
self.alloc(s.w * s.h * 4)?;
|
||
let n = if s.stencil.is_some() { 1 } else { s.space.n() };
|
||
let bpc = usize::from(s.bpc);
|
||
for src in &s.sources {
|
||
if let Source::File(f) = src {
|
||
self.materialize(f)?;
|
||
}
|
||
}
|
||
// A decoded JPEG gives 8-bit gray, RGB or CMYK samples whatever the color space says.
|
||
let jpeg = s.sources.iter().find_map(|src| match src {
|
||
Source::File(f) => match &*f.borrow() {
|
||
Stream::Buf { rgb, .. } => *rgb,
|
||
_ => None,
|
||
},
|
||
_ => None,
|
||
});
|
||
let (n, bpc, space) = match jpeg {
|
||
Some(3) => (3, 8, Space::Rgb),
|
||
Some(4) => (4, 8, Space::Cmyk),
|
||
Some(_) => (1, 8, Space::Gray),
|
||
None => (n, bpc, s.space.clone()),
|
||
};
|
||
let multi = s.sources.len() > 1;
|
||
let row_bits = if multi { s.w * bpc } else { s.w * n * bpc };
|
||
let row = row_bits.div_ceil(8);
|
||
// Several sources are read in turn, a row at a time each (zeros past their ends).
|
||
let sources = if multi { &s.sources[..] } else { s.sources.get(..1).ok_or(PsError::Ps("undefined", "DataSource".into()))? };
|
||
let mut planes = self.read_sources(sources, row * s.h, if multi { row } else { row * s.h })?;
|
||
for p in &mut planes {
|
||
p.resize(row * s.h, 0);
|
||
}
|
||
let max = ((1u32 << bpc.min(16)) - 1) as f64;
|
||
let sample = |plane: &[u8], y: usize, i: usize| -> u32 {
|
||
let bit = y * row * 8 + i * bpc;
|
||
let byte = bit / 8;
|
||
match bpc {
|
||
8 => u32::from(plane.get(byte).copied().unwrap_or(0)),
|
||
16 => u32::from(plane.get(byte).copied().unwrap_or(0)) << 8 | u32::from(plane.get(byte + 1).copied().unwrap_or(0)),
|
||
12 => {
|
||
let v = u32::from(plane.get(byte).copied().unwrap_or(0)) << 8 | u32::from(plane.get(byte + 1).copied().unwrap_or(0));
|
||
if bit.is_multiple_of(8) { v >> 4 } else { v & 0xfff }
|
||
}
|
||
_ => (u32::from(plane.get(byte).copied().unwrap_or(0)) >> (8 - bpc - bit % 8)) & ((1 << bpc) - 1),
|
||
}
|
||
};
|
||
let default_decode: Vec<f64> = match &space {
|
||
Space::Indexed { .. } => vec![0.0, max],
|
||
_ => (0..n).flat_map(|_| [0.0, 1.0]).collect(),
|
||
};
|
||
// The Decode array applies to a CMYK JPEG's samples as stored; gray and RGB JPEGs are read
|
||
// with the default Decode array.
|
||
let decode = if s.decode.len() >= 2 * n && jpeg.is_none_or(|c| c == 4) { s.decode.clone() } else { default_decode };
|
||
let paint = self.g.paint.color().map(|c| c.to_rgba8(1.0));
|
||
let mut rgba = Vec::with_capacity(s.w * s.h * 4);
|
||
// CMYK samples keep their ink amounts, as the Decode array reads them.
|
||
let mut inks = if matches!(space, Space::Cmyk) && s.stencil.is_none() {
|
||
self.alloc(s.w * s.h * 4)?;
|
||
Some(Vec::with_capacity(s.w * s.h * 4))
|
||
} else {
|
||
None
|
||
};
|
||
let mut cache: HashMap<Vec<u32>, [u8; 3]> = HashMap::new();
|
||
let mut raw = vec![0u32; n];
|
||
let mut comps = vec![0f64; n];
|
||
for y in 0..s.h {
|
||
for x in 0..s.w {
|
||
for (c, slot) in raw.iter_mut().enumerate() {
|
||
*slot = match planes.as_slice() {
|
||
[one] => sample(one, y, x * n + c),
|
||
many => many.get(c).map_or(0, |p| sample(p, y, x)),
|
||
};
|
||
}
|
||
if let Some(paint_ones) = s.stencil {
|
||
let on = (raw.first().copied().unwrap_or(0) == 1) == paint_ones;
|
||
let [r, g, b, _] = paint.unwrap_or([0, 0, 0, 255]);
|
||
rgba.extend([r, g, b, if on { 255 } else { 0 }]);
|
||
continue;
|
||
}
|
||
let masked = s.mask_color.as_ref().is_some_and(|m| masked(m, &raw));
|
||
for (c, slot) in comps.iter_mut().enumerate() {
|
||
let (d0, d1) = (decode.get(2 * c).copied().unwrap_or(0.0), decode.get(2 * c + 1).copied().unwrap_or(1.0));
|
||
*slot = d0 + f64::from(*raw.get(c).unwrap_or(&0)) * (d1 - d0) / max;
|
||
}
|
||
if let Some(inks) = inks.as_mut() {
|
||
inks.extend(comps.iter().map(|v| (v.clamp(0.0, 1.0) * 255.0).round() as u8));
|
||
}
|
||
let rgb = match (&space, raw.as_slice()) {
|
||
(Space::Gray, [v]) if bpc == 8 && decode == [0.0, 1.0] => [*v as u8; 3],
|
||
(Space::Rgb, [r, g, b]) if bpc == 8 && decode == [0.0, 1.0, 0.0, 1.0, 0.0, 1.0] => [*r as u8, *g as u8, *b as u8],
|
||
_ => match cache.get(&raw) {
|
||
Some(c) => *c,
|
||
None => {
|
||
let c = self.sample_rgb(&space, &comps)?;
|
||
if cache.len() < MAX_TINTS {
|
||
cache.insert(raw.clone(), c);
|
||
}
|
||
c
|
||
}
|
||
},
|
||
};
|
||
rgba.extend([rgb[0], rgb[1], rgb[2], if masked { 0 } else { 255 }]);
|
||
}
|
||
}
|
||
Ok(Some(Pixels { w: s.w as u32, h: s.h as u32, rgba, inks, matrix: s.matrix }))
|
||
}
|
||
|
||
/// One pixel's colour in `space` as display RGB.
|
||
fn sample_rgb(&mut self, space: &Space, comps: &[f64]) -> Res<[u8; 3]> {
|
||
let color = match space {
|
||
Space::Gray | Space::Rgb | Space::Cmyk => process(space, comps),
|
||
_ => match self.paint_of(space, comps)? {
|
||
Paint::Solid { color, .. } => Some(color),
|
||
_ => None,
|
||
},
|
||
};
|
||
let [r, g, b, _] = color.unwrap_or(Color::BLACK).to_rgba8(1.0);
|
||
Ok([r, g, b])
|
||
}
|
||
|
||
/// Place decoded pixels in user space (its unit square through the image matrix).
|
||
fn place_image(&mut self, img: Pixels) -> Res {
|
||
let m = img.matrix;
|
||
if m.determinant().abs() < 1e-12 {
|
||
return Ok(());
|
||
}
|
||
let xf = self.xf() * m.inverse();
|
||
if !xf.as_coeffs().iter().all(|v| v.is_finite()) || xf.determinant().abs() < 1e-12 {
|
||
return Ok(());
|
||
}
|
||
if self.g.null {
|
||
return Ok(());
|
||
}
|
||
// CMYK samples keep their ink amounts as a CMYK TIFF, as the PDF importer keeps an 8- or
|
||
// 16-bit CMYK image's samples, and transparent pixels (`MaskColor`, an ImageType 3 mask)
|
||
// become its opacity mask.
|
||
if let Some(blob) = img.inks.and_then(|i| Inks::new(img.w, img.h, i)).and_then(|i| ImageBlob::cmyk_tiff(&i)) {
|
||
let alpha: Vec<u8> = img.rgba.iter().skip(3).step_by(4).copied().collect();
|
||
let mask = self.alpha_mask(&alpha, img.w, img.h, xf);
|
||
return self.add_image(blob, img.w, img.h, xf, mask);
|
||
}
|
||
let Some(rgba) = image::RgbaImage::from_raw(img.w, img.h, img.rgba) else { return Ok(()) };
|
||
let mut png = vec![];
|
||
if rgba.write_to(&mut std::io::Cursor::new(&mut png), image::ImageFormat::Png).is_err() {
|
||
self.out.warn("an image that couldn't be read was left out");
|
||
return Ok(());
|
||
}
|
||
self.add_image(ImageBlob::new("image/png", png), img.w, img.h, xf, None)
|
||
}
|
||
|
||
/// Add image `blob` (`w` × `h` pixels, mapped into user space by `xf`) with `mask` as its
|
||
/// opacity mask.
|
||
fn add_image(&mut self, blob: ImageBlob, w: u32, h: u32, xf: Affine, mask: Option<OpacityMask>) -> Res {
|
||
let key = blob.content_key();
|
||
self.out.doc.images.entry(key.clone()).or_insert(blob);
|
||
let im = ImageObject { key, width: w, height: h, xf, link: None, placement: Default::default() };
|
||
let clips = self.g.clips.clone();
|
||
self.out.push(Node::new(NodeId(0), NodeKind::Image(Box::new(im))).with_mask(mask.map(Box::new)), &clips);
|
||
Ok(())
|
||
}
|
||
|
||
/// An image's alpha (`w` × `h` bytes) as a grayscale image over the image (`xf`): its opacity
|
||
/// mask, as in PDF import. `None` when it hides nothing.
|
||
fn alpha_mask(&mut self, alpha: &[u8], w: u32, h: u32, xf: Affine) -> Option<OpacityMask> {
|
||
if alpha.iter().all(|v| *v == 255) {
|
||
return None;
|
||
}
|
||
let mut png = Vec::new();
|
||
image::GrayImage::from_raw(w, h, alpha.to_vec())?.write_to(&mut std::io::Cursor::new(&mut png), image::ImageFormat::Png).ok()?;
|
||
let blob = ImageBlob::new("image/png", png);
|
||
let key = blob.content_key();
|
||
self.out.doc.images.entry(key.clone()).or_insert(blob);
|
||
let im = ImageObject { key, width: w, height: h, xf, link: None, placement: Default::default() };
|
||
Some(OpacityMask::new(Node::new(self.out.doc.alloc_id(), NodeKind::Image(Box::new(im))), true))
|
||
}
|
||
}
|
||
|
||
/// An image to decode.
|
||
struct Spec {
|
||
w: usize,
|
||
h: usize,
|
||
bpc: u8,
|
||
space: Space,
|
||
decode: Vec<f64>,
|
||
/// User space → image space.
|
||
matrix: Affine,
|
||
/// One source, or one per component.
|
||
sources: Vec<Source>,
|
||
/// `MaskColor` (ImageType 4): samples that are transparent.
|
||
mask_color: Option<Vec<u32>>,
|
||
/// An image mask: whether its 1 bits paint.
|
||
stencil: Option<bool>,
|
||
}
|
||
|
||
impl Spec {
|
||
/// Bytes a row of `n` components takes.
|
||
fn row_bytes(&self, n: usize) -> usize {
|
||
(self.w * n * usize::from(self.bpc)).div_ceil(8)
|
||
}
|
||
}
|
||
|
||
/// Data of an ImageType 3 interleaved by row, split into the mask's and the image's: the one
|
||
/// with fewer rows gives one row before each run of the other's (`(row bytes, rows)` each).
|
||
fn by_rows(raw: &[u8], (mrow, mh): (usize, usize), (drow, dh): (usize, usize)) -> (Vec<u8>, Vec<u8>) {
|
||
let room = |row: usize, rows: usize| row.saturating_mul(rows).min(raw.len());
|
||
let (mut mask, mut data) = (Vec::with_capacity(room(mrow, mh)), Vec::with_capacity(room(drow, dh)));
|
||
let mut at = 0;
|
||
let mut take = |into: &mut Vec<u8>, n: usize| {
|
||
into.extend_from_slice(raw.get(at..at + n).unwrap_or_default());
|
||
at += n;
|
||
};
|
||
let (blocks, masks, rows) = if mh >= dh { (dh, mh / dh.max(1), 1) } else { (mh, 1, dh / mh.max(1)) };
|
||
for _ in 0..blocks {
|
||
for _ in 0..masks {
|
||
take(&mut mask, mrow);
|
||
}
|
||
for _ in 0..rows {
|
||
take(&mut data, drow);
|
||
}
|
||
}
|
||
(mask, data)
|
||
}
|
||
|
||
/// Data of an ImageType 3 interleaved by sample (the mask's value first, then `n` colour
|
||
/// components, `bpc` bits each, rows from whole bytes), split into the mask's and the image's.
|
||
fn by_samples(raw: &[u8], w: usize, h: usize, n: usize, bpc: u8) -> (Vec<u8>, Vec<u8>) {
|
||
let bpc = usize::from(bpc);
|
||
let bit = |at: usize| raw.get(at / 8).is_some_and(|b| b & (0x80 >> (at % 8)) != 0);
|
||
let (mrow, drow, row) = ((w * bpc).div_ceil(8), (w * n * bpc).div_ceil(8), (w * (n + 1) * bpc).div_ceil(8));
|
||
let (mut mask, mut data) = (vec![0u8; mrow * h], vec![0u8; drow * h]);
|
||
let put = |into: &mut [u8], at: usize| {
|
||
if let Some(b) = into.get_mut(at / 8) {
|
||
*b |= 0x80 >> (at % 8);
|
||
}
|
||
};
|
||
for y in 0..h {
|
||
for x in 0..w {
|
||
let from = y * row * 8 + x * (n + 1) * bpc;
|
||
for k in 0..bpc {
|
||
if bit(from + k) {
|
||
put(&mut mask, y * mrow * 8 + x * bpc + k);
|
||
}
|
||
}
|
||
for k in 0..n * bpc {
|
||
if bit(from + bpc + k) {
|
||
put(&mut data, y * drow * 8 + x * n * bpc + k);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
(mask, data)
|
||
}
|
||
|
||
/// Decoded pixels.
|
||
struct Pixels {
|
||
w: u32,
|
||
h: u32,
|
||
rgba: Vec<u8>,
|
||
/// A CMYK image's ink amounts: C, M, Y and K bytes a pixel, 0 is no ink.
|
||
inks: Option<Vec<u8>>,
|
||
matrix: Affine,
|
||
}
|
||
|
||
fn source(o: &Obj) -> Res<Source> {
|
||
Ok(match o {
|
||
Obj::File { stream: f, .. } => Source::File(f.clone()),
|
||
Obj::Str(s) => Source::Str(s.to_vec()),
|
||
Obj::Array { items, .. } => Source::Proc(Obj::Array { items: items.clone(), exec: true }),
|
||
_ => return ps_err("typecheck", "DataSource"),
|
||
})
|
||
}
|
||
|
||
/// Do samples `raw` match `MaskColor` `m` (one value per component, or a range)?
|
||
fn masked(m: &[u32], raw: &[u32]) -> bool {
|
||
if m.len() == raw.len() {
|
||
return m.iter().zip(raw).all(|(a, b)| a == b);
|
||
}
|
||
m.len() == raw.len() * 2
|
||
&& raw.iter().enumerate().all(|(i, v)| (m.get(2 * i).copied().unwrap_or(0)..=m.get(2 * i + 1).copied().unwrap_or(0)).contains(v))
|
||
}
|