Files
ONE-OS/docs/render_salary_notice_png.py
王冕 a27e3b8e43 feat: sync full workspace including web modules, docs, and configurations to Gitea
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>
2026-06-09 18:12:25 +08:00

130 lines
3.9 KiB
Python
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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=706716 狭长带:盖住正文与公章之间的浅灰水印,仅涂左侧留白,不压住公章顶边圆弧
draw.rectangle([36, 706, 446, 716], fill=paper)
# 清空左下角旧署名/日期笔迹(不覆盖右下角公章 x≈453596
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()