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
|
"""いじめ重大事態対応フロー図の印刷用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 = 34 * PX2PT
FOOT_TOP = (4382 - 100 + 3) * PX2PT
FOOT_COLOR = (0x45 / 255, 0x5A / 255, 0x64 / 255)
FOOT_6P = TITLE + " {n}/6 (全体の一部・のりしろ重複あり)"
FOOT_1P = TITLE + " 全体像(縮小のため文字は小さめ。読みやすい印刷は6ページ版を推奨)"
# 図だけがコピーされても出所と利用条件が分かるように、フッターにもライセンスを入れる
FOOT_LICENSE = ("© 安竹洋平(小平市議会議員)/CC BY-ND 4.0(表示—改変禁止)/"
"出典 yasutakeyohei.com/ijime-judai-jitai/flow/")
# (SVGファイル名, 出力の6ページ版, 出力の1ページ版, SVGの元の高さ)
JOBS = [
("ijime-judai-flow.svg", "ijime-judai-flow-a4-6p.pdf", "ijime-judai-flow-a4-1p.pdf", 4034),
("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):
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), FOOT_LICENSE,
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):
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)
|