Images: a PDF figure finds poppler, the lookup admits image files only, a missing image is an error
Assembled from development commit 8ed24ef03b36. Since ee8bcf5: a PDF file used
as an image finds poppler's pdfinfo and pdftoppm on the PATH instead of
/usr/bin, and says which package to install when they are missing (the
Linux, macOS and Windows pages name it); the image lookup considers
image files only, so a source or log file beside a figure is never taken
for it; and an @image whose file cannot be found is an error naming the
basename and the search path, instead of a silent red placeholder. Two
test suites that had relied on that placeholder now supply a real image;
a new suite covers the PDF figure.
This commit is contained in:
1 parent
ee8bcf5300
commit
1b00738654
7 files changed
+79
-43
No files matched your search
+23
-19
@@ -45,6 +45,14 @@ def image_dimensions(filename):
|
||||
# html takes them).
|
||||
photograph_formats = {"jpg", "jpeg", "png", "tif", "tiff"}
|
||||
|
||||
# Every extension an @image source may have: the targets' preferred formats
|
||||
# plus what a conversion accepts (OpenImageIO for the rasters, pdftoppm for
|
||||
# a pdf). find_file() admits no other file -- the glob "basename.*" also
|
||||
# returns a basename.kt, .aux or .log sitting beside a figure, and the old
|
||||
# fallback took the first of those blindly, so a document whose PDF figure
|
||||
# shared its stem with its source got "Cannot read image" (2026-09-25).
|
||||
image_formats = {"jpg", "jpeg", "png", "gif", "webp", "tif", "tiff", "bmp", "pdf", "eps"}
|
||||
|
||||
def is_photograph(filename):
|
||||
"""True if the file is a raster with no alpha channel: a photograph
|
||||
(or a grayscale one) that JPEG stores well. A png with transparency,
|
||||
@@ -63,7 +71,6 @@ def is_photograph(filename):
|
||||
class Image_cache:
|
||||
max_width = 1600 # Should be a state variable
|
||||
cache_basedir = "_klammertext_imgsrc" # Should be a state variable
|
||||
error_marker_filename = "_error_marker.png"
|
||||
verbose_label = " [klammertext]" # Should parameterize verbosity
|
||||
def __init__(self, cache_dir, search_path, verbose=False, quality=100):
|
||||
#self.cache_dir = kutil.cache_directory(
|
||||
@@ -89,17 +96,10 @@ class Image_cache:
|
||||
"pdf" : ["png", "jpg", "jpeg", "JPEG", "pdf", "eps"],
|
||||
"tex" : ["png", "jpg", "jpeg", "JPEG", "pdf", "eps"],
|
||||
"latex" : ["png", "jpg", "jpeg", "JPEG", "pdf", "eps"] }
|
||||
self.error_image_filename = f"{self.dir}/{Image_cache.error_marker_filename}"
|
||||
if not os.path.exists(self.dir):
|
||||
os.makedirs(self.dir)
|
||||
self.search = self.search + [f"{os.path.dirname(cache_dir)}/imgsrc"]
|
||||
|
||||
def make_error_marker(self):
|
||||
if not os.path.exists(self.error_image_filename):
|
||||
err_buf = oiio.ImageBuf(oiio.ImageSpec(300, 100, 3, oiio.UINT8))
|
||||
oiio.ImageBufAlgo.fill(err_buf, (1.0, 0.0, 0.0))
|
||||
err_buf.write(self.error_image_filename)
|
||||
|
||||
def construct_cache_filename(self, target, filename):
|
||||
basename, ext = os.path.splitext(filename)
|
||||
required = self.required_format[target]
|
||||
@@ -119,7 +119,6 @@ class Image_cache:
|
||||
"""Whether this source is written to the cache as a quality-N JPEG."""
|
||||
return self.quality < 100 \
|
||||
and target in self.required_format \
|
||||
and source_filename != self.error_image_filename \
|
||||
and is_photograph(source_filename)
|
||||
|
||||
def find_file(self, target, basename):
|
||||
@@ -128,6 +127,8 @@ class Image_cache:
|
||||
matches = []
|
||||
for path in self.search:
|
||||
candidates += glob.glob(f"{path}/{basename}.*")
|
||||
candidates = [c for c in candidates
|
||||
if os.path.splitext(c)[1][1:].lower() in image_formats]
|
||||
candidates = sorted(candidates, key=lambda p: 0 if p.endswith(".png") else 1)
|
||||
if False:
|
||||
print("Candidates:")
|
||||
@@ -142,10 +143,14 @@ class Image_cache:
|
||||
if not matches:
|
||||
result = candidates[0]
|
||||
else:
|
||||
kutil.msg(kutil.red_text(f'No image with basename "{basename}" found in search path:'))
|
||||
kutil.msg(kutil.red_text(" | " + " | ".join(self.search) + " | "))
|
||||
self.make_error_marker()
|
||||
matches = [self.error_image_filename]
|
||||
# An error, as any other missing input is: a document with an
|
||||
# image that is not there does not render. (Until 2026-09-25
|
||||
# this substituted a red marker image and printed a message --
|
||||
# to a stdout the embedded interpreter captures, so the marker
|
||||
# appeared and nothing was said.)
|
||||
raise Exception(
|
||||
f'No image with basename "{basename}" found in the image search path: '
|
||||
+ " | ".join(self.search))
|
||||
|
||||
result = matches[0] if matches else candidates[0]
|
||||
"""
|
||||
@@ -238,10 +243,10 @@ class Image_cache:
|
||||
else:
|
||||
raise Exception(buf.geterror())
|
||||
except Exception as err:
|
||||
print(f'Cannot convert "{source_filename}" to '
|
||||
f'"{os.path.splitext(cached_filename)[-1][1:]}":')
|
||||
print(" ", err)
|
||||
sys.exit(1)
|
||||
# Raised, not printed and exited: the print would go to the
|
||||
# captured stdout and say nothing; the error report says it.
|
||||
raise Exception(f'Cannot convert "{source_filename}" to '
|
||||
f'"{os.path.splitext(cached_filename)[-1][1:]}": {err}')
|
||||
|
||||
if self.verbose:
|
||||
self.describe_caching(source_filename, cached_filename, copied,
|
||||
@@ -265,8 +270,7 @@ class Image_cache:
|
||||
base = os.path.basename(cached_filename)
|
||||
#print(f"{Image_cache.verbose_label} From cache: {base} [{width}x{height}]")
|
||||
kutil.msg("From cache: {base} [{width}x{height}]")
|
||||
# print("image_cache:", cached_filename, Image_cache.error_marker_filename)
|
||||
return cached_filename, width, height, cached_filename == Image_cache.error_marker_filename
|
||||
return cached_filename, width, height
|
||||
|
||||
def clear(self):
|
||||
shutil.rmtree(self.dir)
|
||||
|
||||
Reference in new issue
Block a user