Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ Cargo.lock
/pkg/
*.wasm

# Node
node_modules/

# CodeGraph local index
.codegraph/

# Local HTTPS certificates
/web/certs/*
!/web/certs/README.md

# Output (렌더링 결과물)
/output/
/dist/swift/
Expand Down
3 changes: 2 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
| 포맷 | 파서 위치 | 출력 IR |
|------|----------|---------|
| HWPX (ZIP+XML) | `src/parser/hwpx/` | `Document` |
| HWP5 (OLE 복합) | `src/parser/hwp5/` | `Document` |
| HWP5 (OLE 복합) | `src/parser/` 루트의 HWP5 모듈들 | `Document` |
| HWP3 (고전 바이너리) | `src/parser/hwp3/` | `Document` |

> 역사적으로 `Document` 모델은 HWP5 형식의 구조를 기반으로 설계되었으며, HWPX는 같은 의미의 XML 포맷이다. HWP3는 고전 포맷이지만 동일한 `Document` IR로 변환한다.
> HWP5 파서는 별도 `src/parser/hwp5/` 디렉터리가 아니라 `body_text.rs`, `doc_info.rs`, `control.rs` 등 `src/parser/` 루트 모듈로 구성되어 있다.

**HWP3 파서 규칙**: `src/parser/hwp3/` 내부에서 HWP3 바이너리를 읽어 `Document` IR로 변환하여 반환한다. HWP3 전용 로직은 **반드시 `src/parser/hwp3/` 안에서만** 구현한다. 렌더러(`src/renderer/`), 레이아웃(`src/renderer/layout.rs`), 문서 코어(`src/document_core/`) 등 공통 모듈에 HWP3 전용 분기를 추가하지 않는다.

Expand Down
1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ quick-xml = "0.40"
codepage = "0.1.2"
embedded-io = "0.7.1"
encoding_rs = "0.8.35"
paste = "1.0.15"
snafu = "0.9.0"
strum = { version = "0.28.0", default-features = false, features = ["derive"] }
unicode-segmentation = "1.12.0"
Expand Down
4 changes: 4 additions & 0 deletions bindings/Native/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ pub extern "C" fn rhwp_string_free(ptr: *mut c_char) {
}

unsafe {
// SAFETY: The native API returns strings allocated with CString::into_raw.
// Callers must pass each returned pointer back exactly once for release.
drop(CString::from_raw(ptr));
}
}
Expand Down Expand Up @@ -264,6 +266,8 @@ fn read_utf8(ptr: *const c_char, name: &str) -> Result<String, String> {
}

unsafe {
// SAFETY: Null pointers are rejected above. The FFI contract requires
// input strings to be valid, NUL-terminated C strings for this call.
CStr::from_ptr(ptr)
.to_str()
.map(|s| s.to_string())
Expand Down
2 changes: 1 addition & 1 deletion examples/dump_eqedit_raw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ fn main() {
println!();
}
}
if size % 16 != 0 {
if !size.is_multiple_of(16) {
println!();
}
// 첫 2개 EQEDIT만
Expand Down
2 changes: 1 addition & 1 deletion examples/inspect_574.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ fn main() {

// 본문 default CharShape 비교 (id=9 — section setup paragraph 0.0 의 본문 CS)
println!("\n--- [쪽번호 후보] CharShape id=0 (TextBox 사각형 내 \"1\") ---");
if let Some(cs) = doc.doc_info.char_shapes.get(0) {
if let Some(cs) = doc.doc_info.char_shapes.first() {
print_char_shape(0, cs);
}
println!("\n--- [본문 비교] CharShape id=9 (본문 default ratio=95%) ---");
Expand Down
4 changes: 2 additions & 2 deletions mydocs/eng/working/archives/task_83_done.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ Implemented HWP's F5 cell block selection mode. Pressing F5 inside a table cell
| `src/wasm_api.rs` | Modified | Added `getTableCellBboxes` API — returns all cell bboxes of a table |
| `rhwp-studio/src/core/types.ts` | Modified | Added `CellBbox` interface |
| `rhwp-studio/src/core/wasm-bridge.ts` | Modified | Added `getTableCellBboxes()` bridge method |
| `rhwp-studio/src/engine/cursor.ts` | Modified | Cell selection mode state management (enter/exit/move/shift/ctrl/getRange) |
| `rhwp-studio/src/engine/input-handler.ts` | Modified | F5 key handler, arrow/ESC/Shift+click/Ctrl+click handling |
| `rhwp-studio/src/engine/cursor.ts` | Modified | Cell selection mode state management for enter, exit, move, modifier keys, and range lookup |
| `rhwp-studio/src/engine/input-handler.ts` | Modified | F5 key handler plus arrow, escape, shift-click, and modifier-click handling |
| `rhwp-studio/src/engine/cell-selection-renderer.ts` | New | Cell range highlight overlay renderer (excluded cell support) |
| `rhwp-studio/src/command/types.ts` | Modified | Added `inCellSelectionMode` to EditorContext |
| `rhwp-studio/src/main.ts` | Modified | CellSelectionRenderer creation/injection, getContext extension |
Expand Down
16 changes: 9 additions & 7 deletions rhwp-chrome/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

39 changes: 0 additions & 39 deletions rhwp-firefox/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions rhwp-studio/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions rhwp-vscode/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/document_core/commands/object_ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5293,6 +5293,7 @@ impl DocumentCore {
}

#[cfg(test)]
#[allow(clippy::items_after_test_module)]
mod resize_clamp_tests {
use super::*;
use crate::model::document::{Document, Section, SectionDef};
Expand Down
1 change: 1 addition & 0 deletions src/document_core/commands/text_editing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2178,6 +2178,7 @@ impl DocumentCore {
}

#[cfg(test)]
#[allow(clippy::items_after_test_module)]
mod tests {
use super::*;

Expand Down
28 changes: 14 additions & 14 deletions src/document_core/converters/common_obj_attr_writer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,28 +35,28 @@ pub fn serialize_common_obj_attr(common: &CommonObjAttr) -> Vec<u8> {
} else {
pack_common_attr_bits(common)
};
w.write_u32(attr).unwrap();
w.write_u32(attr);

w.write_u32(common.vertical_offset).unwrap();
w.write_u32(common.horizontal_offset).unwrap();
w.write_u32(common.width).unwrap();
w.write_u32(common.height).unwrap();
w.write_i32(common.z_order).unwrap();
w.write_u32(common.vertical_offset);
w.write_u32(common.horizontal_offset);
w.write_u32(common.width);
w.write_u32(common.height);
w.write_i32(common.z_order);

w.write_i16(common.margin.left).unwrap();
w.write_i16(common.margin.right).unwrap();
w.write_i16(common.margin.top).unwrap();
w.write_i16(common.margin.bottom).unwrap();
w.write_i16(common.margin.left);
w.write_i16(common.margin.right);
w.write_i16(common.margin.top);
w.write_i16(common.margin.bottom);

w.write_u32(common.instance_id).unwrap();
w.write_i32(common.prevent_page_break).unwrap();
w.write_u32(common.instance_id);
w.write_i32(common.prevent_page_break);

// description (HWP string)
w.write_hwp_string(&common.description).unwrap();
w.write_hwp_string(&common.description);

// 라운드트립 보존: raw_extra 가 있으면 이어붙임
if !common.raw_extra.is_empty() {
w.write_bytes(&common.raw_extra).unwrap();
w.write_bytes(&common.raw_extra);
}

w.into_bytes()
Expand Down
9 changes: 7 additions & 2 deletions src/document_core/converters/hwpx_to_hwp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
//! `control.rs` 등은 변경하지 않는다.
//! - **IR 만 만진다**: 진입점은 `&mut Document` 이며, 출력은 IR 필드 갱신뿐.
//! - **idempotent**: 같은 IR 에 두 번 호출해도 같은 결과.
//! - **HWP 출처 보호**: `source_format == Hwpx` 일 때만 동작. HWP 출처는 no-op.
//! - **HWP 출처 보호**: `source_format == Hwpx` 또는 `Hwp3` 일 때만 동작.
//! HWP5 출처는 no-op. HWP3 허용은 HWP 저장 어댑터가 필요한 legacy IR 정규화 경로이며,
//! 공통 converter 에 새 HWP3 파서 로직을 섞는 예외가 아니다.
//!
//! ## 매핑 명세서
//!
Expand Down Expand Up @@ -1260,6 +1262,9 @@ fn adapt_cell_list_attr(cell: &mut Cell, report: &mut AdapterReport) {
///
/// 호출자: `DocumentCore::export_hwp_with_adapter()` (Stage 5 에서 추가).
pub fn convert_if_hwpx_source(doc: &mut Document, source_format: FileFormat) -> AdapterReport {
// Guardrail: this converter only materializes HWP-save contracts for HWPX
// and native HWP3 IR. Do not add format-specific parsing here; keep that in
// the parser modules before DocumentCore reaches the export adapter.
if !matches!(source_format, FileFormat::Hwpx | FileFormat::Hwp3) {
return AdapterReport::new().no_op("source_format != Hwpx/Hwp3");
}
Expand Down Expand Up @@ -2281,7 +2286,7 @@ mod tests {
0.0,
0.0,
0.0,
f64::from(924.090_027_f32),
f64::from(924.09_f32),
0.0,
]));
raw_rendering.extend_from_slice(&matrix([1.0, 0.0, 0.0, 0.0, 1.0, 0.0]));
Expand Down
8 changes: 4 additions & 4 deletions src/document_core/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1410,7 +1410,7 @@ mod tests {
let para = Paragraph {
text: "abc".to_string(),
char_offsets: vec![0, 1, 2],
controls: vec![Control::Footnote(Box::new(Footnote::default()))],
controls: vec![Control::Footnote(Box::default())],
..Default::default()
};

Expand All @@ -1424,10 +1424,10 @@ mod tests {
text: " ".to_string(),
char_offsets: vec![24, 25],
controls: vec![
Control::SectionDef(Box::new(SectionDef::default())),
Control::SectionDef(Box::default()),
Control::ColumnDef(ColumnDef::default()),
Control::Footnote(Box::new(Footnote::default())),
Control::Footnote(Box::new(Footnote::default())),
Control::Footnote(Box::default()),
Control::Footnote(Box::default()),
],
..Default::default()
};
Expand Down
Loading