#!/usr/bin/env python3 """Cut inline type-specimen glyphs out of a page at native resolution. crop_inline.py PAGE NAME=SPEC [NAME=SPEC ...] SPEC is an ink-run index from work/boxes.json, optionally with a side: 13 the whole run 60:right split the run at its widest internal gap, keep the right part 14:trimright drop only the rightmost element (a trailing comma, say) 6:mid drop the first and last elements, keeping the middle 5:last keep what follows the final gap (when the widest gap is elsewhere) 5+59 start 59 px into the run (last resort: no gap is detectable) 4~610 keep only the leftmost 610 px of the run 0-11 the span from run 0 to run 11 (for a whole specimen line) Writes plates/inline/p-.png (1-bit when the source is bitonal).""" import subprocess, io, json, sys import numpy as np from PIL import Image page = int(sys.argv[1]) boxes = json.load(open(f"work/boxes{page}.json")) png = subprocess.run(["pdftoppm","-png","-r","300","-f",str(page),"-l",str(page), "source/10731406.pdf"], capture_output=True).stdout im = Image.open(io.BytesIO(png)).convert("L") arr = np.array(im) < 128 def split(box, side): x0, y0, x1, y1 = box col = arr[y0:y1, x0:x1].mean(0) > 0.004 gaps, i = [], 0 while i < len(col): if not col[i]: j = i while j < len(col) and not col[j]: j += 1 gaps.append((j-i, i, j)); i = j else: i += 1 gaps = [g for g in gaps if g[1] > 0 and g[2] < len(col)] if not gaps: return box if side in ("left", "right"): _, gi, gj = max(gaps) return (x0, y0, x0+gi, y1) if side == "left" else (x0+gj, y0, x1, y1) if side == "last": # keep only what follows the FINAL gap return (x0+gaps[-1][2], y0, x1, y1) if side == "first": # keep only what precedes the FIRST gap return (x0, y0, x0+gaps[0][1], y1) if side == "mid": # drop the first and last elements (`, G ,') return (x0+gaps[0][2], y0, x0+gaps[-1][1], y1) if side == "trimright": # drop the last element only _, gi, gj = gaps[-1] return (x0, y0, x0+gi, y1) _, gi, gj = gaps[0] # trimleft: drop the first element only return (x0+gj, y0, x1, y1) for spec in sys.argv[2:]: name, s = spec.split("=") side = None if ":" in s: s, side = s.split(":") off = 0; keep = None if "~" in s: # 4~610 — keep only the leftmost 610 px of run 4 s, k = s.split("~") keep = int(k) if "+" in s: # 5+59 — start 59 px into run 5, when the space s, o = s.split("+") # between a word and the glyph is too noisy to off = int(o) # register as a gap if "-" in s: a, b = (int(v) for v in s.split("-")) box = (min(boxes[i][1] for i in range(a,b+1)), min(boxes[i][2] for i in range(a,b+1)), max(boxes[i][3] for i in range(a,b+1)), max(boxes[i][4] for i in range(a,b+1))) else: box = tuple(boxes[int(s)][1:]) if side: box = split(box, side) if off: box = (box[0]+off, box[1], box[2], box[3]) if keep: box = (box[0], box[1], box[0]+keep, box[3]) c = im.crop(box) if len(np.unique(np.array(c))) <= 2: c = c.point(lambda v: 255 if v > 128 else 0).convert("1") out = f"plates/inline/p{page:04d}-{name}.png" c.save(out, optimize=True); print(out, c.size)