* Issue #1028: OCR can read pictures on pages that already have text Recognize Text skipped every page with text, so a picture of text on a page that also has real text never became searchable. The engine and the ocr_recognize tool already had skip_text_pages, but the desktop dialog never offered it, and turning it off read the page's own text again and added it a second time, so search found every word twice. - Engine: always collect the page's own glyph boxes; with skip_text_pages false, a recognised word at least half covered by them (an earlier OCR layer included) is left out. OcrPage counts those words as already_on_page. The default still skips pages with text. - Dialog: "Also recognize pages that already have text", kept with the other choices and used by Recognize text in multiple files too. - Tools: ocr_recognize reports already_on_page; ocr_recognize_files accepts skip_text_pages. - The two new labels in the 15 catalogs that translate the OCR dialog. - Synthetic tests (a picture of a sentence plus real text) for the engine, the tool and the dialog; the engine test fails with the filter turned off. Unit tests for the coverage check, including NaN, infinite, inverted and overlapping boxes. - parity, docs/roadmap.md M10 row and the ROADMAP.md progress log. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JqiZ658tGndQaHao5Q6mdm * Issue #1028: Czech labels for the OCR text-pages option The Czech catalog was completed (#1066) while #1028 was in review, and czech_covers_ui_literals requires every tl! literal: the two new Recognize Text labels were missing, so the test failed after the merge. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JqiZ658tGndQaHao5Q6mdm * Contributors: credit rp6186030 as Raj Patel in the About window Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JqiZ658tGndQaHao5Q6mdm --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: rp6186030 <25706807+rp6186030@users.noreply.github.com> Co-authored-by: Brandon Thomas <echelon@gmail.com>
This commit is contained in:
parent
fb97ce958d
commit
634f3d1144
|
|
@ -7,7 +7,7 @@ licence = "MIT OR Apache-2.0"
|
|||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Greek UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "1049c27ed90afac974f371c7e7e3bff94a2baebde2d820adc4c4c84e822e4cdd"
|
||||
sha256 = "e0efc10007da1342d860f881ab64a074444942242b1f09c240418957326b75e7"
|
||||
|
||||
# ATTRIBUTION.toml: one entry per asset (AGENTS.md §1). The source of truth for provenance and licences.
|
||||
# ATTRIBUTION.md is generated from this file: `cargo xtask assets --write`. Checked by `cargo xtask assets` (part of `xtask ci`).
|
||||
|
|
@ -30,7 +30,7 @@ licence = "MIT OR Apache-2.0"
|
|||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Simplified Chinese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "c30f72a1e902ddee4cb902560aaa91dfd3567490fd64db972f74a64fdb9af62c"
|
||||
sha256 = "9dc6614e3a5acbc4d56debdc2df00fcf5d27daa5c06666ff810ac93e8114defe"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/zh-hant.tsv"
|
||||
|
|
@ -41,7 +41,7 @@ licence = "MIT OR Apache-2.0"
|
|||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Traditional Chinese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "ad2c35209b7dbcd366038e6a179d695937ef1c3e333f64eb803de738766fb3e9"
|
||||
sha256 = "c63bcdd45a56a8d078c267601cffda9c665334fa30e8c66c37b2bd82507f1c95"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/cs.tsv"
|
||||
|
|
@ -52,7 +52,7 @@ licence = "MIT OR Apache-2.0"
|
|||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Czech UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "5d5456a0e51a3287ada0faec559dc137197159e64b4df8e7ee22ac8c563a9de9"
|
||||
sha256 = "2b1acb2ac2efc93333b8d5164fabe61747df617b78b5aae3b42e84b1b2d4cca1"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/pt-br.tsv"
|
||||
|
|
@ -63,7 +63,7 @@ licence = "MIT OR Apache-2.0"
|
|||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Brazilian Portuguese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "f0b1e7ac0bf248cf09bf2d038e9224c24d2edb4c3b3ba92b3806f9384161e77b"
|
||||
sha256 = "ad168121e1c1cce409b3b1fb4c90607b796e7e74313cb50766f85bd4f27d3444"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/es.tsv"
|
||||
|
|
@ -74,7 +74,7 @@ licence = "MIT OR Apache-2.0"
|
|||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Spanish UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "468443e23821e248c934e222b245e87f926c38786575b6290d33f959d39a20bf"
|
||||
sha256 = "1102e092fa3412e80b88b6c8112009de0f6606a041f90f6e90511608a8ea042b"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/fr.tsv"
|
||||
|
|
@ -85,7 +85,7 @@ licence = "MIT OR Apache-2.0"
|
|||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "French UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "575b7487137ef8c4b4ae33fc7bdbd50370fa28078e41727216046fdc5b89b81a"
|
||||
sha256 = "78892ec90eca3a8ad335ad80e6444f36c07f7c350519b1c3008ec49b759dc49b"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/de.tsv"
|
||||
|
|
@ -96,7 +96,7 @@ licence = "MIT OR Apache-2.0"
|
|||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "German UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "30f526fc7a1c14737bbaea272f45f123ffe61107188fe74375bd51b4808c9575"
|
||||
sha256 = "6c6098c12d6a9f2809d19f96b5c629da4e5a7130479a079104d6a2f932c4b482"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/te.tsv"
|
||||
|
|
@ -107,7 +107,7 @@ licence = "MIT OR Apache-2.0"
|
|||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Telugu UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "4e4d4e4e2f9d4d4be91ed2767a07640b626d83733093d12bff99d0a3986aa7d7"
|
||||
sha256 = "9f2385b4c401dd65a6f3563006a36731700de8f5bd10947fb085aabbb89d92d4"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/it.tsv"
|
||||
|
|
@ -118,7 +118,7 @@ licence = "MIT OR Apache-2.0"
|
|||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Italian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "8404b7ae758b3cddb28efd8f27405d260cf4193e3fecf68ea9422aebd4d063b4"
|
||||
sha256 = "315a0380974d0cac529a8b942732535af9fadcdda55c4516fe55c55f4d3d4346"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/pl.tsv"
|
||||
|
|
@ -129,7 +129,7 @@ licence = "MIT OR Apache-2.0"
|
|||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Polish UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "0ae768bcb0d7d3c24f5636d85df1532e626452ff97397312270379501a025c52"
|
||||
sha256 = "e46be841e460e4c1ea6f5154d851de825219c78576229561361b01fafd1cde32"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/ru.tsv"
|
||||
|
|
@ -140,7 +140,7 @@ licence = "MIT OR Apache-2.0"
|
|||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Russian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "7b0798851885f59105046e861f682f529f5666b0ef8edc3fb3ba6255459d80ff"
|
||||
sha256 = "994bfe85b3e706a77e5e532c93b3f3e5c47335f65b922a64ad12fdb369103a58"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/hu.tsv"
|
||||
|
|
@ -151,7 +151,7 @@ licence = "MIT OR Apache-2.0"
|
|||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Hungarian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "b1c44f5f1b6deadcdbe216019750f24beeac5533b6f519be4e361c97b57d5ac1"
|
||||
sha256 = "5f0b5dcdc5687fafa1020e6833de317311643f314e9be87cf8d04eac03af415a"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/bg.tsv"
|
||||
|
|
@ -162,7 +162,7 @@ licence = "MIT OR Apache-2.0"
|
|||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Bulgarian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "9dc87fafc98f9d30c4bafa8014f19b32b0e83a0608b2202e647988abb4e6303d"
|
||||
sha256 = "a9481a3890ff1c4fde5f4a84e7be3b31128aeeb38dc362b78c85302e6c1e8be7"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/kk.tsv"
|
||||
|
|
@ -173,7 +173,7 @@ licence = "MIT OR Apache-2.0"
|
|||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Kazakh UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "af1cf608a1561057c530d5716ea8aac27dacf9fe4ab49741743b980f587a1409"
|
||||
sha256 = "33903082ed23eb12a72e2dc22a9328575f6247fcd486647dac0c3d4fb085c36b"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/ja.tsv"
|
||||
|
|
@ -184,7 +184,7 @@ licence = "MIT OR Apache-2.0"
|
|||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Japanese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "f0e5375a5ee6b1151e312ba333f1a9a9ce0c55b27928da9bd6cff1ba95b8a5a1"
|
||||
sha256 = "a077c18dd048a4b5fbea6a7701aecf36bbdc32a3544912c88ec31dd81089b23b"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/uk.tsv"
|
||||
|
|
@ -195,7 +195,7 @@ licence = "MIT OR Apache-2.0"
|
|||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Ukrainian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "9663c11a1f02c55bafb37ed2b12fc4d8542a2f2ed1c6dfdefd0b1f0629208cb8"
|
||||
sha256 = "c779eae2b90e165fa96c361a605917d37298d0dfcad2008d416a3f07c21e9723"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/ar.tsv"
|
||||
|
|
@ -206,7 +206,7 @@ licence = "MIT OR Apache-2.0"
|
|||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Arabic UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "3bfd85b5f6d3ab75573fa7511487561d8ee8b37a69dbf361c5b142cd50fb38e6"
|
||||
sha256 = "8514f239f5dc15123fea56ab9e857dc7cf92cde2b7bc48f68dd02517b8338eba"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/tr.tsv"
|
||||
|
|
@ -217,7 +217,7 @@ licence = "MIT OR Apache-2.0"
|
|||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Turkish UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "e9f46d2738530d4114b9e0a3e3c51bbd2df9bc51cb84506c10f499baa566650d"
|
||||
sha256 = "3c5db01e760a2aa3da3f77730d3fcec6e05d91bc9ac039ab7eed57e22341f065"
|
||||
|
||||
[[asset]]
|
||||
path = "assets/icons/accessibility.svg"
|
||||
|
|
@ -3339,4 +3339,4 @@ licence = "MIT OR Apache-2.0"
|
|||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Slovene UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "855e743c3b21f4d8e158d30c5757ead4f2b6b8bf830c87115ae99e6d16da76a3"
|
||||
sha256 = "7acc4770b7fbca14cd5605eb484fc56a7744693742a86c80166ea61a54eedf31"
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
**Stage: alpha** · next: beta, ~26% (ready for real work 49% → 75%) and ~400–700 h away
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** minor (M10, #1028: OCR reads pictures on pages that have text, on request); earlier: M14 #307 Plan 3 uses one marker traversal on open and full refresh, and the combined scanner borrows metadata instead of cloning scan-only containers; includes a behavior-preserving `organize` binding for the workspace Clippy gate · earlier: minor (localized shortcut labels, #496; readiness table with hours per audience; full number as weighted sum 49%; second look: ready 40% → 47%, mainstream 55% added; core-workflow gate applied → alpha) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** minor (#594 paragraph drags validated in the Linux test environment; localized shortcut labels, #496; readiness table with hours per audience; full number as weighted sum 49%; earlier: second look: ready 40% → 47%, mainstream 55% added; core-workflow gate applied → alpha) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** minor (architectural paper controls from #599; localized shortcut labels, #496; readiness table with hours per audience; full number as weighted sum 49%; earlier: second look: ready 40% → 47%, mainstream 55% added; core-workflow gate applied → alpha) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** M14 #307 Plan 3 uses one marker traversal on open and full refresh, and the combined scanner borrows metadata instead of cloning scan-only containers; includes a behavior-preserving `organize` binding for the workspace Clippy gate · earlier: minor (localized shortcut labels, #496; readiness table with hours per audience; full number as weighted sum 49%; second look: ready 40% → 47%, mainstream 55% added; core-workflow gate applied → alpha) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** minor (closed issues synced: #774, #756 and the #799–#821 batch; earlier: localized shortcut labels, #496; readiness table with hours per audience; full number as weighted sum 49%; earlier: second look: ready 40% → 47%, mainstream 55% added; core-workflow gate applied → alpha) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS)
|
||||
|
||||
PdfCraft's one-page summary: how close it is to Adobe Acrobat Pro's offline feature set, and what
|
||||
|
|
@ -140,6 +140,7 @@ Ranked; detail and the full milestone table in [docs/roadmap.md](docs/roadmap.md
|
|||
|
||||
Newest first: the date, what moved, and the new overall estimate.
|
||||
|
||||
- **2026-10-11 (M10, #1028):** Recognize Text can read pages that already have text: "Also recognize pages that already have text" in the dialog (remembered with the other choices, and used by Recognize text in multiple files) and `skip_text_pages: false` on `ocr_recognize` and, newly, `ocr_recognize_files`. A picture on such a page becomes searchable, and a recognised word at least half covered by the page's own text (an earlier OCR layer included) is left out, so nothing is found twice; `ocr_recognize` reports how many were left out as `already_on_page`. Before, the option existed only for agents and added the page's text a second time. The default still skips pages with text. Synthetic tests (a picture of a sentence plus real text) cover the engine, the tool and the dialog, and fail with the filter turned off. Still missing: reading only the picture areas (the whole page is read, as for a scan). Separately, `recognize_text_makes_a_scanned_page_searchable` fails on main wherever the models are installed (the model reads "quick" as "auick"). Estimates unchanged.
|
||||
- **2026-10-11 (M2, page-label prefixes):** The page-label readers bound the total size of decoded prefixes. Each range decodes its own prefix, and ranges may share one prefix object, so a small file could ask for a copy per range: within the 200,000-entry tree limit, about 200 MiB of copies of one 1,024-byte prefix. Inspection and Organize now refuse a tree whose decoded prefixes pass 4 MiB in total (`MAX_PREFIX_TOTAL_BYTES`), before any label is made, as the other tree limits do; relabelling such a tree changes nothing. Synthetic tests share one 1,000-byte prefix across 5,000 ranges and keep exactly 4 MiB of prefixes readable. Overall estimate unchanged.
|
||||
- **2026-10-11 (UI, checkboxes and radio buttons):** Checkboxes no longer look like radio buttons, and on the light theme neither is invisible. With the theme's 6 px widget radius egui drew a checkbox's 14 px box as a circle, so View ▸ Page display's Continuous scrolling and Show cover page read as two more layout choices; on the light theme an unticked box or an unselected radio button was white on white with no edge. Every checkbox (81) and radio button (79) now goes through `widgets::checkbox` / `widgets::radio` / `widgets::radio_value`: a 1 px outline (darker on hover), the plain fill on the light theme as dialogs already had, and for checkboxes a rounded-square box (3 px), keeping what the caller set (dialogs, the search panel). `clippy.toml` disallows `ui.checkbox`, `ui.radio`, `ui.radio_value`, `egui::Checkbox` and `egui::RadioButton` so new ones use them too. Estimates unchanged.
|
||||
- **2026-10-11 (M13, #971):** `text_find` now bounds each page's temporary match-range list to the remaining result limit plus one, which is enough to detect and return a continuation without allocating every match on a dense page. Added limited-search coverage; the existing end-to-end continuation test passes. The text cache byte budget and per-page extraction warnings remain open.
|
||||
|
|
|
|||
|
|
@ -104,6 +104,10 @@ sync_github_name = true # use the name on your public GitHub profile as displa
|
|||
[people.Losminek]
|
||||
sync_github_name = true
|
||||
|
||||
[people.rp6186030]
|
||||
real_name = "Raj Patel"
|
||||
sync_github_name = false
|
||||
|
||||
[people.yeniufuklar-com]
|
||||
real_name = "Ufuk Efe"
|
||||
display_name = "Ufuk Efe"
|
||||
|
|
|
|||
|
|
@ -195,7 +195,7 @@ impl Automation {
|
|||
.iter()
|
||||
.map(|p| match &p.skipped {
|
||||
Some(why) => json!({ "page": p.page + 1, "skipped": why }),
|
||||
None => json!({ "page": p.page + 1, "words": p.words.len(), "text": p.text() }),
|
||||
None => json!({ "page": p.page + 1, "words": p.words.len(), "already_on_page": p.already_on_page, "text": p.text() }),
|
||||
})
|
||||
.collect();
|
||||
Ok(json!({ "words": found.iter().map(|p| p.words.len()).sum::<usize>(), "pages": list }))
|
||||
|
|
|
|||
|
|
@ -951,7 +951,7 @@ pub fn tools() -> Vec<ToolDef> {
|
|||
)),
|
||||
t("js_enabled", "JavaScript on or off", "Preferences ▸ JavaScript ▸ Enable Acrobat JavaScript: set it with `enabled`, or read it. With JavaScript off, field scripts other than Acrobat's AF calls don't run.")
|
||||
.with(schema(json!({ "enabled": { "type": "boolean" } }), &[])),
|
||||
t("ocr_recognize", "Recognize text (OCR)", "Scan & OCR ▸ Recognize text: render pages, read the words in them and add them as invisible text over the page image, so scanned pages become searchable and selectable (a searchable image; the image is not changed). Pages that already have text are skipped unless skip_text_pages is false. Returns each page's recognised text, word count or why it was skipped. Needs the OCR models (ocr_status). Undoable as one step.")
|
||||
t("ocr_recognize", "Recognize text (OCR)", "Scan & OCR ▸ Recognize text: render pages, read the words in them and add them as invisible text over the page image, so scanned pages become searchable and selectable (a searchable image; the image is not changed). Pages that already have text are skipped unless skip_text_pages is false; then pictures on those pages are read too, and recognised words that the page's own text already has (an earlier OCR layer included) are left out and counted as already_on_page. Returns each page's recognised text, word count or why it was skipped. Needs the OCR models (ocr_status). Undoable as one step.")
|
||||
.cmd("ocr.recognize")
|
||||
.with(schema(
|
||||
json!({
|
||||
|
|
@ -959,11 +959,11 @@ pub fn tools() -> Vec<ToolDef> {
|
|||
"pages": pages("to recognise (default: all)"),
|
||||
"dpi": { "type": "number", "minimum": 72, "maximum": 600, "description": "Resolution pages are read at (default 300; a scanned page is read at most at its own resolution)." },
|
||||
"language": { "type": "string", "enum": ["en"], "description": "Document language (default en)." },
|
||||
"skip_text_pages": { "type": "boolean", "description": "Leave pages that already contain text alone (default true)." },
|
||||
"skip_text_pages": { "type": "boolean", "description": "Leave pages that already contain text alone (default true). false also reads pictures on such pages, without adding their text again." },
|
||||
}),
|
||||
&["doc"],
|
||||
)),
|
||||
t("ocr_recognize_files", "Recognize text in multiple files", "Scan & OCR ▸ Recognize text ▸ In multiple files: read every page of each PDF in paths and write the searchable result into folder under the same name (pages that already have text are left alone). Returns, per file, the output path, word count and skipped pages, or the error.")
|
||||
t("ocr_recognize_files", "Recognize text in multiple files", "Scan & OCR ▸ Recognize text ▸ In multiple files: read every page of each PDF in paths and write the searchable result into folder under the same name (pages that already have text are left alone unless skip_text_pages is false, as in ocr_recognize). Returns, per file, the output path, word count and skipped pages, or the error.")
|
||||
.cmd("ocr.recognize_batch")
|
||||
.with(schema(
|
||||
json!({
|
||||
|
|
@ -971,6 +971,7 @@ pub fn tools() -> Vec<ToolDef> {
|
|||
"folder": { "type": "string" },
|
||||
"dpi": { "type": "number", "minimum": 72, "maximum": 600 },
|
||||
"language": { "type": "string", "enum": ["en"] },
|
||||
"skip_text_pages": { "type": "boolean", "description": "Leave pages that already contain text alone (default true); see ocr_recognize." },
|
||||
}),
|
||||
&["paths", "folder"],
|
||||
)),
|
||||
|
|
|
|||
|
|
@ -3319,6 +3319,34 @@ fn ocr_tools_make_a_scan_searchable() {
|
|||
assert!(matches!(a.call("ocr_recognize", &json!({ "doc": scan, "language": "xx" })), Err(ToolError::InvalidArgs(_))));
|
||||
}
|
||||
|
||||
/// #1028: `skip_text_pages: false` reads a picture on a page that has its own text, and the
|
||||
/// page's own words are left out (counted) instead of being added a second time.
|
||||
#[test]
|
||||
fn ocr_recognize_reads_pictures_on_pages_with_text() {
|
||||
let dir = workdir("ocr-hybrid");
|
||||
let mut a = auto(&dir);
|
||||
if ok(&mut a, "ocr_status", json!({}))["available"] != true {
|
||||
eprintln!("skipped: OCR models not installed");
|
||||
return;
|
||||
}
|
||||
let text = ok(&mut a, "doc_create", json!({ "from": "text", "text": "Pictures of words become searchable" }))["doc"].as_u64().unwrap();
|
||||
ok(&mut a, "doc_export_images", json!({ "doc": text, "folder": "scan", "dpi": 150, "pages": [1] }));
|
||||
let png = std::fs::read_dir(dir.join("scan")).unwrap().next().unwrap().unwrap().path();
|
||||
let doc = ok(&mut a, "doc_create", json!({ "from": "images", "paths": [png.to_string_lossy()] }))["doc"].as_u64().unwrap();
|
||||
ok(&mut a, "page_add_text", json!({ "doc": doc, "page": 1, "text": "Invoice reference 4711", "at": [40, 500], "size": 24 }));
|
||||
|
||||
let skipped = ok(&mut a, "ocr_recognize", json!({ "doc": doc }));
|
||||
assert!(skipped["pages"][0]["skipped"].is_string(), "{skipped}");
|
||||
let r = ok(&mut a, "ocr_recognize", json!({ "doc": doc, "skip_text_pages": false }));
|
||||
let page = &r["pages"][0];
|
||||
let added = page["text"].as_str().unwrap().to_lowercase();
|
||||
assert!(added.contains("searchable") && !added.contains("invoice"), "{r}");
|
||||
assert!(page["already_on_page"].as_u64().unwrap() >= 2, "{r}");
|
||||
let found = |a: &mut Automation, q: &str| ok(a, "text_find", json!({ "doc": doc, "query": q }))["count"].as_u64().unwrap();
|
||||
assert_eq!((found(&mut a, "invoice"), found(&mut a, "searchable")), (1, 1));
|
||||
assert!(matches!(a.call("ocr_recognize", &json!({ "doc": doc, "skip_text_pages": "no" })), Err(ToolError::InvalidArgs(_))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ocr_recognize_files_refuses_paths_outside_the_root() {
|
||||
let dir = workdir("ocr-files-root");
|
||||
|
|
|
|||
|
|
@ -21,10 +21,30 @@ pub struct OcrSettings {
|
|||
/// Language code from [`LANGUAGES`] (the models read the Latin alphabet, whatever this says).
|
||||
pub language: String,
|
||||
/// Leave pages that already have text alone (Acrobat reports "page contains renderable
|
||||
/// text" and skips them).
|
||||
/// text" and skips them). When `false`, such pages are read too (a picture on a page that
|
||||
/// also has text, #1028) and only the words that aren't already there are added.
|
||||
pub skip_text_pages: bool,
|
||||
}
|
||||
|
||||
/// A recognised word whose box is at least this much covered by the page's own text is that
|
||||
/// text read again, not a word in a picture.
|
||||
const ALREADY_ON_PAGE: f32 = 0.5;
|
||||
|
||||
/// How much of `word` the `text` boxes cover, from 0 to 1. Boxes are `[x0, y0, x1, y1]` in page
|
||||
/// view space. Overlaps between the text boxes are counted twice (glyphs rarely overlap), so the
|
||||
/// sum is capped at the whole word.
|
||||
fn covered(word: [f32; 4], text: &[[f32; 4]]) -> f32 {
|
||||
// `max(0.0)` also turns a NaN side into 0: a degenerate box covers nothing.
|
||||
let area = |[x0, y0, x1, y1]: [f32; 4]| (x1 - x0).max(0.0) * (y1 - y0).max(0.0);
|
||||
let total = area(word);
|
||||
if !total.is_finite() || total <= 0.0 {
|
||||
return 0.0;
|
||||
}
|
||||
let inside: f32 =
|
||||
text.iter().map(|g| area([word[0].max(g[0]), word[1].max(g[1]), word[2].min(g[2]), word[3].min(g[3])])).filter(|a| a.is_finite()).sum();
|
||||
(inside / total).min(1.0)
|
||||
}
|
||||
|
||||
impl Default for OcrSettings {
|
||||
fn default() -> Self {
|
||||
OcrSettings { dpi: 300.0, language: "en".into(), skip_text_pages: true }
|
||||
|
|
@ -36,6 +56,8 @@ impl Default for OcrSettings {
|
|||
pub struct OcrPage {
|
||||
pub page: usize,
|
||||
pub words: Vec<PlacedWord>,
|
||||
/// Words recognised but left out because the page's own text already has them there.
|
||||
pub already_on_page: usize,
|
||||
/// Why the page was not read, if it was skipped.
|
||||
pub skipped: Option<String>,
|
||||
}
|
||||
|
|
@ -88,13 +110,16 @@ impl OcrJob {
|
|||
break;
|
||||
}
|
||||
let Some(info) = self.infos.get(page) else { continue };
|
||||
let skip = |why: &str| OcrPage { page, words: Vec::new(), skipped: Some(why.into()) };
|
||||
if self.settings.skip_text_pages {
|
||||
let t = r.render(RenderRequest { page, kind: RequestKind::Text, scale: 1.0, ..Default::default() });
|
||||
if t.text.as_ref().is_some_and(|t| !t.plain_text().trim().is_empty()) {
|
||||
out.push(skip("the page already contains text"));
|
||||
continue;
|
||||
}
|
||||
let skip = |why: &str| OcrPage { page, words: Vec::new(), already_on_page: 0, skipped: Some(why.into()) };
|
||||
// The boxes of the page's own text (an earlier OCR layer's included), in view space.
|
||||
let existing: Vec<[f32; 4]> = r
|
||||
.render(RenderRequest { page, kind: RequestKind::Text, scale: 1.0, ..Default::default() })
|
||||
.text
|
||||
.map(|t| t.glyphs.iter().filter(|g| !g.text.trim().is_empty()).map(|g| g.rect).collect())
|
||||
.unwrap_or_default();
|
||||
if self.settings.skip_text_pages && !existing.is_empty() {
|
||||
out.push(skip("the page already contains text"));
|
||||
continue;
|
||||
}
|
||||
// A scan is read at its own resolution (enlarging it only blurs the letters), at most
|
||||
// the chosen one.
|
||||
|
|
@ -117,8 +142,11 @@ impl OcrJob {
|
|||
let [u, v] = info.view_to_user(x / scale, y / scale);
|
||||
[u as f64, v as f64]
|
||||
};
|
||||
let words = lines.iter().flat_map(|l| &l.words).map(|w| PlacedWord::place(w, to_user)).collect();
|
||||
out.push(OcrPage { page, words, skipped: None });
|
||||
// Words the page's own text already has are not added a second time.
|
||||
let (fresh, already): (Vec<_>, Vec<_>) =
|
||||
lines.iter().flat_map(|l| &l.words).partition(|w| covered(w.rect.map(|v| v / scale), &existing) < ALREADY_ON_PAGE);
|
||||
let words = fresh.into_iter().map(|w| PlacedWord::place(w, to_user)).collect();
|
||||
out.push(OcrPage { page, words, already_on_page: already.len(), skipped: None });
|
||||
}
|
||||
progress(total, total);
|
||||
out
|
||||
|
|
@ -210,3 +238,39 @@ pub fn recognize_file(
|
|||
}
|
||||
Ok(FileResult { bytes: s.save_bytes(id).map_err(|e| e.to_string())?, pages })
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// #1028: a recognised word over the page's own text is that text again; one in an empty
|
||||
/// part of the page (a picture) is new.
|
||||
#[test]
|
||||
fn words_over_existing_text_count_as_already_there() {
|
||||
// Glyph em boxes of "Total" at 12 pt, five letters side by side.
|
||||
let glyphs: Vec<[f32; 4]> = (0..5).map(|i| [100.0 + 7.0 * i as f32, 50.0, 107.0 + 7.0 * i as f32, 62.0]).collect();
|
||||
// OCR's tight box around the same ink.
|
||||
assert!(covered([100.5, 52.0, 134.0, 61.0], &glyphs) >= ALREADY_ON_PAGE);
|
||||
// A word in a picture further down, and one beside the text on the same line.
|
||||
assert_eq!(covered([100.0, 200.0, 140.0, 212.0], &glyphs), 0.0);
|
||||
assert_eq!(covered([140.0, 52.0, 170.0, 61.0], &glyphs), 0.0);
|
||||
// Touching the text's last letter is not enough.
|
||||
assert!(covered([131.0, 52.0, 170.0, 61.0], &glyphs) < ALREADY_ON_PAGE);
|
||||
// A page without text has nothing to cover a word.
|
||||
assert_eq!(covered([100.5, 52.0, 134.0, 61.0], &[]), 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn coverage_of_overlapping_or_broken_boxes_stays_between_zero_and_one() {
|
||||
// A glyph drawn twice (fake bold) does not cover more than the whole word.
|
||||
let twice = [[0.0, 0.0, 10.0, 10.0], [0.0, 0.0, 10.0, 10.0]];
|
||||
assert_eq!(covered([0.0, 0.0, 10.0, 10.0], &twice), 1.0);
|
||||
// Empty, inverted and non-finite boxes cover nothing and are covered by nothing.
|
||||
for word in [[5.0, 5.0, 5.0, 9.0], [9.0, 0.0, 1.0, 9.0], [f32::NAN, 0.0, 9.0, 9.0], [0.0, 0.0, f32::INFINITY, 9.0]] {
|
||||
assert_eq!(covered(word, &twice), 0.0, "{word:?}");
|
||||
}
|
||||
let broken = [[f32::NAN; 4], [f32::NEG_INFINITY, f32::NEG_INFINITY, f32::INFINITY, f32::INFINITY]];
|
||||
let c = covered([0.0, 0.0, 10.0, 10.0], &broken);
|
||||
assert!((0.0..=1.0).contains(&c), "{c}");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2175,6 +2175,49 @@ fn recognize_text_makes_a_scanned_page_searchable() {
|
|||
assert!(again[0].skipped.is_some());
|
||||
}
|
||||
|
||||
/// #1028: a page with its own text and a picture of text. By default it is skipped, as before;
|
||||
/// with `skip_text_pages: false` the picture is read and the page's own text is not added again.
|
||||
#[test]
|
||||
fn recognize_text_reads_a_picture_on_a_page_that_has_text() {
|
||||
if !ocr::available() {
|
||||
eprintln!("skipped: OCR models not installed");
|
||||
return;
|
||||
}
|
||||
let mut s = Session::new().with_clock(|| 1_700_000_000);
|
||||
let text = s.create_from_text("t", "The quick brown fox jumps over the lazy dog.").unwrap();
|
||||
let id = s.open("text.pdf", None, text, None).unwrap();
|
||||
let png = export::Exporter::new(s.get(id).unwrap()).png(0, 150.0).unwrap();
|
||||
let scan = s.create_from_images(&[("scan.png".into(), png)]).unwrap();
|
||||
let id = s.open("scan.pdf", None, scan, None).unwrap();
|
||||
// Real text in the empty lower part of the picture.
|
||||
let (w, h) = {
|
||||
let p = &s.get(id).unwrap().info.pages[0];
|
||||
(p.width as f64, p.height as f64)
|
||||
};
|
||||
let note = AddedText { rect: [w * 0.1, h * 0.75, w * 0.9, h * 0.85], text: "Invoice reference 4711".into(), size: 18.0, ..Default::default() };
|
||||
s.apply(id, Edit::AddText { page: 0, text: note }).unwrap();
|
||||
let count = |s: &Session, word: &str| page_texts(s, id)[0].to_lowercase().matches(word).count();
|
||||
assert_eq!((count(&s, "invoice"), count(&s, "brown")), (1, 0));
|
||||
|
||||
let skipped = s.recognize_text(id, &[], ocr::OcrSettings::default()).unwrap();
|
||||
assert!(skipped[0].skipped.is_some(), "pages with text are still skipped by default");
|
||||
assert_eq!(s.get(id).unwrap().can_undo(), Some("Add text"), "nothing was added");
|
||||
|
||||
let hybrid = ocr::OcrSettings { skip_text_pages: false, ..Default::default() };
|
||||
let found = s.recognize_text(id, &[], hybrid.clone()).unwrap();
|
||||
assert_eq!(found[0].skipped, None);
|
||||
assert!(found[0].already_on_page >= 2, "the page's own words were recognised and left out: {:?}", found[0]);
|
||||
let added = found[0].text().to_lowercase();
|
||||
assert!(added.contains("brown") && added.contains("lazy") && !added.contains("invoice"), "{added}");
|
||||
assert_eq!((count(&s, "invoice"), count(&s, "brown")), (1, 1), "{:?}", page_texts(&s, id));
|
||||
assert_eq!(s.get(id).unwrap().can_undo(), Some("Recognize text"));
|
||||
|
||||
// Reading the page again adds nothing: every word is there now.
|
||||
let again = s.recognize_text(id, &[], hybrid).unwrap();
|
||||
assert!(again[0].words.is_empty(), "{:?}", again[0]);
|
||||
assert_eq!((count(&s, "invoice"), count(&s, "brown")), (1, 1));
|
||||
}
|
||||
|
||||
/// A form whose scripts are custom JavaScript: total = price × qty (calculate, through a
|
||||
/// document-level function), shown with a custom format; qty is validated; a button script.
|
||||
fn scripted_form() -> Vec<u8> {
|
||||
|
|
|
|||
|
|
@ -1222,6 +1222,8 @@
|
|||
Searchable Image (Exact) صورة قابلة للبحث (مطابقة)
|
||||
Adds invisible text over each word; the page image is not changed يضيف نصًا غير مرئي فوق كل كلمة؛ صورة الصفحة لا تتغير
|
||||
Downsample to تقليل العينات إلى
|
||||
Also recognize pages that already have text التعرف على الصفحات التي تحتوي على نص أيضًا
|
||||
Pictures on those pages are recognized too; words already in the page's text are not added again يُتعرَّف على الصور في هذه الصفحات أيضًا، ولا تُضاف مرة أخرى الكلمات الموجودة بالفعل في نص الصفحة
|
||||
Text recognition isn't installed: its model files are missing. Reinstall PdfCraft, or set PDFCRAFT_MODELS to the folder that holds them. التعرف على النص غير مثبت: ملفات نماذجه مفقودة. أعد تثبيت PdfCraft، أو اضبط PDFCRAFT_MODELS على المجلد الذي يحتويها.
|
||||
Settings الإعدادات
|
||||
Optional اختياري
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
|
|
@ -921,6 +921,8 @@
|
|||
Searchable Image (Exact) Изображение с възможност за търсене (точно)
|
||||
Adds invisible text over each word; the page image is not changed Добавя невидим текст върху всяка дума; изображението на страницата не се променя
|
||||
Downsample to Намали разделителната способност до
|
||||
Also recognize pages that already have text Разпознай и страници, които вече съдържат текст
|
||||
Pictures on those pages are recognized too; words already in the page's text are not added again Изображенията на тези страници също се разпознават; думите, които вече са в текста на страницата, не се добавят отново
|
||||
Text recognition isn't installed: its model files are missing. Reinstall PdfCraft, or set PDFCRAFT_MODELS to the folder that holds them. Разпознаването на текст не е инсталирано: файловете на моделите му липсват. Инсталирайте PdfCraft отново или задайте в PDFCRAFT_MODELS папката, която ги съдържа.
|
||||
Settings Настройки
|
||||
Protect Защита
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 8 and column 16.
|
|
|
@ -1291,6 +1291,8 @@ key Space Mezerník
|
|||
Searchable Image (Exact) Prohledávatelný obraz (přesný)
|
||||
Adds invisible text over each word; the page image is not changed Přidá nad každé slovo neviditelný text; obraz stránky se nezmění
|
||||
Downsample to Převzorkovat na
|
||||
Also recognize pages that already have text Rozpoznat i stránky, které už obsahují text
|
||||
Pictures on those pages are recognized too; words already in the page's text are not added again Rozpoznají se i obrázky na těchto stránkách; slova, která už jsou v textu stránky, se znovu nepřidají
|
||||
Text recognition isn't installed: its model files are missing. Reinstall PdfCraft, or set PDFCRAFT_MODELS to the folder that holds them. Rozpoznávání textu není nainstalováno: chybí soubory jeho modelů. Přeinstalujte PdfCraft nebo nastavte PDFCRAFT_MODELS na složku, ve které jsou.
|
||||
Settings Nastavení
|
||||
Optional Volitelné
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
|
|
@ -1207,6 +1207,8 @@
|
|||
Searchable Image (Exact) Durchsuchbares Bild (exakt)
|
||||
Adds invisible text over each word; the page image is not changed Legt unsichtbaren Text über jedes Wort; das Seitenbild bleibt unverändert
|
||||
Downsample to Herunterrechnen auf
|
||||
Also recognize pages that already have text Auch Seiten erkennen, die bereits Text enthalten
|
||||
Pictures on those pages are recognized too; words already in the page's text are not added again Bilder auf diesen Seiten werden ebenfalls erkannt; Wörter, die schon im Text der Seite stehen, werden nicht erneut hinzugefügt
|
||||
Text recognition isn't installed: its model files are missing. Reinstall PdfCraft, or set PDFCRAFT_MODELS to the folder that holds them. Die Texterkennung ist nicht installiert: ihre Modelldateien fehlen. Installiere PdfCraft neu oder setze PDFCRAFT_MODELS auf den Ordner, der sie enthält.
|
||||
Settings Einstellungen
|
||||
Optional Optional
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 6 and column 51.
|
|
|
@ -805,6 +805,8 @@
|
|||
Searchable Image (Exact) Αναζητήσιμη εικόνα (ακριβής)
|
||||
Adds invisible text over each word; the page image is not changed Προσθέτει αόρατο κείμενο πάνω από κάθε λέξη, χωρίς να αλλάζει την εικόνα της σελίδας
|
||||
Downsample to Μείωση ανάλυσης σε
|
||||
Also recognize pages that already have text Αναγνώριση και σελίδων που έχουν ήδη κείμενο
|
||||
Pictures on those pages are recognized too; words already in the page's text are not added again Αναγνωρίζονται και οι εικόνες αυτών των σελίδων· οι λέξεις που υπάρχουν ήδη στο κείμενο της σελίδας δεν προστίθενται ξανά
|
||||
Text recognition isn't installed: its model files are missing. Reinstall PdfCraft, or set PDFCRAFT_MODELS to the folder that holds them. Η αναγνώριση κειμένου δεν είναι εγκατεστημένη: λείπουν τα αρχεία μοντέλων. Εγκαταστήστε ξανά το PdfCraft ή ορίστε το PDFCRAFT_MODELS στον φάκελο που τα περιέχει.
|
||||
Planned · {m} Προγραμματισμένο · {m}
|
||||
Planned for milestone {m} — open to see what it will include Προγραμματισμένο για το ορόσημο {m} — ανοίξτε για να δείτε τι θα περιλαμβάνει
|
||||
|
|
|
|||
|
Can't render this file because it has a wrong number of fields in line 5.
|
|
|
@ -1215,6 +1215,8 @@
|
|||
Searchable Image (Exact) Imagen con búsqueda (exacta)
|
||||
Adds invisible text over each word; the page image is not changed Añade texto invisible sobre cada palabra; la imagen de la página no cambia
|
||||
Downsample to Reducir resolución a
|
||||
Also recognize pages that already have text Reconocer también las páginas que ya tienen texto
|
||||
Pictures on those pages are recognized too; words already in the page's text are not added again También se reconocen las imágenes de esas páginas; las palabras que ya están en el texto de la página no se vuelven a añadir
|
||||
Text recognition isn't installed: its model files are missing. Reinstall PdfCraft, or set PDFCRAFT_MODELS to the folder that holds them. El reconocimiento de texto no está instalado: faltan sus archivos de modelo. Reinstala PdfCraft o define PDFCRAFT_MODELS con la carpeta que los contiene.
|
||||
Settings Ajustes
|
||||
Optional Opcional
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 6 and column 60.
|
|
|
@ -1202,6 +1202,8 @@
|
|||
Searchable Image (Exact) Image recherchable (exacte)
|
||||
Adds invisible text over each word; the page image is not changed Ajoute du texte invisible sur chaque mot ; l'image de la page n'est pas modifiée
|
||||
Downsample to Sous-échantillonner à
|
||||
Also recognize pages that already have text Reconnaître aussi les pages qui contiennent déjà du texte
|
||||
Pictures on those pages are recognized too; words already in the page's text are not added again Les images de ces pages sont aussi reconnues ; les mots déjà présents dans le texte de la page ne sont pas ajoutés une seconde fois
|
||||
Text recognition isn't installed: its model files are missing. Reinstall PdfCraft, or set PDFCRAFT_MODELS to the folder that holds them. La reconnaissance de texte n'est pas installée : ses fichiers de modèles sont absents. Réinstallez PdfCraft, ou définissez PDFCRAFT_MODELS sur le dossier qui les contient.
|
||||
Settings Réglages
|
||||
Optional Facultatif
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 6 and column 39.
|
|
|
@ -1192,6 +1192,8 @@
|
|||
Searchable Image (Exact) Kereshető kép (pontos)
|
||||
Adds invisible text over each word; the page image is not changed Láthatatlan szöveget helyez minden szóra; az oldalkép nem változik
|
||||
Downsample to Feloldás csökkentése erre
|
||||
Also recognize pages that already have text A már szöveget tartalmazó oldalak felismerése is
|
||||
Pictures on those pages are recognized too; words already in the page's text are not added again Az ezeken az oldalakon lévő képeket is felismeri; az oldal szövegében már szereplő szavakat nem adja hozzá újra
|
||||
Text recognition isn't installed: its model files are missing. Reinstall PdfCraft, or set PDFCRAFT_MODELS to the folder that holds them. A szövegfelismerés nincs telepítve: hiányoznak a modellfájljai. Telepítsd újra a PdfCraftot, vagy állítsd a PDFCRAFT_MODELS változót az őket tartalmazó mappára.
|
||||
Settings Beállítások
|
||||
Optional Nem kötelező
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 7 and column 44.
|
|
|
@ -1218,6 +1218,8 @@
|
|||
Searchable Image (Exact) Immagine ricercabile (esatta)
|
||||
Adds invisible text over each word; the page image is not changed Aggiunge testo invisibile sopra ogni parola; l'immagine della pagina non viene modificata
|
||||
Downsample to Sottocampiona a
|
||||
Also recognize pages that already have text Riconosci anche le pagine che contengono già testo
|
||||
Pictures on those pages are recognized too; words already in the page's text are not added again Vengono riconosciute anche le immagini di queste pagine; le parole già presenti nel testo della pagina non vengono aggiunte di nuovo
|
||||
Settings Impostazioni
|
||||
Optional Facoltativo
|
||||
Planned · {m} Pianificato · {m}
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
|
|
@ -1222,6 +1222,8 @@
|
|||
Searchable Image (Exact) 検索可能な画像(元の画像を保持)
|
||||
Adds invisible text over each word; the page image is not changed 各単語に非表示のテキストを重ねます。ページ画像は変更しません
|
||||
Downsample to ダウンサンプル:
|
||||
Also recognize pages that already have text テキストを含むページも認識
|
||||
Pictures on those pages are recognized too; words already in the page's text are not added again それらのページ上の画像も認識します。ページのテキストに既にある単語は再度追加されません
|
||||
Text recognition isn't installed: its model files are missing. Reinstall PdfCraft, or set PDFCRAFT_MODELS to the folder that holds them. テキスト認識がインストールされていません。モデルファイルがありません。PdfCraft を再インストールするか、PDFCRAFT_MODELS にモデルファイルのあるフォルダーを設定してください。
|
||||
Settings 設定
|
||||
Optional 任意
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
|
|
@ -1170,6 +1170,8 @@
|
|||
Searchable Image (Exact) Іздеуге болатын сурет (дәл)
|
||||
Adds invisible text over each word; the page image is not changed Әр сөздің үстіне көрінбейтін мәтін қосады; бет суреті өзгермейді
|
||||
Downsample to Ажыратымдылықты төмендету
|
||||
Also recognize pages that already have text Мәтіні бар беттерді де тану
|
||||
Pictures on those pages are recognized too; words already in the page's text are not added again Бұл беттердегі суреттер де танылады; беттің мәтінінде бұрыннан бар сөздер қайта қосылмайды
|
||||
Settings Параметрлер
|
||||
Optional Міндетті емес
|
||||
Planned · {m} Жоспарланған · {m}
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 813 and column 12.
|
|
|
@ -1209,6 +1209,8 @@
|
|||
Searchable Image (Exact) Obraz z możliwością wyszukiwania (dokładny)
|
||||
Adds invisible text over each word; the page image is not changed Dodaje niewidoczny tekst nad każdym słowem; obraz strony pozostaje bez zmian
|
||||
Downsample to Zmniejsz rozdzielczość do
|
||||
Also recognize pages that already have text Rozpoznaj także strony, które zawierają już tekst
|
||||
Pictures on those pages are recognized too; words already in the page's text are not added again Obrazy na tych stronach też są rozpoznawane; słowa, które już są w tekście strony, nie są dodawane ponownie
|
||||
Text recognition isn't installed: its model files are missing. Reinstall PdfCraft, or set PDFCRAFT_MODELS to the folder that holds them. Rozpoznawanie tekstu nie jest zainstalowane: brakuje plików modeli. Zainstaluj PdfCraft ponownie lub ustaw PDFCRAFT_MODELS na folder zawierający te pliki.
|
||||
Settings Ustawienia
|
||||
Optional Opcjonalne
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
|
|
@ -866,6 +866,8 @@
|
|||
Searchable Image (Exact) Imagem pesquisável (Exata)
|
||||
Adds invisible text over each word; the page image is not changed Adiciona texto invisível sobre cada palavra; a imagem da página não é alterada
|
||||
Downsample to Reduzir amostragem para
|
||||
Also recognize pages that already have text Reconhecer também as páginas que já contêm texto
|
||||
Pictures on those pages are recognized too; words already in the page's text are not added again As imagens dessas páginas também são reconhecidas; as palavras que já estão no texto da página não são adicionadas novamente
|
||||
Settings Configurações
|
||||
Protect Proteger
|
||||
Advanced options Opções avançadas
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 9 and column 16.
|
|
|
@ -1223,6 +1223,8 @@
|
|||
Searchable Image (Exact) Доступное для поиска изображение (точное)
|
||||
Adds invisible text over each word; the page image is not changed Добавляет невидимый текст над каждым словом; изображение страницы не меняется
|
||||
Downsample to Уменьшить до
|
||||
Also recognize pages that already have text Распознавать и страницы, на которых уже есть текст
|
||||
Pictures on those pages are recognized too; words already in the page's text are not added again Изображения на этих страницах тоже распознаются; слова, которые уже есть в тексте страницы, повторно не добавляются
|
||||
Text recognition isn't installed: its model files are missing. Reinstall PdfCraft, or set PDFCRAFT_MODELS to the folder that holds them. Распознавание текста не установлено: отсутствуют файлы моделей. Переустановите PdfCraft или задайте в PDFCRAFT_MODELS папку, в которой они лежат.
|
||||
Settings Настройки
|
||||
Optional Опционально
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 9 and column 16.
|
|
|
@ -623,6 +623,8 @@
|
|||
Downloaded {name} Preneseno: {name}
|
||||
Downloading attachments on the web arrives with M3.10 ({n} bytes ready) Prenos priponk v spletni različici pride z M3.10 (pripravljenih bajtov: {n})
|
||||
Downsample to Zmanjšaj vzorčenje na
|
||||
Also recognize pages that already have text Prepoznaj tudi strani, ki že vsebujejo besedilo
|
||||
Pictures on those pages are recognized too; words already in the page's text are not added again Prepoznane bodo tudi slike na teh straneh; besede, ki so že v besedilu strani, ne bodo dodane znova
|
||||
Drag a rectangle around the area to copy Povlecite pravokotnik okoli območja, ki ga želite kopirati
|
||||
Drag a rectangle on a page to crop it; double-click a page for Set Page Boxes Povlecite pravokotnik na strani, da jo obrežete; dvokliknite stran za nastavitev okvirjev strani
|
||||
Drag a rectangle to create a link; double-click a link to edit it Povlecite pravokotnik, da ustvarite povezavo; dvokliknite povezavo, da jo uredite
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 9 and column 16.
|
|
|
@ -1195,6 +1195,8 @@ comment menu Edit సవరించు
|
|||
Searchable Image (Exact) వెతకగలిగే చిత్రం (యథాతథం)
|
||||
Adds invisible text over each word; the page image is not changed ప్రతి పదంపై కనిపించని టెక్స్ట్ను జోడిస్తుంది; పేజీ చిత్రం మారదు
|
||||
Downsample to ఈ రిజల్యూషన్కు తగ్గించు
|
||||
Also recognize pages that already have text ఇప్పటికే టెక్స్ట్ ఉన్న పేజీలను కూడా గుర్తించు
|
||||
Pictures on those pages are recognized too; words already in the page's text are not added again ఆ పేజీలలోని చిత్రాలు కూడా గుర్తించబడతాయి; పేజీ టెక్స్ట్లో ఇప్పటికే ఉన్న పదాలు మళ్లీ జోడించబడవు
|
||||
Text recognition isn't installed: its model files are missing. Reinstall PdfCraft, or set PDFCRAFT_MODELS to the folder that holds them. టెక్స్ట్ గుర్తింపు ఇన్స్టాల్ కాలేదు: దాని మోడల్ ఫైల్లు లేవు. PdfCraftను మళ్లీ ఇన్స్టాల్ చేయండి, లేదా వాటిని కలిగి ఉన్న ఫోల్డర్కు PDFCRAFT_MODELSను సెట్ చేయండి.
|
||||
Settings సెట్టింగ్లు
|
||||
Optional ఐచ్ఛికం
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 13 and column 16.
|
|
|
@ -1182,6 +1182,8 @@
|
|||
Searchable Image (Exact) Aranabilir Resim (Tam)
|
||||
Adds invisible text over each word; the page image is not changed Her sözcüğün üzerine görünmez metin ekler; sayfa resmi değiştirilmez
|
||||
Downsample to Şu değere alt örnekle:
|
||||
Also recognize pages that already have text Zaten metin içeren sayfaları da tanı
|
||||
Pictures on those pages are recognized too; words already in the page's text are not added again Bu sayfalardaki resimler de tanınır; sayfanın metninde zaten bulunan sözcükler yeniden eklenmez
|
||||
Text recognition isn't installed: its model files are missing. Reinstall PdfCraft, or set PDFCRAFT_MODELS to the folder that holds them. Metin tanıma yüklü değil: model dosyaları eksik. PdfCraft'ı yeniden yükleyin veya PDFCRAFT_MODELS ortam değişkenini bunları içeren klasöre ayarlayın.
|
||||
Settings Ayarlar
|
||||
Optional İsteğe bağlı
|
||||
|
|
|
|||
|
Can't render this file because it has a wrong number of fields in line 7.
|
|
|
@ -1220,6 +1220,8 @@
|
|||
Searchable Image (Exact) Зображення з пошуком (точне)
|
||||
Adds invisible text over each word; the page image is not changed Додає невидимий текст поверх кожного слова; зображення сторінки не змінюється
|
||||
Downsample to Знизити роздільну здатність до
|
||||
Also recognize pages that already have text Розпізнавати й сторінки, на яких уже є текст
|
||||
Pictures on those pages are recognized too; words already in the page's text are not added again Зображення на цих сторінках теж розпізнаються; слова, які вже є в тексті сторінки, повторно не додаються
|
||||
Text recognition isn't installed: its model files are missing. Reinstall PdfCraft, or set PDFCRAFT_MODELS to the folder that holds them. Розпізнавання тексту не встановлено: бракує файлів моделей. Перевстановіть PdfCraft або задайте в PDFCRAFT_MODELS теку, у якій вони лежать.
|
||||
Settings Налаштування
|
||||
Optional Необов’язково
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
|
|
@ -928,6 +928,8 @@
|
|||
Searchable Image (Exact) 可搜索图像(精确)
|
||||
Adds invisible text over each word; the page image is not changed 在每个词上添加不可见文本;页面图像保持不变
|
||||
Downsample to 降采样至
|
||||
Also recognize pages that already have text 同时识别已有文本的页面
|
||||
Pictures on those pages are recognized too; words already in the page's text are not added again 这些页面上的图片也会被识别;页面文本中已有的单词不会重复添加
|
||||
Text recognition isn't installed: its model files are missing. Reinstall PdfCraft, or set PDFCRAFT_MODELS to the folder that holds them. 未安装文本识别:缺少模型文件。请重新安装 PdfCraft,或将 PDFCRAFT_MODELS 设置为包含这些文件的文件夹。
|
||||
Settings 设置
|
||||
Protect 保护
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 9 and column 16.
|
|
|
@ -876,6 +876,8 @@
|
|||
Searchable Image (Exact) 可搜尋圖片(精確)
|
||||
Adds invisible text over each word; the page image is not changed 在每個詞上新增不可見文字;頁面圖片保持不變
|
||||
Downsample to 降取樣至
|
||||
Also recognize pages that already have text 同時辨識已有文字的頁面
|
||||
Pictures on those pages are recognized too; words already in the page's text are not added again 這些頁面上的圖片也會被辨識;頁面文字中已有的字詞不會重複加入
|
||||
Text recognition isn't installed: its model files are missing. Reinstall PdfCraft, or set PDFCRAFT_MODELS to the folder that holds them. 未安裝文字辨識:缺少模型檔案。請重新安裝 PdfCraft,或將 PDFCRAFT_MODELS 設定為包含這些檔案的資料夾。
|
||||
Settings 設定
|
||||
Protect 保護
|
||||
|
|
|
|||
|
Can't render this file because it contains an unexpected character in line 9 and column 16.
|
|
|
@ -28,11 +28,20 @@ pub struct OcrDraft {
|
|||
pub language: String,
|
||||
/// "Downsample to" resolution.
|
||||
pub dpi: u32,
|
||||
/// Also recognize pages that already have text (pictures on them; their text isn't added again).
|
||||
pub text_pages: bool,
|
||||
}
|
||||
|
||||
impl Default for OcrDraft {
|
||||
fn default() -> Self {
|
||||
OcrDraft { pages: OcrPages::All, from: 1, to: 1, language: "en".into(), dpi: 300 }
|
||||
OcrDraft { pages: OcrPages::All, from: 1, to: 1, language: "en".into(), dpi: 300, text_pages: false }
|
||||
}
|
||||
}
|
||||
|
||||
impl OcrDraft {
|
||||
/// The recognition settings these choices make.
|
||||
pub fn settings(&self) -> OcrSettings {
|
||||
OcrSettings { dpi: self.dpi as f32, language: self.language.clone(), skip_text_pages: !self.text_pages }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -110,6 +119,9 @@ pub(crate) fn body(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tokens) -> (boo
|
|||
});
|
||||
ui.end_row();
|
||||
});
|
||||
ui.add_space(4.0);
|
||||
ui.checkbox(&mut d.text_pages, tl!("Also recognize pages that already have text"))
|
||||
.on_hover_text(tl!("Pictures on those pages are recognized too; words already in the page's text are not added again"));
|
||||
});
|
||||
if !available {
|
||||
ui.label(
|
||||
|
|
@ -152,7 +164,7 @@ impl PdfCraftApp {
|
|||
OcrPages::Current => vec![self.views[vi].current],
|
||||
OcrPages::Range => (d.from.saturating_sub(1)..d.to).collect(),
|
||||
};
|
||||
let settings = OcrSettings { dpi: d.dpi as f32, language: d.language.clone(), ..Default::default() };
|
||||
let settings = d.settings();
|
||||
let Some(job) = self.session.ocr_job(id, &pages, settings) else { return };
|
||||
let progress = Arc::new(Mutex::new(OcrProgress { total: job.pages.len(), ..Default::default() }));
|
||||
let p = progress.clone();
|
||||
|
|
@ -189,7 +201,7 @@ impl PdfCraftApp {
|
|||
return;
|
||||
}
|
||||
// The settings showing now, not when the folder arrives.
|
||||
let settings = OcrSettings { dpi: self.ocr_draft.dpi as f32, language: self.ocr_draft.language.clone(), ..Default::default() };
|
||||
let settings = self.ocr_draft.settings();
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
match self.export_dir_override.clone() {
|
||||
Some(d) => self.ocr_files_into(files, settings, d.into()),
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
use egui_kittest::Harness;
|
||||
use egui_kittest::kittest::Queryable;
|
||||
use pdfcraft_engine::{Session, export};
|
||||
use pdfcraft_engine::{AddedText, Edit, Session, export};
|
||||
use pdfcraft_ui_egui::PdfCraftApp;
|
||||
|
||||
/// A one-page PDF that is only a picture of a sentence.
|
||||
|
|
@ -65,3 +65,52 @@ fn recognize_text_in_multiple_files_writes_searchable_copies() {
|
|||
assert!(bytes.len() > scan().len(), "an incremental update was appended");
|
||||
}
|
||||
}
|
||||
|
||||
/// #1028: "Also recognize pages that already have text" reads a picture on a page that has its own
|
||||
/// text, without adding that text a second time.
|
||||
#[test]
|
||||
fn recognize_text_can_read_pictures_on_pages_with_text() {
|
||||
let hybrid = {
|
||||
let mut s = Session::new();
|
||||
let id = s.open("scan.pdf", None, std::sync::Arc::new(scan()), None).unwrap();
|
||||
let (w, h) = {
|
||||
let p = &s.get(id).unwrap().info.pages[0];
|
||||
(p.width as f64, p.height as f64)
|
||||
};
|
||||
let note =
|
||||
AddedText { rect: [w * 0.1, h * 0.75, w * 0.9, h * 0.85], text: "Invoice reference 4711".into(), size: 18.0, ..Default::default() };
|
||||
s.apply(id, Edit::AddText { page: 0, text: note }).unwrap();
|
||||
s.save_bytes(id).unwrap().to_vec()
|
||||
};
|
||||
let mut h = Harness::builder().with_size(egui::vec2(1400.0, 900.0)).build_eframe(|_cc| {
|
||||
let mut app = PdfCraftApp::new();
|
||||
app.set_option("language", "en").unwrap();
|
||||
app.open_bytes("hybrid.pdf", None, hybrid).unwrap();
|
||||
app.run_inline = true;
|
||||
app
|
||||
});
|
||||
h.run_steps(4);
|
||||
assert!(h.state_mut().execute("ocr.recognize"));
|
||||
h.run_steps(2);
|
||||
assert!(h.state().ocr_draft.settings().skip_text_pages, "pages with text are skipped by default");
|
||||
h.get_by_label("Also recognize pages that already have text").click();
|
||||
h.run_steps(2);
|
||||
assert!(!h.state().ocr_draft.settings().skip_text_pages);
|
||||
if !pdfcraft_engine::ocr::available() {
|
||||
eprintln!("skipped: OCR models not installed");
|
||||
return;
|
||||
}
|
||||
h.get_by_label("Recognize text").click();
|
||||
h.run_steps(3);
|
||||
assert!(h.state().ocr_run.is_none(), "finished");
|
||||
let toast = h.state().toast.clone().unwrap().0;
|
||||
assert!(toast.starts_with("Recognized ") && toast.ends_with("on 1 page"), "{toast}");
|
||||
let app = h.state();
|
||||
let id = app.active_ids().unwrap().1;
|
||||
let doc = app.session.get(id).unwrap();
|
||||
assert_eq!(doc.can_undo(), Some("Recognize text"));
|
||||
let mut r = pdfcraft_render::PageRenderer::new(doc.bytes.clone(), Default::default());
|
||||
let text = r.render(pdfcraft_render::RenderRequest { page: 0, kind: pdfcraft_render::RequestKind::Text, scale: 1.0, ..Default::default() });
|
||||
let text = text.text.unwrap().plain_text().to_lowercase();
|
||||
assert_eq!((text.matches("invoice").count(), text.matches("scanned").count()), (1, 1), "{text}");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
# PdfCraft roadmap: milestones and what's next
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** minor (M10, #1028: OCR reads pictures on pages that have text, on request; earlier: direct Organize overview and page actions; localized shortcut labels, #496; alpha gate added; convert-and-share re-judged with the cross-app rule → alpha) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS)
|
||||
> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-11 · **Change:** minor (M13/#971: bound per-page search match allocation; text cache byte budget and extraction warnings remain open) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** minor (#594 paragraph drags validated in the Linux test environment; localized shortcut labels, #496; alpha gate added; convert-and-share re-judged with the cross-app rule → alpha) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** minor (architectural paper controls from #599; M6: respect existing form-font encodings; retain source fonts on appearance fallback; estimates unchanged) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS)
|
||||
|
|
@ -88,7 +89,7 @@ total matches the parity estimate.
|
|||
| M7 | Content editing (text, images, header/footer, watermark) | 250–500 | 20% | 100–180 | Done: header & footer, watermarks, backgrounds, Bates; added text and images that stay editable (move, resize, format, rotate, flip, crop, replace); links (Link tool, Link Properties, create from URLs, remove all). Missing: editing existing text and images in place (the longest pole), image/PDF watermarks |
|
||||
| M8 | Security + redaction | 100–180 | 68% | 15–30 | Done: opening protected documents, permissions, Protect Using Password, Remove security; redaction (mark text/areas/pages, Search & Redact with patterns, apply removing glyphs, image pixels, vectors, XObject content, annotations and fields, verification, full rewrite on save); Remove hidden information and Sanitize.<br>Done since: redaction codes (U.S. FOIA and Privacy Act sets as overlay text, in Redaction properties and `redact_mark`). Missing: certificate security, custom redaction code sets and pattern locales, DCT re-encoding |
|
||||
| M9 | Signatures (PAdES, validation) | 160–280 | 60% | 40–70 | Done: new `sign` crate (DER, X.509, CMS, PKCS #12 on RustCrypto; aws-lc-rs for RSA private keys); PAdES B-B signing (visible, invisible, existing fields, certification with DocMDP); validation with trust store and changes-after-signing classification; self-signed digital IDs; Signatures panel, message bar, sign dialogs; agent tools. Checked with pdfsig and OpenSSL.<br>Done since: macOS Keychain and Windows Current User Personal store signing (software-backed CNG keys), certificate viewer, signed documents protected from rewrites. Missing: timestamps (B-T), LTV (DSS, OCSP, CRL), FieldMDP, smart cards, PKCS #11 |
|
||||
| M10 | OCR, create, export, print | 200–350 | 33% | 120–215 | Done: create from blank/text/PNG/JPEG/TIFF (multi-page)/GIF/BMP; export PNG/JPEG/TIFF and text; Print (Acrobat's sizing, n-up, booklet, poster, comments & forms, preview, the CUPS and Windows spoolers, print-ready PDF).<br>Done since: embedded/72/custom DPI choices for image imports; export all images; OCR (searchable image for pages, ranges and multiple files); single-sided cut-and-stack imposition with cut marks, through Print and doc_print; architectural/ANSI/large ISO sheet presets, Match page at 100% and PDF-scale preview (#599 follow-up); Create PDF from multiple files (PDFs, images and text, edited as pages in the grid). Missing: OCR languages beyond Latin, editable-text OCR output, Office export/import, web printing |
|
||||
| M10 | OCR, create, export, print | 200–350 | 33% | 120–215 | Done: create from blank/text/PNG/JPEG/TIFF (multi-page)/GIF/BMP; export PNG/JPEG/TIFF and text; Print (Acrobat's sizing, n-up, booklet, poster, comments & forms, preview, the CUPS and Windows spoolers, print-ready PDF).<br>Done since: embedded/72/custom DPI choices for image imports; export all images; OCR (searchable image for pages, ranges and multiple files); single-sided cut-and-stack imposition with cut marks, through Print and doc_print; architectural/ANSI/large ISO sheet presets, Match page at 100% and PDF-scale preview (#599 follow-up); Create PDF from multiple files (PDFs, images and text, edited as pages in the grid); OCR can also read pictures on pages that have text, without adding that text twice (#1028). Missing: OCR languages beyond Latin, editable-text OCR output, Office export/import, web printing |
|
||||
| M11 | Optimize, preflight, PDF/A/X/UA, print production | 200–350 | 18% | 80–140 | Done: new `optimize` crate: Reduce File Size and the PDF Optimizer (images measured where drawn, bicubic downsampling, JPEG/ZIP recompression only when smaller, discard objects and user data, Flate clean-up, resource merging, object streams). Missing: fonts and transparency panels, space audit, preflight, PDF/A/X/UA |
|
||||
| M12 | Accessibility, compare, measure, search, XFA | 200–380 | 30% | 90–160 | Done: new `a11y` crate with the Accessibility Checker (all 32 rules, report, Fix/Skip/Explain, options dialog and results panel, agent tools).<br>Done since: 2D distance, perimeter and area measurements with persistent viewport calibration, snapping, live information and CSV export. Missing: autotag, Tags/Order/Content panels, Reading Order tool, alt-text workflow, compare, geospatial/3D measurement, search index, XFA |
|
||||
| M13 | Automation (MCP, Action Wizard, CLI) + AI providers | 60–120 | 45% | 25–45 | Done: MCP resources (document info, text, page images); headless tool table (123 tools incl. signing, optimizing, initial view, links, stamps, data exchange, comment review, forms authoring and scripts, redaction, sanitize, print, add content), opt-in MCP server over stdio, CLI `run`/`tools` (closed stdout pipes exit cleanly), UI control channel with drag. Missing: Action Wizard, AI providers<br>Done since: CLI run and MCP reject missing, empty, repeated and option-valued --root arguments before any tool/script execution or MCP initialization. A single-hyphen directory name remains valid; synthetic CLI regressions preserve a would-be overwritten file. `text_find` also caps the per-page match ranges it materializes at the remaining result limit plus one continuation match. |
|
||||
|
|
|
|||
|
|
@ -6427,7 +6427,7 @@ status = "shipped"
|
|||
commands = ["ocr.recognize"]
|
||||
tools = ["ocr_recognize", "ocr_status"]
|
||||
evidence = ["crates/engine/src/tests.rs::recognize_text_makes_a_scanned_page_searchable", "crates/automation/tests/automation.rs::ocr_tools_make_a_scan_searchable", "crates/ui-egui/tests/ocr.rs::recognize_text_dialog_adds_searchable_text"]
|
||||
notes = "ocrs engine (MIT/Apache) with CC-BY-SA models fetched by `cargo xtask models`; the desktop release packages ship them (#103; packaging/env.sh stage_models), the web build does not yet; runs on a worker thread with progress; one undoable step; pages with text are skipped."
|
||||
notes = "ocrs engine (MIT/Apache) with CC-BY-SA models fetched by `cargo xtask models`; the desktop release packages ship them (#103; packaging/env.sh stage_models), the web build does not yet; runs on a worker thread with progress; one undoable step; pages with text are skipped unless asked (ocr.skip-renderable-text)."
|
||||
|
||||
[[feature]]
|
||||
id = "ocr.recognize-range"
|
||||
|
|
@ -6522,8 +6522,8 @@ milestone = "M10"
|
|||
status = "shipped"
|
||||
commands = ["ocr.recognize"]
|
||||
tools = ["ocr_recognize"]
|
||||
evidence = ["crates/engine/src/tests.rs::recognize_text_makes_a_scanned_page_searchable", "crates/automation/tests/automation.rs::ocr_tools_make_a_scan_searchable"]
|
||||
notes = "Pages that already have text are skipped and reported (skip_text_pages false reads them anyway)."
|
||||
evidence = ["crates/engine/src/tests.rs::recognize_text_makes_a_scanned_page_searchable", "crates/automation/tests/automation.rs::ocr_tools_make_a_scan_searchable", "crates/engine/src/tests.rs::recognize_text_reads_a_picture_on_a_page_that_has_text", "crates/automation/tests/automation.rs::ocr_recognize_reads_pictures_on_pages_with_text", "crates/ui-egui/tests/ocr.rs::recognize_text_can_read_pictures_on_pages_with_text"]
|
||||
notes = "Pages that already have text are skipped and reported by default. The dialog's \"Also recognize pages that already have text\" (skip_text_pages false, also for multiple files) reads pictures on such pages (#1028); a recognised word at least half covered by the page's own text (an earlier OCR layer included) is left out and counted (already_on_page). Missing: recognising only the image areas (the whole page is still read, so it takes as long as a scan), and a word in a picture that sits under real text is treated as that text."
|
||||
|
||||
[[feature]]
|
||||
id = "ocr.rotation-detection"
|
||||
|
|
|
|||
Loading…
Reference in New Issue