Improve API documentation

This commit is contained in:
James R. Barlow
2020-06-30 04:20:14 -07:00
parent 86a73191b0
commit 62924ee280
10 changed files with 190 additions and 60 deletions
-4
View File
@@ -106,7 +106,3 @@ Reference
:undoc-members:
.. autofunction:: ocrmypdf.configure_logging
.. automodule:: ocrmypdf.exceptions
:members:
:undoc-members:
+43
View File
@@ -0,0 +1,43 @@
=============
API Reference
=============
This page summarizes the rest of the public API. Generally speaking this
should mainly of interest to plugin developers.
ocrmypdf.exceptions
===================
.. automodule:: ocrmypdf.exceptions
:members:
:undoc-members:
ocrmypdf.helpers
================
.. automodule:: ocrmypdf.helpers
:members:
ocrmypdf.hocrtransform
======================
.. automodule:: ocrmypdf.hocrtransform
:members:
ocrmypdf.pdfa
=============
.. automodule:: ocrmypdf.pdfa
:members:
ocrmypdf.quality
================
.. automodule:: ocrmypdf.quality
:members:
ocrmypdf.subprocess
===================
.. automodule:: ocrmypdf.subprocess
:members:
+7 -3
View File
@@ -1,5 +1,4 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# ocrmypdf documentation build configuration file, created by
# sphinx-quickstart on Sun Sep 4 14:29:43 2016.
@@ -21,6 +20,8 @@
# import sys
# sys.path.insert(0, os.path.abspath('.'))
"""isort:skip_file"""
# -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
@@ -32,6 +33,8 @@
# ones.
extensions = ['sphinx.ext.napoleon']
napoleon_use_rtype = False
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
@@ -51,7 +54,7 @@ master_doc = 'index'
# General information about the project.
project = 'ocrmypdf'
copyright = (
'2019, James R. Barlow. Licensed under Creative Commons Attribution-ShareAlike 4.0.'
'2020, James R. Barlow. Licensed under Creative Commons Attribution-ShareAlike 4.0.'
)
author = 'James R. Barlow'
@@ -90,6 +93,7 @@ from pkg_resources import get_distribution, DistributionNotFound
release = get_distribution('ocrmypdf').version
version = '.'.join(release.split('.')[:2])
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
@@ -174,7 +178,7 @@ html_theme_options = {'display_version': False}
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#
# html_logo = None
# html_logo = "images/logo.svg" # looks bad
# The name of an image file (relative to this directory) to use as a favicon of
# the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
+1
View File
@@ -36,6 +36,7 @@ image processing and OCR to existing PDFs.
api
plugins
apiref
contributing
Indices and tables
+39 -1
View File
@@ -76,5 +76,43 @@ A plugin may provide the following hooks. Hooks should be decorated with
The following is a complete list of hooks that may be installed and when
they are called.
.. automodule:: ocrmypdf.pluginspec
Custom command line arguments
-----------------------------
.. autofunction:: ocrmypdf.pluginspec.add_options
.. autofunction:: ocrmypdf.pluginspec.check_options
Applying special behavior before processing
-------------------------------------------
.. autofunction:: ocrmypdf.pluginspec.validate
PDF page to image
-----------------
.. autofunction:: ocrmypdf.pluginspec.rasterize_pdf_page
Modifying intermediate images
-----------------------------
.. autofunction:: ocrmypdf.pluginspec.filter_ocr_image
.. autofunction:: ocrmypdf.pluginspec.filter_page_image
OCR engine
----------
.. autofunction:: ocrmypdf.pluginspec.get_ocr_engine
.. autoclass:: ocrmypdf.pluginspec.OcrEngine
:members:
.. automethod:: __str__
.. autoclass:: ocrmypdf.pluginspec.OrientationConfidence
PDF/A production
----------------
.. autofunction:: ocrmypdf.pluginspec.generate_pdfa
+8
View File
@@ -35,6 +35,8 @@ log = logging.getLogger(__name__)
class Resolution(namedtuple('Resolution', ('x', 'y'))):
"""The number of pixels per inch in each 2D direction."""
__slots__ = ()
def round(self, ndigits: int):
@@ -128,6 +130,7 @@ def page_number(input_file: os.PathLike) -> int:
def available_cpu_count() -> int:
"""Returns number of CPUs in the system."""
try:
return multiprocessing.cpu_count()
except NotImplementedError:
@@ -174,6 +177,10 @@ def is_file_writable(test_file: os.PathLike) -> bool:
def check_pdf(input_file: Path) -> bool:
"""Check if a PDF complies with the PDF specification.
Checks for proper formatting and proper linearization.
"""
pdf = None
try:
pdf = pikepdf.open(input_file)
@@ -211,6 +218,7 @@ T = TypeVar('T')
def clamp(n: T, smallest: T, largest: T) -> T:
"""Clamps the value of n to between smallest and largest."""
return max(smallest, min(n, largest))
+22 -8
View File
@@ -35,7 +35,7 @@ from collections import namedtuple
from itertools import chain
from math import atan, cos, sin
from pathlib import Path
from typing import Union
from typing import Optional, Tuple, Union
from xml.etree import ElementTree
from reportlab.lib.colors import black, cyan, magenta, red
@@ -133,7 +133,7 @@ class HocrTransform:
return out
@classmethod
def baseline(cls, element):
def baseline(cls, element) -> Tuple[float, float]:
"""
Returns a tuple containing the baseline slope and intercept.
"""
@@ -143,7 +143,7 @@ class HocrTransform:
return float(matches.group(1)), int(matches.group(2))
return (0.0, 0.0)
def pt_from_pixel(self, pxl):
def pt_from_pixel(self, pxl) -> Rect:
"""
Returns the quantity in PDF units (pt) given quantity in pixels
"""
@@ -156,11 +156,11 @@ class HocrTransform:
return xpath
@classmethod
def replace_unsupported_chars(cls, s: str):
def replace_unsupported_chars(cls, s: str) -> str:
"""
Given an input string, returns the corresponding string that:
- is available in the helvetica facetype
- does not contain any ligature (to allow easy search in the PDF file)
* is available in the Helvetica facetype
* does not contain any ligature (to allow easy search in the PDF file)
"""
return s.translate(cls.ligatures)
@@ -172,12 +172,12 @@ class HocrTransform:
def to_pdf(
self,
out_filename: Path,
image_filename: Path = None,
image_filename: Optional[Path] = None,
show_bounding_boxes: bool = False,
fontname: str = "Helvetica",
invisible_text: bool = False,
interword_spaces: bool = False,
):
) -> None:
"""
Creates a PDF file with an image superimposed on top of the text.
Text is positioned according to the bounding box of the lines in
@@ -185,6 +185,20 @@ class HocrTransform:
The image need not be identical to the image used to create the hOCR
file.
It can have a lower resolution, different color mode, etc.
Arguments:
out_filename: Path of PDF to write.
image_filename: Image to use for this file. If omitted, the OCR text
is shown.
show_bounding_boxes: Show bounding boxes around various text regions,
for debugging.
fontname: Name of font to use.
invisible_text: If True, text is rendered invisible so that is
selectable but never drawn. If False, text is visible and may
be seen if the image is skipped or deleted in Acrobat.
interword_spaces: If True, insert spaces between words rather than
drawing each word without spaces. Generally this improves text
extraction.
"""
# create the PDF file
# page size in points (1/72 in.)
+21 -25
View File
@@ -16,19 +16,7 @@
# along with OCRmyPDF. If not, see <http://www.gnu.org/licenses/>.
"""
Generate a PDFMARK file for Ghostscript >= 9.14, for PDF/A conversion
pdfmark is an extension to the Postscript language that describes some PDF
features like bookmarks and annotations. It was originally specified Adobe
Distiller, for Postscript to PDF conversion:
https://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/pdfmark_reference.pdf
Ghostscript uses pdfmark for PDF to PDF/A conversion as well. To use Ghostscript
to create a PDF/A, we need to create a pdfmark file with the necessary metadata.
This takes care of the many version-specific bugs and pecularities in
Ghostscript's handling of pdfmark.
Utilities for PDF/A production and confirmation with Ghostspcript.
"""
import base64
@@ -68,20 +56,28 @@ def
"""
def generate_pdfa_ps(target_filename, icc='sRGB'):
"""Create a Postscript pdfmark file for Ghostscript PDF/A conversion
def generate_pdfa_ps(target_filename: Path, icc: str = 'sRGB'):
"""Create a Postscript PDFMARK file for Ghostscript PDF/A conversion
A pdfmark file is a small Postscript program that provides some information
Ghostscript needs to perform PDF/A conversion. The only information we put
in specifies that we want the file to be a PDF/A, and we want to Ghostscript
to convert objects to the sRGB colorspace if it runs into any object that
it decides must be converted.
pdfmark is an extension to the Postscript language that describes some PDF
features like bookmarks and annotations. It was originally specified Adobe
Distiller, for Postscript to PDF conversion.
See the Adobe pdfmark Reference for details:
https://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/pdfmark_reference.pdf
Ghostscript uses pdfmark for PDF to PDF/A conversion as well. To use Ghostscript
to create a PDF/A, we need to create a pdfmark file with the necessary metadata.
:param target_filename: filename to save
:param icc: ICC identifier such as 'sRGB'
This function takes care of the many version-specific bugs and pecularities in
Ghostscript's handling of pdfmark.
The only information we put in specifies that we want the file to be a
PDF/A, and we want to Ghostscript to convert objects to the sRGB colorspace
if it runs into any object that it decides must be converted.
Arguments:
target_filename: filename to save
icc: ICC identifier such as 'sRGB'
References:
Adobe PDFMARK Reference: https://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/pdfmark_reference.pdf
"""
if icc == 'sRGB':
icc_profile = SRGB_ICC_PROFILE
@@ -102,7 +98,7 @@ def generate_pdfa_ps(target_filename, icc='sRGB'):
return target_filename
def file_claims_pdfa(filename):
def file_claims_pdfa(filename: Path):
"""Determines if the file claims to be PDF/A compliant
This only checks if the XMP metadata contains a PDF/A marker. It does not
+32 -6
View File
@@ -22,13 +22,13 @@ from pathlib import Path
from typing import TYPE_CHECKING, AbstractSet, List, Optional
import pluggy
from PIL import Image
from ocrmypdf.helpers import Resolution
if TYPE_CHECKING:
from ocrmypdf._jobcontext import PageContext
from ocrmypdf.pdfinfo import PdfInfo
from PIL import Image
hookspec = pluggy.HookspecMarker('ocrmypdf')
@@ -118,7 +118,7 @@ def rasterize_pdf_page(
rotation: Cardinal angle, clockwise, to rotate page
filter_vector: If True, remove vector graphics objects
Returns:
output_file
Path: output_file if successful
Note:
This hook will be called from child processes. Modifying global state
will not affect the main process or other child processes.
@@ -126,7 +126,7 @@ def rasterize_pdf_page(
@hookspec(firstresult=True)
def filter_ocr_image(page: 'PageContext', image: Image) -> Image:
def filter_ocr_image(page: 'PageContext', image: 'Image') -> 'Image':
"""Called to filter the image before it is sent to OCR.
This is the image that OCR sees, not what the user sees when they view the
@@ -159,20 +159,46 @@ def filter_page_image(page: 'PageContext', image_filename: Path) -> Path:
OrientationConfidence = namedtuple('OrientationConfidence', ('angle', 'confidence'))
"""Expresses an OCR engine's confidence in page rotation.
Attributes:
angle (int): The clockwise angle (0, 90, 180, 270) that the page should be
rotated. 0 means no rotation.
confidence (float): How confident the OCR engine is that this the correct
rotation. 0 is not confident, 15 is very confident. Arbitrary units.
"""
class OcrEngine(ABC):
"""A class representing an OCR engine with capabilities similar to Tesseract OCR.
This could be used to create a plugin for another OCR engine instead of
Tesseract OCR.
"""
@abstractstaticmethod
def version() -> str:
"""Returns the version of the OCR engine."""
@abstractstaticmethod
def creator_tag(options: Namespace) -> str:
"""Returns the creator tag to identify this software's role in creating the PDF."""
"""Returns the creator tag to identify this software's role in creating the PDF.
This tag will be inserted in the XMP metadata and DocumentInfo dictionary
as appropriate. Ideally you should include the name of the OCR engine and its
version. The text should not contain line breaks. This is to help developers
like yourself identify the software that produced this file.
OCRmyPDF will always prepend its name to this value.
"""
@abstractmethod
def __str__(self):
"""Returns name of OCR engine and version."""
"""Returns name of OCR engine and version.
This is used when OCRmyPDF wants to mention the name of the OCR engine
to the user, usually in an error message.
"""
@abstractstaticmethod
def languages(options: Namespace) -> AbstractSet[str]:
@@ -248,5 +274,5 @@ def generate_pdfa(
pdfa_part: The desired PDF/A compliance level, such as ``'2B'``.
Returns:
output_file: If successful, the hook should return ``output_file``.
Path: If successful, the hook should return ``output_file``.
"""
+17 -13
View File
@@ -36,18 +36,12 @@ log = logging.getLogger(__name__)
def run(args, *, env=None, **kwargs):
"""Wrapper around subprocess.run()
The main purpose of this wrapper is to log subprocess output.
Secondly we have to account for behavioral differences in Windows in particular.
Creating symbolic links in Windows requires administrator privileges and
may not work if for some reason we're using a FAT file system or the temporary
folder is on a different drive from the working folder. The test suite
works around this by creating shim Python scripts that perform the same function
as a symbolic link, but those shims require support on this side, to ensure
we call them with Python.
"""Wrapper around :py:func:`subprocess.run`
The main purpose of this wrapper is to log subprocess output in an orderly
fashion that indentifies the responsible subprocess. An additional
task is that this function goes to greater lengths to find possible Windows
locations of our dependencies when they are not on the system PATH.
"""
if not env:
env = os.environ
@@ -106,8 +100,18 @@ def _fix_windows_args(program, args, env):
@lru_cache(maxsize=None)
def get_version(program, *, version_arg='--version', regex=r'(\d+(\.\d+)*)', env=None):
"""Get the version of the specified program"""
def get_version(
program: str, *, version_arg: str = '--version', regex=r'(\d+(\.\d+)*)', env=None
):
"""Get the version of the specified program
Arguments:
program: The program to version check.
version_arg: The argument needed to ask for its version, e.g. ``--version``.
regex: A regular expression to parse the program's output and obtain the
version.
env: Custom ``os.environ`` in which to run program.
"""
args_prog = [program, version_arg]
try:
proc = run(