2026-09-25 21:11:33 +02:00
|
|
|
import re, subprocess, os, shutil
|
|
|
|
|
|
|
|
|
|
# The two poppler tools a PDF image needs, found on PATH -- never by an
|
|
|
|
|
# absolute path: /usr/bin/pdfinfo exists on a Debian-family Linux and
|
|
|
|
|
# nowhere else (Homebrew puts poppler under /opt/homebrew/bin, MSYS2 under
|
|
|
|
|
# C:/msys64/ucrt64/bin), and a user on those systems saw "No such file"
|
|
|
|
|
# with no hint of what to install. Mirrors latex_util.cpp's report of a
|
|
|
|
|
# missing xelatex.
|
|
|
|
|
def _tool(name):
|
|
|
|
|
path = shutil.which(name)
|
|
|
|
|
if path is None:
|
|
|
|
|
raise Exception(
|
|
|
|
|
f'The "{name}" command required to use a PDF file as an image is not '
|
|
|
|
|
'installed. It is part of poppler: on Ubuntu "sudo apt-get install '
|
|
|
|
|
'poppler-utils", with Homebrew "brew install poppler", on Windows '
|
|
|
|
|
'MSYS2\'s "pacman -S mingw-w64-ucrt-x86_64-poppler" and '
|
|
|
|
|
'C:\\msys64\\ucrt64\\bin on the Path')
|
|
|
|
|
return path
|
2026-07-18 18:48:23 +02:00
|
|
|
|
|
|
|
|
def dimensions_from_prolog(filename):
|
|
|
|
|
mediabox_pat = re.compile(r"/MediaBox\s+\[(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\]", re.S)
|
|
|
|
|
width_pat = re.compile(r"/Width\s+(\d+)", re.S)
|
|
|
|
|
height_pat = re.compile(r"/Height\s+(\d+)", re.S)
|
|
|
|
|
width = None
|
|
|
|
|
height = None
|
|
|
|
|
with open(filename, mode="rb") as fp:
|
|
|
|
|
line = fp.readline().decode("ascii")
|
|
|
|
|
while line:
|
|
|
|
|
match = mediabox_pat.match(line)
|
|
|
|
|
if match:
|
|
|
|
|
x1, y1, x2, y2 = [int(e) for e in match.groups()]
|
|
|
|
|
width = x2 - x1
|
|
|
|
|
height = y2 - y1
|
|
|
|
|
else:
|
|
|
|
|
match = width_pat.match(line)
|
|
|
|
|
if match:
|
|
|
|
|
width = int(match.group(1))
|
|
|
|
|
else:
|
|
|
|
|
match = height_pat.match(line)
|
|
|
|
|
if match:
|
|
|
|
|
height = int(match.group(1))
|
|
|
|
|
if width and height:
|
|
|
|
|
break
|
|
|
|
|
line = fp.readline().decode("ascii")
|
|
|
|
|
return width, height
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def dimensions_from_pdfinfo(filename):
|
|
|
|
|
sp = subprocess.Popen(
|
2026-09-25 21:11:33 +02:00
|
|
|
[_tool("pdfinfo"), filename], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
2026-07-18 18:48:23 +02:00
|
|
|
out, err = sp.communicate()
|
2026-09-25 21:11:33 +02:00
|
|
|
out = out.decode("ascii", errors="replace")
|
2026-07-18 18:48:23 +02:00
|
|
|
width, height = None, None
|
|
|
|
|
if out:
|
|
|
|
|
pat = re.compile(r"Page size:\s+([\d.]+) x ([\d.]+) pts", re.S)
|
|
|
|
|
match = pat.search(out)
|
|
|
|
|
if match:
|
|
|
|
|
width, height = [int(round(float(e))) for e in match.groups()]
|
|
|
|
|
return width, height
|
|
|
|
|
|
|
|
|
|
# Doesn't agree with pdfinfo; why?
|
|
|
|
|
# from pdf2image import convert_from_path
|
|
|
|
|
# def dimensions_from_pdf2image(filename):
|
|
|
|
|
# width, height = None, None
|
|
|
|
|
# if pdf2image_imported:
|
|
|
|
|
# img = convert_from_path(filename)[0]
|
|
|
|
|
# width, height = img.size
|
|
|
|
|
# return width, height
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def dimensions(filename):
|
2026-09-25 21:11:33 +02:00
|
|
|
# The prolog scan answers for a PDF whose MediaBox is in its first
|
|
|
|
|
# lines; a compressed or binary prolog either raises (a non-ASCII byte)
|
|
|
|
|
# or answers None, and EITHER is pdfinfo's turn -- the None case once
|
|
|
|
|
# fell straight through to the error below without asking it.
|
2026-07-18 18:48:23 +02:00
|
|
|
width, height = None, None
|
|
|
|
|
try:
|
|
|
|
|
width, height = dimensions_from_prolog(filename)
|
2026-09-25 21:11:33 +02:00
|
|
|
except Exception:
|
|
|
|
|
pass
|
|
|
|
|
if width is None or height is None:
|
|
|
|
|
width, height = dimensions_from_pdfinfo(filename)
|
|
|
|
|
if width is None or height is None:
|
|
|
|
|
raise Exception(f'Could not determine the width and height of "{filename}"')
|
2026-07-18 18:48:23 +02:00
|
|
|
return width, height
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def convert(pdf_filename, image_filename):
|
|
|
|
|
if image_filename.endswith(".jpg"):
|
|
|
|
|
arg1 = "-jpeg"
|
|
|
|
|
elif image_filename.endswith(".png"):
|
|
|
|
|
arg1 = "-png"
|
|
|
|
|
else:
|
|
|
|
|
raise Exception(f'PDF file "{pdf_filename}" can only be converted to PNG or JPEG')
|
|
|
|
|
arg2 = "-singlefile"
|
2026-09-25 21:11:33 +02:00
|
|
|
command = _tool("pdftoppm")
|
2026-07-18 18:48:23 +02:00
|
|
|
basename = os.path.splitext(image_filename)[0]
|
|
|
|
|
sp = subprocess.Popen(
|
|
|
|
|
[command, arg1, arg2, pdf_filename, basename],
|
|
|
|
|
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
|
|
|
out, err = sp.communicate()
|
2026-09-25 21:11:33 +02:00
|
|
|
if sp.returncode != 0 or not os.path.exists(image_filename):
|
|
|
|
|
raise Exception(f'Error creating image file from "{pdf_filename}": '
|
|
|
|
|
f'{err.decode("utf-8", errors="replace").strip()}')
|