# -*- coding: utf-8 -*-
"""
四年级黑板报 · 深色黑板粉笔风 · 文字直接写在图上
模型：doubao-seedream-5-0-260128（火山方舟 /api/v3/images/generations）

用法：
  python3 gen_board_text.py --prompt          # 只打印提示词，不调接口（先审稿用这个）
  python3 gen_board_text.py                   # 生成全部 3 版
  python3 gen_board_text.py --only A          # 只生成 A 版
  python3 gen_board_text.py --qa              # 对已生成图做文字质检（查错字/乱码）
  python3 gen_board_text.py --size 2896x2048  # 指定尺寸（默认 A4 横版比例）
"""
import argparse, base64, glob, json, os, sys, time
import requests
from concurrent.futures import ThreadPoolExecutor

CFG = json.load(open('/data/www/files/LhyjdesignPro/config.json'))
API_KEY = CFG['api_key']
BASE = CFG['base_url']
MODEL = CFG['models']['image_gen']          # doubao-seedream-5-0-260128
VLM = CFG['models']['text_vision']          # 质检用
OUT = '/data/www/files/heibanbaoText'
os.makedirs(OUT, exist_ok=True)

# ─────────────────────────────────────────────────────────────
# 一、要写进画面的文字（四年级能读懂的短句，每条 ≤ 10 字）
# ─────────────────────────────────────────────────────────────
TEXT_SAFE = {
    'heading': '安全教育',
    'lines': [
        '上下楼梯靠右行',
        '课间不追逐打闹',
        '过马路一停二看三通过',
        '不玩火 会报119',
        '不私自下水游泳',
        '被欺负要告诉老师',
        '不吃三无食品',
        '不跟陌生人走',
    ],
}
TEXT_FRAUD = {
    'heading': '防诈记心中',
    'lines': [
        '不轻信 免费送皮肤',
        '不透露 验证码密码',
        '不转账 要钱就挂断',
        '多核实 打96110',
        '被骗告诉爸妈老师',
    ],
}
TEXT_DRUG = {
    'heading': '远离毒品',
    'lines': [
        '毒品会伪装成糖果奶茶',
        '一次就可能上瘾',
        '不尝第一口',
        '不帮陌生人带东西',
        '发现涉毒打110',
    ],
}

# ─────────────────────────────────────────────────────────────
# 二、风格块（黑底粉笔手绘，与用户附件一致）
# ─────────────────────────────────────────────────────────────
STYLE = (
 "A hand-drawn CHALK BLACKBOARD NEWSPAPER (中国小学黑板报) painted directly on a real classroom blackboard — "
 "the whole picture IS the dark board, filling the frame edge to edge, seen perfectly straight-on and flat, "
 "no wooden frame, no easel, no room, no wall, no perspective tilt, no photograph of a classroom. "
 "BACKGROUND: deep charcoal-black chalkboard (about #141410), with soft uneven chalk-dust haze and faint grey "
 "eraser smudges; fine dry chalk grain all over. "
 "MEDIUM: everything drawn with chalk. Every shape outlined with a BOLD slightly wobbly WHITE CHALK line that "
 "is dry and grainy, filled with flat bright coloured chalk: crimson red, orange, golden yellow, grass green, "
 "sky blue, lavender, pink. Matte and powdery, like a teacher drawing with a box of coloured chalk. "
 "NO glossy look, NO gradients, NO 3D, NO photorealism, NO drop shadows, NO digital airbrush. "
 "HANDWRITING: all Chinese text is written by hand in CHALK, in a neat rounded teacher's handwriting "
 "style, each line horizontal and evenly spaced, with a clear white or coloured chalk stroke, sized large "
 "enough to read from three metres away. "
 "SPELLING RULE (most important): write the Chinese characters EXACTLY as listed below, character by "
 "character, in the given order. Do NOT add, drop, replace or invent any character. Do NOT write pinyin, "
 "English, Japanese, numbers other than the ones given, or any decorative pseudo-text. If a line does not "
 "fit, shorten the layout, never the characters. "
 "CARTOON STYLE: cute Chinese primary-school chalk cartoons — round heads, dot eyes, small smiles, thick white "
 "chalk outlines, flat chalk fills, NO realistic skin tones, NO realistic hair colour; each child wears a "
 "different coloured shirt. "
 "DECORATION (keep small, in margins only): tiny white four-pointed chalk stars, small white clouds, one "
 "pastel rainbow, grass tufts, pink and yellow flowers, red-and-white spotted mushrooms, yellow sunflowers, "
 "a blue-and-white paper airplane, a small butterfly. Decorations must NEVER overlap any Chinese text. "
 "READABILITY RULE: every Chinese line sits on clean dark board with at least a 20 pixel gap from any "
 "drawing, panel border or decoration; nothing is written on top of an illustration and no illustration "
 "covers a character."
)


