Commit Graph
4368 Commits
Author SHA1 Message Date
James R. Barlow 476ec12383 Merge remote-tracking branches 'origin/dependabot/uv/starlette-1.3.1', 'origin/dependabot/uv/tornado-6.5.7', 'origin/dependabot/uv/cryptography-48.0.1' and 'origin/dependabot/uv/python-multipart-0.0.31' 2026-06-17 11:30:35 -07:00
dependabot[bot]andGitHub e99177ada7 build(deps): bump starlette from 1.1.0 to 1.3.1
Bumps [starlette](https://github.com/Kludex/starlette) from 1.1.0 to 1.3.1.
- [Release notes](https://github.com/Kludex/starlette/releases)
- [Changelog](https://github.com/Kludex/starlette/blob/main/docs/release-notes.md)
- [Commits](https://github.com/Kludex/starlette/compare/1.1.0...1.3.1)

---
updated-dependencies:
- dependency-name: starlette
  dependency-version: 1.3.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-17 09:45:21 +00:00
dependabot[bot]andGitHub e95ec9c497 build(deps): bump tornado from 6.5.5 to 6.5.7
Bumps [tornado](https://github.com/tornadoweb/tornado) from 6.5.5 to 6.5.7.
- [Changelog](https://github.com/tornadoweb/tornado/blob/master/docs/releases.rst)
- [Commits](https://github.com/tornadoweb/tornado/compare/v6.5.5...v6.5.7)

---
updated-dependencies:
- dependency-name: tornado
  dependency-version: 6.5.7
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-17 09:45:20 +00:00
dependabot[bot]andGitHub 82f30bfbec build(deps): bump cryptography from 48.0.0 to 48.0.1
Bumps [cryptography](https://github.com/pyca/cryptography) from 48.0.0 to 48.0.1.
- [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pyca/cryptography/compare/48.0.0...48.0.1)

---
updated-dependencies:
- dependency-name: cryptography
  dependency-version: 48.0.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-17 09:45:06 +00:00
dependabot[bot]andGitHub d1437e6bbc build(deps): bump python-multipart from 0.0.29 to 0.0.31
Bumps [python-multipart](https://github.com/Kludex/python-multipart) from 0.0.29 to 0.0.31.
- [Release notes](https://github.com/Kludex/python-multipart/releases)
- [Changelog](https://github.com/Kludex/python-multipart/blob/main/CHANGELOG.md)
- [Commits](https://github.com/Kludex/python-multipart/compare/0.0.29...0.0.31)

---
updated-dependencies:
- dependency-name: python-multipart
  dependency-version: 0.0.31
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-17 00:16:38 +00:00
dependabot[bot]andGitHub c669d30642 build(deps): bump sigstore/gh-action-sigstore-python from 3.3.0 to 3.4.0 (#1701) 2026-06-15 07:17:34 -07:00
James R. Barlow 3613b30ca8 Bump version: v17.6.0 v17.6.0 2026-06-11 22:37:37 -07:00
James R. Barlow 0d4c3bcdcf feat: add --mode strip to remove the OCR text layer without rasterizing
Adds a processing mode that removes the invisible (render mode 3) OCR text
layer in place. Unlike `--ocr-engine none --force-ocr`, it does not
rasterize the page, so images and visible content are preserved unchanged
and the output is smaller rather than larger. Options that require
rasterization or OCR (--deskew, --clean, --sidecar, etc.) are rejected.

Only invisible text is removed; text drawn as visible glyphs under an
opaque image (some OCR engines, and OCRmyPDF v2.2 and earlier) cannot be
removed this way, as documented.

Closes #1435.
2026-06-11 12:34:03 -07:00
James R. Barlow 8a8d515933 feat: surface raw Tesseract diacritics message at debug level
When Tesseract reports a page with many diacritics, OCRmyPDF rewrites the
message to "lots of diacritics - possibly poor OCR", which hid the
original wording. Keep the interpreted hint but also emit Tesseract's raw
line at debug verbosity (-v 1) so users can see exactly what Tesseract
reported.

Closes #1566.
2026-06-11 00:21:39 -07:00
James R. Barlow 11de13ecfe feat: anti-alias Ghostscript rasterization to improve OCR quality
Ghostscript 10.x renders aliased glyphs that OCR frequently misreads as
extra word breaks or substituted characters. Enable text and graphics
anti-aliasing (-dTextAlphaBits=4 -dGraphicsAlphaBits=4) for the contone
raster devices, which empirically improves OCR accuracy on the
Ghostscript path, especially for small fonts at moderate DPI. The 1-bit
mono devices are excluded, since older Ghostscript rejects alpha bits on
them and pngmonod performs its own anti-aliased downscaling.

Also log which rasterizer rendered each page at debug verbosity and
clarify the --rasterizer help text, so quality reports are easier to
diagnose. The default rasterizer (auto) already prefers pypdfium2, which
anti-aliases; this primarily benefits --rasterizer ghostscript and
installs without pypdfium2.

Closes #1439.
2026-06-10 23:39:03 -07:00
James R. Barlow 58642d8411 fix: report un-optimizable images as a warning, not a traceback
The optimizer is best-effort: any image it cannot process is left
unchanged in the output, which remains valid. Previously, an extraction
failure (e.g. an exotic colorspace pikepdf cannot transcode) was logged
with log.exception, printing a full traceback at ERROR level that alarmed
users even though nothing was wrong with the output (issue #846).

Trap such failures with a concise warning that the image was left
unchanged and the output is still valid, and demote the traceback to
debug verbosity for diagnosis.
2026-06-10 13:38:05 -07:00
James R. Barlow 7e42d3c771 feat: make pdfa-image-compression=auto lossless at -O0
Ghostscript's `auto` image compression heuristic can transcode lossless
images to JPEG during PDF/A generation, which is surprising at
optimization levels that otherwise promise lossless-only operations
(issue #1124).

`--pdfa-image-compression=auto` (the default) now coerces to lossless at
-O0 so Ghostscript will not transcode lossless images to JPEG. -O1 and
above continue to defer to Ghostscript's heuristic; -O1 (the default
level) is kept as a historical exception because coercing it to lossless
substantially bloats output. Users wanting guaranteed lossless image
handling can pass --pdfa-image-compression=lossless or use -O0.

Also make `lossless` pass existing JPEGs through unchanged
(-dPassThroughJPEGImages=true) instead of re-encoding them with a
lossless codec, which only inflates already-lossy data.
2026-06-10 13:10:43 -07:00
James R. Barlow 5cb5d7a682 Merge remote-tracking branch 'origin/dependabot/github_actions/codecov/codecov-action-7' 2026-06-09 01:12:35 -07:00
James R. Barlow 37e71dece6 Merge branch 'feature/page-box-repair' 2026-06-09 01:10:58 -07:00
James R. Barlow df84945773 feat: validate and repair malformed page boxes
Validate and repair the page-boundary boxes (MediaBox, CropBox, TrimBox,
ArtBox, BleedBox) of input PDFs in triage(), following PDF 2.0:

- Coerce coordinates written in invalid exponential notation, stored by
  qpdf/pikepdf as strings (#1398).
- Normalize rectangles whose corners are given in reversed order, which
  previously crashed with NegativeDimensionError (#1526).
- Clamp a crop/trim/art/bleed box that extends outside the MediaBox to
  their intersection, or discard it when the intersection is empty, which
  previously produced a zero-height effective page some viewers rejected
  (#1400).

When a box is discarded, clamped, or reinterpreted, a warning recommends
visual inspection of the output. pdfinfo box reading shares the same
coercion helper so PdfInfo no longer crashes on malformed boxes.

Supersedes PR #1691. Thanks @ajdlinux.

Closes #1398, #1526, #1400
2026-06-09 01:10:26 -07:00
James R. Barlow b5a6a9f9f1 feat: discard stale structure tree when re-OCRing tagged PDFs
A tagged/structured PDF carries a logical structure tree
(/Root/StructTreeRoot, /MarkInfo) that maps marked page content to
semantic elements via MCIDs. When --force-ocr rasterizes pages or
--redo-ocr rewrites the text layer, those MCIDs are destroyed or
renumbered and the tree is left dangling. We cannot rebuild it to match
the new text, so discard it for force/redo modes, following the same
pattern as the thumbnail and search-index discards. --skip-text leaves
text pages untouched, so their structure is preserved.

Also broaden the default-mode "looks born-digital" stop signal to fire
on /StructTreeRoot, not just /MarkInfo/Marked, so structure-tree-only
PDFs are no longer silently OCR'd. The existing --tagged-pdf-mode ignore
escape hatch is unchanged.
2026-06-08 15:47:53 -07:00
dependabot[bot]andGitHub ed36aefe48 Bump codecov/codecov-action from 6 to 7
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 6 to 7.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v6...v7)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-08 10:42:38 +00:00
James R. Barlow 32013f4294 Merge branch 'feature/discard-obsolete-pdf-features' 2026-06-07 02:09:00 -07:00
James R. Barlow 8f2bcc2c64 feat: discard stale embedded page thumbnails when rewriting PDF
A page may carry an optional /Thumb image XObject (ISO 32000-2 12.3.4),
a miniature rendering used only as a navigation aid. OCRmyPDF alters
page appearance (deskew, clean, rasterize, re-render) and plugins may
edit pages arbitrarily, so a retained thumbnail would be stale and no
longer match its page. Modern viewers generate thumbnails on demand, so
there is no loss of functionality.

OcrGrafter.finalize() now strips /Thumb from every page before saving,
alongside the existing search-index discard, covering both the OCR and
hOCR pipelines. Orphaned thumbnail XObjects are garbage-collected on save.
2026-06-07 00:30:34 -07:00
James R. Barlow 015b53ae30 feat: discard stale embedded text search index when rewriting PDF
Adobe Acrobat can embed a proprietary full-text search index in the
document catalog at /Root/PieceInfo/SearchIndex. Only Acrobat reads it;
other viewers ignore it and search the text on the fly. Any change to a
PDF invalidates the index, so once OCRmyPDF rewrites the document the
retained index is stale and returns incorrect search results in Acrobat.

OcrGrafter.finalize() now discards it before saving (covering both the
OCR and hOCR pipelines), preserving any other PieceInfo owner data and
dropping an empty PieceInfo. Modern viewers rebuild a search index on
demand, so there is no loss of search capability.
2026-06-07 00:02:28 -07:00
James R. Barlow 164cf2dc8a test: use bundled font in gray-mask test for macOS/Windows portability
test_gray_mask_ocrs_to_text loaded DejaVu, which only exists on the Linux
CI runners; the OSError fallback hardcoded a Linux-only path, so the test
failed with "cannot open resource" on macOS and Windows. Load the bundled
ocrmypdf.data/NotoSans-Regular.ttf via importlib.resources instead, which
is guaranteed present on every platform.
2026-06-05 12:48:33 -07:00
James R. Barlow 98d6d02704 Merge branch 'fix/1688-mask-fill-color-device'
Promote rasterization device based on image-mask fill color so gray/colored
stencil text is not destroyed by 1-bit dithering before OCR; default the
1-bit Ghostscript device to pngmonod. Fixes #1688.
2026-06-05 11:41:18 -07:00
James R. Barlow 5efb98931d fix: inherit fill color into Form XObjects; reset fill color on cs (#1688)
Address final review findings: a mask painted inside a Form XObject now
inherits the fill color in effect at the form's Do operator (previously it
reset to black, missing gray/color promotion one indirection deep). The cs
operator now resets the fill color to black per PDF spec, so a stale color
set before cs cannot leak to a subsequently drawn mask.
2026-06-05 11:40:50 -07:00
James R. Barlow 2f4e47213f style: ruff format operator whitelist line (#1688) 2026-06-05 11:40:50 -07:00
James R. Barlow 94c8123bd7 docs: release note for image mask fill-color device promotion (#1688) 2026-06-05 11:40:50 -07:00
James R. Barlow 0db130e1c3 test: end-to-end gray image-mask OCR across rasterizers (#1688) 2026-06-05 11:40:50 -07:00
James R. Barlow 91b6a818f5 feat: promote raster device for color/gray image masks; default pngmonod (#1688) 2026-06-05 11:40:49 -07:00
James R. Barlow 6bc9499e68 feat: recognize pngmonod device in pypdfium rasterizer (#1688) 2026-06-05 11:40:49 -07:00
James R. Barlow 09f2d6c386 feat: add pngmonod raster device to enum (#1688) 2026-06-05 11:40:49 -07:00
James R. Barlow 87f918f58c feat: expose fill-color ink classification on ImageInfo (#1688) 2026-06-05 11:40:49 -07:00
James R. Barlow 80e77fb021 feat: track image mask fill color during content stream interpretation (#1688)
Track the current PDF fill color on the graphics stack alongside the CTM and
record an Ink classification (mono/gray/color) per image-draw event. Image
masks are painted with the current fill color, so this enables later device
promotion. Color operators are tolerant of malformed operands to preserve
robustness on untrusted input.
2026-06-05 11:40:49 -07:00
James R. Barlow fa9c5b3fae feat: add fill color -> Ink classification helper (#1688) 2026-06-05 11:40:49 -07:00
James R. Barlow 3d17a60a54 feat: add Ink classification type for image mask fill colors (#1688) 2026-06-05 11:40:49 -07:00
jbarlowandGitHub c33f073d4f Improve DeviceN color conversion guidance (#1623) (#1694)
When Ghostscript reports a DeviceN colorspace with an inappropriate
alternate, the resulting PDF/A may render blank in viewers such as Adobe
Reader (#1187). The error is gated on that Ghostscript warning, which is
the authoritative signal that the *output* is broken.

Previously the error message always told the user to "use
--color-conversion-strategy", which is confusing when they already set
one and it didn't help. Crucially, the warning persists for strategies
that don't actually normalize the colorspace -- notably
UseDeviceIndependentColor (confirmed in #1187) -- so silencing the error
for any non-default strategy would emit a silently-broken PDF/A.

Keep raising whenever Ghostscript still reports the warning, regardless
of strategy, but tailor the guidance: if no conversion was requested,
suggest RGB/CMYK/Gray; if a conversion was requested but the warning
persisted, say so and point at strategies that work or --output-type pdf.

Add unit tests (mocked Ghostscript) covering the default case, the
warning-persists-despite-strategy case for both an ineffective strategy
and a normally-effective one, and the no-warning happy path.
2026-06-04 14:48:24 -07:00
James R. Barlow 5d7b5742e4 Bump version: v17.5.0 v17.5.0 2026-05-27 13:36:30 -07:00
James R. Barlow c391b2b7d0 Draft release notes for v17.5.0 2026-05-27 13:35:45 -07:00
James R. Barlow 0250929150 Update uv.lock 2026-05-26 13:11:12 -07:00
James R. Barlow 9748208e68 Support 'end' alias for last page in --pages
Closes #1615. The token 'end' (case-insensitive) is now accepted as an
alias for the document's last page, e.g. --pages 3-end. Resolution is
deferred until the page count is known from the input PDF.
2026-05-26 12:18:09 -07:00
James R. Barlow e4b0c04be4 Fix pypdfium2 MediaBox rendering when CropBox is smaller
PDFium does not support negative crop values to expand the render
area beyond the CropBox: such values only pad the output canvas with
white, leaving content outside the CropBox clipped. Set the in-memory
CropBox to the MediaBox before rendering instead. Reported in #1685.
2026-05-25 23:17:17 -07:00
James R. Barlow efb83ad64f Add --ghostscript-jpeg-quality and --ghostscript-jpeg-maxdpi
Expose Ghostscript's -dJPEGQ and image downsampling switches as
advanced, plugin-scoped options for tuning PDF/A output, without
polluting the central OcrOptions registry. The optimizer's existing
--jpeg-quality remains the recommended JPEG quality control.

- GhostscriptOptions gains jpeg_quality and jpeg_maxdpi fields and CLI
  args (advanced help text). jpeg_quality=0 is honored as Ghostscript's
  maximum compression rather than being silently coerced to the default.
- _exec.ghostscript.generate_pdfa() forwards both values; when
  jpeg_maxdpi is set, downsample threshold is pinned at 1.0.
- _get_plugin_options falls back to extra_attrs for namespaced fields
  so plugins can own their options without registering them centrally.
- Documentation explains the rationale: Ghostscript is the legacy path
  (pypdfium + verapdf is preferred in v17+), the optimizer is the
  supported file-size lever, and lowering quality is almost always a
  better trade than downsampling.
2026-05-25 10:20:54 -07:00
James R. Barlow 08e40f96e8 Surface Tesseract config errors instead of FileNotFoundError
When Tesseract cannot find its 'hocr' or 'txt' config files in the
tessdata configs/ directory, it prints "read_params_file: Can't open"
warnings, exits 0, and produces no output. OCRmyPDF then crashed with a
confusing FileNotFoundError on the missing hOCR file (issue #1687).

Promote read_params_file warnings to TesseractConfigError with guidance
on the likely cause, and verify the expected output file exists after
Tesseract claims success as defense-in-depth for other silent-failure
modes.
2026-05-25 01:45:45 -07:00
James R. Barlow 3f6feb1dcc Merge branch 'main' of github.com:ocrmypdf/OCRmyPDF 2026-05-25 01:38:46 -07:00
jbarlowandGitHub ab6553f4ff Merge pull request #1677 from ocrmypdf/dependabot/uv/gitpython-3.1.50
Bump gitpython from 3.1.47 to 3.1.50
2026-05-25 01:36:02 -07:00
jbarlowandGitHub cedca9fa1f Merge pull request #1679 from ocrmypdf/dependabot/uv/urllib3-2.7.0
Bump urllib3 from 2.6.3 to 2.7.0
2026-05-25 01:35:45 -07:00
jbarlowandGitHub 3f40118022 Merge pull request #1686 from ocrmypdf/dependabot/uv/idna-3.15
Bump idna from 3.11 to 3.15
2026-05-25 01:35:31 -07:00
dependabot[bot]andGitHub b18b1da6d0 Bump idna from 3.11 to 3.15
Bumps [idna](https://github.com/kjd/idna) from 3.11 to 3.15.
- [Release notes](https://github.com/kjd/idna/releases)
- [Changelog](https://github.com/kjd/idna/blob/master/HISTORY.md)
- [Commits](https://github.com/kjd/idna/compare/v3.11...v3.15)

---
updated-dependencies:
- dependency-name: idna
  dependency-version: '3.15'
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-19 21:26:42 +00:00
James R. Barlow 14fb9f56e8 Add explanatory note about Ghostscript -dJPEG=95 2026-05-16 12:18:34 -07:00
James R. Barlow 8709cf506b Update uv.lock 2026-05-12 10:14:47 -07:00
jbarlowandGitHub 9a92eb40df Merge pull request #1680 from cislunarspace/docs/refresh-chinese-readme 2026-05-12 15:52:47 +02:00
ouyangjiahong 0a59c210f9 docs: refresh Chinese README translation
Align the Chinese README with the current English version and remove stale generated wrapper text.
2026-05-12 09:17:44 +08:00