Spaces:
Paused
Paused
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()
|