COMPLETENESS_RULE = (
 "COMPLETENESS AND DIGIT RULE (check before finishing): every line must be written from its FIRST character to "
 "its LAST character including the final one — never drop, shorten or omit the last character of a line. "
 "Phone numbers must appear in full and nothing else: 119 is exactly the three digits 1-1-9; 110 is exactly the "
 "three digits 1-1-0; 96110 is exactly the FIVE digits 9-6-1-1-0 and must clearly show BOTH ones. Count the "
 "digits with your eyes before you stop drawing. "
 "CHARACTER CLARITY RULE: the character 无 inside 「三无食品」 must be drawn as two short horizontal strokes with "
 "one slanting stroke and one vertical-curved-hook below — it must NOT look like 王 (three horizontals) and NOT "
 "like 元. The character 口 at the end of 「不尝第一口」 is a small closed square box, clearly visible."
)


NUMBER_RULE = (
 "NUMBERING RULE: every line starts with a small ORANGE chalk serial number followed by a full stop, and the "
 "numbers run CONTINUOUSLY with NO skipping and NO repeats: the LEFT zone uses 1. 2. 3. 4. 5. 6. 7. 8. in that "
 "order, the MIDDLE zone uses 1. 2. 3. 4. 5. in that order, the RIGHT zone uses 1. 2. 3. 4. 5. in that order. "
 "Each number appears exactly once; never write two identical numbers in the same zone. "
)


TEXT_SAFE_B = {
    'heading': '安全教育',
    'lines': [
        '上下楼梯靠右行',
        '课间不追逐打闹',
        '过马路一停二看三通过',
        '不玩火 会报119',
        '不私自下水游泳',
        '被欺负要告诉老师',
        '不吃过期食品',
        '不跟陌生人走',
    ],
}


def build_prompt(layout: str, title: str, numbered: bool = True, safe=None) -> str:
    safe = safe or TEXT_SAFE

    def block(t, n):
        return (f"Heading in large coloured chalk: 「{t['heading']}」. Then exactly {n} lines below it, "
                f"one under another, written exactly as: " + " / ".join(f"「{x}」" for x in t['lines']) + ". ")
    return STYLE + " " + layout + " " + COMPLETENESS_RULE + (NUMBER_RULE if numbered else "") + (
        "TEXT TO WRITE ON THE BOARD — three zones, three colours of chalk. "
        "ZONE 1 (left, white and light-blue chalk): " + block(safe, len(safe['lines'])) +
        "ZONE 2 (middle, golden-yellow and orange chalk): " + block(TEXT_FRAUD, len(TEXT_FRAUD['lines'])) +
        "ZONE 3 (right, pink and red chalk): " + block(TEXT_DRUG, len(TEXT_DRUG['lines'])) +
        "MAIN TITLE at the top centre, the biggest text on the board, in thick coloured chalk letters with a "
        f"white outline: 「{title}」. Write it once, complete, exactly these characters."
    )


# ─────────────────────────────────────────────────────────────
# 三、三种版式
# ─────────────────────────────────────────────────────────────
LAYOUTS = {
 'A_三联区_盾牌手机禁字': (
   "LAYOUT: the board is divided into THREE ZONES by two thin white chalk dividing lines. "
   "LEFT ZONE (about 40 percent of the width): at the bottom left a smiling boy in a white shirt, blue shorts "
   "and a red neckerchief holds up a big SKY-BLUE SHIELD with a white tick; behind him a pastel rainbow "
   "between two white clouds, a blue laptop and a smartphone with an orange warning triangle; grass, two "
   "pink flowers and a red-and-white spotted mushroom at his feet. The upper two thirds of this zone stay "
   "clean dark board for the 「安全教育」 text. "
   "MIDDLE ZONE (about 30 percent): a friendly cartoon POLICE OFFICER in a dark navy-blue cap and light blue "
   "uniform shirt stands at the bottom, one arm raised pointing forward; above him a winding pale-yellow "
   "chalk path and a big white chalk LOCK; the upper part of the zone stays clean dark board for the "
   "「防诈四不」 text. "
   "RIGHT ZONE (about 30 percent): at the bottom a BIG RED PROHIBITION CIRCLE with one diagonal bar "
   "containing a white chalk syringe and two pills — clearly a syringe and pills, NOT a flower, NOT a "
   "plant; beside it two yellow sunflowers and two children in purple and green shirts cheering; the upper "
   "part of the zone stays clean dark board for the 「远离毒品」 text."
 ),
 'B_三栏竖排_最像教室板报': (
   "LAYOUT: three tall vertical columns of text separated by two thin white chalk vertical rules, like a "
   "real classroom blackboard newspaper. Each column has a slim coloured chalk rule around it — the left "
   "one sky blue, the middle one golden yellow, the right one red — drawn loosely by hand. "
   "LEFT COLUMN, above the text: a small sky-blue shield with a white tick, a red-yellow-green traffic "
   "light, a fire extinguisher and a lifebuoy drawn small in a row. "
   "MIDDLE COLUMN, above the text: a small smartphone with an orange warning triangle and a white padlock. "
   "RIGHT COLUMN, above the text: a small red prohibition circle with a syringe and two pills inside. "
   "Along the bottom edge: a continuous chalk grass line with flowers, two mushrooms, three sunflowers and "
   "three cheering children in red, green and blue shirts."
 ),
 'C_三分区大框_留白可加内容': (
   "LAYOUT: THREE BIG ROUNDED CHALK PANELS side by side, the left one outlined in WHITE chalk, the middle "
   "in GOLDEN YELLOW chalk, the right in RED chalk, each panel holding one zone of text. Inside each panel "
   "the text starts near the top and leaves the lower third of the panel as clean empty dark board, so the "
   "class can add a few more lines by hand later. "
   "Above the LEFT panel: a big sky-blue shield with a white tick and a small blue laptop. Above the MIDDLE "
   "panel: a smartphone with an orange warning triangle and a small white padlock. Above the RIGHT panel: a "
   "big red prohibition circle with a syringe and two pills inside — NOT a flower, NOT a plant. "
   "Along the bottom edge: grass tufts, pink and yellow flowers, two mushrooms, three sunflowers and three "
   "cheering children in blue, red and green shirts; a few white chalk stars in the top corners."
 ),
}

