Initial commit: Klammertext source distribution
Curated source subset assembled by klammertext-dev's doc/make_dist.sh: the Klammermachine (mac), the Standard Klammer Set (sks), the commands (com), editor plugins and install guides (doc), a test subset (tst), and lib/bin placeholders. Builds with 'make -C com'. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
35
sks/image/css/image.css
Normal file
35
sks/image/css/image.css
Normal file
@@ -0,0 +1,35 @@
|
||||
|
||||
|
||||
img {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.image_grid_row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between; /* center; */
|
||||
align-content: center;
|
||||
align-items: flex-start;
|
||||
/*gap: 0px;*/
|
||||
}
|
||||
|
||||
.image_grid {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
align-items: flex-start;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.image_border {
|
||||
border: solid gray 1px;
|
||||
}
|
||||
|
||||
.image_margin {
|
||||
margin-top: 2em;
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
1
sks/image/css/list.txt
Normal file
1
sks/image/css/list.txt
Normal file
@@ -0,0 +1 @@
|
||||
image.css
|
||||
59
sks/image/image.k
Normal file
59
sks/image/image.k
Normal file
@@ -0,0 +1,59 @@
|
||||
#[
|
||||
@@@state image_search_path.list :create Series of directories for image search
|
||||
:value . imgsrc ../imgsrc ../dbg
|
||||
@@@
|
||||
]#
|
||||
|
||||
@@@state Image_search_path :desc Series of directories for image search
|
||||
:value . imgsrc ../imgsrc ../dbg
|
||||
@@@
|
||||
|
||||
@@@state Image_output_dir :desc Output directory for images :value img @@@
|
||||
|
||||
@@@state Image_default_width :desc Default width for images :value .667w @@@
|
||||
|
||||
#
|
||||
# @@@argtype pixels | a pixel count :pattern \d+px @@@
|
||||
# @@image.k basename : Image read from a file @@
|
||||
# @@image.html basename | width | height : @eval import image ; result = image.image(K) eval@ @@
|
||||
@@image
|
||||
basename
|
||||
:id
|
||||
:width.length .5w
|
||||
@caption_arguments@
|
||||
:vmargin.bool true
|
||||
:center.bool true
|
||||
:hpos.hpos center
|
||||
:rel
|
||||
:abswidth.number 0.0
|
||||
:border.bool false
|
||||
:
|
||||
@eval image.Image(K) eval@
|
||||
@@
|
||||
|
||||
@@image_grid
|
||||
image_specs.rest
|
||||
:caption
|
||||
:number.bool true
|
||||
:cell_number.bool false
|
||||
:landscape.bool false
|
||||
:scale.number 0.98
|
||||
:caption_side.side bottom
|
||||
:caption_side_center.bool true
|
||||
#
|
||||
:thumbnail.bool false
|
||||
:allow_break.bool true
|
||||
# :indent.length
|
||||
# :xmargin.length
|
||||
:id
|
||||
:captionfont
|
||||
:caption_width.float .9
|
||||
:rel
|
||||
:hsep.number 0.02
|
||||
:
|
||||
@eval image_grid.Image_grid(K) eval@
|
||||
@@
|
||||
|
||||
@@fig spec.figure_id :
|
||||
@reference *spec* | Figure @
|
||||
@@
|
||||
148
sks/image/image.py
Normal file
148
sks/image/image.py
Normal file
@@ -0,0 +1,148 @@
|
||||
import sys
|
||||
import os
|
||||
_klammertext_home = os.environ.get('KLAMMERTEXT_HOME', os.path.expanduser('~/projects/klammertext/K'))
|
||||
sys.path.append(os.path.join(_klammertext_home, 'sks/kutil'))
|
||||
sys.path.append(os.path.join(_klammertext_home, 'sks/target'))
|
||||
import re
|
||||
import os
|
||||
import shutil
|
||||
import kutil
|
||||
import html_util
|
||||
import latex_util
|
||||
import klammer_base
|
||||
import image_cache
|
||||
import pprint
|
||||
from html_util import E
|
||||
import sys
|
||||
|
||||
class Image(klammer_base.Klammer_base):
|
||||
id = 0
|
||||
def __init__(self, K, as_string=True, width=None):
|
||||
super().__init__(K)
|
||||
# pprint.pprint(self.__dict__)
|
||||
# self.show()
|
||||
# /dev/shm is a fast RAM-backed tmpfs on Linux; on macOS it does not
|
||||
# exist, so fall back to the platform temp directory.
|
||||
cache_dir = "/dev/shm"
|
||||
if not os.path.isdir(cache_dir):
|
||||
import tempfile
|
||||
cache_dir = tempfile.gettempdir()
|
||||
self.cache = image_cache.Image_cache(cache_dir, self.Image_search_path, verbose=False)
|
||||
self.basename = klammer_base.unescape_ktesc(self.basename)
|
||||
self.source, self.pwidth, self.pheight, self.file_error = self.cache.get(self.K_target, self.basename)
|
||||
if self.file_error:
|
||||
self.file_error_message = f'\nERROR: File "{self.K_input_filenames}" not found'
|
||||
self.as_string = as_string
|
||||
if width:
|
||||
self.width = width
|
||||
self.rel_image = None
|
||||
self.rel_fraction = 1.0
|
||||
if self.rel:
|
||||
self.rel_image, self.rel_pwidth, self.rel_pheight, self.file_error = self.cache.get(self.K_target, self.rel)
|
||||
self.rel_fraction = self.pwidth / self.rel_pwidth
|
||||
|
||||
if self.file_error:
|
||||
self.file_error_message = f'\nERROR: File "{self.K_input_filenames}" not found'
|
||||
|
||||
def html(self):
|
||||
img_dir = f"{self.K_output_dir}/{self.K_output_basename}/{self.Image_output_dir}"
|
||||
|
||||
# output_dir = self.K_output_dir or "."
|
||||
# input_basename = self.__dict__.get("K_input_name", "")
|
||||
# input_basename = "/" + input_basename if input_basename else ""
|
||||
# img_dir = f"{output_dir}/{self.image_output_dir}{input_basename}"
|
||||
#print("img_dir:", img_dir)
|
||||
|
||||
if not os.path.exists(img_dir):
|
||||
os.makedirs(img_dir)
|
||||
if not os.path.exists(f"{img_dir}/{os.path.basename(self.source)}"):
|
||||
shutil.copy2(self.source, img_dir)
|
||||
|
||||
# width, scale_x, scale_y = kutil.parse_length(
|
||||
# "html",
|
||||
# self.width if self.width != "none" else self.image_default_width)
|
||||
|
||||
scale_x, scale_y = 1, 1 # Old
|
||||
|
||||
result = E("img")
|
||||
if self.id:
|
||||
result.attr("id", self.id)
|
||||
|
||||
filename = f"{self.Image_output_dir}/{os.path.basename(self.source)}"
|
||||
|
||||
result.attr("src", filename).attr("alt", os.path.basename(filename))
|
||||
#if self.width:
|
||||
|
||||
# kutil.msg("rel: " + str(self.rel_fraction))
|
||||
|
||||
length, value, ltype = kutil.parse_length("html", self.width, self.rel_fraction)
|
||||
if ltype == "w":
|
||||
percent = float(value) * 100
|
||||
result.sty(f"width: {percent}%")
|
||||
else:
|
||||
result.attr("width", length)
|
||||
|
||||
if self.border:
|
||||
result.cls("image_border")
|
||||
|
||||
# print(result)
|
||||
|
||||
#result.attr("style", f"width: {width}")
|
||||
# if width and width != "none":
|
||||
# result.attr("style", f"width: {width}")
|
||||
# if width == "none":
|
||||
# if scale_x:
|
||||
# result.attr("style", "width: 0px;")
|
||||
# result.attr("scale-x", scale_x)
|
||||
# elif scale_y:
|
||||
# result.attr("style", "height: 0px;")
|
||||
# result.attr("scale-y", scale_y)
|
||||
|
||||
if self.file_error:
|
||||
self.caption += self.file_error_message
|
||||
|
||||
result = html_util.add_caption(
|
||||
result, "Figure", self.number, self.caption, self.caption_font,
|
||||
self.hpos, self.caption_side, False, self.caption_font_size)
|
||||
|
||||
result = E("div").body(result).cls("image_margin")
|
||||
|
||||
if self.as_string:
|
||||
result = str(result) + "\n"
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def tex(self):
|
||||
# kutil.msg(f"number: {self.number}")
|
||||
#selfsource, width, height = self.cache.get(self.K_target, self.basename)
|
||||
source = os.path.abspath(self.source)
|
||||
#if self.abswidth != 0:
|
||||
# width = f"{self.abswidth}\\paperwidth"
|
||||
#if self.caption or self.number:
|
||||
# width = "\\textwidth"
|
||||
#else:
|
||||
self.number = self.number == "true"
|
||||
width = kutil.parse_length("tex", self.width, self.rel_fraction)[0]
|
||||
width = re.sub("px", "pt", width)
|
||||
#result = f'\\includegraphics[width={width}]{{{source}}}'
|
||||
if self.number or self.caption:
|
||||
result = f'\\includegraphics[width=\\textwidth]{{{source}}}'
|
||||
if self.file_error:
|
||||
self.caption += self.file_error_message
|
||||
result = latex_util.add_caption(
|
||||
result, "Figure", self.number, self.caption, width,
|
||||
self.hpos, self.caption_side)
|
||||
#result, "Figure", self.number, self.caption, self.hpos, self.caption_side,
|
||||
#self.width, self.caption_side_center, self.vmargin, self.caption_margin)
|
||||
else:
|
||||
result = f'\\includegraphics[width={width}]{{{source}}}'
|
||||
result = latex_util.caption_wrapper(result, self.hpos)
|
||||
name = f"Reference-Figure-{Image.id}"
|
||||
result = f"\\hypertarget{{{name}}}{{}}\\label{{Label-{name}}}\n{result}"
|
||||
Image.id += 1
|
||||
return result
|
||||
|
||||
|
||||
def txt(self):
|
||||
return f"[Image: {self.basename}\nCaption: {self.caption}]"
|
||||
232
sks/image/image_cache.py
Normal file
232
sks/image/image_cache.py
Normal file
@@ -0,0 +1,232 @@
|
||||
"""
|
||||
Does every target has a preferred format?
|
||||
HTML - JPEG (or SVG if available?)
|
||||
LaTeX - PDF or EPS?
|
||||
Plaintext - A URL?
|
||||
EPUB - JPEG?
|
||||
"""
|
||||
|
||||
import sys, os, glob, shutil, subprocess, re, codecs
|
||||
import filecmp
|
||||
import pdf_image
|
||||
import kutil
|
||||
|
||||
import OpenImageIO as oiio
|
||||
|
||||
def find_ext(filenames, ext):
|
||||
for f in filenames:
|
||||
if f.endswith(ext):
|
||||
return f
|
||||
return None
|
||||
|
||||
def cache_update_required(source, cached):
|
||||
return cached is None \
|
||||
or not(os.path.exists(cached)) \
|
||||
or (os.path.getmtime(cached) < os.path.getmtime(source))
|
||||
|
||||
|
||||
def image_dimensions(filename):
|
||||
resize = True
|
||||
if filename.endswith(".pdf"):
|
||||
width, height = pdf_image.dimensions(filename)
|
||||
resize = False
|
||||
else:
|
||||
inp = oiio.ImageInput.open(filename)
|
||||
if not inp:
|
||||
raise Exception(f'Cannot read image "{filename}": {oiio.geterror()}')
|
||||
spec = inp.spec()
|
||||
width, height = spec.width, spec.height
|
||||
inp.close()
|
||||
return width, height, resize
|
||||
|
||||
|
||||
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):
|
||||
#self.cache_dir = kutil.cache_directory(
|
||||
self.dir = kutil.cache_directory(cache_dir, "_image_cache")
|
||||
self.search = search_path.split(" ")
|
||||
self.verbose = verbose or os.environ.get("KLAMMERTEXT_IMAGE_CACHE_DISPLAY")
|
||||
self.required_format = { "html" : ["jpg", "jpeg", "JPEG", "png", "gif", "webp"],
|
||||
"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]
|
||||
if ext in self.required_format[target]:
|
||||
cache_ext = ext
|
||||
else:
|
||||
cache_ext = required[0]
|
||||
return f"{self.dir}/{filename}.{cache_ext}"
|
||||
|
||||
|
||||
def find_file(self, target, basename):
|
||||
result = None
|
||||
candidates = []
|
||||
matches = []
|
||||
for path in self.search:
|
||||
candidates += glob.glob(f"{path}/{basename}.*")
|
||||
candidates = sorted(candidates, key=lambda p: 0 if p.endswith(".png") else 1)
|
||||
if False:
|
||||
print("Candidates:")
|
||||
for c in candidates:
|
||||
print(f" {c}")
|
||||
if candidates:
|
||||
matches = []
|
||||
for ext in self.required_format[target]:
|
||||
candidate = find_ext(candidates, ext)
|
||||
if candidate:
|
||||
matches.append(candidate)
|
||||
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]
|
||||
|
||||
result = matches[0] if matches else candidates[0]
|
||||
"""
|
||||
if len(matches) > 1:
|
||||
target = f"for .{target} format"
|
||||
print(f'{Image_cache.verbose_label} Warning: Multiple matches for "{basename}" {target}:')
|
||||
sp = " "*len(Image_cache.verbose_label)
|
||||
for f in matches:
|
||||
print(f"{sp} {f}")
|
||||
"""
|
||||
return result
|
||||
|
||||
|
||||
def find_cached_file(self, target, basename):
|
||||
exts = self.required_format.get(target)
|
||||
if exts is None:
|
||||
raise Exception(
|
||||
f'No preferred image format defined for target "{target}"')
|
||||
result = None
|
||||
for ext in exts:
|
||||
cached_filename = f"{self.dir}/{basename}.{ext}"
|
||||
if os.path.exists(cached_filename):
|
||||
return cached_filename
|
||||
return None
|
||||
|
||||
|
||||
def describe_caching(self, source_filename, cached_filename, copied,
|
||||
width, height, new_width, new_height):
|
||||
src = os.path.relpath(source_filename)
|
||||
if src[:2] == "..":
|
||||
src = source_filename
|
||||
dest = os.path.splitext(cached_filename)[1]
|
||||
dim = "" if width == new_width else f" [{new_width}x{new_height}]"
|
||||
desc = "(copied)" if copied else f"-> {dest}{dim}"
|
||||
#print(f"{Image_cache.verbose_label} To cache: {src} [{width}x{height}] {desc}")
|
||||
kutil.msg("To cache: {src} [{width}x{height}] {desc}")
|
||||
|
||||
def update_cache(self, target, basename, source_filename):
|
||||
cached_filename = self.construct_cache_filename(target, basename)
|
||||
width, height, resize = image_dimensions(source_filename)
|
||||
new_width = width
|
||||
new_height = height
|
||||
|
||||
source_format = os.path.splitext(source_filename)[-1][1:]
|
||||
#print("Source:", source_format, basename)
|
||||
if source_format in self.required_format[target]:
|
||||
cached_filename = f"{os.path.dirname(cached_filename)}/{os.path.basename(source_filename)}"
|
||||
|
||||
copied = False
|
||||
if resize and width > Image_cache.max_width:
|
||||
new_width = Image_cache.max_width
|
||||
new_height = int(round(new_width * float(height) / float(width)))
|
||||
if source_filename.endswith(".pdf"):
|
||||
pdf_image.convert(source_filename, cached_filename)
|
||||
buf = oiio.ImageBuf(cached_filename)
|
||||
else:
|
||||
buf = oiio.ImageBuf(source_filename)
|
||||
roi = oiio.ROI(0, new_width, 0, new_height, 0, 1, 0, buf.nchannels)
|
||||
resized = oiio.ImageBufAlgo.resize(buf, roi=roi)
|
||||
resized.write(cached_filename)
|
||||
elif source_format in self.required_format[target]:
|
||||
#print("COPY", source_filename, cached_filename)
|
||||
if not os.path.exists(cached_filename) or \
|
||||
not filecmp.cmp(source_filename, cached_filename, shallow=False):
|
||||
shutil.copy2(source_filename, cached_filename)
|
||||
copied = True
|
||||
else:
|
||||
try:
|
||||
if source_filename.endswith(".pdf"):
|
||||
pdf_image.convert(source_filename, cached_filename)
|
||||
else:
|
||||
buf = oiio.ImageBuf(source_filename)
|
||||
if not buf.has_error:
|
||||
buf.write(cached_filename)
|
||||
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)
|
||||
|
||||
if self.verbose:
|
||||
self.describe_caching(source_filename, cached_filename, copied,
|
||||
width, height, new_width, new_height)
|
||||
return cached_filename, new_width, new_height
|
||||
|
||||
|
||||
def get(self, target, basename):
|
||||
cached_filename = self.find_cached_file(target, basename)
|
||||
source_filename = self.find_file(target, basename)
|
||||
if cache_update_required(source_filename, cached_filename):
|
||||
cached_filename, width, height = self.update_cache(target, basename, source_filename)
|
||||
else:
|
||||
width, height, _ = image_dimensions(cached_filename)
|
||||
if self.verbose:
|
||||
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
|
||||
|
||||
def clear(self):
|
||||
shutil.rmtree(self.dir)
|
||||
os.makedirs(self.dir)
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
def getter(format, basename):
|
||||
C.get(format, basename)
|
||||
C.get(format, basename)
|
||||
|
||||
def label(format=""):
|
||||
print("-"*80)
|
||||
print(format)
|
||||
|
||||
home = os.environ.get("HOME")
|
||||
C = Image_cache(f"{home}",
|
||||
f"{home}/projects/klammertext/K/doc/handbook/imgsrc".split())
|
||||
a = "heringsdorf_stairwell"
|
||||
b = "usedom_clouds"
|
||||
|
||||
C.clear()
|
||||
label("HTML")
|
||||
getter("html", a)
|
||||
getter("html", b)
|
||||
label("LATEX")
|
||||
getter("latex", a)
|
||||
getter("latex", b)
|
||||
label()
|
||||
|
||||
114
sks/image/image_grid.py
Normal file
114
sks/image/image_grid.py
Normal file
@@ -0,0 +1,114 @@
|
||||
import os
|
||||
import re
|
||||
import kutil
|
||||
import klammer_base
|
||||
import image
|
||||
import latex_util
|
||||
import html
|
||||
import html_util
|
||||
from html_util import E
|
||||
import image_cache
|
||||
|
||||
#import kutil
|
||||
|
||||
class Kargs:
|
||||
def __init__(self, target, K, **kwargs):
|
||||
self.__dict__ = kwargs
|
||||
self.center = False
|
||||
self.caption_font = "i"
|
||||
self.caption_side = "bottom"
|
||||
self.caption_side_center = False
|
||||
self.Image_search_path = K.Image_search_path
|
||||
self.number = K.cell_number
|
||||
self.hpos = "none"
|
||||
self.K_target = target
|
||||
self.K_input_filenames = K.K_input_filenames
|
||||
self.K_output_dir = K.K_output_dir
|
||||
self.K_output_basename = K.K_output_basename
|
||||
self.image_output_dir = K.Image_output_dir
|
||||
self.K_toc_only = False
|
||||
self.id = ""
|
||||
self.as_string = False
|
||||
self.rel = None
|
||||
|
||||
def img(K, target, basename, caption, width):
|
||||
result = image.Image(
|
||||
Kargs(target, K, basename=basename, caption=caption), width=width)
|
||||
return result
|
||||
|
||||
class Image_grid(klammer_base.Klammer_base):
|
||||
def __init__(self, K):
|
||||
super().__init__(K)
|
||||
self.K = K
|
||||
image_pat = re.compile("([^\s]+)\s*(.*?)", re.S)
|
||||
|
||||
self.images = []
|
||||
self.basenames = []
|
||||
self.captions = []
|
||||
|
||||
for row in kutil.rest_args(self.image_specs, dimensions=2):
|
||||
self.images.append(
|
||||
[e.groups() for e in [image_pat.fullmatch(s.strip()) for s in row]])
|
||||
self.basenames.append([e[0] for e in self.images[-1]])
|
||||
self.captions.append([e[1] for e in self.images[-1]])
|
||||
self.cache = image_cache.Image_cache(
|
||||
os.path.dirname(os.path.abspath(self.K_input_filenames)),
|
||||
self.Image_search_path,
|
||||
verbose=False)
|
||||
|
||||
self.margin = 0.01
|
||||
|
||||
self.grid = []
|
||||
for basenames, captions in zip(self.basenames, self.captions):
|
||||
cell_widths = self.widths(self.K_target, basenames)
|
||||
row = []
|
||||
for basename, caption, cell_width in zip(basenames, captions, cell_widths):
|
||||
row.append([basename, caption, cell_width])
|
||||
self.grid.append(row)
|
||||
|
||||
def widths(self, target, basenames):
|
||||
dims = []
|
||||
for basename in basenames:
|
||||
dims.append(self.cache.get("html", basename))
|
||||
heights = [e[2] for e in dims]
|
||||
total_height = sum(heights)
|
||||
height_scales = [h/total_height for h in heights]
|
||||
widths = [e[1] for e in dims]
|
||||
widths = [e[0] / e[1] for e in zip(widths, height_scales)]
|
||||
total_width = sum(widths)
|
||||
margin = self.margin * total_width
|
||||
total_width += margin * (len(widths) - 1)
|
||||
result = [w/total_width for w in widths]
|
||||
result = [f"{w:0.6f}w" for w in result]
|
||||
return result
|
||||
|
||||
def html(self):
|
||||
result = ""
|
||||
for row in self.grid:
|
||||
row_cells = ""
|
||||
for basename, caption, cell_width in row:
|
||||
cell_image = img(self.K, "html", basename, caption, cell_width).html()
|
||||
row_cells += str(cell_image).strip() + "\n"
|
||||
result += str(E("div").body(row_cells).cls("image_grid_row").attr("data-row-size", len(row)))
|
||||
result = E("div").body(result).cls("image_grid")
|
||||
if self.number or self.caption:
|
||||
result = html_util.add_caption(result, "Figure", self.number, self.caption)
|
||||
return str(result)
|
||||
|
||||
def tex(self):
|
||||
result = ""
|
||||
for row in self.grid:
|
||||
row_cells = ""
|
||||
for basename, caption, cell_width in row:
|
||||
cell_image = img(self.K, "tex", basename, caption, cell_width).tex()
|
||||
row_cells += str(cell_image).strip() + f"\\hspace*{{{self.margin}\\textwidth}}%\n"
|
||||
#result += "\\begin{minipage}[t]{\\textwidth}" \
|
||||
# + f"{{{row_cells}}}\n\\end{{minipage}}
|
||||
result += latex_util.minipage(row_cells, vertical="t") + f"\\\\[{self.margin}\\textwidth]\n"
|
||||
if self.number or self.caption:
|
||||
result = latex_util.add_caption(
|
||||
result.strip(), "Figure", self.number, self.caption, "\\textwidth")
|
||||
else:
|
||||
#result = f"\\begin{{minipage}}{{\\textwidth}}\n\\centering {result}\n\\end{{minipage}}\n"
|
||||
result = latex_util.minipage("\\centering " + result, vertical="t") + "\n"
|
||||
return result
|
||||
51
sks/image/js/image.js
Normal file
51
sks/image/js/image.js
Normal file
@@ -0,0 +1,51 @@
|
||||
function resize_images() {
|
||||
//console.log("resize_images");
|
||||
K.getv("#text img[data-scale-x]").forEach(
|
||||
function (img) {
|
||||
let text_area = K.get("#text"); //img.parentNode.parentNode;
|
||||
let scale_x = parseFloat(K.attr(img, "data-scale-x"));
|
||||
let style = window.getComputedStyle(text_area); //text_area.currentStyle;
|
||||
let parent_width = text_area.clientWidth
|
||||
- parseFloat(style.paddingLeft) - parseFloat(style.paddingRight);
|
||||
let container = img.parentNode.parentNode;
|
||||
if (container.classList.contains("image_grid_row")) {
|
||||
if (!container.style) {
|
||||
container.style = {};
|
||||
}
|
||||
container.style.width = parent_width + "px";
|
||||
} else if (container.parentNode.classList.contains("image_grid_row")) {
|
||||
container = container.parentNode;
|
||||
if (!container.style) {
|
||||
container.style = {};
|
||||
}
|
||||
container.style.width = parent_width + "px";
|
||||
}
|
||||
let image_width = scale_x * parent_width;
|
||||
if (!img.style) {
|
||||
img.style = {};
|
||||
}
|
||||
img.style.width = image_width + "px";
|
||||
let caption = img.parentNode.getElementsByClassName("caption")[0];
|
||||
if (typeof caption !== 'undefined') {
|
||||
if (typeof caption.style == 'undefined') {
|
||||
caption.style = {};
|
||||
}
|
||||
caption.style.width = image_width + "px";
|
||||
}
|
||||
});
|
||||
|
||||
K.getv(".caption_right, .caption_left").forEach(
|
||||
function (img) {
|
||||
K.width(img, img.parentNode.clientWidth);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
window.addEventListener("load", function (event) {
|
||||
window.addEventListener(
|
||||
"resize",
|
||||
function (event) {
|
||||
resize_images();
|
||||
});
|
||||
resize_images();
|
||||
});
|
||||
81
sks/image/pdf_image.py
Normal file
81
sks/image/pdf_image.py
Normal file
@@ -0,0 +1,81 @@
|
||||
import re, subprocess, os
|
||||
|
||||
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(
|
||||
["/usr/bin/pdfinfo", filename], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
out, err = sp.communicate()
|
||||
out = out.decode("ascii")
|
||||
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):
|
||||
width, height = None, None
|
||||
try:
|
||||
width, height = dimensions_from_prolog(filename)
|
||||
except:
|
||||
if width is None:
|
||||
width, height = dimensions_from_pdfinfo(filename)
|
||||
if width is None:
|
||||
raise Exception(f'Could not determine the width and height of "{filename}"')
|
||||
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"
|
||||
command = "/usr/bin/pdftoppm"
|
||||
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()
|
||||
if err:
|
||||
raise Exception(f'Error creating image file from "{pdf_filename}": {err}')
|
||||
2
sks/image/sty/image.sty
Normal file
2
sks/image/sty/image.sty
Normal file
@@ -0,0 +1,2 @@
|
||||
\usepackage[xetex]{graphicx}
|
||||
\usepackage[export]{adjustbox}
|
||||
Reference in New Issue
Block a user