Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4e974ebd46 | ||
|
|
6f2b8408c1 | ||
|
|
1dba941261 | ||
|
|
ef76625abb | ||
|
|
57bb554a70 | ||
|
|
5b9d6f979e | ||
|
|
b588e3bfd7 |
@@ -3,6 +3,20 @@
|
||||
|
||||
# v17
|
||||
|
||||
## v17.4.0
|
||||
|
||||
- Added ``--no-overwrite`` / ``-n`` option to prevent overwriting output files.
|
||||
If the destination file already exists, OCRmyPDF exits with code 5
|
||||
(``OutputFileAccessError``). {issue}`1642`
|
||||
- Fixed text layer stretching in the fpdf2 renderer for widely-spaced words.
|
||||
The horizontal scaling (Tz) was incorrectly stretched to fill inter-word gaps
|
||||
instead of relying on Td positioning, causing text selection to highlight far
|
||||
beyond the actual word boundaries. {issue}`1635`
|
||||
- Fixed ``optimize=2`` or ``optimize=3`` crash when using the Python API without
|
||||
explicitly setting ``jpg_quality`` or ``png_quality``. {issue}`1641`
|
||||
- Fixed ``verapdf`` availability check crashing with ``NotADirectoryError`` on
|
||||
some platforms. {issue}`1638`
|
||||
|
||||
## v17.3.0
|
||||
|
||||
- Fixed Python API ignoring the ``language`` parameter, always defaulting to
|
||||
|
||||
+7
-2
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "ocrmypdf"
|
||||
version = "17.3.0"
|
||||
version = "17.4.0"
|
||||
description = "OCRmyPDF adds an OCR text layer to scanned PDF files, allowing them to be searched"
|
||||
readme = "README.md"
|
||||
license = "MPL-2.0"
|
||||
@@ -156,7 +156,12 @@ quote-style = "preserve"
|
||||
|
||||
[dependency-groups]
|
||||
# Developer-only tools - use `uv sync --group <name>`
|
||||
dev = ["mypy>=1.13.0", "ipykernel>=6.29.5", "reportlab>=4.4.4"]
|
||||
dev = [
|
||||
"mypy>=1.13.0",
|
||||
"ipykernel>=6.29.5",
|
||||
"reportlab>=4.4.4",
|
||||
"cyclopts>=4.5.1",
|
||||
]
|
||||
test = [
|
||||
# Core testing framework
|
||||
"coverage[toml]>=6.2",
|
||||
|
||||
@@ -36,7 +36,7 @@ def available() -> bool:
|
||||
"""Check if verapdf is available."""
|
||||
try:
|
||||
version()
|
||||
except MissingDependencyError:
|
||||
except (MissingDependencyError, OSError):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
@@ -188,6 +188,9 @@ class OcrOptions(BaseModel):
|
||||
"""Compatibility alias for jpg_quality."""
|
||||
self.jpg_quality = value
|
||||
|
||||
# Output behavior
|
||||
no_overwrite: bool = False
|
||||
|
||||
# Advanced options
|
||||
max_image_mpixels: float = 250.0
|
||||
pdf_renderer: str = 'auto'
|
||||
|
||||
@@ -217,6 +217,17 @@ def check_requested_output_file(options: OcrOptions) -> None:
|
||||
f"Output file location ({options.output_file}) is not a writable file."
|
||||
)
|
||||
|
||||
if (
|
||||
options.no_overwrite
|
||||
and not hasattr(options.output_file, 'writable')
|
||||
and options.output_file != '-'
|
||||
and Path(str(options.output_file)).exists()
|
||||
):
|
||||
raise OutputFileAccessError(
|
||||
f"Output file already exists: {options.output_file}\n"
|
||||
"To overwrite it, omit the --no-overwrite / -n option."
|
||||
)
|
||||
|
||||
|
||||
def report_output_file_size(
|
||||
options: OcrOptions,
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# SPDX-FileCopyrightText: 2022 James R. Barlow
|
||||
# SPDX-License-Identifier: MPL-2.0
|
||||
__version__ = "17.3.0"
|
||||
__version__ = "17.4.0"
|
||||
|
||||
@@ -440,6 +440,7 @@ def ocr(
|
||||
continue_on_soft_render_error: bool | None = None,
|
||||
invalidate_digital_signatures: bool | None = None,
|
||||
tagged_pdf_mode: str | None = None,
|
||||
no_overwrite: bool | None = None,
|
||||
plugins: Iterable[Path | str] | None = None,
|
||||
plugin_manager: OcrmypdfPluginManager | None = None,
|
||||
keep_temporary_files: bool | None = None,
|
||||
@@ -502,6 +503,7 @@ def ocr( # noqa: D417
|
||||
continue_on_soft_render_error: bool | None = None,
|
||||
invalidate_digital_signatures: bool | None = None,
|
||||
tagged_pdf_mode: str | None = None,
|
||||
no_overwrite: bool | None = None,
|
||||
plugins: Iterable[Path | str] | None = None,
|
||||
plugin_manager: OcrmypdfPluginManager | None = None,
|
||||
keep_temporary_files: bool | None = None,
|
||||
|
||||
+12
-2
@@ -137,8 +137,9 @@ Online documentation is located at:
|
||||
'output_file',
|
||||
metavar="output_pdf",
|
||||
help="Output searchable PDF file (or '-' to write to standard output). "
|
||||
"Existing files will be overwritten. If same as input file, the "
|
||||
"input file will be updated only if processing is successful.",
|
||||
"Existing files will be overwritten (use --no-overwrite to prevent this). "
|
||||
"If same as input file, the input file will be updated only if "
|
||||
"processing is successful.",
|
||||
)
|
||||
parser.add_argument(
|
||||
'-l',
|
||||
@@ -190,6 +191,15 @@ Online documentation is located at:
|
||||
"may not both use stdout at the same time.",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'-n',
|
||||
'--no-overwrite',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help="If the output file already exists, exit with an error instead of "
|
||||
"overwriting it.",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--version',
|
||||
action='version',
|
||||
|
||||
@@ -677,12 +677,12 @@ class Fpdf2PdfRenderer:
|
||||
dy_pdf = -(py_curr_f - py_prev_f)
|
||||
ops.append(f'{dx_pdf:.2f} {dy_pdf:.2f} Td')
|
||||
|
||||
# Determine text to render and compute Tz
|
||||
# Determine text to render
|
||||
if not is_last:
|
||||
next_text, next_x_baseline, _, _ = word_render_data[i + 1]
|
||||
advance = next_x_baseline - x_baseline
|
||||
|
||||
# Add trailing space unless both words are CJK-only
|
||||
# Add trailing space for text extraction unless both are CJK
|
||||
if (
|
||||
advance > 0
|
||||
and not (
|
||||
@@ -691,18 +691,14 @@ class Fpdf2PdfRenderer:
|
||||
)
|
||||
):
|
||||
text_to_render = text + ' '
|
||||
natural_w = pdf.get_string_width(text_to_render)
|
||||
render_tz = (
|
||||
(advance / natural_w) * 100
|
||||
if natural_w > 0
|
||||
else word_tz
|
||||
)
|
||||
else:
|
||||
text_to_render = text
|
||||
render_tz = word_tz
|
||||
else:
|
||||
text_to_render = text
|
||||
render_tz = word_tz
|
||||
|
||||
# Use word_tz (fits word into its hOCR bbox) — Td handles
|
||||
# inter-word gaps, so Tz should not stretch to fill them.
|
||||
render_tz = word_tz
|
||||
|
||||
ops.append(f'{render_tz:.2f} Tz')
|
||||
ops.append(self._encode_shaped_text(pdf, text_to_render))
|
||||
|
||||
@@ -681,9 +681,9 @@ def optimize(
|
||||
safe_symlink(input_file, output_file)
|
||||
return output_file
|
||||
|
||||
if options.jpg_quality == 0:
|
||||
if not options.jpg_quality:
|
||||
options.jpg_quality = DEFAULT_JPEG_QUALITY if options.optimize < 3 else 40
|
||||
if options.png_quality == 0:
|
||||
if not options.png_quality:
|
||||
options.png_quality = DEFAULT_PNG_QUALITY if options.optimize < 3 else 30
|
||||
|
||||
with Pdf.open(input_file) as pdf:
|
||||
|
||||
@@ -1283,7 +1283,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "ocrmypdf"
|
||||
version = "17.2.0"
|
||||
version = "17.3.0"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "deprecation" },
|
||||
@@ -1313,6 +1313,7 @@ webservice = [
|
||||
|
||||
[package.dev-dependencies]
|
||||
dev = [
|
||||
{ name = "cyclopts" },
|
||||
{ name = "ipykernel" },
|
||||
{ name = "mypy" },
|
||||
{ name = "reportlab" },
|
||||
@@ -1367,6 +1368,7 @@ provides-extras = ["watcher", "webservice"]
|
||||
|
||||
[package.metadata.requires-dev]
|
||||
dev = [
|
||||
{ name = "cyclopts", specifier = ">=4.5.1" },
|
||||
{ name = "ipykernel", specifier = ">=6.29.5" },
|
||||
{ name = "mypy", specifier = ">=1.13.0" },
|
||||
{ name = "reportlab", specifier = ">=4.4.4" },
|
||||
|
||||
Reference in New Issue
Block a user