Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
a5bb5fa
fix: restore HWPX package graph for Hancom
humdrum00001010 Jun 28, 2026
e6cd296
WASM canvas: synchronous embedded-image paint + fix substituted-font …
humdrum00001010 Jun 7, 2026
70807f8
fix: avoid double-transforming matrix-positioned group children
humdrum00001010 Jun 25, 2026
fe52bdd
fix: render matrix-scaled HWPX cover text
humdrum00001010 Jun 25, 2026
20038f3
fix: suppress HWPX chapter-divider matrix-group construction strokes
humdrum00001010 Jun 26, 2026
c8bd535
fix: stop orphan near-blank pages from drift-spill before forced page…
humdrum00001010 Jun 26, 2026
9971ddd
fix: reserve footnote area and sort master-page furniture
humdrum00001010 Jun 27, 2026
fb39cdc
fix: ground HWPX render fidelity
humdrum00001010 Jun 28, 2026
d86231b
fix: anchor master page furniture to paper origin
humdrum00001010 Jun 28, 2026
24bb65f
fix: keep trailing empty paragraph with footnotes
humdrum00001010 Jun 28, 2026
bc9bd3e
test: align SVG and visual baselines
humdrum00001010 Jun 28, 2026
bc37020
chore: scrub host-specific viewer name
humdrum00001010 Jun 28, 2026
0dc2204
Merge branch 'devel' into gather/hwpx-render-fidelity
humdrum00001010 Jun 28, 2026
122f87d
fix: keep master page backgrounds behind body text
humdrum00001010 Jun 28, 2026
9386605
fix: refine HWPX render fidelity
humdrum00001010 Jun 28, 2026
36484ca
fix: constrain saved vpos pagination fit
humdrum00001010 Jun 29, 2026
d8455e3
Merge branch 'devel' into gather/hwpx-render-fidelity
humdrum00001010 Jun 29, 2026
5c1575f
test: refresh exam kor SVG snapshot
humdrum00001010 Jun 29, 2026
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
58 changes: 58 additions & 0 deletions Cargo.lock

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

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ snafu = "0.9.0"
strum = { version = "0.28.0", default-features = false, features = ["derive"] }
unicode-segmentation = "1.12.0"
unicode-width = "0.2.2"
image = { version = "0.25", default-features = false, features = ["bmp", "jpeg", "png"] }
image = { version = "0.25", default-features = false, features = ["bmp", "jpeg", "png", "tiff"] }
pcx = "0.2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
Expand Down Expand Up @@ -62,6 +62,7 @@ web-sys = { version = "0.3", features = [
"CanvasPattern",
"HtmlCanvasElement",
"HtmlImageElement",
"ImageData",
"TextMetrics",
"Document",
"Window",
Expand Down
138 changes: 138 additions & 0 deletions examples/diag_blank_pages.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
//! 진단: HWPX/HWP 본문 페이지에 본문 1줄만 배치되고 거대한 빈 공간이 생기는
//! "near-blank page" 결함을 네이티브로 재현한다.
//!
//! WASM 바인딩 `pageCount`/`getPageTextLayout`/`getPageFootnoteInfo` 가 호출하는
//! 동일 내부 경로(`DocumentCore`)를 직접 호출한다.
//!
//! 사용: `cargo run --release --example diag_blank_pages -- <file.hwpx> [from] [to]`
//! 기본 출력 범위: 0-indexed page 8..=20.

use rhwp::wasm_api::HwpDocument;
use std::env;
use std::fs;

fn main() {
let args: Vec<String> = env::args().skip(1).collect();
if args.is_empty() {
eprintln!(
"사용: cargo run --release --example diag_blank_pages -- <file.hwpx> [from] [to]"
);
std::process::exit(1);
}
let file = &args[0];

// --coltype <sec> <from_para> <to_para>: 문단별 column_type/page_break_before/텍스트 덤프
// (강제 쪽나누기 속성 검증용 — orphan break 의 원인 분류).
if args.get(1).map(|s| s == "--coltype").unwrap_or(false) {
let sec: usize = args.get(2).and_then(|s| s.parse().ok()).unwrap_or(1);
let from_p: usize = args.get(3).and_then(|s| s.parse().ok()).unwrap_or(0);
let to_p: usize = args.get(4).and_then(|s| s.parse().ok()).unwrap_or(60);
let data = fs::read(file).expect("read file");
let doc = HwpDocument::from_bytes(&data).expect("parse document");
let dpi = 96.0_f64;
let styles = rhwp::renderer::style_resolver::resolve_styles(&doc.document().doc_info, dpi);
let section = &doc.document().sections[sec];
println!(
"sec {} paragraphs (col_type / page_break_before / text)",
sec
);
for (pi, p) in section.paragraphs.iter().enumerate() {
if pi < from_p || pi > to_p {
continue;
}
let pbb = styles
.para_styles
.get(p.para_shape_id as usize)
.map(|s| s.page_break_before)
.unwrap_or(false);
let text: String = p.text.chars().take(28).collect();
println!(
"pi={:>4} col_type={:?} pbb={} lines={} ctrls={} text={:?}",
pi,
p.column_type,
pbb,
p.line_segs.len(),
p.controls.len(),
text.trim()
);
}
return;
}

let from: u32 = args.get(1).and_then(|s| s.parse().ok()).unwrap_or(8);
let to: u32 = args.get(2).and_then(|s| s.parse().ok()).unwrap_or(20);

let data = fs::read(file).expect("read file");
let doc = HwpDocument::from_bytes(&data).expect("parse document");

let total = doc.page_count();
println!("file: {}", file);
println!("TOTAL PAGES: {}", total);
println!(
"{:>4} | {:>3} | {:>9} | {:>9} | {:>9} | {:>7} | {:>7} | {:>7} | {:>7}",
"page",
"sec",
"bodyLines",
"bodyMinY",
"bodyMaxY",
"fnLines",
"fnCount",
"paraMin",
"paraMax"
);

let end = to.min(total.saturating_sub(1));
for page in from..=end {
match doc.diag_page_layout_native(page) {
Ok(json) => {
let sec = extract_int(&json, "sectionIdx");
let body_lines = extract_int(&json, "bodyLines");
let body_min = extract_f64(&json, "bodyMinY");
let body_max = extract_f64(&json, "bodyMaxY");
let fn_lines = extract_int(&json, "footnoteLines");
let fn_count = extract_int(&json, "footnoteCount");
let para_min = extract_int(&json, "paraMin");
let para_max = extract_int(&json, "paraMax");
println!(
"{:>4} | {:>3} | {:>9} | {:>9.1} | {:>9.1} | {:>7} | {:>7} | {:>7} | {:>7}",
page,
sec,
body_lines,
body_min,
body_max,
fn_lines,
fn_count,
para_min,
para_max
);
}
Err(e) => {
println!("{:>4} | ERROR: {:?}", page, e);
}
}
}
}

/// 미니 JSON 정수 추출: `"key":<int>` 패턴.
fn extract_int(json: &str, key: &str) -> i64 {
extract_raw(json, key)
.and_then(|s| s.trim().parse::<i64>().ok())
.unwrap_or(-1)
}

/// 미니 JSON 실수 추출.
fn extract_f64(json: &str, key: &str) -> f64 {
extract_raw(json, key)
.and_then(|s| s.trim().parse::<f64>().ok())
.unwrap_or(f64::NAN)
}

fn extract_raw(json: &str, key: &str) -> Option<String> {
let needle = format!("\"{}\":", key);
let start = json.find(&needle)? + needle.len();
let rest = &json[start..];
let end = rest
.find(|c: char| c == ',' || c == '}')
.unwrap_or(rest.len());
Some(rest[..end].to_string())
}
9 changes: 9 additions & 0 deletions rhwp-studio/src/core/font-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,15 @@ const FONT_LIST: FontEntry[] = [
// Haansoft Dotum: HWP 문서가 직접 지정하는 한컴 돋움 영문명(예: 수능 모의고사 본문).
// 기존 미등록 → 체인의 'Malgun Gothic'(Pretendard) 가 먼저 매칭되어 굵게 렌더됐다.
{ name: 'Haansoft Dotum', file: 'fonts/NotoSansKR-ExtraLight.woff2' },
{ name: 'KoPub돋움체 Light', file: 'fonts/NotoSansKR-ExtraLight.woff2' },
{ name: 'KoPub Dotum Light', file: 'fonts/NotoSansKR-ExtraLight.woff2' },
{ name: 'KoPubWorld돋움체 Light', file: 'fonts/NotoSansKR-ExtraLight.woff2' },
{ name: 'KoPub바탕체 Light', file: 'fonts/NotoSerifKR-Regular.woff2' },
{ name: 'KoPub바탕체 Medium', file: 'fonts/NotoSerifKR-Regular.woff2' },
{ name: 'KoPub바탕체 Bold', file: 'fonts/NotoSerifKR-Bold.woff2' },
{ name: 'KoPub Batang Light', file: 'fonts/NotoSerifKR-Regular.woff2' },
{ name: 'KoPub Batang Medium', file: 'fonts/NotoSerifKR-Regular.woff2' },
{ name: 'KoPub Batang Bold', file: 'fonts/NotoSerifKR-Bold.woff2' },
{ name: '바탕', file: 'fonts/NotoSerifKR-Regular.woff2' },
{ name: '바탕체', file: 'fonts/D2Coding-Regular.woff2' },
{ name: '궁서', file: 'fonts/GowunBatang-Regular.woff2' },
Expand Down
Loading
Loading