mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-29 04:07:12 -06:00
EPS: images encoded with CCITTFaxDecode draw
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.
This commit is contained in:
@@ -5,6 +5,7 @@ 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};
|
||||
@@ -164,6 +165,118 @@ fn dct(raw: &[u8]) -> Res<(Vec<u8>, usize, u8)> {
|
||||
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 {
|
||||
@@ -172,6 +285,8 @@ pub(crate) struct Filter {
|
||||
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`.
|
||||
@@ -266,6 +381,39 @@ fn paeth(a: u8, b: u8, c: u8) -> u8 {
|
||||
}
|
||||
}
|
||||
|
||||
/// `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 {
|
||||
@@ -292,6 +440,10 @@ fn decode(f: &Filter, raw: &[u8]) -> Res<(Vec<u8>, usize, Option<u8>)> {
|
||||
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() {
|
||||
@@ -540,13 +692,15 @@ impl Interp<'_> {
|
||||
} else {
|
||||
(0, vec![])
|
||||
};
|
||||
let mut predictor = None;
|
||||
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 };
|
||||
let spec = Filter { name, sub, predictor, fax };
|
||||
let stream = match self.pop()? {
|
||||
Obj::File { stream: f, .. } => {
|
||||
if let Stream::Buf { data, pos, .. } = &*f.borrow() {
|
||||
|
||||
@@ -2,14 +2,19 @@
|
||||
//! read in turn (§4.10 "Images", `image` and `colorimage` in chapter 8), rows padded to a whole
|
||||
//! byte at every bit depth and odd widths (§4.10), and Flate predictors undone row by row (§3.13,
|
||||
//! `FlateDecode` parameters) (#498). CMYK images keep their ink amounts, as the Decode array
|
||||
//! reads their samples, and their transparent pixels become an opacity mask.
|
||||
//! reads their samples, and their transparent pixels become an opacity mask. CCITT fax data
|
||||
//! (`CCITTFaxDecode`: ITU-T T.4 Group 3 and T.6 Group 4) decodes to rows of 1-bit samples.
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use fax::{BitWriter, Bits};
|
||||
use vectorcraft_doc::cmyk::Inks;
|
||||
use vectorcraft_doc::{ImageBlob, Node, NodeKind};
|
||||
|
||||
use super::tests::{all, read};
|
||||
use super::import;
|
||||
use super::tests::{all, eps, read};
|
||||
use super::tests_generators::pixel;
|
||||
|
||||
/// Each pixel of the first image, row by row.
|
||||
@@ -152,6 +157,182 @@ fn cmyk_jpegs_keep_their_ink_amounts() {
|
||||
}
|
||||
}
|
||||
|
||||
/// The bits a fax encoder writes, in order.
|
||||
#[derive(Clone, Default)]
|
||||
struct Written(Rc<RefCell<Vec<Bits>>>);
|
||||
|
||||
impl BitWriter for Written {
|
||||
type Error = std::convert::Infallible;
|
||||
|
||||
fn write(&mut self, bits: Bits) -> Result<(), Self::Error> {
|
||||
self.0.borrow_mut().push(bits);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// A 70 × 7 picture, `true` for black: a white row and a black row (runs past 63 take a make-up
|
||||
/// code), then runs of several lengths.
|
||||
fn picture() -> Vec<Vec<bool>> {
|
||||
(0..7).map(|y| (0..70).map(|x| if y < 2 { y == 1 } else { (x * (y + 2) / 5 + y) % 3 == 0 }).collect()).collect()
|
||||
}
|
||||
|
||||
/// `row` as one-dimensional runs (modified Huffman), white and black in turn from a white one
|
||||
/// (ITU-T T.4 §4.1.1).
|
||||
fn runs(row: &[bool]) -> Vec<Bits> {
|
||||
let mut out = vec![];
|
||||
let (mut x, mut black) = (0, false);
|
||||
while x < row.len() {
|
||||
let n = row[x..].iter().take_while(|b| **b == black).count();
|
||||
let code = |n: usize| if black { fax::maps::black::encode(n as u16) } else { fax::maps::white::encode(n as u16) }.unwrap();
|
||||
if n >= 64 {
|
||||
out.push(code(n & !63));
|
||||
}
|
||||
out.push(code(n % 64));
|
||||
(x, black) = (x + n, !black);
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// `rows` as CCITT data. With `k` below 0 it is Group 4 (each row coded against the one above,
|
||||
/// ITU-T T.6), ending with EOFB. Otherwise it is Group 3 (T.4): one-dimensional rows, and with `k`
|
||||
/// above 0 every `k`th row from the first one-dimensional and the others coded against the row
|
||||
/// above, each after its tag bit. Group 3 rows follow an EOL when `eol`, and six end the data.
|
||||
/// With `aligned`, each row (its EOL first) and the six EOLs at the end start at a byte.
|
||||
fn fax_data(rows: &[Vec<bool>], k: i32, eol: bool, aligned: bool) -> Vec<u8> {
|
||||
let written = Written::default();
|
||||
let mut encoder = fax::encoder::Encoder::new(written.clone());
|
||||
let mut bits: Vec<Bits> = vec![];
|
||||
let align = |bits: &mut Vec<Bits>| {
|
||||
let used: usize = bits.iter().map(|b| usize::from(b.len)).sum();
|
||||
if aligned && !used.is_multiple_of(8) {
|
||||
bits.push(Bits { data: 0, len: (8 - used % 8) as u8 });
|
||||
}
|
||||
};
|
||||
for (i, row) in rows.iter().enumerate() {
|
||||
let at = written.0.borrow().len();
|
||||
encoder.encode_line(row.iter().map(|b| if *b { fax::Color::Black } else { fax::Color::White }), row.len() as u16).unwrap();
|
||||
let two_d = written.0.borrow()[at..].to_vec();
|
||||
align(&mut bits);
|
||||
if k < 0 {
|
||||
bits.extend(two_d);
|
||||
continue;
|
||||
}
|
||||
if eol {
|
||||
bits.push(fax::maps::EOL);
|
||||
}
|
||||
let one_d = k == 0 || i % k as usize == 0;
|
||||
if k > 0 {
|
||||
bits.push(Bits { data: u16::from(one_d), len: 1 });
|
||||
}
|
||||
bits.extend(if one_d { runs(row) } else { two_d });
|
||||
}
|
||||
if k < 0 {
|
||||
bits.extend([fax::maps::EOL, fax::maps::EOL]);
|
||||
} else if eol {
|
||||
align(&mut bits);
|
||||
let end: &[Bits] = if k > 0 { &[fax::maps::EOL, Bits { data: 1, len: 1 }] } else { &[fax::maps::EOL] };
|
||||
bits.extend(end.repeat(6));
|
||||
}
|
||||
let mut w = fax::VecWriter::new();
|
||||
for b in bits {
|
||||
w.write(b).unwrap();
|
||||
}
|
||||
w.finish()
|
||||
}
|
||||
|
||||
/// Bytes as hexadecimal digits.
|
||||
fn hex(data: &[u8]) -> String {
|
||||
data.iter().map(|b| format!("{b:02x}")).collect()
|
||||
}
|
||||
|
||||
/// Images read through `CCITTFaxDecode` draw: Group 4, Group 3 one-dimensional and Group 3 mixed
|
||||
/// one- and two-dimensional (ITU-T T.4 and T.6), with and without EOLs, with rows starting at a
|
||||
/// byte, ended by a row count (`EndOfBlock false`) or by the end-of-block code, which overrides
|
||||
/// `Rows`. `BlackIs1` gives black as 1 bits, so the image comes out inverted unless a Decode array
|
||||
/// of `[1 0]` reads it.
|
||||
#[test]
|
||||
fn ccitt_fax_images_draw() {
|
||||
let rows = picture();
|
||||
let want: Vec<u8> = rows.concat().iter().map(|black| if *black { 0 } else { 255 }).collect();
|
||||
let cases = [
|
||||
(-1, false, false, "/K -1 /Columns 70"),
|
||||
(-1, false, false, "/K -1 /Columns 70 /Rows 4"),
|
||||
(-1, false, false, "/K -1 /Columns 70 /Rows 7 /EndOfBlock false"),
|
||||
(-1, false, true, "/K -1 /Columns 70 /EncodedByteAlign true"),
|
||||
(0, false, false, "/Columns 70 /Rows 7"),
|
||||
(0, true, false, "/Columns 70 /EndOfLine true"),
|
||||
(0, true, false, "/Columns 70 /Rows 4 /EndOfLine true"),
|
||||
(0, true, true, "/Columns 70 /Rows 7 /EndOfLine true /EncodedByteAlign true"),
|
||||
(2, true, false, "/K 2 /Columns 70 /Rows 7 /EndOfLine true"),
|
||||
(3, true, false, "/K 3 /Columns 70 /EndOfLine true"),
|
||||
];
|
||||
for (k, eol, aligned, params) in cases {
|
||||
let data = hex(&fax_data(&rows, k, eol, aligned));
|
||||
for (black_is_1, decode, inverted) in [("", "", false), (" /BlackIs1 true", "", true), (" /BlackIs1 true", " /Decode [1 0]", false)] {
|
||||
let r = read(&format!(
|
||||
"30 20 scale << /ImageType 1 /Width 70 /Height 7 /BitsPerComponent 1{decode} /ImageMatrix [70 0 0 -7 0 7] \
|
||||
/DataSource currentfile /ASCIIHexDecode filter << {params}{black_is_1} >> /CCITTFaxDecode filter >> image\n{data}>"
|
||||
));
|
||||
let got: Vec<u8> = pixels(&r, 70, 7).iter().map(|p| if inverted { 255 - p[0] } else { p[0] }).collect();
|
||||
assert_eq!(got, want, "{params}{black_is_1}{decode}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Binary CCITT data right after the operator that reads it: an image mask paints its black
|
||||
/// pixels, and the program goes on after the data's end-of-block code (EOFB in Group 4, RTC in
|
||||
/// Group 3, its EOLs followed by tag bits in two-dimensional data).
|
||||
#[test]
|
||||
fn ccitt_fax_data_in_the_program_ends_at_its_end_of_block() {
|
||||
let rows = picture();
|
||||
for (k, eol, aligned, params) in [
|
||||
(-1, false, false, "/K -1 /Columns 70"),
|
||||
(0, true, false, "/Columns 70 /EndOfLine true"),
|
||||
(2, true, false, "/K 2 /Columns 70 /Rows 7 /EndOfLine true"),
|
||||
(3, true, true, "/K 3 /Columns 70 /EndOfLine true /EncodedByteAlign true"),
|
||||
] {
|
||||
let mut file = eps("");
|
||||
file.truncate(file.len() - "\nshowpage\n%%EOF\n".len());
|
||||
file.extend_from_slice(
|
||||
format!("gsave 30 20 scale 70 7 false [70 0 0 -7 0 7] currentfile << {params} >> /CCITTFaxDecode filter imagemask\n").as_bytes(),
|
||||
);
|
||||
file.extend(fax_data(&rows, k, eol, aligned));
|
||||
file.extend_from_slice(b"\ngrestore 1 0 0 setrgbcolor 50 50 10 10 rectfill\nshowpage\n%%EOF\n");
|
||||
let r = import(&file).unwrap();
|
||||
let alpha: Vec<bool> = pixels(&r, 70, 7).iter().map(|p| p[3] == 255).collect();
|
||||
assert_eq!(alpha, rows.concat(), "{params}");
|
||||
assert!(all(&r.document).iter().any(|n| matches!(n.kind, NodeKind::Path { .. })), "the rectangle after the data is drawn: {params}");
|
||||
}
|
||||
}
|
||||
|
||||
/// Damaged CCITT data keeps the rows decoded before the damage, and damage in the first row
|
||||
/// stops the program. Parameters past the limits are refused.
|
||||
#[test]
|
||||
fn damaged_or_oversized_ccitt_fax_data() {
|
||||
let rows = picture();
|
||||
// Four rows, then an end-of-block code read as a fifth row: the four rows draw.
|
||||
let data = hex(&fax_data(&rows[..4], -1, false, false));
|
||||
let r = read(&format!(
|
||||
"30 20 scale 70 7 1 [70 0 0 -7 0 7] currentfile /ASCIIHexDecode filter << /K -1 /Columns 70 /Rows 7 /EndOfBlock false >> \
|
||||
/CCITTFaxDecode filter image\n{data}>"
|
||||
));
|
||||
let got: Vec<u8> = pixels(&r, 70, 7).iter().map(|p| p[0]).collect();
|
||||
let want: Vec<u8> = rows[..4].concat().iter().map(|black| if *black { 0 } else { 255 }).collect();
|
||||
assert_eq!(got[..4 * 70], want);
|
||||
for (params, why) in [
|
||||
("/K -1 /Columns 70 /Rows 7", "ioerror in `filter`: CCITTFaxDecode"),
|
||||
("/K -1 /Columns 65535 /Rows 100000 /EndOfBlock false", "decoded data is too large"),
|
||||
("/K -1 /Columns 65536", "rangecheck in `filter`: CCITTFaxDecode"),
|
||||
("/K -1 /Columns 0", "rangecheck in `filter`: CCITTFaxDecode"),
|
||||
("/K -1 /Columns 70 /Rows -1", "rangecheck in `filter`: CCITTFaxDecode"),
|
||||
] {
|
||||
let program = format!("0 0 10 10 rectfill 30 20 scale 70 7 1 [70 0 0 -7 0 7] <0000000000> << {params} >> /CCITTFaxDecode filter image");
|
||||
let r = import(&eps(&program)).unwrap();
|
||||
assert!(r.warnings.iter().any(|w| w.contains(why)), "{params}: {:?}", r.warnings);
|
||||
assert!(!all(&r.document).iter().any(|n| matches!(n.kind, NodeKind::Image(_))), "{params}");
|
||||
}
|
||||
}
|
||||
|
||||
/// Rows of an odd width at 1, 2, 4, 12 and 16 bits a sample start at a byte (their last byte
|
||||
/// padded), in one source and in several.
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user