TITLES = {
 'A_三联区_盾牌手机禁字': '安全小卫士',
 'B_三栏竖排_最像教室板报': '平安校园 从我做起',
 'C_三分区大框_留白可加内容': '三防小课堂',
}


def prompt_for(key):
    layout = LAYOUTS[key]
    is_b = key.startswith('B')
    return build_prompt(layout, TITLES[key], numbered=not is_b,
                        safe=TEXT_SAFE_B if is_b else TEXT_SAFE)


# ─────────────────────────────────────────────────────────────
# 四、调用接口
# ─────────────────────────────────────────────────────────────
def gen(key, size='2896x2048'):
    name = f'{key}.jpg'
    prompt = prompt_for(key)
    for attempt in range(4):
        try:
            r = requests.post(f"{BASE}/images/generations",
                headers={"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"},
                json={"model": MODEL, "prompt": prompt, "size": size, "n": 1, "watermark": False},
                timeout=300)
            d = r.json()
            if d.get('data'):
                img = requests.get(d['data'][0]['url'], timeout=180).content
                p = os.path.join(OUT, name)
                open(p, 'wb').write(img)
                print(f"OK  {p}  {len(img)//1024}KB", flush=True)
                return p
            print(f"ERR {name} #{attempt}: {json.dumps(d, ensure_ascii=False)[:300]}", flush=True)
        except Exception as e:
            print(f"EXC {name} #{attempt}: {e}", flush=True)
        time.sleep(8)
    return None


QA_PROMPT = (
 "这是一张小学黑板报。请逐个区域如实读出画面里所有中文（按左/中/右三个区域分别列出，一行一行写），"
 "然后指出：1)有没有错字、缺笔、多字、乱码、拼音或英文；2)有没有文字被插图或边框压住；"
 "3)文字大小是否够大、行距是否整齐。只描述你真实看到的字样，不要推测。"
)


def qa(path):
    b64 = base64.b64encode(open(path, 'rb').read()).decode()
    r = requests.post(f"{BASE}/chat/completions",
        headers={"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"},
        json={"model": VLM, "messages": [{"role": "user", "content": [
            {"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{b64}"}},
            {"type": "text", "text": QA_PROMPT}]}], "temperature": 0.1}, timeout=300)
    return r.json()['choices'][0]['message']['content']


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument('--prompt', action='store_true', help='只打印提示词')
    ap.add_argument('--only', default='', help='只处理名字含该字符的版式，如 A 或 B')
    ap.add_argument('--size', default='2896x2048')
    ap.add_argument('--qa', action='store_true', help='对已有图做文字质检')
    a = ap.parse_args()

    keys = [k for k in LAYOUTS if not a.only or a.only in k]
    if a.prompt:
        for k in keys:
            print('=' * 78)
            print('【' + k + '】  len=' + str(len(prompt_for(k))))
            print('=' * 78)
            print(prompt_for(k))
        return
    if a.qa:
        for p in sorted(glob.glob(os.path.join(OUT, '*.jpg'))):
            print('\n' + '=' * 70 + f'\n【{os.path.basename(p)}】\n' + qa(p))
        return
    with ThreadPoolExecutor(max_workers=3) as ex:
        list(ex.map(lambda k: gen(k, a.size), keys))


if __name__ == '__main__':
    main()
