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:
aiongg
2026-10-24 06:26:33 +00:00
43 changed files with 442 additions and 6 deletions
Generated
+1
View File
@@ -4235,6 +4235,7 @@ dependencies = [
"pdfcraft-annot",
"pdfcraft-content",
"pdfcraft-cos",
"pdfcraft-filters",
"pdfcraft-model",
"pdfcraft-render",
"thiserror 2.0.21",
+2
View File
@@ -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,
+2 -1
View File
@@ -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" } },
+50
View File
@@ -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> {
+1
View File
@@ -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 }
+5 -1
View File
@@ -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
View File
@@ -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);
}
}
+212
View File
@@ -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);
}
+1
View File
@@ -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.
+1
View File
@@ -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.
+1
View File
@@ -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.
+1
View File
@@ -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.
+1
View File
@@ -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.
+1
View File
@@ -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.
+1
View File
@@ -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.
+1
View File
@@ -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.
+1
View File
@@ -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.
+1
View File
@@ -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.
+1
View File
@@ -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) दस्तावेज़ संरचना संपीड़ित करें (ऑब्जेक्ट स्ट्रीम)
1 # Hindi (hi) interface catalog. Columns, separated by a TAB:
558
559
560
561
562
563
564
+1
View File
@@ -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.
+1
View File
@@ -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.
+1
View File
@@ -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.
+1
View File
@@ -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.
+1
View File
@@ -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) Құжат құрылымын сығу (нысан ағындары)
1 Menu Мәзір
571 Discard private data of other applications Басқа қосымшалардың жеке деректерін алып тастау
572 Discard hidden text Жасырын мәтінді алып тастау
573 Use Flate to encode streams that are not encoded Кодталмаған ағындарды Flate көмегімен кодтау
574 In streams that use LZW encoding, use Flate instead LZW кодталған ағындарда оның орнына Flate қолдану
575 Remove invalid links and bookmarks Жарамсыз сілтемелерді және бетбелгілерді алып тастау
576 Remove unreferenced named destinations Сілтеме берілмеген атаулы межелерді алып тастау
577 Compress document structure (object streams) Құжат құрылымын сығу (нысан ағындары)
+1
View File
@@ -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) 문서 구조 압축(개체 스트림)
1 # Korean (ko) interface catalog. Columns, separated by a TAB:
558
559
560
561
562
563
564
+1
View File
@@ -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.
+1
View File
@@ -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.
+1
View File
@@ -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.
+1
View File
@@ -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.
+1
View File
@@ -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.
+1
View File
@@ -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.
+1
View File
@@ -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.
+1
View File
@@ -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.
+1
View File
@@ -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.
+1
View File
@@ -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.
+1
View File
@@ -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)
1 # Thai (th) catalog. Columns, separated by a TAB: context <TAB> source <TAB> translation
568
569
570
571
572
573
574
+1
View File
@@ -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ı)
1 # Turkish (tr) catalog. Format: see the header of ja.tsv
569
570
571
572
573
574
575
+1
View File
@@ -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.
+1
View File
@@ -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.
+1
View File
@@ -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.
+1
View File
@@ -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.
+1
View File
@@ -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();
+2 -2
View File
@@ -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"