
    Bi2C                    n   U d Z ddlmZ ddlmZ ddlZddlZddl	Z	ddl
Z
ddlZddlmZ ddlmZ dZ ed       ed       ed	       ed
      gZded<    ed       ed       ed       ed      gZded<    ed      Zedz  Z ed      Zdaded<   ddZddZddZd dZ	 	 	 	 	 	 	 	 	 	 d!dZd"dZy)#uR  Satori 카드뉴스 렌더링 핵심 모듈.

silent fallback 0건 보장:
- Node.js Satori 강제 호출 (Pillow fallback 절대 금지)
- hybrid-image silent fallback monkey-patch (install_silent_fallback_guard)
- 한글 폰트 명시 로드 (Pretendard + NotoSansCJK)

렌더링 방식:
- skills/satori-cardnews/render_html.mjs (전용 래퍼)를 primary로 사용
- satori_cli.js 존재 확인 (변경 금지 파일 — 참조/확인 전용)
- HTML 문자열 → Python html.parser → Satori virtual DOM JSON → render_html.mjs

IDS Phase 1 task-2401 — silent corruption 근본 수정.
    )annotationsN)Path)Anyz'Pretendard', 'Noto Sans KR'z3/home/jay/.local/share/fonts/Pretendard-Regular.otfz0/home/jay/.local/share/fonts/Pretendard-Bold.otfz4/home/jay/.local/share/fonts/Pretendard-SemiBold.otfz1/home/jay/.local/share/fonts/Pretendard-Black.otfz
list[Path]PRETENDARD_PATHSz./home/jay/.local/share/fonts/NotoSansCJKKR.otfz3/home/jay/.local/share/fonts/NotoSansCJKKR-Bold.otfzR/home/jay/workspace/tools/ai-image-gen/satori-test/fonts/NotoSansCJKkr-Regular.otfzO/home/jay/workspace/tools/ai-image-gen/satori-test/fonts/NotoSansCJKkr-Bold.otfNOTO_CJK_PATHSz2/home/jay/workspace/tools/ai-image-gen/satori-testzsatori_cli.jsz:/home/jay/workspace/skills/satori-cardnews/render_html.mjsFbool_GUARD_INSTALLEDc                     ~ ~t        d      )uB   hybrid-image _pillow_fallback 차단 — silent corruption 방지.W   silent fallback blocked by satori-cardnews guard — investigate Node.js Satori failureRuntimeErrorargskwargss     5/home/jay/workspace/skills/satori-cardnews/_satori.py_blocked_fallbackr   ;       f
	-     c                     ~ ~t        d      )uB   hybrid-image _write_blank_png 차단 — silent corruption 방지.r   r   r   s     r   _blocked_writer   D   r   r   c            	        g } d}d}t         D ]D  }|j                         s|j                  j                         }d|v sd|v sd|v r|=|}@|C|}F |)t	        dt         D cg c]  }t        |       c}       | j                  dt        |      ddd	       || j                  dt        |      d
dd	       d}d}t        D ]<  }|j                         s|j                  j                         }d|v r|5|}8|;|}> |)t	        dt        D cg c]  }t        |       c}       | j                  dt        |      ddd	       || j                  dt        |      d
dd	       | S c c}w c c}w )u  시스템에서 Pretendard + NotoSansCJK 폰트를 검색하여 satori fonts 옵션 dict 리스트로 반환.

    하나라도 없으면 FileNotFoundError raise.

    Returns:
        [{"name": str, "path": str, "weight": int, "style": str}, ...] 형태의 리스트.

    Raises:
        FileNotFoundError: Pretendard 또는 NotoSansCJK 폰트 파일을 찾을 수 없을 때.
    Nboldsemiboldblackz-Pretendard Regular font not found. Searched: 
Pretendardi  normal)namepathweightstylei  z.NotoSansCJK Regular font not found. Searched: zNoto Sans KR)r   existsr   lowerFileNotFoundErrorstrappendr   )resultpretendard_regularpretendard_boldp
name_lowernoto_regular	noto_bolds          r   find_korean_fontsr-   O   s    ')F '+#'O +88:J#zZ'?7jCX"*&'O%-)*&+ !*:;Q#a&;<>
 	
 MM&'	  " (	
 	 !%L!I %88:J#$ !I'#$L% *89Q#a&9:<
 	
 MML!	  "	N	
 	 Me <B :s   $E5
E:
c                     G d dt         j                        } |       }|j                  |        |j                  |j                  S dd| d| ddt        dddd	d
	dddS )uA  HTML 문자열을 Satori virtual DOM 객체로 변환.

    Python html.parser를 사용하여 HTML을 파싱하고
    Satori가 소비할 수 있는 virtual DOM JSON 구조로 변환합니다.

    지원: 단순 HTML (div, span, 인라인 style 속성).
    Satori는 inline style만 지원 (CSS class 미지원).
    c                  @     e Zd Zd fdZddZddZd	dZd
