mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-26 11:08:48 -06:00
Merge PR #4181: M11: PDF Optimizer Clean Up stores LZW streams with Flate
Conflicts: ATTRIBUTION.toml: took main's side (hashes recomputed after the merges). crates/optimize/src/lib.rs, crates/automation/src/lib.rs, tools.rs: Settings and tool schema keep object_streams (#3227) and lzw_to_flate (#4181); doc_optimize description names both options. crates/optimize/tests/optimize.rs: both sides' imports and tests kept.
This commit is contained in:
Generated
+1
@@ -4235,6 +4235,7 @@ dependencies = [
|
||||
"pdfcraft-annot",
|
||||
"pdfcraft-content",
|
||||
"pdfcraft-cos",
|
||||
"pdfcraft-filters",
|
||||
"pdfcraft-model",
|
||||
"pdfcraft-render",
|
||||
"thiserror 2.0.21",
|
||||
|
||||
@@ -1261,6 +1261,7 @@ impl Automation {
|
||||
("discard_print_settings", &mut settings.discard_print_settings),
|
||||
("flate_unencoded", &mut settings.flate_unencoded),
|
||||
("object_streams", &mut settings.object_streams),
|
||||
("lzw_to_flate", &mut settings.lzw_to_flate),
|
||||
("remove_invalid_links", &mut settings.remove_invalid_links),
|
||||
("remove_unreferenced_dests", &mut settings.remove_unreferenced_dests),
|
||||
] {
|
||||
@@ -1314,6 +1315,7 @@ impl Automation {
|
||||
"alternate_images": o.alternate_images,
|
||||
"tags_removed": o.tags_removed,
|
||||
"streams_compressed": o.streams_compressed,
|
||||
"lzw_streams": o.lzw_streams,
|
||||
"invalid_links": o.invalid_links,
|
||||
"invalid_bookmarks": o.invalid_bookmarks,
|
||||
"unreferenced_dests": o.unreferenced_dests,
|
||||
|
||||
@@ -1375,7 +1375,7 @@ pub fn tools() -> Vec<ToolDef> {
|
||||
.with(schema(json!({ "doc": doc() }), &["doc"])),
|
||||
t("doc_open_revision", "Open a revision", "Open saved revision `revision` (1 = the oldest) of a document as a new, unsaved document, to see the file as it was then.")
|
||||
.with(schema(json!({ "doc": doc(), "revision": { "type": "integer", "minimum": 1 } }), &["doc", "revision"])),
|
||||
t("doc_optimize", "Optimize PDF", "Write an optimized copy to `path` (Acrobat's PDF Optimizer). preset: standard (default; Reduce File Size's choices) or mobile (images to 96 ppi above 144, JPEG high quality); the options below change it. compatibility: retain (default; keeps the document's version, or 1.5 when object streams are written), 1.4, 1.5, 1.6, 1.7 or 2.0. merge_fragments (default true) first puts a page picture drawn as many stacked image strips (as some PDF printers write it) back together into one image, so it can be resampled (merged_images, fragments_merged). color / gray: { downsample, ppi, above_ppi, compression: jpeg|zip|retain, quality 1–100 } (defaults: downsample to 150 ppi above 225, JPEG 60). Images are measured where pages draw them and replaced only when smaller. discard_*: thumbnails (default true), alternate_images (true), tags, print_settings; flate_unencoded (true); object_streams (default true; always off for 1.4); remove_invalid_links and remove_unreferenced_dests (true); images and forms the pages list but never draw are always dropped (unused_xobjects). discard: Remove Hidden Information categories (metadata, attachments, comments, form-fields, hidden-text, hidden-layers, bookmarks, links-actions-scripts, private-data). Signed documents are refused. The open document is unchanged.")
|
||||
t("doc_optimize", "Optimize PDF", "Write an optimized copy to `path` (Acrobat's PDF Optimizer). preset: standard (default; Reduce File Size's choices) or mobile (images to 96 ppi above 144, JPEG high quality); the options below change it. compatibility: retain (default; keeps the document's version, or 1.5 when object streams are written), 1.4, 1.5, 1.6, 1.7 or 2.0. merge_fragments (default true) first puts a page picture drawn as many stacked image strips (as some PDF printers write it) back together into one image, so it can be resampled (merged_images, fragments_merged). color / gray: { downsample, ppi, above_ppi, compression: jpeg|zip|retain, quality 1–100 } (defaults: downsample to 150 ppi above 225, JPEG 60). Images are measured where pages draw them and replaced only when smaller. discard_*: thumbnails (default true), alternate_images (true), tags, print_settings; flate_unencoded (true); object_streams (default true; always off for 1.4); lzw_to_flate (true) decodes LZW streams and stores them with Flate instead, leaving any it cannot decode unchanged (lzw_streams counts the streams converted); remove_invalid_links and remove_unreferenced_dests (true); images and forms the pages list but never draw are always dropped (unused_xobjects). discard: Remove Hidden Information categories (metadata, attachments, comments, form-fields, hidden-text, hidden-layers, bookmarks, links-actions-scripts, private-data). Signed documents are refused. The open document is unchanged.")
|
||||
.cmd("optimize.advanced")
|
||||
.with(schema(
|
||||
json!({
|
||||
@@ -1392,6 +1392,7 @@ pub fn tools() -> Vec<ToolDef> {
|
||||
"discard_print_settings": { "type": "boolean" },
|
||||
"flate_unencoded": { "type": "boolean" },
|
||||
"object_streams": { "type": "boolean" },
|
||||
"lzw_to_flate": { "type": "boolean" },
|
||||
"remove_invalid_links": { "type": "boolean" },
|
||||
"remove_unreferenced_dests": { "type": "boolean" },
|
||||
"discard": { "type": "array", "items": { "type": "string" } },
|
||||
|
||||
@@ -5784,6 +5784,56 @@ fn optimizing_through_tools() {
|
||||
assert!(matches!(a.call("doc_optimize", &json!({ "doc": doc, "path": "x.pdf", "discard": ["everything"] })), Err(ToolError::InvalidArgs(_))));
|
||||
}
|
||||
|
||||
/// `data` as LZW with one 9-bit literal code per byte (clear-table first, EOD last): valid for
|
||||
/// fewer than 250 bytes, before the code width grows.
|
||||
fn lzw_literals(data: &[u8]) -> Vec<u8> {
|
||||
let codes: Vec<u16> = std::iter::once(256).chain(data.iter().map(|&b| u16::from(b))).chain([257]).collect();
|
||||
let bits: Vec<bool> = codes.iter().flat_map(|c| (0..9).rev().map(move |i| c >> i & 1 == 1)).collect();
|
||||
bits.chunks(8).map(|byte| byte.iter().enumerate().fold(0u8, |acc, (i, &b)| acc | (u8::from(b) << (7 - i)))).collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lzw_streams_are_stored_with_flate_through_tools() {
|
||||
use pdfcraft_cos::{Dict, Document, Object, SaveOptions, Stream, write_full};
|
||||
let dir = workdir("optimize-lzw");
|
||||
let ops = b"0.2 0.4 0.8 rg 72 72 200 100 re f 0 0 1 RG 72 300 m 400 500 l S".to_vec();
|
||||
let mut doc = Document::new_empty();
|
||||
let pages = doc.get(doc.root().unwrap()).as_dict().unwrap().reference(b"Pages").unwrap();
|
||||
let lzw = Stream::from_raw(Dict::from_iter([(b"Filter".to_vec(), Object::name("LZWDecode"))]), lzw_literals(&ops));
|
||||
assert_eq!(lzw.decoded_strict().unwrap(), ops);
|
||||
let content = doc.add(Object::Stream(lzw));
|
||||
let page = doc.add(Object::Dict(Dict::from_iter([
|
||||
(b"Type".to_vec(), Object::name("Page")),
|
||||
(b"Parent".to_vec(), Object::Ref(pages)),
|
||||
(b"MediaBox".to_vec(), Object::Array(vec![0.into(), 0.into(), 612.into(), 792.into()])),
|
||||
(b"Contents".to_vec(), Object::Ref(content)),
|
||||
])));
|
||||
doc.update_dict(pages, |d| {
|
||||
d.set(b"Kids".to_vec(), Object::Array(vec![Object::Ref(page)]));
|
||||
d.set(b"Count".to_vec(), Object::Int(1));
|
||||
})
|
||||
.unwrap();
|
||||
std::fs::write(dir.join("lzw.pdf"), write_full(&doc, &SaveOptions::default()).unwrap()).unwrap();
|
||||
let mut a = auto(&dir);
|
||||
let doc = ok(&mut a, "doc_open", json!({ "path": "lzw.pdf" }))["doc"].as_u64().unwrap();
|
||||
let filters = |name: &str| {
|
||||
let saved = pdfcraft_cos::Document::open(std::sync::Arc::new(std::fs::read(dir.join(name)).unwrap())).unwrap();
|
||||
let catalog = saved.get(saved.root().unwrap()).as_dict().cloned().unwrap();
|
||||
let pages = saved.get(catalog.reference(b"Pages").unwrap()).as_dict().cloned().unwrap();
|
||||
let page = saved.get(pages.get(b"Kids").and_then(|k| k.as_array()?.first()?.as_ref()).unwrap()).as_dict().cloned().unwrap();
|
||||
match &*saved.get(page.reference(b"Contents").unwrap()) {
|
||||
Object::Stream(s) => (s.dict.get(b"Filter").cloned(), s.decoded_strict().unwrap()),
|
||||
_ => panic!("the page content is not a stream"),
|
||||
}
|
||||
};
|
||||
let r = ok(&mut a, "doc_optimize", json!({ "doc": doc, "path": "flate.pdf" }));
|
||||
assert_eq!(r["lzw_streams"].as_u64(), Some(1), "{r}");
|
||||
assert_eq!(filters("flate.pdf"), (Some(Object::name("FlateDecode")), ops.clone()));
|
||||
let off = ok(&mut a, "doc_optimize", json!({ "doc": doc, "path": "kept.pdf", "lzw_to_flate": false }));
|
||||
assert_eq!(off["lzw_streams"].as_u64(), Some(0), "{off}");
|
||||
assert_eq!(filters("kept.pdf"), (Some(Object::name("LZWDecode")), ops));
|
||||
}
|
||||
|
||||
/// A one-page PDF whose page picture is drawn as `rows` grey images one pixel row high, 600 px
|
||||
/// wide on a 72 pt square (600 ppi), as some PDF printers write it.
|
||||
fn printed_in_strips(rows: usize) -> Vec<u8> {
|
||||
|
||||
@@ -12,6 +12,7 @@ workspace = true
|
||||
|
||||
[dependencies]
|
||||
pdfcraft-cos = { workspace = true }
|
||||
pdfcraft-filters = { workspace = true }
|
||||
pdfcraft-content = { workspace = true }
|
||||
pdfcraft-model = { workspace = true }
|
||||
pdfcraft-annot = { workspace = true }
|
||||
|
||||
@@ -32,7 +32,11 @@ let report = optimize_with_progress(&mut doc, &settings, &mut |stage| keep_going
|
||||
- **Discard objects:** thumbnails, alternate images, document tags, print settings.
|
||||
- **Clean up:** Flate for streams with no filter; for streams with an outer ASCII85 or ASCIIHex
|
||||
layer, that layer is removed when the result is smaller (the inner filters keep their encoded
|
||||
bytes). Streams with `/DecodeParms`, images and object streams are left alone.
|
||||
bytes; streams with `/DecodeParms`, images and object streams keep their ASCII layer. LZW-compressed
|
||||
streams, content and images alike, are decoded and stored with Flate instead (Acrobat's "In
|
||||
streams that use LZW encoding, use Flate instead"): a predictor carries over to Flate without
|
||||
`EarlyChange`, filters applied before the LZW one go with it and those after it keep their
|
||||
bytes. A stream whose LZW data doesn't decode cleanly is left as it is. Not yet: inline images.
|
||||
|
||||
The engine runs Remove Hidden Information (`pdfcraft-redact`) for the user-data categories,
|
||||
then this, then merges identical objects and writes a full, compressed save
|
||||
|
||||
+133
-2
@@ -14,7 +14,7 @@
|
||||
//! print settings.
|
||||
//! - **Clean up:** drop images and forms that pages list but never draw (such as a deleted
|
||||
//! image); Flate-compress streams that have no filter; remove a redundant outer ASCII85 or
|
||||
//! ASCIIHex layer from streams that have one.
|
||||
//! ASCIIHex layer from streams that have one; store LZW-compressed streams with Flate instead.
|
||||
//!
|
||||
//! Metadata, attachments, comments, scripts, private data, hidden layers, bookmarks and form
|
||||
//! fields are discarded by `pdfcraft-redact`'s Remove Hidden Information, which the engine
|
||||
@@ -30,7 +30,8 @@ mod unused;
|
||||
|
||||
pub use audit::{SpaceCategory, SpaceUse, audit_space};
|
||||
|
||||
use pdfcraft_cos::{Document, ObjRef, Object, Stream, serialize};
|
||||
use pdfcraft_cos::{Dict, Document, MAX_PAGE_DECODED, ObjRef, Object, Stream, serialize};
|
||||
use pdfcraft_filters::{Filter, Params};
|
||||
|
||||
pub use images::effective_resolutions;
|
||||
|
||||
@@ -155,6 +156,8 @@ pub struct Settings {
|
||||
/// Clean Up: pack non-stream objects into compressed object streams with a cross-reference
|
||||
/// stream (PDF 1.5+, §7.5.7–7.5.8). Always off when `compatibility` is [`Compatibility::Pdf1_4`].
|
||||
pub object_streams: bool,
|
||||
/// Clean Up: decode LZW-compressed streams and store them with Flate instead.
|
||||
pub lzw_to_flate: bool,
|
||||
/// Clean Up: remove links (and bookmark destinations) that point nowhere.
|
||||
pub remove_invalid_links: bool,
|
||||
/// Clean Up: remove named destinations nothing refers to.
|
||||
@@ -183,6 +186,7 @@ impl Default for Settings {
|
||||
discard_print_settings: false,
|
||||
flate_unencoded: true,
|
||||
object_streams: true,
|
||||
lzw_to_flate: true,
|
||||
remove_invalid_links: true,
|
||||
remove_unreferenced_dests: true,
|
||||
}
|
||||
@@ -243,6 +247,8 @@ pub struct Report {
|
||||
pub tags_removed: bool,
|
||||
pub print_settings: usize,
|
||||
pub streams_compressed: usize,
|
||||
/// Streams whose LZW compression was replaced with Flate (Clean Up).
|
||||
pub lzw_streams: usize,
|
||||
/// Links removed and bookmarks whose destination was cleared (Clean Up).
|
||||
pub invalid_links: usize,
|
||||
pub invalid_bookmarks: usize,
|
||||
@@ -375,6 +381,19 @@ pub fn optimize_with_progress(doc: &mut Document, settings: &Settings, progress:
|
||||
if settings.remove_unreferenced_dests {
|
||||
report.unreferenced_dests = links::remove_unreferenced_dests(doc)?;
|
||||
}
|
||||
// Before the unencoded-stream pass, which would otherwise take the ASCII layer off an
|
||||
// `[/ASCII85Decode /LZWDecode]` stream first and count it twice.
|
||||
if settings.lzw_to_flate {
|
||||
for num in doc.object_numbers() {
|
||||
let r = ObjRef { num, generation: doc.generation(num) };
|
||||
let obj = doc.get(r);
|
||||
let Object::Stream(s) = &*obj else { continue };
|
||||
if let Some(replacement) = lzw_to_flate(s, MAX_LZW_DECODED) {
|
||||
doc.set(r, Object::Stream(replacement));
|
||||
report.lzw_streams += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if settings.flate_unencoded {
|
||||
for num in doc.object_numbers() {
|
||||
let r = ObjRef { num, generation: doc.generation(num) };
|
||||
@@ -401,6 +420,101 @@ pub fn optimize_with_progress(doc: &mut Document, settings: &Settings, progress:
|
||||
Ok(report)
|
||||
}
|
||||
|
||||
/// Largest decoded LZW stream converted, as for a page's content: the data and its check are
|
||||
/// held together, so a few hundred kilobytes of hostile LZW can't ask for gigabytes.
|
||||
const MAX_LZW_DECODED: usize = MAX_PAGE_DECODED;
|
||||
|
||||
/// `s` with its LZW compression replaced by Flate (Acrobat's "In streams that use LZW encoding,
|
||||
/// use Flate instead"). The filters up to the last LZW one are decoded, strictly and with its
|
||||
/// predictor; the result is Flate-encoded under the same predictor, without `EarlyChange`, and
|
||||
/// the filters after it keep their encoded bytes. A stream that doesn't decode cleanly, or
|
||||
/// whose filters or parameters can't be read for certain, or that decodes past `max` bytes, is
|
||||
/// left as it is.
|
||||
fn lzw_to_flate(s: &Stream, max: usize) -> Option<Stream> {
|
||||
// An external file's data isn't in `raw`; cross-reference and object streams are rewritten
|
||||
// on save, so converting them would change nothing in the file.
|
||||
if s.dict.contains(b"F") || matches!(s.dict.name(b"Type"), Some(b"XRef" | b"ObjStm")) {
|
||||
return None;
|
||||
}
|
||||
let names: Vec<&[u8]> = match s.dict.get(b"Filter")? {
|
||||
Object::Name(n) => vec![n.as_slice()],
|
||||
Object::Array(a) => a.iter().map(Object::as_name).collect::<Option<_>>()?,
|
||||
_ => return None,
|
||||
};
|
||||
// `/Crypt` was applied when the object was loaded, and dropping it would change how the
|
||||
// stream is encrypted.
|
||||
if names.contains(&&b"Crypt"[..]) {
|
||||
return None;
|
||||
}
|
||||
let lzw = names.iter().rposition(|n| matches!(*n, b"LZWDecode" | b"LZW"))?;
|
||||
// One entry per filter, each a dictionary or null; anything else (an indirect reference, an
|
||||
// array of the wrong length) would be read as defaults, so the data might not decode as
|
||||
// written.
|
||||
let parms: Vec<Option<&Dict>> = match s.dict.get(b"DecodeParms") {
|
||||
None | Some(Object::Null) => vec![None; names.len()],
|
||||
Some(Object::Dict(d)) if names.len() == 1 => vec![Some(d)],
|
||||
Some(Object::Array(a)) if a.len() == names.len() => a
|
||||
.iter()
|
||||
.map(|p| match p {
|
||||
Object::Dict(d) => Some(Some(d)),
|
||||
Object::Null => Some(None),
|
||||
_ => None,
|
||||
})
|
||||
.collect::<Option<_>>()?,
|
||||
_ => return None,
|
||||
};
|
||||
let chain = s.filters();
|
||||
let stages = chain.get(..=lzw)?;
|
||||
let (_, params) = stages.last()?;
|
||||
// The decoded stages' parameters must be direct integers: `filters` reads anything else (a
|
||||
// reference, 12.5, a name) as the default, and the dictionary carried over would then
|
||||
// describe the new data differently from how it was encoded.
|
||||
let direct = |p: &Option<&Dict>| p.is_none_or(|d| d.iter().all(|(_, v)| matches!(v, Object::Int(_))));
|
||||
if !parms.get(..=lzw)?.iter().all(direct) {
|
||||
return None;
|
||||
}
|
||||
let data = pdfcraft_filters::decode(stages, &s.raw, max).ok()?;
|
||||
let flate = Params { early_change: 1, ..params.clone() };
|
||||
let raw = pdfcraft_filters::encode(&Filter::Flate, &flate, &data).ok()?;
|
||||
// The predictor's encoder must give back exactly what its decoder read (a short last row,
|
||||
// unusual parameters), or the stream would change. The limit counts a PNG predictor's tag
|
||||
// bytes too, at most one per row of at least one byte.
|
||||
if pdfcraft_filters::decode(&[(Filter::Flate, flate)], &raw, data.len().saturating_mul(2).saturating_add(1)).ok()? != data {
|
||||
return None;
|
||||
}
|
||||
// Without a predictor the other parameters say nothing to Flate.
|
||||
let first = parms.get(lzw).copied().flatten().filter(|_| params.predictor > 1).map(|d| {
|
||||
let mut d = d.clone();
|
||||
d.remove(b"EarlyChange");
|
||||
Object::Dict(d)
|
||||
});
|
||||
let rest_names = names.get(lzw + 1..)?;
|
||||
let rest_parms = parms.get(lzw + 1..)?;
|
||||
let mut dict = s.dict.clone();
|
||||
dict.remove(b"Length");
|
||||
if rest_names.is_empty() {
|
||||
dict.set(b"Filter".to_vec(), Object::name("FlateDecode"));
|
||||
match first {
|
||||
Some(p) => dict.set(b"DecodeParms".to_vec(), p),
|
||||
None => {
|
||||
dict.remove(b"DecodeParms");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let filters = std::iter::once(Object::name("FlateDecode")).chain(rest_names.iter().map(|n| Object::Name(n.to_vec())));
|
||||
dict.set(b"Filter".to_vec(), Object::Array(filters.collect()));
|
||||
let parms: Vec<Object> = std::iter::once(first.unwrap_or(Object::Null))
|
||||
.chain(rest_parms.iter().map(|p| p.map_or(Object::Null, |d| Object::Dict(d.clone()))))
|
||||
.collect();
|
||||
if parms.iter().all(|p| matches!(p, Object::Null)) {
|
||||
dict.remove(b"DecodeParms");
|
||||
} else {
|
||||
dict.set(b"DecodeParms".to_vec(), Object::Array(parms));
|
||||
}
|
||||
}
|
||||
Some(Stream::from_raw(dict, raw))
|
||||
}
|
||||
|
||||
/// `s` without its outer ASCII85 or ASCIIHex layer (`[/ASCII85Decode /FlateDecode]` becomes
|
||||
/// `[/FlateDecode]`), when that is smaller. Only the outer layer is decoded: strictly, and within
|
||||
/// the stored size, which ASCII text never falls below, so the inner encoded bytes stay exact.
|
||||
@@ -431,3 +545,20 @@ fn stored_len(s: &Stream) -> Option<usize> {
|
||||
serialize(&Object::Dict(dict), &mut out);
|
||||
out.len().checked_add(s.raw.len())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn an_lzw_stream_that_decodes_past_the_limit_is_left_as_it_is() {
|
||||
let data = b"0 0 m 612 792 l S\n".repeat(64);
|
||||
let raw = pdfcraft_filters::encode(&Filter::Lzw, &Params::default(), &data).unwrap();
|
||||
let mut dict = Dict::new();
|
||||
dict.set(b"Filter".to_vec(), Object::name("LZWDecode"));
|
||||
let s = Stream::from_raw(dict, raw);
|
||||
assert!(lzw_to_flate(&s, data.len() - 1).is_none());
|
||||
let converted = lzw_to_flate(&s, data.len()).unwrap();
|
||||
assert_eq!(converted.decoded_strict().unwrap(), data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use pdfcraft_cos::{Dict, Document, ObjRef, Object, SaveOptions, Stream, write_full};
|
||||
use pdfcraft_filters::{Filter, Params};
|
||||
use pdfcraft_optimize::{
|
||||
Compatibility, Compression, ImageSettings, OptimizeError, Settings, SpaceCategory, audit_space, effective_resolutions, optimize,
|
||||
};
|
||||
@@ -1280,3 +1281,214 @@ fn named_dest_keys(doc: &Document) -> Vec<Vec<u8>> {
|
||||
let arr = doc.resolve(tree.get(b"Names").unwrap()).as_array().cloned().unwrap();
|
||||
arr.chunks(2).filter_map(|c| doc.resolve(&c[0]).as_string().map(|s| s.bytes.clone())).collect()
|
||||
}
|
||||
|
||||
/// Page content long enough for LZW codes to grow past 9 bits, where `EarlyChange` matters.
|
||||
fn lzw_ops() -> Vec<u8> {
|
||||
(0..300).flat_map(|i| format!("{} {} m {} {} l S\n", i % 97, i * 7 % 389, i * 13 % 612, i * 31 % 792).into_bytes()).collect()
|
||||
}
|
||||
|
||||
fn lzw(data: &[u8], params: &Params) -> Vec<u8> {
|
||||
pdfcraft_filters::encode(&Filter::Lzw, params, data).unwrap()
|
||||
}
|
||||
|
||||
/// `params` as a `/DecodeParms` dictionary, only the entries that differ from the defaults.
|
||||
fn decode_parms(p: &Params) -> Dict {
|
||||
let mut d = Dict::new();
|
||||
for (k, v, default) in [
|
||||
(&b"Predictor"[..], p.predictor, 1),
|
||||
(b"Colors", p.colors, 1),
|
||||
(b"BitsPerComponent", p.bits_per_component, 8),
|
||||
(b"Columns", p.columns, 1),
|
||||
(b"EarlyChange", p.early_change, 1),
|
||||
] {
|
||||
if v != default {
|
||||
d.set(k.to_vec(), Object::Int(v));
|
||||
}
|
||||
}
|
||||
d
|
||||
}
|
||||
|
||||
/// A 64 × 48 RGB image, LZW-encoded with `params`.
|
||||
fn lzw_image(doc: &mut Document, params: &Params) -> (ObjRef, Vec<u8>) {
|
||||
let (w, h) = (64u32, 48u32);
|
||||
let px: Vec<u8> = (0..w * h).flat_map(|i| [(i % w * 4) as u8, (i / w * 5) as u8, ((i % w + i / w) * 2) as u8]).collect();
|
||||
let mut d = Dict::new();
|
||||
d.set(b"Type".to_vec(), Object::name("XObject"));
|
||||
d.set(b"Subtype".to_vec(), Object::name("Image"));
|
||||
d.set(b"Width".to_vec(), Object::Int(w.into()));
|
||||
d.set(b"Height".to_vec(), Object::Int(h.into()));
|
||||
d.set(b"BitsPerComponent".to_vec(), Object::Int(8));
|
||||
d.set(b"ColorSpace".to_vec(), Object::name("DeviceRGB"));
|
||||
d.set(b"Filter".to_vec(), Object::name("LZWDecode"));
|
||||
d.set(b"DecodeParms".to_vec(), Object::Dict(decode_parms(params)));
|
||||
(doc.add(Object::Stream(Stream::from_raw(d, lzw(&px, params)))), px)
|
||||
}
|
||||
|
||||
/// Settings that leave images to the clean-up: no resampling, no recompression.
|
||||
fn clean_up_only() -> Settings {
|
||||
let keep = ImageSettings { downsample: false, compression: Compression::Retain, ..ImageSettings::REDUCE };
|
||||
Settings { color: keep, gray: keep, ..Settings::default() }
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lzw_streams_become_flate_and_decode_to_the_same_bytes() {
|
||||
let ops = lzw_ops();
|
||||
let early = Params::default();
|
||||
let late = Params { early_change: 0, ..Params::default() };
|
||||
let png = Params { predictor: 15, colors: 3, columns: 64, ..Params::default() };
|
||||
let png_late = Params { early_change: 0, ..png.clone() };
|
||||
let mut doc = Document::new_empty();
|
||||
let content = |doc: &mut Document, p: &Params| {
|
||||
let mut d = Dict::new();
|
||||
d.set(b"Filter".to_vec(), Object::name("LZWDecode"));
|
||||
if *p != Params::default() {
|
||||
d.set(b"DecodeParms".to_vec(), Object::Dict(decode_parms(p)));
|
||||
}
|
||||
doc.add(Object::Stream(Stream::from_raw(d, lzw(&ops, p))))
|
||||
};
|
||||
let c_early = content(&mut doc, &early);
|
||||
let c_late = content(&mut doc, &late);
|
||||
let (img, px) = lzw_image(&mut doc, &png);
|
||||
let (img_late, _) = lzw_image(&mut doc, &png_late);
|
||||
page(&mut doc, &[("Im", img), ("Im2", img_late)], "q 64 0 0 48 72 600 cm /Im Do Q q 64 0 0 48 172 600 cm /Im2 Do Q");
|
||||
let pg = pdfcraft_annot::page_refs(&doc).unwrap()[0];
|
||||
doc.update_dict(pg, |d| {
|
||||
d.set(b"Contents".to_vec(), Object::Array(vec![Object::Ref(c_early), Object::Ref(c_late), d.get(b"Contents").unwrap().clone()]))
|
||||
})
|
||||
.unwrap();
|
||||
// Each LZW stream reads as its own data before, so a decoder ignoring EarlyChange fails here.
|
||||
assert_eq!((stream(&doc, c_early).decoded_strict().unwrap(), stream(&doc, c_late).decoded_strict().unwrap()), (ops.clone(), ops.clone()));
|
||||
assert!(lzw(&ops, &early) != lzw(&ops, &late), "the two code widths differ on this data");
|
||||
|
||||
let r = optimize(&mut doc, &clean_up_only()).unwrap();
|
||||
assert_eq!(r.lzw_streams, 4, "{r:?}");
|
||||
for (label, at, data) in
|
||||
[("content", c_early, &ops), ("content, EarlyChange 0", c_late, &ops), ("image", img, &px), ("image, EarlyChange 0", img_late, &px)]
|
||||
{
|
||||
let after = stream(&doc, at);
|
||||
assert_eq!(after.dict.get(b"Filter"), Some(&Object::name("FlateDecode")), "{label}");
|
||||
assert_eq!(&after.decoded_strict().unwrap(), data, "{label}");
|
||||
let parms = after.dict.get(b"DecodeParms").and_then(Object::as_dict);
|
||||
assert!(parms.is_none_or(|p| !p.contains(b"EarlyChange")), "{label}: EarlyChange is LZW's alone");
|
||||
}
|
||||
// The image keeps its PNG predictor, now applied under Flate.
|
||||
let parms = stream(&doc, img).dict.get(b"DecodeParms").and_then(Object::as_dict).cloned().unwrap();
|
||||
assert_eq!((parms.int(b"Predictor"), parms.int(b"Colors"), parms.int(b"Columns")), (Some(15), Some(3), Some(64)));
|
||||
assert!(!stream(&doc, c_late).dict.contains(b"DecodeParms"), "nothing left to say for the content stream");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_page_drawn_from_lzw_streams_renders_the_same_after_conversion() {
|
||||
let png = Params { predictor: 12, colors: 3, columns: 64, early_change: 0, ..Params::default() };
|
||||
let mut doc = Document::new_empty();
|
||||
let (img, _) = lzw_image(&mut doc, &png);
|
||||
let pg = page(&mut doc, &[("Im", img)], "");
|
||||
let ops = b"0.2 0.5 0.8 rg 36 36 300 200 re f q 256 0 0 192 72 400 cm /Im Do Q\n".repeat(4);
|
||||
let mut d = Dict::new();
|
||||
d.set(b"Filter".to_vec(), Object::name("LZWDecode"));
|
||||
d.set(b"DecodeParms".to_vec(), Object::Dict(decode_parms(&Params { early_change: 0, ..Params::default() })));
|
||||
let c = doc.add(Object::Stream(Stream::from_raw(d, lzw(&ops, &Params { early_change: 0, ..Params::default() }))));
|
||||
doc.update_dict(pg, |p| p.set(b"Contents".to_vec(), Object::Ref(c))).unwrap();
|
||||
let render = |doc: &Document| {
|
||||
let bytes = write_full(doc, &SaveOptions::default()).unwrap();
|
||||
let mut r = pdfcraft_render::PageRenderer::new(Arc::new(bytes), pdfcraft_render::RenderConfig::default());
|
||||
let out = r.render(pdfcraft_render::RenderRequest { page: 0, scale: 1.0, ..Default::default() });
|
||||
assert!(out.error.is_none(), "{:?}", out.error);
|
||||
out.rgba
|
||||
};
|
||||
let before = render(&doc);
|
||||
// The picture and the rectangle are drawn: the page is not blank.
|
||||
assert!(before.chunks(4).any(|p| p[..3] != [255, 255, 255]));
|
||||
let r = optimize(&mut doc, &clean_up_only()).unwrap();
|
||||
assert_eq!(r.lzw_streams, 2, "{r:?}");
|
||||
assert_eq!(stream(&doc, c).dict.name(b"Filter"), Some(&b"FlateDecode"[..]));
|
||||
assert!(render(&doc) == before, "the page renders pixel for pixel as before");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lzw_inside_a_filter_chain_is_replaced_and_the_other_stages_kept() {
|
||||
let ops = lzw_ops();
|
||||
let inner = lzw(&ops, &Params::default());
|
||||
// An ASCII85 layer outside the LZW goes with it; a JPEG inside it stays encoded as it was.
|
||||
let (mut doc, c) = with_content(filtered(&["ASCII85Decode", "LZWDecode"], ascii85(&inner)));
|
||||
let photo = image(&mut doc, 40, 30, 3, true, None);
|
||||
let jpeg = stream(&doc, photo).raw.to_vec();
|
||||
let mut picture = filtered(&["LZWDecode", "DCTDecode"], lzw(&jpeg, &Params::default()));
|
||||
picture.dict.set(b"DecodeParms".to_vec(), Object::Array(vec![Object::Null, Object::Null]));
|
||||
let pic = doc.add(Object::Stream(picture));
|
||||
// An ASCII85 layer over LZW with a PNG predictor: the predictor's parameters move to the one
|
||||
// Flate filter left, without EarlyChange.
|
||||
let rows: Vec<u8> = (0..40 * 30).map(|i| (i % 40 * 3 + i / 40) as u8).collect();
|
||||
let png = Params { predictor: 12, columns: 40, early_change: 0, ..Params::default() };
|
||||
let mut predicted = filtered(&["ASCII85Decode", "LZWDecode"], ascii85(&lzw(&rows, &png)));
|
||||
predicted.dict.set(b"DecodeParms".to_vec(), Object::Array(vec![Object::Null, Object::Dict(decode_parms(&png))]));
|
||||
let pred = doc.add(Object::Stream(predicted));
|
||||
let r = optimize(&mut doc, &clean_up_only()).unwrap();
|
||||
assert_eq!(r.lzw_streams, 3, "{r:?}");
|
||||
let after = stream(&doc, pred);
|
||||
assert_eq!(after.dict.get(b"Filter"), Some(&Object::name("FlateDecode")));
|
||||
let parms = after.dict.get(b"DecodeParms").and_then(Object::as_dict).cloned().unwrap();
|
||||
assert_eq!((parms.int(b"Predictor"), parms.int(b"Columns"), parms.contains(b"EarlyChange")), (Some(12), Some(40), false));
|
||||
assert_eq!(after.decoded_strict().unwrap(), rows);
|
||||
let after = stream(&doc, c);
|
||||
assert_eq!(after.dict.get(b"Filter"), Some(&Object::name("FlateDecode")));
|
||||
assert_eq!(after.decoded_strict().unwrap(), ops);
|
||||
let after = stream(&doc, pic);
|
||||
assert_eq!(after.dict.get(b"Filter"), Some(&Object::Array(vec![Object::name("FlateDecode"), Object::name("DCTDecode")])));
|
||||
assert!(!after.dict.contains(b"DecodeParms"), "all null: dropped");
|
||||
let inside = pdfcraft_filters::decode(&[(Filter::Flate, Params::default())], &after.raw, 1 << 20).unwrap();
|
||||
assert_eq!(inside, jpeg, "the JPEG bytes are kept exactly");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lzw_streams_that_cannot_be_read_or_are_not_asked_for_stay_unchanged() {
|
||||
let ops = lzw_ops();
|
||||
let good = lzw(&ops, &Params::default());
|
||||
// 9-bit codes 511 after a literal: past the table, corrupt.
|
||||
let corrupt = vec![0x80, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF];
|
||||
assert!(filtered(&["LZWDecode"], corrupt.clone()).decoded_strict().is_err());
|
||||
let mut bad_predictor = filtered(&["LZWDecode"], good.clone());
|
||||
let mut parms = Dict::new();
|
||||
parms.set(b"Predictor".to_vec(), Object::Int(12));
|
||||
parms.set(b"Columns".to_vec(), Object::Int(7));
|
||||
bad_predictor.dict.set(b"DecodeParms".to_vec(), Object::Dict(parms));
|
||||
let mut indirect_parms = filtered(&["LZWDecode"], good.clone());
|
||||
indirect_parms.dict.set(b"DecodeParms".to_vec(), Object::Ref(ObjRef { num: 999, generation: 0 }));
|
||||
// Columns the reader can't take as an integer: read as the default 1 here, so the data is
|
||||
// predicted with one column and decodes, but a viewer resolving the value would use another.
|
||||
let one_column = Params { predictor: 12, ..Params::default() };
|
||||
let odd_columns = |columns: Object| {
|
||||
let mut s = filtered(&["LZWDecode"], lzw(&ops, &one_column));
|
||||
let mut parms = Dict::new();
|
||||
parms.set(b"Predictor".to_vec(), Object::Int(12));
|
||||
parms.set(b"Columns".to_vec(), columns);
|
||||
s.dict.set(b"DecodeParms".to_vec(), Object::Dict(parms));
|
||||
s
|
||||
};
|
||||
assert_eq!(odd_columns(Object::Real(12.5)).decoded_strict().unwrap(), ops, "read with one column");
|
||||
let mut objstm = filtered(&["LZWDecode"], good.clone());
|
||||
objstm.dict.set(b"Type".to_vec(), Object::name("ObjStm"));
|
||||
let mut external = filtered(&["LZWDecode"], good.clone());
|
||||
external.dict.set(b"F".to_vec(), Object::String(pdfcraft_cos::PdfString::literal(b"data.bin".to_vec())));
|
||||
let cases = [
|
||||
("corrupt LZW", filtered(&["LZWDecode"], corrupt), Settings::default()),
|
||||
("corrupt under a PNG predictor", bad_predictor, Settings::default()),
|
||||
("indirect DecodeParms", indirect_parms, Settings::default()),
|
||||
("external file", external, Settings::default()),
|
||||
("object stream, rewritten on save", objstm, Settings::default()),
|
||||
("Columns by reference", odd_columns(Object::Ref(ObjRef { num: 9, generation: 0 })), Settings::default()),
|
||||
("Columns not an integer", odd_columns(Object::Real(12.5)), Settings::default()),
|
||||
("crypt filter", filtered(&["Crypt", "LZWDecode"], good.clone()), Settings::default()),
|
||||
("option off", filtered(&["LZWDecode"], good.clone()), Settings { lzw_to_flate: false, ..Settings::default() }),
|
||||
];
|
||||
for (label, s, settings) in cases {
|
||||
let (mut doc, c) = with_content(s.clone());
|
||||
let r = optimize(&mut doc, &settings).unwrap();
|
||||
assert_eq!(r.lzw_streams, 0, "{label}");
|
||||
assert_eq!(stream(&doc, c), s, "{label}");
|
||||
}
|
||||
// The same data, readable and asked for, is converted: the cases above fail for their reason.
|
||||
let (mut doc, c) = with_content(filtered(&["LZWDecode"], good));
|
||||
assert_eq!(optimize(&mut doc, &Settings::default()).unwrap().lzw_streams, 1);
|
||||
assert_eq!(stream(&doc, c).decoded_strict().unwrap(), ops);
|
||||
}
|
||||
|
||||
@@ -611,6 +611,7 @@
|
||||
Discard private data of other applications تجاهل البيانات الخاصة بالتطبيقات الأخرى
|
||||
Discard hidden text تجاهل النص المخفي
|
||||
Use Flate to encode streams that are not encoded استخدام Flate لترميز التدفقات غير المرمَّزة
|
||||
In streams that use LZW encoding, use Flate instead في التدفقات التي تستخدم ترميز LZW، استخدام Flate بدلاً منه
|
||||
Remove invalid links and bookmarks إزالة الروابط والإشارات المرجعية غير الصالحة
|
||||
Remove unreferenced named destinations إزالة الوجهات المسماة غير المشار إليها
|
||||
Compress document structure (object streams) ضغط بنية المستند (تدفقات الكائنات)
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -282,6 +282,7 @@
|
||||
Discard private data of other applications Премахни частните данни на други приложения
|
||||
Discard hidden text Премахни скрития текст
|
||||
Use Flate to encode streams that are not encoded Кодирай с Flate некодираните потоци
|
||||
In streams that use LZW encoding, use Flate instead Кодирай с Flate потоците, кодирани с LZW
|
||||
Remove invalid links and bookmarks Премахни невалидните връзки и отметки
|
||||
Remove unreferenced named destinations Премахни неизползваните именувани местоназначения
|
||||
Compress document structure (object streams) Компресирай структурата на документа (потоци от обекти)
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 8 and column 16.
|
@@ -574,6 +574,7 @@
|
||||
Discard private data of other applications Descarta les dades privades d'altres aplicacions
|
||||
Discard hidden text Descarta el text ocult
|
||||
Use Flate to encode streams that are not encoded Utilitza Flate per codificar els fluxos que no estan codificats
|
||||
In streams that use LZW encoding, use Flate instead Als fluxos que utilitzen codificació LZW, utilitza Flate
|
||||
Remove invalid links and bookmarks Elimina els enllaços i marcadors no vàlids
|
||||
Remove unreferenced named destinations Elimina les destinacions amb nom sense referència
|
||||
Compress document structure (object streams) Comprimeix l'estructura del document (fluxos d'objectes)
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 7 and column 3.
|
@@ -581,6 +581,7 @@
|
||||
Discard private data of other applications فڕێدانی داتا تایبەتەکانی بەرنامەکانی تر
|
||||
Discard hidden text فڕێدانی دەقی شاراوە
|
||||
Use Flate to encode streams that are not encoded بەکارهێنانی Flate بۆ کۆدکردنی ئەو لێشاوانەی کۆد نەکراون
|
||||
In streams that use LZW encoding, use Flate instead بەکارهێنانی Flate لە جیاتی LZW بۆ ئەو لێشاوانەی بە LZW کۆد کراون
|
||||
Remove invalid links and bookmarks لابردنی بەستەر و دڵخوازە نادروستەکان
|
||||
Remove unreferenced named destinations لابردنی مەبەستە ناونراوەکانی ئاماژەپێنەکراو
|
||||
Compress document structure (object streams) پەستاندنی پێکهاتەی بەڵگەنامە (لێشاوەکانی تەن)
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -692,6 +692,7 @@ key Space Mezerník
|
||||
Discard private data of other applications Zahodit soukromá data jiných aplikací
|
||||
Discard hidden text Zahodit skrytý text
|
||||
Use Flate to encode streams that are not encoded Kódovat nekódované proudy metodou Flate
|
||||
In streams that use LZW encoding, use Flate instead Proudy kódované LZW kódovat metodou Flate
|
||||
Remove invalid links and bookmarks Odebrat neplatné odkazy a záložky
|
||||
Remove unreferenced named destinations Odebrat nepoužívané pojmenované cíle
|
||||
Compress document structure (object streams) Komprimovat strukturu dokumentu (proudy objektů)
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -584,6 +584,7 @@
|
||||
Discard private data of other applications Private Daten anderer Anwendungen verwerfen
|
||||
Discard hidden text Ausgeblendeten Text verwerfen
|
||||
Use Flate to encode streams that are not encoded Nicht codierte Streams mit Flate codieren
|
||||
In streams that use LZW encoding, use Flate instead In LZW-codierten Streams stattdessen Flate verwenden
|
||||
Remove invalid links and bookmarks Ungültige Links und Lesezeichen entfernen
|
||||
Remove unreferenced named destinations Nicht referenzierte benannte Ziele entfernen
|
||||
Compress document structure (object streams) Dokumentstruktur komprimieren (Objekt-Streams)
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 51.
|
@@ -1923,6 +1923,7 @@
|
||||
Discard private data of other applications Αφαίρεση ιδιωτικών δεδομένων άλλων εφαρμογών
|
||||
Discard hidden text Αφαίρεση κρυφού κειμένου
|
||||
Use Flate to encode streams that are not encoded Χρήση συμπίεσης Flate για ροές που δεν είναι συμπιεσμένες
|
||||
In streams that use LZW encoding, use Flate instead Χρήση Flate αντί για LZW στις ροές με κωδικοποίηση LZW
|
||||
Remove invalid links and bookmarks Αφαίρεση μη έγκυρων συνδέσμων και σελιδοδεικτών
|
||||
Remove unreferenced named destinations Αφαίρεση ονομασμένων προορισμών χωρίς αναφορές
|
||||
Compress document structure (object streams) Συμπίεση δομής εγγράφου (ροές αντικειμένων)
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 482 and column 9.
|
@@ -607,6 +607,7 @@
|
||||
Discard private data of other applications Descartar datos privados de otras aplicaciones
|
||||
Discard hidden text Descartar texto oculto
|
||||
Use Flate to encode streams that are not encoded Usar Flate para codificar las secuencias sin codificar
|
||||
In streams that use LZW encoding, use Flate instead En las secuencias con codificación LZW, usar Flate en su lugar
|
||||
Remove invalid links and bookmarks Quitar enlaces y marcadores no válidos
|
||||
Remove unreferenced named destinations Quitar destinos con nombre sin referencias
|
||||
Compress document structure (object streams) Comprimir la estructura del documento (secuencias de objetos)
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 60.
|
@@ -574,6 +574,7 @@
|
||||
Discard private data of other applications Hylkää muiden sovellusten yksityiset tiedot
|
||||
Discard hidden text Hylkää piilotettu teksti
|
||||
Use Flate to encode streams that are not encoded Koodaa koodaamattomat virrat Flate-menetelmällä
|
||||
In streams that use LZW encoding, use Flate instead Koodaa LZW-koodatut virrat Flate-menetelmällä
|
||||
Remove invalid links and bookmarks Poista virheelliset linkit ja kirjanmerkit
|
||||
Remove unreferenced named destinations Poista viittaamattomat nimetyt kohteet
|
||||
Compress document structure (object streams) Pakkaa asiakirjan rakenne (objektivirrat)
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 5 and column 47.
|
@@ -584,6 +584,7 @@
|
||||
Discard private data of other applications Ignorer les données privées d'autres applications
|
||||
Discard hidden text Ignorer le texte masqué
|
||||
Use Flate to encode streams that are not encoded Utiliser Flate pour coder les flux qui ne sont pas codés
|
||||
In streams that use LZW encoding, use Flate instead Dans les flux qui utilisent le codage LZW, utiliser Flate à la place
|
||||
Remove invalid links and bookmarks Retirer les liens et signets non valides
|
||||
Remove unreferenced named destinations Retirer les destinations nommées non référencées
|
||||
Compress document structure (object streams) Compresser la structure du document (flux d'objets)
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 39.
|
@@ -558,6 +558,7 @@
|
||||
Discard private data of other applications अन्य ऐप्लिकेशन का निजी डेटा हटाएँ
|
||||
Discard hidden text छिपा हुआ टेक्स्ट हटाएँ
|
||||
Use Flate to encode streams that are not encoded जो स्ट्रीम एन्कोडेड नहीं हैं उन्हें Flate से एन्कोड करें
|
||||
In streams that use LZW encoding, use Flate instead LZW एन्कोडिंग वाली स्ट्रीम में इसके बजाय Flate का उपयोग करें
|
||||
Remove invalid links and bookmarks अमान्य लिंक और बुकमार्क हटाएँ
|
||||
Remove unreferenced named destinations बिना संदर्भ वाले नामित गंतव्य हटाएँ
|
||||
Compress document structure (object streams) दस्तावेज़ संरचना संपीड़ित करें (ऑब्जेक्ट स्ट्रीम)
|
||||
|
||||
|
@@ -582,6 +582,7 @@
|
||||
Discard private data of other applications Más alkalmazások privát adatainak elvetése
|
||||
Discard hidden text Rejtett szöveg elvetése
|
||||
Use Flate to encode streams that are not encoded A kódolatlan adatfolyamok Flate kódolása
|
||||
In streams that use LZW encoding, use Flate instead Az LZW kódolású adatfolyamok Flate kódolása
|
||||
Remove invalid links and bookmarks Érvénytelen hivatkozások és könyvjelzők eltávolítása
|
||||
Remove unreferenced named destinations Hivatkozás nélküli elnevezett célok eltávolítása
|
||||
Compress document structure (object streams) Dokumentumszerkezet tömörítése (objektumfolyamok)
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 7 and column 44.
|
@@ -1219,6 +1219,7 @@
|
||||
Update Perbarui
|
||||
Use 72 DPI (one point per pixel) Gunakan 72 DPI (satu titik per piksel)
|
||||
Use Flate to encode streams that are not encoded Gunakan Flate untuk mengodekan stream yang belum dikodekan
|
||||
In streams that use LZW encoding, use Flate instead Pada stream yang memakai pengodean LZW, gunakan Flate sebagai gantinya
|
||||
Use a Digital ID from a file Gunakan ID Digital dari file
|
||||
Use custom DPI Gunakan DPI kustom
|
||||
Use each image's embedded DPI; use 72 DPI when it is absent. Gunakan DPI tertanam pada setiap gambar; gunakan 72 DPI jika tidak ada.
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 483 and column 12.
|
@@ -608,6 +608,7 @@
|
||||
Discard private data of other applications Elimina dati privati di altre applicazioni
|
||||
Discard hidden text Elimina testo nascosto
|
||||
Use Flate to encode streams that are not encoded Usa Flate per codificare i flussi non codificati
|
||||
In streams that use LZW encoding, use Flate instead Nei flussi con codifica LZW, usa Flate
|
||||
Remove invalid links and bookmarks Rimuovi collegamenti e segnalibri non validi
|
||||
Remove unreferenced named destinations Rimuovi destinazioni con nome non referenziate
|
||||
Compress document structure (object streams) Comprimi struttura del documento (flussi di oggetti)
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -611,6 +611,7 @@
|
||||
Discard private data of other applications 他のアプリケーションのプライベートデータを破棄
|
||||
Discard hidden text 非表示テキストを破棄
|
||||
Use Flate to encode streams that are not encoded 未エンコードのストリームを Flate でエンコード
|
||||
In streams that use LZW encoding, use Flate instead LZW エンコードのストリームを Flate でエンコード
|
||||
Remove invalid links and bookmarks 無効なリンクとしおりを削除
|
||||
Remove unreferenced named destinations 参照されていない名前付き移動先を削除
|
||||
Compress document structure (object streams) 文書構造を圧縮(オブジェクトストリーム)
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -571,6 +571,7 @@
|
||||
Discard private data of other applications Басқа қосымшалардың жеке деректерін алып тастау
|
||||
Discard hidden text Жасырын мәтінді алып тастау
|
||||
Use Flate to encode streams that are not encoded Кодталмаған ағындарды Flate көмегімен кодтау
|
||||
In streams that use LZW encoding, use Flate instead LZW кодталған ағындарда оның орнына Flate қолдану
|
||||
Remove invalid links and bookmarks Жарамсыз сілтемелерді және бетбелгілерді алып тастау
|
||||
Remove unreferenced named destinations Сілтеме берілмеген атаулы межелерді алып тастау
|
||||
Compress document structure (object streams) Құжат құрылымын сығу (нысан ағындары)
|
||||
|
||||
|
@@ -558,6 +558,7 @@
|
||||
Discard private data of other applications 다른 응용 프로그램의 개인 데이터 삭제
|
||||
Discard hidden text 숨겨진 텍스트 삭제
|
||||
Use Flate to encode streams that are not encoded 인코딩되지 않은 스트림을 Flate로 인코딩
|
||||
In streams that use LZW encoding, use Flate instead LZW 인코딩 스트림을 Flate로 인코딩
|
||||
Remove invalid links and bookmarks 잘못된 링크 및 책갈피 제거
|
||||
Remove unreferenced named destinations 참조되지 않은 이름이 지정된 대상 제거
|
||||
Compress document structure (object streams) 문서 구조 압축(개체 스트림)
|
||||
|
||||
|
@@ -579,6 +579,7 @@
|
||||
Discard private data of other applications ຕັດຂໍ້ມູນສ່ວນຕົວຂອງແອັບພລິເຄຊັນອື່ນອອກ
|
||||
Discard hidden text ຕັດຂໍ້ຄວາມທີ່ເຊື່ອງໄວ້ອອກ
|
||||
Use Flate to encode streams that are not encoded ໃຊ້ Flate ເພື່ອເຂົ້າລະຫັດສະຕຣີມທີ່ຍັງບໍ່ໄດ້ເຂົ້າລະຫັດ
|
||||
In streams that use LZW encoding, use Flate instead ໃຊ້ Flate ແທນ LZW ໃນສະຕຣີມທີ່ເຂົ້າລະຫັດດ້ວຍ LZW
|
||||
Remove invalid links and bookmarks ລຶບລິ້ງຄ໌ ແລະ ບຸກມາກທີ່ໃຊ້ການບໍ່ໄດ້
|
||||
Remove unreferenced named destinations ລຶບຈຸດໝາຍປາຍທາງທີ່ມີຊື່ເຊິ່ງບໍ່ມີການອ້າງອີງ
|
||||
Compress document structure (object streams) ບີບອັດໂຄງສ້າງເອກະສານ (ສະຕຣີມວັດຖຸ)
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 32.
|
@@ -1278,6 +1278,7 @@
|
||||
Update Atnaujinti
|
||||
Use 72 DPI (one point per pixel) Naudoti 72 DPI (vienas taškas vienam pikseliui)
|
||||
Use Flate to encode streams that are not encoded Nekoduotiems srautams koduoti naudoti „Flate“
|
||||
In streams that use LZW encoding, use Flate instead LZW koduotiems srautams naudoti „Flate“
|
||||
Use a Digital ID from a file Naudoti faile esantį skaitmeninį ID
|
||||
Use custom DPI Naudoti pasirinktinį DPI
|
||||
Use each image's embedded DPI; use 72 DPI when it is absent. Naudoti paveikslėlyje įrašytą DPI; jei jo nėra, naudoti 72 DPI.
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 406 and column 9.
|
@@ -1228,6 +1228,7 @@
|
||||
Update Oppdater
|
||||
Use 72 DPI (one point per pixel) Bruk 72 DPI (ett punkt per piksel)
|
||||
Use Flate to encode streams that are not encoded Bruk Flate til å kode strømmer som ikke er kodet
|
||||
In streams that use LZW encoding, use Flate instead Bruk Flate i stedet for LZW i strømmer som er LZW-kodet
|
||||
Use a Digital ID from a file Bruk en digital ID fra en fil
|
||||
Use custom DPI Bruk egendefinert DPI
|
||||
Use each image's embedded DPI; use 72 DPI when it is absent. Bruk hvert bildes innebygde DPI; bruk 72 DPI når den mangler.
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 8 and column 16.
|
@@ -606,6 +606,7 @@
|
||||
Discard private data of other applications Usuń prywatne dane innych aplikacji
|
||||
Discard hidden text Usuń ukryty tekst
|
||||
Use Flate to encode streams that are not encoded Zakoduj nieskompresowane strumienie metodą Flate
|
||||
In streams that use LZW encoding, use Flate instead Zakoduj strumienie LZW metodą Flate
|
||||
Remove invalid links and bookmarks Usuń nieprawidłowe łącza i zakładki
|
||||
Remove unreferenced named destinations Usuń nieużywane nazwane miejsca docelowe
|
||||
Compress document structure (object streams) Kompresuj strukturę dokumentu (strumienie obiektów)
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -238,6 +238,7 @@
|
||||
Discard private data of other applications Descartar dados privados de outros aplicativos
|
||||
Discard hidden text Descartar texto oculto
|
||||
Use Flate to encode streams that are not encoded Usar Flate para codificar fluxos não codificados
|
||||
In streams that use LZW encoding, use Flate instead Em fluxos que usam codificação LZW, usar Flate
|
||||
Remove invalid links and bookmarks Remover links e marcadores inválidos
|
||||
Remove unreferenced named destinations Remover destinos nomeados não referenciados
|
||||
Compress document structure (object streams) Compactar estrutura do documento (fluxos de objetos)
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 9 and column 16.
|
@@ -1228,6 +1228,7 @@
|
||||
Update Actualizare
|
||||
Use 72 DPI (one point per pixel) Folosire 72 DPI (un punct pe pixel)
|
||||
Use Flate to encode streams that are not encoded Folosire Flate pentru codarea fluxurilor necodate
|
||||
In streams that use LZW encoding, use Flate instead Folosire Flate în locul LZW pentru fluxurile codate LZW
|
||||
Use a Digital ID from a file Folosire ID digital dintr-un fișier
|
||||
Use custom DPI Folosire DPI personalizat
|
||||
Use each image's embedded DPI; use 72 DPI when it is absent. Folosește DPI-ul încorporat al fiecărei imagini; 72 DPI când lipsește.
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 3 and column 110.
|
@@ -604,6 +604,7 @@
|
||||
Discard private data of other applications Удалить личные данные других приложений
|
||||
Discard hidden text Удалить скрытый текст
|
||||
Use Flate to encode streams that are not encoded Использовать Flate для кодирования некодированных потоков
|
||||
In streams that use LZW encoding, use Flate instead Использовать Flate вместо LZW в потоках с кодированием LZW
|
||||
Remove invalid links and bookmarks Удалить недействительные ссылки и закладки
|
||||
Remove unreferenced named destinations Удалить неиспользуемые именованные места назначения
|
||||
Compress document structure (object streams) Сжать структуру документа (потоки объектов)
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 9 and column 16.
|
@@ -1964,6 +1964,7 @@
|
||||
Update watermark… Posodobi vodni žig…
|
||||
Use 72 DPI (one point per pixel) Uporabi 72 DPI (ena točka na slikovno piko)
|
||||
Use Flate to encode streams that are not encoded Za kodiranje nekodiranih tokov uporabi Flate
|
||||
In streams that use LZW encoding, use Flate instead V tokovih s kodiranjem LZW uporabi Flate
|
||||
Use JavaScript Uporabi JavaScript
|
||||
Use a Digital ID from a file Uporabi digitalno identiteto iz datoteke
|
||||
Use a certificate Uporabi potrdilo
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 9 and column 16.
|
@@ -1910,6 +1910,7 @@
|
||||
Update watermark… Përditëso filigranin…
|
||||
Use 72 DPI (one point per pixel) Përdor 72 DPI (një pikë për piksel)
|
||||
Use Flate to encode streams that are not encoded Përdor Flate për të koduar rrjedhat që nuk janë të koduara
|
||||
In streams that use LZW encoding, use Flate instead Në rrjedhat që përdorin kodimin LZW, përdor Flate në vend të tij
|
||||
Use JavaScript Përdor JavaScript
|
||||
Use a Digital ID from a file Përdor një ID dixhitale nga një skedar
|
||||
Use a certificate Përdor një certifikatë
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 9 and column 16.
|
@@ -588,6 +588,7 @@ comment menu Edit సవరించు
|
||||
Discard private data of other applications ఇతర అప్లికేషన్ల ప్రైవేట్ డేటాను విస్మరించు
|
||||
Discard hidden text దాచిన టెక్స్ట్ను విస్మరించు
|
||||
Use Flate to encode streams that are not encoded ఎన్కోడ్ చేయని స్ట్రీమ్లను Flateతో ఎన్కోడ్ చేయి
|
||||
In streams that use LZW encoding, use Flate instead LZW ఎన్కోడింగ్ ఉన్న స్ట్రీమ్లలో బదులుగా Flate ఉపయోగించు
|
||||
Remove invalid links and bookmarks చెల్లని లింక్లు, బుక్మార్క్లను తీసివేయి
|
||||
Remove unreferenced named destinations ఎక్కడా సూచించని పేరున్న గమ్యాలను తీసివేయి
|
||||
Compress document structure (object streams) పత్రం నిర్మాణాన్ని కుదించు (ఆబ్జెక్ట్ స్ట్రీమ్లు)
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 13 and column 16.
|
@@ -568,6 +568,7 @@
|
||||
Discard private data of other applications ละทิ้งข้อมูลเฉพาะของแอปพลิเคชันอื่น
|
||||
Discard hidden text ละทิ้งข้อความที่ซ่อนอยู่
|
||||
Use Flate to encode streams that are not encoded ใช้ Flate เข้ารหัสสตรีมที่ยังไม่ได้เข้ารหัส
|
||||
In streams that use LZW encoding, use Flate instead ใช้ Flate แทนในสตรีมที่เข้ารหัสแบบ LZW
|
||||
Remove invalid links and bookmarks เอาลิงก์และบุ๊กมาร์กที่ใช้ไม่ได้ออก
|
||||
Remove unreferenced named destinations เอาปลายทางที่มีชื่อซึ่งไม่ถูกอ้างถึงออก
|
||||
Compress document structure (object streams) บีบอัดโครงสร้างเอกสาร (object streams)
|
||||
|
||||
|
@@ -569,6 +569,7 @@
|
||||
Discard private data of other applications Diğer uygulamaların özel verilerini at
|
||||
Discard hidden text Gizli metni at
|
||||
Use Flate to encode streams that are not encoded Kodlanmamış akışları kodlamak için Flate kullan
|
||||
In streams that use LZW encoding, use Flate instead LZW kodlamalı akışlarda bunun yerine Flate kullan
|
||||
Remove invalid links and bookmarks Geçersiz bağlantıları ve yer işaretlerini kaldır
|
||||
Remove unreferenced named destinations Referans verilmeyen adlandırılmış hedefleri kaldır
|
||||
Compress document structure (object streams) Belge yapısını sıkıştır (nesne akışları)
|
||||
|
||||
|
@@ -609,6 +609,7 @@
|
||||
Discard private data of other applications Видалити приватні дані інших застосунків
|
||||
Discard hidden text Видалити прихований текст
|
||||
Use Flate to encode streams that are not encoded Кодувати незакодовані потоки за допомогою Flate
|
||||
In streams that use LZW encoding, use Flate instead Використовувати Flate замість LZW у потоках із кодуванням LZW
|
||||
Remove invalid links and bookmarks Видалити недійсні посилання й закладки
|
||||
Remove unreferenced named destinations Видалити іменовані призначення без посилань
|
||||
Compress document structure (object streams) Стиснути структуру документа (потоки об’єктів)
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -1268,6 +1268,7 @@ theme Appearance Giao diện
|
||||
Update Cập nhật
|
||||
Use 72 DPI (one point per pixel) Dùng 72 DPI (một điểm cho mỗi điểm ảnh)
|
||||
Use Flate to encode streams that are not encoded Dùng Flate để mã hóa các luồng chưa được mã hóa
|
||||
In streams that use LZW encoding, use Flate instead Dùng Flate thay cho LZW trong các luồng mã hóa LZW
|
||||
Use a Digital ID from a file Dùng Digital ID từ một tệp
|
||||
Use custom DPI Dùng DPI tùy chỉnh
|
||||
Use each image's embedded DPI; use 72 DPI when it is absent. Dùng DPI nhúng của từng hình ảnh; dùng 72 DPI khi không có.
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 493 and column 12.
|
@@ -288,6 +288,7 @@
|
||||
Discard private data of other applications 丢弃其他应用程序的私有数据
|
||||
Discard hidden text 丢弃隐藏文本
|
||||
Use Flate to encode streams that are not encoded 使用 Flate 编码未编码的流
|
||||
In streams that use LZW encoding, use Flate instead 在使用 LZW 编码的流中改用 Flate
|
||||
Remove invalid links and bookmarks 删除无效链接和书签
|
||||
Remove unreferenced named destinations 删除未引用的命名目标
|
||||
Compress document structure (object streams) 压缩文档结构(对象流)
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 9 and column 16.
|
@@ -244,6 +244,7 @@
|
||||
Discard private data of other applications 捨棄其他應用程式的私有資料
|
||||
Discard hidden text 捨棄隱藏文字
|
||||
Use Flate to encode streams that are not encoded 使用 Flate 編碼未編碼的流
|
||||
In streams that use LZW encoding, use Flate instead 在使用 LZW 編碼的流中改用 Flate
|
||||
Remove invalid links and bookmarks 刪除無效連結和書籤
|
||||
Remove unreferenced named destinations 刪除未引用的命名目標
|
||||
Compress document structure (object streams) 壓縮文件結構(物件流)
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 9 and column 16.
|
@@ -230,6 +230,7 @@ pub(crate) fn body(ui: &mut egui::Ui, d: &mut OptimizeDraft, t: &Tokens) -> (boo
|
||||
}
|
||||
OptimizeTab::CleanUp => {
|
||||
ui.add(widgets::checkbox(&mut s.flate_unencoded, tl!("Use Flate to encode streams that are not encoded")));
|
||||
ui.add(widgets::checkbox(&mut s.lzw_to_flate, tl!("In streams that use LZW encoding, use Flate instead")));
|
||||
ui.add(widgets::checkbox(&mut s.remove_invalid_links, tl!("Remove invalid links and bookmarks")));
|
||||
ui.add(widgets::checkbox(&mut s.remove_unreferenced_dests, tl!("Remove unreferenced named destinations")));
|
||||
let mut object_streams = s.effective_object_streams();
|
||||
|
||||
@@ -7589,8 +7589,8 @@ milestone = "M11"
|
||||
status = "partial"
|
||||
commands = ["optimize.advanced"]
|
||||
tools = ["doc_optimize"]
|
||||
evidence = ["crates/optimize/tests/optimize.rs::discards_and_clean_up"]
|
||||
notes = "unencoded streams; LZW not yet re-encoded"
|
||||
evidence = ["crates/optimize/tests/optimize.rs::discards_and_clean_up", "crates/optimize/tests/optimize.rs::lzw_streams_become_flate_and_decode_to_the_same_bytes", "crates/optimize/tests/optimize.rs::a_page_drawn_from_lzw_streams_renders_the_same_after_conversion"]
|
||||
notes = "Two Clean Up options, as in Acrobat: unencoded streams are Flate-compressed, and LZW streams (content and image XObjects; EarlyChange 0 and 1, with or without a predictor, LZW anywhere in a filter chain) are decoded and stored with Flate instead, keeping the predictor and the filters after the LZW one; LZW data that doesn't decode cleanly is left as it is. Not yet: LZW inline images inside content streams."
|
||||
|
||||
[[feature]]
|
||||
id = "optimize.remove-invalid-links"
|
||||
|
||||
Reference in New Issue
Block a user