{small_v(it["question"])}
' # ── p1:一語を大きく ───────────────────────────────────── def p1(it, n): key, q = it["key"], it["question"] kv = round(min(22, 46 / vlen(key)), 2) # 縦に置くときの大きさ(高さ 56.25cqw に収める) kh = round(min(26, 84 / hlen(key)), 2) # 横に置くときの大きさ(幅 100cqw に収める) s = 3.3 lay = n % 4 if lay == 0: # 一語を縦に右端、問いを縦にその左 css = f""" .k {{ right: 7cqw; top: 4.5cqw; font-size: {kv}cqw; }} .s {{ right: {7 + kv + 5}cqw; top: 6cqw; font-size: {s}cqw; line-height: 1.9; }}""" body = f'{esc(key, True)}
{small_v(q)}
' elif lay == 1: # 一語を横に下へ大きく(少し下にはみ出す)、問いを横に左上 css = f""" .k {{ left: 4.5cqw; bottom: -{kh * 0.12:.2f}cqw; font-size: {kh}cqw; }} .s {{ left: 6cqw; top: 6cqw; font-size: {s}cqw; line-height: 1.8; }}""" body = f'{esc(key, False)}
{small_h(q)}
' elif lay == 2: # 一語を横に右上、問いを縦に左 css = f""" .k {{ right: 5cqw; top: 5cqw; font-size: {min(kh, 22)}cqw; text-align: right; }} .s {{ left: 6cqw; top: 6cqw; font-size: {s}cqw; line-height: 1.9; }}""" body = f'{esc(key, False)}
{small_v(q)}
' else: # 一語を縦に左、問いを横に右下 css = f""" .k {{ left: 6cqw; top: 4.5cqw; font-size: {kv}cqw; }} .s {{ right: 6cqw; bottom: 6cqw; font-size: {s}cqw; line-height: 1.8; text-align: right; }}""" body = f'{esc(key, True)}
{small_h(q)}
' return css, body # ── p2:黒い面で分ける ─────────────────────────────────── def p2(it, n): key, q = it["key"], it["question"] s = 3.4 lay = n % 4 if lay == 0: # 黒い面を右に縦長、一語は縦 w = 36 kv = round(min(19, 44 / vlen(key), w * 0.62), 2) css = f""" .bk {{ right: 0; top: 0; bottom: 0; width: {w}cqw; display: flex; align-items: center; justify-content: center; }} .k {{ font-size: {kv}cqw; }} .s {{ right: {w + 7}cqw; top: 6cqw; font-size: {s}cqw; line-height: 1.9; }}""" body = f'{esc(key, True)}
{small_v(q)}
' elif lay == 1: # 黒い面を下に帯、一語は横 hgt = 25 kh = round(min(17, 84 / hlen(key)), 2) css = f""" .bk {{ left: 0; right: 0; bottom: 0; height: {hgt}cqw; display: flex; align-items: center; padding-left: 6cqw; }} .k {{ font-size: {kh}cqw; }} .s {{ left: 6cqw; top: 6cqw; font-size: {s}cqw; line-height: 1.8; }}""" body = f'{esc(key, False)}
{small_h(q)}
' elif lay == 2: # 黒い面を左に、一語は縦。問いは右に縦 w = 32 kv = round(min(17, 44 / vlen(key), w * 0.6), 2) css = f""" .bk {{ left: 0; top: 0; bottom: 0; width: {w}cqw; display: flex; align-items: center; justify-content: center; }} .k {{ font-size: {kv}cqw; }} .s {{ right: 8cqw; top: 6cqw; font-size: {s}cqw; line-height: 1.9; }}""" body = f'{esc(key, True)}
{small_v(q)}
' else: # 黒い面を右上に、一語は横。問いは左下に横 w, hgt = 46, 30 kh = round(min(15, (w - 8) / hlen(key)), 2) css = f""" .bk {{ right: 0; top: 0; width: {w}cqw; height: {hgt}cqw; display: flex; align-items: center; justify-content: center; }} .k {{ font-size: {kh}cqw; }} .s {{ left: 6cqw; bottom: 6cqw; font-size: {s}cqw; line-height: 1.8; }}""" body = f'{esc(key, False)}
{small_h(q)}
' return css, body # ── p3:文の強弱 ───────────────────────────────────────── # 強弱の付け方(大きさと太さ)だけを変えた型。組み(位置と縦・横)は4通りを順に回す # small: 小さい文字の大きさ(縦, 横)/ big: 一語の大きさの上限 / w: 太さ(一語, 残り) # mid: 問いの結びの行を中くらいにする(大きさ, 太さ)。None なら2段階 P3_STYLES = { "p3": dict(small=(3.6, 3.8), big=(13, 15), w=(900, 400), mid=None), # 今の案3:一語を大きく、残りを小さく "p3a": dict(small=(4.4, 4.6), big=(7.5, 8.5), w=(700, 400), mid=None), # 差を控えめに:一語は今の半分ほど、文は大きめ "p3b": dict(small=(4.2, 4.4), big=(16, 18), w=(900, 300), mid=(6.2, 700), pad=4.5), # 3段階:一語・問いの結び・残り。余白を詰め、文字を大きく "p3c": dict(small=(5.2, 5.0), big=(5.2, 5.0), w=(900, 200), mid=None), # 太さだけで差:大きさは全部同じ、一語は極太・残りは極細 } def p3_style(name): st = P3_STYLES[name] def render_p3(it, n): key, q = it["key"], it["question"] lay = n % 4 vertical = lay in (0, 2) ls = lines_of(q) if vertical else hlines(q) if not any(key in l for l in ls): raise SystemExit(f"LOG{it['log']}: key「{key}」が問いの行の中で切れています。/ の位置を直してください") small = st["small"][0 if vertical else 1] L = vlen if vertical else hlen pad = st.get("pad") # 外側の余白(cqw)。None なら案3の標準 room = (56.25 - pad * 2 if pad else 46) if vertical else (100 - pad * 2 if pad else 86) # 1行が使える長さ k = 1.06 if vertical else 1.0 cap = st["big"][0 if vertical else 1] def fit(ls): ki = next(i for i, l in enumerate(ls) if key in l) rest = ls[ki].replace(key, "", 1) return ki, round(min(cap, (room - L(rest) * small * k) / (L(key) * k)), 2) ki, big = fit(ls) if big < cap * 0.8: # 一語が上限の大きさまで届かないときは、一語の行を短くするため改行する(縦書き・横書きとも) # 切るのは「一語+助詞1字(+読点)」の後ろだけ。語の途中で切らない。残りが3字未満なら切らない # 例:フィジカルAIにあるものは何か。 → フィジカルAIに/あるものは何か。 4万円の教材と、 → 4万円の/教材と、 a, b = ls[ki].split(key, 1) if b[:1] in PARTICLES: n = 2 if b[1:2] == "、" else 1 head, tail = key + b[:n], b[n:] if len(tail) >= 3: ls = ls[:ki] + [x for x in (a + head, tail) if x] + ls[ki + 1:] ki, big = fit(ls) # 中くらいにする行:問いの結び(最後の行)。一語がその行にあれば最初の行 mi = None if st["mid"] and len(ls) > 1: mi = len(ls) - 1 if ki != len(ls) - 1 else 0 wb, ws = st["w"] # 大きい一語と中くらいの行は、その文字の大きさに合わせて行(縦書きでは列)が広がり、前後の行との間が開く。行の高さを詰める lb, lm = "line-height:1.12;", "line-height:1.25;" def line(i, l): if i == ki: a, b = l.split(key, 1) return f'{esc(a, vertical)}{esc(key, vertical)}{esc(b, vertical)}' if i == mi: ms = round(min(st["mid"][0], room / (L(l) * k)), 2) return f'{esc(l, vertical)}' return esc(l, vertical) inner = "{inner}
' return render_p3 RENDER = {"plain": plain, "p1": p1, "p2": p2, **{k: p3_style(k) for k in P3_STYLES}} def render(it: dict, plan: str, n: int) -> str: longest = max(vlen(l) for l in lines_of(it["question"])) if longest > MAX_CHARS: raise SystemExit(f"LOG{it['log']}: 1行が{longest:g}字あります。8字までになるよう「/」で区切ってください") css, body = RENDER[plan](it, n) return HEAD.format(log=it["log"], paper=PAPER, ink=INK, css=css, body=body) SITE = Path(__file__).resolve().parent.parent DATA = SITE / "tools/deep_dive_thumbs.json" ADOPTED = "p3b" def main(argv): if argv and argv[0] == "--batch": conf = json.loads(Path(argv[1]).read_text()) plans = [argv[argv.index("--plan") + 1]] if "--plan" in argv else PLANS for plan in plans: out = Path(conf["out"]) / plan out.mkdir(parents=True, exist_ok=True) for n, it in enumerate(conf["items"]): (out / f"LOG{it['log']}.html").write_text(render(it, plan, it.get("layout", n))) print(plan, len(conf["items"])) return conf = json.loads(DATA.read_text()) items = conf["items"] if argv and argv[0] == "--add": log, question, key = int(argv[1]), argv[2], argv[3] items = [it for it in items if it["log"] != log] newest = max(items, key=lambda it: it["log"]) # 一覧は LOG の大きい順に並ぶ。いちばん新しい記事の組みの1つ前を使うと、並んだときに4通りが順に回る items.append({"log": log, "question": question, "key": key, "layout": (newest["layout"] - 1) % 4}) items.sort(key=lambda it: -it["log"]) conf["items"] = items render(items[0] if items[0]["log"] == log else next(it for it in items if it["log"] == log), ADOPTED, 0) # 1行の字数などを先に確かめる DATA.write_text(json.dumps(conf, ensure_ascii=False, indent=2) + "\n") out = SITE / "public/thumbs" out.mkdir(parents=True, exist_ok=True) for it in items: (out / f"LOG{it['log']}.html").write_text(render(it, ADOPTED, it["layout"])) print(f"書き出し {len(items)} 件 → public/thumbs/") if __name__ == "__main__": main(sys.argv[1:])