dZ xZS )#_html_to_vdom.<locals>._VdomBuilderc                >    t         |           g | _        d | _        y )N)super__init___stackroot)self	__class__s    r   r3   z,_html_to_vdom.<locals>._VdomBuilder.__init__   s    G02DK/3DIr   c                   i }|j                  d      D ]n  }|j                         }d|vr|j                  d      \  }}}|j                         }|j                         }|r|sRt        j                  dd |      }|||<   p |S )uD   인라인 CSS 문자열을 camelCase dict로 변환 (Satori 호환).;:z-([a-z])c                @    | j                  d      j                         S )N   )groupupper)ms    r   <lambda>zI_html_to_vdom.<locals>._VdomBuilder._parse_inline_style.<locals>.<lambda>   s    aggaj6F6F6H r   )splitstrip	partitionresub)r6   	style_strr&   declarationprop_valcamels           r   _parse_inline_stylez7_html_to_vdom.<locals>._VdomBuilder._parse_inline_style   s    %'F(s3 $)//1k)*44S9azz|iik3{,H$O #u$ Mr   c                   i }i }|D ]&  \  }}|dk(  r|r| j                  |      }|"|||<   ( |r||d<   ||d}| j                  rl| j                  d   }d|d   vrg |d   d<   |d   d   }	t        |	t              r|	j	                  |       n$t        |	t
              r|	|g|d   d<   n	|g|d   d<   | j                  j	                  |       y )Nr    typepropschildrenrP   )rL   r4   
isinstancelistr%   r$   )
r6   tagattrs	attr_dict
style_dictr   valuenodeparentrR   s
             r   handle_starttagz3_html_to_vdom.<locals>._VdomBuilder.handle_starttag   s    (*I)+J$ ,e7?u!%!9!9%!@J&&+IdO	, %/	'" "$D
 {{RVG_424F7OJ/!'?:6h-OOD)#.3;T2BF7OJ/37&F7OJ/KKt$r   c                    | j                   rE| j                   d   d   |k(  r/| j                   j                         }| j                   s|| _        y y y y )NrQ   rO   )r4   popr5   )r6   rU   rZ   s      r   handle_endtagz1_html_to_vdom.<locals>._VdomBuilder.handle_endtag   sG    {{t{{2v6#={{({{ $DI #  >{r   c                   |j                         }|r| j                  sy | j                  d   }d|d   vr	||d   d<   y |d   d   }t        |t              r||z   |d   d<   y t        |t              r|j                  |       y y )NrQ   rR   rP   )rB   r4   rS   r$   rT   r%   )r6   datatextr[   existings        r   handle_dataz/_html_to_vdom.<locals>._VdomBuilder.handle_data   s    ::<Dt{{[[_F0.2w
+!'?:6h,2:T/F7OJ/$/OOD) 0r   returnNone)rF   r$   rf   dict[str, Any])rU   r$   rV   zlist[tuple[str, str | None]]rf   rg   )rU   r$   rf   rg   )ra   r$   rf   rg   )	__name__
__module____qualname__r3   rL   r\   r_   rd   __classcell__)r7   s   @r   _VdomBuilderr0      s    	4
	$	%@	%	*r   rm   divflexpxz#1a1a2ecenterz#ffffff48px)	displaywidthheight
background
fontFamily
alignItemsjustifyContentcolorfontSizeu'   렌더링 오류 — HTML 파싱 실패)r    rR   rN   )_html_parser
HTMLParserfeedr5   KOREAN_FONT_STACK)html_strrt   ru   rm   parsers        r   _html_to_vdomr      s    J*|.. J*X ^F
KK{{{{  "!7"#HB-'/&"*""
 B
 r   c               "   t        |      }|j                  j                  dd       t                t	        j
                  d      }|t        d      t        j                         st        dt         d      t        j                         st        dt         d	      t        | ||      }|t        |      ||d
}t        j                  |d      }	 t        j                  |t        t              d|gddt        t               d      }|j&                  dk7  r8|j(                  r|j(                  dd nd}
t%        d|j&                   d|
       |j                         s.|j*                  r|j*                  dd nd}t%        d| d|       |j-                         j.                  }d}||k  rt%        d| d| d|       |S # t        j"                  $ r}	t%        d|       |	d}	~	wt        $ r}	t        d|	       |	d}	~	ww xY w)uu  Satori (Node.js) 강제 렌더. 실패 시 RuntimeError 또는 FileNotFoundError raise.

    silent fallback 절대 금지:
    - 폰트 파일 존재 확인 → 없으면 FileNotFoundError
    - node binary 존재 확인 → 없으면 FileNotFoundError
    - satori_cli.js 존재 확인 → 없으면 FileNotFoundError (참조용)
    - render_html.mjs 존재 확인 → 없으면 FileNotFoundError
    - 출력 PNG 크기 검증 (≥ 10KB) → 미달 시 RuntimeError

    Args:
        html: Satori-호환 HTML 문자열.
        output_path: 출력 PNG 파일 경로.
        width: 이미지 폭(px).
        height: 이미지 높이(px).

    Returns:
        실제 저장된 PNG 파일 경로.

    Raises:
        FileNotFoundError: 폰트 / node binary / satori_cli.js / render_html.mjs 부재.
        RuntimeError: Satori 호출 실패 또는 출력 PNG 크기 미달.
    T)parentsexist_okrZ   Nu4   node binary not found — install Node.js (e.g. nvm)zsatori_cli.js not found at u4    — check tools/ai-image-gen/satori-test/ directoryzrender_html.mjs not found at u2    — skills/satori-cardnews/render_html.mjs 필요)elementoutputrt   ru   F)ensure_asciiz--jsonx   )capture_outputrb   cwdtimeoutz0render_html.mjs timed out after 120s for output=zFailed to launch node: r   i  z(empty)z!render_html.mjs exited with code z
