File size: 3,647 Bytes
f66643d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
from __future__ import annotations

import argparse
import json
import logging
import sys
from pathlib import Path

from .config import RenderConfig
from .renderer import render_document


def _parse_pages(s: str) -> list[int]:
    pages: list[int] = []
    for part in s.split(","):
        part = part.strip()
        if "-" in part:
            a, b = part.split("-", 1)
            pages.extend(range(int(a), int(b) + 1))
        else:
            pages.append(int(part))
    return pages


def main() -> None:
    ap = argparse.ArgumentParser(description="Phase 3: render translated scanned PDF")
    ap.add_argument("--pdf", required=True, help="Original scanned PDF")
    ap.add_argument("--parsed", required=True, help="Translated JSON (phase 2 output)")
    ap.add_argument("--output", required=True, help="Output PDF path")
    ap.add_argument("--font-config", default=None, help="JSON font/render config file")
    ap.add_argument(
        "--font-family",
        default="Helvetica",
        help="Typst font (single name or comma-separated fallback chain)",
    )
    ap.add_argument(
        "--font-path",
        action="append",
        default=[],
        dest="font_paths",
        help="Typst --font-path directory (repeatable)",
    )
    ap.add_argument("--pages", default=None, help="Page filter e.g. 0-4,7,10")
    ap.add_argument("--min-font", type=float, default=7.0, dest="min_font_size_pt")
    ap.add_argument("--typst-bin", default="typst", help="Path to typst binary")
    ap.add_argument(
        "--keep-typst-source",
        action="store_true",
        help="Save intermediate .typ file alongside output",
    )
    ap.add_argument(
        "--no-bg-sampling",
        action="store_true",
        help="Disable background color sampling (use white)",
    )
    ap.add_argument(
        "--no-redact",
        action="store_true",
        help="Skip native text layer redaction (faster, but original text remains selectable)",
    )
    ap.add_argument(
        "--aggressive-compress",
        action="store_true",
        help="Re-encode images via pikepdf",
    )
    ap.add_argument("--verbose", action="store_true")
    args = ap.parse_args()

    logging.basicConfig(
        level=logging.DEBUG if args.verbose else logging.INFO,
        format="%(asctime)s %(levelname)s %(message)s",
    )

    if args.font_config:
        cfg = RenderConfig.from_json(args.font_config)
    else:
        cfg = RenderConfig()

    # CLI args override config file
    if args.font_family:
        fonts = [f.strip() for f in args.font_family.split(",") if f.strip()]
        cfg.font_family = fonts[0] if len(fonts) == 1 else fonts
    if args.font_paths:
        cfg.typst_font_paths = args.font_paths
    cfg.typst_binary = args.typst_bin
    cfg.min_font_size_pt = args.min_font_size_pt
    cfg.keep_typst_source = args.keep_typst_source
    cfg.compress.pikepdf_image_recompress = args.aggressive_compress
    if args.no_bg_sampling:
        cfg.background.enabled = False
        cfg.text_color.enabled = False
    if args.no_redact:
        cfg.redact_native_text = False
    if args.pages:
        cfg.pages = _parse_pages(args.pages)

    parsed = json.loads(Path(args.parsed).read_text(encoding="utf-8"))
    try:
        stats = render_document(args.pdf, parsed, args.output, cfg)
        print(
            f"Done: pages={stats['pages']} rendered={stats['elements_rendered']} "
            f"skipped={stats['elements_skipped']} cells={stats['cells_rendered']}"
        )
    except RuntimeError as e:
        print(f"ERROR: {e}", file=sys.stderr)
        sys.exit(1)


if __name__ == "__main__":
    main()