#!/usr/bin/env python3
"""
护照文字精确写入工具 (Mask-based rendering)
- 绕过 Pillow 9.5.0 draw.text() bug，使用 font.getmask() + paste
- 坐标/字体/颜色/锚点 与 gen_passport_v3.py 完全一致
- MRZ 自动补齐 44 字符 + 校验位重算
- 支持 --debug 可视化每个文本框
用法:
    python write_passport.py \
        --img v3_passport_final.png \
        --out v3_passport_replaced.png \
        --data '{"surname":"LI","given_name":"WEI","passport_no":"MK888888",...}' \
        [--debug] [--skip-header]
"""
import os, json, sys, argparse
from datetime import datetime
from pathlib import Path
from PIL import Image, ImageDraw, ImageFont

# ──────────────────────────────────────────────
# 1. 字体加载
# ──────────────────────────────────────────────
FONT_DIR = Path(__file__).parent.parent / "fonts"
FONT_FILES = {
    "sans":      "DejaVuSans.ttf",
    "sans_bold": "DejaVuSans-Bold.ttf",
    "mono":      "DejaVuSansMono.ttf",
}

def load_font(name: str, size: int) -> ImageFont.FreeTypeFont:
    path = FONT_DIR / FONT_FILES[name]
    if not path.exists():
        local = Path(__file__).with_name("fonts") / FONT_FILES[name]
        if local.exists():
            path = local
        else:
            raise FileNotFoundError(f"字体不存在: {path} (也检查了 {local})")
    try:
        return ImageFont.truetype(str(path), size)
    except Exception as e:
        raise RuntimeError(f"加载字体失败 {path}: {e}")

# ──────────────────────────────────────────────
# 2. 坐标计算（完全复刻 gen_passport_v3.py）
# ──────────────────────────────────────────────
def calc_layout(W: int, H: int) -> dict:
    scale_f = min(W / 900, H / 630)
    sf = lambda v: max(1, int(v * scale_f))

    fs_title = max(12, int(24 * scale_f))
    fs_pass  = max(10, int(17 * scale_f))
    fs_label = max(8,  int(11 * scale_f))
    fs_val   = max(9,  int(13 * scale_f))
    fs_mrz   = max(10, int(18 * scale_f))
    fs_tiny  = max(6,  int(8  * scale_f))

    f_title = load_font("sans_bold", fs_title)
    f_pass  = load_font("sans_bold", fs_pass)
    f_label = load_font("sans",      fs_label)
    f_val   = load_font("sans_bold", fs_val)
    f_mrz   = load_font("mono",      fs_mrz)
    f_tiny  = load_font("sans",      fs_tiny)

    COL = {
        "title": "#1a1a1a", "pass": "#6B0000", "gold": "#C4A35A",
        "label": "#666", "val": "#111", "mrz": "#111",
        "photo_out": "#8B7B5D", "photo_in": "#aaa", "sig_line": "#aaa",
        "mrz_bg": "#FCFAF5", "mrz_border": "#c8b898",
    }

    title_y = sf(30)
    passport_y = sf(25)
    type_y = sf(45)
    photo = {"px": sf(280), "py": sf(118), "pw": sf(165), "ph": sf(200)}

    c_l1 = sf(35); c_v1 = sf(170)
    c_l2 = sf(480); c_v2 = sf(610)
    ry = sf(360); gap = sf(28)

    sepy = ry + 6*gap + sf(5) + sf(60)
    mrzh = sf(14)

    return {
        "scale_f": scale_f, "sf": sf,
        "fonts": {"title": f_title, "pass": f_pass, "label": f_label,
                  "val": f_val, "mrz": f_mrz, "tiny": f_tiny},
        "colors": COL,
        "coords": {
            "title_y": title_y,
            "passport_y": passport_y,
            "type_y": type_y,
            "photo": photo,
            "fields": {"c_l1": c_l1, "c_v1": c_v1,
                       "c_l2": c_l2, "c_v2": c_v2,
                       "ry": ry, "gap": gap},
            "mrz": {"sepy": sepy, "mrzh": mrzh,
                    "left": sf(20), "right": W - sf(20),
                    "center_x": W // 2,
                    "line1_y": sepy + mrzh,
                    "line2_y": sepy + mrzh + sf(24),
                    "prefix_x": sf(20), "prefix_y": sepy + mrzh - sf(6)},
        }
    }

# ──────────────────────────────────────────────
# 3. MRZ 生成
# ──────────────────────────────────────────────
sys.path.insert(0, str(Path(__file__).parent))
from mrz import build_line1, build_line2  # type: ignore

