fix(render): #1154 잔상 통합 fix — clip + overlay crop + flow image prefetch#1164
Merged
Conversation
## 문제
exam_eng.hwp 2페이지 18번 박스 하단에 가로 줄 잔상 (이중 라인) 노출.
원인: 같은 bin_data_id 의 두 Pic 컨트롤이 같은 x/width 로 세로로 인접
겹쳐 그려질 때, 두 그림의 미세한 세로 스케일 차이 (LOWER 256.09→1612.77
src px, UPPER 70→434.43 src px) 가 SVG 리샘플링/안티엘리어싱에서 경계
어긋남으로 나타남. 한컴 편집기/뷰어는 이 잔상 없음.
## 해결 — IR 단계 후처리 (옵션 3: Clip below to top of above)
PageRenderTree::clip_overlapping_same_bin_images() 신규 메서드:
strict 5 조건 (bin_id 동일 / x 동일 / width 동일 / A 가 위 / 세로 겹침)
모두 만족 시, 아래 깔리는 LOWER 의 bbox.height 와 crop.bottom 을 위에
덮는 UPPER 의 top 까지 비례 축소. UPPER 가 100% 위에 덮으므로 시각 결과
한컴과 동일하면서 잔상 사라짐.
DocumentCore::build_page_tree() 종단(extra master pages 머지 후) 에서
1회 호출 — 모든 렌더러(SVG/HTML/Canvas/WebCanvas/SvgLayer/CanvasLayer)
공통 적용. LayerBuilder(paint/builder.rs:86)가 ImageNode bbox/crop 을
PaintOp::Image 에 그대로 복사하므로 layer 경로도 자동 영향.
## 회귀 보호 (strict 가드)
조건 2/3 (|Δx|≤1, |Δw|≤1) 가드로 의도적 효과 케이스 보호:
- test-image.hwp — 대각선 다중 배치 (Δx≈129px)
- 3-10월_교육_통합_2022.hwp — 그림자 효과 (Δx≈8px)
- pic2.hwp — 다른 너비 (Δw 큼)
검증:
- Stage 1 식별 17 영향 sample 합계 379 페이지 중 exam_eng page 2 한
장만 변경. 16 sample 100% 불변.
- 일반 회귀 7 sample (페어 없음): 100% 불변.
- cargo test --release --lib: 1421 passed / 0 failed
- cargo clippy --release --lib -- -D warnings: clean
- Docker WASM 빌드 통과
## 변경 파일
- src/renderer/render_tree.rs (+438): clip 메서드 + 11 단위 테스트
- src/document_core/queries/rendering.rs (+4): build_page_tree 끝에 호출
- mydocs/{plans,working,report}/task_m100_1154*.md: 수행·구현 계획서 +
단계별 보고서 + 최종 결과보고서
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
## 배경 commit 8ee17fd (v1) 머지 후 rhwp-studio 검증에서 잔상이 남아 추가 조사. edwardkim#1154 의 시각 증상은 세 가지 독립 원인이 동시 발생하고 있었음: 1. 동일 bin_id Pic 컨트롤 수직 인접 시 스케일 미스매치 (SVG/Canvas 공통) 2. rhwp-studio overlay `<img>` 가 crop 필드를 완전히 무시 (overlay only) 3. 큰 PNG/JPEG 의 비동기 디코드가 scheduleReRender(200/600ms) 를 초과 (flow only) v1 commit 8ee17fd 은 원인 1 처리. v2 (본 commit) 가 원인 2 + 3 처리. ## Stage 1 — overlay path 가 crop 을 honor - src/document_core/queries/rendering.rs : write_overlay_image 가 image.crop 이 Some 일 때 JSON 에 crop 필드 출력. - rhwp-studio/src/view/page-renderer.ts : - OverlayImageInfo.crop?: { left, top, right, bottom } 필드 추가. - toOverlayInfo (폴백 경로) 도 op.crop 전달. - createOverlayLayer 가 crop 있으면 wrapper div + overflow:hidden + scaled img 패턴으로 source rect → dest rect 매핑. (HU/px = 75, compute_image_crop_src 와 동일 환산) - filter / mixBlendMode / opacity 는 기존대로 `<img>` 자체에 적용. → 페이지 2 박스 18 윈도우 chrome frame 잔상 제거, 한컴 PDF 시각 일치. ## Stage 2 — 큰 이미지 비동기 디코드 안전망 - scheduleReRender delays [200, 600] → [200, 600, 1500] 확장. - prefetchFlowImages 신규 메서드 : - wasm.getPageLayerTree(pageIdx) JSON 에서 flow image 의 mime/base64 추출 (behindText/inFrontOfText 는 제외, overlay 별도 처리). - new Image() + image.decode() 으로 prefetch, 모든 디코드 완료 후 한 번 더 renderPageToCanvasFiltered 호출. - scheduleReRender 안에서 queueMicrotask 로 prefetch 실행 — setTimeout 흐름과 독립 동작하는 안전망. → 페이지 4 박스 27 (Adenville City Pass Card 종이+핀 디자인, PNG ~130KB) 외곽 그림 누락 해결, 한컴 PDF 시각 일치. ## 검증 - 페이지 2 박스 18 / 페이지 4 박스 27 / 페이지 4 박스 28 시각 한컴 PDF 일치 - cargo test --release --lib : 1432 passed / 0 failed / 6 ignored - cargo clippy --release --lib -- -D warnings : clean - npx tsc --noEmit (rhwp-studio) : clean - Docker WASM 빌드 : 정상 ## 회귀 보호 - Stage 1 변경은 BehindText/InFrontOfText overlay 경로에만 영향, flow layer 변경 없음. crop 없는 overlay 는 기존 동작 유지. - Stage 2 변경은 flow layer 추가 재렌더 + image prefetch 만 추가. 이미지가 없는 페이지는 imageCount=0 으로 skip, 비용 없음. - Rust 변경은 JSON 출력 1 줄 추가 — 기존 모든 cargo test / clippy 통과 유지. ## 변경 파일 - src/document_core/queries/rendering.rs (+7) - rhwp-studio/src/view/page-renderer.ts (+120 / -13) - mydocs/{plans,working,report}/task_m100_1154_v2*.md (수행/구현/단계별/최종) - pkg/* WASM 재빌드 (gitignore) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Owner
|
devel 에 머지했습니다 (`969cd022`). #1154 의 세 원인을 모두 확인했습니다:
devel 최신(#1167 SVG z-order)과 |
edwardkim
added a commit
that referenced
this pull request
May 29, 2026
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This was referenced May 31, 2026
edwardkim
pushed a commit
that referenced
this pull request
May 31, 2026
## 증상 samples/한셀OLE.hwp 등 OLE 미리보기 페이지를 rhwp-studio 에서 첫 로드 시 백지로 렌더되고, 두 번째 로드부터는 정상. 모듈 static IMAGE_CACHE 가 첫 로드 중 디코드한 HtmlImageElement 를 유지해 두 번째 호출에서만 img.complete() 분기로 즉시 그려지는 패턴. ## 원인 OLE/차트 미리보기는 PaintOp::RawSvg 로 emit (src/paint/json.rs:814) 되며 web_canvas RawSvg 핸들러는 단일 <image data:...> 또는 SVG 조각을 draw_image() 로 그려서 IMAGE_CACHE 비동기 디코드 경로를 그대로 탄다. 그런데 #1154 v2 (PR #1164) 의 디코드 안전망은 PaintOp::Image 만 image_count 에 포함시켜 RawSvg 케이스에서는: - rendering.rs collect() : image_count == 0 - page-renderer.ts scheduleReRender : if (imageCount <= 0) return 으로 200/600/1500ms 재시도 미발화 - prefetchFlowImages 정규식 ("type":"image") 도 rawSvg 미매칭 결과적으로 첫 렌더 후 캔버스가 백지로 남는다. ## 수정 1. src/document_core/queries/rendering.rs : collect() 가 PaintOp::RawSvg 도 image_count += 1 처리. scheduleReRender 재시도 발화 트리거. 2. rhwp-studio/src/view/page-renderer.ts : prefetchFlowImages 가 전체 JSON 의 data:image/MIME;base64,... 패턴을 직접 스캔하여 rawSvg 내장 데이터 URL 도 prefetch (중복 dedupe). 기존 image 항목 regex 는 유지. ## 검증 Puppeteer 측정 (samples/한셀OLE.hwp): | 시점 | 수정 전 load #1 | 수정 후 load #1 | 수정 후 load #2 | |------|-----------------|-----------------|-----------------| | t+0ms | 6 (백지) | 1276 | 1276 | | t+1500ms | 6 (백지) | 1276 | 1276 | | t+3000ms | 6 (백지) | 1276 | 1276 | cargo test --lib document_core::queries::rendering : 6 passed. Closes #1181
edwardkim
added a commit
that referenced
this pull request
May 31, 2026
…et6897) #1154 v2(PR #1164) flow 디코드 안전망이 PaintOp::Image 만 잡아 RawSvg 경로 누락. 한셀 OLE / 차트 OOXML / EMF 미리보기가 첫 로드 시 백지로 그려지던 회귀 수정. - rendering.rs collect(): PaintOp::RawSvg 도 image_count++ (scheduleReRender 재시도 발화) - page-renderer.ts prefetchFlowImages(): data:image/...;base64 직접 스캔 + dedupe Set 검증: build / test --lib rendering(6) / test --tests(1826, 0 failed) / clippy / fmt / tsc 통과. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
#1154 의 시각 증상은 단일 원인이 아니라 세 가지 독립 원인이 동시 발생 하고 있었습니다. 본 PR 은 세 원인 모두를 처리합니다.
PageRenderTree::clip_overlapping_same_bin_images()<img>가crop필드를 무시 → 원본 전체가 bbox 에 stretchcreateOverlayLayerwrapper div + overflow:hidden 패턴scheduleReRender(200/600ms) 를 초과 → 첫 렌더에 누락prefetchFlowImages안전망권위 자료: 한컴 PDF (pdf/exam_eng-2022.pdf) 와 페이지 2 박스 18 / 페이지 4 박스 27 / 페이지 4 박스 28 모두 시각 일치 확인.
변경 파일
src/renderer/render_tree.rs—clip_overlapping_same_bin_images+ 11 단위 테스트 (v1)src/document_core/queries/rendering.rs—build_page_tree끝에 clip 호출 (v1) + overlay JSON 에 crop 필드 출력 (v2)rhwp-studio/src/view/page-renderer.ts— OverlayImageInfo.crop / createOverlayLayer wrapper / scheduleReRender delays / prefetchFlowImages (v2)mydocs/{plans,working,report}/task_m100_1154*.md+task_m100_1154_v2*.md— 수행/구현 계획서, 단계별 + 최종 결과 보고서회귀 보호 / 검증
cargo test --release --lib: 1432 passed / 0 failed / 6 ignoredcargo clippy --release --lib -- -D warnings: cleannpx tsc --noEmit(rhwp-studio) : cleanMerge 옵션
squash merge 권장 — 두 commit (8ee17fd + 897db4e) 을 단일 commit 으로 통합.
Test plan
samples/exam_eng.hwp2 페이지 박스 18 (윈도우 chrome frame) 잔상 없이 단일 정상 표시 (rhwp-studio + SVG export)samples/exam_eng.hwp4 페이지 박스 27 / 28 (종이+핀 디자인) 외곽 그림 정상 표시 (rhwp-studio + SVG export)closes #1154
🤖 Generated with Claude Code