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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
|
"""いじめ重大事態対応フロー図の印刷用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_NOTE_SIZE = 29 * PX2PT
FOOT_TOP = (4382 - 100 + 3) * PX2PT
FOOT_COLOR = (0x45 / 255, 0x5A / 255, 0x64 / 255)
FOOT_6P = TITLE + " {n}/6 (全体の一部・のりしろ重複あり)"
FOOT_1P = TITLE + " 全体像(縮小のため文字は小さめ。読みやすい印刷は6ページ版を推奨)"
# 図だけがコピーされても出所と利用条件が分かるように、フッターにもライセンスを入れる。
# 「公立版・私立版とも」で、2種類が同じライセンスであることを示す。
# 「無保証」は、内容の正確性を保証せず、誤りがあっても責任を負わない旨。
# 課題欄があるのは公立学校版だけなので、その旨も公立版にだけ入れる
LIC_PUBLIC = ("© 安竹洋平(小平市議会議員)/公立版・私立版とも CC BY-ND 4.0"
"(課題欄の省略・切り取り可・無保証)/yasutakeyohei.com/ijime-judai-jitai/flow/")
# 私立版と簡易版には課題欄がないので、その但し書きは入れない
LIC_NO_KADAI = ("© 安竹洋平(小平市議会議員)/公立版・私立版とも CC BY-ND 4.0"
"(切り取り可・無保証)/yasutakeyohei.com/ijime-judai-jitai/flow/")
# 図に合わせた版の情報(PDFのメタデータにも入れる)
EDITION = "第1版(令和8年9月23日)"
# (SVGファイル名, 出力の6ページ版, 出力の1ページ版, SVGの元の高さ, フッターのライセンス, 版の名前)
JOBS = [
("ijime-judai-flow.svg", "ijime-judai-flow-a4-6p.pdf",
"ijime-judai-flow-a4-1p.pdf", 4034, LIC_PUBLIC, "公立学校版"),
("ijime-judai-flow-simple.svg", "ijime-judai-flow-simple-a4-6p.pdf",
"ijime-judai-flow-simple-a4-1p.pdf", 3509, LIC_NO_KADAI, "公立学校版・簡易版"),
("ijime-judai-flow-private.svg", "ijime-judai-flow-private-a4-6p.pdf",
"ijime-judai-flow-private-a4-1p.pdf", 3624, LIC_NO_KADAI, "私立学校版"),
]
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, license_text):
y = FOOT_TOP + FOOT_SIZE * 0.8
page.insert_text(
pymupdf.Point(MARGIN, y), text, fontsize=FOOT_SIZE,
fontname="japan", color=FOOT_COLOR,
)
page.insert_text(
pymupdf.Point(MARGIN, y + FOOT_NOTE_SIZE * 1.15), license_text,
fontsize=FOOT_NOTE_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, license_text, label):
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, license_text)
# PDFのメタデータにも、出典とライセンスを入れる(AIや検索が読むため)
out.set_metadata({
"title": f"{TITLE}({label})",
"author": "安竹洋平(小平市議会議員)",
"subject": (f"{EDITION}。CC BY-ND 4.0(表示—改変禁止)。公立学校版・私立学校版とも同じライセンス。"
"出典 https://yasutakeyohei.com/ijime-judai-jitai/flow/"),
"keywords": f"いじめ重大事態, フロー図, {label}, {EDITION}, CC BY-ND 4.0, 安竹洋平, 小平市",
"creator": "https://yasutakeyohei.com/ijime-judai-jitai/flow/",
})
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, license_text, label in JOBS:
build(svg_name, out_6p, out_1p, base_h, license_text, label)
|