Repository navigation
Expand file tree
/
Copy pathbuild_tutorial_v02_appendix.py
More file actions
279 lines (249 loc) · 10.9 KB
/
Copy pathbuild_tutorial_v02_appendix.py
File metadata and controls
279 lines (249 loc) · 10.9 KB
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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
#!/usr/bin/env python3
"""Append the plain-language automation and prompt-rule pages to the tutorial PDF."""
from __future__ import annotations
import io
from pathlib import Path
from pypdf import PdfReader, PdfWriter
from reportlab.lib.pagesizes import A4, landscape
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.pdfgen import canvas
PAGE_W, PAGE_H = landscape(A4)
BG = "#F3F1EA"
DARK = "#202B30"
INK = "#182126"
MUTED = "#6F797E"
GREEN = "#6FBF26"
RED = "#F44B43"
BLUE = "#168FC5"
GOLD = "#D2A054"
WHITE = "#FFFFFF"
LINE = "#D7D4CB"
def register_fonts() -> None:
candidates = [
(Path("C:/Windows/Fonts/Deng.ttf"), Path("C:/Windows/Fonts/Dengb.ttf")),
(Path("C:/Windows/Fonts/msyh.ttc"), Path("C:/Windows/Fonts/msyhbd.ttc")),
(Path("C:/Windows/Fonts/simhei.ttf"), Path("C:/Windows/Fonts/simhei.ttf")),
]
for regular, bold in candidates:
if regular.is_file() and bold.is_file():
pdfmetrics.registerFont(TTFont("CN", str(regular)))
pdfmetrics.registerFont(TTFont("CN-Bold", str(bold)))
return
raise FileNotFoundError("No compatible Chinese font found")
def color(value: str):
from reportlab.lib.colors import HexColor
return HexColor(value)
def rounded(c: canvas.Canvas, x: float, y: float, w: float, h: float, fill: str, stroke: str = LINE, radius: float = 12) -> None:
c.setFillColor(color(fill))
c.setStrokeColor(color(stroke))
c.roundRect(x, y, w, h, radius, fill=1, stroke=1)
def split_lines(text: str, font: str, size: float, width: float) -> list[str]:
output: list[str] = []
for paragraph in text.split("\n"):
current = ""
for char in paragraph:
trial = current + char
if current and pdfmetrics.stringWidth(trial, font, size) > width:
output.append(current)
current = char
else:
current = trial
output.append(current)
return output
def text_box(
c: canvas.Canvas,
text: str,
x: float,
y_top: float,
width: float,
font: str = "CN",
size: float = 9,
leading: float = 13,
fill: str = INK,
max_lines: int | None = None,
) -> float:
lines = split_lines(text, font, size, width)
if max_lines is not None:
lines = lines[:max_lines]
c.setFont(font, size)
c.setFillColor(color(fill))
y = y_top
for line in lines:
c.drawString(x, y, line)
y -= leading
return y
def page_header(c: canvas.Canvas, index: str, section: str, title: str, subtitle: str, dark: bool = False) -> None:
fg = WHITE if dark else INK
muted = "#AEB8BC" if dark else MUTED
c.setFillColor(color(DARK if dark else BG))
c.rect(0, 0, PAGE_W, PAGE_H, fill=1, stroke=0)
c.setFillColor(color(GREEN))
c.rect(36, PAGE_H - 54, 27, 4, fill=1, stroke=0)
c.setFont("CN-Bold", 9)
c.setFillColor(color(muted))
c.drawString(70, PAGE_H - 55, f"{index} / {section}")
c.setFont("CN-Bold", 24)
c.setFillColor(color(fg))
c.drawString(36, PAGE_H - 90, title)
text_box(c, subtitle, 36, PAGE_H - 109, PAGE_W - 72, size=8.5, leading=11, fill=muted, max_lines=2)
def footer(c: canvas.Canvas, page_number: int, dark: bool = False) -> None:
fg = "#8F999D" if dark else "#8A9295"
c.setFont("CN", 7)
c.setFillColor(color(fg))
c.drawString(36, 20, "批注驱动的局部图像编辑 · 先分清任务,再生成,再检查边界")
c.drawRightString(PAGE_W - 36, 20, f"{page_number:02d}")
def automation_page(c: canvas.Canvas) -> None:
page_header(
c,
"11",
"自动流程",
"从一张批注图开始,自动走到可检查的结果",
"正常情况下,原图、批注、文字和参考图只交一次。系统会拆任务、做局部生成、收边并检查越界;只有批注意思不清、外部 API 未授权或检查不通过时才停下来。",
)
stages = [
("01", "收好输入", "原图、批注、文字和参考图\n统一锁定当前版本", RED),
("02", "拆清任务", "一块区域只做一件事\n设计和材质分开", BLUE),
("03", "发起生图", "默认使用 ImageGen\n外部 API 先确认授权", GOLD),
("04", "先看局部", "设计对不对、透视顺不顺\n柜子是否真的落地", GREEN),
("05", "再做遮罩", "按真实生成轮廓收边\n不碰架子、沙发等细节", RED),
("06", "最后检查", "重新和原图逐像素比对\n没越界才交付候选结果", BLUE),
]
margin = 36
gap = 9
card_w = (PAGE_W - margin * 2 - gap * 5) / 6
card_y = 245
card_h = 190
for i, (number, title, body, accent) in enumerate(stages):
x = margin + i * (card_w + gap)
rounded(c, x, card_y, card_w, card_h, WHITE)
c.setFillColor(color(accent))
c.circle(x + 24, card_y + card_h - 28, 13, fill=1, stroke=0)
c.setFont("CN-Bold", 8)
c.setFillColor(color(WHITE))
c.drawCentredString(x + 24, card_y + card_h - 31, number)
c.setFont("CN-Bold", 12)
c.setFillColor(color(INK))
c.drawString(x + 15, card_y + card_h - 65, title)
text_box(c, body, x + 15, card_y + card_h - 90, card_w - 30, size=7.6, leading=11, fill=MUTED, max_lines=5)
if i < len(stages) - 1:
c.setStrokeColor(color("#A8B0B2"))
c.setLineWidth(1.2)
c.line(x + card_w + 1, card_y + card_h / 2, x + card_w + gap - 2, card_y + card_h / 2)
c.line(x + card_w + gap - 6, card_y + card_h / 2 + 3, x + card_w + gap - 2, card_y + card_h / 2)
c.line(x + card_w + gap - 6, card_y + card_h / 2 - 3, x + card_w + gap - 2, card_y + card_h / 2)
rounded(c, 42, 82, PAGE_W - 84, 125, DARK, DARK, 14)
c.setFont("CN-Bold", 13)
c.setFillColor(color(WHITE))
c.drawString(60, 178, "省时间的办法:该复用就复用,该重做才重做")
metrics = [
("任务拆开", "上墙和下柜各做各的"),
("能复用就复用", "同一面上墙不用重复生成"),
("只修出错环节", "遮罩错了就只重新合成"),
("不盲目重试", "没有新原因就不再烧一次"),
]
block_w = (PAGE_W - 120) / 4
for i, (label, detail) in enumerate(metrics):
x = 60 + i * block_w
c.setFont("CN-Bold", 10)
c.setFillColor(color(GREEN))
c.drawString(x, 140, label)
text_box(c, detail, x, 120, block_w - 18, size=8, leading=11, fill="#D6DCDE", max_lines=2)
footer(c, 12)
c.showPage()
def prompt_page(c: canvas.Canvas) -> None:
page_header(
c,
"12",
"PROMPT RULES",
"提示词规则",
"提示词不需要堆很多形容词。先说清这次只改什么,再把不能动的东西锁住;比例、分缝、接地这些能量化的条件,直接写成数字。",
dark=True,
)
left_x, left_w = 36, 365
right_x, right_w = 420, PAGE_W - 456
c.setFont("CN-Bold", 13)
c.setFillColor(color(WHITE))
c.drawString(left_x, 450, "今天几次为什么会跑偏")
failures = [
("01", "把错误写得很具体", "提示词明确要求宽横玻璃盒和铜色金属框,模型只是照着做。", RED),
("02", "旧要求没有退出", "已经改成琥珀金玻璃竖砖,旧的黄色釉面陶瓷却还留在提示词里。", GOLD),
("03", "一张图被安排两个身份", "guide 一边说只负责定位,一边又塞入材质参考,模型不知道该听哪一个。", BLUE),
("04", "一次让模型做太多事", "上墙换材质、下柜改设计放在同一次调用里,彼此干扰,也浪费调用。", GREEN),
]
y = 420
for number, title, body, accent in failures:
rounded(c, left_x, y - 68, left_w, 62, "#2C383D", "#3A474C", 9)
c.setFillColor(color(accent))
c.circle(left_x + 23, y - 37, 12, fill=1, stroke=0)
c.setFillColor(color(WHITE))
c.setFont("CN-Bold", 7.5)
c.drawCentredString(left_x + 23, y - 40, number)
c.setFont("CN-Bold", 10)
c.drawString(left_x + 45, y - 27, title)
text_box(c, body, left_x + 45, y - 45, left_w - 60, size=7.8, leading=10, fill="#C6CED1", max_lines=2)
y -= 72
rounded(c, right_x, 154, right_w, 296, BG, BG, 13)
c.setFont("CN-Bold", 13)
c.setFillColor(color(INK))
c.drawString(right_x + 20, 420, "写提示词时,只守这七条")
fields = [
("一件事", "一次只改一个区域、完成一个动作"),
("分职责", "设计只管形体;材质只管表面"),
("说准材质", "琥珀金玻璃,不要同时出现黄釉陶瓷"),
("写清比例", "竖砖约 1:3-1:4;接缝不超过 2 px"),
("交代接地", "柜体必须落地;可见离地缝为 0"),
("限制参考", "只借指定材质或比例,不照搬参考场景"),
("锁住不动", "相机、透视、架子、沙发和未标注区不变"),
]
fy = 382
for label, detail in fields:
c.setFillColor(color(GREEN))
c.roundRect(right_x + 20, fy - 4, 50, 19, 9, fill=1, stroke=0)
c.setFont("CN-Bold", 8)
c.setFillColor(color(DARK))
c.drawCentredString(right_x + 45, fy + 2, label)
text_box(c, detail, right_x + 82, fy + 5, right_w - 105, size=7.8, leading=10, fill=INK, max_lines=2)
fy -= 36
rounded(c, 36, 66, PAGE_W - 72, 66, "#11191D", "#11191D", 11)
c.setFont("CN-Bold", 12)
c.setFillColor(color(GREEN))
c.drawString(56, 108, "更省额度的做法")
c.setFont("CN-Bold", 12)
c.setFillColor(color(WHITE))
c.drawString(185, 108, "上墙材质只生成一次;下柜方案分别生成")
c.setFont("CN", 8.5)
c.setFillColor(color("#B9C3C6"))
c.drawString(56, 82, "先选柜体设计,再给入选方案做材质。改遮罩只重新合成;没有新原因,不自动重试。")
footer(c, 13, dark=True)
c.showPage()
def build(pdf_path: Path) -> None:
register_fonts()
buffer = io.BytesIO()
c = canvas.Canvas(buffer, pagesize=(PAGE_W, PAGE_H), pageCompression=1)
automation_page(c)
prompt_page(c)
c.save()
buffer.seek(0)
source = PdfReader(str(pdf_path))
appendix = PdfReader(buffer)
if len(source.pages) < 11:
raise ValueError("tutorial base must contain at least 11 pages")
writer = PdfWriter()
for page in source.pages[:11]:
writer.add_page(page)
for page in appendix.pages:
writer.add_page(page)
writer.add_metadata(
{
"/Title": "批注驱动的局部图像编辑教程",
"/Subject": "批注驱动的自动局部修改流程与提示词规则",
"/Author": "Codex · LINE+ workflow",
}
)
temporary = pdf_path.with_name(pdf_path.stem + ".tmp.pdf")
with temporary.open("wb") as stream:
writer.write(stream)
temporary.replace(pdf_path)
if __name__ == "__main__":
build(Path(__file__).with_name("annotated-local-image-editing-tutorial-zh.pdf"))