Skip to content

Task #534 v2 + #537 + #539 + #540 + #544 + #547 + #548: lazy_base / overlay-shape / 빈 paragraph 음수 ls floor / paragraph border 좌표·텍스트 inset / 셀 inline shape margin (21_언어_기출 줄간격 정정)#538

Closed
planet6897 wants to merge 200 commits into
edwardkim:develfrom
planet6897:devel

Conversation

@planet6897

@planet6897 planet6897 commented May 2, 2026

Copy link
Copy Markdown
Contributor

Summary

5개 task 합본 PR. 21_언어_기출_편집가능본.hwp 줄간격 정합 + paragraph border 좌표 정합.

Task #537 — TAC 표 직후 첫 답안

작업지시자 보고 11곳 (P2 q3 등) 모두 IR vpos delta 와 정확 일치.

위치 라인수 수정 전 (px) 수정 후 (px) IR (px)
P2 q3 3 63.09 72.64 72.64
P5 q9 1+2 14.67 24.21 24.21
그 외 9곳 2 38.88 48.43 48.43

근본 원인: prev_tac_seg_applied 가드 + Task #479 trailing-ls 제외 + lazy_base sequential drift 동결.

Task #539 — 글박스 호스트 직후 (#537 후속)

작업지시자 보고 그룹 B 2곳 (7p 르포르, 9p 수피즘).

위치 gap (수정 전) gap (수정 후) IR
7p pi=145→146 14.67 px 24.21 px 24.21
9p pi=181→182 14.67 px 24.21 px 24.21

근본 원인: pi=145/pi=181 의 controls 에 Shape wrap=InFrontOfText tac=true 보유. prev_has_overlay_shape 가드가 treat_as_char 무관하게 true → 직후 paragraph 의 vpos correction skipped.

Task #540 — 빈 paragraph 음수 ls floor (#539 그룹 A 후속)

가설 H2 채택 — 한컴은 빈 paragraph 의 음수 line_spacing 을 floor (무시) 하여 advance = line_height 만큼만 진행.

위치 gap (수정 전) gap (수정 후) 한컴 측정값
P2 col1 [4~6]→지문 33.01 px 38.88 px 38.88

근본 원인: HWP IR 의 LINE_SEG.line_spacing 은 음수 값 그대로 저장. vpos correction 시 음수 ls 가 반영되어 빈 paragraph advance 가 한컴보다 짧음.

정정 (layout.rs):
```rust
let mut vpos_neg_ls_floor_total: i32 = 0;
let prev_neg_ls_floor: i32 = paragraphs.get(prev_pi)
.map(|p| {
if !p.text.is_empty() || !p.controls.is_empty() { return 0; }
p.line_segs.iter()
.map(|s| if s.line_spacing < 0 { -s.line_spacing } else { 0 })
.sum::()
})
.unwrap_or(0);
vpos_neg_ls_floor_total += prev_neg_ls_floor;
let vpos_end = vpos_end_raw + vpos_neg_ls_floor_total;
let lazy_base = prev_vpos_end - y_delta_hu + vpos_neg_ls_floor_total;
```

가드 근거: `text.is_empty() && controls.is_empty()` — synam-001 의 음수 ls 57건 중 일반 paragraph 보존, exam_science section-setup paragraph (cc=57 controls=7 ls=-1348) floor 제외.

Task #544 — paragraph border 좌표 PDF 정합 (#540 후속)

작업지시자 시각 검증 (PDF 비교) 결과 passage 글상자 위치/크기 차이 발견. 두 본질 정정.

항목 PDF rhwp 수정 전 rhwp 수정 후
박스 top y (페이지 4) 233.8 224.4 (-9.4 px) 231.97 (-1.84 px ✓)
박스 left x 117.0 128.5 (+11.5 px) 117.17 (+0.34 px ✓)
박스 width 425.1 402.5 (-22.6 px) 425.17 (+0.04 px ✓)

근본 원인 (1): ParaShape margin_left/right 가 박스 outline 좌표에 적용 (box_x = col_area.x + box_margin_left). 21_언어_기출 의 ps_id=11 margin_left=1704 HU → 11.36 px 시프트 노출. PDF 는 col_area 전체.

근본 원인 (2): vpos correction 가드 (seg.vertical_pos == 0 && prev_pi > 0) 가 페이지 시작 paragraph 직후 transition 의 vpos correction 도 skip 시킴 → trailing-ls 716 HU 가 sequential y_offset 에서 누락. PDF 는 IR vpos 기반.

정정 (`layout.rs` + `paragraph_layout.rs`):
```rust
// LayoutEngine 에 보정 Cell 추가
paragraph_border_y_correction_px: Cell,

// vpos correction 가드 skip 시 trailing-ls set
if seg.vertical_pos == 0 && prev_pi > 0 {
let trailing_ls_hu = seg.line_spacing.max(0);
let next_has_border = ...; // 다음 paragraph 가 border 가지면
if trailing_ls_hu > 0 && next_has_border {
self.paragraph_border_y_correction_px.set(
hwpunit_to_px(trailing_ls_hu, self.dpi)
);
}
}

// paragraph_layout 에서 bg_y_start 보정 + reset
let bg_y_start = if para_border_fill_id > 0 {
let corrected = y_start + self.paragraph_border_y_correction_px.get();
self.paragraph_border_y_correction_px.set(0.0);
corrected
} else {
self.paragraph_border_y_correction_px.set(0.0);
y
};

// box_x / box_w 산식 정정 (margin 미적용)
let (box_x, box_w) = if let Some((ox, ow)) = self.border_box_override.get() {
(ox, ow)
} else {
(col_area.x, col_area.width)
};
```

Task #540 Stage 4 push skip 가드 revert: Task #544 의 trailing-ls 보정이 동일 회귀 (passage 박스 안 위쪽 여백 증가) 를 더 본질적으로 해결하므로 임시 우회 제거. 페이지 2 [4~6] 박스 PDF 정합 (Stage 4 만으로는 +12.83 px 차이) + 코드 단순화.

박스 좌표 검증 (페이지 2/4/10 모두 PDF 일치, ±2 px tolerance 내).

검증

