aboutsummaryrefslogtreecommitdiffhomepage
path: root/scripts/make-flow-pdfs.py
blob: b4a62abb06f35700b4eb6f53becdc8d55bedd5d5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
"""いじめ重大事態対応フロー図の印刷用PDFを、SVGからベクターで生成する。

以前は 2.5 倍のラスタPNGを埋め込んだPDF(1ページ版 約2MB / 6ページ版 約4MB)を
使っていたが、同じレイアウトのままベクターで組み直し、大幅に軽量化する
(各ファイル約200KB。文字は拡大しても綺麗)。

- A4縦・タイル分割版(2列×3行=6ページ、のりしろ重複あり)
- A4縦・全体像1ページ版(縮小・見取り図)

レイアウトは旧ラスタ版と同一(A4 3100x4382px 相当、余白100px、紙面2900x4182px、
タイル 1160x1672.8、列 x=[0,660]、行 y=[0, base/2, base])。
base は SVG の元の高さ(リンク行を追記する前の値)。SVG の末尾に追記した
解説ページへのリンク行は、PDFではフッターのリンク注釈として付ける。

使い方: python scripts/make-flow-pdfs.py
"""
import pathlib
import re
import sys

import pymupdf

sys.stdout.reconfigure(encoding="utf-8")

IMAGES = pathlib.Path(__file__).resolve().parent.parent / "public" / "ijime-judai-jitai" / "images"
URL = "https://yasutakeyohei.com/ijime-judai-jitai/flow/"
TITLE = "いじめの重大事態の対応フロー(ロール別)"

# 旧ラスタ版と同じジオメトリ(A4 3100x4382px 相当)
A4W = pymupdf.paper_rect("a4").width
A4H = pymupdf.paper_rect("a4").height
PX2PT = A4W / 3100.0
MARGIN = 100 * PX2PT
DEST = pymupdf.Rect(MARGIN, MARGIN, MARGIN + 2900 * PX2PT, MARGIN + 4182 * PX2PT)
TILE_W1 = 1160.0          # タイル幅(SVG座標=1x基準)
TILE_H1 = 1672.8          # タイル高さ
FOOT_SIZE = 50 * PX2PT    # 旧: int(20*2.5)=50px
FOOT_TOP = (4382 - 100 + 15) * PX2PT
FOOT_COLOR = (0x45 / 255, 0x5A / 255, 0x64 / 255)
FOOT_6P = TITLE + " {n}/6 (全体の一部・のりしろ重複あり)"
FOOT_1P = TITLE + " 全体像(縮小のため文字は小さめ。読みやすい印刷は6ページ版を推奨)"

# (SVGファイル名, 出力の6ページ版, 出力の1ページ版, SVGの元の高さ)
JOBS = [
    ("ijime-judai-flow.svg", "ijime-judai-flow-a4-6p.pdf", "ijime-judai-flow-a4-1p.pdf", 3970),
    ("ijime-judai-flow-private.svg", "ijime-judai-flow-private-a4-6p.pdf",
     "ijime-judai-flow-private-a4-1p.pdf", 3624),
]


ANCHOR_RE = re.compile(r"<a\b[^>]*>|</a>")


def strip_anchors(svg_text):
    """<a>要素を取り除く(中身は残す)。

    PyMuPDF の SVG→PDF 変換は <a> の中身を描画しないため、PDF化するときだけ外す。
    リンクとしては機能しなくなるが、元のラスタ版PDFもリンクは持っていなかった。
    """
    return ANCHOR_RE.sub("", svg_text)


def one_page_rect(base_h):
    """1ページ版の配置(旧スクリプトの縮小計算を再現)。"""
    sc = min((2900 / 2.5) / 1820, ((4182 / 2.5) - 30) / base_h)
    w = 1820 * sc * 2.5 * PX2PT
    h = base_h * sc * 2.5 * PX2PT
    x = (3100 - 1820 * sc * 2.5) * PX2PT / 2
    return pymupdf.Rect(x, MARGIN, x + w, MARGIN + h)


def add_footer(page, text):
    page.insert_text(
        pymupdf.Point(MARGIN, FOOT_TOP + FOOT_SIZE * 0.8),
        text, fontsize=FOOT_SIZE, fontname="japan", color=FOOT_COLOR,
    )
    page.insert_link({
        "kind": pymupdf.LINK_URI,
        "from": pymupdf.Rect(0, FOOT_TOP - 5, A4W, A4H),
        "uri": URL,
    })


def build(svg_name, out_6p, out_1p, base_h):
    path = IMAGES / svg_name
    svg = pymupdf.open("svg", strip_anchors(path.read_text(encoding="utf-8")).encode("utf-8"))
    # show_pdf_page は PDF しか受け付けないため、いったんベクターPDFに変換する
    src = pymupdf.open("pdf", svg.tobytes(garbage=4, deflate=True))
    svg.close()

    xs = [0.0, 660.0]
    span = base_h - TILE_H1
    ys = [0.0, float(round(span / 2)), float(span)]

    tiles = []
    n = 0
    for y in ys:
        for x in xs:
            n += 1
            tiles.append((pymupdf.Rect(x, y, x + TILE_W1, y + TILE_H1), FOOT_6P.format(n=n)))
    tiles.append((pymupdf.Rect(0, 0, 1820, base_h), FOOT_1P))

    def make(pages, path):
        out = pymupdf.open()
        for i, (clip, footer) in enumerate(pages):
            page = out.new_page(width=A4W, height=A4H)
            dest = DEST if i < 6 else one_page_rect(base_h)
            page.show_pdf_page(dest, src, 0, clip=clip)
            add_footer(page, footer)
        out.subset_fonts()
        tmp = path.with_suffix(".tmp.pdf")
        out.save(tmp, garbage=4, deflate=True, clean=True)
        out.close()
        tmp.replace(path)
        print(f"{path.name}: {path.stat().st_size:,} bytes")

    make(tiles[:6], IMAGES / out_6p)
    make(tiles[6:], IMAGES / out_1p)

    src.close()


for svg_name, out_6p, out_1p, base_h in JOBS:
    build(svg_name, out_6p, out_1p, base_h)