.
stderr: i  z<render_html.mjs reported success but output file not found: z	
stdout: i (  zOutput PNG too small (z	 bytes < z. bytes). Likely a placeholder or blank image: )r   r[   mkdirr-   shutilwhichr#   SATORI_CLI_SCRIPTr!   RENDER_HTML_SCRIPTr   r$   jsondumps
subprocessrunSATORI_SCRIPT_DIRTimeoutExpiredr   
returncodestderrstdoutstatst_size)htmloutput_pathrt   ru   node_binvdompayloadjson_strr&   excstderr_snippetstdout_snippet	file_sizemin_size_bytess                 r   safe_render_html_to_pngr     so   : {#KTD9  ||F#H VWW ##%)*;)< => >
 	
 $$&+,>+? @< <
 	
 uf-D k"	G zz'6Hs-.(C%&
  A17u-I/0A0A/B C%&(
 	
 06t,9J;- X%&(
 	

   "**IN>!$YKy8H I44?=B
 	

 A $$ >{mL
	  %cU+
	s$   8G HG..H:H		Hc                    ddl } t        ryt        d      }|j                         sdayg d}d}|D ]!  }|| j                  v s| j                  |   } n |	 t
        j                  j                  dt        |            }||j                  dayt
        j                  j                  |      }|| j                  d<   |j                  j                  |       t        |d
d      }|t        |dd      dk(  rdayt         |_        t$        |_        day# t        $ r2}ddl }|j                  j                  d| d|d	       daY d}~yd}~ww xY w)u  import-time monkey-patch: hybrid-image의 _pillow_fallback과 _write_blank_png를 RuntimeError로 차단.

    회귀 시 silent corruption 발생을 즉시 stack trace로 표면화.
    멱등성: 이미 패치되었으면 재패치 스킵.

    패치 전략:
    1. sys.modules에서 이미 로드된 hybrid-image _satori 모듈 탐색 (동일 인스턴스 패치)
    2. 없으면 importlib으로 새 모듈 로드 후 패치 + sys.modules 등록
    3. hybrid-image 미존재 환경(CI, 테스트)에서는 silently return
    r   Nz;/home/jay/workspace/skills/hybrid-image/patterns/_satori.pyT)z$skills.hybrid_image.patterns._satorizhybrid_image.patterns._satori	hi_satori_hybrid_image_satori_guardz$skills.hybrid-image.patterns._satorir   zv[satori-cardnews guard] WARNING: hybrid-image _satori import failed; silent fallback monkey-patch NOT installed (file=z, error=zV). If hybrid-image is in use, silent corruption may occur until guard is reinstalled.
_pillow_fallbackri    r   )sysr	   r   r!   modules	importlibutilspec_from_file_locationr$   loadermodule_from_specexec_module	Exceptionr   writegetattrr   r   r   _write_blank_png)	_syshi_satori_path_HI_MOD_KEYShi_modkeyspecr   	_diag_sysexisting_fallbacks	            r   install_silent_fallback_guardr     s     WXN  "L F $,,\\#&F ~	>>99,N#D |t{{2#' ^^44T:F9?DLL56KK##F+  (:DA$1BJPR)SWj)j 0F,F+  		#""DDRCSS[\_[b cgh
  $		s   9D A	D 	E (EE)r   r   r   r   rf   r   )rf   zlist[dict[str, object]])r   r$   rt   intru   r   rf   rh   )
r   r$   r   r   rt   r   ru   r   rf   r   re   )__doc__
__future__r   html.parserr   r|   importlib.utilr   r   rD   r   r   pathlibr   typingr   r   r   __annotations__r   r   r   r   r	   r   r   r-   r   r   r    r   r   <module>r      s$   # "   	     3  		>?	;<	?@	<=	  *  		9:	>?	]^	Z[	
  MN %7  VW   $ 
Odm`l
ll 	l
 l 
l^Jr   