"""SVG → PNG(요구 347-5 · 요구 350 — PyMuPDF 안 씀) — 헤드리스 Edge(없으면 Chrome) Playwright 찍기. svgs_to_pngs([svg 글], scale=2) → [PNG 바이트] 브라우저 한 번으로 여러 장(차례 그대로) 크기 = SVG 의 width · height(없으면 viewBox) × scale. 보고서 그림(단면 · 평면 위험 띠)을 내장 엑셀 그림(xlsx · 화면)으로 넣을 때 씀 — PDF 는 인쇄 HTML 이 data: 주소로 그대로 씀. 브라우저 띄우는 규칙 · 잠금 · 까닭 글은 공용 PDF 엔진(`common_util_print_pdf`)과 한 벌 — 자식 파이썬 프로세스(서버 스레드에선 Playwright 드라이버가 못 뜸) · 스크립트 끔 · 바깥 요청 막음. """ from __future__ import annotations import base64 import json import subprocess import sys from pathlib import Path from typing import Any from common_util.common_util_print_pdf import CHANNELS, NO_BROWSER, TIMEOUT_S, PrintError, _lock ROOT = Path(__file__).resolve().parents[1] def _shoot(svgs: list[str], scale: float) -> list[bytes]: """자식 프로세스 안 — SVG 마다 요소만 찍음. 실패 = PrintError.""" try: from playwright.sync_api import Error as PwError from playwright.sync_api import sync_playwright except ImportError as error: raise PrintError("서버에 Playwright 가 없습니다(pip install playwright)") from error try: with sync_playwright() as p: browser = None for channel in CHANNELS: try: browser = p.chromium.launch(channel=channel, headless=True) break except PwError: continue if browser is None: raise PrintError(NO_BROWSER) try: context = browser.new_context(java_script_enabled=False, device_scale_factor=scale) context.route("**/*", lambda route: route.abort()) page = context.new_page() out = [] for svg in svgs: page.set_content( f'{svg}' ) out.append(page.locator("svg").first.screenshot()) return out finally: browser.close() except PrintError: raise except Exception as error: # 드라이버 못 뜸 등 — 예외 글은 내지 않음 raise PrintError(NO_BROWSER) from error def svgs_to_pngs(svgs: list[str], scale: float = 2, timeout_s: float = TIMEOUT_S) -> list[bytes]: """SVG 여러 장 → PNG 여러 장(자식 프로세스 한 번).""" if not svgs: return [] with _lock: try: done = subprocess.run( [sys.executable, "-m", "common_util.common_util_svg_png"], # 묶음 import 되게 input=json.dumps({"svgs": svgs, "scale": scale}).encode(), cwd=ROOT, capture_output=True, timeout=timeout_s, ) except subprocess.TimeoutExpired as error: raise PrintError(f"그림 만들기가 {timeout_s:.0f}초 안에 안 끝남") from error except OSError as error: raise PrintError(NO_BROWSER) from error try: result: dict[str, Any] = json.loads(done.stdout.decode().strip().splitlines()[-1]) except (ValueError, IndexError): # 자식이 죽음 — stderr 는 내지 않음 result = {"reason": NO_BROWSER} if result.get("reason"): raise PrintError(result["reason"]) return [base64.b64decode(png) for png in result["pngs"]] if __name__ == "__main__": # 자식 프로세스 몸통 job = json.loads(sys.stdin.buffer.read()) try: pngs = _shoot(job["svgs"], job["scale"]) print(json.dumps({"reason": None, "pngs": [base64.b64encode(p).decode() for p in pngs]})) except PrintError as error: print(json.dumps({"reason": str(error)}, ensure_ascii=False)) sys.exit(0)