djvu-rs 0.28.0 → 0.30.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # djvu-rs
2
2
 
3
3
  [![Crates.io](https://badgen.net/crates/v/djvu-rs)](https://crates.io/crates/djvu-rs)
4
+ [![PyPI](https://img.shields.io/pypi/v/djvu-rs)](https://pypi.org/project/djvu-rs/)
5
+ [![npm](https://img.shields.io/npm/v/djvu-rs)](https://www.npmjs.com/package/djvu-rs)
4
6
  [![docs.rs](https://docs.rs/djvu-rs/badge.svg)](https://docs.rs/djvu-rs)
5
7
  [![CI](https://github.com/matyushkin/djvu-rs/actions/workflows/ci.yml/badge.svg)](https://github.com/matyushkin/djvu-rs/actions/workflows/ci.yml)
6
8
  [![Benchmarks](https://img.shields.io/badge/benchmarks-dashboard-blue)](https://matyushkin.github.io/djvu-rs/dev/bench/)
@@ -8,16 +10,19 @@
8
10
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
9
11
 
10
12
  Read, render, convert, and create DjVu files. Pure-Rust library with a CLI,
11
- WebAssembly, and Python bindings — MIT licensed, no GPL dependencies, written
12
- from the public DjVu v3 specification.
13
+ WebAssembly, and Python bindings — on [crates.io](https://crates.io/crates/djvu-rs),
14
+ [PyPI](https://pypi.org/project/djvu-rs/), and [npm](https://www.npmjs.com/package/djvu-rs)
15
+ as `djvu-rs`. MIT licensed, no GPL dependencies, written from the public DjVu v3
16
+ specification.
13
17
 
14
18
  | Your task | How |
15
19
  |-----------|-----|
16
20
  | Convert DjVu → PDF, EPUB, TIFF, PNG, CBZ | [`djvu render`](#cli) or [`djvu_to_pdf`](#pdf-export) / [`djvu_to_epub`](#epub-export) / [`djvu_to_tiff`](#tiff-export) |
17
21
  | Extract text (plain, hOCR, ALTO XML) | [`djvu text`](#cli) or [`page.text()`](#text-extraction), [`to_hocr` / `to_alto`](#hocr-and-alto-xml-export) |
18
22
  | Render pages to RGBA pixels | [`render_pixmap`](#quick-start) — sync, [async](#async-render), or [parallel](#feature-flags) |
23
+ | Build a zoomable viewer (tiles) | [`djvu_tile`](#tile-rendering) — cached, prefetchable, cancellable tile rendering |
19
24
  | Show DjVu in the browser | [WebAssembly bindings](#webassembly), incl. lazy HTTP-Range loading |
20
- | Read DjVu from Python | [PyO3 bindings, built from source](#python) |
25
+ | Read DjVu from Python | `pip install djvu-rs` — [PyO3 bindings](#python) |
21
26
  | Create DjVu from images (PNG/JPEG/TIFF) | [`djvu encode`](#cli) or [`PageEncoder`](#encoding--low-level-api) |
22
27
  | Add an OCR text layer to a scan | [`djvu ocr`](#ocr-recognition-backends) (Tesseract) |
23
28
  | Merge, split, edit documents | [`djvu merge` / `djvu split`](#cli), `DocumentEditor`, `DjVuDocumentMut` |
@@ -134,6 +139,8 @@ djvu split book.djvu --pages 10-25 --output chapter.djvu
134
139
  djvu optimize book.djvu --output optimized.djvu --preset lossless-cleanup --dry-run
135
140
  djvu optimize book.djvu --output optimized.djvu --preset lossless-cleanup
136
141
  djvu optimize book.djvu --output optimized.djvu --preset archival --target-size 26214400
142
+ # (--max-ssim-loss is reserved for the planned archival re-encode; the current
143
+ # lossless cleanup is pixel-exact by construction and reports this)
137
144
  djvu optimize book.djvu --output optimized.djvu --max-ssim-loss 0.001
138
145
 
139
146
  # Encode an image (PNG, JPEG, or TIFF) into a single-page DjVu (bilevel JB2, lossless)
@@ -155,6 +162,12 @@ djvu encode scan.png --quality quality --binarization sauvola --bg-inpaint --out
155
162
  # Cap the IW44 background at a bits-per-pixel budget (smaller file, lower quality)
156
163
  djvu encode scan.jpg --quality quality --bg-bpp 0.8 --output scan.djvu
157
164
 
165
+ # Composite transparent PNG/TIFF pixels onto a solid colour (hex or white/black)
166
+ djvu encode logo.png --background white --output logo.djvu
167
+
168
+ # Refuse ICC-profiled input instead of silently dropping the profile
169
+ djvu encode scan.png --icc reject --output scan.djvu
170
+
158
171
  # Encode a directory of images into a bundled DJVM with shared Djbz
159
172
  djvu encode pages/ --output book.djvu --shared-dict-pages 2
160
173
 
@@ -422,6 +435,45 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
422
435
  }
423
436
  ```
424
437
 
438
+ ### Tile rendering
439
+
440
+ For viewer engines: [`djvu_tile`](https://docs.rs/djvu-rs/latest/djvu_rs/djvu_tile/)
441
+ renders a page as a display-space tile grid over the region renderer. Tile
442
+ pixels are byte-identical to the same rectangle of a full-page render, in any
443
+ request order.
444
+
445
+ ```rust,no_run
446
+ use djvu_rs::{DjVuDocument, djvu_render::RenderOptions};
447
+ use djvu_rs::djvu_tile::{TileLayout, render_tile_cached};
448
+
449
+ fn main() -> Result<(), Box<dyn std::error::Error>> {
450
+ let data = std::fs::read("book.djvu")?;
451
+ let doc = DjVuDocument::parse(&data)?;
452
+ let page = doc.page(0)?;
453
+
454
+ let opts = RenderOptions { width: 2400, height: 3200, ..Default::default() };
455
+ let layout = TileLayout::new(page, &opts, 256)?;
456
+
457
+ for row in 0..layout.rows() {
458
+ for col in 0..layout.cols() {
459
+ let tile = render_tile_cached(page, &opts, 256, col, row)?;
460
+ // tile.data — RGBA bytes of exactly this tile rectangle
461
+ let _ = tile;
462
+ }
463
+ }
464
+ Ok(())
465
+ }
466
+ ```
467
+
468
+ `render_tile_cached` memoizes composited tiles per page; the cache is
469
+ tile-granular and controllable (`tile_cache_usage`, `set_tile_cache_budget`,
470
+ `clear_tile_cache`, `invalidate_tile_region`). `render_tile_with` +
471
+ `TileRenderControls` / `TileCancelToken` add progressive quality steps and
472
+ cooperative cancellation, and with the `parallel` feature `prefetch_tiles` /
473
+ `prefetch_tiles_cancellable` warm the cache in the background with a bounded
474
+ worker pool. The full contract lives in
475
+ [`docs/tile-rendering.md`](docs/tile-rendering.md).
476
+
425
477
  ## Encoding & low-level API
426
478
 
427
479
  ### JB2 bilevel image encoder
@@ -541,6 +593,16 @@ whole is not transactional). Opening an indirect index directly with
541
593
  `DjVuDocumentMut::from_bytes` and calling `page_mut` remains unsupported; see
542
594
  [`docs/indirect-djvm-mutation.md`](docs/indirect-djvm-mutation.md).
543
595
 
596
+ The reverse direction is covered too: `djvm::to_indirect` splits a bundled
597
+ `FORM:DJVM` into an indirect index plus standalone component files, keeping
598
+ component ids, names, titles, and the document `NAVM` stable. Related
599
+ bundled-document operations in the same module: `djvm::remove_pages` deletes
600
+ pages with an explicit `UnreachablePolicy` (preserve or garbage-collect shared
601
+ components that lose their last including page),
602
+ `djvm::dedup_shared_components` merges byte-identical shared components, and
603
+ `djvm::DjvmStreamWriter` writes a bundle to any `io::Write` sink with memory
604
+ bounded to the spooled component being appended.
605
+
544
606
  ### Typed document editing
545
607
 
546
608
  `DocumentEditor` provides a versioned, typed operation list with a semantic
@@ -666,8 +728,9 @@ Honest boundaries, so you can decide fast:
666
728
 
667
729
  - **Library + CLI, not a viewer.** There is no GUI; the WASM demo is the
668
730
  closest thing to one.
669
- - **Python bindings are source-only for now.** The `djvu-py` package is not
670
- published to PyPI yet install it from the repository checkout.
731
+ - **Python bindings cover the reading surface only.** Open, render, and text
732
+ extraction ship in the PyPI wheels; encode, mutation, and PDF/EPUB/TIFF
733
+ export stay on the Rust crate / CLI for now.
671
734
  - **Indirect DJVM mutation is indirect-only via two paths.**
672
735
  `DjVuDocumentMut::from_bytes` + `page_mut` on an indirect index errors;
673
736
  use `from_indirect_resolved` (rebundles) or `IndirectRewritePlan` (rewrites
@@ -676,7 +739,9 @@ Honest boundaries, so you can decide fast:
676
739
  and single-page `FORM:DJVU` only; indirect returns a clean `Unsupported`
677
740
  error.
678
741
  - **`create_indirect` does not emit shared `DJVI` dictionary components** —
679
- build a bundled document with `djvu merge` when pages share a dictionary.
742
+ build a bundled document with `djvu merge` when pages share a dictionary, or
743
+ convert an existing bundled document with `djvm::to_indirect`, which
744
+ preserves shared components.
680
745
  - **Encoder size parity is corpus- and profile-dependent.** Run the
681
746
  reproducible [`encoder parity scorecard`](docs/encoder-parity.md) to compare
682
747
  the same raster through DjVuLibre 3.5.29's `c44`/`cjb2` and the archival-safe
@@ -756,7 +821,10 @@ combinations and targets), and is enforced in CI. In short:
756
821
  [`tests/panic_free_corpus.rs`](tests/panic_free_corpus.rs), proptests, and
757
822
  libFuzzer/OSS-Fuzz targets.
758
823
  - **Resource limits** — decode/render inherit documented, bounded memory/work
759
- ceilings; exceeding one returns a typed error naming the codec and axis. See
824
+ ceilings; exceeding one returns a typed error naming the codec and axis. The
825
+ ceilings are caller-configurable: pass `ResourceLimits` via `ParseOptions` to
826
+ `DjVuDocument::parse_with_options` (pages inherit them at render time), or
827
+ use `render_pixmap_with_limits` / `render_into_with_limits` directly. See
760
828
  [`SECURITY.md`](SECURITY.md#decode-time-resource-ceilings).
761
829
 
762
830
  ## Performance
@@ -797,7 +865,9 @@ MIT. See [LICENSE](LICENSE).
797
865
 
798
866
  Written from the public DjVu v3 specification:
799
867
  - https://www.sndjvu.org/spec.html
800
- - https://djvu.sourceforge.net/spec/DjVu3Spec.djvu (the spec is itself a DjVu file)
868
+ - https://web.archive.org/web/20251005122807/http://www.djvu.org/docs/DjVu3Spec.djvu
869
+ (the spec is itself a DjVu file; archived copy — djvu.org and
870
+ djvu.sourceforge.net no longer serve the original)
801
871
 
802
872
  No code derived from GPL-licensed DjVuLibre or any other GPL source.
803
873
  All algorithms are independent implementations from the spec.
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "djvu-rs",
3
3
  "type": "module",
4
4
  "description": "Read, render, convert, and create DjVu files. Pure-Rust DjVu decoder/encoder with CLI, WebAssembly, and Python bindings. DjVu to PDF, EPUB, TIFF, PNG, and text. MIT licensed, no GPL dependencies.",
5
- "version": "0.28.0",
5
+ "version": "0.30.1",
6
6
  "license": "MIT",
7
7
  "repository": {
8
8
  "type": "git",
package/scalar/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # djvu-rs
2
2
 
3
3
  [![Crates.io](https://badgen.net/crates/v/djvu-rs)](https://crates.io/crates/djvu-rs)
4
+ [![PyPI](https://img.shields.io/pypi/v/djvu-rs)](https://pypi.org/project/djvu-rs/)
5
+ [![npm](https://img.shields.io/npm/v/djvu-rs)](https://www.npmjs.com/package/djvu-rs)
4
6
  [![docs.rs](https://docs.rs/djvu-rs/badge.svg)](https://docs.rs/djvu-rs)
5
7
  [![CI](https://github.com/matyushkin/djvu-rs/actions/workflows/ci.yml/badge.svg)](https://github.com/matyushkin/djvu-rs/actions/workflows/ci.yml)
6
8
  [![Benchmarks](https://img.shields.io/badge/benchmarks-dashboard-blue)](https://matyushkin.github.io/djvu-rs/dev/bench/)
@@ -8,16 +10,19 @@
8
10
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
9
11
 
10
12
  Read, render, convert, and create DjVu files. Pure-Rust library with a CLI,
11
- WebAssembly, and Python bindings — MIT licensed, no GPL dependencies, written
12
- from the public DjVu v3 specification.
13
+ WebAssembly, and Python bindings — on [crates.io](https://crates.io/crates/djvu-rs),
14
+ [PyPI](https://pypi.org/project/djvu-rs/), and [npm](https://www.npmjs.com/package/djvu-rs)
15
+ as `djvu-rs`. MIT licensed, no GPL dependencies, written from the public DjVu v3
16
+ specification.
13
17
 
14
18
  | Your task | How |
15
19
  |-----------|-----|
16
20
  | Convert DjVu → PDF, EPUB, TIFF, PNG, CBZ | [`djvu render`](#cli) or [`djvu_to_pdf`](#pdf-export) / [`djvu_to_epub`](#epub-export) / [`djvu_to_tiff`](#tiff-export) |
17
21
  | Extract text (plain, hOCR, ALTO XML) | [`djvu text`](#cli) or [`page.text()`](#text-extraction), [`to_hocr` / `to_alto`](#hocr-and-alto-xml-export) |
18
22
  | Render pages to RGBA pixels | [`render_pixmap`](#quick-start) — sync, [async](#async-render), or [parallel](#feature-flags) |
23
+ | Build a zoomable viewer (tiles) | [`djvu_tile`](#tile-rendering) — cached, prefetchable, cancellable tile rendering |
19
24
  | Show DjVu in the browser | [WebAssembly bindings](#webassembly), incl. lazy HTTP-Range loading |
20
- | Read DjVu from Python | [PyO3 bindings, built from source](#python) |
25
+ | Read DjVu from Python | `pip install djvu-rs` — [PyO3 bindings](#python) |
21
26
  | Create DjVu from images (PNG/JPEG/TIFF) | [`djvu encode`](#cli) or [`PageEncoder`](#encoding--low-level-api) |
22
27
  | Add an OCR text layer to a scan | [`djvu ocr`](#ocr-recognition-backends) (Tesseract) |
23
28
  | Merge, split, edit documents | [`djvu merge` / `djvu split`](#cli), `DocumentEditor`, `DjVuDocumentMut` |
@@ -134,6 +139,8 @@ djvu split book.djvu --pages 10-25 --output chapter.djvu
134
139
  djvu optimize book.djvu --output optimized.djvu --preset lossless-cleanup --dry-run
135
140
  djvu optimize book.djvu --output optimized.djvu --preset lossless-cleanup
136
141
  djvu optimize book.djvu --output optimized.djvu --preset archival --target-size 26214400
142
+ # (--max-ssim-loss is reserved for the planned archival re-encode; the current
143
+ # lossless cleanup is pixel-exact by construction and reports this)
137
144
  djvu optimize book.djvu --output optimized.djvu --max-ssim-loss 0.001
138
145
 
139
146
  # Encode an image (PNG, JPEG, or TIFF) into a single-page DjVu (bilevel JB2, lossless)
@@ -155,6 +162,12 @@ djvu encode scan.png --quality quality --binarization sauvola --bg-inpaint --out
155
162
  # Cap the IW44 background at a bits-per-pixel budget (smaller file, lower quality)
156
163
  djvu encode scan.jpg --quality quality --bg-bpp 0.8 --output scan.djvu
157
164
 
165
+ # Composite transparent PNG/TIFF pixels onto a solid colour (hex or white/black)
166
+ djvu encode logo.png --background white --output logo.djvu
167
+
168
+ # Refuse ICC-profiled input instead of silently dropping the profile
169
+ djvu encode scan.png --icc reject --output scan.djvu
170
+
158
171
  # Encode a directory of images into a bundled DJVM with shared Djbz
159
172
  djvu encode pages/ --output book.djvu --shared-dict-pages 2
160
173
 
@@ -422,6 +435,45 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
422
435
  }
423
436
  ```
424
437
 
438
+ ### Tile rendering
439
+
440
+ For viewer engines: [`djvu_tile`](https://docs.rs/djvu-rs/latest/djvu_rs/djvu_tile/)
441
+ renders a page as a display-space tile grid over the region renderer. Tile
442
+ pixels are byte-identical to the same rectangle of a full-page render, in any
443
+ request order.
444
+
445
+ ```rust,no_run
446
+ use djvu_rs::{DjVuDocument, djvu_render::RenderOptions};
447
+ use djvu_rs::djvu_tile::{TileLayout, render_tile_cached};
448
+
449
+ fn main() -> Result<(), Box<dyn std::error::Error>> {
450
+ let data = std::fs::read("book.djvu")?;
451
+ let doc = DjVuDocument::parse(&data)?;
452
+ let page = doc.page(0)?;
453
+
454
+ let opts = RenderOptions { width: 2400, height: 3200, ..Default::default() };
455
+ let layout = TileLayout::new(page, &opts, 256)?;
456
+
457
+ for row in 0..layout.rows() {
458
+ for col in 0..layout.cols() {
459
+ let tile = render_tile_cached(page, &opts, 256, col, row)?;
460
+ // tile.data — RGBA bytes of exactly this tile rectangle
461
+ let _ = tile;
462
+ }
463
+ }
464
+ Ok(())
465
+ }
466
+ ```
467
+
468
+ `render_tile_cached` memoizes composited tiles per page; the cache is
469
+ tile-granular and controllable (`tile_cache_usage`, `set_tile_cache_budget`,
470
+ `clear_tile_cache`, `invalidate_tile_region`). `render_tile_with` +
471
+ `TileRenderControls` / `TileCancelToken` add progressive quality steps and
472
+ cooperative cancellation, and with the `parallel` feature `prefetch_tiles` /
473
+ `prefetch_tiles_cancellable` warm the cache in the background with a bounded
474
+ worker pool. The full contract lives in
475
+ [`docs/tile-rendering.md`](docs/tile-rendering.md).
476
+
425
477
  ## Encoding & low-level API
426
478
 
427
479
  ### JB2 bilevel image encoder
@@ -541,6 +593,16 @@ whole is not transactional). Opening an indirect index directly with
541
593
  `DjVuDocumentMut::from_bytes` and calling `page_mut` remains unsupported; see
542
594
  [`docs/indirect-djvm-mutation.md`](docs/indirect-djvm-mutation.md).
543
595
 
596
+ The reverse direction is covered too: `djvm::to_indirect` splits a bundled
597
+ `FORM:DJVM` into an indirect index plus standalone component files, keeping
598
+ component ids, names, titles, and the document `NAVM` stable. Related
599
+ bundled-document operations in the same module: `djvm::remove_pages` deletes
600
+ pages with an explicit `UnreachablePolicy` (preserve or garbage-collect shared
601
+ components that lose their last including page),
602
+ `djvm::dedup_shared_components` merges byte-identical shared components, and
603
+ `djvm::DjvmStreamWriter` writes a bundle to any `io::Write` sink with memory
604
+ bounded to the spooled component being appended.
605
+
544
606
  ### Typed document editing
545
607
 
546
608
  `DocumentEditor` provides a versioned, typed operation list with a semantic
@@ -666,8 +728,9 @@ Honest boundaries, so you can decide fast:
666
728
 
667
729
  - **Library + CLI, not a viewer.** There is no GUI; the WASM demo is the
668
730
  closest thing to one.
669
- - **Python bindings are source-only for now.** The `djvu-py` package is not
670
- published to PyPI yet install it from the repository checkout.
731
+ - **Python bindings cover the reading surface only.** Open, render, and text
732
+ extraction ship in the PyPI wheels; encode, mutation, and PDF/EPUB/TIFF
733
+ export stay on the Rust crate / CLI for now.
671
734
  - **Indirect DJVM mutation is indirect-only via two paths.**
672
735
  `DjVuDocumentMut::from_bytes` + `page_mut` on an indirect index errors;
673
736
  use `from_indirect_resolved` (rebundles) or `IndirectRewritePlan` (rewrites
@@ -676,7 +739,9 @@ Honest boundaries, so you can decide fast:
676
739
  and single-page `FORM:DJVU` only; indirect returns a clean `Unsupported`
677
740
  error.
678
741
  - **`create_indirect` does not emit shared `DJVI` dictionary components** —
679
- build a bundled document with `djvu merge` when pages share a dictionary.
742
+ build a bundled document with `djvu merge` when pages share a dictionary, or
743
+ convert an existing bundled document with `djvm::to_indirect`, which
744
+ preserves shared components.
680
745
  - **Encoder size parity is corpus- and profile-dependent.** Run the
681
746
  reproducible [`encoder parity scorecard`](docs/encoder-parity.md) to compare
682
747
  the same raster through DjVuLibre 3.5.29's `c44`/`cjb2` and the archival-safe
@@ -756,7 +821,10 @@ combinations and targets), and is enforced in CI. In short:
756
821
  [`tests/panic_free_corpus.rs`](tests/panic_free_corpus.rs), proptests, and
757
822
  libFuzzer/OSS-Fuzz targets.
758
823
  - **Resource limits** — decode/render inherit documented, bounded memory/work
759
- ceilings; exceeding one returns a typed error naming the codec and axis. See
824
+ ceilings; exceeding one returns a typed error naming the codec and axis. The
825
+ ceilings are caller-configurable: pass `ResourceLimits` via `ParseOptions` to
826
+ `DjVuDocument::parse_with_options` (pages inherit them at render time), or
827
+ use `render_pixmap_with_limits` / `render_into_with_limits` directly. See
760
828
  [`SECURITY.md`](SECURITY.md#decode-time-resource-ceilings).
761
829
 
762
830
  ## Performance
@@ -797,7 +865,9 @@ MIT. See [LICENSE](LICENSE).
797
865
 
798
866
  Written from the public DjVu v3 specification:
799
867
  - https://www.sndjvu.org/spec.html
800
- - https://djvu.sourceforge.net/spec/DjVu3Spec.djvu (the spec is itself a DjVu file)
868
+ - https://web.archive.org/web/20251005122807/http://www.djvu.org/docs/DjVu3Spec.djvu
869
+ (the spec is itself a DjVu file; archived copy — djvu.org and
870
+ djvu.sourceforge.net no longer serve the original)
801
871
 
802
872
  No code derived from GPL-licensed DjVuLibre or any other GPL source.
803
873
  All algorithms are independent implementations from the spec.
Binary file
package/simd128/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # djvu-rs
2
2
 
3
3
  [![Crates.io](https://badgen.net/crates/v/djvu-rs)](https://crates.io/crates/djvu-rs)
4
+ [![PyPI](https://img.shields.io/pypi/v/djvu-rs)](https://pypi.org/project/djvu-rs/)
5
+ [![npm](https://img.shields.io/npm/v/djvu-rs)](https://www.npmjs.com/package/djvu-rs)
4
6
  [![docs.rs](https://docs.rs/djvu-rs/badge.svg)](https://docs.rs/djvu-rs)
5
7
  [![CI](https://github.com/matyushkin/djvu-rs/actions/workflows/ci.yml/badge.svg)](https://github.com/matyushkin/djvu-rs/actions/workflows/ci.yml)
6
8
  [![Benchmarks](https://img.shields.io/badge/benchmarks-dashboard-blue)](https://matyushkin.github.io/djvu-rs/dev/bench/)
@@ -8,16 +10,19 @@
8
10
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
9
11
 
10
12
  Read, render, convert, and create DjVu files. Pure-Rust library with a CLI,
11
- WebAssembly, and Python bindings — MIT licensed, no GPL dependencies, written
12
- from the public DjVu v3 specification.
13
+ WebAssembly, and Python bindings — on [crates.io](https://crates.io/crates/djvu-rs),
14
+ [PyPI](https://pypi.org/project/djvu-rs/), and [npm](https://www.npmjs.com/package/djvu-rs)
15
+ as `djvu-rs`. MIT licensed, no GPL dependencies, written from the public DjVu v3
16
+ specification.
13
17
 
14
18
  | Your task | How |
15
19
  |-----------|-----|
16
20
  | Convert DjVu → PDF, EPUB, TIFF, PNG, CBZ | [`djvu render`](#cli) or [`djvu_to_pdf`](#pdf-export) / [`djvu_to_epub`](#epub-export) / [`djvu_to_tiff`](#tiff-export) |
17
21
  | Extract text (plain, hOCR, ALTO XML) | [`djvu text`](#cli) or [`page.text()`](#text-extraction), [`to_hocr` / `to_alto`](#hocr-and-alto-xml-export) |
18
22
  | Render pages to RGBA pixels | [`render_pixmap`](#quick-start) — sync, [async](#async-render), or [parallel](#feature-flags) |
23
+ | Build a zoomable viewer (tiles) | [`djvu_tile`](#tile-rendering) — cached, prefetchable, cancellable tile rendering |
19
24
  | Show DjVu in the browser | [WebAssembly bindings](#webassembly), incl. lazy HTTP-Range loading |
20
- | Read DjVu from Python | [PyO3 bindings, built from source](#python) |
25
+ | Read DjVu from Python | `pip install djvu-rs` — [PyO3 bindings](#python) |
21
26
  | Create DjVu from images (PNG/JPEG/TIFF) | [`djvu encode`](#cli) or [`PageEncoder`](#encoding--low-level-api) |
22
27
  | Add an OCR text layer to a scan | [`djvu ocr`](#ocr-recognition-backends) (Tesseract) |
23
28
  | Merge, split, edit documents | [`djvu merge` / `djvu split`](#cli), `DocumentEditor`, `DjVuDocumentMut` |
@@ -134,6 +139,8 @@ djvu split book.djvu --pages 10-25 --output chapter.djvu
134
139
  djvu optimize book.djvu --output optimized.djvu --preset lossless-cleanup --dry-run
135
140
  djvu optimize book.djvu --output optimized.djvu --preset lossless-cleanup
136
141
  djvu optimize book.djvu --output optimized.djvu --preset archival --target-size 26214400
142
+ # (--max-ssim-loss is reserved for the planned archival re-encode; the current
143
+ # lossless cleanup is pixel-exact by construction and reports this)
137
144
  djvu optimize book.djvu --output optimized.djvu --max-ssim-loss 0.001
138
145
 
139
146
  # Encode an image (PNG, JPEG, or TIFF) into a single-page DjVu (bilevel JB2, lossless)
@@ -155,6 +162,12 @@ djvu encode scan.png --quality quality --binarization sauvola --bg-inpaint --out
155
162
  # Cap the IW44 background at a bits-per-pixel budget (smaller file, lower quality)
156
163
  djvu encode scan.jpg --quality quality --bg-bpp 0.8 --output scan.djvu
157
164
 
165
+ # Composite transparent PNG/TIFF pixels onto a solid colour (hex or white/black)
166
+ djvu encode logo.png --background white --output logo.djvu
167
+
168
+ # Refuse ICC-profiled input instead of silently dropping the profile
169
+ djvu encode scan.png --icc reject --output scan.djvu
170
+
158
171
  # Encode a directory of images into a bundled DJVM with shared Djbz
159
172
  djvu encode pages/ --output book.djvu --shared-dict-pages 2
160
173
 
@@ -422,6 +435,45 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
422
435
  }
423
436
  ```
424
437
 
438
+ ### Tile rendering
439
+
440
+ For viewer engines: [`djvu_tile`](https://docs.rs/djvu-rs/latest/djvu_rs/djvu_tile/)
441
+ renders a page as a display-space tile grid over the region renderer. Tile
442
+ pixels are byte-identical to the same rectangle of a full-page render, in any
443
+ request order.
444
+
445
+ ```rust,no_run
446
+ use djvu_rs::{DjVuDocument, djvu_render::RenderOptions};
447
+ use djvu_rs::djvu_tile::{TileLayout, render_tile_cached};
448
+
449
+ fn main() -> Result<(), Box<dyn std::error::Error>> {
450
+ let data = std::fs::read("book.djvu")?;
451
+ let doc = DjVuDocument::parse(&data)?;
452
+ let page = doc.page(0)?;
453
+
454
+ let opts = RenderOptions { width: 2400, height: 3200, ..Default::default() };
455
+ let layout = TileLayout::new(page, &opts, 256)?;
456
+
457
+ for row in 0..layout.rows() {
458
+ for col in 0..layout.cols() {
459
+ let tile = render_tile_cached(page, &opts, 256, col, row)?;
460
+ // tile.data — RGBA bytes of exactly this tile rectangle
461
+ let _ = tile;
462
+ }
463
+ }
464
+ Ok(())
465
+ }
466
+ ```
467
+
468
+ `render_tile_cached` memoizes composited tiles per page; the cache is
469
+ tile-granular and controllable (`tile_cache_usage`, `set_tile_cache_budget`,
470
+ `clear_tile_cache`, `invalidate_tile_region`). `render_tile_with` +
471
+ `TileRenderControls` / `TileCancelToken` add progressive quality steps and
472
+ cooperative cancellation, and with the `parallel` feature `prefetch_tiles` /
473
+ `prefetch_tiles_cancellable` warm the cache in the background with a bounded
474
+ worker pool. The full contract lives in
475
+ [`docs/tile-rendering.md`](docs/tile-rendering.md).
476
+
425
477
  ## Encoding & low-level API
426
478
 
427
479
  ### JB2 bilevel image encoder
@@ -541,6 +593,16 @@ whole is not transactional). Opening an indirect index directly with
541
593
  `DjVuDocumentMut::from_bytes` and calling `page_mut` remains unsupported; see
542
594
  [`docs/indirect-djvm-mutation.md`](docs/indirect-djvm-mutation.md).
543
595
 
596
+ The reverse direction is covered too: `djvm::to_indirect` splits a bundled
597
+ `FORM:DJVM` into an indirect index plus standalone component files, keeping
598
+ component ids, names, titles, and the document `NAVM` stable. Related
599
+ bundled-document operations in the same module: `djvm::remove_pages` deletes
600
+ pages with an explicit `UnreachablePolicy` (preserve or garbage-collect shared
601
+ components that lose their last including page),
602
+ `djvm::dedup_shared_components` merges byte-identical shared components, and
603
+ `djvm::DjvmStreamWriter` writes a bundle to any `io::Write` sink with memory
604
+ bounded to the spooled component being appended.
605
+
544
606
  ### Typed document editing
545
607
 
546
608
  `DocumentEditor` provides a versioned, typed operation list with a semantic
@@ -666,8 +728,9 @@ Honest boundaries, so you can decide fast:
666
728
 
667
729
  - **Library + CLI, not a viewer.** There is no GUI; the WASM demo is the
668
730
  closest thing to one.
669
- - **Python bindings are source-only for now.** The `djvu-py` package is not
670
- published to PyPI yet install it from the repository checkout.
731
+ - **Python bindings cover the reading surface only.** Open, render, and text
732
+ extraction ship in the PyPI wheels; encode, mutation, and PDF/EPUB/TIFF
733
+ export stay on the Rust crate / CLI for now.
671
734
  - **Indirect DJVM mutation is indirect-only via two paths.**
672
735
  `DjVuDocumentMut::from_bytes` + `page_mut` on an indirect index errors;
673
736
  use `from_indirect_resolved` (rebundles) or `IndirectRewritePlan` (rewrites
@@ -676,7 +739,9 @@ Honest boundaries, so you can decide fast:
676
739
  and single-page `FORM:DJVU` only; indirect returns a clean `Unsupported`
677
740
  error.
678
741
  - **`create_indirect` does not emit shared `DJVI` dictionary components** —
679
- build a bundled document with `djvu merge` when pages share a dictionary.
742
+ build a bundled document with `djvu merge` when pages share a dictionary, or
743
+ convert an existing bundled document with `djvm::to_indirect`, which
744
+ preserves shared components.
680
745
  - **Encoder size parity is corpus- and profile-dependent.** Run the
681
746
  reproducible [`encoder parity scorecard`](docs/encoder-parity.md) to compare
682
747
  the same raster through DjVuLibre 3.5.29's `c44`/`cjb2` and the archival-safe
@@ -756,7 +821,10 @@ combinations and targets), and is enforced in CI. In short:
756
821
  [`tests/panic_free_corpus.rs`](tests/panic_free_corpus.rs), proptests, and
757
822
  libFuzzer/OSS-Fuzz targets.
758
823
  - **Resource limits** — decode/render inherit documented, bounded memory/work
759
- ceilings; exceeding one returns a typed error naming the codec and axis. See
824
+ ceilings; exceeding one returns a typed error naming the codec and axis. The
825
+ ceilings are caller-configurable: pass `ResourceLimits` via `ParseOptions` to
826
+ `DjVuDocument::parse_with_options` (pages inherit them at render time), or
827
+ use `render_pixmap_with_limits` / `render_into_with_limits` directly. See
760
828
  [`SECURITY.md`](SECURITY.md#decode-time-resource-ceilings).
761
829
 
762
830
  ## Performance
@@ -797,7 +865,9 @@ MIT. See [LICENSE](LICENSE).
797
865
 
798
866
  Written from the public DjVu v3 specification:
799
867
  - https://www.sndjvu.org/spec.html
800
- - https://djvu.sourceforge.net/spec/DjVu3Spec.djvu (the spec is itself a DjVu file)
868
+ - https://web.archive.org/web/20251005122807/http://www.djvu.org/docs/DjVu3Spec.djvu
869
+ (the spec is itself a DjVu file; archived copy — djvu.org and
870
+ djvu.sourceforge.net no longer serve the original)
801
871
 
802
872
  No code derived from GPL-licensed DjVuLibre or any other GPL source.
803
873
  All algorithms are independent implementations from the spec.
Binary file