Merge branch 'windows'

This commit is contained in:
James R. Barlow
2019-12-06 15:09:09 -08:00
34 changed files with 562 additions and 409 deletions
+1 -1
View File
@@ -21,7 +21,7 @@ from pathlib import Path
import pikepdf
MAX_REPLACE_PAGES = int(os.environ.get('_OCRMYPDF_MAX_REPLACE_PAGES', 100))
MAX_REPLACE_PAGES = 100
def _update_page_resources(*, page, font, font_key, procset):
+3 -3
View File
@@ -37,7 +37,7 @@ from .exceptions import (
UnsupportedImageFormatError,
)
from .exec import ghostscript, tesseract
from .helpers import re_symlink
from .helpers import safe_symlink
from .hocrtransform import HocrTransform
from .optimize import optimize
from .pdfa import generate_pdfa_ps
@@ -132,7 +132,7 @@ def triage(input_file, output_file, options, log):
"input file is a PDF, not an image."
)
# Origin file is a pdf create a symlink with pdf extension
re_symlink(input_file, output_file)
safe_symlink(input_file, output_file)
return output_file
except EnvironmentError as e:
log.error(e)
@@ -701,7 +701,7 @@ def convert_to_pdfa(input_pdf, input_ps_stub, context):
if modified:
pdf_file.save(fix_docinfo_file)
else:
os.symlink(input_pdf, fix_docinfo_file)
safe_symlink(input_pdf, fix_docinfo_file)
ghostscript.generate_pdfa(
pdf_version=input_pdfinfo.min_version,
+21 -5
View File
@@ -26,6 +26,7 @@ from collections import namedtuple
from tempfile import mkdtemp
from tqdm import tqdm
import PIL
from ._graft import OcrGrafter
from ._jobcontext import PDFContext, cleanup_working_files, make_logger
@@ -176,7 +177,7 @@ def post_process(pdf_file, context):
return optimize_pdf(pdf_out, context)
def worker_init(queue):
def worker_init(queue, max_pixels):
"""Initialize a process pool worker"""
# Ignore SIGINT (our parent process will kill us gracefully)
@@ -188,9 +189,15 @@ def worker_init(queue):
root.handlers = []
root.addHandler(h)
# In Windows, child process will not inherit our change to this value in
# the parent process, so ensure workers get it set
PIL.Image.MAX_IMAGE_PIXELS = max_pixels
def worker_thread_init(_queue):
pass
def worker_thread_init(_queue, max_pixels):
# This is probably not needed since threads should all see the same memory,
# but done for consistency.
PIL.Image.MAX_IMAGE_PIXELS = max_pixels
def log_listener(queue):
@@ -261,7 +268,9 @@ def exec_concurrent(context):
unit_scale=0.5,
disable=not context.options.progress_bar,
) as pbar, Pool(
processes=max_workers, initializer=initializer, initargs=(log_queue,)
processes=max_workers,
initializer=initializer,
initargs=(log_queue, PIL.Image.MAX_IMAGE_PIXELS),
) as pool:
results = pool.imap_unordered(exec_page_sync, context.get_page_contexts())
while True:
@@ -304,6 +313,13 @@ class NeverRaise(Exception):
pass # pylint: disable=unnecessary-pass
def samefile(f1, f2):
if os.name == 'nt':
return f1 == f2
else:
return os.path.samefile(f1, f2)
def run_pipeline(options, api=False):
log = make_logger(options, __name__)
@@ -339,7 +355,7 @@ def run_pipeline(options, api=False):
if options.output_file == '-':
log.info("Output sent to stdout")
elif os.path.samefile(options.output_file, os.devnull):
elif samefile(options.output_file, os.devnull):
pass # Say nothing when sending to dev null
else:
if options.output_type.startswith('pdfa'):
+3 -3
View File
@@ -42,7 +42,7 @@ from .exec import (
tesseract,
unpaper,
)
from .helpers import is_file_writable, is_iterable_notstr, monotonic, re_symlink
from .helpers import is_file_writable, is_iterable_notstr, monotonic, safe_symlink
# -------------
# External dependencies
@@ -374,7 +374,7 @@ def create_input_file(options, work_folder):
else:
try:
target = os.path.join(work_folder, 'origin')
re_symlink(options.input_file, target)
safe_symlink(options.input_file, target)
return target
except FileNotFoundError:
raise InputFileError(f"File not found - {options.input_file}")
@@ -446,7 +446,7 @@ def report_output_file_size(options, input_file, output_file):
def check_dependency_versions(options):
check_external_program(
program='tesseract',
package={'darwin': 'tesseract', 'linux': 'tesseract-ocr'},
package={'linux': 'tesseract-ocr'},
version_checker=tesseract.version,
need_version='4.0.0', # using backport for Travis CI
)
+2 -1
View File
@@ -18,6 +18,7 @@
import logging
import os
import sys
import warnings
from enum import IntEnum
from pathlib import Path
@@ -158,7 +159,7 @@ def create_options(*, input_file, output_file, **kwargs):
# If we are running a Tesseract spoof, ensure it knows what the input file is
if os.environ.get('PYTEST_CURRENT_TEST') and options.tesseract_env:
options.tesseract_env['_OCRMYPDF_TEST_INFILE'] = input_file
options.tesseract_env['_OCRMYPDF_TEST_INFILE'] = os.fspath(input_file)
return options
+33 -2
View File
@@ -21,14 +21,35 @@ import logging
import os
import re
import sys
import shutil
from collections.abc import Mapping
from subprocess import PIPE, STDOUT, CalledProcessError, run
from subprocess import PIPE, STDOUT, CalledProcessError, run as subprocess_run
from ..exceptions import ExitCode, MissingDependencyError
log = logging.Logger(__name__)
def _get_program(args, env=None):
program = args[0]
test_path = env.get('_OCRMYPDF_TEST_PATH', '')
if test_path:
program = shutil.which(program, path=test_path)
return program
def run(args, *, env=None, **kwargs):
if not env:
env = os.environ
program = _get_program(args, env)
if os.name == 'nt' and program.lower().endswith('.py'):
args = [sys.executable, program] + args[1:]
else:
args = [program] + args[1:]
log.debug(args)
return subprocess_run(args, env=env, **kwargs)
def get_version(program, *, version_arg='--version', regex=r'(\d+(\.\d+)*)', env=None):
"Get the version of the specified program"
args_prog = [program, version_arg]
@@ -111,23 +132,33 @@ On RPM-based systems (Red Hat, Fedora), search for instructions on
installing the RPM for {program}.
'''
windows_install_advice = '''
If not already installed, install the Chocolatey package manager. Then use
a command prompt to install the missing package:
choco install {package}
'''
def _get_platform():
if sys.platform.startswith('freebsd'):
return 'freebsd'
elif sys.platform.startswith('linux'):
return 'linux'
elif sys.platform.startswith('win'):
return 'windows'
return sys.platform
def _error_trailer(program, package, **kwargs):
if isinstance(package, Mapping):
package = package[_get_platform()]
package = package.get(_get_platform(), program)
if _get_platform() == 'darwin':
log.info(osx_install_advice.format(**locals()))
elif _get_platform() == 'linux':
log.info(linux_install_advice.format(**locals()))
elif _get_platform() == 'windows':
log.info(windows_install_advice.format(**locals()))
def _error_missing_program(program, package, required_for, recommended):
+118 -97
View File
@@ -19,24 +19,36 @@
import logging
import re
import os
import warnings
from contextlib import suppress
from functools import lru_cache
from io import BytesIO
from os import fspath
from shutil import copy
from subprocess import PIPE, STDOUT, run
from tempfile import NamedTemporaryFile
from pathlib import Path
from subprocess import PIPE, CalledProcessError
from shutil import which
from PIL import Image
from ..exceptions import SubprocessOutputError
from . import get_version
from ..exceptions import SubprocessOutputError, MissingDependencyError
from . import get_version, run
gslog = logging.getLogger()
GS = 'gs'
if os.name == 'nt':
GS = which('gswin64c')
if not GS:
GS = which('gswin32c')
if not GS:
raise MissingDependencyError("Ghostscript (gswin64c or gswin32c)")
GS = Path(GS).stem
@lru_cache(maxsize=1)
def version():
return get_version('gs')
return get_version(GS)
def jpeg_passthrough_available():
@@ -83,7 +95,7 @@ def extract_text(input_file, pageno=1):
args_gs = (
[
'gs',
GS,
'-dQUIET',
'-dSAFER',
'-dBATCH',
@@ -92,14 +104,15 @@ def extract_text(input_file, pageno=1):
'-dTextFormat=0',
]
+ pages
+ ['-o', '-', fspath(input_file)]
+ ['-o', '-', fspath(input_file), "-sstdout=%stderr"]
)
p = run(args_gs, stdout=PIPE, stderr=PIPE)
if p.returncode != 0:
try:
p = run(args_gs, stdout=PIPE, stderr=PIPE, check=True)
except CalledProcessError as e:
raise SubprocessOutputError(
'Ghostscript text extraction failed\n%s\n%s\n%s'
% (input_file, p.stdout.decode(), p.stderr.decode())
'Ghostscript text extraction failed\n%s\n%s'
% (input_file, e.stderr.decode(errors='replace'))
)
return p.stdout
@@ -141,54 +154,58 @@ def rasterize_pdf(
if not log:
log = gslog
with NamedTemporaryFile(delete=True) as tmp:
args_gs = (
[
'gs',
'-dQUIET',
'-dSAFER',
'-dBATCH',
'-dNOPAUSE',
f'-sDEVICE={raster_device}',
f'-dFirstPage={pageno}',
f'-dLastPage={pageno}',
f'-r{res[0]:f}x{res[1]:f}',
]
+ (['-dFILTERVECTOR'] if filter_vector else [])
+ [
'-o',
tmp.name,
'-dAutoRotatePages=/None', # Probably has no effect on raster
'-f',
fspath(input_file),
]
)
args_gs = (
[
GS,
'-dQUIET',
'-dSAFER',
'-dBATCH',
'-dNOPAUSE',
f'-sDEVICE={raster_device}',
f'-dFirstPage={pageno}',
f'-dLastPage={pageno}',
f'-r{res[0]:f}x{res[1]:f}',
]
+ (['-dFILTERVECTOR'] if filter_vector else [])
+ [
'-o',
'-',
'-sstdout=%stderr',
'-dAutoRotatePages=/None', # Probably has no effect on raster
'-f',
fspath(input_file),
]
)
log.debug(args_gs)
p = run(args_gs, stdout=PIPE, stderr=STDOUT, universal_newlines=True)
if _gs_error_reported(p.stdout):
log.error(p.stdout)
elif p.stdout:
log.debug(p.stdout)
log.debug(args_gs)
try:
p = run(args_gs, stdout=PIPE, stderr=PIPE, check=True)
except CalledProcessError as e:
with suppress(OSError):
Path(output_file).unlink() # no unfinished files
log.error(e.stderr.decode(errors='replace'))
raise SubprocessOutputError('Ghostscript rasterizing failed')
else:
stderr = p.stderr.decode(errors='replace')
if _gs_error_reported(stderr):
log.error(stderr)
elif stderr:
log.debug(stderr)
if p.returncode != 0:
raise SubprocessOutputError('Ghostscript rasterizing failed')
tmp.seek(0)
with Image.open(tmp) as im:
if rotation is not None:
log.debug("Rotating output by %i", rotation)
# rotation is a clockwise angle and Image.ROTATE_* is
# counterclockwise so this cancels out the rotation
if rotation == 90:
im = im.transpose(Image.ROTATE_90)
elif rotation == 180:
im = im.transpose(Image.ROTATE_180)
elif rotation == 270:
im = im.transpose(Image.ROTATE_270)
if rotation % 180 == 90:
page_dpi = page_dpi[1], page_dpi[0]
im.save(fspath(output_file), dpi=page_dpi)
with Image.open(BytesIO(p.stdout)) as im:
if rotation is not None:
log.debug("Rotating output by %i", rotation)
# rotation is a clockwise angle and Image.ROTATE_* is
# counterclockwise so this cancels out the rotation
if rotation == 90:
im = im.transpose(Image.ROTATE_90)
elif rotation == 180:
im = im.transpose(Image.ROTATE_180)
elif rotation == 270:
im = im.transpose(Image.ROTATE_270)
if rotation % 180 == 90:
page_dpi = page_dpi[1], page_dpi[0]
im.save(fspath(output_file), dpi=page_dpi)
def generate_pdfa(
@@ -256,37 +273,48 @@ def generate_pdfa(
# https://bugs.ghostscript.com/show_bug.cgi?id=699216
compression_args.append('-dPassThroughJPEGImages=false')
with NamedTemporaryFile(delete=True) as gs_pdf:
# nb no need to specify ProcessColorModel when ColorConversionStrategy
# is set; see:
# https://bugs.ghostscript.com/show_bug.cgi?id=699392
args_gs = (
[
"gs",
"-dQUIET",
"-dBATCH",
"-dNOPAUSE",
"-dSAFER",
"-dCompatibilityLevel=" + str(pdf_version),
"-sDEVICE=pdfwrite",
"-dAutoRotatePages=/None",
"-sColorConversionStrategy=" + strategy,
]
+ compression_args
+ [
"-dJPEGQ=95",
"-dPDFA=" + pdfa_part,
"-dPDFACompatibilityPolicy=1",
"-sOutputFile=" + gs_pdf.name,
]
)
args_gs.extend(fspath(s) for s in pdf_pages) # Stringify Path objs
log.debug(args_gs)
p = run(args_gs, stdout=PIPE, stderr=STDOUT, universal_newlines=True)
if _gs_error_reported(p.stdout):
log.error(p.stdout)
elif 'overprint mode not set' in p.stdout:
# nb no need to specify ProcessColorModel when ColorConversionStrategy
# is set; see:
# https://bugs.ghostscript.com/show_bug.cgi?id=699392
args_gs = (
[
GS,
"-dQUIET",
"-dBATCH",
"-dNOPAUSE",
"-dSAFER",
"-dCompatibilityLevel=" + str(pdf_version),
"-sDEVICE=pdfwrite",
"-dAutoRotatePages=/None",
"-sColorConversionStrategy=" + strategy,
]
+ compression_args
+ [
"-dJPEGQ=95",
"-dPDFA=" + pdfa_part,
"-dPDFACompatibilityPolicy=1",
"-o",
"-",
"-sstdout=%stderr",
]
)
args_gs.extend(fspath(s) for s in pdf_pages) # Stringify Path objs
log.debug(args_gs)
try:
with Path(output_file).open('wb') as output:
p = run(args_gs, stdout=output, stderr=PIPE, check=True)
except CalledProcessError as e:
# Ghostscript does not change return code when it fails to create
# PDF/A - check PDF/A status elsewhere
with suppress(OSError):
Path(output_file).unlink()
log.error(e.stderr.decode(errors='replace'))
raise SubprocessOutputError('Ghostscript PDF/A rendering failed')
else:
stderr = p.stderr.decode('utf-8', errors='replace')
if _gs_error_reported(stderr):
log.error(stderr)
elif 'overprint mode not set' in stderr:
# Unless someone is going to print PDF/A documents on a
# magical sRGB printer I can't see the removal of overprinting
# being a problem....
@@ -295,11 +323,4 @@ def generate_pdfa(
"input file to complete PDF/A conversion. "
)
else:
log.debug(p.stdout)
if p.returncode == 0:
# Ghostscript does not change return code when it fails to create
# PDF/A - check PDF/A status elsewhere
copy(gs_pdf.name, fspath(output_file))
else:
raise SubprocessOutputError('Ghostscript PDF/A rendering failed')
log.debug(stderr)
+2 -2
View File
@@ -18,10 +18,10 @@
"""Interface to jbig2 executable"""
from functools import lru_cache
from subprocess import PIPE, run
from subprocess import PIPE
from ..exceptions import MissingDependencyError
from . import get_version
from . import get_version, run
@lru_cache(maxsize=1)
+2 -2
View File
@@ -19,9 +19,9 @@
from functools import lru_cache
from os import fspath
from subprocess import PIPE, STDOUT, CalledProcessError, run
from subprocess import PIPE, STDOUT, CalledProcessError
from . import get_version
from . import get_version, run
@lru_cache(maxsize=1)
+8 -12
View File
@@ -23,15 +23,15 @@ from collections import namedtuple
from contextlib import suppress
import logging
from os import fspath
from subprocess import PIPE, STDOUT, CalledProcessError, TimeoutExpired, run
from subprocess import PIPE, STDOUT, CalledProcessError, TimeoutExpired
from ..exceptions import (
MissingDependencyError,
SubprocessOutputError,
TesseractConfigError,
)
from ..helpers import page_number
from . import get_version
from ..helpers import page_number, safe_symlink
from . import get_version, run
OrientationConfidence = namedtuple('OrientationConfidence', ('angle', 'confidence'))
@@ -128,9 +128,10 @@ def languages(tesseract_env=None):
except CalledProcessError as e:
raise MissingDependencyError(lang_error(e.output)) from e
for line in output.splitlines():
if line.startswith('Error'):
raise MissingDependencyError(lang_error(output))
header, *rest = output.splitlines()
if not header.startswith('List of available languages'):
raise MissingDependencyError(lang_error(output))
return set(lang.strip() for lang in rest)
@@ -194,12 +195,7 @@ def tesseract_log_output(mainlog, stdout, input_file):
try:
text = stdout.decode()
except UnicodeDecodeError:
log.error(
"Tesseract's output was not utf-8. "
"This usually means Tesseract's language packs do not match "
"the installed version of Tesseract."
)
text = stdout.decode('utf-8', 'backslashreplace')
text = stdout.decode('utf-8', 'ignore')
lines = text.splitlines()
for line in lines:
@@ -325,7 +321,7 @@ def use_skip_page(text_only, skip_pdf, output_pdf, output_text):
# Substitute a "skipped page"
with suppress(FileNotFoundError):
os.remove(output_pdf) # In case it was partially created
os.symlink(skip_pdf, output_pdf)
safe_symlink(skip_pdf, output_pdf)
return
# Or normally, just write a 0 byte file to the output to indicate a skip
+2 -3
View File
@@ -22,7 +22,6 @@
import os
import shlex
import subprocess
from functools import lru_cache
from subprocess import PIPE, STDOUT, CalledProcessError
from tempfile import TemporaryDirectory
@@ -30,7 +29,7 @@ from tempfile import TemporaryDirectory
from PIL import Image
from ..exceptions import MissingDependencyError, SubprocessOutputError
from . import get_version
from . import get_version, run as external_run
@lru_cache(maxsize=1)
@@ -77,7 +76,7 @@ def run(input_file, output_file, dpi, log, mode_args):
# their unpaper arguments (whether intentionally or otherwise)
args_unpaper.extend([input_pnm, output_pnm])
try:
proc = subprocess.run(
proc = external_run(
args_unpaper,
check=True,
close_fds=True,
+9 -3
View File
@@ -18,6 +18,7 @@
import logging
import multiprocessing
import os
import shutil
import warnings
from collections.abc import Iterable
from contextlib import suppress
@@ -27,14 +28,14 @@ from pathlib import Path
log = logging.getLogger(__name__)
def re_symlink(input_file, soft_link_name, *args, **kwargs):
def safe_symlink(input_file, soft_link_name, *args, **kwargs):
"""
Helper function: relinks soft symbolic link if necessary
"""
if len(args) == 1 and isinstance(args[0], logging.Logger):
log.warning("Deprecated: re_symlink(,log)")
log.warning("Deprecated: safe_symlink(,log)")
if 'log' in kwargs:
log.warning('Deprecated: re_symlink(...log=)')
log.warning('Deprecated: safe_symlink(...log=)')
input_file = os.fspath(input_file)
soft_link_name = os.fspath(soft_link_name)
@@ -60,6 +61,11 @@ def re_symlink(input_file, soft_link_name, *args, **kwargs):
if not os.path.exists(input_file):
raise FileNotFoundError(f"trying to create a broken symlink to {input_file}")
if os.name == 'nt':
# Don't actually use symlinks on Windows due to permission issues
shutil.copyfile(input_file, soft_link_name)
return
log.debug("os.symlink(%s, %s)", input_file, soft_link_name)
# Create symbolic link using absolute path
+34 -17
View File
@@ -34,12 +34,22 @@ from os import fspath
from tempfile import TemporaryFile
from .lib._leptonica import ffi
from .exceptions import MissingDependencyError
# pylint: disable=protected-access
logger = logging.getLogger(__name__)
lept = ffi.dlopen(find_library('lept'))
if os.name == 'nt':
libname = 'liblept-5'
else:
libname = 'lept'
_libpath = find_library(libname)
if not _libpath and os.name == 'nt':
raise MissingDependencyError(
"Please ensure that 'tesseract' is on your PATH environment variable. "
)
lept = ffi.dlopen(_libpath)
lept.setMsgSeverity(lept.L_SEVERITY_WARNING)
@@ -292,9 +302,11 @@ class Pix(LeptonicaObject):
Leptonica can load TIFF, PNM (PBM, PGM, PPM), PNG, and JPEG. If
loading fails then the object will wrap a C null pointer.
"""
filename = fspath(path)
with _LeptonicaErrorTrap():
return cls(lept.pixRead(os.fsencode(filename)))
with open(path, 'rb') as py_file:
data = py_file.read()
buffer = ffi.from_buffer(data)
with _LeptonicaErrorTrap():
return cls(lept.pixReadMem(buffer, len(buffer)))
def write_implied_format(self, path, jpeg_quality=0, jpeg_progressive=0):
"""Write pix to the filename, with the extension indicating format.
@@ -302,11 +314,19 @@ class Pix(LeptonicaObject):
jpeg_quality -- quality (iff JPEG; 1 - 100, 0 for default)
jpeg_progressive -- (iff JPEG; 0 for baseline seq., 1 for progressive)
"""
filename = fspath(path)
with _LeptonicaErrorTrap():
lept.pixWriteImpliedFormat(
os.fsencode(filename), self._cdata, jpeg_quality, jpeg_progressive
)
lept_format = lept.getImpliedFileFormat(os.fsencode(path))
with open(path, 'wb') as py_file:
data = ffi.new('l_uint8 **pdata')
size = ffi.new('size_t *psize')
with _LeptonicaErrorTrap():
if lept_format == lept.L_JPEG_ENCODE:
lept.pixWriteMemJpeg(
data, size, self._cdata, jpeg_quality, jpeg_progressive
)
else:
lept.pixWriteMem(data, size, self._cdata, lept_format)
buffer = ffi.buffer(data[0], size[0])
py_file.write(buffer)
@classmethod
def frompil(self, pillow_image):
@@ -502,17 +522,14 @@ class Pix(LeptonicaObject):
display=0,
pdfdir=ffi.NULL,
):
if get_leptonica_version() < 'leptonica-1.76':
# Leptonica 1.76 changed the API for pixFindPageForeground; we don't
# support the old version
raise LeptonicaError("Not available in this version of Leptonica")
with _LeptonicaErrorTrap():
cropbox = Box(
lept.pixFindPageForeground(
self._cdata,
threshold,
mindist,
erasedist,
pagenum,
showmorph,
display,
pdfdir,
self._cdata, threshold, mindist, erasedist, showmorph, ffi.NULL
)
)
File diff suppressed because one or more lines are too long
+34 -9
View File
@@ -16,6 +16,8 @@
# You should have received a copy of the GNU General Public License
# along with OCRmyPDF. If not, see <http://www.gnu.org/licenses/>.
from pathlib import Path
from cffi import FFI
ffibuilder = FFI()
@@ -74,6 +76,17 @@ struct Pixa
};
typedef struct Pixa PIXA;
/*! Array of compressed pix */
struct PixaComp
{
l_int32 n; /*!< number of PixComp in ptr array */
l_int32 nalloc; /*!< number of PixComp ptrs allocated */
l_int32 offset; /*!< indexing offset into ptr array */
struct PixComp **pixc; /*!< the array of ptrs to PixComp */
struct Boxa *boxa; /*!< array of boxes */
};
typedef struct PixaComp PIXAC;
struct Box
{
l_int32 x;
@@ -210,9 +223,15 @@ ffibuilder.cdef(
"""
PIX * pixRead ( const char *filename );
PIX * pixReadMem ( const l_uint8 *data, size_t size );
PIX * pixReadStream ( FILE *fp, l_int32 hint );
PIX * pixScale ( PIX *pixs, l_float32 scalex, l_float32 scaley );
l_int32 pixFindSkew ( PIX *pixs, l_float32 *pangle, l_float32 *pconf );
l_int32 pixWriteImpliedFormat ( const char *filename, PIX *pix, l_int32 quality, l_int32 progressive );
l_int32 getImpliedFileFormat ( const char *filename );
l_ok pixWriteStream ( FILE *fp, PIX *pix, l_int32 format );
l_ok pixWriteStreamJpeg ( FILE *fp, PIX *pixs, l_int32 quality, l_int32 progressive );
l_ok pixWriteMem ( l_uint8 **pdata, size_t *psize, PIX *pix, l_int32 format );
l_ok pixWriteMemJpeg ( l_uint8 **pdata, size_t *psize, PIX *pix, l_int32 quality, l_int32 progressive );
l_int32
pixWriteMemPng(l_uint8 **pdata,
size_t *psize,
@@ -294,14 +313,12 @@ pixCleanBackgroundToWhite(PIX *pixs,
l_int32 whiteval);
BOX *
pixFindPageForeground(PIX *pixs,
l_int32 threshold,
l_int32 mindist,
l_int32 erasedist,
l_int32 pagenum,
l_int32 showmorph,
l_int32 display,
const char *pdfdir);
pixFindPageForeground ( PIX *pixs,
l_int32 threshold,
l_int32 mindist,
l_int32 erasedist,
l_int32 showmorph,
PIXAC *pixac );
PIX *
pixClipRectangle(PIX *pixs,
@@ -414,7 +431,10 @@ pixExtractBarcodes(PIX *pixs,
l_int32 debugflag);
BOXA *
pixLocateBarcodes ( PIX *pixs, l_int32 thresh, PIX **ppixb, PIX **ppixm );
pixLocateBarcodes ( PIX *pixs,
l_int32 thresh,
PIX **ppixb,
PIX **ppixm );
SARRAY *
pixReadBarcodes(PIXA *pixa,
@@ -491,3 +511,8 @@ ffibuilder.set_source("ocrmypdf.lib._leptonica", None)
if __name__ == '__main__':
ffibuilder.compile(verbose=True)
if Path('ocrmypdf/lib/_leptonica.py').exists() and Path('src/ocrmypdf').exists():
output = Path('ocrmypdf/lib/_leptonica.py')
output.rename('src/ocrmypdf/lib/_leptonica.py')
Path('ocrmypdf/lib').rmdir()
Path('ocrmypdf').rmdir()
+3 -3
View File
@@ -31,7 +31,7 @@ from . import leptonica
from ._jobcontext import PDFContext
from .exceptions import OutputFileAccessError
from .exec import jbig2enc, pngquant
from .helpers import re_symlink
from .helpers import safe_symlink
DEFAULT_JPEG_QUALITY = 75
DEFAULT_PNG_QUALITY = 70
@@ -492,7 +492,7 @@ def optimize(input_file, output_file, context, save_settings):
log = context.log
options = context.options
if options.optimize == 0:
re_symlink(input_file, output_file)
safe_symlink(input_file, output_file)
return
if options.jpeg_quality == 0:
@@ -538,7 +538,7 @@ def optimize(input_file, output_file, context, save_settings):
pike.remove_unreferenced_resources()
pike.save(output_file, **save_settings)
else:
re_symlink(target_file, output_file)
safe_symlink(target_file, output_file)
def main(infile, outfile, level, jobs=1):
+1
View File
@@ -96,6 +96,7 @@ def extract_text_xml(infile, pdf, pageno=None, log=gslog):
page_count_difference = len(pdf.pages) - len(page_xml)
if page_count_difference != 0:
log.error("The number of pages in the input file is inconsistent.")
log.error(f"Expected {len(pdf.pages)}, txtwrite says {len(page_xml)}")
if page_count_difference > 0:
page_xml.extend([None] * page_count_difference)
return page_xml