mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-29 04:07:12 -06:00
Simplify file import and export checks
This commit is contained in:
@@ -38,7 +38,7 @@ impl PrintService for SystemPrint {
|
||||
|
||||
#[cfg(not(all(windows, any(feature = "windows7", target_vendor = "win7"))))]
|
||||
mod system {
|
||||
use super::*;
|
||||
use super::{Command, PrintJob, Printer};
|
||||
pub fn printers() -> Vec<Printer> {
|
||||
craft_print::printers().into_iter().map(|p| Printer { name: p.name, default: p.default }).collect()
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use serde_json::{Map, Value, json};
|
||||
use vectorcraft_doc::metadata::{MAX_RATING, iso8601};
|
||||
use vectorcraft_doc::{ColorMode, CopyrightStatus, DocMetadata, Document};
|
||||
|
||||
use super::*;
|
||||
use super::{Result, Session, bad};
|
||||
|
||||
const C: &str = "file.info";
|
||||
/// Longest text field, in characters.
|
||||
|
||||
@@ -92,7 +92,7 @@ pub use svg::options_map as svg_options;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub use vectorcraft_format::write_atomic;
|
||||
|
||||
use super::*;
|
||||
use super::{CommandSpec, Result, Session, always, bad, cmd, has_doc, has_selection};
|
||||
use crate::EngineError;
|
||||
|
||||
mod access;
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use super::*;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use super::write_atomic;
|
||||
use super::{EngineError, Result, Session, Value, bad, json};
|
||||
|
||||
// ---------- the file system (none on the web, where commands take and return bytes) ----------
|
||||
//
|
||||
|
||||
@@ -9,7 +9,9 @@ use serde_json::Value;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use serde_json::json;
|
||||
|
||||
use super::super::*;
|
||||
use super::super::{Result, Session, bad};
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use super::super::{bool_or, find_command, str_param};
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub(super) fn action_batch(_: &mut Session, _: &Value) -> Result<Value> {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use super::layout::{master_instances, master_prefix, page_masters};
|
||||
use super::master_copies::{CopyCost, MAX_MASTER_COPIES, MasterPart, copy_cost};
|
||||
use super::nodes::rect_of;
|
||||
use super::*;
|
||||
use super::{Affine, Arc, Artboard, Blend, Builder, Document, Kind, Node, NodeKind, Result, af, err};
|
||||
|
||||
impl Builder {
|
||||
pub(super) fn new(k: f64) -> Self {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{Builder, Cursor, Document, Result, err, raster_doc};
|
||||
|
||||
/// What an Affinity file opened as.
|
||||
pub(in super::super) struct Imported {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::nodes::MAX_DEPTH;
|
||||
use super::*;
|
||||
use super::{AfAlign, CharStyle, Cursor, Justify, Kind, Rect, TextKind, TextObject, VerticalAlign, af, shapes};
|
||||
|
||||
/// The master page instances among `nodes` (also inside layers and groups): (master, first page,
|
||||
/// first master page, page count).
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::nodes::MAX_DEPTH;
|
||||
use super::*;
|
||||
use super::{Kind, af};
|
||||
|
||||
/// What copying a subtree takes: its nodes, characters and table cells (`work`), and its pixels.
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{Affine, Arc, Blend, BlendMode, Builder, FillRule, Kind, LayerColor, Node, NodeKind, OpacityMask, Rect, af};
|
||||
|
||||
/// Deepest node tree mapped (the reader bounds trees well below this).
|
||||
pub(super) const MAX_DEPTH: usize = 160;
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
use super::layout::png;
|
||||
use super::nodes::affine;
|
||||
use super::text::Placement;
|
||||
use super::*;
|
||||
use super::{
|
||||
AfKind, Affine, Appearance, AppearanceItem, Arc, BezPath, Builder, Cap, Color, Dash, Effect, FillLayer, Gradient, GradientGeom, GradientKind,
|
||||
GradientPaint, GradientStop, ImageObject, Join, LineCap, LineJoin, NodeKind, Paint, PathData, Pixels, Point, StrokeAlign, StrokeLayer,
|
||||
TextObject, Vec2, af, ap, json,
|
||||
};
|
||||
|
||||
impl Builder {
|
||||
/// Drop shadows, inner shadows and outer glows as VectorCraft's Stylize effects. Affinity's
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
use super::layout::{align_lines, fit_lines, font_style, justify, line_gap, place_cell};
|
||||
use super::nodes::affine;
|
||||
use super::*;
|
||||
use super::{
|
||||
Affine, Appearance, AppearanceItem, Arc, BezPath, Builder, CharStyle, FillRule, FirstBaseline, Node, NodeKind, Paint, PathData, Point, Rect,
|
||||
TextKind, TextObject, TextRun, Vec2, VerticalAlign, af, ap, shapes,
|
||||
};
|
||||
|
||||
/// Where text goes: a rigid placement (translation and rotation) and the scale that maps node
|
||||
/// space into it (applied to the frame's geometry, not the type), plus the type's own scale.
|
||||
|
||||
@@ -5,7 +5,7 @@ use std::sync::Arc;
|
||||
use serde_json::{Value, json};
|
||||
use vectorcraft_doc::Document;
|
||||
|
||||
use super::super::*;
|
||||
use super::super::{Result, Session, bad, str_param};
|
||||
use crate::{DocState, EngineError};
|
||||
|
||||
pub(super) fn batch(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::access::file_list;
|
||||
use super::formats::formats;
|
||||
use super::*;
|
||||
use super::{CommandSpec, action_batch, always, batch, cmd, export, has_doc, has_selection, load, pdfimport, save, screens};
|
||||
|
||||
pub fn specs() -> Vec<CommandSpec> {
|
||||
vec![
|
||||
|
||||
@@ -10,7 +10,7 @@ use vectorcraft_cad::{ColorDepth, DxfOptions, DxfVersion, Preserve, RasterFormat
|
||||
use vectorcraft_doc::{Document, Unit};
|
||||
use vectorcraft_geom::Rect;
|
||||
|
||||
use super::super::*;
|
||||
use super::super::{CommandSpec, Result, Session, bad, cmd, has_doc};
|
||||
use super::{ArtboardPick, Encoded, FormatOption};
|
||||
|
||||
const C: &str = "document.export";
|
||||
|
||||
@@ -7,7 +7,7 @@ use serde_json::{Value, json};
|
||||
use vectorcraft_cad::ImportOptions;
|
||||
use vectorcraft_doc::{Document, Unit};
|
||||
|
||||
use super::super::*;
|
||||
use super::super::{CommandSpec, Result, Session, always, bad, cmd};
|
||||
use super::{err, source};
|
||||
|
||||
pub fn specs() -> Vec<CommandSpec> {
|
||||
|
||||
@@ -14,7 +14,7 @@ use vectorcraft_render::encode::quantize::{Dither, PaletteOptions, Reduction};
|
||||
use vectorcraft_render::encode::tiff::{ByteOrder, TiffOptions};
|
||||
use vectorcraft_render::encode::{RasterExportOptions, RasterFormat, bmp, psd, tga};
|
||||
|
||||
use super::super::*;
|
||||
use super::super::{EngineError, Result, bad, color_value, parse_range};
|
||||
use super::Format;
|
||||
use super::imagemap::{self, MapKind};
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ use vectorcraft_eps::{EpsOptions, Level, Overprint, Preview, Raster};
|
||||
use vectorcraft_geom::{Point, Rect};
|
||||
|
||||
use super::super::flatten::{FlattenOptions, flatten_document};
|
||||
use super::super::*;
|
||||
use super::super::{CommandSpec, EngineError, Result, Session, bad, cmd, has_doc, str_param};
|
||||
use super::{ArtboardPick, Encoded, FormatOption};
|
||||
|
||||
const C: &str = "document.export";
|
||||
|
||||
@@ -9,7 +9,7 @@ use vectorcraft_doc::{Document, Node, NodeId, NodeKind, SymbolBoxes};
|
||||
|
||||
use crate::DocState;
|
||||
|
||||
use super::super::*;
|
||||
use super::super::{Result, Session, bad, bool_or, edit, f64_or, str_param};
|
||||
use super::encode::single_artboard;
|
||||
use super::{
|
||||
ARTBOARD_PARAMS, Format, default_name, encode_all, encode_with_warnings, merge, with_black_output, writable, write_encoded, write_or_return,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::access::write_file;
|
||||
use super::*;
|
||||
use super::{Encoded, EngineError, Result, Session, Value, json};
|
||||
|
||||
/// File-name parts for artboards `boards` of `doc`: the artboard's name with unsafe characters as
|
||||
/// `-`, `Artboard-N` when unnamed, and `-2`, `-3`… after a name already used (compared without
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{Result, Session, TEMPLATE_FORMATS, UNSUPPORTED, Unsupported, Value, bad, dxf, eps, json, metafile, native, pdf, svg, text, unsupported};
|
||||
|
||||
/// One option a format's encoder reads from the export params.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
|
||||
@@ -37,7 +37,7 @@ use vectorcraft_color::BlendMode;
|
||||
use vectorcraft_doc::{Document, ImageBlob, ImageObject, Node, NodeKind};
|
||||
use vectorcraft_geom::{Anchor, FillRule, PathData, Point, Rect, SubPath};
|
||||
|
||||
use super::super::*;
|
||||
use super::super::{Affine, EngineError, NodeId, Result, bad};
|
||||
use super::{ArtboardPick, Encoded, Format};
|
||||
|
||||
/// Does `p` ask a save in `f` for a legacy `.ai` file of version 7 (`aiVersion: "7"`)? An unknown
|
||||
|
||||
@@ -7,7 +7,7 @@ use vectorcraft_doc::{Document, ImageBlob, ImageObject, Node, NodeKind};
|
||||
use vectorcraft_geom::Affine;
|
||||
use vectorcraft_render::effects::pixel::LoadedTexture;
|
||||
|
||||
use super::super::*;
|
||||
use super::super::{Result, Session, bad, bool_or, clipboard, parse_range, str_param};
|
||||
use super::pdfimport::LoadOptions;
|
||||
use super::{Format, SAVE_FORMATS, absolute_path, file_stamp, format, format_for_name, psdread, read_file};
|
||||
use crate::cmd::links::Resolved;
|
||||
|
||||
@@ -7,7 +7,7 @@ use serde_json::Value;
|
||||
use vectorcraft_doc::Document;
|
||||
use vectorcraft_metafile::Kind;
|
||||
|
||||
use super::super::*;
|
||||
use super::super::{Result, bad};
|
||||
use super::{ArtboardPick, Encoded, FormatOption};
|
||||
|
||||
const C: &str = "document.export";
|
||||
|
||||
@@ -12,7 +12,7 @@ use vectorcraft_color::cms;
|
||||
use vectorcraft_doc::Document;
|
||||
use vectorcraft_format::{PREVIEW_MAX, SaveOptions};
|
||||
|
||||
use super::super::*;
|
||||
use super::super::{EngineError, Result, bad};
|
||||
use super::{Format, FormatOption};
|
||||
|
||||
/// The options `document.save` and the native encoder read.
|
||||
|
||||
@@ -11,7 +11,7 @@ use vectorcraft_doc::{Document, Node, NodeKind};
|
||||
use vectorcraft_pdf::{Overprint, PdfError, PdfOptions, PdfPreset, PdfSettings, Standard, THUMBNAIL_SIZE, Thumbnail};
|
||||
|
||||
use super::super::flatten::{FlattenOptions, flatten_document, see_through_images};
|
||||
use super::super::*;
|
||||
use super::super::{CommandSpec, Result, Session, always, bad, bool_or, cmd, has_doc, str_param};
|
||||
use super::{ArtboardPick, FormatOption, write_or_return};
|
||||
use crate::EngineError;
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ use vectorcraft_doc::{ColorMode, Document};
|
||||
use vectorcraft_pdf::{Choice, CropTo, ImportOptions, PdfError, TextAs};
|
||||
|
||||
use super::super::colormgmt::Grays;
|
||||
use super::super::*;
|
||||
use super::super::{Result, Session, bad, f64_or, parse_range, str_param};
|
||||
use super::{err, source};
|
||||
|
||||
/// The `document.open` options besides the file. The PDF ones (pages, box, password, text,
|
||||
|
||||
@@ -12,7 +12,7 @@ use std::sync::Arc;
|
||||
use serde_json::{Map, Value, json};
|
||||
use vectorcraft_doc::Document;
|
||||
|
||||
use super::super::*;
|
||||
use super::super::{CommandSpec, Result, Session, always, bad, bool_or, clock_date, cmd, date_param, has_doc, str_param};
|
||||
use super::{
|
||||
ARTBOARD_PARAMS, ArtboardPick, Encoded, Format, Loaded, encode_all, file_stem, format, format_for_name, load, read_file, with_compression_pref,
|
||||
write_encoded,
|
||||
|
||||
@@ -16,7 +16,7 @@ use serde_json::{Map, Value, json};
|
||||
use vectorcraft_doc::Document;
|
||||
use vectorcraft_geom::Rect;
|
||||
|
||||
use super::super::*;
|
||||
use super::super::{NodeId, Result, Session, bad, str_param};
|
||||
use super::encode::single_artboard;
|
||||
use super::export::{export_crop, isolated, selected_artwork, without_artboards};
|
||||
use super::naming::{self, NameParts};
|
||||
|
||||
@@ -386,8 +386,8 @@ fn open_exts_cover_every_readable_format() {
|
||||
|
||||
#[test]
|
||||
fn library_open_filters_list_library_formats_first() {
|
||||
let filters: Vec<_> = library_open_filters("Swatch libraries", super::swatchlib::LIBRARY_EXTS).collect();
|
||||
assert_eq!(filters[0], ("Swatch libraries", super::swatchlib::LIBRARY_EXTS));
|
||||
let filters: Vec<_> = library_open_filters("Swatch libraries", crate::cmd::swatchlib::LIBRARY_EXTS).collect();
|
||||
assert_eq!(filters[0], ("Swatch libraries", crate::cmd::swatchlib::LIBRARY_EXTS));
|
||||
assert!(filters[0].1.contains(&"ase"));
|
||||
assert_eq!(filters[1], ("All readable files", OPEN_EXTS));
|
||||
}
|
||||
|
||||
@@ -140,7 +140,7 @@ fn metafiles_open_and_place() {
|
||||
let placed = st.doc.layers[0].children().unwrap().last().unwrap().clone();
|
||||
assert_eq!(placed.name.as_deref(), Some("pic.emf"));
|
||||
assert!(matches!(placed.kind, NodeKind::Group { clip: true, .. }));
|
||||
let ext = fileio::PLACE_EXTS;
|
||||
let ext = crate::cmd::fileio::PLACE_EXTS;
|
||||
assert!(ext.contains(&"emf") && ext.contains(&"wmf") && OPEN_EXTS.contains(&"emf") && OPEN_EXTS.contains(&"wmf"));
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ use serde::Deserialize;
|
||||
use serde_json::Value;
|
||||
use vectorcraft_doc::{Document, Node, NodeId, NodeKind};
|
||||
|
||||
use super::super::*;
|
||||
use super::super::{Result, bad};
|
||||
use super::FormatOption;
|
||||
|
||||
const C: &str = "document.export";
|
||||
|
||||
@@ -8,7 +8,7 @@ use serde_json::{Value, json};
|
||||
use vectorcraft_pdf::{PdfPreset, builtin_preset, builtin_presets};
|
||||
|
||||
use super::fileio::pdf::{find_preset, resolve};
|
||||
use super::*;
|
||||
use super::{CommandSpec, Result, Session, always, bad, bool_or, cmd, str_param, unique_name};
|
||||
|
||||
/// The `format` of a PDF presets file, also its extension.
|
||||
pub const PRESET_FORMAT: &str = "vcpdfpresets";
|
||||
|
||||
@@ -44,7 +44,7 @@ use vectorcraft_geom::{Affine, Point, Rect, shapes};
|
||||
use vectorcraft_pdf::CropTo;
|
||||
|
||||
use super::fileio::{self, Format, RasterImage};
|
||||
use super::*;
|
||||
use super::{CommandSpec, Result, Session, always, bad, bool_or, cmd, finite_numbers, has_doc, id_param, str_param, unique_name};
|
||||
use crate::{EngineError, MAX_COORD};
|
||||
|
||||
const PLACE: &str = "file.place";
|
||||
|
||||
@@ -25,7 +25,7 @@ use vectorcraft_geom::{Affine, Rect};
|
||||
use vectorcraft_pdf::CropTo;
|
||||
|
||||
use super::super::fileio;
|
||||
use super::super::*;
|
||||
use super::super::{EngineError, NodeId, Result, bad};
|
||||
|
||||
/// The largest file placed (bytes).
|
||||
pub(crate) const MAX_FILE: usize = 256 << 20;
|
||||
|
||||
@@ -43,7 +43,7 @@ use vectorcraft_doc::text::Kashida;
|
||||
use vectorcraft_doc::{CharPosition, CharStyle, Justify, ParaDirection, ParaStyle, ScriptMetrics, TextRun};
|
||||
|
||||
use super::text::{self, Story};
|
||||
use super::*;
|
||||
use super::{EngineError, Paint, Result, bad, fileio};
|
||||
use crate::cmd::fileio::zip::{self, Archive, ReadError};
|
||||
|
||||
/// Word documents as `file.place` reports them (not a format `document.open` reads).
|
||||
|
||||
@@ -31,7 +31,7 @@ use std::collections::HashMap;
|
||||
use vectorcraft_doc::{CharPosition, CharStyle, Justify, ParaDirection, ParaStyle, ScriptMetrics, TextRun};
|
||||
|
||||
use super::text::{self, Story};
|
||||
use super::*;
|
||||
use super::{Paint, Result, bad, fileio};
|
||||
|
||||
/// RTF files as `file.place` reports them (not a format `document.open` reads).
|
||||
pub(super) const FORMAT: fileio::Format = fileio::Format {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
use serde_json::Value;
|
||||
|
||||
use super::*;
|
||||
use super::{Point, Rect, Result, bad, bool_or, fileio, shapes, str_param};
|
||||
|
||||
/// Text files as `file.place` reports them (not a format `document.open` reads).
|
||||
pub(super) const FORMAT: fileio::Format = fileio::Format {
|
||||
|
||||
@@ -21,7 +21,7 @@ use vectorcraft_render::encode::quantize::{self, Dither, Indexed, PaletteOptions
|
||||
use vectorcraft_render::encode::web::{self, ColorSort};
|
||||
use vectorcraft_render::encode::{gif, jpeg, png};
|
||||
|
||||
use super::*;
|
||||
use super::{CommandSpec, Result, Session, always, bad, bool_or, cmd, f64_or, has_doc, str_param, unique_name};
|
||||
|
||||
const C: &str = "document.exportForWeb";
|
||||
|
||||
|
||||
@@ -135,6 +135,16 @@ struct Linearised {
|
||||
encrypted: bool,
|
||||
}
|
||||
|
||||
/// The main cross-reference table at `main`, covering the objects before the first page's `first`.
|
||||
fn check_main_table(pdf: &[u8], lin: &Indirect, main: usize, first: u32) {
|
||||
// /T: the white space before the main table's first entry.
|
||||
let entries = main + format!("xref\n0 {first}\n").len();
|
||||
assert!(pdf[main..].starts_with(format!("xref\n0 {first}\n").as_bytes()), "the main table covers 0..{first}");
|
||||
assert_eq!(int(&lin.value, b"T"), entries - 1);
|
||||
let main_trailer = Lexer::at(pdf, crate::lab_spot::find(pdf, b"trailer", main).unwrap() + 7).object(0).unwrap();
|
||||
assert_eq!(int(&main_trailer, b"Size") as u32, first);
|
||||
}
|
||||
|
||||
/// Check `pdf` is linearised as ISO 32000-1 Annex F says, as a linearisation checker does: the
|
||||
/// linearization dictionary, both cross-reference tables and their trailers, and (unless the file
|
||||
/// is encrypted) the hint tables against where every page's objects are and what each page uses.
|
||||
@@ -160,12 +170,7 @@ fn check_linearised(pdf: &[u8]) -> Linearised {
|
||||
assert_eq!(all.len() as u32, size - 1, "the tables cover every object");
|
||||
let first = lin.num;
|
||||
assert!(first_part.iter().all(|n| (first..size).contains(n)) && first_part.len() as u32 == size - first);
|
||||
// /T: the white space before the main table's first entry.
|
||||
let entries = main + format!("xref\n0 {first}\n").len();
|
||||
assert!(pdf[main..].starts_with(format!("xref\n0 {first}\n").as_bytes()), "the main table covers 0..{first}");
|
||||
assert_eq!(int(&lin.value, b"T"), entries - 1);
|
||||
let main_trailer = Lexer::at(pdf, crate::lab_spot::find(pdf, b"trailer", main).unwrap() + 7).object(0).unwrap();
|
||||
assert_eq!(int(&main_trailer, b"Size") as u32, first);
|
||||
check_main_table(pdf, &lin, main, first);
|
||||
|
||||
// Every object where its table says, read as the file's objects.
|
||||
let objects: HashMap<u32, Indirect> = all.iter().map(|(n, off)| (*n, indirect(pdf, *off).unwrap())).collect();
|
||||
@@ -205,7 +210,41 @@ fn check_linearised(pdf: &[u8]) -> Linearised {
|
||||
let data = &pdf[data_at..data_at + int(&hint.value, b"Length")];
|
||||
let mut r = BitReader { data, at: 0 };
|
||||
|
||||
// The page offset hint table.
|
||||
let (counts, ids) = check_page_hints(&mut r, &pages, &adjusted, &span, first_page_objects);
|
||||
|
||||
// The shared object hint table.
|
||||
let s = int(&hint.value, b"S");
|
||||
let mut r = BitReader { data, at: 8 * s };
|
||||
let (first_shared, first_shared_at, nfirst, ntotal) = read_shared_header(&mut r);
|
||||
let shared_object = |id: u64| if id < nfirst { pages[0] + id as u32 } else { first_shared + (id - nfirst) as u32 };
|
||||
if ntotal > nfirst {
|
||||
assert_eq!(first_shared_at, adjusted(first_shared), "the shared objects' location");
|
||||
}
|
||||
check_shared_hints(&mut r, ntotal, nfirst, first_page_objects, &shared_object, &span);
|
||||
|
||||
// What each page uses lies in its own objects, the first part or its shared objects.
|
||||
let page_set: HashSet<u32> = pages.iter().copied().collect();
|
||||
let references = PageReferenceHints {
|
||||
pages: &pages,
|
||||
page_set: &page_set,
|
||||
first_part: &first_part,
|
||||
objects: &objects,
|
||||
nodes: &nodes,
|
||||
shared_object: &shared_object,
|
||||
};
|
||||
for (i, &page) in pages.iter().enumerate() {
|
||||
check_page_references(&references, i, page, counts[i], &ids[i]);
|
||||
}
|
||||
Linearised { pages, first_part, encrypted }
|
||||
}
|
||||
|
||||
fn check_page_hints(
|
||||
r: &mut BitReader<'_>,
|
||||
pages: &[u32],
|
||||
adjusted: &impl Fn(u32) -> usize,
|
||||
span: &impl Fn(u32) -> usize,
|
||||
first_page_objects: u32,
|
||||
) -> (Vec<u64>, Vec<Vec<u64>>) {
|
||||
let (min_objects, first_page_at, objects_bits) = (r.get(32), r.get(32) as usize, r.get(16));
|
||||
let (min_len, len_bits) = (r.get(32), r.get(16));
|
||||
let (_content_at, content_at_bits, _content_len, content_len_bits) = (r.get(32), r.get(16), r.get(32), r.get(16));
|
||||
@@ -230,42 +269,58 @@ fn check_linearised(pdf: &[u8]) -> Linearised {
|
||||
assert_eq!(len as u64, lengths[i], "page {i}'s length");
|
||||
at += len;
|
||||
}
|
||||
(counts, ids)
|
||||
}
|
||||
|
||||
// The shared object hint table.
|
||||
let s = int(&hint.value, b"S");
|
||||
let mut r = BitReader { data, at: 8 * s };
|
||||
let (first_shared, first_shared_at, nfirst, ntotal) = (r.get(32) as u32, r.get(32) as usize, r.get(32), r.get(32));
|
||||
let (group_bits, min_group, group_len_bits) = (r.get(16), r.get(32), r.get(16));
|
||||
fn read_shared_header(r: &mut BitReader<'_>) -> (u32, usize, u64, u64) {
|
||||
let first_shared = r.get(32) as u32;
|
||||
let first_shared_at = r.get(32) as usize;
|
||||
let nfirst = r.get(32);
|
||||
let ntotal = r.get(32);
|
||||
let group_bits = r.get(16);
|
||||
assert_eq!(group_bits, 0, "one object a group");
|
||||
(first_shared, first_shared_at, nfirst, ntotal)
|
||||
}
|
||||
|
||||
fn check_shared_hints(
|
||||
r: &mut BitReader<'_>,
|
||||
ntotal: u64,
|
||||
nfirst: u64,
|
||||
first_page_objects: u32,
|
||||
shared_object: &impl Fn(u64) -> u32,
|
||||
span: &impl Fn(u32) -> usize,
|
||||
) {
|
||||
assert_eq!(nfirst as u32, first_page_objects);
|
||||
let shared_object = |id: u64| if id < nfirst { pages[0] + id as u32 } else { first_shared + (id - nfirst) as u32 };
|
||||
if ntotal > nfirst {
|
||||
assert_eq!(first_shared_at, adjusted(first_shared), "the shared objects' location");
|
||||
}
|
||||
let groups = r.column(ntotal as usize, group_len_bits);
|
||||
for (id, g) in groups.iter().enumerate() {
|
||||
let (min_group, group_len_bits) = (r.get(32), r.get(16));
|
||||
for (id, g) in r.column(ntotal as usize, group_len_bits).iter().enumerate() {
|
||||
assert_eq!((g + min_group) as usize, span(shared_object(id as u64)), "shared object {id}'s length");
|
||||
}
|
||||
assert!(r.column(ntotal as usize, 1).iter().all(|s| *s == 0), "no signatures");
|
||||
}
|
||||
|
||||
// What each page uses lies in its own objects, the first part or its shared objects.
|
||||
let page_set: HashSet<u32> = pages.iter().copied().collect();
|
||||
for (i, &page) in pages.iter().enumerate() {
|
||||
let own: HashSet<u32> = (page..page + counts[i] as u32).collect();
|
||||
let listed: HashSet<u32> = ids[i].iter().map(|id| shared_object(*id)).collect();
|
||||
let (mut seen, mut queue) = (HashSet::from([page]), vec![page]);
|
||||
while let Some(o) = queue.pop() {
|
||||
assert!(
|
||||
own.contains(&o) || first_part.contains(&o) && (i == 0 || o < pages[0] || listed.contains(&o)) || listed.contains(&o),
|
||||
"page {i} uses object {o}, which isn't among its objects or listed"
|
||||
);
|
||||
let mut links = vec![];
|
||||
objects[&o].value.refs(&[b"Parent", b"Thumb"], &mut links);
|
||||
queue.extend(links.into_iter().filter(|m| !page_set.contains(m) && !nodes.contains(m) && seen.insert(*m)));
|
||||
}
|
||||
assert!(listed.iter().all(|o| seen.contains(o)), "page {i} lists only objects it uses");
|
||||
struct PageReferenceHints<'a> {
|
||||
pages: &'a [u32],
|
||||
page_set: &'a HashSet<u32>,
|
||||
first_part: &'a HashSet<u32>,
|
||||
objects: &'a HashMap<u32, Indirect<'a>>,
|
||||
nodes: &'a HashSet<u32>,
|
||||
shared_object: &'a dyn Fn(u64) -> u32,
|
||||
}
|
||||
|
||||
fn check_page_references(hints: &PageReferenceHints<'_>, i: usize, page: u32, count: u64, ids: &[u64]) {
|
||||
let own: HashSet<u32> = (page..page + count as u32).collect();
|
||||
let listed: HashSet<u32> = ids.iter().map(|id| (hints.shared_object)(*id)).collect();
|
||||
let (mut seen, mut queue) = (HashSet::from([page]), vec![page]);
|
||||
while let Some(o) = queue.pop() {
|
||||
assert!(
|
||||
own.contains(&o) || hints.first_part.contains(&o) && (i == 0 || o < hints.pages[0] || listed.contains(&o)) || listed.contains(&o),
|
||||
"page {i} uses object {o}, which isn't among its objects or listed"
|
||||
);
|
||||
let mut links = vec![];
|
||||
hints.objects[&o].value.refs(&[&b"Parent"[..], &b"Thumb"[..]], &mut links);
|
||||
queue.extend(links.into_iter().filter(|m| !hints.page_set.contains(m) && !hints.nodes.contains(m) && seen.insert(*m)));
|
||||
}
|
||||
Linearised { pages, first_part, encrypted }
|
||||
assert!(listed.iter().all(|o| seen.contains(o)), "page {i} lists only objects it uses");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user