Lossy JBIG2 has been removed due to well-documented risks of character substitution errors (e.g., 6/8 confusion). The --jbig2-lossy and --jbig2-page-group-size arguments are now deprecated and ignored with a warning. Changes: - Remove jbig2_lossy and jbig2_page_group_size from OCROptions - Simplify optimize.py to use single-image JBIG2 encoding only (no symbol dictionaries/JBIG2Globals) - Remove convert_group() from jbig2enc.py - Deprecate CLI args with warnings for backward compatibility - Update documentation to explain lossless-only JBIG2
135 lines
5.1 KiB
Python
135 lines
5.1 KiB
Python
# SPDX-FileCopyrightText: 2024 James R. Barlow
|
|
# SPDX-License-Identifier: MPL-2.0
|
|
|
|
"""Validation coordinator for plugin options and cross-cutting concerns."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
import os
|
|
from typing import TYPE_CHECKING
|
|
|
|
if TYPE_CHECKING:
|
|
import pluggy
|
|
|
|
from ocrmypdf._options import OCROptions
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
class ValidationCoordinator:
|
|
"""Coordinates validation across plugin models and core options."""
|
|
|
|
def __init__(self, plugin_manager: pluggy.PluginManager):
|
|
self.plugin_manager = plugin_manager
|
|
self.registry = getattr(plugin_manager, '_option_registry', None)
|
|
|
|
def validate_all_options(self, options: OCROptions) -> None:
|
|
"""Run comprehensive validation on all options.
|
|
|
|
This runs validation in the correct order:
|
|
1. Plugin self-validation (already done by Pydantic)
|
|
2. Plugin context validation (requires external context)
|
|
3. Cross-cutting validation (between plugins and core)
|
|
|
|
Args:
|
|
options: The options to validate
|
|
"""
|
|
# Step 1: Plugin context validation
|
|
self._validate_plugin_contexts(options)
|
|
|
|
# Step 2: Cross-cutting validation
|
|
self._validate_cross_cutting_concerns(options)
|
|
|
|
def _validate_plugin_contexts(self, options: OCROptions) -> None:
|
|
"""Validate plugin options that require external context."""
|
|
# For now, we'll run the plugin validation directly since the models
|
|
# are still being integrated. This ensures the validation warnings
|
|
# and checks still work as expected.
|
|
|
|
# Run Tesseract validation
|
|
self._validate_tesseract_options(options)
|
|
|
|
# Run Optimize validation
|
|
self._validate_optimize_options(options)
|
|
|
|
def _validate_tesseract_options(self, options: OCROptions) -> None:
|
|
"""Validate Tesseract options."""
|
|
# Check pagesegmode warning
|
|
if options.tesseract.pagesegmode in (0, 2):
|
|
log.warning(
|
|
"The tesseract-pagesegmode you selected will disable OCR. "
|
|
"This may cause processing to fail."
|
|
)
|
|
|
|
# Check downsample consistency
|
|
if (
|
|
options.tesseract.downsample_above != 32767
|
|
and not options.tesseract.downsample_large_images
|
|
):
|
|
log.warning(
|
|
"The --tesseract-downsample-above argument will have no effect unless "
|
|
"--tesseract-downsample-large-images is also given."
|
|
)
|
|
|
|
# Check for blocked languages
|
|
from ocrmypdf.exceptions import BadArgsError
|
|
DENIED_LANGUAGES = {'equ', 'osd'}
|
|
if DENIED_LANGUAGES & set(options.languages):
|
|
raise BadArgsError(
|
|
"The following languages are for Tesseract's internal use and should not "
|
|
"be issued explicitly: "
|
|
f"{', '.join(DENIED_LANGUAGES & set(options.languages))}\n"
|
|
"Remove them from the -l/--language argument."
|
|
)
|
|
|
|
def _validate_optimize_options(self, options: OCROptions) -> None:
|
|
"""Validate optimization options."""
|
|
# Check optimization consistency
|
|
if options.optimize == 0 and any([
|
|
options.png_quality and options.png_quality > 0,
|
|
options.jpeg_quality and options.jpeg_quality > 0
|
|
]):
|
|
log.warning(
|
|
"The arguments --png-quality and --jpeg-quality "
|
|
"will be ignored because --optimize=0."
|
|
)
|
|
|
|
def _validate_cross_cutting_concerns(self, options: OCROptions) -> None:
|
|
"""Validate cross-cutting concerns that span multiple plugins."""
|
|
# Validate mutually exclusive OCR options
|
|
exclusive_options = sum(
|
|
1 for opt in [options.force_ocr, options.skip_text, options.redo_ocr] if opt
|
|
)
|
|
if exclusive_options >= 2:
|
|
raise ValueError("Choose only one of --force-ocr, --skip-text, --redo-ocr.")
|
|
|
|
# Validate redo_ocr compatibility
|
|
if options.redo_ocr:
|
|
if options.deskew or options.clean_final or options.remove_background:
|
|
raise ValueError(
|
|
"--redo-ocr is not currently compatible with --deskew, "
|
|
"--clean-final, and --remove-background"
|
|
)
|
|
|
|
# Validate output type compatibility
|
|
if options.output_type == 'none' and str(options.output_file) not in (
|
|
os.devnull, '-'
|
|
):
|
|
raise ValueError(
|
|
"Since you specified `--output-type none`, the output file "
|
|
f"{options.output_file} cannot be produced. Set the output file to "
|
|
"`-` to suppress this message."
|
|
)
|
|
|
|
# Validate PDF/A image compression compatibility
|
|
if (
|
|
options.ghostscript.pdfa_image_compression
|
|
and options.ghostscript.pdfa_image_compression != 'auto'
|
|
and not options.output_type.startswith('pdfa')
|
|
):
|
|
log.warning(
|
|
"--pdfa-image-compression argument only applies when "
|
|
"--output-type is one of 'pdfa', 'pdfa-1', or 'pdfa-2'"
|
|
)
|