Skip to content

fix: 빈 paragraph + 다음 [쪽나누기] case 단독 page 차단 — HWP3 sample18 페이지 수 +2 inflate 해소 (closes #967)#968

Closed
jangster77 wants to merge 4 commits into
edwardkim:develfrom
jangster77:local/task967
Closed

fix: 빈 paragraph + 다음 [쪽나누기] case 단독 page 차단 — HWP3 sample18 페이지 수 +2 inflate 해소 (closes #967)#968
jangster77 wants to merge 4 commits into
edwardkim:develfrom
jangster77:local/task967

Conversation

@jangster77
Copy link
Copy Markdown
Contributor

@jangster77 jangster77 commented May 17, 2026

요약

samples/hwp3-sample18.hwp (HWP3) 페이지 수 rhwp 69 vs 한컴 67+2 inflate.

빈 paragraph (pi=27, pi=164) 직후 [쪽나누기] (pi=28, pi=165) case 에서 빈 paragraph 가 별도 page 분기 → 단독 빈 페이지 생성.

Root cause

src/renderer/typeset.rs:555-584next_will_vpos_reset 가드:

let next_force_break = next_para.column_type == ColumnBreakType::Page
    || next_para.column_type == ColumnBreakType::Section;
if next_force_break {
    false   // ← hwp-multi-001 회귀 차단 목적
}

pi=28/165 의 [쪽나누기] → next_force_break=true → 가드 미발동 → pi=27/164 단독 page 생성.

Fix (refined v2 — overflow 시에만 발동)

기존 가드 직후 별도 분기 추가:

} else if !st.current_items.is_empty() && para_idx + 1 < paragraphs.len() {
    // [Task #967] 빈 paragraph 직후 force page break (쪽나누기) case
    let next_force_break = next_para.column_type == ColumnBreakType::Page
        || next_para.column_type == ColumnBreakType::Section;
    let is_curr_empty = para.text.is_empty() && para.controls.is_empty();
    if next_force_break && is_curr_empty {
        // empty paragraph 의 예상 height = first line_seg 의 lh + ls
        let empty_h_px = para.line_segs.first().map(|s|
            hwpunit_to_px((s.line_height + s.line_spacing) as i32, self.dpi)
        ).unwrap_or(0.0);
        let avail = st.available_height() - st.current_height;
        if empty_h_px > avail {
            // 빈 paragraph 가 fit 안 됨 → skip 으로 단독 page 차단
            continue;
        }
        // fit 가능 — 정상 emit (기존 동작)
    }
}

핵심 정밀화: 빈 paragraph 가 실제로 fit 안 되는 case 만 skip. fit 가능한 case (aift.hwp 의 18 case) 는 정상 emit.

CI 회귀 (aift snapshot) 발견 후 v2 로 정밀화. v1 (조건 무관 skip) 은 aift.hwp page 3 의 normally-fitting empty paragraph 도 skip 하여 layout 변경 → snapshot 회귀.

검증

  • cargo test --release (전체): 통과 (lib 1288 + integration tests 8 svg_snapshot 포함)
  • sample18.hwp 페이지 수: 69 → 67 ✓ (한컴 정합)
  • 다중 sample 회귀 검증 (16 sample): 모두 회귀 0
    • sample, 10/11/13/14/16/19/4/5, table_test*, multi-table-001/002, exam_kor/math/eng
    • hwp-multi-001 (회귀 차단 case): 변경 없음 ✓
  • aift.hwp snapshot test: PASS ✓ (v1 회귀 해소)

영향

영역 영향
빈 paragraph + 다음 [쪽나누기] + overflow skip → +1 page inflate 제거
빈 paragraph + 다음 [쪽나누기] + fit 가능 영향 없음 (정상 emit)
비-빈 paragraph + 다음 [쪽나누기] 영향 없음 (기존 동작)
빈 paragraph + 다음 일반 paragraph 영향 없음 (기존 vpos-reset 가드)
hwp-multi-001 (회귀 차단) 영향 없음

잔존 (별도 task)

  • HWPX sample18-hwp5.hwpx +7 inflate — 별도 issue (HWPX 특화 pagination)

관련

  • 닫힌 issue #927 — sample16 페이지 수 inflate (본 fix 와 무관)

Test plan

  • cargo test --release 전체 통과 (lib + integration svg_snapshot 포함)
  • sample18.hwp 페이지 수 한컴 정합 (67)
  • 다중 sample 회귀 0 (특히 hwp-multi-001, aift.hwp)

🤖 Generated with Claude Code

… inflate 해소 (closes edwardkim#967)

## 본질

`samples/hwp3-sample18.hwp` (HWP3) 페이지 수 rhwp 69 vs 한컴 67 — **+2 inflate**.

## Root cause

`dump-pages` 분석:
- Page 2: pi=27 "(빈)" 1개만 (24px), vpos=69356 HU
- Page 14: pi=164 "(빈)" 1개만 (25.6px), vpos=69836 HU
- pi=28, pi=165 (다음 paragraph) 에 [쪽나누기] (column_type::Page)

빈 paragraph 가 이전 page 잔여 공간 초과 (page 1: body 935px, used 934.2px, 빈 24px → over) → 별도 page 분기 → +1 inflate × 2.

`src/renderer/typeset.rs:555-584` 의 `next_will_vpos_reset` 가드:
```rust
let next_force_break = next_para.column_type == ColumnBreakType::Page
    || next_para.column_type == ColumnBreakType::Section;
if next_force_break {
    false   // ← 다음 force_break 시 false (hwp-multi-001 회귀 차단 목적)
}
```

→ pi=28/165 의 [쪽나누기] → next_force_break=true → next_will_vpos_reset=false → 단독 빈페이지 차단 가드 미발동 → pi=27/164 단독 page 생성.

## Fix

`src/renderer/typeset.rs:584-604`: 기존 next_will_vpos_reset 가드 직후 별도 분기 추가.

```rust
} else if !st.current_items.is_empty() && para_idx + 1 < paragraphs.len() {
    // [Task edwardkim#967] 빈 paragraph 직후 force page break (쪽나누기) case 가드
    let next_para = &paragraphs[para_idx + 1];
    let next_force_break = next_para.column_type == ColumnBreakType::Page
        || next_para.column_type == ColumnBreakType::Section;
    let is_curr_empty = para.text.is_empty() && para.controls.is_empty();
    if next_force_break && is_curr_empty {
        continue;  // 빈 paragraph skip — 단독 page 차단
    }
}
```

기존 next_will_vpos_reset 의 next_force_break 제외 조건 (hwp-multi-001 회귀 차단) **보존**.

## 검증

- cargo test --release --lib: 1288 passed, 0 failed, 2 ignored
- sample18.hwp 페이지 수: 69 → 67 ✓ (한컴 정합)
- 다중 sample 회귀 검증 (16 sample):
  - sample, sample10/11/13/14/16/19/4/5, table_test*, multi-table-001/002, exam_kor/math/eng: 모두 변경 없음
  - **hwp-multi-001 (회귀 차단 case): 변경 없음 ✓** (기존 가드 보존 확인)

## 영향

| 영역 | 영향 |
|------|------|
| 빈 paragraph + 다음 [쪽나누기] | skip → +1 page inflate 제거 (회귀 fix) |
| 비-빈 paragraph + 다음 [쪽나누기] | 영향 없음 |
| 빈 paragraph + 다음 일반 paragraph | 영향 없음 (기존 가드) |
| hwp-multi-001 (회귀 차단) | 영향 없음 |

## 관련

- 닫힌 issue edwardkim#927 — sample16 페이지 수 inflate (본 fix 와 무관)
- 잔존: HWPX sample18-hwp5.hwpx +7 inflate (별도 issue, HWPX 특화 pagination)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
edwardkim added a commit that referenced this pull request May 17, 2026
…gn vertical bits 파싱 정정

@jangster77 — Issue #965: sample16 page 18 WMF 다이어그램 박스 내부 한글 텍스트
박스 외부 벗어남.

Root cause (src/wmf/converter/svg/mod.rs:2208 set_text_align): mode & VTA_TOP(=0x0000)
== 0x0000 항상 true → BASELINE/BOTTOM mode 도 VTA_TOP 오매핑 → +ascent shift →
baseline 박스 하단 걸침. WMF [MS-WMF] 2.1.2.18 spec 정합.

본질 (svg/mod.rs 3 영역 ~60 lines):
- set_text_align (:2208): v_bits = mode & 0x0018 마스킹 + 우선순위 BASELINE→BOTTOM→TOP
- ext_text_out (:811): baseline y shift 정합 (VTA_BASELINE=0, BOTTOM=-em*0.2, TOP=+ascent)
  — font.height < 0 잘못된 보정 제거
- text_out (:1545): META_TEXTOUT 동일 보정 (PR #918 미포함, 본 PR 추가)

PR #918 supersede: PR #918 (CLOSED 5/16, +5082/-74 거대 PR, 다양한 부작용
LibreOffice emfio/WASM RasterPlayer 등) → Stage 33-A root cause ~60 lines 만
단독 포팅. feedback_pr_supersede_chain (a) + feedback_small_batch_release_strategy
권위 사례.

본 환경 충돌 수동 해결: orders/20260517.md (--ours + Task #965 작업 일지 갱신).
svg/mod.rs auto-merge — devel PR #860/#864 (5/16, EMF/WMF image 렌더) 변경
보존 + PR #966 정정 양립 확인.

자기 검증: cargo test --release --lib 1288 passed / clippy 통과 /
광범위 sweep 7 fixture / 169 페이지 / 169 same / 0 diff / WASM 4.4 MB 재빌드
시각 판정: 작업지시자 시각 검증 통과 (sample16 p18 WMF 박스 한글 텍스트 정상
+ WMF sample14/4 + devel PR #860/#864 EMF/WMF 회귀 부재)
CI: ✅ Build & Test + CodeQL

@jangster77 연속 5 PR (#956~#964) 완결 후 추가 #966 (#968 후속)
edwardkim added a commit that referenced this pull request May 17, 2026
- mydocs/pr/archives/pr_966_review.md (WMF SetTextAlign vertical bits 분석)
- mydocs/pr/archives/pr_966_report.md (옵션 A + PR #918 supersede)
- mydocs/orders/20260517.md PR #966 행 추가

핵심:
- mode & VTA_TOP(=0) == 0 항상-true 버그 root cause
- WMF [MS-WMF] 2.1.2.18 spec 정합 (v_bits 0x0018 mask)
- PR #918 (CLOSED, +5082 거대 PR) → root cause ~60 lines 단독 포팅
- svg/mod.rs auto-merge (devel PR #860/#864 EMF/WMF image 렌더 보존)
- sweep 169/169 same + 작업지시자 시각 판정 통과
- Issue #965 close, 추가 PR #968 후속
@edwardkim edwardkim self-requested a review May 17, 2026 17:08
@edwardkim edwardkim added the enhancement New feature or request label May 17, 2026
@edwardkim edwardkim added this to the v1.0.0 milestone May 17, 2026
edwardkim added a commit that referenced this pull request May 17, 2026
@jangster77 — Issue #967: HWP3 sample18 페이지 수 rhwp 69 vs 한컴 67 (+2 inflate).
빈 paragraph (pi=27/164) 직후 [쪽나누기] (pi=28/165) → 빈 paragraph 별도
page 분기 → 단독 빈 페이지.

Root cause (typeset.rs:555-584): next_force_break (쪽나누기) 시
next_will_vpos_reset 가드 미발동 (hwp-multi-001 회귀 차단 목적) → 빈 paragraph
단독 page 생성.

본질 (src/renderer/typeset.rs:585 별도 분기):
next_force_break && is_curr_empty && empty_h_px > avail (overflow) 시에만
continue (skip → 단독 page 차단). fit 가능 시 정상 emit.

v2 정밀화: v1 (조건 무관 skip) → aift.hwp snapshot 회귀 → v2 (overflow 한정)
→ aift.hwp 18 case 정상 emit + sample18 fix. feedback_hancom_compat_specific_over_general
권위 사례 (일반화 위험 발견 후 케이스별 정밀화).

영역 좁힘: 빈 paragraph + 쪽나누기 + overflow 한정 — fit 가능/비-빈/일반
paragraph/hwp-multi-001 영향 없음.

본 환경 cherry-pick 충돌 0건 — typeset.rs auto-merge (devel Task #836 Endnote
:1064/1091 보존 + PR #968 :585 분기 양립). orders/20260518.md 신규 (5/18).
devel merge commit 2개 cherry-pick 제외 — 본질 3c1ea87 만.

자기 검증: cargo test --release 전체 (lib 1288 + integration svg_snapshot 8) ALL GREEN
+ cargo clippy --release -D warnings 통과 + 광범위 sweep 7 fixture / 169 페이지 /
169 same / 0 diff (aift.hwp 74 same — v1 회귀 해소 입증) + WASM 4.4 MB 재빌드.

⚠️ samples/hwp3-sample18.hwp 영역 영역 PR 미포함 + 본 환경 부재 — 작업지시자 결정
영역 영역 sweep 169/169 same + cargo test 전체 통과 영역 영역 회귀 부재 입증 +
sample18 페이지 수 69→67 정합 영역 영역 컨트리뷰터 PR 본문 신뢰 (작업지시자 승인).

CI: ✅ Build & Test + CodeQL + Canvas visual diff

@jangster77 PR 시리즈 완결 (연속 5 PR #956~#964 + #966 + #968)
@edwardkim
Copy link
Copy Markdown
Owner

@jangster77 머지 완료 (commit d0c25754).

next_force_break (쪽나누기) 시 next_will_vpos_reset 가드 미발동 (hwp-multi-001 회귀 차단 목적) root cause 정확 진단. v2 정밀화 — v1 (조건 무관 skip) → aift.hwp snapshot 회귀 → v2 (overflow 한정, empty_h_px > avail 시에만 skip) → aift.hwp 18 case 정상 emit + sample18 fix. 일반화 위험 발견 후 케이스별 정밀화 (feedback_hancom_compat_specific_over_general 권위 사례).

본 환경 자기 검증:

참고: samples/hwp3-sample18.hwp 영역 영역 본 PR 미포함 + 본 환경 부재 (PR 본문 fixture 추가 명시 부재). 작업지시자 결정 영역 영역 sweep 169/169 same + cargo test 전체 통과 영역 영역 회귀 부재 입증 + sample18 페이지 수 69→67 정합 영역 영역 PR 본문 신뢰 영역 영역 머지. 차후 sample18 fixture 별도 추가 권장 (회귀 가드).

CI ✅ Build & Test + CodeQL + Canvas visual diff. 잔존: HWPX sample18-hwp5.hwpx +7 inflate (별도 task).

@jangster77 PR 시리즈 (연속 5 PR #956~#964 + #966 + #968) 완결. 수고하셨습니다.

@edwardkim edwardkim closed this May 17, 2026
edwardkim added a commit that referenced this pull request May 17, 2026
- mydocs/pr/archives/pr_968_review.md (빈 paragraph + 쪽나누기 v2 정밀화 분석)
- mydocs/pr/archives/pr_968_report.md (옵션 A + fixture 부재 작업지시자 결정 + PR 시리즈 총평)
- mydocs/orders/20260518.md 신규 PR 처리 섹션 + @jangster77 PR 시리즈 총평

핵심:
- next_force_break 가드 미발동 root cause + v2 정밀화 (overflow 한정, aift.hwp 회귀 해소)
- typeset.rs auto-merge (devel Task #836 Endnote 보존 + PR #968 :585 분기 양립)
- cargo test 전체 + sweep 169/169 same + 기존 HWP3 샘플 시각 판정 통과
- samples/hwp3-sample18.hwp 부재 → 작업지시자 결정 회귀 부재 입증 + PR 본문 신뢰
- Issue #967 close, @jangster77 PR 시리즈 (7 PR #956~#968) 완결
- 잔존: HWPX sample18-hwp5 +7 inflate + sample18 fixture 추가 권장
edwardkim added a commit that referenced this pull request May 17, 2026
버전 0.7.11 → 0.7.12 (Cargo.toml + rhwp-vscode/npm-editor/rhwp-studio package.json)
CHANGELOG 갱신 (CHANGELOG.md / CHANGELOG_EN.md / rhwp-vscode/CHANGELOG.md)
WASM 재빌드 산출물 동기화 (rhwp-studio/public/rhwp.js)

@jangster77 7-PR 시리즈 (#956~#968) + Issue #952 5-결함 완결 + WMF #966 + HWP3 #968 + LTO #818

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@edwardkim edwardkim mentioned this pull request May 17, 2026
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