def gen_mrz(data: dict, dob: datetime, expiry: datetime) -> tuple[str, str]:
    dob_str = dob.strftime("%y%m%d")
    exp_str = expiry.strftime("%y%m%d")
    l1 = build_line1(data["surname"], data["given_name"],
                     data.get("type", "PV"), data.get("country_code", "MMR"))
    l2 = build_line2(data["passport_no"], dob_str, exp_str,
                     data.get("sex", "M"), "MMR")
    assert len(l1) == 44 and len(l2) == 44, f"MRZ长度异常: {len(l1)}/{len(l2)}"
    return l1, l2

# ──────────────────────────────────────────────
# 4. Mask-based 绘制（核心修复）
# ──────────────────────────────────────────────
def hex_to_rgb(hex_color: str) -> tuple:
    h = hex_color.lstrip('#')
    if len(h) == 3:
        h = ''.join(c*2 for c in h)
    return tuple(int(h[i:i+2], 16) for i in (0, 2, 4))

def draw_text_mask(draw: ImageDraw.ImageDraw, img: Image.Image, xy, text, font, fill, anchor, debug=False, label=""):
    """
    使用 font.getmask() 绘制文本，绕过 draw.text() bug
    xy: 锚点坐标
    anchor: 'lt'(默认), 'mt', 'rt', 'mm', 'lb', 'mb', 'rb'
    """
    # 获取文本掩码
    mask = font.getmask(text, mode='L')
    mask_img = Image.frombytes('L', mask.size, bytes(mask))
    mw, mh = mask_img.size

    # 计算左上角坐标
    x, y = xy
    if anchor == "lt":
        tl_x, tl_y = x, y
    elif anchor == "mt":
        tl_x, tl_y = x - mw // 2, y
    elif anchor == "rt":
        tl_x, tl_y = x - mw, y
    elif anchor == "mm":
        tl_x, tl_y = x - mw // 2, y - mh // 2
    elif anchor == "lb":
        tl_x, tl_y = x, y - mh
    elif anchor == "mb":
        tl_x, tl_y = x - mw // 2, y - mh
    elif anchor == "rb":
        tl_x, tl_y = x - mw, y - mh
    else:
        tl_x, tl_y = x, y

    # 创建文本颜色层
    text_color = hex_to_rgb(fill) if isinstance(fill, str) else fill
    text_layer = Image.new('RGB', (mw, mh), text_color)

    # 贴到主图
    img.paste(text_layer, (tl_x, tl_y), mask_img)

    # Debug: 画包围盒和锚点
    if debug:
        draw.rectangle([tl_x, tl_y, tl_x + mw, tl_y + mh], outline="#FF0000", width=2)
        draw.ellipse([x-3, y-3, x+3, y+3], fill="#00FF00")
        # 标注尺寸
        debug_font = load_font("sans", 12)
        draw.text((tl_x, tl_y - 14), f"{label} {mw}x{mh}", fill="#FF0000", font=debug_font)