잔존 / 후속

  1. base=716 잔존 케이스 (Task 21_언어_기출.hwp TAC 표 직후 첫 문단의 trailing line_spacing 누락으로 줄간격 좁아짐 #537 잔존): Task 21_언어_기출.hwp 지문 시작 표시 [X~Y] 직후 + 박스 안 paragraph 줄간격 좁음 (Task #537 후속) #539 fix 로 일부 해소됐을 가능성. 별도 검증 후 처리.
  2. 페이지 6/8/13/14/15 박스 자동 검출 한계 (Task 21_언어_기출 passage 박스 (paragraph border) 위치/크기 PDF 정합 (Task #540 후속) #544): paginator 분배 차이 (페이지/컬럼 배치) 로 인한 것. 본 PR 과 무관, 별도 분석 task 등록 가능.
  3. paragraph border 회귀 검증 자동화: 박스 outline 좌표 회귀 검증 자동화 추가 권고.
  4. Clippy 기존 결함 (table_ops.rs:1007, object_ops.rs:298): 본 PR 과 무관, 별도 issue 권장.

Test plan

closes #537
closes #539
closes #540
closes #544

planet6897 and others added 30 commits April 29, 2026 11:54
exam_kor.hwp 24페이지 → 20페이지 정합 작업 1단계.

- 페이지별 단별 used/hwp_used/diff CSV (output/debug/task435/)
- 회귀 대상 5문서 페이지 수 (exam_kor 24, exam_eng 8, k-water-rfp 28, hwpspec 177, synam-001 35)
- RHWP_TYPESET_DRIFT 출력 캡처 (pi=0.30, pi=1.25 split 진단)
- compute_body_wide_top_reserve_for_para 산정 경로 추적

핵심 진단: col 1 reserve 306.1px (HWP 실제 94.5px 대비 +211.6px 과대).
원인: Paper-rel 좌표를 body-rel 변환 없이 그대로 reserve 에 누적.
Stage 2 에서 typeset.rs:2127-2172 의 VertRelTo::Paper 분기 정정.

수행계획서/구현계획서/Stage 1 보고서 포함.
compute_body_wide_top_reserve_for_para 의 VertRelTo::Paper 분기에서
body-rel 변환 누락 정정. body 와 일부만 겹치는 (header→body 침범)
케이스에서 Paper-rel 좌표를 그대로 reserve 에 누적하던 버그 수정.

수정 전 reserve = shape_y_offset(paper) + h + outer_bottom = 306.1 px
수정 후 reserve = max(0, shape_top_abs - body_top) ... = 94.4 px

결과:
- exam_kor.hwp: 24 → 22 페이지 (page 2, 15 orphan 해소)
- pi=0.30, pi=1.25 split → FullParagraph
- 회귀: exam_eng 8, k-water-rfp 28, hwpspec 177, synam-001 35 유지
- cargo test: 1062 passed

Stage 3 에서 일반 페이지 누적 -100~-300px 정정 (22 → 20).
원래 가설 ("표/도형 후 컬럼 잔여 공간 산정 부족") 재검토.
RHWP_TYPESET_DRIFT 분석 결과 diff 메트릭은 typeset cur_h 누적
(height_for_fit, trail_ls 제외) vs hwp_used (last line vpos+lh)
의 좌표계 차이를 측정하는 것일 뿐, "rhwp 가 채울 수 있는데 못 채운
잔여 공간" 이 아님을 확인.

실제 22→20 페이지 단축 장애물:
1. 섹션 1 페이지 14: Square wrap 표 + col 0 over-fill (1225>1211)
   → col 1 under-use (64px)
2. 섹션 1 페이지 15: 단일 컬럼 출력 (단정의는 2단인데 단 1 누락)
3. 섹션 2 페이지 18: pi=11 split + pi=13 [단나누기] orphan-like

3가지 전부 해결 시 22→19 가능 (목표 20 도달).

옵션 A/B/C 결정 필요 (현 상태 종료 / Stage 4 확장 / Stage 4 부분).
옵션 A 종료: 24→22 페이지 (Stage 2 col 1 reserve 정정).
잔여 22→20 미달성, 3가지 별도 메커니즘 (Square wrap over-fill,
단일 컬럼 출력 버그, col 0 cur_h over-advance) 별도 task 분리 권고.

edwardkim#393 (옵션 A) 본 task Stage 2 로 적용 완료, close 가능.
수행계획서(task_m100_439.md) + 단계1 진단 보고서.

핵심 발견:
- 이슈 가설의 engine.rs:702-711 는 fallback 경로 (RHWP_USE_PAGINATOR=1).
  기본 활성 엔진은 typeset.rs::TypesetEngine.
- 실제 버그 위치: typeset.rs::place_table_with_text (1400-1467).
  engine.rs 의 !is_wrap_around_table 가드 (engine.rs:1349, 1422) 누락.
- 페이지 14 col 0 used=1225.8 (본문 1211.3 초과 +14.5) 정확히 재현.

코드 변경 없음 — 임시 디버그 코드는 모두 revert.
place_table_with_text 의 정확한 cur_h 누적 추적 (디버그 후 revert):
- Square wrap 4 표가 pre_height + table_total 합산 → +244.88 px 과다
- HWP 의도: max(호스트 텍스트, 표 + v_offset) 만 누적

수정안: typeset.rs::place_table_with_text 에서
  current_height += max(pre_height, v_off + table_total) (Square wrap 시)

코드 변경 없음 — 임시 디버그 println 모두 revert.
typeset.rs::place_table_with_text 에서 Square wrap (어울림) 표일 때
current_height 누적을 pre_height + table_total → max(pre_height, v_off + table_total)
로 변경.

호스트 문단 텍스트와 어울림 표는 같은 수직 영역을 공유하므로
더 큰 쪽만 한 번 누적해야 HWP layout 의도와 일치.
PageItem 자체는 PartialParagraph + Table 모두 push (layout 렌더링 보존).

검증:
- 페이지 14 col 0 used 1225.8 → 1036.1 px (≤ 1211.3 충족)
- exam_kor.hwp 22 → 20 페이지 (목표 ≤ 21 충족)
- 회귀 샘플 6 종 (exam_eng/math, 21언어, aift, 2010-01-06, biz_plan) 페이지 수 동일
- cargo test 1066 개 모두 통과
- SVG 렌더링 정상

closes edwardkim#439
Stage 4 회귀 검증 결과:
- 149 개 sample HWP 전수 페이지 수 비교: exam_kor 만 22→20 변경, 148개 동일
- cargo test 1066 passed
- DoD 전부 충족

본 task 완료. closes edwardkim#439.
typeset.rs::place_table_with_text 의 Square wrap 표 누적 정책을
pre_height + table_total → max(pre_height, v_off + table_total) 로 변경.

효과:
- exam_kor.hwp 페이지 14 col 0 used 1225.8 → 1036.1 px (over-fill 해소)
- exam_kor.hwp 22 → 20 페이지
- 149 개 sample 중 exam_kor 만 변화, 148 개 동일 (회귀 0건)
- cargo test 1066 passed

closes edwardkim#439
…문제 수정

- 수행/구현 계획서 + Stage 1·2 보고서 작성
- src/renderer/layout.rs: paragraph border merge 그룹을 col_area 바닥/꼭대기로 클램프
- exam_kor p2/5/8/15 의 세로 구분선이 PDF 와 일치하는 길이로 정상화
  (p8: 1671 → 1425, 246px 단축, 페이지 바깥 침범 해소)
- vpos-reset 미존중으로 인한 텍스트 자체의 overflow 는 별도 이슈로 분리
- snapshot 갱신: tests/golden_svg/issue-267/ktx-toc-page.svg
  (invisible 구조 rect 의 height 5.34px 변화, 가시 변화 없음)
페이지 번호 박스가 column divider line 과 붙어 보이는 문제 해결.

원인: 꼬리말 paragraph 의 vert=Para + wrap=TopAndBottom 표가 paragraph
top 에 배치되어 본문 바닥과 같은 y 에 위치. HWP 의 실제 동작은 첫 라인의
line_height/2 만큼 아래에 anchor 되어 본문과 시각적 갭 형성.

수정: layout_header_footer_paragraphs 에서 해당 조건의 첫 paragraph 표는
y_offset 에 line_height/2 (px) 를 더하여 배치.

검증 (exam_kor.hwp 20p):
- 박스 top y: 1422.93 → 1439.47 (PDF 380.6mm 와 일치)
- column line - 박스 갭: 0px → 16.3-17.0px (PDF 16.0px 와 일치)
- column line 길이/위치는 PDF 자연 그대로 유지 (p1 1131px, p2+ 1226px)
- cargo test --release: 1117 passed, 0 failed
이슈 edwardkim#445 의 두 시각적 결함 (paragraph border 페이지 바깥 침범 + 페이지
번호 박스가 column line 에 붙음) 모두 PDF 와 일치하도록 수정 완료.

승인 후 local/devel 머지 + edwardkim#445 close 예정.
- 수행계획서 (mydocs/plans/task_m100_452.md): 옵션 A 선언, 4단계 분해
- 구현 계획서 (mydocs/plans/task_m100_452_impl.md): paragraph_layout.rs:2511-2520
  is_para_last_line 분기 제거, is_cell_last_line 만 보존
- Stage 1 보고서 (mydocs/working/task_m100_452_stage1.md):
  - exam_kor pi=1.line9↔pi=2.line0 step = 15.34px (버그) ↔ 단락내 24.51px
  - 10종 샘플 페이지 수 baseline 캡처 (/tmp/task_452_baseline/)
  - 21_언어 p1 col 1 pi=26+보기①②③ fit 확인 (edwardkim#332 회귀 baseline)
- orders 갱신: 버그 섹션 edwardkim#452 항목 추가
… + golden 갱신

- src/renderer/layout/paragraph_layout.rs:2511-2519: is_para_last_line 분기
  제거. is_cell_last_line(셀 내) 만 trailing 제외 보존, 본문 단락은 모든 줄에서
  y += lh + ls 통일. pagination/engine.rs 의 current_height 누적과 정합.
- 검증: exam_kor 1페이지 pi=1.line9↔pi=2.line0 step = 15.34 → 24.50 px
  (단락내 step 과 동일). cargo test --lib 1066 passed.
- golden SVG 2건 baseline 갱신 (Task edwardkim#332 에서 갱신된 것을 본 정합으로 재갱신):
  - tests/golden_svg/issue-147/aift-page3.svg
  - tests/golden_svg/issue-157/page-1.svg
- LAYOUT_OVERFLOW 메시지: 페이지 마지막 단락의 trailing ls (~10.9 px)
  가 col_bottom 을 살짝 넘으나 빈 공간이므로 시각 무영향. pagination
  engine 의 effective_trailing 처리로 페이지 분배는 유지.
paragraph_layout.rs:2511-2520 의 is_para_last_line 분기 제거. 본문 단락
모든 줄에서 y += lh + ls 통일 → pagination/engine.rs 의 current_height
누적과 정합. is_cell_last_line(셀 내) 만 trailing 제외 보존.

효과: exam_kor pi=1.line9↔pi=2.line0 step 15.34→24.50 px (단락내 step 과
동일, PDF 정합). 10종 샘플 페이지 수 변동 0. Task edwardkim#332 회귀 0 (21_언어
p1 col 1 pi=26+보기①②③ fit 유지). cargo test --lib 1066 passed +
svg_snapshot 6/6 (issue-147/157 baseline 재갱신).

closes edwardkim#452
paragraph_layout 의 skip_text_for_inline_shape 분기 제거. 인라인 글상자가 있는
줄에서 외부 문단 본문이 통째로 스킵되던 버그를 수정.

원인: skip_text_for_inline_shape 가 글상자 내부 텍스트와 외부 문단 본문 텍스트를
혼동하여, 외부 본문(글상자 좌·우의 일반 텍스트)도 함께 스킵하고 있었음.
글상자 자체와 내부 텍스트는 shape_layout 의 inline_shape_position 경로로 별도
렌더되므로 외부 본문을 항상 렌더해도 중복되지 않는다.

검증: samples/exam_kor.hwp 페이지 2 좌측 단 line 2 의 본문 39자(척사파의 ...
거스를) 가 글상자 좌·우로 분리 렌더되어 복원됨. 페이지 수 20 유지, 전체
테스트 1117 passed.
Stage 3 회귀 검증 결과 (1117 tests passed, 페이지 수 회귀 0).
Stage 4 최종 결과보고서 및 PR 메시지(`mydocs/report/task_m100_455_pr.md`) 작성.
오늘할일에 Task edwardkim#455 항목 추가.
`on_first_multicolumn_page` 가드는 새 페이지 시작 시 false 로 리셋되어
다단 구역이 여러 페이지에 걸칠 때 후속 페이지에서
`detect_column_breaks_in_paragraph` 호출이 차단됨. HWP 원본의 LINE_SEG
vpos 리셋 위치가 무시되어 좌측 단 하단이 col_bottom 을 초과해 그려지던
버그.

수정: `typeset.rs:856` (기본 경로) + `pagination/engine.rs:607`
(RHWP_USE_PAGINATOR=1 fallback) 의 가드에서 `on_first_multicolumn_page`
조건 제거.

검증:
- exam_kor p2 pi=39 0..4/4..7 → 0..2/2..7 (HWP 원본 정합)
- exam_kor LAYOUT_OVERFLOW 36 → 16 (페이지 5/8 의 동일 메커니즘 자동 해소)
- 다단 샘플 4종 (exam_eng/math/science/social) 페이지 수 + SVG byte 동일
- cargo test --release 1120 passed, 0 failed

closes edwardkim#459
planet6897 and others added 5 commits May 3, 2026 12:38
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
페이지 4 [7~9] 박스 PDF vs SVG 비교 결과 본질 분석:

(1) 박스 left/width 차이: ParaShape margin (HWP 2배 저장) 이 SVG 박스 outline
    에 적용. PDF 는 col_area 전체 폭 (margin 미적용). pi=82 margin_left=1704 HU
    → resolved 852 HU = 11.36 px 가 box_x 에 더해져서 11.5 px 시프트.

(2) 박스 top y 차이: SVG bg_y_start 가 sequential y_offset (Task edwardkim#479 trailing-ls
    제외). PDF 는 IR vpos 기반. pi=80 line_spacing 716 HU 가 SVG box top 에서
    누락되어 9.4 px 위로 쏠림.

(3) PDF 9개 passage 박스 모두 일관 패턴 (col_width 425.1 px, x=col_area.x).

광범위 회귀 위험 매우 큼 (paragraph border 본질 변경, 모든 샘플 영향).
[feedback_essential_fix_regression_risk] [feedback_pdf_not_authoritative]
[feedback_rule_not_heuristic] 메모리 룰 적용. 작업지시자 검증 입력 대기:

1. 다른 샘플 PDF 비교 (synam-001, 복학원서 등)
2. 한컴 2020 / 한컴독스 환경 비교
3. HWP 표준 명세 paragraph border 좌표 산출 룰
4. fix 범위 (A: 광범위 / B: 빈 paragraph 직후만 / C: heuristic)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… 범위 결정 대기)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
테스트:
- test_544_passage_box_coords_match_pdf_p4 추가 (RED, ignore attribute)
- 페이지 4 [7~9] col 0 박스 top_y/left_x/width PDF 정합 검증
- 1120 기존 테스트 모두 통과

진단 결과:

(1) 박스 top y -9.4 px: layout.rs:1481 가드 (seg.vertical_pos == 0 &&
    prev_pi > 0) 가 페이지 시작 paragraph 의 vpos correction 도 skip 시킴
    → trailing-ls 716 HU 누락. 가드 의도 (vpos reset 보호) 가 페이지 시작
    case 와 구분 안 됨.

(2) 박스 left x +11.5 px / width -22.6 px: paragraph_layout.rs:2697 의
    box_x = col_area.x + box_margin_left 산식. ParaShape margin_left=1704 HU
    (2배 저장) → resolved 11.36 px 가 박스 좌표에 적용. PDF 는 미적용.

광범위 사전 평가:
- exam_math (표 outline 우세) / exam_eng (margin=0) / 21_언어_기출 (margin=1704)
- 모든 샘플 동일 산식, margin_left=0 샘플은 우연히 PDF 일치
- 21_언어_기출 만 큰 margin 으로 차이 노출

권장 fix (A안 부분):
- 박스 left/width: (col_area.x, col_area.width) 로 변경 (광범위)
- 박스 top y: bg_y_start 보정 (paragraph border 한정, 회귀 위험 최소)

작업지시자 입력 대기:
1. fix 범위 (A안 부분) 진행 OK?
2. 셀 내부 paragraph border 케이스 샘플
3. wrap=Square 호스트 케이스 샘플
4. 한컴 2020 / 한컴독스 검증

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
(1) 박스 left/width — paragraph_layout.rs:2697:
    box_x = col_area.x, box_w = col_area.width (margin 미적용)

(2) 박스 top y — paragraph_layout.rs:786 + layout.rs:1492:
    LayoutEngine 에 paragraph_border_y_correction_px Cell 추가.
    vpos correction 가드 skip 케이스에서 다음 paragraph 가 border 가지면
    prev paragraph 의 trailing-ls 를 보정값으로 set. paragraph_layout
    진입 시 read 후 reset, bg_y_start 에 더함 (본문 텍스트 위치 보존).

(3) Task edwardkim#540 Stage 4 push skip 가드 revert:
    Task edwardkim#540 Stage 4 는 cumulative comp 후 박스 위쪽 여백 회귀의 임시
    우회. Task edwardkim#544 의 trailing-ls 보정이 동일 회귀 본질 해결. Stage 4
    우회 제거로 페이지 2 [4~6] PDF 정합 + 코드 단순화.

검증:
- test_544_passage_box_coords_match_pdf_p4 RED → GREEN
- 1120 unit tests 통과
- 21_언어_기출 핵심 박스 (페이지 2 [4~6] / 페이지 4 [7~9] / 페이지 10 [16~18])
  모두 PDF 일치 (-1.84 px tolerance 내)
- 광범위 회귀: text 음의 시프트 0건, line diff 는 paragraph border 정정의
  의도된 결과

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@edwardkim

Copy link
Copy Markdown
Owner

메인테이너가 회귀 검증을 진행하다가 samples/exam_science.hwp 파일의 2 페이지에서 페이지네이션 회귀 발견을 하였습니다. 0.7.9 에서는 발생되지 않은 케이스여서 어디서 부터 회귀가 발생되었는지 점검 중입니다.

planet6897 and others added 3 commits May 3, 2026 13:26
검증 결과:
- 1120 unit tests 통과 (Task edwardkim#537/edwardkim#539/edwardkim#540 무회귀)
- 21_언어_기출 핵심 3개 박스 (페이지 2/4/10) PDF 일치 (-1.84 px tolerance)
- 광범위 회귀 (6 샘플, 104 페이지): 텍스트 -시프트 0건
- synam-001 (음수 ls 57건): 0 변경 (보존)

페이지 6/8/13/14/15 박스 자동 검출 한계는 paginator 분배 차이 (페이지/컬럼
배치) 로 인한 것으로 본 task 와 무관 (별도 분석).

오늘할일 (orders/20260503.md) Task edwardkim#544 항목 추가.

closes edwardkim#544

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…edwardkim#540 후속)

paragraph border 좌표 산출 두 본질 정정:
(1) box_x/box_w: paragraph margin 미적용, col_area 전체 사용
(2) bg_y_start: vpos correction 가드 skip 시 trailing-ls 보정 (paragraph
    border 한정, 본문 텍스트 위치 보존)

Task edwardkim#540 Stage 4 의 임시 우회 (push skip 가드) 는 Task edwardkim#544 의 본질 정정이
동일 회귀 (passage 박스 안 위쪽 여백) 를 더 본질적으로 해결하므로 revert.

검증: 페이지 4 [7~9] 박스 PDF 일치 (top/left/width 모두 ±2 px), 1120 unit
tests 통과, 광범위 회귀 (6 샘플) 텍스트 -시프트 0건.

closes edwardkim#544

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…dwardkim#540 후속)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@edwardkim

Copy link
Copy Markdown
Owner

다른쪽 PR 처리하면서 발생된 것으로 확인되어 이번 PR 처리 재개하겠습니다.

@planet6897 planet6897 changed the title Task #534 v2 + #537 + #539 + #540: lazy_base / overlay-shape / 빈 paragraph 음수 ls floor (21_언어_기출 줄간격 정정) Task #534 v2 + #537 + #539 + #540 + #544: lazy_base / overlay-shape / 빈 paragraph 음수 ls floor / paragraph border 좌표 정합 (21_언어_기출 줄간격 정정) May 3, 2026
edwardkim added a commit that referenced this pull request May 3, 2026
cherry-pick (본질 12 commits 만 — fork devel 누적 170 commits 제외):
- e7f1adb~1bca866: Task #534 v1 (4 commits, layout_shape_item TAC Picture inner_pad)
- 576aa29~57ccde6: Task #534 v2 (2 commits, LINE_SEG.column_start 정합)
- d70599d~58af6c9: Task #537 (3 commits, lazy_base trailing-ls 보정)
- f60f580~fc32bd3: Task #539 (3 commits, prev_has_overlay_shape 가드 완화)

검증:
- cargo test --lib 1113 passed
- cargo test --test issue_530/505/418/501 회귀 0
- cargo test --test svg_snapshot 6/6 passed
- cargo clippy --lib 0 건
- cargo build --release 정상
- WASM 빌드 4,461,870 bytes + studio 동기화

시각 판정 (작업지시자 한컴 2010/2020 직접 판정):
- 1차 (SVG export-svg) 통과
- 2차 (rhwp-studio web Canvas) 통과

부수 발견 (별도 이슈):
- 이슈 #545 — aift.hwp p41 표 위치 (PR #538 와 무관, 이전부터)
- 이슈 #546 — exam_science.hwp p2 페이지네이션 회귀 (PR #506 origin, bisect 확정)

컨트리뷰터 안내: 다음 PR 전 fork devel 동기화 부탁.

산출물:
- mydocs/pr/pr_538_review.md (검토)
- mydocs/pr/pr_538_review_impl.md (cherry-pick 절차)
- mydocs/pr/pr_538_report.md (처리 결과)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
edwardkim added a commit that referenced this pull request May 3, 2026
…— cherry-pick @planet6897 12 commits) — closes #534/#537/#539

본 PR 은 외부 컨트리뷰터 @planet6897 (Jaeuk Ryu) 의 PR.
21_언어_기출_편집가능본.hwp 의 줄간격 716 HU drift + exam_kor 18p 그림 위치
결함의 본질 정정.

cherry-pick (본질 12 commits 만 — fork devel 누적 170 commits 제외):
- Task #534 v1+v2: layout_shape_item TAC Picture inner_pad + LINE_SEG.column_start
  · src/renderer/layout.rs (+37 / -3 LOC)
- Task #537: lazy_base trailing-ls 보정 (TAC 표 직후 첫 답안 줄간격 716 HU drift,
  P2/3/5/6/8/9/12/13/14 11곳)
  · src/renderer/layout.rs (+14 / -2 LOC)
- Task #539: prev_has_overlay_shape 가드 완화 (treat_as_char 제외, 글박스 호스트
  직후 paragraph 줄간격 P7/P9 2곳)
  · src/renderer/layout.rs (+9 / -0 LOC)
