Optimized the root .gitignore to exclude virtual environments, node modules, and temp folders to ensure clean and lightweight version tracking. Co-authored-by: Cursor <cursoragent@cursor.com>
130 lines
3.9 KiB
Python
130 lines
3.9 KiB
Python
#!/usr/bin/env python3
|
||
"""将「关于暂缓下月工资发放事宜的通知」正文叠到扫描件上,输出 工资通知-暂缓下月.png"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import re
|
||
import textwrap
|
||
from pathlib import Path
|
||
|
||
from PIL import Image, ImageDraw, ImageFont
|
||
|
||
ROOT = Path(__file__).resolve().parent
|
||
SRC_PNG = ROOT / "工资通知-原图.png"
|
||
TXT = ROOT / "关于暂缓下月工资发放事宜的通知.txt"
|
||
OUT = ROOT / "工资通知-暂缓下月.png"
|
||
|
||
SONGTI = "/System/Library/Fonts/Supplemental/Songti.ttc"
|
||
|
||
MARGIN_L = 72
|
||
MARGIN_R = 72
|
||
LINE_H = 34
|
||
TITLE_Y = 128
|
||
CHARS_PER_LINE = 24
|
||
|
||
|
||
def load_font(size: int, bold: bool = False) -> ImageFont.FreeTypeFont:
|
||
idx = 1 if bold else 7
|
||
return ImageFont.truetype(SONGTI, size, encoding="utf-8", index=idx)
|
||
|
||
|
||
def text_width(s: str, font: ImageFont.FreeTypeFont, draw: ImageDraw.ImageDraw) -> int:
|
||
bbox = draw.textbbox((0, 0), s, font=font)
|
||
return bbox[2] - bbox[0]
|
||
|
||
|
||
def wrap_line_to_chars(s: str, width: int) -> list[str]:
|
||
s = s.strip()
|
||
if not s:
|
||
return []
|
||
return textwrap.wrap(s, width=width, break_long_words=False, break_on_hyphens=False)
|
||
|
||
|
||
def draw_body_paragraph(
|
||
draw: ImageDraw.ImageDraw,
|
||
y: int,
|
||
text: str,
|
||
font: ImageFont.FreeTypeFont,
|
||
x0: int,
|
||
line_h: int,
|
||
first_indent: bool,
|
||
) -> int:
|
||
max_chars = CHARS_PER_LINE - (2 if first_indent else 0)
|
||
parts = wrap_line_to_chars(text, max_chars)
|
||
for part in parts:
|
||
line = (" " + part) if first_indent else part
|
||
draw.text((x0, y), line, font=font, fill=(30, 30, 30))
|
||
y += line_h
|
||
return y
|
||
|
||
|
||
def main() -> None:
|
||
im = Image.open(SRC_PNG).convert("RGB")
|
||
draw = ImageDraw.Draw(im)
|
||
w, _h = im.size
|
||
paper = (255, 255, 255)
|
||
|
||
# 覆盖标题与正文,下缘略低于原「正文末行」留白,整块至公章上沿(公章约自 y≥708)
|
||
draw.rectangle([32, 114, w - 34, 706], fill=paper)
|
||
# y=706–716 狭长带:盖住正文与公章之间的浅灰水印,仅涂左侧留白,不压住公章顶边圆弧
|
||
draw.rectangle([36, 706, 446, 716], fill=paper)
|
||
# 清空左下角旧署名/日期笔迹(不覆盖右下角公章 x≈453–596)
|
||
draw.rectangle([120, 716, 446, 832], fill=paper)
|
||
|
||
raw = TXT.read_text(encoding="utf-8")
|
||
lines = [ln.rstrip() for ln in raw.splitlines()]
|
||
title = lines[0].strip()
|
||
i = 1
|
||
while i < len(lines) and not lines[i].strip():
|
||
i += 1
|
||
|
||
font_title = load_font(26, bold=True)
|
||
font_body = load_font(22, bold=False)
|
||
x0 = MARGIN_L
|
||
max_px = w - MARGIN_L - MARGIN_R
|
||
|
||
y = TITLE_Y
|
||
tw = text_width(title, font_title, draw)
|
||
draw.text(((w - tw) // 2, y), title, font=font_title, fill=(30, 30, 30))
|
||
y += 54
|
||
|
||
while i < len(lines):
|
||
line = lines[i].strip()
|
||
i += 1
|
||
if not line:
|
||
y += LINE_H // 2
|
||
continue
|
||
|
||
if line == "全体员工:":
|
||
draw.text((x0, y), line, font=font_body, fill=(30, 30, 30))
|
||
y += LINE_H
|
||
continue
|
||
|
||
if line == "特此通知。":
|
||
twl = text_width(line, font_body, draw)
|
||
draw.text((x0 + max_px - twl, y), line, font=font_body, fill=(30, 30, 30))
|
||
y += LINE_H * 2
|
||
continue
|
||
|
||
if line.startswith("浙江羚牛"):
|
||
twl = text_width(line, font_body, draw)
|
||
draw.text((x0 + max_px - twl, y), line, font=font_body, fill=(30, 30, 30))
|
||
y += LINE_H
|
||
continue
|
||
|
||
if re.match(r"^\d{4}年\d{1,2}月\d{1,2}日$", line):
|
||
twl = text_width(line, font_body, draw)
|
||
draw.text((x0 + max_px - twl, y), line, font=font_body, fill=(30, 30, 30))
|
||
y += LINE_H
|
||
continue
|
||
|
||
first_indent = not bool(re.match(r"^[一二三四五六七八九十]+、", line))
|
||
y = draw_body_paragraph(draw, y, line, font_body, x0, LINE_H, first_indent)
|
||
|
||
im.save(OUT, format="PNG", optimize=True)
|
||
print(f"写入 {OUT}")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|