def write_passport(img_path: str, out_path: str, data: dict, debug: bool = False, skip_header: bool = False):
    img = Image.open(img_path).convert("RGB")
    W, H = img.size
    L = calc_layout(W, H)
    sf = L["sf"]
    F = L["fonts"]
    C = L["colors"]
    XY = L["coords"]

    draw = ImageDraw.Draw(img)

    if not skip_header:
        # ── 1. 页眉 ──
        draw_text_mask(draw, img, (W//2, XY["title_y"]),
                       "REPUBLIC OF THE UNION OF MYANMAR",
                       F["title"], C["title"], "mt", debug, "title1")
        title_y2 = XY["title_y"] + sf(36)
        draw_text_mask(draw, img, (W//2, title_y2),
                       "P A S S P O R T",
                       F["pass"], C["pass"], "mt", debug, "title2")

        # ── 2. 右上 ──
        draw_text_mask(draw, img, (W - sf(30), XY["passport_y"]),
                       f"Passport No  {data['passport_no']}",
                       F["label"], "#1a1a1a", "rt", debug, "passport_no")
        draw_text_mask(draw, img, (W - sf(30), XY["type_y"]),
                       f"Type  {data.get('type','PV')}    Code  {data.get('country_code','MMR')}",
                       F["tiny"], "#555", "rt", debug, "type_code")

    # ── 3. 数据字段（6行双列）──
    fields = [
        ("surname", "Surname / Nom", "given_name", "Given Name / Prenoms"),
        ("nationality", "Nationality", None, ""),
        ("dob", "Date of birth", "sex", "Sex / Sexe"),
        ("birth_place", "Place of birth", None, ""),
        ("issue_date", "Date of issue", "expiry_date", "Date of expiry"),
        ("authority", "Authority / Autorite", None, ""),
    ]
    ry = XY["fields"]["ry"]
    gap = XY["fields"]["gap"]
    for i, (k1, lab1, k2, lab2) in enumerate(fields):
        y = ry + i * gap
        # 左列
        draw_text_mask(draw, img, (XY["fields"]["c_l1"], y), lab1, F["label"], C["label"], "lt", debug, f"L{i}_lab")
        draw_text_mask(draw, img, (XY["fields"]["c_v1"], y), str(data.get(k1, "")), F["val"], C["val"], "lt", debug, f"L{i}_val")
        # 右列
        if k2:
            draw_text_mask(draw, img, (XY["fields"]["c_l2"], y), lab2, F["label"], C["label"], "lt", debug, f"R{i}_lab")
            draw_text_mask(draw, img, (XY["fields"]["c_v2"], y), str(data.get(k2, "")), F["val"], C["val"], "lt", debug, f"R{i}_val")

    # ── 4. 签名行 ──
    sy = ry + len(fields)*gap + sf(5)
    draw_text_mask(draw, img, (XY["fields"]["c_l1"], sy),
                   "Holder's signature / Signature du titulaire",
                   F["label"], C["label"], "lt", debug, "sig_lab")
    if debug:
        x1 = XY["fields"]["c_v1"]
        x2 = x1 + sf(200)
        y_line = sy + sf(28)
        draw.line([x1, y_line, x2, y_line], fill="#FF0000", width=2)

    # ── 5. MRZ ──
    dob = datetime.strptime(data["dob"], "%d %b %Y")
    expiry = datetime.strptime(data["expiry_date"], "%d %b %Y")
    l1, l2 = gen_mrz(data, dob, expiry)

    mrz = XY["mrz"]
    if debug:
        draw.line([sf(15), mrz["sepy"], W-sf(15), mrz["sepy"]], fill="#FF0000", width=2)
        draw.rectangle([mrz["left"], mrz["sepy"]+mrz["mrzh"]-sf(10),
                        mrz["right"], mrz["sepy"]+mrz["mrzh"]+sf(52)],
                       outline="#FF0000", width=2)

    draw_text_mask(draw, img, (mrz["prefix_x"], mrz["prefix_y"]), "P<", F["mrz"], C["mrz"], "lt", debug, "mrz_prefix")
    draw_text_mask(draw, img, (mrz["center_x"], mrz["line1_y"]), l1, F["mrz"], C["mrz"], "mt", debug, "mrz_l1")
    draw_text_mask(draw, img, (mrz["center_x"], mrz["line2_y"]), l2, F["mrz"], C["mrz"], "mt", debug, "mrz_l2")

    # ── 保存 ──
    img.save(out_path, dpi=(300, 300))
    print(f"✓ 写入完成: {out_path} ({W}x{H})")
    if debug:
        dbg_path = out_path.replace(".png", "_debug.png")
        img.save(dbg_path)
        print(f"✓ 调试图: {dbg_path}")

# ──────────────────────────────────────────────
# 5. CLI
# ──────────────────────────────────────────────
def parse_args():
    p = argparse.ArgumentParser(description="护照文字精确写入")
    p.add_argument("--img", required=True, help="输入图片（已 inpaint 干净底图）")
    p.add_argument("--out", required=True, help="输出图片")
    p.add_argument("--data", required=True, help="JSON 字符串或 @文件路径")
    p.add_argument("--debug", action="store_true", help="生成调试图（红框=文本包围盒，绿点=锚点）")
    p.add_argument("--skip-header", action="store_true", help="跳过页眉/右上区域，仅写入数据字段+MRZ")
    return p.parse_args()

def load_data(arg: str) -> dict:
    if arg.startswith("@"):
        with open(arg[1:], "r", encoding="utf-8") as f:
            return json.load(f)
    return json.loads(arg)

if __name__ == "__main__":
    args = parse_args()
    data = load_data(args.data)

    required = ["surname", "given_name", "nationality", "dob", "sex",
                "birth_place", "issue_date", "expiry_date", "authority",
                "passport_no", "type", "country_code"]
    missing = [k for k in required if k not in data]
    if missing:
        sys.exit(f"❌ 缺少字段: {missing}")

    write_passport(args.img, args.out, data, args.debug, args.skip_header)