- src/renderer/layout/integration_tests.rs (TDD 통합 테스트 3건)

검증:
- cargo test --lib 1113 passed
- cargo test --test issue_530/505/418/501 회귀 0
- cargo test --test svg_snapshot 6/6 통과
- cargo clippy --lib 0 건
- cargo build --release 정상
- WASM 4,461,870 bytes + rhwp-studio 동기화

시각 판정 (작업지시자 한컴 2010/2020 직접):
- 1차 SVG export-svg 통과
- 2차 rhwp-studio web Canvas 통과

부수 발견 (별도 이슈, PR #538 와 무관):
- 이슈 #545: aift.hwp p41 표 위치 (이전부터 발생)
- 이슈 #546: exam_science.hwp p2 페이지네이션 회귀 (PR #506 origin, bisect 확정)

컨트리뷰터 안내: 다음 PR 전 fork devel 동기화 부탁.
@edwardkim

Copy link
Copy Markdown
Owner

@planet6897 님의 PR을 본질 cherry-pick 으로 devel 에 반영했습니다 (devel 4d42a7e).

머지된 commits (본질 12 commits, fork devel 누적 170 commits 제외)

PR 본문에 명시한 3 task 의 정정만 추출하여 cherry-pick:

author 는 @planet6897 으로 보존되었습니다.

검증

  • `cargo test --lib` 1113 passed
  • `cargo test --test issue_530/505/418/501` 회귀 0
  • `cargo test --test svg_snapshot` 6/6 통과
  • `cargo clippy --lib` 0 건
  • WASM 빌드 4,461,870 bytes + rhwp-studio 동기화

시각 판정 (한컴 2010/2020)

  • 1차 SVG (export-svg) 통과
  • 2차 rhwp-studio web Canvas 통과

부수 발견 (별도 이슈, 본 PR 와 무관)

본 PR 의 시각 판정 중 발견된 결함 — 모두 본 PR 과 별개로 분리 등록:

컨트리뷰터께 부탁드리는 사항

본 PR 의 base/head 가 모두 `devel` 로 설정되어 있어 fork 의 devel 브랜치 누적분 (170 commits / 약 40 task) 이 PR 에 모두 포함되었습니다. PR 본문에 명시한 3 task (#534 v2 + #537 + #539) 외의 task 들은 별도 검토 절차 미진행 상태이므로, 본 PR 에서는 본질만 추출하여 cherry-pick 으로 반영했습니다 (CONFLICTING 상태도 함께 해소).

다음 PR 작업 전에 본 환경 `devel` 동기화 부탁드립니다:

```bash
git checkout devel
git fetch upstream # 또는 origin (본 repo)
git pull --ff-only upstream devel
git push origin devel
```

처리 보고서: `mydocs/pr/archives/pr_538_report.md`

이슈 #534/#537/#539 는 PR closes 자동 동작으로 이미 closed 상태이고, 정정이 적용되어 close 유지 정합합니다. milestone v1.0.0 은 사후 처리하겠습니다.

@edwardkim edwardkim closed this May 3, 2026
edwardkim added a commit that referenced this pull request May 3, 2026
- mydocs/pr/pr_538_{review,review_impl,report}.md → archives/
- mydocs/orders/20260503.md 갱신 (PR #538 머지 + 이슈 #545/#546 등록 추가)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@planet6897 planet6897 changed the title Task #534 v2 + #537 + #539 + #540 + #544: lazy_base / overlay-shape / 빈 paragraph 음수 ls floor / paragraph border 좌표 정합 (21_언어_기출 줄간격 정정) Task #534 v2 + #537 + #539 + #540 + #544 + #547: lazy_base / overlay-shape / 빈 paragraph 음수 ls floor / paragraph border 좌표·텍스트 inset 정합 (21_언어_기출 줄간격 정정) May 3, 2026
@planet6897

Copy link
Copy Markdown
Contributor Author

Task #547 추가 (Task #544 후속)

문제: Task #544 가 paragraph border 박스 outline 을 col_area 로 정정한 후, 박스 안 본문 텍스트의 좌측 inset 이 PDF 보다 넓게 보임 (작업지시자 시각 검증: [13~15] 박스 안 본문 inset 우측으로 치우침).

본질: paragraph_layout.rs:709-717inner_pad_left = box_margin_left 분기 (paragraph border + visible stroke + border_spacing=0 케이스에 box_margin_left 한 번 더 더하는 logic) 가 Task #544 이전 (박스도 margin 적용 시) 에 의미 있던 보정인데, Task #544 후 이중 inset 부작용으로 남음.

정정: inner_pad 분기 완전 제거 + 단일 룰 (margin_left = box_margin_left 한 번만) 적용.

// 정정
let margin_left = box_margin_left;
let margin_right = box_margin_right;

제거된 변수: para_border_fill_id_pre, has_visible_stroke, bs_left_px, bs_right_px, inner_pad_left, inner_pad_right (-25 / +8 LOC).

핵심 측정값 (페이지 4 [7~9] passage 박스):

항목 PDF 수정 전 수정 후
본문 텍스트 min x 128.5 139.89 (+11.4 px) 128.53 (+0.03 ✓)
박스 안 좌측 여백 ≈11.33 px 22.66 px 11.36 px

[13~15] 박스 직접 검증: 페이지 8 col 1 박스 안 본문 556 건 모두 -11.36 px 좌측 시프트, 좌측 여백 22.66→11.36 px 정상화.

검증:

closes #547 본 PR 머지 시 자동 closure (이미 issue 는 closure 처리됨).

상세 보고서: mydocs/report/task_m100_547_report.md

@planet6897 planet6897 changed the title Task #534 v2 + #537 + #539 + #540 + #544 + #547: lazy_base / overlay-shape / 빈 paragraph 음수 ls floor / paragraph border 좌표·텍스트 inset 정합 (21_언어_기출 줄간격 정정) Task #534 v2 + #537 + #539 + #540 + #544 + #547 + #548: lazy_base / overlay-shape / 빈 paragraph 음수 ls floor / paragraph border 좌표·텍스트 inset / 셀 inline shape margin (21_언어_기출 줄간격 정정) May 3, 2026
@planet6897

Copy link
Copy Markdown
Contributor Author

Task #548 추가 (Task #547 후속)

문제: 페이지 8 보기 표 (pi=167) 셀 5 (3-col 병합 본문 셀) 의 첫 줄 [푸코] inline rectangle Shape 가 cell 좌측 가장자리 (x=131.04 px) 에 렌더되어 PDF (한컴 2010, x≈155.6 px) 보다 24.6 px 좌측 시프트 (작업지시자 시각 검증).

본질: table_layout.rs 의 셀 안 inline TAC Shape 렌더링용 inline_x 초기화/리셋 (3 위치) 가 paragraph 의 margin_left + first_line_indent 미적용. paragraph_layout (텍스트 경로) 은 effective_margin_left 적용해 정확하지만, table_layout (shape 경로) 은 inner_area.x 만 사용 → 두 경로 위치 불일치 (텍스트 정확, shape 만 어긋남).

정정: effective_margin_left_line 헬퍼 함수 추가 (paragraph_layout.rs:851-858 와 동일 룰) + 3 분기 (초기 / Picture target_line reset / Shape target_line reset) 의 Left/Justify 케이스에 line_margin 적용 (-3 / +30 LOC).

fn effective_margin_left_line(margin_left: f64, indent: f64, line_n: usize) -> f64 {
    let line_indent = if indent > 0.0 {
        if line_n == 0 { indent } else { 0.0 }
    } else if indent < 0.0 {
        if line_n == 0 { 0.0 } else { indent.abs() }
    } else {
        0.0
    };
    margin_left + line_indent
}

// 3 위치 적용
let line_margin = effective_margin_left_line(para_margin_left_px, para_indent_px, line_n);
match para_alignment {
    Alignment::Center | Alignment::Distribute => { ... }  // 변경 없음
    Alignment::Right => { ... }                           // 변경 없음
    _ => inner_area.x + line_margin,                      // fix
}

핵심 측정값 (페이지 8 셀 5 line 0 [푸코] box):

항목 PDF 수정 전 수정 후
Box left x 155.6 131.04 (-24.6 px) 155.60 (+0.0 ✓)
텍스트 "는" (직후) ≈185.8 185.83 (paragraph_layout 정확) 185.83 (변경 없음)

shape 와 직후 텍스트 정확 일치 (185.83 = 155.60 + 30.23).

검증:

메모리 룰 적용:

  • [feedback_rule_not_heuristic]: paragraph_layout 의 line_indent 산식과 동일 헬퍼로 단일 룰 보장
  • [feedback_essential_fix_regression_risk]: 6 샘플 회귀 검증, 음의 시프트 0건
  • [feedback_pdf_not_authoritative]: 한컴 2010 PDF 일치 + 한컴 2020/한컴독스 검증 권고

closes #548 본 PR 머지 시 자동 closure (이미 issue 는 closure 처리됨).

상세 보고서: mydocs/report/task_m100_548_report.md

edwardkim added a commit that referenced this pull request May 3, 2026
회귀 origin: 82e41ba (Task #460 보완5: Square wrap 그림 아래 텍스트 y위치 보정 — layout + typeset)

옵션 C (페이지/단 경계 검사 추가) 시도 결과 0 효과 — 모든 보정값이
col_h 이내라 가드 trigger 안 됨. 결함의 본질이 wrap-around paragraph
들의 누적 height 와 결합 영역이라 작업지시자 결정으로 옵션 A (전체
revert) 진행.

변경:
- src/renderer/layout.rs (-58 LOC, wrap_pic_bottom_y + y_offset 보정)
- src/renderer/typeset.rs (-36 LOC, wrap_around_pic_bottom_px 처리)
- tests/issue_546.rs (신규 회귀 테스트 1건)

검증:
- cargo test --lib 1113 passed
- cargo test --test issue_546 1 passed
- cargo test --test issue_530/505/418/501 회귀 0
- cargo test --test svg_snapshot 6/6 통과
- cargo clippy --lib 0 건

광범위 fixture sweep (PR #538 머지 후 ↔ revert 후 byte 비교):
- exam_kor/exam_eng/exam_math/synam-001/복학원서/2010-01-06/21_언어_기출
  : 105 페이지 모두 byte-identical (회귀 0)
- exam_science: 4 페이지 모두 의도된 정정 (회귀 6→4 + p2 본문 복원)

페이지 수 v0.7.9 정합:
- exam_science.hwp 6 페이지 → 4 페이지 (회귀 정정)
- 다른 8 fixture v0.7.9 와 동일 페이지 수 보존

Task #460 보완5 의 본 의도 (HWP3 Square wrap 그림 아래 텍스트 y위치
정합) 는 본 revert 로 손실. Stage 5 시각 판정 시 HWP3 fixture 점검 후
재발 시 별도 task 분리.

closes #546

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
edwardkim added a commit that referenced this pull request May 3, 2026
작업지시자 시각 판정 통과 ("시각 회귀 통과입니다").

WASM 빌드: 4,442,504 bytes (PR #538 시점 4,461,870 -19,366, 94 LOC 제거 반영) +
rhwp-studio 동기화 완료.

산출물:
- mydocs/report/task_m100_546_report.md (최종 보고서)

회귀 origin 분석:
- 82e41ba (Task #460 보완5: Square wrap 그림 아래 텍스트 y위치 보정)
- 본 의도: HWP3 Square wrap 그림 아래 텍스트 y위치 정합
- exam_science 의 부작용: 2단 + 그림이 단 0 끝 + 풍부한 wrap-around 조합에서
  current_height = max(current_height, bottom_px) 가 wrap-around paragraph 의
  누적 height 와 결합한 double advance 발생 → 페이지/단 강제 분리

광범위 영향 작은 이유: specific 조합에서만 결함 trigger.
PR #506 의 다른 fixture 검증에서도 미검출 (exam_science 가 정밀 fixture).

Task #460 보완5 의 본 의도 손실 — HWP3 fixture 시각 결함 재발견 시 별도 task 로
페이지네이션 안전한 방식 (wrap-around paragraph 누적 height 추적) 재시도 권장.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
edwardkim added a commit that referenced this pull request May 3, 2026
…revert) — closes #546

회귀 origin: 82e41ba (Task #460 보완5: Square wrap 그림 아래 텍스트 y위치 보정 — layout + typeset)

bisect 결과 정확히 식별:
- v0.7.9 (main, 0fb3e67): 4 페이지 + p2 본문 37 items ✅
- 0aa7a5e (보완4 직전): 4 페이지 ✅
- ab2f4d0 (보완4 HWP3): 4 페이지 ✅
- 9d9b4ed (보완4 어울림 텍스트): 4 페이지 ✅
- 82e41ba (보완5): 6 페이지 + p2 본문 2 items ❌ ← 회귀 origin

옵션 C (페이지/단 경계 검사) 시도 결과 0 효과 — 모든 보정값이 col_h 이내라
가드 trigger 안 됨. 결함의 본질이 wrap-around paragraph 누적 height 와 결합한
double advance 영역이라 작업지시자 결정으로 옵션 A (전체 revert) 진행.

변경:
- src/renderer/layout.rs (-58 LOC, wrap_pic_bottom_y + y_offset 보정)
- src/renderer/typeset.rs (-36 LOC, wrap_around_pic_bottom_px 처리)
- tests/issue_546.rs (신규 회귀 테스트 1건)

검증:
- cargo test --lib 1113 passed
- cargo test --test issue_546/505/530/418/501/svg_snapshot 회귀 0
- cargo clippy --lib 0 건
- WASM 4,442,504 bytes + rhwp-studio 동기화

광범위 fixture sweep (PR #538 머지 후 ↔ revert 후 byte 비교):
- 8 fixture / 105 페이지 byte-identical (회귀 0)
- exam_science 4 페이지 의도된 정정 (회귀 6→4 + p2 본문 복원)

작업지시자 시각 판정 통과 (한컴 2010/2020 직접 비교).

Task #460 보완5 의 본 의도 (HWP3 Square wrap 그림 아래 텍스트 y위치 정합) 는
본 revert 로 손실. HWP3 fixture 재발견 시 별도 task 로 페이지네이션 안전한
방식 (wrap-around 누적 height 추적) 재시도 권장.
edwardkim added a commit that referenced this pull request May 3, 2026
…e wrap 호스트 텍스트 중복 emit 정정 — cherry-pick @planet6897 6 commits)

본 PR 은 외부 컨트리뷰터 @planet6897 (Jaeuk Ryu) 의 두 번째 PR (PR #538 후속).
fork devel 누적 (248 commits / 50+ task) 이라 작업지시자 결정으로 본질 Task #525 만
cherry-pick. 작업지시자도 인지하고 있던 결함의 root cause 정확 식별.

cherry-pick (6 commits):
- Task #525 수행 계획서 / Stage 1 진단 / 구현 계획서 / Stage 2 정정 / Stage 3 회귀 검증 / Stage 4 보고서
- 코드 변경: src/renderer/layout.rs (+14 / -69 LOC, net -55)

본질: layout_wrap_around_paras 가 비-TAC Picture wrap=Square host 의 호스트
paragraph 자기 텍스트를 PageItem::FullParagraph 정상 경로 외에 두 곳에서 추가
emit (layout.rs:3093 layout_shape_item + :3496 layout_column_shapes_pass) →
같은 줄을 다른 col_w 정렬로 distinct x 위치 emit → 시각 중첩.

정정: Picture Square wrap 의 wrap-around 호출 두 곳 모두 제거. Table Square
wrap 호출 (layout.rs:2555) 은 의도된 동작 유지. paragraph_layout.rs:822/973 의
has_picture_shape_square_wrap 분기가 LINE_SEG.cs/sw 기반으로 그림 옆 (좁은) +
그림 아래 (넓은) 모두 처리.

검증:
- cargo test --lib 1113 passed
- cargo test --test issue_546/530/505/418/501 회귀 0
- cargo test --test svg_snapshot 6/6 통과
- cargo clippy --lib 0 건
- WASM 4,441,878 bytes (-626 from Task #546 시점) + rhwp-studio 동기화

광범위 fixture sweep:
- 8 fixture / 113 페이지 byte-identical (회귀 0)
- exam_science 2 페이지 의도된 정정 (dup emit 해소)

시각 판정 (작업지시자 한컴 2010/2020 직접): 통과.

Task #546 양립 확인 — 두 정정이 다른 영역 (typeset.rs current_height vs
layout.rs layout_wrap_around_paras 중복 호출).

PR #551 의 다른 영역 (242 commits / ~50 task) 은 별도 처리 결정 — 컨트리뷰터에게
다음 PR 전 fork devel 동기화 + task 별 분리 PR 안내 예정.
planet6897 added a commit to planet6897/rhwp that referenced this pull request May 3, 2026
…g 좁음 (5+ 케이스)

본 task edwardkim#544 v2 시각 판정 중 작업지시자 발견된 회귀 5 케이스. byte-identical
baseline 검증으로 본 task edwardkim#544 v2 와 무관한 사전 회귀 확정.

영역: PR edwardkim#538 의 Task edwardkim#539 그룹 A (지문 시작 [X~Y] 직후 9곳 보류) 일치.

작업지시자 보고 케이스:
- p2 [4~6]: pi=46 "15세기 초..." → pi=47 "고진에 따르면..."
- p5 [10~12]: pi=112 "살펴보건대..." → pi=113 "형법은 선왕..."
- p10 [19~21]: → "성리학의 논의..."
- p11 [22~24]: pi=234 "빈곤 퇴치..." → pi=235 "제도의 역할..."
- p13 [25~27]: → "세포는 영양분..."

사전 측정 (정상 24.21 px = 1 line spacing 대비):
- p2 [4~6]: gap 18.35 (drift -5.86)
- p5 [10~12]: gap 19.10 (drift -5.11)
- p11 [22~24]: gap 14.66 (drift -9.55, line_height 1100 HU 정확)

본질 가설: 박스 안 sequential paragraph (prev/next 같은 박스 outline) 끝에서
Task edwardkim#479 trailing-ls 제외가 작동 → next_starts_border=false → trailing ls 누락.
Task edwardkim#552 가 border 시작 직전만 처리, 박스 안 sequential 은 미처리 영역.

수정 방향: paragraph_layout.rs 의 next_starts_border 가드 확장 또는 layout.rs
에 next_para_continues_visible_border Cell 추가.

4 단계 구성:
- Stage 1: 정밀 진단 (p10/13 측정 + p2/5 부분 제외 본질) + TDD RED + 방법 결정
- Stage 2: Phase A 적용
- Stage 3: Phase B (필요 시) + 광범위 회귀
- Stage 4: 최종 보고서 + orders 갱신

브랜치: local/task544_v3 (local/task544_v2 위에 분기)
작업지시자 결정으로 이슈 등록 없이 v3 suffix 사용.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants