—output-type=pdf now outputs /UserUnit PDFs at the correct size

This currently distorts the output size because Tesseract assumes it
 knows the DPI better than we do.

Does not work for Ghostscript, because it emerges that Ghostscript
honors /UserUnit for rasterizing but not in pdfwrite (resolve/wontfix).

https://bugs.ghostscript.com/show_bug.cgi?id=690781

Ghostscript’s output would need to be patched in a PDF/A safe way for
this to work. Temporary route may be to block Ghostscript if
/UserUnit.
This commit is contained in:
James R. Barlow
2017-05-24 23:26:07 -07:00
parent eb1cd38f6c
commit 6ff6c8614f
4 changed files with 57 additions and 15 deletions
+3 -1
View File
@@ -64,7 +64,8 @@ def rasterize_pdf(input_file, output_file, xres, yres, raster_device, log,
raise SubprocessOutputError()
def generate_pdfa(pdf_pages, output_file, compression, log, threads=1):
def generate_pdfa(*, pdf_version, pdf_pages, output_file, compression, log,
threads=1):
compression_args = []
if compression == 'jpeg':
compression_args = [
@@ -92,6 +93,7 @@ def generate_pdfa(pdf_pages, output_file, compression, log, threads=1):
"-dQUIET",
"-dBATCH",
"-dNOPAUSE",
'-dCompatibilityLevel=' + str(pdf_version),
'-dNumRenderingThreads=' + str(threads),
"-sDEVICE=pdfwrite",
"-dAutoRotatePages=/None",
+3 -2
View File
@@ -120,13 +120,14 @@ def split_pages(input_file, work_folder, npages):
run(args_qpdf, check=True)
def merge(input_files, output_file):
def merge(*, min_version, input_files, output_file):
"""Merge the list of input files (all filenames) into the output file.
The input files may contain one or more pages.
"""
args_qpdf = [
get_program('qpdf'), input_files[0], '--pages'
get_program('qpdf'), '--min-version={}'.format(min_version),
input_files[0], '--pages'
] + input_files + ['--', output_file]
run(args_qpdf, check=True)
+44 -11
View File
@@ -205,10 +205,18 @@ def get_page_dpi(pageinfo, options):
def get_page_square_dpi(pageinfo, options):
"Get the DPI when we require xres == yres"
"Get the working DPI when we require xres == yres"
return float(max(
pageinfo.xres or VECTOR_PAGE_DPI,
pageinfo.yres or VECTOR_PAGE_DPI,
(pageinfo.xres * pageinfo.userunit) or VECTOR_PAGE_DPI,
(pageinfo.yres * pageinfo.userunit) or VECTOR_PAGE_DPI,
options.oversample or 0))
def get_page_true_square_dpi(pageinfo, options):
"Get the true DPI when we require xres == yres, and scaled in userunits"
return float(max(
(pageinfo.xres) or VECTOR_PAGE_DPI,
(pageinfo.yres) or VECTOR_PAGE_DPI,
options.oversample or 0))
@@ -414,11 +422,24 @@ def rasterize_with_ghostscript(
os.path.basename(input_file), device))
# Produce the page image with square resolution or else deskew and OCR
# will not work properly
dpi = get_page_square_dpi(pageinfo, options)
ghostscript.rasterize_pdf(
input_file, output_file, xres=dpi, yres=dpi, raster_device=device,
log=log)
# will not work properly.
true_dpi = get_page_true_square_dpi(pageinfo, options)
working_dpi = get_page_square_dpi(pageinfo, options)
if true_dpi == working_dpi:
dpi = true_dpi
ghostscript.rasterize_pdf(
input_file, output_file, xres=dpi, yres=dpi,
raster_device=device, log=log)
else:
# Ghostscript respects /UserUnit when rasterizing so replace the
# image's DPI with the working DPI
ghostscript.rasterize_pdf(
input_file, output_file + '.tmp', xres=true_dpi, yres=true_dpi,
raster_device=device, log=log)
with Image.open(output_file + ".tmp") as im:
im.save(output_file, dpi=(working_dpi, working_dpi))
def preprocess_remove_background(
@@ -692,6 +713,12 @@ def combine_layers(
pdf_output = pypdf.PdfFileWriter()
pdf_output.addPage(page_text)
# If the input was scaled, re-apply the scaling
pageinfo = get_pageinfo(text, context)
if pageinfo.userunit != 1:
page_text[pypdf.generic.NameObject('/UserUnit')] = pageinfo.userunit
pdf_output._header = b'%PDF-1.6' # Hack header to correct version
with open(output_file, "wb") as out:
pdf_output.write(out)
@@ -835,9 +862,14 @@ def merge_pages_ghostscript(
if not f.endswith('.txt'))
pdf_pages = sorted(input_files, key=input_file_order)
log.debug("Final pages: " + "\n".join(pdf_pages))
input_pdfinfo = context.get_pdfinfo()
ghostscript.generate_pdfa(
pdf_pages, output_file, options.pdfa_image_compression,
log, options.jobs or 1)
pdf_version=input_pdfinfo.min_version,
pdf_pages=pdf_pages,
output_file=output_file,
compression=options.pdfa_image_compression,
log=log,
threads=options.jobs or 1)
def merge_pages_qpdf(
@@ -879,7 +911,8 @@ def merge_pages_qpdf(
pdf_pages[0] = writer_file
qpdf.merge(pdf_pages, output_file)
qpdf.merge(input_files=pdf_pages, output_file=output_file,
min_version=context.get_pdfinfo().min_version)
def merge_sidecars(
+7 -1
View File
@@ -116,4 +116,10 @@ def test_no_contents(resources):
pdf = pdfinfo.PdfInfo(filename)
assert len(pdf[0].images) == 0
assert pdf[0].has_text == False
assert pdf[0].has_text == False
def test_oversized_page(resources):
pdf = pdfinfo.PdfInfo(resources / 'poster.pdf')
image = pdf[0].images[0]
assert image.width * image.xres > 200, "this is supposed to be oversized"