Skip to content

fix(render): #1154 잔상 통합 fix — clip + overlay crop + flow image prefetch#1164

Merged
edwardkim merged 2 commits into
edwardkim:develfrom
planet6897:pr/task-1154
May 29, 2026
Merged

fix(render): #1154 잔상 통합 fix — clip + overlay crop + flow image prefetch#1164
edwardkim merged 2 commits into
edwardkim:develfrom
planet6897:pr/task-1154

Conversation

@planet6897

Copy link
Copy Markdown
Contributor

Summary

#1154 의 시각 증상은 단일 원인이 아니라 세 가지 독립 원인이 동시 발생 하고 있었습니다. 본 PR 은 세 원인 모두를 처리합니다.

# 원인 노출 경로 처리 commit
1 동일 bin_id Pic 컨트롤 수직 인접 시 세로 스케일 미스매치 → SVG 리샘플링/안티엘리어싱 잔상 SVG / Canvas / CanvasKit 공통 8ee17fd (v1) PageRenderTree::clip_overlapping_same_bin_images()
2 rhwp-studio 의 BehindText/InFrontOfText overlay <img>crop 필드를 무시 → 원본 전체가 bbox 에 stretch rhwp-studio overlay only 897db4e (v2) overlay JSON 에 crop 필드 추가 + createOverlayLayer wrapper div + overflow:hidden 패턴
3 큰 PNG/JPEG (≈ 100+ KB) 의 비동기 디코드가 scheduleReRender (200/600ms) 를 초과 → 첫 렌더에 누락 rhwp-studio flow layer only 897db4e (v2) delays 확장 + prefetchFlowImages 안전망

권위 자료: 한컴 PDF (pdf/exam_eng-2022.pdf) 와 페이지 2 박스 18 / 페이지 4 박스 27 / 페이지 4 박스 28 모두 시각 일치 확인.

변경 파일

  • src/renderer/render_tree.rsclip_overlapping_same_bin_images + 11 단위 테스트 (v1)
  • src/document_core/queries/rendering.rsbuild_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 — 수행/구현 계획서, 단계별 + 최종 결과 보고서

회귀 보호 / 검증

  • Stage 17 영향 sample 합계 379 페이지 중 exam_eng page 2 한 장만 변경, 나머지 100% 불변 (v1 검증)
  • 시각: 한컴 PDF, SVG export, rhwp-studio (BehindText overlay + flow large PNG) 모두 시각 일치
  • 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 빌드 통과

Merge 옵션

squash merge 권장 — 두 commit (8ee17fd + 897db4e) 을 단일 commit 으로 통합.

Test plan

  • samples/exam_eng.hwp 2 페이지 박스 18 (윈도우 chrome frame) 잔상 없이 단일 정상 표시 (rhwp-studio + SVG export)
  • samples/exam_eng.hwp 4 페이지 박스 27 / 28 (종이+핀 디자인) 외곽 그림 정상 표시 (rhwp-studio + SVG export)
  • 권위 PDF (pdf/exam_eng-2022.pdf) 와 시각 비교 일치
  • 기존 BehindText overlay 있는 다른 문서 회귀 없음 (워터마크 페이지 등)
  • 페이지에 그림이 없는 일반 페이지 렌더 비용 영향 없음 (imageCount=0 skip)

closes #1154

🤖 Generated with Claude Code

planet6897 and others added 2 commits May 29, 2026 16:00
## 문제

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>
@edwardkim edwardkim self-requested a review May 29, 2026 07:12
@edwardkim edwardkim added the enhancement New feature or request label May 29, 2026
@edwardkim edwardkim added this to the v1.0.0 milestone May 29, 2026
@edwardkim edwardkim merged commit 969cd02 into edwardkim:devel May 29, 2026
7 checks passed
@edwardkim

Copy link
Copy Markdown
Owner

devel 에 머지했습니다 (`969cd022`). #1154 의 세 원인을 모두 확인했습니다:

  1. clip_overlapping_same_bin_images() — 동일 bin_id 수직 인접 그림의 세로 스케일 미스매치 잔상 정정. strict 가드(같은 bin_id + x/width 동일 + 세로 겹침)와 11개 단위 테스트가 의도적 시각 효과를 보호하는 점 좋았습니다.
  2. overlay crop — wrapper div + overflow:hidden 으로 source rect 처리 (75 HU/px 룰 정합).
  3. flow prefetch — 큰 PNG 비동기 디코드 누락 안전망.

devel 최신(#1167 SVG z-order)과 render_tree.rs 의 다른 영역을 수정하여 충돌 없이 병합됐고, 전체 테스트 + native-skia + #1167/svg_snapshot 공존 회귀 없음을 확인했습니다.

edwardkim added a commit that referenced this pull request May 29, 2026
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants