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 +78 -8
- package/package.json +1 -1
- package/scalar/README.md +78 -8
- package/scalar/djvu_rs_bg.wasm +0 -0
- package/simd128/README.md +78 -8
- package/simd128/djvu_rs_bg.wasm +0 -0
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# djvu-rs
|
|
2
2
|
|
|
3
3
|
[](https://crates.io/crates/djvu-rs)
|
|
4
|
+
[](https://pypi.org/project/djvu-rs/)
|
|
5
|
+
[](https://www.npmjs.com/package/djvu-rs)
|
|
4
6
|
[](https://docs.rs/djvu-rs)
|
|
5
7
|
[](https://github.com/matyushkin/djvu-rs/actions/workflows/ci.yml)
|
|
6
8
|
[](https://matyushkin.github.io/djvu-rs/dev/bench/)
|
|
@@ -8,16 +10,19 @@
|
|
|
8
10
|
[](LICENSE)
|
|
9
11
|
|
|
10
12
|
Read, render, convert, and create DjVu files. Pure-Rust library with a CLI,
|
|
11
|
-
WebAssembly, and Python bindings —
|
|
12
|
-
|
|
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
|
|
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
|
|
670
|
-
|
|
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.
|
|
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://
|
|
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.
|
|
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
|
[](https://crates.io/crates/djvu-rs)
|
|
4
|
+
[](https://pypi.org/project/djvu-rs/)
|
|
5
|
+
[](https://www.npmjs.com/package/djvu-rs)
|
|
4
6
|
[](https://docs.rs/djvu-rs)
|
|
5
7
|
[](https://github.com/matyushkin/djvu-rs/actions/workflows/ci.yml)
|
|
6
8
|
[](https://matyushkin.github.io/djvu-rs/dev/bench/)
|
|
@@ -8,16 +10,19 @@
|
|
|
8
10
|
[](LICENSE)
|
|
9
11
|
|
|
10
12
|
Read, render, convert, and create DjVu files. Pure-Rust library with a CLI,
|
|
11
|
-
WebAssembly, and Python bindings —
|
|
12
|
-
|
|
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
|
|
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
|
|
670
|
-
|
|
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.
|
|
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://
|
|
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/scalar/djvu_rs_bg.wasm
CHANGED
|
Binary file
|
package/simd128/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# djvu-rs
|
|
2
2
|
|
|
3
3
|
[](https://crates.io/crates/djvu-rs)
|
|
4
|
+
[](https://pypi.org/project/djvu-rs/)
|
|
5
|
+
[](https://www.npmjs.com/package/djvu-rs)
|
|
4
6
|
[](https://docs.rs/djvu-rs)
|
|
5
7
|
[](https://github.com/matyushkin/djvu-rs/actions/workflows/ci.yml)
|
|
6
8
|
[](https://matyushkin.github.io/djvu-rs/dev/bench/)
|
|
@@ -8,16 +10,19 @@
|
|
|
8
10
|
[](LICENSE)
|
|
9
11
|
|
|
10
12
|
Read, render, convert, and create DjVu files. Pure-Rust library with a CLI,
|
|
11
|
-
WebAssembly, and Python bindings —
|
|
12
|
-
|
|
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
|
|
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
|
|
670
|
-
|
|
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.
|
|
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://
|
|
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/simd128/djvu_rs_bg.wasm
CHANGED
|
Binary file
|