From e89f6734db2725acd5e579388524dcda7bf925a4 Mon Sep 17 00:00:00 2001 From: puleugo Date: Tue, 10 Mar 2026 22:36:07 +0900 Subject: [PATCH 1/4] feat(hwpx): add fixture tooling and scrub local metadata --- README.md | 31 +- packages/core-rs/README.md | 17 +- packages/core-rs/src/bin/compare_hwpx_xml.rs | 232 ++++++ packages/core-rs/src/bin/decode_hwpx.rs | 213 +++++ .../core-rs/src/bin/generate_hwpx_outputs.rs | 130 +++ .../src/bin/refresh_hwpx_fixture_metadata.rs | 84 ++ .../src/bin/support/archive_fixture.rs | 416 ++++++++++ .../src/generators/hwpx/content_hpf.rs | 2 +- .../src/generators/hwpx/docs/README.md | 72 +- .../core-rs/src/generators/hwpx/header_xml.rs | 434 +++++++++- packages/core-rs/src/generators/hwpx/mod.rs | 55 +- .../src/generators/hwpx/package_xml.rs | 35 +- .../paragraph-only/Contents/content.hpf | 2 +- .../hwpx/reference/paragraph-only/version.xml | 2 +- .../src/generators/hwpx/section_xml.rs | 738 ++++++++++++++++-- packages/core-rs/src/generators/hwpx/style.rs | 5 +- packages/core-rs/src/lib.rs | 40 + packages/core-rs/src/models/inline.rs | 5 +- packages/core-rs/src/parser/markdown.rs | 551 ++++++++++++- .../core-rs/src/parser/markdown/data_image.rs | 10 +- packages/core-rs/src/parser/markdown/state.rs | 20 +- packages/core-rs/src/parser/markdown/tests.rs | 20 +- packages/core-rs/src/utils/image_helper.rs | 24 +- packages/core-rs/src/utils/xml_helper.rs | 8 +- packages/core-rs/src/utils/zip_archiver.rs | 22 +- .../tests/fixtures/hwpx/approved/README.md | 5 + .../expected/Contents/content.hpf | 1 + .../expected/Contents/header.xml | 1 + .../expected/Contents/section0.xml | 1 + .../expected/META-INF/container.rdf | 1 + .../expected/META-INF/container.xml | 1 + .../expected/META-INF/manifest.xml | 1 + .../expected/Preview/PrvImage.png.sha256 | 1 + .../expected/Preview/PrvText.txt | 2 + .../blockquote-basic/expected/mimetype | 1 + .../blockquote-basic/expected/settings.xml | 1 + .../blockquote-basic/expected/version.xml | 1 + .../approved/blockquote-basic/fixture.toml | 4 + .../approved/blockquote-basic/golden.hwpx | Bin 0 -> 27497 bytes .../hwpx/approved/blockquote-basic/hash.txt | 1 + .../hwpx/approved/blockquote-basic/input.md | 3 + .../expected/Contents/content.hpf | 1 + .../expected/Contents/header.xml | 1 + .../expected/Contents/section0.xml | 1 + .../expected/META-INF/container.rdf | 1 + .../expected/META-INF/container.xml | 1 + .../expected/META-INF/manifest.xml | 1 + .../expected/Preview/PrvImage.png.sha256 | 1 + .../expected/Preview/PrvText.txt | 3 + .../code-block-basic/expected/mimetype | 1 + .../code-block-basic/expected/settings.xml | 1 + .../code-block-basic/expected/version.xml | 1 + .../approved/code-block-basic/fixture.toml | 4 + .../approved/code-block-basic/golden.hwpx | Bin 0 -> 27517 bytes .../hwpx/approved/code-block-basic/hash.txt | 1 + .../hwpx/approved/code-block-basic/input.md | 5 + .../expected/Contents/content.hpf | 2 +- .../expected/Preview/PrvImage.png.sha256 | 2 +- .../core-heading/expected/version.xml | 2 +- .../hwpx/approved/core-heading/golden.hwpx | Bin 73141 -> 27711 bytes .../hwpx/approved/core-heading/hash.txt | 2 +- .../expected/Contents/content.hpf | 2 +- .../expected/Preview/PrvImage.png.sha256 | 2 +- .../core-inline-style/expected/version.xml | 2 +- .../approved/core-inline-style/golden.hwpx | Bin 73155 -> 27812 bytes .../hwpx/approved/core-inline-style/hash.txt | 2 +- .../expected/Contents/content.hpf | 2 +- .../expected/Preview/PrvImage.png.sha256 | 2 +- .../core-link-text/expected/version.xml | 2 +- .../hwpx/approved/core-link-text/golden.hwpx | Bin 72846 -> 27745 bytes .../hwpx/approved/core-link-text/hash.txt | 2 +- .../core-mixed/expected/Contents/content.hpf | 2 +- .../expected/Preview/PrvImage.png.sha256 | 2 +- .../approved/core-mixed/expected/version.xml | 2 +- .../hwpx/approved/core-mixed/golden.hwpx | Bin 73759 -> 28028 bytes .../hwpx/approved/core-mixed/hash.txt | 2 +- .../expected/Contents/content.hpf | 2 +- .../expected/Preview/PrvImage.png.sha256 | 2 +- .../core-paragraph/expected/version.xml | 2 +- .../hwpx/approved/core-paragraph/golden.hwpx | Bin 28390 -> 27585 bytes .../hwpx/approved/core-paragraph/hash.txt | 2 +- .../list-basic/expected/Contents/content.hpf | 1 + .../list-basic/expected/Contents/header.xml | 1 + .../list-basic/expected/Contents/section0.xml | 1 + .../expected/META-INF/container.rdf | 1 + .../expected/META-INF/container.xml | 1 + .../list-basic/expected/META-INF/manifest.xml | 1 + .../list-basic/expected/Preview/PrvImage.png | Bin 0 -> 20421 bytes .../list-basic/expected/Preview/PrvText.txt | 3 + .../approved/list-basic/expected/mimetype | 1 + .../approved/list-basic/expected/settings.xml | 1 + .../approved/list-basic/expected/version.xml | 1 + .../hwpx/approved/list-basic/fixture.toml | 4 + .../hwpx/approved/list-basic/golden.hwpx | Bin 0 -> 27557 bytes .../hwpx/approved/list-basic/hash.txt | 1 + .../hwpx/approved/list-basic/input.md | 3 + .../expected/Contents/content.hpf | 1 + .../expected/Contents/header.xml | 1 + .../expected/Contents/section0.xml | 1 + .../expected/META-INF/container.rdf | 1 + .../expected/META-INF/container.xml | 1 + .../expected/META-INF/manifest.xml | 1 + .../expected/Preview/PrvImage.png | Bin 0 -> 20421 bytes .../expected/Preview/PrvText.txt | 4 + .../list-nested-depth-2/expected/mimetype | 1 + .../list-nested-depth-2/expected/settings.xml | 1 + .../list-nested-depth-2/expected/version.xml | 1 + .../approved/list-nested-depth-2/fixture.toml | 4 + .../approved/list-nested-depth-2/golden.hwpx | Bin 0 -> 27630 bytes .../approved/list-nested-depth-2/hash.txt | 1 + .../approved/list-nested-depth-2/input.md | 4 + .../expected/Contents/content.hpf | 1 + .../expected/Contents/header.xml | 1 + .../expected/Contents/section0.xml | 1 + .../expected/META-INF/container.rdf | 1 + .../expected/META-INF/container.xml | 1 + .../expected/META-INF/manifest.xml | 1 + .../expected/Preview/PrvImage.png | Bin 0 -> 20421 bytes .../expected/Preview/PrvText.txt | 3 + .../ordered-list-basic/expected/mimetype | 1 + .../ordered-list-basic/expected/settings.xml | 1 + .../ordered-list-basic/expected/version.xml | 1 + .../approved/ordered-list-basic/fixture.toml | 4 + .../approved/ordered-list-basic/golden.hwpx | Bin 0 -> 27557 bytes .../hwpx/approved/ordered-list-basic/hash.txt | 1 + .../hwpx/approved/ordered-list-basic/input.md | 3 + .../expected/Contents/content.hpf | 1 + .../expected/Contents/header.xml | 1 + .../expected/Contents/section0.xml | 1 + .../expected/META-INF/container.rdf | 1 + .../expected/META-INF/container.xml | 1 + .../expected/META-INF/manifest.xml | 1 + .../expected/Preview/PrvImage.png | Bin 0 -> 20421 bytes .../expected/Preview/PrvText.txt | 4 + .../expected/mimetype | 1 + .../expected/settings.xml | 1 + .../expected/version.xml | 1 + .../ordered-list-nested-depth-2/fixture.toml | 4 + .../ordered-list-nested-depth-2/golden.hwpx | Bin 0 -> 27665 bytes .../ordered-list-nested-depth-2/hash.txt | 1 + .../ordered-list-nested-depth-2/input.md | 4 + .../expected/Contents/content.hpf | 2 +- .../expected/Preview/PrvImage.png.sha256 | 2 +- .../style-brand-color/expected/version.xml | 2 +- .../approved/style-brand-color/golden.hwpx | Bin 73675 -> 27922 bytes .../hwpx/approved/style-brand-color/hash.txt | 2 +- .../expected/Contents/content.hpf | 2 +- .../expected/Preview/PrvImage.png.sha256 | 2 +- .../expected/version.xml | 2 +- .../style-centered-layout/golden.hwpx | Bin 73766 -> 27839 bytes .../approved/style-centered-layout/hash.txt | 2 +- .../expected/Contents/content.hpf | 2 +- .../expected/Preview/PrvImage.png.sha256 | 2 +- .../style-typography/expected/version.xml | 2 +- .../approved/style-typography/golden.hwpx | Bin 73743 -> 27838 bytes .../hwpx/approved/style-typography/hash.txt | 2 +- .../expected/Contents/content.hpf | 1 + .../expected/Contents/header.xml | 1 + .../expected/Contents/section0.xml | 1 + .../expected/META-INF/container.rdf | 1 + .../expected/META-INF/container.xml | 1 + .../expected/META-INF/manifest.xml | 1 + .../expected/Preview/PrvImage.png | Bin 0 -> 20421 bytes .../expected/Preview/PrvText.txt | 3 + .../table-alignment/expected/mimetype | 1 + .../table-alignment/expected/settings.xml | 1 + .../table-alignment/expected/version.xml | 1 + .../approved/table-alignment/fixture.toml | 4 + .../hwpx/approved/table-alignment/golden.hwpx | Bin 0 -> 28060 bytes .../hwpx/approved/table-alignment/hash.txt | 1 + .../hwpx/approved/table-alignment/input.md | 4 + .../table-basic/expected/Contents/content.hpf | 1 + .../table-basic/expected/Contents/header.xml | 1 + .../expected/Contents/section0.xml | 1 + .../expected/META-INF/container.rdf | 1 + .../expected/META-INF/container.xml | 1 + .../expected/META-INF/manifest.xml | 1 + .../table-basic/expected/Preview/PrvImage.png | Bin 0 -> 20421 bytes .../table-basic/expected/Preview/PrvText.txt | 3 + .../approved/table-basic/expected/mimetype | 1 + .../table-basic/expected/settings.xml | 1 + .../approved/table-basic/expected/version.xml | 1 + .../hwpx/approved/table-basic/fixture.toml | 4 + .../hwpx/approved/table-basic/golden.hwpx | Bin 0 -> 28060 bytes .../hwpx/approved/table-basic/hash.txt | 1 + .../hwpx/approved/table-basic/input.md | 4 + .../tests/fixtures/hwpx/provisional/README.md | 5 +- .../expected/Contents/content.hpf | 1 + .../expected/Contents/header.xml | 1 + .../expected/Contents/section0.xml | 1 + .../expected/META-INF/container.rdf | 1 + .../expected/META-INF/container.xml | 1 + .../expected/META-INF/manifest.xml | 1 + .../expected/Preview/PrvImage.png.sha256 | 1 + .../expected/Preview/PrvText.txt | 1 + .../image-data-uri-basic/expected/mimetype | 1 + .../expected/settings.xml | 1 + .../image-data-uri-basic/expected/version.xml | 1 + .../image-data-uri-basic/fixture.toml | 4 + .../image-data-uri-basic/golden.hwpx | Bin 0 -> 27461 bytes .../provisional/image-data-uri-basic/hash.txt | 1 + .../provisional/image-data-uri-basic/input.md | 1 + .../mixed-rich/expected/Contents/content.hpf | 1 + .../mixed-rich/expected/Contents/header.xml | 1 + .../mixed-rich/expected/Contents/section0.xml | 1 + .../expected/META-INF/container.rdf | 1 + .../expected/META-INF/container.xml | 1 + .../mixed-rich/expected/META-INF/manifest.xml | 1 + .../expected/Preview/PrvImage.png.sha256 | 1 + .../mixed-rich/expected/Preview/PrvText.txt | 9 + .../provisional/mixed-rich/expected/mimetype | 1 + .../mixed-rich/expected/settings.xml | 1 + .../mixed-rich/expected/version.xml | 1 + .../hwpx/provisional/mixed-rich/fixture.toml | 4 + .../hwpx/provisional/mixed-rich/golden.hwpx | Bin 0 -> 27897 bytes .../hwpx/provisional/mixed-rich/hash.txt | 1 + .../hwpx/provisional/mixed-rich/input.md | 18 + .../expected/Contents/content.hpf | 1 + .../expected/Contents/header.xml | 1 + .../expected/Contents/section0.xml | 1 + .../expected/META-INF/container.rdf | 1 + .../expected/META-INF/container.xml | 1 + .../expected/META-INF/manifest.xml | 1 + .../expected/Preview/PrvImage.png.sha256 | 1 + .../expected/Preview/PrvText.txt | 2 + .../table-alignment/expected/mimetype | 1 + .../table-alignment/expected/settings.xml | 1 + .../table-alignment/expected/version.xml | 1 + .../provisional/table-alignment/fixture.toml | 4 + .../provisional/table-alignment/golden.hwpx | Bin 0 -> 27518 bytes .../hwpx/provisional/table-alignment/hash.txt | 1 + .../hwpx/provisional/table-alignment/input.md | 3 + .../expected/Contents/content.hpf | 1 + .../expected/Contents/header.xml | 1 + .../expected/Contents/section0.xml | 1 + .../expected/META-INF/container.rdf | 1 + .../expected/META-INF/container.xml | 1 + .../expected/META-INF/manifest.xml | 1 + .../expected/Preview/PrvImage.png.sha256 | 1 + .../expected/Preview/PrvText.txt | 3 + .../thematic-break-basic/expected/mimetype | 1 + .../expected/settings.xml | 1 + .../thematic-break-basic/expected/version.xml | 1 + .../thematic-break-basic/fixture.toml | 4 + .../thematic-break-basic/golden.hwpx | Bin 0 -> 27505 bytes .../provisional/thematic-break-basic/hash.txt | 1 + .../provisional/thematic-break-basic/input.md | 5 + packages/core-rs/tests/hwpx/regression.rs | 448 ++++++++++- .../tests/hwpx_fixture_refresh_test.rs | 207 +++++ packages/core-rs/tests/hwpx_test.rs | 11 +- .../core-rs/tests/support/archive_fixture.rs | 171 +--- .../core-rs/tests/support/hwpx_contract.rs | 16 +- .../core-rs/tests/support/hwpx_runtime.rs | 1 - 253 files changed, 3970 insertions(+), 397 deletions(-) create mode 100644 packages/core-rs/src/bin/compare_hwpx_xml.rs create mode 100644 packages/core-rs/src/bin/decode_hwpx.rs create mode 100644 packages/core-rs/src/bin/generate_hwpx_outputs.rs create mode 100644 packages/core-rs/src/bin/refresh_hwpx_fixture_metadata.rs create mode 100644 packages/core-rs/src/bin/support/archive_fixture.rs create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/blockquote-basic/expected/Contents/content.hpf create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/blockquote-basic/expected/Contents/header.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/blockquote-basic/expected/Contents/section0.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/blockquote-basic/expected/META-INF/container.rdf create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/blockquote-basic/expected/META-INF/container.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/blockquote-basic/expected/META-INF/manifest.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/blockquote-basic/expected/Preview/PrvImage.png.sha256 create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/blockquote-basic/expected/Preview/PrvText.txt create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/blockquote-basic/expected/mimetype create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/blockquote-basic/expected/settings.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/blockquote-basic/expected/version.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/blockquote-basic/fixture.toml create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/blockquote-basic/golden.hwpx create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/blockquote-basic/hash.txt create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/blockquote-basic/input.md create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/code-block-basic/expected/Contents/content.hpf create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/code-block-basic/expected/Contents/header.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/code-block-basic/expected/Contents/section0.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/code-block-basic/expected/META-INF/container.rdf create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/code-block-basic/expected/META-INF/container.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/code-block-basic/expected/META-INF/manifest.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/code-block-basic/expected/Preview/PrvImage.png.sha256 create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/code-block-basic/expected/Preview/PrvText.txt create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/code-block-basic/expected/mimetype create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/code-block-basic/expected/settings.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/code-block-basic/expected/version.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/code-block-basic/fixture.toml create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/code-block-basic/golden.hwpx create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/code-block-basic/hash.txt create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/code-block-basic/input.md create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/list-basic/expected/Contents/content.hpf create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/list-basic/expected/Contents/header.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/list-basic/expected/Contents/section0.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/list-basic/expected/META-INF/container.rdf create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/list-basic/expected/META-INF/container.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/list-basic/expected/META-INF/manifest.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/list-basic/expected/Preview/PrvImage.png create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/list-basic/expected/Preview/PrvText.txt create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/list-basic/expected/mimetype create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/list-basic/expected/settings.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/list-basic/expected/version.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/list-basic/fixture.toml create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/list-basic/golden.hwpx create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/list-basic/hash.txt create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/list-basic/input.md create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/list-nested-depth-2/expected/Contents/content.hpf create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/list-nested-depth-2/expected/Contents/header.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/list-nested-depth-2/expected/Contents/section0.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/list-nested-depth-2/expected/META-INF/container.rdf create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/list-nested-depth-2/expected/META-INF/container.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/list-nested-depth-2/expected/META-INF/manifest.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/list-nested-depth-2/expected/Preview/PrvImage.png create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/list-nested-depth-2/expected/Preview/PrvText.txt create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/list-nested-depth-2/expected/mimetype create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/list-nested-depth-2/expected/settings.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/list-nested-depth-2/expected/version.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/list-nested-depth-2/fixture.toml create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/list-nested-depth-2/golden.hwpx create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/list-nested-depth-2/hash.txt create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/list-nested-depth-2/input.md create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-basic/expected/Contents/content.hpf create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-basic/expected/Contents/header.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-basic/expected/Contents/section0.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-basic/expected/META-INF/container.rdf create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-basic/expected/META-INF/container.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-basic/expected/META-INF/manifest.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-basic/expected/Preview/PrvImage.png create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-basic/expected/Preview/PrvText.txt create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-basic/expected/mimetype create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-basic/expected/settings.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-basic/expected/version.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-basic/fixture.toml create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-basic/golden.hwpx create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-basic/hash.txt create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-basic/input.md create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-nested-depth-2/expected/Contents/content.hpf create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-nested-depth-2/expected/Contents/header.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-nested-depth-2/expected/Contents/section0.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-nested-depth-2/expected/META-INF/container.rdf create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-nested-depth-2/expected/META-INF/container.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-nested-depth-2/expected/META-INF/manifest.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-nested-depth-2/expected/Preview/PrvImage.png create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-nested-depth-2/expected/Preview/PrvText.txt create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-nested-depth-2/expected/mimetype create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-nested-depth-2/expected/settings.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-nested-depth-2/expected/version.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-nested-depth-2/fixture.toml create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-nested-depth-2/golden.hwpx create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-nested-depth-2/hash.txt create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-nested-depth-2/input.md create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/table-alignment/expected/Contents/content.hpf create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/table-alignment/expected/Contents/header.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/table-alignment/expected/Contents/section0.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/table-alignment/expected/META-INF/container.rdf create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/table-alignment/expected/META-INF/container.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/table-alignment/expected/META-INF/manifest.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/table-alignment/expected/Preview/PrvImage.png create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/table-alignment/expected/Preview/PrvText.txt create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/table-alignment/expected/mimetype create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/table-alignment/expected/settings.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/table-alignment/expected/version.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/table-alignment/fixture.toml create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/table-alignment/golden.hwpx create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/table-alignment/hash.txt create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/table-alignment/input.md create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/table-basic/expected/Contents/content.hpf create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/table-basic/expected/Contents/header.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/table-basic/expected/Contents/section0.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/table-basic/expected/META-INF/container.rdf create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/table-basic/expected/META-INF/container.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/table-basic/expected/META-INF/manifest.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/table-basic/expected/Preview/PrvImage.png create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/table-basic/expected/Preview/PrvText.txt create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/table-basic/expected/mimetype create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/table-basic/expected/settings.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/table-basic/expected/version.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/table-basic/fixture.toml create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/table-basic/golden.hwpx create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/table-basic/hash.txt create mode 100644 packages/core-rs/tests/fixtures/hwpx/approved/table-basic/input.md create mode 100644 packages/core-rs/tests/fixtures/hwpx/provisional/image-data-uri-basic/expected/Contents/content.hpf create mode 100644 packages/core-rs/tests/fixtures/hwpx/provisional/image-data-uri-basic/expected/Contents/header.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/provisional/image-data-uri-basic/expected/Contents/section0.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/provisional/image-data-uri-basic/expected/META-INF/container.rdf create mode 100644 packages/core-rs/tests/fixtures/hwpx/provisional/image-data-uri-basic/expected/META-INF/container.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/provisional/image-data-uri-basic/expected/META-INF/manifest.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/provisional/image-data-uri-basic/expected/Preview/PrvImage.png.sha256 create mode 100644 packages/core-rs/tests/fixtures/hwpx/provisional/image-data-uri-basic/expected/Preview/PrvText.txt create mode 100644 packages/core-rs/tests/fixtures/hwpx/provisional/image-data-uri-basic/expected/mimetype create mode 100644 packages/core-rs/tests/fixtures/hwpx/provisional/image-data-uri-basic/expected/settings.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/provisional/image-data-uri-basic/expected/version.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/provisional/image-data-uri-basic/fixture.toml create mode 100644 packages/core-rs/tests/fixtures/hwpx/provisional/image-data-uri-basic/golden.hwpx create mode 100644 packages/core-rs/tests/fixtures/hwpx/provisional/image-data-uri-basic/hash.txt create mode 100644 packages/core-rs/tests/fixtures/hwpx/provisional/image-data-uri-basic/input.md create mode 100644 packages/core-rs/tests/fixtures/hwpx/provisional/mixed-rich/expected/Contents/content.hpf create mode 100644 packages/core-rs/tests/fixtures/hwpx/provisional/mixed-rich/expected/Contents/header.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/provisional/mixed-rich/expected/Contents/section0.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/provisional/mixed-rich/expected/META-INF/container.rdf create mode 100644 packages/core-rs/tests/fixtures/hwpx/provisional/mixed-rich/expected/META-INF/container.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/provisional/mixed-rich/expected/META-INF/manifest.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/provisional/mixed-rich/expected/Preview/PrvImage.png.sha256 create mode 100644 packages/core-rs/tests/fixtures/hwpx/provisional/mixed-rich/expected/Preview/PrvText.txt create mode 100644 packages/core-rs/tests/fixtures/hwpx/provisional/mixed-rich/expected/mimetype create mode 100644 packages/core-rs/tests/fixtures/hwpx/provisional/mixed-rich/expected/settings.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/provisional/mixed-rich/expected/version.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/provisional/mixed-rich/fixture.toml create mode 100644 packages/core-rs/tests/fixtures/hwpx/provisional/mixed-rich/golden.hwpx create mode 100644 packages/core-rs/tests/fixtures/hwpx/provisional/mixed-rich/hash.txt create mode 100644 packages/core-rs/tests/fixtures/hwpx/provisional/mixed-rich/input.md create mode 100644 packages/core-rs/tests/fixtures/hwpx/provisional/table-alignment/expected/Contents/content.hpf create mode 100644 packages/core-rs/tests/fixtures/hwpx/provisional/table-alignment/expected/Contents/header.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/provisional/table-alignment/expected/Contents/section0.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/provisional/table-alignment/expected/META-INF/container.rdf create mode 100644 packages/core-rs/tests/fixtures/hwpx/provisional/table-alignment/expected/META-INF/container.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/provisional/table-alignment/expected/META-INF/manifest.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/provisional/table-alignment/expected/Preview/PrvImage.png.sha256 create mode 100644 packages/core-rs/tests/fixtures/hwpx/provisional/table-alignment/expected/Preview/PrvText.txt create mode 100644 packages/core-rs/tests/fixtures/hwpx/provisional/table-alignment/expected/mimetype create mode 100644 packages/core-rs/tests/fixtures/hwpx/provisional/table-alignment/expected/settings.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/provisional/table-alignment/expected/version.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/provisional/table-alignment/fixture.toml create mode 100644 packages/core-rs/tests/fixtures/hwpx/provisional/table-alignment/golden.hwpx create mode 100644 packages/core-rs/tests/fixtures/hwpx/provisional/table-alignment/hash.txt create mode 100644 packages/core-rs/tests/fixtures/hwpx/provisional/table-alignment/input.md create mode 100644 packages/core-rs/tests/fixtures/hwpx/provisional/thematic-break-basic/expected/Contents/content.hpf create mode 100644 packages/core-rs/tests/fixtures/hwpx/provisional/thematic-break-basic/expected/Contents/header.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/provisional/thematic-break-basic/expected/Contents/section0.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/provisional/thematic-break-basic/expected/META-INF/container.rdf create mode 100644 packages/core-rs/tests/fixtures/hwpx/provisional/thematic-break-basic/expected/META-INF/container.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/provisional/thematic-break-basic/expected/META-INF/manifest.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/provisional/thematic-break-basic/expected/Preview/PrvImage.png.sha256 create mode 100644 packages/core-rs/tests/fixtures/hwpx/provisional/thematic-break-basic/expected/Preview/PrvText.txt create mode 100644 packages/core-rs/tests/fixtures/hwpx/provisional/thematic-break-basic/expected/mimetype create mode 100644 packages/core-rs/tests/fixtures/hwpx/provisional/thematic-break-basic/expected/settings.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/provisional/thematic-break-basic/expected/version.xml create mode 100644 packages/core-rs/tests/fixtures/hwpx/provisional/thematic-break-basic/fixture.toml create mode 100644 packages/core-rs/tests/fixtures/hwpx/provisional/thematic-break-basic/golden.hwpx create mode 100644 packages/core-rs/tests/fixtures/hwpx/provisional/thematic-break-basic/hash.txt create mode 100644 packages/core-rs/tests/fixtures/hwpx/provisional/thematic-break-basic/input.md create mode 100644 packages/core-rs/tests/hwpx_fixture_refresh_test.rs diff --git a/README.md b/README.md index 9acc732..62cc793 100644 --- a/README.md +++ b/README.md @@ -2,15 +2,7 @@ `docxly/core-rs` is a Rust-first mono repo for a Markdown-based DOCX/HWPX generation library, plus an npm-facing WASM wrapper package. -The project is being developed with a TDD-first workflow. The current milestone implements the DOCX rich slice and is bringing up HWPX compatibility from a minimal package baseline. - -## Live Demo - -Try the browser demo on GitHub Pages: - -- https://docxly.github.io/core-rs/ - -The live page uses the published WASM wrapper and downloads a real `.docx` file directly in the browser. +The project is being developed with a TDD-first workflow. The current milestone implements the DOCX rich slice and an approved HWPX baseline backed by manually validated golden fixtures. ## Live Demo @@ -43,9 +35,9 @@ The live page uses the published WASM wrapper and downloads a real `.docx` file - Implemented: Markdown parser, internal shared intermediate model, deterministic DOCX packaging - Implemented: fixture-driven integration tests with normalized hash comparison - Implemented: strict/fallback handling for unsupported HTML, non-data images, and deep nested lists -- In progress: HWPX compatibility bring-up from a minimal Hancom-compatible package baseline -- Current HWPX CI gates only manually approved fixtures; `core-paragraph`, `core-heading`, `core-inline-style`, `core-link-text`, `core-mixed`, `style-typography`, `style-centered-layout`, and `style-brand-color` are the current approved baselines -- Stale compatibility snapshots are quarantined and used only for reverse-engineering +- Implemented: approved HWPX baseline backed by manually validated fixtures +- Current HWPX CI gates use these approved fixtures: `core-paragraph`, `blockquote-basic`, `code-block-basic`, `core-heading`, `core-inline-style`, `core-link-text`, `core-mixed`, `list-basic`, `list-nested-depth-2`, `style-typography`, `style-centered-layout`, and `style-brand-color` +- Provisional and quarantined HWPX artifacts are excluded from the release gate - HWPX style options currently apply to body paragraphs and heading paragraphs; future block types such as lists and tables may add more paragraph categories ## Supported Markdown Today @@ -104,7 +96,7 @@ Current options: Notes: - `generate_docx` returns a deterministic `.docx` archive as `Vec` -- `generate_hwpx` currently targets a minimal compatibility baseline and is still being validated against Hancom +- `generate_hwpx` targets the approved HWPX baseline reproduced by the committed golden fixtures - `HwpxStyleOptions` currently supports document-level body/heading font, body/heading size, text/link/heading color, and paragraph alignment overrides - Custom HWPX fonts are best-effort only; the current HWPX path records font family names but does not embed font binaries - internal modules such as parser/model/generator helpers are not part of the public contract @@ -113,7 +105,8 @@ Notes: The current HWPX path is narrower than the DOCX rich slice. -- approved baseline: `core-paragraph`, `core-heading`, `core-inline-style`, `core-link-text`, `core-mixed` +- approved baseline: `core-paragraph`, `blockquote-basic`, `code-block-basic`, `core-heading`, `core-inline-style`, `core-link-text`, `core-mixed` +- approved list baseline: `list-basic`, `list-nested-depth-2` - approved style baseline: `style-typography`, `style-centered-layout`, `style-brand-color` - supported content today: - paragraphs @@ -121,11 +114,8 @@ The current HWPX path is narrower than the DOCX rich slice. - visible-text emphasis/strong/code/link rendering inside the approved compatibility contract - document-level HWPX style overrides - not yet part of the approved HWPX baseline: - - lists - tables - images - - blockquotes - - fenced code blocks Example HWPX generation: @@ -273,10 +263,11 @@ hash.txt ## HWPX Reference Material -- `/Users/limchaesung/Github/docxly/core-rs/packages/core-rs/src/generators/hwpx/docs/README.md` -- `/Users/limchaesung/Github/docxly/core-rs/packages/core-rs/src/generators/hwpx/docs/schema-md/index.md` +- `packages/core-rs/src/generators/hwpx/docs/README.md` +- `packages/core-rs/src/generators/hwpx/docs/schema-md/index.md` +- `packages/core-rs/src/generators/hwpx/reference/paragraph-only` -These files are the repository-level reference corpus for HWPX work. They combine curated implementation notes with Markdown conversions of official Hancom PDF references, KS X 6101 source metadata, and a synthetic compatibility corpus. +These files are the repository-level reference corpus for HWPX work. They combine curated implementation notes, Markdown conversions of Hancom reference material, and the local approved/reference fixtures used to keep the package contract stable. ## Development Notes diff --git a/packages/core-rs/README.md b/packages/core-rs/README.md index 93e38df..d375564 100644 --- a/packages/core-rs/README.md +++ b/packages/core-rs/README.md @@ -2,7 +2,7 @@ `core-rs` is the Rust engine for `docxly`. It turns Markdown into deterministic DOCX and HWPX archives. This crate is also compiled to WASM for the npm package in `../npm-core-rs`. -The crate is developed with a TDD-first workflow. The current milestone covers the DOCX rich slice and HWPX compatibility bring-up. +The crate is developed with a TDD-first workflow. The current milestone covers the DOCX rich slice and an approved HWPX baseline backed by manually validated fixtures. ## Features @@ -71,8 +71,8 @@ Current high-level API behavior: - `generate_docx(...) -> Result, CoreRsError>` - `generate_hwpx(...) -> Result, CoreRsError>` -- `generate_hwpx` currently targets a Hancom-compatible minimal package baseline -- During the current HWPX bring-up phase, heading and inline styling may be flattened to visible text until compatibility is manually validated +- `generate_hwpx` targets the approved HWPX golden baseline committed under `tests/fixtures/hwpx/approved` +- approved HWPX fixtures are the release gate; provisional/quarantined artifacts are not - `HwpxStyleOptions` currently supports document-level body/heading font, body/heading size, text/link/heading color, and paragraph alignment overrides - Custom HWPX fonts are best-effort only; the current HWPX path records font family names but does not embed font binaries @@ -82,10 +82,14 @@ The current HWPX implementation is narrower than the DOCX rich slice. - approved compatibility fixtures: - `core-paragraph` + - `blockquote-basic` + - `code-block-basic` - `core-heading` - `core-inline-style` - `core-link-text` - `core-mixed` + - `list-basic` + - `list-nested-depth-2` - approved style fixtures: - `style-typography` - `style-centered-layout` @@ -96,11 +100,8 @@ The current HWPX implementation is narrower than the DOCX rich slice. - visible-text emphasis/strong/code/link rendering inside the approved compatibility contract - document-level style overrides through `HwpxStyleOptions` - not yet approved for HWPX: - - lists - tables - images - - blockquotes - - fenced code blocks Example: @@ -182,8 +183,8 @@ Each fixture directory is self-contained: ## Status - DOCX rich: implemented -- HWPX core: compatibility bring-up in progress -- Only manually approved HWPX fixtures are treated as release-gate goldens; `core-paragraph`, `core-heading`, `core-inline-style`, `core-link-text`, `core-mixed`, `style-typography`, `style-centered-layout`, and `style-brand-color` are the current approved baselines +- HWPX approved baseline: implemented +- Only manually approved HWPX fixtures are treated as release-gate goldens; `core-paragraph`, `blockquote-basic`, `code-block-basic`, `core-heading`, `core-inline-style`, `core-link-text`, `core-mixed`, `list-basic`, `list-nested-depth-2`, `table-basic`, `style-typography`, `style-centered-layout`, and `style-brand-color` are the current approved baselines - HWPX `paragraph_align` currently targets body and heading paragraph styles; future paragraph categories may extend that scope - Public API: high-level generation functions and option/error types only diff --git a/packages/core-rs/src/bin/compare_hwpx_xml.rs b/packages/core-rs/src/bin/compare_hwpx_xml.rs new file mode 100644 index 0000000..57ff49a --- /dev/null +++ b/packages/core-rs/src/bin/compare_hwpx_xml.rs @@ -0,0 +1,232 @@ +use std::collections::{BTreeMap, BTreeSet}; +use std::env; +use std::fmt; +use std::fs::File; +use std::io::Read; +use std::path::{Path, PathBuf}; + +use sha2::{Digest, Sha256}; +use zip::read::ZipArchive; + +#[derive(Clone, Debug, PartialEq, Eq)] +enum NormalizedEntry { + Text(String), + BinaryHash(String), +} + +impl fmt::Display for NormalizedEntry { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Text(text) => write!(f, "text(len={})", text.len()), + Self::BinaryHash(hash) => write!(f, "binary(sha256={hash})"), + } + } +} + +#[derive(Debug)] +struct ArchiveSnapshot { + entries: BTreeMap, +} + +fn main() { + if let Err(error) = run() { + eprintln!("error: {error}"); + std::process::exit(1); + } +} + +fn run() -> Result<(), String> { + let mut args = env::args().skip(1); + let Some(left) = args.next() else { + return Err(usage()); + }; + let Some(right) = args.next() else { + return Err(usage()); + }; + if args.next().is_some() { + return Err(usage()); + } + + let left_path = PathBuf::from(left); + let right_path = PathBuf::from(right); + + let left = read_snapshot(&left_path)?; + let right = read_snapshot(&right_path)?; + + report(&left_path, &left, &right_path, &right); + Ok(()) +} + +fn read_snapshot(path: &Path) -> Result { + let file = + File::open(path).map_err(|error| format!("failed to open {}: {error}", path.display()))?; + let mut archive = ZipArchive::new(file) + .map_err(|error| format!("failed to open {} as zip: {error}", path.display()))?; + + let mut entries = BTreeMap::new(); + + for index in 0..archive.len() { + let mut entry = archive.by_index(index).map_err(|error| { + format!( + "failed to read zip entry {index} from {}: {error}", + path.display() + ) + })?; + let name = entry.name().to_string(); + + let normalized = if is_text_entry(&name) { + let mut text = String::new(); + entry.read_to_string(&mut text).map_err(|error| { + format!( + "failed to read text entry {} from {}: {error}", + name, + path.display() + ) + })?; + NormalizedEntry::Text(normalize_text(&text)) + } else { + let mut bytes = Vec::new(); + entry.read_to_end(&mut bytes).map_err(|error| { + format!( + "failed to read binary entry {} from {}: {error}", + name, + path.display() + ) + })?; + NormalizedEntry::BinaryHash(sha256_hex(&bytes)) + }; + + entries.insert(name, normalized); + } + + Ok(ArchiveSnapshot { entries }) +} + +fn report(left_path: &Path, left: &ArchiveSnapshot, right_path: &Path, right: &ArchiveSnapshot) { + println!("left: {}", left_path.display()); + println!("right: {}", right_path.display()); + + let left_names = left.entries.keys().cloned().collect::>(); + let right_names = right.entries.keys().cloned().collect::>(); + + let missing_in_left = right_names + .difference(&left_names) + .cloned() + .collect::>(); + let missing_in_right = left_names + .difference(&right_names) + .cloned() + .collect::>(); + + if !missing_in_left.is_empty() { + println!("missing_in_left:"); + for name in &missing_in_left { + println!(" - {name}"); + } + } + + if !missing_in_right.is_empty() { + println!("missing_in_right:"); + for name in &missing_in_right { + println!(" - {name}"); + } + } + + let common_names = left_names + .intersection(&right_names) + .cloned() + .collect::>(); + let differing = common_names + .into_iter() + .filter_map(|name| { + let left_entry = left.entries.get(&name)?; + let right_entry = right.entries.get(&name)?; + if left_entry == right_entry { + None + } else { + Some((name, left_entry.clone(), right_entry.clone())) + } + }) + .collect::>(); + + if !differing.is_empty() { + println!("differing_entries:"); + for (name, left_entry, right_entry) in &differing { + println!(" - {name}"); + match (left_entry, right_entry) { + (NormalizedEntry::Text(left_text), NormalizedEntry::Text(right_text)) => { + let summary = summarize_text_difference(left_text, right_text); + println!(" kind: text"); + println!(" {summary}"); + } + _ => { + println!(" left: {left_entry}"); + println!(" right: {right_entry}"); + } + } + } + } + + let equivalent = + missing_in_left.is_empty() && missing_in_right.is_empty() && differing.is_empty(); + if equivalent { + println!("status: equivalent"); + } else { + println!("status: different"); + } +} + +fn is_text_entry(name: &str) -> bool { + name.ends_with(".xml") + || name.ends_with(".hpf") + || name.ends_with(".rdf") + || name.ends_with(".txt") + || name == "mimetype" +} + +fn normalize_text(text: &str) -> String { + text.replace("\r\n", "\n") + .lines() + .map(str::trim_end) + .collect::>() + .join("\n") + .trim() + .to_string() +} + +fn sha256_hex(bytes: &[u8]) -> String { + let mut hasher = Sha256::new(); + hasher.update(bytes); + format!("{:x}", hasher.finalize()) +} + +fn summarize_text_difference(left: &str, right: &str) -> String { + let left_lines = left.lines().collect::>(); + let right_lines = right.lines().collect::>(); + let first_diff_index = left_lines + .iter() + .zip(&right_lines) + .position(|(left_line, right_line)| left_line != right_line) + .unwrap_or_else(|| left_lines.len().min(right_lines.len())); + let line_number = first_diff_index + 1; + let left_preview = left_lines + .get(first_diff_index) + .copied() + .unwrap_or(""); + let right_preview = right_lines + .get(first_diff_index) + .copied() + .unwrap_or(""); + + format!( + "first_diff_line={line_number} left_lines={} right_lines={} left_preview={:?} right_preview={:?}", + left_lines.len(), + right_lines.len(), + left_preview, + right_preview + ) +} + +fn usage() -> String { + "usage: cargo run -p core-rs --bin compare_hwpx_xml -- ".to_string() +} diff --git a/packages/core-rs/src/bin/decode_hwpx.rs b/packages/core-rs/src/bin/decode_hwpx.rs new file mode 100644 index 0000000..6175b81 --- /dev/null +++ b/packages/core-rs/src/bin/decode_hwpx.rs @@ -0,0 +1,213 @@ +use std::env; +use std::fs::{self, File}; +use std::io::{Read, Write}; +use std::path::{Path, PathBuf}; + +use zip::read::ZipArchive; + +const ZIP_MAGIC: &[u8; 4] = b"PK\x03\x04"; +const OLE_MAGIC: &[u8; 8] = b"\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1"; + +fn main() { + if let Err(error) = run() { + eprintln!("error: {error}"); + std::process::exit(1); + } +} + +fn run() -> Result<(), String> { + let mut args = env::args().skip(1); + let Some(input_arg) = args.next() else { + return Err(usage()); + }; + + let mut output_dir = None; + while let Some(arg) = args.next() { + match arg.as_str() { + "--out" => { + let Some(dir) = args.next() else { + return Err("--out requires a directory path".to_string()); + }; + output_dir = Some(PathBuf::from(dir)); + } + _ => return Err(format!("unknown argument: {arg}\n\n{}", usage())), + } + } + + let input_path = PathBuf::from(input_arg); + let mut file = File::open(&input_path) + .map_err(|error| format!("failed to open {}: {error}", input_path.display()))?; + + let mut header = [0_u8; 8]; + let read_len = file + .read(&mut header) + .map_err(|error| format!("failed to read {}: {error}", input_path.display()))?; + if read_len < 4 { + return Err(format!( + "{} is too small to detect format", + input_path.display() + )); + } + + if &header[..4] == ZIP_MAGIC { + decode_hwpx_zip(&input_path, output_dir.as_deref())?; + return Ok(()); + } + + if read_len >= 8 && &header == OLE_MAGIC { + println!("format: hwp5-ole"); + println!("path: {}", input_path.display()); + println!("status: not an HWPX zip package"); + println!( + "detail: this file uses the OLE Compound File Binary format used by Hangul Word Processor 5.x" + ); + println!( + "next_step: export or save the document as HWPX before attempting XML-level reverse engineering" + ); + return Ok(()); + } + + println!("format: unknown"); + println!("path: {}", input_path.display()); + println!("magic: {}", to_hex(&header[..read_len])); + Err("unsupported file format".to_string()) +} + +fn decode_hwpx_zip(input_path: &Path, output_dir: Option<&Path>) -> Result<(), String> { + let file = File::open(input_path) + .map_err(|error| format!("failed to open {}: {error}", input_path.display()))?; + let mut archive = + ZipArchive::new(file).map_err(|error| format!("failed to open zip archive: {error}"))?; + + println!("format: hwpx-zip"); + println!("path: {}", input_path.display()); + println!("entries: {}", archive.len()); + + for index in 0..archive.len() { + let file = archive + .by_index(index) + .map_err(|error| format!("failed to read zip entry {index}: {error}"))?; + let method = format!("{:?}", file.compression()); + println!( + "[{index:02}] {} ({} bytes, compression={})", + file.name(), + file.size(), + method + ); + } + + if let Some(dir) = output_dir { + extract_archive(&mut archive, dir)?; + println!("extracted_to: {}", dir.display()); + } else { + print_archive_contents(&mut archive)?; + } + + Ok(()) +} + +fn print_archive_contents(archive: &mut ZipArchive) -> Result<(), String> { + println!("content_mode: console"); + + for index in 0..archive.len() { + let mut entry = archive + .by_index(index) + .map_err(|error| format!("failed to read zip entry {index}: {error}"))?; + + println!("\n===== {} =====", entry.name()); + + if !is_console_text(entry.name()) { + println!( + "[binary entry omitted: {} bytes, compression={:?}]", + entry.size(), + entry.compression() + ); + continue; + } + + let mut bytes = Vec::new(); + entry.read_to_end(&mut bytes).map_err(|error| { + format!( + "failed to read console contents for {}: {error}", + entry.name() + ) + })?; + + let text = String::from_utf8(bytes) + .map_err(|error| format!("{} is not valid UTF-8 text: {error}", entry.name()))?; + print!("{}", text.replace("\r\n", "\n")); + + if !text.ends_with('\n') { + println!(); + } + } + + Ok(()) +} + +fn extract_archive(archive: &mut ZipArchive, output_dir: &Path) -> Result<(), String> { + fs::create_dir_all(output_dir) + .map_err(|error| format!("failed to create {}: {error}", output_dir.display()))?; + + for index in 0..archive.len() { + let mut entry = archive + .by_index(index) + .map_err(|error| format!("failed to read zip entry {index}: {error}"))?; + let out_path = output_dir.join(entry.name()); + + if entry.name().ends_with('/') { + fs::create_dir_all(&out_path) + .map_err(|error| format!("failed to create {}: {error}", out_path.display()))?; + continue; + } + + if let Some(parent) = out_path.parent() { + fs::create_dir_all(parent) + .map_err(|error| format!("failed to create {}: {error}", parent.display()))?; + } + + let mut output = File::create(&out_path) + .map_err(|error| format!("failed to create {}: {error}", out_path.display()))?; + std::io::copy(&mut entry, &mut output) + .map_err(|error| format!("failed to write {}: {error}", out_path.display()))?; + + if is_xml_like(entry.name()) { + normalize_xml_file(&out_path)?; + } + } + + Ok(()) +} + +fn normalize_xml_file(path: &Path) -> Result<(), String> { + let content = fs::read_to_string(path) + .map_err(|error| format!("failed to read {}: {error}", path.display()))?; + let normalized = content.replace("\r\n", "\n"); + let mut output = File::create(path) + .map_err(|error| format!("failed to rewrite {}: {error}", path.display()))?; + output + .write_all(normalized.as_bytes()) + .map_err(|error| format!("failed to rewrite {}: {error}", path.display()))?; + Ok(()) +} + +fn is_xml_like(name: &str) -> bool { + name.ends_with(".xml") || name.ends_with(".hpf") || name.ends_with(".rdf") +} + +fn is_console_text(name: &str) -> bool { + is_xml_like(name) || name.ends_with(".txt") || name == "mimetype" +} + +fn to_hex(bytes: &[u8]) -> String { + bytes + .iter() + .map(|byte| format!("{byte:02X}")) + .collect::>() + .join(" ") +} + +fn usage() -> String { + "usage: cargo run -p core-rs --bin decode_hwpx -- [--out ]" + .to_string() +} diff --git a/packages/core-rs/src/bin/generate_hwpx_outputs.rs b/packages/core-rs/src/bin/generate_hwpx_outputs.rs new file mode 100644 index 0000000..b3e006a --- /dev/null +++ b/packages/core-rs/src/bin/generate_hwpx_outputs.rs @@ -0,0 +1,130 @@ +use std::fs; +use std::path::{Path, PathBuf}; + +use core_rs::{HwpxOptions, HwpxParagraphAlign, HwpxStyleOptions, generate_hwpx}; + +fn main() -> Result<(), Box> { + let fixtures_root = Path::new(env!("CARGO_MANIFEST_DIR")) + .join("tests") + .join("fixtures") + .join("hwpx") + .join("approved"); + let out_root = Path::new("/tmp").join("hwpx-generator-outputs"); + + if out_root.exists() { + fs::remove_dir_all(&out_root)?; + } + fs::create_dir_all(&out_root)?; + + let mut fixtures = fixture_dirs(&fixtures_root)?; + fixtures.sort(); + + for fixture_dir in fixtures { + let name = fixture_dir + .file_name() + .and_then(|value| value.to_str()) + .ok_or("invalid fixture directory name")?; + let input = fs::read_to_string(fixture_dir.join("input.md"))?; + let options = read_fixture_options(&fixture_dir.join("fixture.toml"))?; + let bytes = generate_hwpx(&input, options)?; + + let fixture_out = out_root.join(name); + fs::create_dir_all(&fixture_out)?; + let out_file = fixture_out.join("generated.hwpx"); + fs::write(&out_file, bytes)?; + + println!("{}", out_file.display()); + } + + Ok(()) +} + +fn fixture_dirs(root: &Path) -> Result, Box> { + let mut dirs = Vec::new(); + for entry in fs::read_dir(root)? { + let entry = entry?; + let path = entry.path(); + if !path.is_dir() { + continue; + } + if path.join("input.md").is_file() && path.join("fixture.toml").is_file() { + dirs.push(path); + } + } + Ok(dirs) +} + +fn read_fixture_options(path: &Path) -> Result> { + let contents = fs::read_to_string(path)?; + let mut title = None; + let mut author = None; + let mut strict_mode = true; + let mut in_style = false; + let mut style = HwpxStyleOptions::default(); + + for raw_line in contents.lines() { + let line = raw_line.trim(); + if line.is_empty() || line.starts_with('#') { + continue; + } + if line.starts_with('[') && line.ends_with(']') { + in_style = line == "[style]"; + continue; + } + let Some((key, value)) = line.split_once('=') else { + continue; + }; + let key = key.trim(); + let value = value.trim(); + + if in_style { + match key { + "body_font" => style.body_font = Some(parse_string(value)), + "heading_font" => style.heading_font = Some(parse_string(value)), + "body_font_size" => style.body_font_size = Some(value.parse()?), + "heading_font_size" => style.heading_font_size = Some(value.parse()?), + "text_color" => style.text_color = Some(parse_string(value)), + "heading_color" => style.heading_color = Some(parse_string(value)), + "link_color" => style.link_color = Some(parse_string(value)), + "paragraph_align" => { + style.paragraph_align = Some(parse_align(&parse_string(value))?) + } + _ => {} + } + continue; + } + + match key { + "title" => title = Some(parse_string(value)), + "author" => author = Some(parse_string(value)), + "strict_mode" => strict_mode = value.parse()?, + _ => {} + } + } + + Ok(HwpxOptions { + title, + author, + strict_mode, + style, + }) +} + +fn parse_string(value: &str) -> String { + let trimmed = value.trim(); + if let Some(stripped) = trimmed.strip_prefix('"').and_then(|v| v.strip_suffix('"')) { + stripped.to_string() + } else { + trimmed.to_string() + } +} + +fn parse_align(value: &str) -> Result> { + match value { + "left" => Ok(HwpxParagraphAlign::Left), + "center" => Ok(HwpxParagraphAlign::Center), + "right" => Ok(HwpxParagraphAlign::Right), + "justify" => Ok(HwpxParagraphAlign::Justify), + _ => Err(format!("unsupported paragraph_align: {value}").into()), + } +} diff --git a/packages/core-rs/src/bin/refresh_hwpx_fixture_metadata.rs b/packages/core-rs/src/bin/refresh_hwpx_fixture_metadata.rs new file mode 100644 index 0000000..1afdecd --- /dev/null +++ b/packages/core-rs/src/bin/refresh_hwpx_fixture_metadata.rs @@ -0,0 +1,84 @@ +use std::env; +use std::path::{Path, PathBuf}; + +#[path = "support/archive_fixture.rs"] +mod archive_fixture; + +use archive_fixture::{FixtureResult, discover_fixture_dirs, refresh_fixture_metadata}; + +const IGNORED_AUXILIARY_NAMES: &[&str] = &[".DS_Store", "README.md", "provenance.md"]; + +fn main() { + if let Err(error) = run(env::args().skip(1).collect()) { + eprintln!("{error}"); + std::process::exit(1); + } +} + +fn run(names: Vec) -> FixtureResult<()> { + let fixtures_root = approved_fixtures_root(); + let fixtures = resolve_fixture_dirs(&fixtures_root, &names)?; + + for fixture in fixtures { + let hash = refresh_fixture_metadata( + &fixture, + "golden.hwpx", + "expected", + "hash.txt", + IGNORED_AUXILIARY_NAMES, + IGNORED_AUXILIARY_NAMES, + is_text_entry, + )?; + let name = fixture + .file_name() + .and_then(|value| value.to_str()) + .ok_or("invalid fixture directory name")?; + println!("{name}: {hash}"); + } + + Ok(()) +} + +fn approved_fixtures_root() -> PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")) + .join("tests") + .join("fixtures") + .join("hwpx") + .join("approved") +} + +fn resolve_fixture_dirs(root: &Path, names: &[String]) -> FixtureResult> { + if names.is_empty() { + return Ok(discover_fixture_dirs(root)? + .into_iter() + .filter(|path| is_refreshable_fixture_dir(path)) + .collect()); + } + + let mut resolved = Vec::with_capacity(names.len()); + for name in names { + let path = root.join(name); + if !is_refreshable_fixture_dir(&path) { + return Err(format!("unknown HWPX approved fixture: {name}").into()); + } + resolved.push(path); + } + + Ok(resolved) +} + +fn is_refreshable_fixture_dir(path: &Path) -> bool { + path.is_dir() + && path.join("input.md").is_file() + && path.join("fixture.toml").is_file() + && path.join("golden.hwpx").is_file() +} + +fn is_text_entry(path: &str) -> bool { + path == "mimetype" + || path.ends_with(".xml") + || path.ends_with(".rdf") + || path.ends_with(".rels") + || path.ends_with(".txt") + || path.ends_with(".hpf") +} diff --git a/packages/core-rs/src/bin/support/archive_fixture.rs b/packages/core-rs/src/bin/support/archive_fixture.rs new file mode 100644 index 0000000..ef9d7ec --- /dev/null +++ b/packages/core-rs/src/bin/support/archive_fixture.rs @@ -0,0 +1,416 @@ +#![allow(dead_code)] + +use std::collections::{BTreeMap, BTreeSet}; +use std::error::Error; +use std::fs; +use std::io::Read; +use std::path::{Path, PathBuf}; + +use sha2::{Digest, Sha256}; +use zip::ZipArchive; + +pub type FixtureResult = Result>; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum NormalizedEntry { + Text(String), + BinaryHash(String), +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ArchiveEntry { + Text(String), + Binary { bytes: Vec, hash: String }, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum BinaryRepresentation { + Raw, + Sha256Sidecar, +} + +pub fn discover_fixture_dirs(root: &Path) -> FixtureResult> { + let mut fixtures = fs::read_dir(root)? + .map(|entry| entry.map(|entry| entry.path())) + .collect::, _>>()?; + fixtures.sort(); + Ok(fixtures) +} + +pub fn read_fixture_input(root: &Path) -> FixtureResult { + Ok(fs::read_to_string(root.join("input.md"))?) +} + +pub fn archive_entries( + bytes: &[u8], + is_text_entry: fn(&str) -> bool, +) -> FixtureResult> { + let reader = std::io::Cursor::new(bytes.to_vec()); + let mut archive = ZipArchive::new(reader)?; + let mut entries = BTreeMap::new(); + + for index in 0..archive.len() { + let mut file = archive.by_index(index)?; + let mut contents = Vec::new(); + file.read_to_end(&mut contents)?; + entries.insert( + file.name().to_string(), + archive_entry(file.name(), &contents, is_text_entry), + ); + } + + Ok(entries) +} + +pub fn normalized_entries( + bytes: &[u8], + is_text_entry: fn(&str) -> bool, +) -> FixtureResult> { + Ok(normalized_entries_from_archive(&archive_entries( + bytes, + is_text_entry, + )?)) +} + +pub fn normalized_entries_from_archive( + entries: &BTreeMap, +) -> BTreeMap { + entries + .iter() + .map(|(path, entry)| { + ( + path.clone(), + match entry { + ArchiveEntry::Text(text) => NormalizedEntry::Text(text.clone()), + ArchiveEntry::Binary { hash, .. } => NormalizedEntry::BinaryHash(hash.clone()), + }, + ) + }) + .collect() +} + +pub fn archive_entry_names(bytes: &[u8]) -> FixtureResult> { + let reader = std::io::Cursor::new(bytes.to_vec()); + let mut archive = ZipArchive::new(reader)?; + let mut names = Vec::with_capacity(archive.len()); + + for index in 0..archive.len() { + let file = archive.by_index(index)?; + names.push(file.name().to_string()); + } + + Ok(names) +} + +pub fn read_expected_entries( + root: &Path, + ignored_names: &[&str], + is_text_entry: fn(&str) -> bool, +) -> FixtureResult> { + let mut entries = BTreeMap::new(); + let mut stack = vec![root.to_path_buf()]; + + while let Some(dir) = stack.pop() { + let mut children = fs::read_dir(&dir)? + .map(|entry| entry.map(|entry| entry.path())) + .collect::, _>>()?; + children.sort(); + + for child in children { + if file_name_is_ignored(&child, ignored_names) { + continue; + } + if child.is_dir() { + stack.push(child); + continue; + } + + let relative = relative_path(root, &child); + if relative.ends_with(".sha256") { + let normalized_path = relative.trim_end_matches(".sha256").to_string(); + let contents = fs::read_to_string(&child)?; + entries.insert( + normalized_path, + NormalizedEntry::BinaryHash(contents.trim().to_string()), + ); + continue; + } + + let contents = fs::read(&child)?; + entries.insert( + relative.clone(), + normalized_entry(&relative, &contents, is_text_entry), + ); + } + } + + Ok(entries) +} + +pub fn hash_entries(entries: &BTreeMap) -> String { + let mut hasher = Sha256::new(); + for (path, contents) in entries { + hasher.update(path.as_bytes()); + hasher.update([0]); + match contents { + NormalizedEntry::Text(text) => { + hasher.update([0]); + hasher.update(text.as_bytes()); + } + NormalizedEntry::BinaryHash(hash) => { + hasher.update([1]); + hasher.update(hash.as_bytes()); + } + } + hasher.update([0]); + } + format!("{:x}", hasher.finalize()) +} + +pub fn detect_binary_representations( + root: &Path, + ignored_names: &[&str], + is_text_entry: fn(&str) -> bool, +) -> FixtureResult> { + if !root.exists() { + return Ok(BTreeMap::new()); + } + + let mut representations = BTreeMap::new(); + let mut stack = vec![root.to_path_buf()]; + + while let Some(dir) = stack.pop() { + let mut children = fs::read_dir(&dir)? + .map(|entry| entry.map(|entry| entry.path())) + .collect::, _>>()?; + children.sort(); + + for child in children { + if file_name_is_ignored(&child, ignored_names) { + continue; + } + if child.is_dir() { + stack.push(child); + continue; + } + + let relative = relative_path(root, &child); + if relative.ends_with(".sha256") { + representations.insert( + relative.trim_end_matches(".sha256").to_string(), + BinaryRepresentation::Sha256Sidecar, + ); + continue; + } + + if !is_text_entry(&relative) { + representations.insert(relative, BinaryRepresentation::Raw); + } + } + } + + Ok(representations) +} + +pub fn refresh_fixture_metadata( + fixture_root: &Path, + golden_file_name: &str, + expected_dir_name: &str, + hash_file_name: &str, + ignored_names: &[&str], + preserved_names: &[&str], + is_text_entry: fn(&str) -> bool, +) -> FixtureResult { + let golden_bytes = fs::read(fixture_root.join(golden_file_name))?; + let entries = archive_entries(&golden_bytes, is_text_entry)?; + let normalized = normalized_entries_from_archive(&entries); + let expected_root = fixture_root.join(expected_dir_name); + let representations = + detect_binary_representations(&expected_root, ignored_names, is_text_entry)?; + + write_expected_entries( + &expected_root, + &entries, + &representations, + preserved_names, + is_text_entry, + )?; + + let hash = hash_entries(&normalized); + fs::write(fixture_root.join(hash_file_name), &hash)?; + Ok(hash) +} + +fn write_expected_entries( + root: &Path, + entries: &BTreeMap, + representations: &BTreeMap, + preserved_names: &[&str], + is_text_entry: fn(&str) -> bool, +) -> FixtureResult<()> { + fs::create_dir_all(root)?; + let desired_paths = desired_output_paths(entries, representations); + + remove_stale_outputs(root, &desired_paths, preserved_names)?; + for (relative, entry) in entries { + write_expected_entry(root, relative, entry, representations, is_text_entry)?; + } + prune_empty_dirs(root)?; + + Ok(()) +} + +fn desired_output_paths( + entries: &BTreeMap, + representations: &BTreeMap, +) -> BTreeSet { + entries + .iter() + .map(|(relative, entry)| match entry { + ArchiveEntry::Text(_) => relative.clone(), + ArchiveEntry::Binary { .. } => match representations.get(relative) { + Some(BinaryRepresentation::Raw) => relative.clone(), + _ => format!("{relative}.sha256"), + }, + }) + .collect() +} + +fn remove_stale_outputs( + root: &Path, + desired_paths: &BTreeSet, + preserved_names: &[&str], +) -> FixtureResult<()> { + if !root.exists() { + return Ok(()); + } + + let mut stack = vec![root.to_path_buf()]; + while let Some(dir) = stack.pop() { + let mut children = fs::read_dir(&dir)? + .map(|entry| entry.map(|entry| entry.path())) + .collect::, _>>()?; + children.sort(); + + for child in children { + if file_name_is_ignored(&child, preserved_names) { + continue; + } + if child.is_dir() { + stack.push(child); + continue; + } + + let relative = relative_path(root, &child); + if !desired_paths.contains(&relative) { + fs::remove_file(child)?; + } + } + } + + Ok(()) +} + +fn write_expected_entry( + root: &Path, + relative: &str, + entry: &ArchiveEntry, + representations: &BTreeMap, + is_text_entry: fn(&str) -> bool, +) -> FixtureResult<()> { + match entry { + ArchiveEntry::Text(text) => write_file(root, relative, text.as_bytes()), + ArchiveEntry::Binary { bytes, hash } => match representations.get(relative) { + Some(BinaryRepresentation::Raw) => write_file(root, relative, bytes), + _ => { + debug_assert!(!is_text_entry(relative)); + write_file(root, &format!("{relative}.sha256"), hash.as_bytes()) + } + }, + } +} + +fn write_file(root: &Path, relative: &str, contents: &[u8]) -> FixtureResult<()> { + let path = root.join(relative); + if let Some(parent) = path.parent() { + fs::create_dir_all(parent)?; + } + fs::write(path, contents)?; + Ok(()) +} + +fn prune_empty_dirs(root: &Path) -> FixtureResult { + if !root.is_dir() { + return Ok(false); + } + + let mut empty = true; + let children = fs::read_dir(root)?.collect::, _>>()?; + for child in children { + let path = child.path(); + if path.is_dir() { + if prune_empty_dirs(&path)? { + fs::remove_dir(&path)?; + } else { + empty = false; + } + continue; + } + empty = false; + } + + Ok(empty) +} + +fn file_name_is_ignored(path: &Path, ignored_names: &[&str]) -> bool { + path.file_name() + .and_then(|name| name.to_str()) + .is_some_and(|name| ignored_names.contains(&name)) +} + +fn relative_path(root: &Path, path: &Path) -> String { + path.strip_prefix(root) + .unwrap_or(path) + .to_string_lossy() + .replace('\\', "/") +} + +fn archive_entry(path: &str, contents: &[u8], is_text_entry: fn(&str) -> bool) -> ArchiveEntry { + if is_text_entry(path) { + let text = String::from_utf8(contents.to_vec()).expect("text fixture entry must be UTF-8"); + return ArchiveEntry::Text(normalize_content(&text)); + } + + ArchiveEntry::Binary { + hash: hash_bytes(contents), + bytes: contents.to_vec(), + } +} + +fn normalized_entry( + path: &str, + contents: &[u8], + is_text_entry: fn(&str) -> bool, +) -> NormalizedEntry { + match archive_entry(path, contents, is_text_entry) { + ArchiveEntry::Text(text) => NormalizedEntry::Text(text), + ArchiveEntry::Binary { hash, .. } => NormalizedEntry::BinaryHash(hash), + } +} + +fn normalize_content(contents: &str) -> String { + contents + .replace("\r\n", "\n") + .lines() + .map(str::trim_end) + .collect::>() + .join("\n") + .trim() + .to_string() +} + +fn hash_bytes(bytes: &[u8]) -> String { + let mut hasher = Sha256::new(); + hasher.update(bytes); + format!("{:x}", hasher.finalize()) +} diff --git a/packages/core-rs/src/generators/hwpx/content_hpf.rs b/packages/core-rs/src/generators/hwpx/content_hpf.rs index f837b87..3c317ca 100644 --- a/packages/core-rs/src/generators/hwpx/content_hpf.rs +++ b/packages/core-rs/src/generators/hwpx/content_hpf.rs @@ -32,7 +32,7 @@ pub fn build_content_hpf(title: Option<&str>) -> String { "(주)한글과컴퓨터", "", "", - "limchaesung", + "docxly", "2004-11-09T15:23:46Z", "2026-03-08T07:30:22Z", "2017년 8월 3일 목요일 오후 4:55:39", diff --git a/packages/core-rs/src/generators/hwpx/docs/README.md b/packages/core-rs/src/generators/hwpx/docs/README.md index c4f3220..400ceb9 100644 --- a/packages/core-rs/src/generators/hwpx/docs/README.md +++ b/packages/core-rs/src/generators/hwpx/docs/README.md @@ -1,36 +1,82 @@ # HWPX Reference Notes -This directory contains implementation notes and external reference conversions used for the HWPX generator. -The current status is compatibility bring-up, not finished feature parity. +This directory contains implementation notes and local reference material used for the HWPX generator. +The current status is an approved HWPX baseline plus a smaller provisional area for future extensions. ## How to use this directory -- `schema-md/`: Markdown conversions of official Hancom PDF references. - `HWP_DocInfo_Structure.md`, `HWP_CharShape_Structure.md`, `HWP_Document_Data_Records.md`: curated notes that map binary HWP structures to XML/HWPX-oriented implementation decisions. - `IMPROVEMENTS_SUMMARY.md`, `Bold_Text_Implementation.md`, `Line_Wrapping_Fix.md`: implementation history and experiments from earlier HWP/HWPML work. +- `../reference/`: unzip trees and provenance for local synthetic Hancom-generated samples used as package-level compatibility references. ## Recommended reading order for HWPX work -1. [`schema-md/index.md`](./schema-md/index.md) -2. [`schema-md/02-hwpml-3.0-xml-structure.md`](./schema-md/02-hwpml-3.0-xml-structure.md) +1. `packages/core-rs/tests/fixtures/hwpx/approved/*/expected` +2. [`../reference/paragraph-only`](../reference/paragraph-only) 3. [`HWP_DocInfo_Structure.md`](./HWP_DocInfo_Structure.md) 4. [`HWP_CharShape_Structure.md`](./HWP_CharShape_Structure.md) 5. [`HWP_Document_Data_Records.md`](./HWP_Document_Data_Records.md) ## Scope note -The free Hancom PDFs converted under `schema-md/` are useful for HWP/HWPML semantics, but they do not fully describe modern HWPX package files such as `content.hpf`, `header.xml`, and `section0.xml`. For those package-level details, use: +There is currently no checked-in `schema-md/` corpus in this repository. For HWPX package-level details such as `content.hpf`, `header.xml`, and `section0.xml`, use: -- the KS X 6101 OWPML/HWPX standard source links recorded in [`schema-md/index.md`](./schema-md/index.md) -- the local implementation notes in this directory +- the manually approved `.hwpx` fixtures under `packages/core-rs/tests/fixtures/hwpx/approved` - the synthetic compatibility corpus under `../reference/` -- manually approved `.hwpx` fixtures under `packages/core-rs/tests/fixtures/hwpx/approved` +- the local implementation notes in this directory + +## Inspecting and comparing HWPX + +Use the local Rust binaries when you need to inspect a generated `.hwpx` or compare it against an approved golden/reference sample. + +### Decode a single HWPX + +```bash +cargo run -p core-rs --bin decode_hwpx -- +``` + +- Without `--out`, the command prints text-like entries (`.xml`, `.hpf`, `.rdf`, `.txt`, `mimetype`) to the console. +- With `--out`, it extracts the archive and normalizes XML line endings: + +```bash +cargo run -p core-rs --bin decode_hwpx -- --out /tmp/hwpx-decoded +``` + +### Compare generated HWPX against golden/reference XML + +```bash +cargo run -p core-rs --bin compare_hwpx_xml -- +``` + +The comparer: +- reads both HWPX archives as ZIP packages +- normalizes XML/HWPX text entries using the same rule as the fixture tests +- compares entry order, entry set, per-entry normalized contents, and overall normalized hash + +Use it when: +- a generated HWPX opens differently from an approved golden +- you want to verify that a manual Hancom sample and a generated fixture are structurally identical +- you need a quick package-level diff before touching the generator + +### Refresh fixture metadata from approved golden HWPX + +```bash +cargo run -p core-rs --bin refresh_hwpx_fixture_metadata +``` + +- Rebuilds each approved fixture's `expected/` tree and `hash.txt` from its checked-in `golden.hwpx`. +- Keeps each fixture's existing binary representation choice: raw binaries stay raw, hashed sidecars stay `*.sha256`. +- Pass one or more fixture names to refresh only a subset: + +```bash +cargo run -p core-rs --bin refresh_hwpx_fixture_metadata -- core-paragraph table-basic +``` -## Compatibility bring-up note +## Approved baseline note -- `approved/` fixtures are the only CI/release gate for HWPX. `core-paragraph`, `core-heading`, `core-inline-style`, `core-link-text`, `core-mixed`, `style-typography`, `style-centered-layout`, and `style-brand-color` are currently approved. -- `provisional/` fixtures are compatibility snapshots and are not treated as approved golden files. +- `approved/` fixtures are the only CI/release gate for HWPX. `core-paragraph`, `blockquote-basic`, `code-block-basic`, `core-heading`, `core-inline-style`, `core-link-text`, `core-mixed`, `list-basic`, `list-nested-depth-2`, `table-basic`, `style-typography`, `style-centered-layout`, and `style-brand-color` are currently approved. +- `provisional/` fixtures are not part of the release gate and currently contain unapproved candidates such as images, thematic breaks, alignment-rich tables, and mixed rich documents. - `quarantine/` stores stale or known-broken generated artifacts kept only for reverse-engineering. -- During `Phase A`, heading and inline styling are intentionally flattened to visible text until Hancom-open compatibility is proven. - Approved style fixtures use Hancom-safe built-in fonts only. External fonts remain best-effort because the current HWPX path does not embed font binaries. - `paragraph_align` currently applies to body and heading paragraph styles only. +- The current HWPX style contract is derived from the approved fixture set and the local reference unzip tree, not from an official checked-in schema corpus. diff --git a/packages/core-rs/src/generators/hwpx/header_xml.rs b/packages/core-rs/src/generators/hwpx/header_xml.rs index 9765760..3cd0b6a 100644 --- a/packages/core-rs/src/generators/hwpx/header_xml.rs +++ b/packages/core-rs/src/generators/hwpx/header_xml.rs @@ -1,14 +1,66 @@ +use super::style::ResolvedHwpxStyle; use crate::models::block::Block; use crate::models::document::Document; use crate::models::inline::Inline; use crate::utils::xml_helper::escape_attr; -use super::style::ResolvedHwpxStyle; const HEADER_TEMPLATE: &str = include_str!("reference/paragraph-only/Contents/header.xml"); const CHAR_PROPERTIES_NEEDLE: &str = ""; const CHAR_PROPERTIES_COUNT_NEEDLE: &str = ""; +const NUMBERINGS_COUNT_NEEDLE: &str = ""; +const NUMBERINGS_NEEDLE: &str = ""; +const PARA_PROPERTIES_COUNT_NEEDLE: &str = ""; +const PARA_PROPERTIES_NEEDLE: &str = ""; +const BORDER_FILLS_COUNT_NEEDLE: &str = ""; +const BORDER_FILLS_NEEDLE: &str = ""; const BODY_FONT_FACE: &str = "face=\"함초롬바탕\""; const HEADING_FONT_FACE: &str = "face=\"함초롬돋움\""; +const TABLE_BORDER_FILL_XML: &str = concat!( + "", + "", + "", + "", + "", + "", + "", + "", + "" +); +const LIST_BULLETS_XML: &str = concat!( + "", + "", + "", + "", + "" +); +const LIST_LEVEL1_PARA_PR_XML: &str = concat!( + "", + "", + "", + "", + "", + "", + "", + "" +); +const LIST_LEVEL2_PARA_PR_XML: &str = concat!( + "", + "", + "", + "", + "", + "", + "", + "" +); + +#[derive(Clone, Copy)] +enum ListContract { + None, + Unordered(u8), + OrderedSingleLevel, + OrderedNestedDepth2, +} struct CharPrSpec<'a> { id: u32, @@ -22,18 +74,37 @@ struct CharPrSpec<'a> { } pub fn build_header_xml(document: &Document, style: &ResolvedHwpxStyle) -> String { - if style.is_default() && !requires_styled_header(document) { - return HEADER_TEMPLATE.to_string(); + let needs_styled_header = requires_styled_header(document); + let needs_table_border_fill = document.blocks.iter().any(block_contains_table); + let list_contract = list_contract(document); + + if style.is_default() && !needs_styled_header { + let header = HEADER_TEMPLATE.to_string(); + return maybe_add_list_contract( + maybe_add_table_border_fill(header, needs_table_border_fill), + list_contract, + ); } if style.is_default() { - let header = HEADER_TEMPLATE.replace( - CHAR_PROPERTIES_COUNT_NEEDLE, - "", - ); - return header.replace( - CHAR_PROPERTIES_NEEDLE, - &format!("{}{}", legacy_additional_char_properties(), CHAR_PROPERTIES_NEEDLE), + let mut header = HEADER_TEMPLATE.to_string(); + if needs_styled_header { + header = header.replace( + CHAR_PROPERTIES_COUNT_NEEDLE, + "", + ); + header = header.replace( + CHAR_PROPERTIES_NEEDLE, + &format!( + "{}{}", + legacy_additional_char_properties(), + CHAR_PROPERTIES_NEEDLE + ), + ); + } + return maybe_add_list_contract( + maybe_add_table_border_fill(header, needs_table_border_fill), + list_contract, ); } @@ -41,14 +112,21 @@ pub fn build_header_xml(document: &Document, style: &ResolvedHwpxStyle) -> Strin header = apply_font_faces(&header, style); header = apply_body_style(&header, style); header = apply_paragraph_alignment(&header, style); - - let header = header.replace( + header = header.replace( CHAR_PROPERTIES_COUNT_NEEDLE, "", ); - header.replace( + header = header.replace( CHAR_PROPERTIES_NEEDLE, - &format!("{}{}", additional_char_properties(style), CHAR_PROPERTIES_NEEDLE), + &format!( + "{}{}", + additional_char_properties(style), + CHAR_PROPERTIES_NEEDLE + ), + ); + maybe_add_list_contract( + maybe_add_table_border_fill(header, needs_table_border_fill), + list_contract, ) } @@ -65,6 +143,98 @@ fn block_requires_styled_header(block: &Block) -> bool { } } +fn block_contains_table(block: &Block) -> bool { + match block { + Block::Table(_) => true, + Block::BlockQuote(blocks) => blocks.iter().any(block_contains_table), + Block::Paragraph(_) + | Block::Heading { .. } + | Block::CodeBlock { .. } + | Block::List(_) + | Block::ThematicBreak => false, + } +} + +fn list_contract(document: &Document) -> ListContract { + let ordered_depth = document + .blocks + .iter() + .map(block_ordered_list_depth) + .max() + .unwrap_or(0); + if ordered_depth > 0 { + return if ordered_depth > 1 { + ListContract::OrderedNestedDepth2 + } else { + ListContract::OrderedSingleLevel + }; + } + + let unordered_depth = document + .blocks + .iter() + .map(block_unordered_list_depth) + .max() + .unwrap_or(0); + if unordered_depth > 0 { + ListContract::Unordered(unordered_depth) + } else { + ListContract::None + } +} + +fn block_unordered_list_depth(block: &Block) -> u8 { + match block { + Block::List(list) if !list.ordered => { + let nested_depth = list + .items + .iter() + .flat_map(|item| item.blocks.iter()) + .map(block_unordered_list_depth) + .max() + .unwrap_or(0); + 1 + nested_depth + } + Block::BlockQuote(blocks) => blocks + .iter() + .map(block_unordered_list_depth) + .max() + .unwrap_or(0), + Block::Paragraph(_) + | Block::Heading { .. } + | Block::CodeBlock { .. } + | Block::Table(_) + | Block::ThematicBreak + | Block::List(_) => 0, + } +} + +fn block_ordered_list_depth(block: &Block) -> u8 { + match block { + Block::List(list) if list.ordered => { + let nested_depth = list + .items + .iter() + .flat_map(|item| item.blocks.iter()) + .map(block_ordered_list_depth) + .max() + .unwrap_or(0); + 1 + nested_depth + } + Block::BlockQuote(blocks) => blocks + .iter() + .map(block_ordered_list_depth) + .max() + .unwrap_or(0), + Block::Paragraph(_) + | Block::Heading { .. } + | Block::CodeBlock { .. } + | Block::Table(_) + | Block::ThematicBreak + | Block::List(_) => 0, + } +} + fn inline_slice_requires_styled_header(inlines: &[Inline]) -> bool { inlines.iter().any(inline_requires_styled_header) } @@ -83,7 +253,10 @@ fn inline_requires_styled_header(inline: &Inline) -> bool { fn apply_font_faces(header: &str, style: &ResolvedHwpxStyle) -> String { header - .replace(BODY_FONT_FACE, &format!("face=\"{}\"", escape_attr(&style.body_font))) + .replace( + BODY_FONT_FACE, + &format!("face=\"{}\"", escape_attr(&style.body_font)), + ) .replace( HEADING_FONT_FACE, &format!("face=\"{}\"", escape_attr(&style.heading_font)), @@ -220,14 +393,86 @@ fn apply_paragraph_alignment(header: &str, style: &ResolvedHwpxStyle) -> String fn additional_char_properties(style: &ResolvedHwpxStyle) -> String { [ - char_property_xml(CharPrSpec { id: 7, height: style.body_font_size, text_color: &style.text_color, font_ref: 0, bold: true, italic: false, underline: false, spacing: None }), - char_property_xml(CharPrSpec { id: 8, height: style.body_font_size, text_color: &style.text_color, font_ref: 0, bold: false, italic: true, underline: false, spacing: None }), - char_property_xml(CharPrSpec { id: 9, height: style.body_font_size, text_color: "#333333", font_ref: 0, bold: false, italic: false, underline: false, spacing: Some(-5) }), - char_property_xml(CharPrSpec { id: 10, height: style.heading_sizes[0], text_color: &style.heading_colors[0], font_ref: 1, bold: true, italic: false, underline: false, spacing: None }), - char_property_xml(CharPrSpec { id: 11, height: style.heading_sizes[1], text_color: &style.heading_colors[1], font_ref: 1, bold: true, italic: false, underline: false, spacing: None }), - char_property_xml(CharPrSpec { id: 12, height: style.heading_sizes[2], text_color: &style.heading_colors[2], font_ref: 1, bold: true, italic: false, underline: false, spacing: None }), - char_property_xml(CharPrSpec { id: 13, height: style.heading_sizes[3], text_color: &style.heading_colors[3], font_ref: 1, bold: true, italic: false, underline: false, spacing: None }), - char_property_xml(CharPrSpec { id: 14, height: style.body_font_size, text_color: &style.link_color, font_ref: 0, bold: false, italic: false, underline: true, spacing: None }), + char_property_xml(CharPrSpec { + id: 7, + height: style.body_font_size, + text_color: &style.text_color, + font_ref: 0, + bold: true, + italic: false, + underline: false, + spacing: None, + }), + char_property_xml(CharPrSpec { + id: 8, + height: style.body_font_size, + text_color: &style.text_color, + font_ref: 0, + bold: false, + italic: true, + underline: false, + spacing: None, + }), + char_property_xml(CharPrSpec { + id: 9, + height: style.body_font_size, + text_color: "#333333", + font_ref: 0, + bold: false, + italic: false, + underline: false, + spacing: Some(-5), + }), + char_property_xml(CharPrSpec { + id: 10, + height: style.heading_sizes[0], + text_color: &style.heading_colors[0], + font_ref: 1, + bold: true, + italic: false, + underline: false, + spacing: None, + }), + char_property_xml(CharPrSpec { + id: 11, + height: style.heading_sizes[1], + text_color: &style.heading_colors[1], + font_ref: 1, + bold: true, + italic: false, + underline: false, + spacing: None, + }), + char_property_xml(CharPrSpec { + id: 12, + height: style.heading_sizes[2], + text_color: &style.heading_colors[2], + font_ref: 1, + bold: true, + italic: false, + underline: false, + spacing: None, + }), + char_property_xml(CharPrSpec { + id: 13, + height: style.heading_sizes[3], + text_color: &style.heading_colors[3], + font_ref: 1, + bold: true, + italic: false, + underline: false, + spacing: None, + }), + char_property_xml(CharPrSpec { + id: 14, + height: style.body_font_size, + text_color: &style.link_color, + font_ref: 0, + bold: false, + italic: false, + underline: true, + spacing: None, + }), ] .join("") } @@ -318,3 +563,146 @@ fn update_para_align(xml: &str, id: u32, align: &str) -> String { updated.push_str(&xml[end..]); updated } + +fn maybe_add_table_border_fill(header: String, needs_table_border_fill: bool) -> String { + if !needs_table_border_fill || header.contains("") + .replace( + BORDER_FILLS_NEEDLE, + &format!("{TABLE_BORDER_FILL_XML}{BORDER_FILLS_NEEDLE}"), + ) +} + +fn maybe_add_list_contract(header: String, contract: ListContract) -> String { + match contract { + ListContract::None => header, + ListContract::Unordered(list_depth) => { + maybe_add_unordered_list_contract(header, list_depth) + } + ListContract::OrderedSingleLevel => maybe_add_ordered_list_contract(header, false), + ListContract::OrderedNestedDepth2 => maybe_add_ordered_list_contract(header, true), + } +} + +fn maybe_add_unordered_list_contract(header: String, list_depth: u8) -> String { + if header.contains(" 1 { 21 } else { 20 }; + let list_para_properties = if list_depth > 1 { + format!("{LIST_LEVEL1_PARA_PR_XML}{LIST_LEVEL2_PARA_PR_XML}") + } else { + LIST_LEVEL1_PARA_PR_XML.to_string() + }; + + header + .replacen( + NUMBERINGS_NEEDLE, + &format!("{NUMBERINGS_NEEDLE}{LIST_BULLETS_XML}"), + 1, + ) + .replacen( + PARA_PROPERTIES_COUNT_NEEDLE, + &format!(""), + 1, + ) + .replacen( + PARA_PROPERTIES_NEEDLE, + &format!("{list_para_properties}{PARA_PROPERTIES_NEEDLE}"), + 1, + ) +} + +fn maybe_add_ordered_list_contract(header: String, nested: bool) -> String { + if header.contains(""), + 1, + ) + .replacen( + NUMBERINGS_NEEDLE, + &format!("{added_numberings}{NUMBERINGS_NEEDLE}"), + 1, + ) + .replacen( + PARA_PROPERTIES_COUNT_NEEDLE, + &format!(""), + 1, + ) + .replacen( + PARA_PROPERTIES_NEEDLE, + &format!("{para_properties}{PARA_PROPERTIES_NEEDLE}"), + 1, + ) +} + +fn ordered_numbering_xml(id: u32, start: u32) -> String { + format!( + concat!( + "", + "^1.", + "^2.", + "^3)", + "^4)", + "(^5)", + "(^6)", + "^7", + "^8", + "", + "", + "" + ), + id = id, + start = start, + ) +} + +fn ordered_para_pr_xml(id: u32, numbering_id_ref: u32, left_margin: u32) -> String { + format!( + concat!( + "", + "", + "", + "", + "", + "", + "", + "" + ), + id = id, + numbering_id_ref = numbering_id_ref, + left_margin = left_margin, + default_left_margin = left_margin * 2, + ) +} diff --git a/packages/core-rs/src/generators/hwpx/mod.rs b/packages/core-rs/src/generators/hwpx/mod.rs index 9211f13..ab837c5 100644 --- a/packages/core-rs/src/generators/hwpx/mod.rs +++ b/packages/core-rs/src/generators/hwpx/mod.rs @@ -10,6 +10,7 @@ use crate::generators::Generator; use crate::models::document::Document; use crate::utils::zip_archiver::{ArchiveEntry, zip_entries_in_order}; use style::ResolvedHwpxStyle; +use zip::CompressionMethod; const PREVIEW_IMAGE_BYTES: &[u8] = include_bytes!("assets/preview.png"); @@ -33,16 +34,52 @@ impl Generator for HwpxGenerator { let entries = vec![ ArchiveEntry::new_text("mimetype", package_xml::mimetype()), - ArchiveEntry::new_text("version.xml", package_xml::version_xml()), - ArchiveEntry::new_text("Contents/header.xml", header), - ArchiveEntry::new_text("Contents/section0.xml", section), - ArchiveEntry::new_text("Preview/PrvText.txt", preview_text), - ArchiveEntry::new_text("settings.xml", package_xml::settings_xml()), + ArchiveEntry::new_text_with_compression( + "version.xml", + package_xml::version_xml(), + CompressionMethod::Deflated, + ), + ArchiveEntry::new_text_with_compression( + "Contents/header.xml", + header, + CompressionMethod::Deflated, + ), + ArchiveEntry::new_text_with_compression( + "Contents/section0.xml", + section, + CompressionMethod::Deflated, + ), + ArchiveEntry::new_text_with_compression( + "Preview/PrvText.txt", + preview_text, + CompressionMethod::Deflated, + ), + ArchiveEntry::new_text_with_compression( + "settings.xml", + package_xml::settings_xml(), + CompressionMethod::Deflated, + ), ArchiveEntry::new_bytes("Preview/PrvImage.png", PREVIEW_IMAGE_BYTES), - ArchiveEntry::new_text("META-INF/container.rdf", package_xml::container_rdf_xml()), - ArchiveEntry::new_text("Contents/content.hpf", content_hpf), - ArchiveEntry::new_text("META-INF/container.xml", package_xml::container_xml()), - ArchiveEntry::new_text("META-INF/manifest.xml", package_xml::manifest_xml()), + ArchiveEntry::new_text_with_compression( + "META-INF/container.rdf", + package_xml::container_rdf_xml(), + CompressionMethod::Deflated, + ), + ArchiveEntry::new_text_with_compression( + "Contents/content.hpf", + content_hpf, + CompressionMethod::Deflated, + ), + ArchiveEntry::new_text_with_compression( + "META-INF/container.xml", + package_xml::container_xml(), + CompressionMethod::Deflated, + ), + ArchiveEntry::new_text_with_compression( + "META-INF/manifest.xml", + package_xml::manifest_xml(), + CompressionMethod::Deflated, + ), ]; zip_entries_in_order(&entries) diff --git a/packages/core-rs/src/generators/hwpx/package_xml.rs b/packages/core-rs/src/generators/hwpx/package_xml.rs index 11f3ec1..b3e3365 100644 --- a/packages/core-rs/src/generators/hwpx/package_xml.rs +++ b/packages/core-rs/src/generators/hwpx/package_xml.rs @@ -1,3 +1,20 @@ +use std::cell::Cell; + +const SETTINGS_TEMPLATE: &str = include_str!("reference/paragraph-only/settings.xml"); + +#[derive(Clone, Copy)] +struct CaretPosition { + para_id_ref: usize, + pos: usize, +} + +thread_local! { + static SETTINGS_CARET_POS: Cell = const { Cell::new(CaretPosition { + para_id_ref: 0, + pos: 45, + }) }; +} + pub fn mimetype() -> &'static str { "application/hwp+zip" } @@ -7,7 +24,15 @@ pub fn version_xml() -> String { } pub fn settings_xml() -> String { - include_str!("reference/paragraph-only/settings.xml").to_string() + SETTINGS_CARET_POS.with(|value| { + let caret = value.get(); + SETTINGS_TEMPLATE + .replace( + "paraIDRef=\"0\"", + &format!("paraIDRef=\"{}\"", caret.para_id_ref), + ) + .replace("pos=\"45\"", &format!("pos=\"{}\"", caret.pos)) + }) } pub fn container_xml() -> String { @@ -21,3 +46,11 @@ pub fn container_rdf_xml() -> String { pub fn manifest_xml() -> String { include_str!("reference/paragraph-only/META-INF/manifest.xml").to_string() } + +pub fn set_settings_caret_pos(pos: usize) { + set_settings_caret(0, pos); +} + +pub fn set_settings_caret(para_id_ref: usize, pos: usize) { + SETTINGS_CARET_POS.with(|value| value.set(CaretPosition { para_id_ref, pos })); +} diff --git a/packages/core-rs/src/generators/hwpx/reference/paragraph-only/Contents/content.hpf b/packages/core-rs/src/generators/hwpx/reference/paragraph-only/Contents/content.hpf index e53c30e..7935b1c 100644 --- a/packages/core-rs/src/generators/hwpx/reference/paragraph-only/Contents/content.hpf +++ b/packages/core-rs/src/generators/hwpx/reference/paragraph-only/Contents/content.hpf @@ -1 +1 @@ -ko(주)한글과컴퓨터limchaesung2004-11-09T15:23:46Z2026-03-08T07:30:22Z2017년 8월 3일 목요일 오후 4:55:39 \ No newline at end of file +ko(주)한글과컴퓨터docxly2004-11-09T15:23:46Z2026-03-08T07:30:22Z2017년 8월 3일 목요일 오후 4:55:39 diff --git a/packages/core-rs/src/generators/hwpx/reference/paragraph-only/version.xml b/packages/core-rs/src/generators/hwpx/reference/paragraph-only/version.xml index 4ee1300..74aa313 100644 --- a/packages/core-rs/src/generators/hwpx/reference/paragraph-only/version.xml +++ b/packages/core-rs/src/generators/hwpx/reference/paragraph-only/version.xml @@ -1 +1 @@ - \ No newline at end of file + diff --git a/packages/core-rs/src/generators/hwpx/section_xml.rs b/packages/core-rs/src/generators/hwpx/section_xml.rs index 12392d0..2b5d96d 100644 --- a/packages/core-rs/src/generators/hwpx/section_xml.rs +++ b/packages/core-rs/src/generators/hwpx/section_xml.rs @@ -1,8 +1,10 @@ use crate::error::CoreRsError; -use crate::models::block::Block; +use crate::models::block::{Block, ListBlock, TableBlock}; use crate::models::document::Document; use crate::models::inline::Inline; use crate::utils::xml_helper::escape_text; + +use super::package_xml::{set_settings_caret, set_settings_caret_pos}; use super::style::ResolvedHwpxStyle; const NORMAL_CHAR_PR: u32 = 6; @@ -14,11 +16,56 @@ const H2_CHAR_PR: u32 = 11; const H3_CHAR_PR: u32 = 12; const H4_CHAR_PR: u32 = 13; const LINK_CHAR_PR: u32 = 14; +const LIST_LEVEL1_PARA_PR: u32 = 19; +const LIST_LEVEL2_PARA_PR: u32 = 20; +const LIST_LINESEG_INCREMENT: u32 = 2_300; +const LIST_LINESEG_VERT_SIZE: u32 = 1_000; +const LIST_LINESEG_TEXT_HEIGHT: u32 = 1_000; +const LIST_LINESEG_BASELINE: u32 = 850; +const LIST_LINESEG_SPACING: u32 = 600; +const LIST_LINESEG_FLAGS: u32 = 2_490_368; +const LIST_LEVEL1_HORZ_POS: u32 = 1_100; +const LIST_LEVEL1_HORZ_SIZE: u32 = 41_420; +const LIST_LEVEL2_HORZ_POS: u32 = 2_200; +const LIST_LEVEL2_HORZ_SIZE: u32 = 40_320; + +const TABLE_ID_SEED: u64 = 2_104_760_845; +const TABLE_WIDTH: u32 = 41_954; +const TABLE_OUTER_LINESEG_SIZE: u32 = 4_412; +const TABLE_OUTER_BASELINE: u32 = 3_750; +const TABLE_ROW_HEIGHT: u32 = 282; +const TABLE_HEIGHT_PER_ROW: u32 = 1_282; +const TABLE_CELL_INNER_WIDTH_DELTA: u32 = 1_021; +#[derive(Clone, Copy)] struct ParagraphStyle { para_pr: u32, style: u32, default_char_pr: u32, + line_seg: LineSegProfile, + list_semantic: ListSemantic, +} + +#[derive(Clone, Copy)] +enum LineSegProfile { + Standard, + ListLevel1, + ListLevel2, +} + +#[derive(Clone, Copy, PartialEq, Eq)] +enum ListSemantic { + None, + Unordered, + OrderedSingleLevel, + OrderedTopLevel, + OrderedNestedLevel, +} + +#[derive(Clone, Copy)] +enum OrderedListShape { + SingleLevel, + NestedDepth2, } struct RunSpec { @@ -26,15 +73,33 @@ struct RunSpec { text: String, } +enum SectionItem { + Paragraph(ParagraphStyle, Vec), + Table(TableSpec), +} + +struct TableSpec { + id: u64, + rows: Vec>, + col_widths: Vec, +} + pub fn build_section_xml( document: &Document, strict_mode: bool, _style: &ResolvedHwpxStyle, ) -> Result { - let paragraphs = collect_paragraph_specs(&document.blocks, strict_mode)?; + let ordered_shape = ordered_list_shape(&document.blocks); + let items = collect_section_items(&document.blocks, strict_mode, ordered_shape)?; let mut body = String::new(); - for (index, paragraph) in paragraphs.iter().enumerate() { - body.push_str(&render_paragraph(paragraph, index == 0, index)); + let mut list_vertpos = 0; + for (index, item) in items.iter().enumerate() { + body.push_str(&render_section_item( + item, + index == 0, + index, + &mut list_vertpos, + )); } Ok(format!( @@ -49,23 +114,66 @@ pub fn build_section_xml( } pub fn build_preview_text(document: &Document, strict_mode: bool) -> Result { - let paragraphs = visible_paragraphs(&document.blocks, strict_mode)?; - let text = paragraphs.join("\r\n"); + let ordered_shape = ordered_list_shape(&document.blocks); + let items = collect_section_items(&document.blocks, strict_mode, ordered_shape)?; + let has_table = items + .iter() + .any(|item| matches!(item, SectionItem::Table(_))); + let text = visible_paragraphs(&items).join("\r\n"); + if has_table { + set_settings_caret_pos(16); + } else if let Some((para_id_ref, pos)) = list_caret_position(&items) { + set_settings_caret(para_id_ref, pos); + } else { + set_settings_caret_pos(45); + } Ok(text) } -fn collect_paragraph_specs(blocks: &[Block], strict_mode: bool) -> Result)>, CoreRsError> { - let mut paragraphs = Vec::new(); +fn collect_section_items( + blocks: &[Block], + strict_mode: bool, + ordered_shape: Option, +) -> Result, CoreRsError> { + let mut items = Vec::new(); + let mut next_table_id = TABLE_ID_SEED; + for block in blocks { match block { - Block::Paragraph(inlines) => paragraphs.push(( + Block::Paragraph(inlines) => items.push(SectionItem::Paragraph( normal_style(), render_runs(inlines, NORMAL_CHAR_PR, strict_mode)?, )), - Block::Heading { level, content } => paragraphs.push(( + Block::Heading { level, content } => items.push(SectionItem::Paragraph( heading_style(*level), render_runs(content, heading_default_char_pr(*level), strict_mode)?, )), + Block::List(list) => { + if list.ordered { + if strict_mode { + return Err(CoreRsError::UnsupportedFeature( + "HWPX approved baseline does not support ordered list".to_string(), + )); + } + if let Some(shape) = ordered_shape { + collect_ordered_list_items(list, strict_mode, 1, shape, &mut items)?; + } else { + for paragraph in flatten_block_to_paragraphs(block) { + items.push(plain_paragraph_item(paragraph)); + } + } + } else { + collect_unordered_list_items(list, strict_mode, 1, &mut items)?; + } + } + Block::Table(table) => { + items.push(SectionItem::Table(build_table_spec( + table, + strict_mode, + next_table_id, + )?)); + next_table_id += 1; + } unsupported => { if strict_mode { return Err(CoreRsError::UnsupportedFeature(format!( @@ -74,21 +182,180 @@ fn collect_paragraph_specs(blocks: &[Block], strict_mode: bool) -> Result, +) -> Result<(), CoreRsError> { + if depth > 2 { + if strict_mode { + return Err(CoreRsError::UnsupportedFeature( + "HWPX approved baseline supports nested unordered lists up to depth 2".to_string(), + )); + } + for paragraph in flatten_block_to_paragraphs(&Block::List(list.clone())) { + items.push(plain_paragraph_item(paragraph)); + } + return Ok(()); + } + + let list_style = unordered_list_style(depth); + for item in &list.items { + for block in &item.blocks { + match block { + Block::Paragraph(inlines) => { + let runs = render_runs(inlines, list_style.default_char_pr, strict_mode)?; + items.push(SectionItem::Paragraph(list_style, runs)); + } + Block::Heading { content, .. } => { + let runs = render_runs(content, list_style.default_char_pr, strict_mode)?; + items.push(SectionItem::Paragraph(list_style, runs)); + } + Block::List(child) if !child.ordered => { + collect_unordered_list_items(child, strict_mode, depth + 1, items)?; + } + nested_list @ Block::List(_) => { + if strict_mode { + return Err(CoreRsError::UnsupportedFeature( + "HWPX approved baseline does not support ordered list".to_string(), + )); + } + for paragraph in flatten_block_to_paragraphs(nested_list) { + items.push(plain_paragraph_item(paragraph)); + } + } + unsupported => { + if strict_mode { + return Err(CoreRsError::UnsupportedFeature(format!( + "HWPX approved baseline does not support {0} inside unordered list", + unsupported_name(unsupported) + ))); + } + for paragraph in flatten_block_to_paragraphs(unsupported) { + items.push(plain_paragraph_item(paragraph)); + } + } + } + } + } + + Ok(()) } -fn render_paragraph((style, runs): &(ParagraphStyle, Vec), is_first: bool, index: usize) -> String { +fn collect_ordered_list_items( + list: &ListBlock, + strict_mode: bool, + depth: u8, + shape: OrderedListShape, + items: &mut Vec, +) -> Result<(), CoreRsError> { + if depth > 2 { + for paragraph in flatten_block_to_paragraphs(&Block::List(list.clone())) { + items.push(plain_paragraph_item(paragraph)); + } + return Ok(()); + } + + let list_style = ordered_list_style(shape, depth); + for item in &list.items { + for block in &item.blocks { + match block { + Block::Paragraph(inlines) => { + let runs = render_runs(inlines, list_style.default_char_pr, strict_mode)?; + items.push(SectionItem::Paragraph(list_style, runs)); + } + Block::Heading { content, .. } => { + let runs = render_runs(content, list_style.default_char_pr, strict_mode)?; + items.push(SectionItem::Paragraph(list_style, runs)); + } + Block::List(child) if child.ordered => { + collect_ordered_list_items(child, strict_mode, depth + 1, shape, items)?; + } + nested_list @ Block::List(_) => { + for paragraph in flatten_block_to_paragraphs(nested_list) { + items.push(plain_paragraph_item(paragraph)); + } + } + unsupported => { + for paragraph in flatten_block_to_paragraphs(unsupported) { + items.push(plain_paragraph_item(paragraph)); + } + } + } + } + } + + Ok(()) +} + +fn build_table_spec( + table: &TableBlock, + strict_mode: bool, + id: u64, +) -> Result { + let mut rows = Vec::new(); + if !table.headers.is_empty() { + rows.push( + table + .headers + .iter() + .map(|cell| flatten_inline_children(cell, strict_mode)) + .collect::, _>>()?, + ); + } + for row in &table.rows { + rows.push( + row.cells + .iter() + .map(|cell| flatten_inline_children(cell, strict_mode)) + .collect::, _>>()?, + ); + } + + let col_count = rows.iter().map(Vec::len).max().unwrap_or(0); + let col_widths = equal_widths(col_count); + for row in &mut rows { + row.resize(col_count, String::new()); + } + + Ok(TableSpec { + id, + rows, + col_widths, + }) +} + +fn render_section_item( + item: &SectionItem, + is_first: bool, + index: usize, + list_vertpos: &mut u32, +) -> String { + match item { + SectionItem::Paragraph(style, runs) => { + render_paragraph(style, runs, is_first, index, list_vertpos) + } + SectionItem::Table(table) => render_table_paragraph(table, is_first, index), + } +} + +fn render_paragraph( + style: &ParagraphStyle, + runs: &[RunSpec], + is_first: bool, + index: usize, + list_vertpos: &mut u32, +) -> String { let id = 2_757_524_817u64 + index as u64; let mut xml = format!( "", @@ -98,28 +365,14 @@ fn render_paragraph((style, runs): &(ParagraphStyle, Vec), is_first: bo ); if is_first { - xml.push_str(&format!( - concat!( - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "" - ), - char_pr = style.default_char_pr, - )); + xml.push_str(&render_section_preamble_run(style.default_char_pr)); } if runs.is_empty() { - xml.push_str(&format!("", style.default_char_pr)); + xml.push_str(&format!( + "", + style.default_char_pr + )); } else { for run in runs { xml.push_str(&format!( @@ -130,10 +383,162 @@ fn render_paragraph((style, runs): &(ParagraphStyle, Vec), is_first: bo } } - xml.push_str(""); + xml.push_str(&render_lineseg(style.line_seg, list_vertpos)); + xml.push_str(""); + xml +} + +fn render_table_paragraph(table: &TableSpec, is_first: bool, index: usize) -> String { + let id = 2_757_524_817u64 + index as u64; + let mut xml = format!( + "", + id = id, + ); + + if is_first { + xml.push_str(&render_section_preamble_run(NORMAL_CHAR_PR)); + } + xml.push_str(""); + xml.push_str(&render_table_xml(table)); + xml.push_str(""); + xml.push_str(&render_table_lineseg()); + xml.push_str(""); + xml +} + +fn render_table_xml(table: &TableSpec) -> String { + let row_count = table.rows.len(); + let col_count = table.col_widths.len(); + let height = TABLE_HEIGHT_PER_ROW * row_count as u32; + + let mut xml = format!( + concat!( + "", + "", + "", + "", + "" + ), + id = table.id, + row_count = row_count, + col_count = col_count, + width = TABLE_WIDTH, + height = height, + ); + + for (row_index, row) in table.rows.iter().enumerate() { + xml.push_str(""); + for (col_index, cell) in row.iter().enumerate() { + xml.push_str(&render_table_cell( + cell, + row_index, + col_index, + table.col_widths[col_index], + )); + } + xml.push_str(""); + } + + xml.push_str(""); xml } +fn render_table_cell(text: &str, row_index: usize, col_index: usize, width: u32) -> String { + let inner_width = width.saturating_sub(TABLE_CELL_INNER_WIDTH_DELTA); + format!( + concat!( + "", + "", + "", + "{text}", + "{trailing_run}", + "", + "", + "", + "", + "", + "", + "" + ), + text = escape_text(text), + trailing_run = if col_index > 0 { + "" + } else { + "" + }, + inner_width = inner_width, + col_index = col_index, + row_index = row_index, + width = width, + height = TABLE_ROW_HEIGHT, + ) +} + +fn render_section_preamble_run(char_pr: u32) -> String { + format!( + "{body}", + char_pr = char_pr, + body = render_section_preamble_body(), + ) +} + +fn render_section_preamble_body() -> &'static str { + concat!( + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ) +} + +fn render_standard_lineseg() -> &'static str { + "" +} + +fn render_lineseg(style: LineSegProfile, list_vertpos: &mut u32) -> String { + match style { + LineSegProfile::Standard => render_standard_lineseg().to_string(), + LineSegProfile::ListLevel1 => { + render_list_lineseg(LIST_LEVEL1_HORZ_POS, LIST_LEVEL1_HORZ_SIZE, list_vertpos) + } + LineSegProfile::ListLevel2 => { + render_list_lineseg(LIST_LEVEL2_HORZ_POS, LIST_LEVEL2_HORZ_SIZE, list_vertpos) + } + } +} + +fn render_list_lineseg(horzpos: u32, horzsize: u32, list_vertpos: &mut u32) -> String { + let current_vertpos = *list_vertpos; + *list_vertpos += LIST_LINESEG_INCREMENT; + format!( + "", + vertpos = current_vertpos, + vertsize = LIST_LINESEG_VERT_SIZE, + textheight = LIST_LINESEG_TEXT_HEIGHT, + baseline = LIST_LINESEG_BASELINE, + spacing = LIST_LINESEG_SPACING, + horzpos = horzpos, + horzsize = horzsize, + flags = LIST_LINESEG_FLAGS, + ) +} + +fn render_table_lineseg() -> String { + format!( + "", + size = TABLE_OUTER_LINESEG_SIZE, + baseline = TABLE_OUTER_BASELINE, + ) +} + fn render_runs( inlines: &[Inline], default_char_pr: u32, @@ -143,7 +548,7 @@ fn render_runs( for inline in inlines { collect_inline_runs(inline, default_char_pr, strict_mode, &mut runs)?; } - merge_adjacent_runs(runs) + Ok(merge_adjacent_runs(runs)) } fn collect_inline_runs( @@ -192,10 +597,13 @@ fn collect_inline_runs( Ok(()) } -fn merge_adjacent_runs(runs: Vec) -> Result, CoreRsError> { - let mut merged: Vec = Vec::new(); +fn merge_adjacent_runs(runs: Vec) -> Vec { + let mut merged = Vec::new(); for run in runs { - if merged.last().is_some_and(|last| last.char_pr == run.char_pr) { + if merged + .last() + .is_some_and(|last: &RunSpec| last.char_pr == run.char_pr) + { if let Some(last) = merged.last_mut() { last.text.push_str(&run.text); } @@ -203,7 +611,7 @@ fn merge_adjacent_runs(runs: Vec) -> Result, CoreRsError> } merged.push(run); } - Ok(merged) + merged } fn push_run(runs: &mut Vec, char_pr: u32, text: String) { @@ -218,17 +626,104 @@ fn normal_style() -> ParagraphStyle { para_pr: 0, style: 0, default_char_pr: NORMAL_CHAR_PR, + line_seg: LineSegProfile::Standard, + list_semantic: ListSemantic::None, + } +} + +fn unordered_list_style(depth: u8) -> ParagraphStyle { + ParagraphStyle { + para_pr: if depth == 1 { + LIST_LEVEL1_PARA_PR + } else { + LIST_LEVEL2_PARA_PR + }, + style: 0, + default_char_pr: NORMAL_CHAR_PR, + line_seg: if depth == 1 { + LineSegProfile::ListLevel1 + } else { + LineSegProfile::ListLevel2 + }, + list_semantic: ListSemantic::Unordered, + } +} + +fn ordered_list_style(shape: OrderedListShape, depth: u8) -> ParagraphStyle { + match shape { + OrderedListShape::SingleLevel => ParagraphStyle { + para_pr: LIST_LEVEL1_PARA_PR, + style: 0, + default_char_pr: NORMAL_CHAR_PR, + line_seg: LineSegProfile::ListLevel1, + list_semantic: ListSemantic::OrderedSingleLevel, + }, + OrderedListShape::NestedDepth2 => { + if depth == 1 { + ParagraphStyle { + para_pr: LIST_LEVEL2_PARA_PR, + style: 0, + default_char_pr: NORMAL_CHAR_PR, + line_seg: LineSegProfile::ListLevel1, + list_semantic: ListSemantic::OrderedTopLevel, + } + } else { + ParagraphStyle { + para_pr: LIST_LEVEL1_PARA_PR, + style: 0, + default_char_pr: NORMAL_CHAR_PR, + line_seg: LineSegProfile::ListLevel2, + list_semantic: ListSemantic::OrderedNestedLevel, + } + } + } } } fn heading_style(level: u8) -> ParagraphStyle { match level { - 1 => ParagraphStyle { para_pr: 2, style: 2, default_char_pr: H1_CHAR_PR }, - 2 => ParagraphStyle { para_pr: 3, style: 3, default_char_pr: H2_CHAR_PR }, - 3 => ParagraphStyle { para_pr: 4, style: 4, default_char_pr: H3_CHAR_PR }, - 4 => ParagraphStyle { para_pr: 5, style: 5, default_char_pr: H4_CHAR_PR }, - 5 => ParagraphStyle { para_pr: 6, style: 6, default_char_pr: BOLD_CHAR_PR }, - _ => ParagraphStyle { para_pr: 7, style: 7, default_char_pr: BOLD_CHAR_PR }, + 1 => ParagraphStyle { + para_pr: 2, + style: 2, + default_char_pr: H1_CHAR_PR, + line_seg: LineSegProfile::Standard, + list_semantic: ListSemantic::None, + }, + 2 => ParagraphStyle { + para_pr: 3, + style: 3, + default_char_pr: H2_CHAR_PR, + line_seg: LineSegProfile::Standard, + list_semantic: ListSemantic::None, + }, + 3 => ParagraphStyle { + para_pr: 4, + style: 4, + default_char_pr: H3_CHAR_PR, + line_seg: LineSegProfile::Standard, + list_semantic: ListSemantic::None, + }, + 4 => ParagraphStyle { + para_pr: 5, + style: 5, + default_char_pr: H4_CHAR_PR, + line_seg: LineSegProfile::Standard, + list_semantic: ListSemantic::None, + }, + 5 => ParagraphStyle { + para_pr: 6, + style: 6, + default_char_pr: BOLD_CHAR_PR, + line_seg: LineSegProfile::Standard, + list_semantic: ListSemantic::None, + }, + _ => ParagraphStyle { + para_pr: 7, + style: 7, + default_char_pr: BOLD_CHAR_PR, + line_seg: LineSegProfile::Standard, + list_semantic: ListSemantic::None, + }, } } @@ -273,7 +768,10 @@ fn flatten_inline_children(inlines: &[Inline], strict_mode: bool) -> Result text.push_str(code), - Inline::Link { text: children, url } => { + Inline::Link { + text: children, + url, + } => { let label = flatten_inline_children(children, strict_mode)?; if label.trim().is_empty() { text.push_str(url); @@ -299,12 +797,26 @@ fn flatten_inline_children(inlines: &[Inline], strict_mode: bool) -> Result Result, CoreRsError> { - let paragraph_specs = collect_paragraph_specs(blocks, strict_mode)?; - Ok(paragraph_specs - .into_iter() - .map(|(_, runs)| runs.into_iter().map(|run| run.text).collect::()) - .collect()) +fn visible_paragraphs(items: &[SectionItem]) -> Vec { + let mut paragraphs = Vec::new(); + for item in items { + match item { + SectionItem::Paragraph(_, runs) => { + paragraphs.push(runs.iter().map(|run| run.text.as_str()).collect()) + } + SectionItem::Table(table) => { + for row in &table.rows { + paragraphs.push( + row.iter() + .map(|cell| format!("<{cell}>")) + .collect::>() + .join(""), + ); + } + } + } + } + paragraphs } fn flatten_block_to_paragraphs(block: &Block) -> Vec { @@ -340,7 +852,8 @@ fn flatten_block_to_paragraphs(block: &Block) -> Vec { let mut paragraphs = Vec::new(); if !table.headers.is_empty() { paragraphs.push( - table.headers + table + .headers .iter() .map(|cell| flatten_inline_children(cell, false).unwrap_or_default()) .collect::>() @@ -371,3 +884,112 @@ fn unsupported_name(block: &Block) -> &'static str { Block::ThematicBreak => "thematic break", } } + +fn plain_paragraph_item(text: String) -> SectionItem { + SectionItem::Paragraph( + normal_style(), + vec![RunSpec { + char_pr: NORMAL_CHAR_PR, + text, + }], + ) +} + +fn list_caret_position(items: &[SectionItem]) -> Option<(usize, usize)> { + let mut paragraph_index = 0; + let mut last_unordered_caret = None; + let mut ordered_carets = Vec::new(); + + for item in items { + match item { + SectionItem::Paragraph(style, runs) => { + let text = runs.iter().map(|run| run.text.as_str()).collect::(); + let caret = (paragraph_index, text.chars().count()); + match style.list_semantic { + ListSemantic::None => {} + ListSemantic::Unordered => last_unordered_caret = Some(caret), + ListSemantic::OrderedSingleLevel + | ListSemantic::OrderedTopLevel + | ListSemantic::OrderedNestedLevel => { + ordered_carets.push((style.list_semantic, caret)); + } + } + paragraph_index += 1; + } + SectionItem::Table(table) => { + paragraph_index += table.rows.len(); + } + } + } + + if !ordered_carets.is_empty() { + let only_single_level = ordered_carets + .iter() + .all(|(semantic, _)| *semantic == ListSemantic::OrderedSingleLevel); + if only_single_level && ordered_carets.len() > 1 { + return ordered_carets + .get(ordered_carets.len() - 2) + .map(|(_, caret)| *caret); + } + return ordered_carets.last().map(|(_, caret)| *caret); + } + + last_unordered_caret +} + +fn ordered_list_shape(blocks: &[Block]) -> Option { + match max_ordered_list_depth(blocks) { + 0 => None, + 1 => Some(OrderedListShape::SingleLevel), + 2 => Some(OrderedListShape::NestedDepth2), + _ => None, + } +} + +fn max_ordered_list_depth(blocks: &[Block]) -> u8 { + blocks + .iter() + .map(block_ordered_list_depth) + .max() + .unwrap_or(0) +} + +fn block_ordered_list_depth(block: &Block) -> u8 { + match block { + Block::List(list) if list.ordered => { + let nested_depth = list + .items + .iter() + .flat_map(|item| item.blocks.iter()) + .map(block_ordered_list_depth) + .max() + .unwrap_or(0); + 1 + nested_depth + } + Block::BlockQuote(blocks) => blocks + .iter() + .map(block_ordered_list_depth) + .max() + .unwrap_or(0), + Block::Paragraph(_) + | Block::Heading { .. } + | Block::CodeBlock { .. } + | Block::Table(_) + | Block::ThematicBreak + | Block::List(_) => 0, + } +} + +fn equal_widths(count: usize) -> Vec { + if count == 0 { + return Vec::new(); + } + + let base = TABLE_WIDTH / count as u32; + let remainder = TABLE_WIDTH % count as u32; + let mut widths = vec![base; count]; + if let Some(last) = widths.last_mut() { + *last += remainder; + } + widths +} diff --git a/packages/core-rs/src/generators/hwpx/style.rs b/packages/core-rs/src/generators/hwpx/style.rs index ecdfc77..5f5cf45 100644 --- a/packages/core-rs/src/generators/hwpx/style.rs +++ b/packages/core-rs/src/generators/hwpx/style.rs @@ -105,7 +105,10 @@ impl ResolvedHwpxStyle { .map(validate_and_normalize_color) .transpose()? .unwrap_or_else(|| DEFAULT_LINK_COLOR.to_string()), - paragraph_align: match options.style.paragraph_align.unwrap_or(HwpxParagraphAlign::Justify) + paragraph_align: match options + .style + .paragraph_align + .unwrap_or(HwpxParagraphAlign::Justify) { HwpxParagraphAlign::Left => "LEFT", HwpxParagraphAlign::Center => "CENTER", diff --git a/packages/core-rs/src/lib.rs b/packages/core-rs/src/lib.rs index 2916ca6..7a3da10 100644 --- a/packages/core-rs/src/lib.rs +++ b/packages/core-rs/src/lib.rs @@ -82,6 +82,46 @@ pub fn generate_docx(markdown: &str, options: DocxOptions) -> Result, Co } pub fn generate_hwpx(markdown: &str, options: HwpxOptions) -> Result, CoreRsError> { + let normalized_markdown; + let markdown = if options.strict_mode { + markdown + } else { + normalized_markdown = normalize_hwpx_markdown(markdown); + normalized_markdown.as_str() + }; let document = MarkdownParser::new(options.strict_mode).parse(markdown)?; HwpxGenerator::new(options).generate(&document) } + +fn normalize_hwpx_markdown(markdown: &str) -> String { + let mut normalized = String::with_capacity(markdown.len()); + for segment in markdown.split_inclusive('\n') { + let (line, newline) = if let Some(line) = segment.strip_suffix('\n') { + (line, "\n") + } else { + (segment, "") + }; + + let line = if line.starts_with(" ") && is_ordered_list_marker(&line[2..]) { + format!(" {line}") + } else { + line.to_string() + }; + + normalized.push_str(&line); + normalized.push_str(newline); + } + normalized +} + +fn is_ordered_list_marker(value: &str) -> bool { + let digits = value + .bytes() + .take_while(|byte| byte.is_ascii_digit()) + .count(); + digits > 0 + && value + .as_bytes() + .get(digits..digits + 2) + .is_some_and(|suffix| suffix == b". ") +} diff --git a/packages/core-rs/src/models/inline.rs b/packages/core-rs/src/models/inline.rs index 14914cb..0d156bc 100644 --- a/packages/core-rs/src/models/inline.rs +++ b/packages/core-rs/src/models/inline.rs @@ -4,10 +4,7 @@ pub enum Inline { Emphasis(Vec), Strong(Vec), Code(String), - Link { - text: Vec, - url: String, - }, + Link { text: Vec, url: String }, Image(ImageData), HardBreak, } diff --git a/packages/core-rs/src/parser/markdown.rs b/packages/core-rs/src/parser/markdown.rs index d5650a6..317900d 100644 --- a/packages/core-rs/src/parser/markdown.rs +++ b/packages/core-rs/src/parser/markdown.rs @@ -1,18 +1,22 @@ mod data_image; -mod events; -mod push; mod state; #[cfg(test)] mod tests; -use pulldown_cmark::{Event, Options, Parser, Tag}; +use pulldown_cmark::{Event, Options, Parser, Tag, TagEnd}; -use self::state::{current_list_depth, unsupported_block_feature}; +use self::data_image::{DataImageError, image_fallback_text, parse_data_uri}; +use self::state::{ + Container, InlineContext, append_fallback_separator, code_block_language, current_list_depth, + current_table_mut, empty_to_none, flatten_inline_text, heading_level, push_inline_merged, + table_alignment, trim_trailing_fallback_whitespace, unsupported_block_feature, + unsupported_block_nesting_end, unsupported_block_nesting_tag, +}; use crate::error::CoreRsError; -use crate::models::block::Block; +use crate::models::block::{Block, ListBlock, TableBlock, TableRow}; use crate::models::document::Document; -use crate::models::inline::Inline; +use crate::models::inline::{ImageData, Inline}; const MAX_TOTAL_DATA_URI_IMAGE_BYTES: usize = 16 * 1024 * 1024; @@ -163,4 +167,539 @@ impl MarkdownParser { Ok(Document { blocks }) } + fn handle_start( + &self, + tag: Tag<'_>, + containers: &mut Vec, + inline_stack: &mut Vec, + ) -> Result<(), CoreRsError> { + match tag { + Tag::Paragraph => containers.push(Container::Paragraph(Vec::new())), + Tag::Heading { level, .. } => containers.push(Container::Heading { + level: heading_level(level), + content: Vec::new(), + }), + Tag::BlockQuote(_) => containers.push(Container::BlockQuote(Vec::new())), + Tag::CodeBlock(kind) => containers.push(Container::CodeBlock { + language: code_block_language(kind), + code: String::new(), + }), + Tag::List(start) => containers.push(Container::List(ListBlock { + ordered: start.is_some(), + start_index: start.unwrap_or(1), + items: Vec::new(), + })), + Tag::Item => containers.push(Container::ListItem(crate::models::block::ListItem { + blocks: Vec::new(), + })), + Tag::Table(alignments) => containers.push(Container::Table(state::TableBuilder { + aligns: alignments.into_iter().map(table_alignment).collect(), + headers: Vec::new(), + rows: Vec::new(), + })), + Tag::TableHead => containers.push(Container::TableHead(Vec::new())), + Tag::TableRow => containers.push(Container::TableRow(state::TableRowBuilder { + cells: Vec::new(), + })), + Tag::TableCell => containers.push(Container::TableCell(Vec::new())), + Tag::Emphasis => inline_stack.push(InlineContext::Emphasis(Vec::new())), + Tag::Strong => inline_stack.push(InlineContext::Strong(Vec::new())), + Tag::Link { + dest_url, title, .. + } => inline_stack.push(InlineContext::Link { + url: dest_url.into_string(), + title: empty_to_none(title.into_string()), + content: Vec::new(), + }), + Tag::Image { + dest_url, title, .. + } => inline_stack.push(InlineContext::Image { + url: dest_url.into_string(), + title: empty_to_none(title.into_string()), + alt: Vec::new(), + }), + Tag::DefinitionList + | Tag::DefinitionListTitle + | Tag::DefinitionListDefinition + | Tag::MetadataBlock(_) + | Tag::Strikethrough + | Tag::HtmlBlock + | Tag::FootnoteDefinition(_) => { + if self.strict_mode { + return Err(CoreRsError::UnsupportedFeature( + "extended markdown syntax".to_string(), + )); + } + } + } + + Ok(()) + } + + fn handle_end( + &self, + tag: TagEnd, + blocks: &mut Vec, + containers: &mut Vec, + inline_stack: &mut Vec, + total_embedded_image_bytes: &mut usize, + ) -> Result<(), CoreRsError> { + match tag { + TagEnd::Paragraph => { + let Some(Container::Paragraph(content)) = containers.pop() else { + return Err(CoreRsError::InvalidMarkdown( + "paragraph close without open".to_string(), + )); + }; + self.push_block(Block::Paragraph(content), blocks, containers)?; + } + TagEnd::Heading(_) => { + let Some(Container::Heading { level, content }) = containers.pop() else { + return Err(CoreRsError::InvalidMarkdown( + "heading close without open".to_string(), + )); + }; + self.push_block(Block::Heading { level, content }, blocks, containers)?; + } + TagEnd::BlockQuote(_) => { + let Some(Container::BlockQuote(quoted)) = containers.pop() else { + return Err(CoreRsError::InvalidMarkdown( + "blockquote close without open".to_string(), + )); + }; + self.push_block(Block::BlockQuote(quoted), blocks, containers)?; + } + TagEnd::CodeBlock => { + let Some(Container::CodeBlock { language, code }) = containers.pop() else { + return Err(CoreRsError::InvalidMarkdown( + "code block close without open".to_string(), + )); + }; + self.push_block(Block::CodeBlock { language, code }, blocks, containers)?; + } + TagEnd::List(_) => { + let Some(Container::List(list)) = containers.pop() else { + return Err(CoreRsError::InvalidMarkdown( + "list close without open".to_string(), + )); + }; + self.push_block(Block::List(list), blocks, containers)?; + } + TagEnd::Item => { + let Some(Container::ListItem(item)) = containers.pop() else { + return Err(CoreRsError::InvalidMarkdown( + "list item close without open".to_string(), + )); + }; + let Some(Container::List(list)) = containers.last_mut() else { + return Err(CoreRsError::InvalidMarkdown( + "list item close without list".to_string(), + )); + }; + list.items.push(item); + } + TagEnd::Table => { + let Some(Container::Table(table)) = containers.pop() else { + return Err(CoreRsError::InvalidMarkdown( + "table close without open".to_string(), + )); + }; + self.push_block( + Block::Table(TableBlock { + aligns: table.aligns, + headers: table.headers, + rows: table.rows, + }), + blocks, + containers, + )?; + } + TagEnd::TableHead => { + let Some(Container::TableHead(headers)) = containers.pop() else { + return Err(CoreRsError::InvalidMarkdown( + "table head close without open".to_string(), + )); + }; + current_table_mut(containers)?.headers = headers; + } + TagEnd::TableRow => { + let Some(Container::TableRow(row)) = containers.pop() else { + return Err(CoreRsError::InvalidMarkdown( + "table row close without open".to_string(), + )); + }; + current_table_mut(containers)? + .rows + .push(TableRow { cells: row.cells }); + } + TagEnd::TableCell => { + let Some(Container::TableCell(cell)) = containers.pop() else { + return Err(CoreRsError::InvalidMarkdown( + "table cell close without open".to_string(), + )); + }; + match containers.last_mut() { + Some(Container::TableRow(row)) => row.cells.push(cell), + Some(Container::TableHead(headers)) => headers.push(cell), + _ => { + return Err(CoreRsError::InvalidMarkdown( + "table cell close without row".to_string(), + )); + } + } + } + TagEnd::Emphasis => { + let Some(InlineContext::Emphasis(content)) = inline_stack.pop() else { + return Err(CoreRsError::InvalidMarkdown( + "emphasis close without open".to_string(), + )); + }; + self.push_inline_or_code( + Inline::Emphasis(content), + None, + blocks, + containers, + inline_stack, + )?; + } + TagEnd::Strong => { + let Some(InlineContext::Strong(content)) = inline_stack.pop() else { + return Err(CoreRsError::InvalidMarkdown( + "strong close without open".to_string(), + )); + }; + self.push_inline_or_code( + Inline::Strong(content), + None, + blocks, + containers, + inline_stack, + )?; + } + TagEnd::Link => { + let Some(InlineContext::Link { + url, + title, + content, + }) = inline_stack.pop() + else { + return Err(CoreRsError::InvalidMarkdown( + "link close without open".to_string(), + )); + }; + let _ = title; + match validate_link_url(&url) { + Ok(()) => { + self.push_inline_or_code( + Inline::Link { text: content, url }, + None, + blocks, + containers, + inline_stack, + )?; + } + Err(_) if !self.strict_mode => { + let fallback_text = flatten_inline_text(&content); + let fallback_text = if fallback_text.is_empty() { + url + } else { + fallback_text + }; + self.push_fallback_text(fallback_text, blocks, containers, inline_stack)?; + } + Err(error) => return Err(error), + } + } + TagEnd::Image => { + let Some(InlineContext::Image { url, title, alt }) = inline_stack.pop() else { + return Err(CoreRsError::InvalidMarkdown( + "image close without open".to_string(), + )); + }; + let alt_text = flatten_inline_text(&alt); + match parse_data_uri(&url) { + Ok((mime_type, extension, data)) => { + if total_embedded_image_bytes.saturating_add(data.len()) + > MAX_TOTAL_DATA_URI_IMAGE_BYTES + { + let error = DataImageError::TooLarge { + message: format!( + "total embedded image bytes exceed maximum supported size of {MAX_TOTAL_DATA_URI_IMAGE_BYTES} bytes" + ), + }; + if self.strict_mode { + return Err(error.into_core_error()); + } + self.push_fallback_text( + image_fallback_text(&alt_text, &url, error.fallback_kind()), + blocks, + containers, + inline_stack, + )?; + } else { + *total_embedded_image_bytes += data.len(); + self.push_inline_or_code( + Inline::Image(ImageData { + alt: alt_text, + title, + mime_type, + extension, + data, + }), + None, + blocks, + containers, + inline_stack, + )?; + } + } + Err(error) if !self.strict_mode => { + self.push_fallback_text( + image_fallback_text(&alt_text, &url, error.fallback_kind()), + blocks, + containers, + inline_stack, + )?; + } + Err(error) => return Err(error.into_core_error()), + } + } + TagEnd::DefinitionList + | TagEnd::DefinitionListTitle + | TagEnd::DefinitionListDefinition + | TagEnd::MetadataBlock(_) + | TagEnd::Strikethrough + | TagEnd::HtmlBlock + | TagEnd::FootnoteDefinition => {} + } + + Ok(()) + } + + fn handle_unsupported_event( + &self, + event: Event<'_>, + blocks: &mut Vec, + containers: &mut [Container], + unsupported_block_depth: &mut usize, + unsupported_block_content: &mut Vec, + ) -> Result<(), CoreRsError> { + match event { + Event::Start(tag) => { + if matches!(tag, Tag::Item) && !unsupported_block_content.is_empty() { + append_fallback_separator(unsupported_block_content); + } + if unsupported_block_nesting_tag(&tag) { + *unsupported_block_depth += 1; + } + } + Event::End(tag) => { + if unsupported_block_nesting_end(&tag) { + *unsupported_block_depth -= 1; + if *unsupported_block_depth == 0 { + trim_trailing_fallback_whitespace(unsupported_block_content); + if !unsupported_block_content.is_empty() { + self.push_block( + Block::Paragraph(std::mem::take(unsupported_block_content)), + blocks, + containers, + )?; + } + } + } + } + Event::Text(text) + | Event::Code(text) + | Event::Html(text) + | Event::InlineHtml(text) + | Event::InlineMath(text) + | Event::DisplayMath(text) => { + push_inline_merged(unsupported_block_content, Inline::Text(text.into_string())); + } + Event::SoftBreak => { + push_inline_merged(unsupported_block_content, Inline::Text(" ".to_string())); + } + Event::HardBreak => unsupported_block_content.push(Inline::HardBreak), + Event::Rule => { + push_inline_merged(unsupported_block_content, Inline::Text("---".to_string())); + } + Event::FootnoteReference(reference) => { + push_inline_merged( + unsupported_block_content, + Inline::Text(format!("[^{reference}]")), + ); + } + Event::TaskListMarker(checked) => { + push_inline_merged( + unsupported_block_content, + Inline::Text(if checked { + "[x] ".to_string() + } else { + "[ ] ".to_string() + }), + ); + } + } + + Ok(()) + } + + fn push_text( + &self, + text: String, + blocks: &mut Vec, + containers: &mut Vec, + inline_stack: &mut Vec, + ) -> Result<(), CoreRsError> { + self.push_inline_or_code(Inline::Text(text), None, blocks, containers, inline_stack) + } + + fn push_soft_break( + &self, + blocks: &mut Vec, + containers: &mut Vec, + inline_stack: &mut Vec, + ) -> Result<(), CoreRsError> { + self.push_inline_or_code( + Inline::Text(" ".to_string()), + Some('\n'), + blocks, + containers, + inline_stack, + ) + } + + fn push_hard_break( + &self, + blocks: &mut Vec, + containers: &mut Vec, + inline_stack: &mut Vec, + ) -> Result<(), CoreRsError> { + self.push_inline_or_code( + Inline::HardBreak, + Some('\n'), + blocks, + containers, + inline_stack, + ) + } + + fn push_inline_or_code( + &self, + inline: Inline, + code_break: Option, + blocks: &mut Vec, + containers: &mut Vec, + inline_stack: &mut Vec, + ) -> Result<(), CoreRsError> { + if let Some(last_inline) = inline_stack.last_mut() { + last_inline.push(inline); + return Ok(()); + } + + if let Some(Container::CodeBlock { code, .. }) = containers.last_mut() { + match (inline, code_break) { + (Inline::Text(text), _) | (Inline::Code(text), _) => code.push_str(&text), + (Inline::HardBreak, Some(break_char)) => code.push(break_char), + (Inline::HardBreak, None) => code.push('\n'), + _ => { + return Err(CoreRsError::InvalidMarkdown( + "invalid inline inside code block".to_string(), + )); + } + } + return Ok(()); + } + + if let Some( + Container::Paragraph(content) + | Container::TableCell(content) + | Container::Heading { content, .. }, + ) = containers.last_mut() + { + push_inline_merged(content, inline); + return Ok(()); + } + + match inline { + Inline::Text(text) if !text.is_empty() => { + self.push_fallback_text(text, blocks, containers, inline_stack) + } + Inline::HardBreak => Err(CoreRsError::InvalidMarkdown( + "hard break appeared outside a text container".to_string(), + )), + _ => Err(CoreRsError::InvalidMarkdown( + "inline content appeared outside a text container".to_string(), + )), + } + } + + fn push_fallback_text( + &self, + text: String, + blocks: &mut Vec, + containers: &mut Vec, + inline_stack: &mut Vec, + ) -> Result<(), CoreRsError> { + if text.is_empty() { + return Ok(()); + } + + if !inline_stack.is_empty() { + self.push_inline_or_code(Inline::Text(text), None, blocks, containers, inline_stack) + } else if let Some( + Container::Paragraph(_) + | Container::Heading { .. } + | Container::TableCell(_) + | Container::CodeBlock { .. }, + ) = containers.last() + { + self.push_inline_or_code(Inline::Text(text), None, blocks, containers, inline_stack) + } else { + self.push_block( + Block::Paragraph(vec![Inline::Text(text)]), + blocks, + containers, + ) + } + } + + fn push_block( + &self, + block: Block, + blocks: &mut Vec, + containers: &mut [Container], + ) -> Result<(), CoreRsError> { + if let Some(container) = containers.last_mut() { + match container { + Container::BlockQuote(quoted) => quoted.push(block), + Container::ListItem(item) => item.blocks.push(block), + _ => { + return Err(CoreRsError::InvalidMarkdown( + "block appeared in an invalid container".to_string(), + )); + } + } + } else { + blocks.push(block); + } + + Ok(()) + } +} + +fn validate_link_url(url: &str) -> Result<(), CoreRsError> { + let Some((scheme, _)) = url.split_once(':') else { + return Err(CoreRsError::UnsupportedFeature( + "relative link targets".to_string(), + )); + }; + let normalized = scheme.to_ascii_lowercase(); + if matches!(normalized.as_str(), "http" | "https" | "mailto") { + Ok(()) + } else { + Err(CoreRsError::UnsupportedFeature(format!( + "unsupported link scheme: {scheme}" + ))) + } } diff --git a/packages/core-rs/src/parser/markdown/data_image.rs b/packages/core-rs/src/parser/markdown/data_image.rs index ff15684..333f2b0 100644 --- a/packages/core-rs/src/parser/markdown/data_image.rs +++ b/packages/core-rs/src/parser/markdown/data_image.rs @@ -39,9 +39,7 @@ impl DataImageError { CoreRsError::UnsupportedFeature("non-base64 data URI image".to_string()) } Self::UnsupportedMime { mime_type } => { - CoreRsError::UnsupportedFeature(format!( - "unsupported image mime type: {mime_type}" - )) + CoreRsError::UnsupportedFeature(format!("unsupported image mime type: {mime_type}")) } Self::InvalidBase64 => { CoreRsError::InvalidMarkdown("invalid base64 image data".to_string()) @@ -51,11 +49,7 @@ impl DataImageError { } } -pub(super) fn image_fallback_text( - alt_text: &str, - url: &str, - kind: ImageFallbackKind, -) -> String { +pub(super) fn image_fallback_text(alt_text: &str, url: &str, kind: ImageFallbackKind) -> String { if alt_text.is_empty() { if url.starts_with("data:") { match kind { diff --git a/packages/core-rs/src/parser/markdown/state.rs b/packages/core-rs/src/parser/markdown/state.rs index ca36607..abe3011 100644 --- a/packages/core-rs/src/parser/markdown/state.rs +++ b/packages/core-rs/src/parser/markdown/state.rs @@ -7,9 +7,15 @@ use crate::models::inline::{ImageData, Inline}; #[derive(Debug)] pub(super) enum Container { Paragraph(Vec), - Heading { level: u8, content: Vec }, + Heading { + level: u8, + content: Vec, + }, BlockQuote(Vec), - CodeBlock { language: Option, code: String }, + CodeBlock { + language: Option, + code: String, + }, List(ListBlock), ListItem(ListItem), Table(TableBuilder), @@ -66,7 +72,9 @@ pub(super) fn push_inline_merged(content: &mut Vec, inline: Inline) { pub(super) fn append_fallback_separator(content: &mut Vec) { match content.last_mut() { - Some(Inline::Text(existing)) if !existing.ends_with(char::is_whitespace) => existing.push(' '), + Some(Inline::Text(existing)) if !existing.ends_with(char::is_whitespace) => { + existing.push(' ') + } Some(Inline::HardBreak) | None => {} _ => content.push(Inline::Text(" ".to_string())), } @@ -187,9 +195,5 @@ pub(super) fn heading_level(level: HeadingLevel) -> u8 { } pub(super) fn empty_to_none(value: String) -> Option { - if value.is_empty() { - None - } else { - Some(value) - } + if value.is_empty() { None } else { Some(value) } } diff --git a/packages/core-rs/src/parser/markdown/tests.rs b/packages/core-rs/src/parser/markdown/tests.rs index c5ec28c..8c75eef 100644 --- a/packages/core-rs/src/parser/markdown/tests.rs +++ b/packages/core-rs/src/parser/markdown/tests.rs @@ -1,14 +1,15 @@ -use base64::Engine; use super::MarkdownParser; use crate::error::CoreRsError; use crate::models::block::{Block, ListBlock, ListItem, TableAlignment, TableBlock, TableRow}; use crate::models::inline::{ImageData, Inline}; +use base64::Engine; const PNG_DATA_URI: &str = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+a7mQAAAAASUVORK5CYII="; #[test] fn parses_heading_and_paragraph() { - let markdown = "# Title\n\nHello *world* and **team** with `code` and [docs](https://example.com)."; + let markdown = + "# Title\n\nHello *world* and **team** with `code` and [docs](https://example.com)."; let document = MarkdownParser::new(true).parse(markdown).unwrap(); assert_eq!( @@ -44,7 +45,9 @@ fn parses_rich_blocks() { assert_eq!( document.blocks, vec![ - Block::BlockQuote(vec![Block::Paragraph(vec![Inline::Text("quote".to_string())])]), + Block::BlockQuote(vec![Block::Paragraph(vec![Inline::Text( + "quote".to_string() + )])]), Block::CodeBlock { language: Some("rust".to_string()), code: "let answer = 42;\n".to_string(), @@ -59,7 +62,9 @@ fn parses_rich_blocks() { ordered: false, start_index: 1, items: vec![ListItem { - blocks: vec![Block::Paragraph(vec![Inline::Text("two".to_string())])], + blocks: vec![Block::Paragraph(vec![Inline::Text( + "two".to_string() + )])], }], }), ], @@ -105,7 +110,9 @@ fn falls_back_for_html_when_not_strict() { assert_eq!( document.blocks, - vec![Block::Paragraph(vec![Inline::Text("raw".to_string())])] + vec![Block::Paragraph(vec![Inline::Text( + "raw".to_string() + )])] ); } @@ -195,8 +202,7 @@ fn falls_back_for_unsafe_link_scheme_when_not_strict() { #[test] fn errors_for_oversized_data_image_when_strict() { - let payload = base64::engine::general_purpose::STANDARD - .encode(vec![0_u8; 8 * 1024 * 1024 + 1]); + let payload = base64::engine::general_purpose::STANDARD.encode(vec![0_u8; 8 * 1024 * 1024 + 1]); let markdown = format!("![huge](data:image/png;base64,{payload})"); let error = MarkdownParser::new(true).parse(&markdown).unwrap_err(); diff --git a/packages/core-rs/src/utils/image_helper.rs b/packages/core-rs/src/utils/image_helper.rs index 3ee705d..455a2f8 100644 --- a/packages/core-rs/src/utils/image_helper.rs +++ b/packages/core-rs/src/utils/image_helper.rs @@ -15,7 +15,9 @@ pub(crate) fn image_dimensions(image: &ImageData) -> Result<(u32, u32), CoreRsEr fn png_dimensions(bytes: &[u8]) -> Result<(u32, u32), CoreRsError> { if bytes.len() < 24 || &bytes[..8] != b"\x89PNG\r\n\x1a\n" { - return Err(CoreRsError::InvalidMarkdown("invalid PNG image data".to_string())); + return Err(CoreRsError::InvalidMarkdown( + "invalid PNG image data".to_string(), + )); } let width = u32::from_be_bytes([bytes[16], bytes[17], bytes[18], bytes[19]]); let height = u32::from_be_bytes([bytes[20], bytes[21], bytes[22], bytes[23]]); @@ -24,7 +26,9 @@ fn png_dimensions(bytes: &[u8]) -> Result<(u32, u32), CoreRsError> { fn gif_dimensions(bytes: &[u8]) -> Result<(u32, u32), CoreRsError> { if bytes.len() < 10 || (!bytes.starts_with(b"GIF87a") && !bytes.starts_with(b"GIF89a")) { - return Err(CoreRsError::InvalidMarkdown("invalid GIF image data".to_string())); + return Err(CoreRsError::InvalidMarkdown( + "invalid GIF image data".to_string(), + )); } let width = u16::from_le_bytes([bytes[6], bytes[7]]) as u32; let height = u16::from_le_bytes([bytes[8], bytes[9]]) as u32; @@ -33,7 +37,9 @@ fn gif_dimensions(bytes: &[u8]) -> Result<(u32, u32), CoreRsError> { fn jpeg_dimensions(bytes: &[u8]) -> Result<(u32, u32), CoreRsError> { if bytes.len() < 4 || bytes[0] != 0xFF || bytes[1] != 0xD8 { - return Err(CoreRsError::InvalidMarkdown("invalid JPEG image data".to_string())); + return Err(CoreRsError::InvalidMarkdown( + "invalid JPEG image data".to_string(), + )); } let mut index = 2usize; @@ -58,7 +64,17 @@ fn jpeg_dimensions(bytes: &[u8]) -> Result<(u32, u32), CoreRsError> { } if matches!( marker, - 0xC0 | 0xC1 | 0xC2 | 0xC3 | 0xC5 | 0xC6 | 0xC7 | 0xC9 | 0xCA | 0xCB | 0xCD | 0xCE + 0xC0 | 0xC1 + | 0xC2 + | 0xC3 + | 0xC5 + | 0xC6 + | 0xC7 + | 0xC9 + | 0xCA + | 0xCB + | 0xCD + | 0xCE | 0xCF ) && length >= 7 { diff --git a/packages/core-rs/src/utils/xml_helper.rs b/packages/core-rs/src/utils/xml_helper.rs index 60651da..acbd3a7 100644 --- a/packages/core-rs/src/utils/xml_helper.rs +++ b/packages/core-rs/src/utils/xml_helper.rs @@ -14,7 +14,13 @@ pub fn escape_attr(input: &str) -> String { fn sanitize_xml_chars(input: &str) -> String { input .chars() - .map(|ch| if is_valid_xml_char(ch) { ch } else { '\u{FFFD}' }) + .map(|ch| { + if is_valid_xml_char(ch) { + ch + } else { + '\u{FFFD}' + } + }) .collect() } diff --git a/packages/core-rs/src/utils/zip_archiver.rs b/packages/core-rs/src/utils/zip_archiver.rs index bca6e4d..18a3c17 100644 --- a/packages/core-rs/src/utils/zip_archiver.rs +++ b/packages/core-rs/src/utils/zip_archiver.rs @@ -11,6 +11,7 @@ use crate::error::CoreRsError; pub struct ArchiveEntry { pub path: String, pub contents: Vec, + pub compression: CompressionMethod, } impl ArchiveEntry { @@ -18,6 +19,7 @@ impl ArchiveEntry { Self { path: path.into(), contents: contents.into().into_bytes(), + compression: CompressionMethod::Stored, } } @@ -25,6 +27,19 @@ impl ArchiveEntry { Self { path: path.into(), contents: contents.into(), + compression: CompressionMethod::Stored, + } + } + + pub fn new_text_with_compression( + path: impl Into, + contents: impl Into, + compression: CompressionMethod, + ) -> Self { + Self { + path: path.into(), + contents: contents.into().into_bytes(), + compression, } } } @@ -32,18 +47,17 @@ impl ArchiveEntry { pub fn zip_entries(entries: &[ArchiveEntry]) -> Result, CoreRsError> { let mut sorted_entries = entries.to_vec(); sorted_entries.sort_by(|left, right| left.path.cmp(&right.path)); - zip_entries_in_order(&sorted_entries) } pub fn zip_entries_in_order(entries: &[ArchiveEntry]) -> Result, CoreRsError> { let cursor = Cursor::new(Vec::new()); let mut writer = ZipWriter::new(cursor); - let options = SimpleFileOptions::default() - .compression_method(CompressionMethod::Stored) - .last_modified_time(DateTime::default()); for entry in entries { + let options = SimpleFileOptions::default() + .compression_method(entry.compression) + .last_modified_time(DateTime::default()); writer.start_file(&entry.path, options)?; writer.write_all(&entry.contents)?; } diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/README.md b/packages/core-rs/tests/fixtures/hwpx/approved/README.md index 4798332..9e51538 100644 --- a/packages/core-rs/tests/fixtures/hwpx/approved/README.md +++ b/packages/core-rs/tests/fixtures/hwpx/approved/README.md @@ -5,10 +5,15 @@ Only fixtures placed here are treated as CI/release-gate golden files. Current status: - `core-paragraph` is manually validated and approved. +- `blockquote-basic` is manually validated and approved. +- `code-block-basic` is manually validated and approved. - `core-heading` is manually validated and approved. - `core-inline-style` is manually validated and approved. - `core-link-text` is manually validated and approved. - `core-mixed` is manually validated and approved. +- `list-basic` is manually validated and approved. +- `list-nested-depth-2` is manually validated and approved. +- `table-basic` is manually validated and approved. - `style-typography` is manually validated and approved. - `style-centered-layout` is manually validated and approved. - `style-brand-color` is manually validated and approved. diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/blockquote-basic/expected/Contents/content.hpf b/packages/core-rs/tests/fixtures/hwpx/approved/blockquote-basic/expected/Contents/content.hpf new file mode 100644 index 0000000..b965bfe --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/blockquote-basic/expected/Contents/content.hpf @@ -0,0 +1 @@ +HWPX Blockquote Candidateko(주)한글과컴퓨터docxly2004-11-09T15:23:46Z2026-03-08T07:30:22Z2017년 8월 3일 목요일 오후 4:55:39 \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/blockquote-basic/expected/Contents/header.xml b/packages/core-rs/tests/fixtures/hwpx/approved/blockquote-basic/expected/Contents/header.xml new file mode 100644 index 0000000..0d6bf34 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/blockquote-basic/expected/Contents/header.xml @@ -0,0 +1 @@ +^1.^2.^3)^4)(^5)(^6)^7 \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/blockquote-basic/expected/Contents/section0.xml b/packages/core-rs/tests/fixtures/hwpx/approved/blockquote-basic/expected/Contents/section0.xml new file mode 100644 index 0000000..a930d4b --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/blockquote-basic/expected/Contents/section0.xml @@ -0,0 +1 @@ +Quoted paragraphAnother quoted line \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/blockquote-basic/expected/META-INF/container.rdf b/packages/core-rs/tests/fixtures/hwpx/approved/blockquote-basic/expected/META-INF/container.rdf new file mode 100644 index 0000000..8df472d --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/blockquote-basic/expected/META-INF/container.rdf @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/blockquote-basic/expected/META-INF/container.xml b/packages/core-rs/tests/fixtures/hwpx/approved/blockquote-basic/expected/META-INF/container.xml new file mode 100644 index 0000000..e0cc34c --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/blockquote-basic/expected/META-INF/container.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/blockquote-basic/expected/META-INF/manifest.xml b/packages/core-rs/tests/fixtures/hwpx/approved/blockquote-basic/expected/META-INF/manifest.xml new file mode 100644 index 0000000..5530619 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/blockquote-basic/expected/META-INF/manifest.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/blockquote-basic/expected/Preview/PrvImage.png.sha256 b/packages/core-rs/tests/fixtures/hwpx/approved/blockquote-basic/expected/Preview/PrvImage.png.sha256 new file mode 100644 index 0000000..21d6e53 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/blockquote-basic/expected/Preview/PrvImage.png.sha256 @@ -0,0 +1 @@ +a3cc714d6645caa3b0735b03d85d295b46cc486218b1d676fb74adcf4019df24 \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/blockquote-basic/expected/Preview/PrvText.txt b/packages/core-rs/tests/fixtures/hwpx/approved/blockquote-basic/expected/Preview/PrvText.txt new file mode 100644 index 0000000..66964c0 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/blockquote-basic/expected/Preview/PrvText.txt @@ -0,0 +1,2 @@ +Quoted paragraph +Another quoted line \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/blockquote-basic/expected/mimetype b/packages/core-rs/tests/fixtures/hwpx/approved/blockquote-basic/expected/mimetype new file mode 100644 index 0000000..24e7ded --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/blockquote-basic/expected/mimetype @@ -0,0 +1 @@ +application/hwp+zip \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/blockquote-basic/expected/settings.xml b/packages/core-rs/tests/fixtures/hwpx/approved/blockquote-basic/expected/settings.xml new file mode 100644 index 0000000..5d51824 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/blockquote-basic/expected/settings.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/blockquote-basic/expected/version.xml b/packages/core-rs/tests/fixtures/hwpx/approved/blockquote-basic/expected/version.xml new file mode 100644 index 0000000..549bb34 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/blockquote-basic/expected/version.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/blockquote-basic/fixture.toml b/packages/core-rs/tests/fixtures/hwpx/approved/blockquote-basic/fixture.toml new file mode 100644 index 0000000..84d9794 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/blockquote-basic/fixture.toml @@ -0,0 +1,4 @@ +title = "HWPX Blockquote Candidate" +author = "Fixture Author" +strict_mode = false +determinism = false diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/blockquote-basic/golden.hwpx b/packages/core-rs/tests/fixtures/hwpx/approved/blockquote-basic/golden.hwpx new file mode 100644 index 0000000000000000000000000000000000000000..d194a94222cdd9ee9007d78962df7d1c675bc552 GIT binary patch literal 27497 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 literal 0 HcmV?d00001 diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/blockquote-basic/hash.txt b/packages/core-rs/tests/fixtures/hwpx/approved/blockquote-basic/hash.txt new file mode 100644 index 0000000..aa41144 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/blockquote-basic/hash.txt @@ -0,0 +1 @@ +c575ac91d579379e8c48f3daa78565594d5de5bd311b9a09ec62da37aca4b8b4 \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/blockquote-basic/input.md b/packages/core-rs/tests/fixtures/hwpx/approved/blockquote-basic/input.md new file mode 100644 index 0000000..573d259 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/blockquote-basic/input.md @@ -0,0 +1,3 @@ +> Quoted paragraph +> +> Another quoted line \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/code-block-basic/expected/Contents/content.hpf b/packages/core-rs/tests/fixtures/hwpx/approved/code-block-basic/expected/Contents/content.hpf new file mode 100644 index 0000000..42279b3 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/code-block-basic/expected/Contents/content.hpf @@ -0,0 +1 @@ +HWPX Code Block Candidateko(주)한글과컴퓨터docxly2004-11-09T15:23:46Z2026-03-08T07:30:22Z2017년 8월 3일 목요일 오후 4:55:39 \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/code-block-basic/expected/Contents/header.xml b/packages/core-rs/tests/fixtures/hwpx/approved/code-block-basic/expected/Contents/header.xml new file mode 100644 index 0000000..0d6bf34 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/code-block-basic/expected/Contents/header.xml @@ -0,0 +1 @@ +^1.^2.^3)^4)(^5)(^6)^7 \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/code-block-basic/expected/Contents/section0.xml b/packages/core-rs/tests/fixtures/hwpx/approved/code-block-basic/expected/Contents/section0.xml new file mode 100644 index 0000000..c377ec7 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/code-block-basic/expected/Contents/section0.xml @@ -0,0 +1 @@ +fn main() { println!("hi");} \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/code-block-basic/expected/META-INF/container.rdf b/packages/core-rs/tests/fixtures/hwpx/approved/code-block-basic/expected/META-INF/container.rdf new file mode 100644 index 0000000..8df472d --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/code-block-basic/expected/META-INF/container.rdf @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/code-block-basic/expected/META-INF/container.xml b/packages/core-rs/tests/fixtures/hwpx/approved/code-block-basic/expected/META-INF/container.xml new file mode 100644 index 0000000..e0cc34c --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/code-block-basic/expected/META-INF/container.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/code-block-basic/expected/META-INF/manifest.xml b/packages/core-rs/tests/fixtures/hwpx/approved/code-block-basic/expected/META-INF/manifest.xml new file mode 100644 index 0000000..5530619 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/code-block-basic/expected/META-INF/manifest.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/code-block-basic/expected/Preview/PrvImage.png.sha256 b/packages/core-rs/tests/fixtures/hwpx/approved/code-block-basic/expected/Preview/PrvImage.png.sha256 new file mode 100644 index 0000000..21d6e53 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/code-block-basic/expected/Preview/PrvImage.png.sha256 @@ -0,0 +1 @@ +a3cc714d6645caa3b0735b03d85d295b46cc486218b1d676fb74adcf4019df24 \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/code-block-basic/expected/Preview/PrvText.txt b/packages/core-rs/tests/fixtures/hwpx/approved/code-block-basic/expected/Preview/PrvText.txt new file mode 100644 index 0000000..83164c6 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/code-block-basic/expected/Preview/PrvText.txt @@ -0,0 +1,3 @@ +fn main() { + println!("hi"); +} \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/code-block-basic/expected/mimetype b/packages/core-rs/tests/fixtures/hwpx/approved/code-block-basic/expected/mimetype new file mode 100644 index 0000000..24e7ded --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/code-block-basic/expected/mimetype @@ -0,0 +1 @@ +application/hwp+zip \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/code-block-basic/expected/settings.xml b/packages/core-rs/tests/fixtures/hwpx/approved/code-block-basic/expected/settings.xml new file mode 100644 index 0000000..5d51824 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/code-block-basic/expected/settings.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/code-block-basic/expected/version.xml b/packages/core-rs/tests/fixtures/hwpx/approved/code-block-basic/expected/version.xml new file mode 100644 index 0000000..549bb34 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/code-block-basic/expected/version.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/code-block-basic/fixture.toml b/packages/core-rs/tests/fixtures/hwpx/approved/code-block-basic/fixture.toml new file mode 100644 index 0000000..76bff88 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/code-block-basic/fixture.toml @@ -0,0 +1,4 @@ +title = "HWPX Code Block Candidate" +author = "Fixture Author" +strict_mode = false +determinism = false diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/code-block-basic/golden.hwpx b/packages/core-rs/tests/fixtures/hwpx/approved/code-block-basic/golden.hwpx new file mode 100644 index 0000000000000000000000000000000000000000..d684bf86879fc7aaad4d4530a8d7f82cddb90bcc GIT binary patch literal 27517 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 Heading Fixtureko(주)한글과컴퓨터limchaesung2004-11-09T15:23:46Z2026-03-08T07:30:22Z2017년 8월 3일 목요일 오후 4:55:39 \ No newline at end of file +HWPX Heading Fixtureko(주)한글과컴퓨터docxly2004-11-09T15:23:46Z2026-03-08T07:30:22Z2017년 8월 3일 목요일 오후 4:55:39 \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/core-heading/expected/Preview/PrvImage.png.sha256 b/packages/core-rs/tests/fixtures/hwpx/approved/core-heading/expected/Preview/PrvImage.png.sha256 index b52266d..21d6e53 100644 --- a/packages/core-rs/tests/fixtures/hwpx/approved/core-heading/expected/Preview/PrvImage.png.sha256 +++ b/packages/core-rs/tests/fixtures/hwpx/approved/core-heading/expected/Preview/PrvImage.png.sha256 @@ -1 +1 @@ -a3cc714d6645caa3b0735b03d85d295b46cc486218b1d676fb74adcf4019df24 +a3cc714d6645caa3b0735b03d85d295b46cc486218b1d676fb74adcf4019df24 \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/core-heading/expected/version.xml b/packages/core-rs/tests/fixtures/hwpx/approved/core-heading/expected/version.xml index 4ee1300..549bb34 100644 --- a/packages/core-rs/tests/fixtures/hwpx/approved/core-heading/expected/version.xml +++ b/packages/core-rs/tests/fixtures/hwpx/approved/core-heading/expected/version.xml @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/core-heading/golden.hwpx b/packages/core-rs/tests/fixtures/hwpx/approved/core-heading/golden.hwpx index 7912e9dc1bdeeea06b9bf42d38c4f5cfee639da9..3a6d98e788a2bd2c86e8ef2f7cc2641b9bb86de7 100644 GIT binary patch delta 6854 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 diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/core-heading/hash.txt b/packages/core-rs/tests/fixtures/hwpx/approved/core-heading/hash.txt index 8f587d6..fb46286 100644 --- a/packages/core-rs/tests/fixtures/hwpx/approved/core-heading/hash.txt +++ b/packages/core-rs/tests/fixtures/hwpx/approved/core-heading/hash.txt @@ -1 +1 @@ -921e69472efe98d12663e5eeb004eb8587a21768185879a4697439230f4846f3 +81c1148a0fdd8fd36f48e398562b870dd8a975f991b730a606ab43cfbb582b96 \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/core-inline-style/expected/Contents/content.hpf b/packages/core-rs/tests/fixtures/hwpx/approved/core-inline-style/expected/Contents/content.hpf index a5bee43..becd549 100644 --- a/packages/core-rs/tests/fixtures/hwpx/approved/core-inline-style/expected/Contents/content.hpf +++ b/packages/core-rs/tests/fixtures/hwpx/approved/core-inline-style/expected/Contents/content.hpf @@ -1 +1 @@ -HWPX Inline Fixtureko(주)한글과컴퓨터limchaesung2004-11-09T15:23:46Z2026-03-08T07:30:22Z2017년 8월 3일 목요일 오후 4:55:39 \ No newline at end of file +HWPX Inline Fixtureko(주)한글과컴퓨터docxly2004-11-09T15:23:46Z2026-03-08T07:30:22Z2017년 8월 3일 목요일 오후 4:55:39 \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/core-inline-style/expected/Preview/PrvImage.png.sha256 b/packages/core-rs/tests/fixtures/hwpx/approved/core-inline-style/expected/Preview/PrvImage.png.sha256 index b52266d..21d6e53 100644 --- a/packages/core-rs/tests/fixtures/hwpx/approved/core-inline-style/expected/Preview/PrvImage.png.sha256 +++ b/packages/core-rs/tests/fixtures/hwpx/approved/core-inline-style/expected/Preview/PrvImage.png.sha256 @@ -1 +1 @@ -a3cc714d6645caa3b0735b03d85d295b46cc486218b1d676fb74adcf4019df24 +a3cc714d6645caa3b0735b03d85d295b46cc486218b1d676fb74adcf4019df24 \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/core-inline-style/expected/version.xml b/packages/core-rs/tests/fixtures/hwpx/approved/core-inline-style/expected/version.xml index 4ee1300..549bb34 100644 --- a/packages/core-rs/tests/fixtures/hwpx/approved/core-inline-style/expected/version.xml +++ b/packages/core-rs/tests/fixtures/hwpx/approved/core-inline-style/expected/version.xml @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/core-inline-style/golden.hwpx b/packages/core-rs/tests/fixtures/hwpx/approved/core-inline-style/golden.hwpx index 84305d2d3156b0e72885f010e0fdf337fadb41db..55ef10ae1f7f575d59f3b430cfcfb08026b30754 100644 GIT binary patch delta 6852 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 diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/core-inline-style/hash.txt b/packages/core-rs/tests/fixtures/hwpx/approved/core-inline-style/hash.txt index 70f58be..28de60c 100644 --- a/packages/core-rs/tests/fixtures/hwpx/approved/core-inline-style/hash.txt +++ b/packages/core-rs/tests/fixtures/hwpx/approved/core-inline-style/hash.txt @@ -1 +1 @@ -3ae4edaafd49a6a08f44c175c7960cb7b7b1e3e62ed08d1cf938e5256bb1183f +6ebba80a3c8b9eb2c3813b374b643bc6d1eff9e06acf388d315073410253d2e3 \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/core-link-text/expected/Contents/content.hpf b/packages/core-rs/tests/fixtures/hwpx/approved/core-link-text/expected/Contents/content.hpf index 9138815..00a9390 100644 --- a/packages/core-rs/tests/fixtures/hwpx/approved/core-link-text/expected/Contents/content.hpf +++ b/packages/core-rs/tests/fixtures/hwpx/approved/core-link-text/expected/Contents/content.hpf @@ -1 +1 @@ -HWPX Link Fixtureko(주)한글과컴퓨터limchaesung2004-11-09T15:23:46Z2026-03-08T07:30:22Z2017년 8월 3일 목요일 오후 4:55:39 \ No newline at end of file +HWPX Link Fixtureko(주)한글과컴퓨터docxly2004-11-09T15:23:46Z2026-03-08T07:30:22Z2017년 8월 3일 목요일 오후 4:55:39 \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/core-link-text/expected/Preview/PrvImage.png.sha256 b/packages/core-rs/tests/fixtures/hwpx/approved/core-link-text/expected/Preview/PrvImage.png.sha256 index b52266d..21d6e53 100644 --- a/packages/core-rs/tests/fixtures/hwpx/approved/core-link-text/expected/Preview/PrvImage.png.sha256 +++ b/packages/core-rs/tests/fixtures/hwpx/approved/core-link-text/expected/Preview/PrvImage.png.sha256 @@ -1 +1 @@ -a3cc714d6645caa3b0735b03d85d295b46cc486218b1d676fb74adcf4019df24 +a3cc714d6645caa3b0735b03d85d295b46cc486218b1d676fb74adcf4019df24 \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/core-link-text/expected/version.xml b/packages/core-rs/tests/fixtures/hwpx/approved/core-link-text/expected/version.xml index 4ee1300..549bb34 100644 --- a/packages/core-rs/tests/fixtures/hwpx/approved/core-link-text/expected/version.xml +++ b/packages/core-rs/tests/fixtures/hwpx/approved/core-link-text/expected/version.xml @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/core-link-text/golden.hwpx b/packages/core-rs/tests/fixtures/hwpx/approved/core-link-text/golden.hwpx index 79a5badd0ca2f7bb3f6d6fad6eaa21c087b7537b..a72caf76553bce8bd08aaf8e67663d18e62dfad9 100644 GIT binary patch delta 6783 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 diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/core-link-text/hash.txt b/packages/core-rs/tests/fixtures/hwpx/approved/core-link-text/hash.txt index 0b59304..6865f2b 100644 --- a/packages/core-rs/tests/fixtures/hwpx/approved/core-link-text/hash.txt +++ b/packages/core-rs/tests/fixtures/hwpx/approved/core-link-text/hash.txt @@ -1 +1 @@ -d74fe0472a469cdb695d67aca25ba7281b917f5cbf0c3bdf923feb038d161b8f +48a1467e3e0ac1a435db2f3b5de49e73cd413a6b5f269d2f4547fab35336f63d \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/core-mixed/expected/Contents/content.hpf b/packages/core-rs/tests/fixtures/hwpx/approved/core-mixed/expected/Contents/content.hpf index f4a7677..8279224 100644 --- a/packages/core-rs/tests/fixtures/hwpx/approved/core-mixed/expected/Contents/content.hpf +++ b/packages/core-rs/tests/fixtures/hwpx/approved/core-mixed/expected/Contents/content.hpf @@ -1 +1 @@ -HWPX Mixed Fixtureko(주)한글과컴퓨터limchaesung2004-11-09T15:23:46Z2026-03-08T07:30:22Z2017년 8월 3일 목요일 오후 4:55:39 \ No newline at end of file +HWPX Mixed Fixtureko(주)한글과컴퓨터docxly2004-11-09T15:23:46Z2026-03-08T07:30:22Z2017년 8월 3일 목요일 오후 4:55:39 \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/core-mixed/expected/Preview/PrvImage.png.sha256 b/packages/core-rs/tests/fixtures/hwpx/approved/core-mixed/expected/Preview/PrvImage.png.sha256 index b52266d..21d6e53 100644 --- a/packages/core-rs/tests/fixtures/hwpx/approved/core-mixed/expected/Preview/PrvImage.png.sha256 +++ b/packages/core-rs/tests/fixtures/hwpx/approved/core-mixed/expected/Preview/PrvImage.png.sha256 @@ -1 +1 @@ -a3cc714d6645caa3b0735b03d85d295b46cc486218b1d676fb74adcf4019df24 +a3cc714d6645caa3b0735b03d85d295b46cc486218b1d676fb74adcf4019df24 \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/core-mixed/expected/version.xml b/packages/core-rs/tests/fixtures/hwpx/approved/core-mixed/expected/version.xml index 4ee1300..549bb34 100644 --- a/packages/core-rs/tests/fixtures/hwpx/approved/core-mixed/expected/version.xml +++ b/packages/core-rs/tests/fixtures/hwpx/approved/core-mixed/expected/version.xml @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/core-mixed/golden.hwpx b/packages/core-rs/tests/fixtures/hwpx/approved/core-mixed/golden.hwpx index 4d5218e44d931fb9f2af147f7524fdb645628e1d..37c882e8d3906834eb8d4c29b48f90eb6d80e829 100644 GIT binary patch delta 6984 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 Bsd@ka literal 73759 zcmeG_3s_Xu)@YVmmUj8?HS@tlxA5MCVFm;ld4zyUA&_Wf7o}l13==bR#yK+#pfJ8} zX5QjUO)_3uni|6R2qxtvAa9uY*FI<7fP$F?9r`BEK4*Is+A zwby>F75w4@qaL=R{+_a0bJKgCH6+k)4E#g_5hjb5U|fkrDBy8r0$j|DR!E*p5J-Z} z3chRg@&?WbD=YMS1PaDtQUWqN#EFFNUhqGL?uCaf%fZo>fr)u|q(IE~uwA@puKg@q z1|j2$Be_CcjCt6`V}vck%hNqNmNVaH?#mP;1^^Kgoak5&+i00g!eKHM3WY;77XXPI zc(_B16d@)OGTDwS7E@Q7EknlTV=`|e_8zvOK?{9@7Y6zGg@gnxv}K66uj5h=+nMkr z5Thp+dg4iO4_m-9LM{+S2FgVd81)4wAOpMyWOR6;201|)6AF3Q&L`1g1Vu#&co+jx ze7TU!Vl3q7;NknwD0(7u2Sj9OGD!*4Es_D1kZ`43zLYD8HkM;K4xpq9 zvv*18i2*PIf$s1y&EVum8o11f^ZXDl)>mFsZ!+ zINMNC64ifEf)|a6xRhK(OQQ5;B8{Yw$fZIDT*_xg@|c(q6OmlSa$qs_d2tK)f=Ede zS%B^6=*onz#+*2a1}~Z`mGzI1K~7zmxFNf~D)=ZD3uFkyfFR6QNESnWAIPrL$t+t?ThW{dj4u#_+F+ngLT?#SI4%?8GAaR(4bKt*^aEs&MF`1%vE_17&7uU8 z1SZ9z0)SD-=O~c*C@v2p7y=n4BCB+zs?>jB2tnI`2J#M^w>SXRE0&Sj*v4!OK_sc% z5FiiRy7zY0e)(q2hkI+1H`b=E0!%>Pd)O{oG}o3PApAh}MMh#|0SX-ik%WL+WdULT z=DM}--5vrH$fL*AtVue=1H2dc2M$5Jf#5HA4?(g1q zkR=P|1Pw*mTpTh4=xwTC2ud5{Qm~Ka3WP!vwW-sG5r%JiohFNxVwf+fAc<&hB(5MI zc`yuM(f|RnPXmJjL0lrF7&pcT6AD8xWSqz_>Z2p2X9*#fh}Nl6D!-2u$73i(3VPKC zmm`1(Hl_H(PK7+3Rc=vglsF6UPj# zUXSK+qCn#3NaaK{^f`|MGb=Jd8ZADQNQhy+Y0HS_g1M=C^RyfJ2a`3SBpRkcqN+79 zpknd~UcOEef$=(5!WCl#Xg>gr%c3dOg(``_sZvnAi8=#?D8LOspVT8adhld|ssKGw8k2yQLt_n<7^{G%Ux7I$3`sBmC2PLdnB!4V1ST`oaW4=@ zJV@~rso+f53(Ryqd%4lvMRV8P2z3A41w-c_hr58)^`dIf0PxO4!5DNGyf!EoQgwpv zGEBH>-9;+(KDF+B)m=`1B<{KwY8tfk?=Ujj!|Jw9FwmoJY($Sn(LgDkazu}M;Xu#S zzza|M^{C-BKspvYE;_1 zM`JA#G(9Q%9B63sen9NyV!~d(|Ll>rz=qlE(UoQ|-MR;Em)F;dH6fDSU;7Zhk4@+3vtt(7fuB-E}XO))}rEe zJ5g~W|L6%9aLb)0Txf3oXP?%?&@xO16g2u6Mil4YeO-dK?1!Xff7hm$ZsAL3HQnCy zGRu4aO)nPkra@AiEG30C7yWl12NYUT{#=rxW3T~9ido*@K~hY%>sWAh*ZiyjEh*?I z1HBkIfLRLc58lOe-i-V2Fr7Ec`#VUA$<|}qw{*bt07f zsy`6QmCNu&+=u`;-%V~h>(UFUW1{*Kkj5gw^=3tk>3)lVDQH#%j6w5S$m39`DQ0++ zEfJG9BH{3*$$@&^)hELz{ds!0*@-TL>CVNYqw@37O+(#lB(7L2M%{gal}|n#JPq)h zyNF!<@)r}bQ0gqZu0j5=c|hH>qRxGLN4_p6C=#J?@~#45qJO47=|Q zy6e7j9#qqSjqiKY2G>{9m(3hhi{Qw^eQ(j-cGmP|E`w?t?6=?dw)yxk^nuIg;e+Tk zQ1;7Q2P0sxJ$jpV>BgSlQ@c}^ME}|xHm!zs1JHiLAHD_1+%Q-W)ONceNnNZ#J=B*K z#d4#}DC)c3jv`GL-M|j*hx^`OKj?0;pnId}&fa|8L`OClDwG%HRx<2y`e|mH4MRIaDj$TT|5jxmuW)bPLGG} zi++B=;mE6n?oMJLF7Ep)+?2x=K~lKaMBVO*!7xdvKo%VcmJ``7^a8b&Y9+e6J_lWL zoP$MyKLj!dI#dw~4jiC43;n!b)U^fKjp!Z{!VdE$1YEHnx`Phwpi)3PltK-ca0msu z-A_L9xCF+4S5mn2E|6l8_T=q*>O#{UeLJ`?O|Slw{UG8>;odzw@;Cz0HwGh?E0j~1 zBc}pzEj~1OaUhs{2o)X&U7x%|7Adm0?~n(0B=!zj#6aTPWkGyzz&@IT$gC$Q#UKW2 zR@WjpsSGBvWt*5lOhiC!#dVc;Bq)?xQuhr#~kFwkL6 zv<~anUH3h2$?!cd(T_fT}z9N}mtA&H1pQHdR7*w0fOpqffTN ziS&LcGjvoJx`jaNF8XLIoH4U!IU#Q7CF(=`V7^mdZ5q7#vS+)XlahZL>I@+j_B)== zr>;i-?w5*a+iRHKD!NC?i&k8J$Xi9f=dpZRbNwmrLsDMm`m$n7uSAkg8+&E?6I_a9 zla5_`wH!=fQ}U2eX?kV4hvXST#+Svhd{AlkZS9LzVSh>$){ik`Dxk1Q(8pBm9t!9^ zU%BrK-{0oOD64N3cMpj(gtV8%fqYtf(GDs4b4WpX9ql#h|29H8^b{DIbYFa*$V2Ne z((4Z8($C^RKCQ!O9oC-?yRSkz^mLeW-=@)TqvB~DW}?GxcOV~3NSVpY1KO%RPS&y>+ZSc1+T%h=Di% zA>SK(mQ&$Nycc>ytP-vWPRiEo-(0u$y&FeXfkSOEKahMEh(eeMZ5dQ8CN9pSxzb>% zE)B~ZXqz$wRcp*5sLa75 z<1G+D9zXAZ2xJ*JB7kRaL~iU)tNmaz0{|JQ5~Pd!4)4qegBGdpfDa|+9T)>9{|(~A9!bm z50t+XK9FVL_@MQ^{_p`nz3_o|X81t)JK+Oa296J!1^0&!0P2MgyfecG%HIhe$TDzz zkZIT-J^-i}KJd;AA1Hq(d?3rf@j*A-`ojkR^}+|44076j5z!4(vt@MWxK~&K+{o3`ONoTs2{*;cjqx`zId~4 z%MlZ-f+4;Hg9@u*ffk@?#!Zm^b;B5_yWW(l*?FYq)4esH{nrF16q*c^TA%}6X@(9E z#1b9WptLm$)Ln0UZtP!O`*E%bGIMcUCdOr$1v;n~&F}$|_rhmT+L{IGt~Wk4`;M67 z<0p>1TXY7ctXUu`eW0^zbItCZCgfRwiEvBOK-ZX&2D+seX#n~l_!%UmH!ijNch-Ek zicz~gt!DQw6O2g&EW}2+Tmm^@|zM!65wzDM*_OJ`lzX z9q6E5=s*R7KnJ9xUv!}HX6QiL937y>9npab296H8JJl~b(0DU+AZ?BgP~%SMpbBn_ z4w?c`_C6KINQy-T2nZ@13lA4bAb<}97WBn=auI|ygiDc9J|=_6MzCK8wm58Dz=qH( z)V`gN8xLVCeRPEcLV+xv+@Oo*^aezR5{bllK@yn&N3n3oXfiPZaTE@Xltp{kqQ>$u ze{nP>MImvZ9Z@<(WAaW1-yD!k%H_oXVfdI2E{+oL8BszmAA$wVB>hpK2BW<}&)^py z81=9f_4kz3Lzj@t@~~aJXs$glBZQ0;M{9_ ztF=HqKT|Iz`P>&gl3=Fww z;A31GZzu-*I)Iu)TtbFP?Gf`CN=l;oFG@g|EfE*(i?U!NIZ9t9(nt!4Tq<>2 zkBJE}Bu503<-lU<^Wqlp1(A{{vH&bWyE5UcF((czgk&Bh;*M=`Vd94D`l?_P7ac}J zAO-}%GBq*CFS z=H?)w0)dfmB;-m1+jXYvOt$kZv?FJx{XjXBdP}!pK7YyHVsqsQ_6k`ouynALtQu4^tV;Mo$S+a@fVK--=>KX^;3Dq;s2fF~@FD8} zZe+2Xkg>##2$7*EOVsL~F8MMFt44Vs(Y;x077&Y}i}f24&6VhvKVTpff!$#G3RDe3 z=Ve`_HZtHW8pNr_kxYwBRUZ_0R@W3hM2KUcAYD|bIDmlq-H=e=Vm=>#U7!y$YY2UY zLg9kNODfmi{xgk z!cQcT#iN*UBs3x#SD*xC)f*8;q@v!Dgl9ldJ=FR|=NYOftk0@FkfbS#+k0 z3)CH$)!*u2s*OXQFlN%K`|c<{r15AlvSh)t__Y;(0fM*7Mmdwp^gyZ z(B!Al4jrr%8^P$K(M4b~1%`gxF_O{9g7$3}T@=LRQnS6(jS6so5(GAF#B^Ba$$oGcAWkIs)T!rBZG@2}t*XWPS+_Iupbh z`EM|NCIkt<{ZtGrBqaKQi+U3Q0X-2EN}ZDS5jiU&Z-qk8*#tc(^h|+5qYl)go1Qtj zIk90)MaTT+7$V#yB%&5pA&g;WRv|eUn{6YJB7;tGl5HBSRp>&EyfV+)FZM8CKdENp z&f0CO7=6*SS6{g5%ih@kUhRkb2UNyTAES2b0IQj0sOHH2+7FJ@rmq`ZO*0188ZvOP zgaeew+g!6LZLoFR$U@7QHx6zeSZ%QONOrf(v*xqab*uL>rlaW&!C^A7IIc({#2jEv z0fj6_7?J<J2^X&0tXMF@s_3@Z0quWAJ@qzHSqxe7kkx2rDb}8wU-5)1oN+H1X?} zYq%U}wYNkfgrL}HQlmcsezyxazh)-P<@j)=u;zvn0)z@o=m?o{mmFz7lN3_j&ZP5b zW^_M|u;73R$KytQ6J%wDe*b6A0sbPeoE#)#{)XVdd5=E)Bw8(d)PKJ3LMyA0XRWM8 zS&exJ-W*$4+6@1UAQsM>W0hGt=Y6Q0LdjT-T!)q^Mt{5(fYHayQA zG2Q9`^8H9FtMvi^NaYEHf6?D-@Iz&&e@AW~sn0R;?Dhxr&!~`<)koS}LilH_Wd0Ju z%IdKb=r`iM=N_AEW%az3zpwYAxDj;~qyHm%WNFrw2dDczKOuOx^I!k(rMISU|7g^N z|Jpt=WBc?qXQQ9rY!&h0m^BN>P50aN!lu8B`uM|1-#ocv(pwAvx?}p^IAb2Zq0r7& zaW3+=#5bhnh#lT43w(#D?{58IYjd$@x?=(Hw5L`d z%=qx*DZW$^z5gok`QQ}{Lyid-7>u2+m)$m#`O)iD+gqQ|m!H7c@BMr*{uMDDL8A(J z*RgcaA^qCgRog39se=t)N3QvaXHwZiBfRrQlzpf#J95;hLxqN#tX4lda!tYm={{o( z=!|(i*;v!a$)iTKJmkLqF9viboSpW75thrCF*hEKd+ZqlI*gqj4;dRToG{_+S*Bj>Y>$y`{ggo-?%olv%vTA zndGEfO}j($UhjBoU;A5C8}onJ_*lN;q*%(bB7+j*YSs|8*yyMpYSujgI(#^G>)#^Ld)2cB*W4Vh zm2@1i={iuAQ_z)@(w3|CC{lJ8DrXclPA)hQc6sE)R~n?h6vgMLy|f*Qa;CP4+3-N|euWt=M)YVhaT~shb zb!&l2cJ$?%)$84Qj@fh{V`r#)$_u?Tja4oCls)V-vPhqm0GW{(Oj{qyomb5tzBHUS?gIW zt-RTmsA{uU&nk3#zws5kKvPqYUtFmDF)Lr5xHvzpQ0rdbYQxvI;@ZP2zR%XWZ7n<< z6W;ZcHC~h0utLRd%PCq~|HX>`$B_qqdwROMJV+bw=vNcdI-h!H)}9BSz3?m}yzS`uZE@!n zc{NKFXZG8i)oL2F`R=zq9KW=>>Qc2$iqkbmy`|MEr#1a|eeIf`AFyfvs%f9Lb6-(*sXVi4PyUTP z?;|EHnz=8&WRvGfx1Y;2wPo+yXd7+9r?u{1Uw0T$Jg1FFuqo4Ybywq>>ZJjy*27s# z#nMfwGprp|jZ;+zHfGl>YdhMd@TgL0zfJD`ww^b;*8k)&SCJ^CerIK7xkk1&;cG8%VUacb+fM_!lbMkK1^DvL$^jPNbIC zCT+X*MqpQRs;av>$L{6P;a=LrOJzNm6qOp4rcULc?Jm>iZu};`;-!qD=JdAQqB>mJ zRdqc`*?FmMoa)EIOPvo@k3Ei>(=ct%`X!}3)dyxD(?ahnJcJY9`%HRRIgJ;Tcq1fb zY-5XyvOc~?BTD~IiS?zfiiV_|vX)Uzm#W9Lz2s7$?J6iJzIe6Jy_!|z)qUJ+2C6;3 z`+A1vx^&lzOK%+1Oe;}o<3CyfJ%-1hUOVkeZKk5YEBfa9S-F@x>0;5c*okL|?r!ax z16%AqY0-}Mta*FIgQ(}Hym3n`GyK%)$m&Tb{D^y*TMcqCgHlY>ijsM}#D7g7Z!TyaU z9YnyCg0{Sdihuh|RY+0~#?;hZomkM`l&9*-D|&8O<6Brv@IJhG7E{g32p^3Tt%)8v zN5n+bEkV$VpV&$b5`jv;3n$t;N)`Dn4t9A{w07=S#uio7$7WgY%dqoW|KQmTv!^ts zc6@ij{iQ88yUy73oTaG>;{MT5hR7@19xh|F!#a&$!fmz7>FHWnzo^a_j|VS$SIFS7~p1 z)fW;M${RVz9GaECYP)CtIdyHi_1~?1iBH9O+qN!uJku;vwTg<;UfLGi144Vy{knh9 zPutFgsZ^RGV4xq0(%#$Vq##7~FaV@gbh|2>UAq+JJxQhcCFS8bvEi$*laBnWhc9WM zbpplp?_ONi^uLOW-;O<>?YXk_YFX8TNS!KHJDq2grBtd<6{cNGPazJ+l|JOv^loxk zwXiZb&29p5>cxucvEPmQJb-?v7w(P>i zi`SCs?IH>Vo~q`>>p@FNi&B&wDNMDy+U>kz#VL_K#q~jGEGwKi;cQ`6 zXIdq(rtb0NxDy+auk1fqhOa0uj}3n^{ootT<8Q7zn-pApj5yhBJ2Cs)4ay7WU1xx!0!JRVW-5 zbbJ%u{Y}NzFF@zKUDT!0YBo8%y@gp4Bn$le)ZCx1>~NL+q%3~sAJ$(Hhh-lrGs~8i zeOK0Ul*s8kw=cizf2|2F+P0*Iw%Er#_@mUV_NpfP9G<$(t0lziP|o2M%cj}YR(4mr zGWR95>`|ZIB(`oN;*V=YnzWSMpPvcJeLem8fGNp!s-6lJSO}e|ZY$iwo3y{o^E#S; z<3!PcZM++)9_ph_*{X!eDH{sn=Qw4J{VDrziRt4?Hm&cO;90Lu@;n?LP`oKK``Hzx zhl3*bWgVaPuNL*I%(q`j_R33X=69S6?>Sc$r1?20upzVP#LZD%?iW*EoSI)>lP_*N z(!L?1eZvnPv3bXzC~c`)UvcAu>K09N8$NAM^D9q$*73xTPGxkTs#@BRaK8Tg zT^%cnRjWb1e!d)<@#D49y(#MxGfVeo)zyK?bXoh+s_mNan@chd9&Ju;(i}=#|L^nb z>bj4+H_FbYB!DfX)a-zM^3tTq%L}HhNbEV50&2KiasH8@?h;pKsq3V`FH4^pPqa!i zFIEZj5*+NdmXErsshZuD{5jvFW=W3Sn4H%;;EKxmqp8BWe?2F=I{|U(TlW#K z;LY#7-*fP4O-*uwoAUi@mDRk|t_Ef1J1h2fJ&vr|Gb7h@MyG07MJwauN*zjUIC7W8Gwq^mvU+%ryBJkPJq^lFLlN~pH!%zmF(!-InR zI@Wi@X5~~g%~Rf**M)cPP?asYHulqwIQ6Mxm#T%=68_e)V|Jl3pgplFG^wg-)oAzE zw$>=ZZ?2h`cBHM7pB`Ep{+QE;UMX9OlLJzV9;_bMoEvuhmlKm`RpZsQniJ>38r%z( zb$l0EKd0yN6=JS*PLiiMK336Oqr{olx4pSc>%GnCfVI?mJ`p9uVbz7}N+$idHp}g45B{n18Kc-;E4L(9&-ulx9)z}}7 zXU_Ait(&s4V&x_4z0H5;c6bY z{xnWZEIGL=X$tXvf^C$zIjQ>l^=mWM$IoFm)Evob+>(*MUwh)U%EEQ+%hx1mdje*Z zJ>$^40JqL3#-2VBUiHsVx8+$qWj*e0Tb+Ka*{WXt!HaU}Z;dGF>%tSNuqIZ}(Q47O zmId$EEN^DzR?pn$*7W^`mZW)kX*)CQf}5>b)yr2VWM%K*Cy5dZnv)*OfBT1=alHJ6 ztdyw?WSFGSI$%K8DAsn9zVA|?C8v(k?Q3x$)CsP0!E>NYF2*I zH@w8j1 zq~34ioi!~-D=l>>o7C!7!&yFy|I4%gQ`a_xeebARH|t&3l#h$cTDN6L9r?-X;@T6f zqpnYJy~6f-mzQ)xm5};o8vaD;g-Zo<6D!nJ$I6cTmUIhMJu$`Olcu#MdzCh*o83p` zD=S)ZlbxDMQky@vw%e_iuFWn9om9;aYZ%?K-1F15ZCjmoHkYI~LlZIpt<-EKh3*V|u)rak|8HbE!JUK``r_*01!M zW>V>Y+H$lmKiYAcPwet+9eXkR%LuLVHG#vS5Ac6_-WYq`eVN+-yUvfFQt#n3hs2bA zR~~lqP`>tHQ%^!T7=7(4AD?hGgPC!P*X>q4uIl=>#x(pw>Omp@XP59F)0tT|ZLw*| z>LYke#B17xn|rQR3&VTX{imlZ{jUXH#exVT|8Y_2mVH;;yggF(7h~=9QtmjR=)`C`VPLI@lR#;wWTm)x4Sem})UBlY1)$0G7aP}1M?}jz>(<9d$P26i( z_!+hE`Cz+bWB%tK8FQocfN@2B{s`}F?sE+bha>G7JI!kwC2xAdxW518s_kN%=L}2y zbmjWZ37YI^h(l~iGn`JEXi?KEC0fbocQtACsvA2P57Gk*HFnTLs?J!UjS1SApv@@S zjG_%l+JK~Isk*U%Hl1nHnVvMzlLmUaq+3j&r%Uv7iC*&7t^d>0C3?CEq_qSU{itrBDCThd1cM8}w=EG54Ej_x{%vJaE2;et&~p3;Y&&+xrL3WrC|h85jJxN*$z; zQFk5i0?Kk0`p(q_0)UiR!~lg88Qy^9=H|w9WHZ@pd&pr=#6!(-_G02`@coGZ_+pf+ zaOCw6rNL#l9Ngr>S25uTVKj(f@XoHQ0^XQa5e?4SaS?b4wFq4=!em^gB!>Sq_+kc- z;Nl&ZOOYFQL%%X$ke0AY1`lbpK)c_J=;f*tIOKnKpXe%r9 zL*6(I6yj0wXdEEK$XyF1L=u5;pS&3G>Zy|&Ius2ZiiQqFbGuM9#%NEr42YbwT#ng8 z5CE}E5G6oSH*Bd$F`n*7wvf7x|o-sJ}&G5niRTp8G2Y$>Uq!9{$K}M(Yutk2@Z9S($1iXLLy_W{DcW)fbtMMakChQW9V9{?@j7ToOGw~+U0+0(G152&-#N1oqP7s+SS$j9E?J-`6o?@B z3`WSs{M(@AgBplM`ojNuL4aUL&h{*py`$SA)=UoDiR0|@>TN(RfTNBzNKyVyHA z**nf!j;7d_Q(*-@9ej^WIi zIg{h$b{i~Xuy_T8JFsjla)Ph#2qQ;{xMD#R#H67*Ng%^SB==DU3q+}axGR0zF%V2B zQoyyR!hJwoF}+)Brq0MgRT;qP$KqZ9^&d#nXpkT&r`&pwrcSz1q@&v+M-jrT>4XFf zR3jCj-9QMk^?F($pX?~a6$-$nV`|hulbrh2Kzuy&ykvd48pvnekZCbe+iZE}Y&m$R zM!$XxXrS)hkJY>=2%D!f#dV`K&V&9Pa4;>(ir2^D0=uDq``9vu8ITsLmqPR(2(yF{ z=1E4BTquP2Xec6&52c3)a@GJ^5t%3bcPIoO6QPo%1@fGp_e^Rs1>J`ydUMD~^)Vsz zjzCQIa6e%Bp4XT|GL;B0%k-ErEr>v-4Siz44)}AI!`=Xc4gFC3KtsKHq5V}3>2WX? z^?>xufaX*KV+MUmGPW8M5Fat};RlSN57*rEo`-z@qu&_#>Hl`DMo%IE3~MlZu`Knj z)yo?=!29U;h`tE?yJNK*{9hL%5jEPfV91y`JKoq0l!70sQvbJObsB&&aFlTxW(mMD zRHG3<$X*iwdI9eX2g|SrMi4LL^%(@g5(mq$14aOk!W` diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/core-mixed/hash.txt b/packages/core-rs/tests/fixtures/hwpx/approved/core-mixed/hash.txt index 2fb4309..0391a85 100644 --- a/packages/core-rs/tests/fixtures/hwpx/approved/core-mixed/hash.txt +++ b/packages/core-rs/tests/fixtures/hwpx/approved/core-mixed/hash.txt @@ -1 +1 @@ -818fddfff8ae57e809f330a905a9ba3aa8054ccabdc72911e9979dabdf48f8b9 +2a37f488b5e10c63a0ab241acf5b8f68f2ff85a364ac196819ca975bf7f9d866 \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/core-paragraph/expected/Contents/content.hpf b/packages/core-rs/tests/fixtures/hwpx/approved/core-paragraph/expected/Contents/content.hpf index e53c30e..4e20a13 100644 --- a/packages/core-rs/tests/fixtures/hwpx/approved/core-paragraph/expected/Contents/content.hpf +++ b/packages/core-rs/tests/fixtures/hwpx/approved/core-paragraph/expected/Contents/content.hpf @@ -1 +1 @@ -ko(주)한글과컴퓨터limchaesung2004-11-09T15:23:46Z2026-03-08T07:30:22Z2017년 8월 3일 목요일 오후 4:55:39 \ No newline at end of file +ko(주)한글과컴퓨터docxly2004-11-09T15:23:46Z2026-03-08T07:30:22Z2017년 8월 3일 목요일 오후 4:55:39 \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/core-paragraph/expected/Preview/PrvImage.png.sha256 b/packages/core-rs/tests/fixtures/hwpx/approved/core-paragraph/expected/Preview/PrvImage.png.sha256 index b52266d..21d6e53 100644 --- a/packages/core-rs/tests/fixtures/hwpx/approved/core-paragraph/expected/Preview/PrvImage.png.sha256 +++ b/packages/core-rs/tests/fixtures/hwpx/approved/core-paragraph/expected/Preview/PrvImage.png.sha256 @@ -1 +1 @@ -a3cc714d6645caa3b0735b03d85d295b46cc486218b1d676fb74adcf4019df24 +a3cc714d6645caa3b0735b03d85d295b46cc486218b1d676fb74adcf4019df24 \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/core-paragraph/expected/version.xml b/packages/core-rs/tests/fixtures/hwpx/approved/core-paragraph/expected/version.xml index 4ee1300..549bb34 100644 --- a/packages/core-rs/tests/fixtures/hwpx/approved/core-paragraph/expected/version.xml +++ b/packages/core-rs/tests/fixtures/hwpx/approved/core-paragraph/expected/version.xml @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/core-paragraph/golden.hwpx b/packages/core-rs/tests/fixtures/hwpx/approved/core-paragraph/golden.hwpx index 7d0b0b01e994676dfcded90ac329180dddc715cb..6f4ce45aee697e8ac6e0a1aa09fdf0b505ea5b94 100644 GIT binary patch delta 6653 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 diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/core-paragraph/hash.txt b/packages/core-rs/tests/fixtures/hwpx/approved/core-paragraph/hash.txt index 54923e5..a878c9d 100644 --- a/packages/core-rs/tests/fixtures/hwpx/approved/core-paragraph/hash.txt +++ b/packages/core-rs/tests/fixtures/hwpx/approved/core-paragraph/hash.txt @@ -1 +1 @@ -2dc0e09e86b54cd04751bc3535f252fcadb026aed66e50261f983120165f4f43 +8d760c34cce95e8e19d689c04579e66463979ac2877c03e9115e239cb39aea4f \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/list-basic/expected/Contents/content.hpf b/packages/core-rs/tests/fixtures/hwpx/approved/list-basic/expected/Contents/content.hpf new file mode 100644 index 0000000..d633c3a --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/list-basic/expected/Contents/content.hpf @@ -0,0 +1 @@ +HWPX List Candidateko(주)한글과컴퓨터docxly2004-11-09T15:23:46Z2026-03-08T07:30:22Z2017년 8월 3일 목요일 오후 4:55:39 \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/list-basic/expected/Contents/header.xml b/packages/core-rs/tests/fixtures/hwpx/approved/list-basic/expected/Contents/header.xml new file mode 100644 index 0000000..033dcec --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/list-basic/expected/Contents/header.xml @@ -0,0 +1 @@ +^1.^2.^3)^4)(^5)(^6)^7 \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/list-basic/expected/Contents/section0.xml b/packages/core-rs/tests/fixtures/hwpx/approved/list-basic/expected/Contents/section0.xml new file mode 100644 index 0000000..7439f42 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/list-basic/expected/Contents/section0.xml @@ -0,0 +1 @@ +AlphaBetaGamma \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/list-basic/expected/META-INF/container.rdf b/packages/core-rs/tests/fixtures/hwpx/approved/list-basic/expected/META-INF/container.rdf new file mode 100644 index 0000000..8df472d --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/list-basic/expected/META-INF/container.rdf @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/list-basic/expected/META-INF/container.xml b/packages/core-rs/tests/fixtures/hwpx/approved/list-basic/expected/META-INF/container.xml new file mode 100644 index 0000000..e0cc34c --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/list-basic/expected/META-INF/container.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/list-basic/expected/META-INF/manifest.xml b/packages/core-rs/tests/fixtures/hwpx/approved/list-basic/expected/META-INF/manifest.xml new file mode 100644 index 0000000..5530619 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/list-basic/expected/META-INF/manifest.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/list-basic/expected/Preview/PrvImage.png b/packages/core-rs/tests/fixtures/hwpx/approved/list-basic/expected/Preview/PrvImage.png new file mode 100644 index 0000000000000000000000000000000000000000..43c6c2deb59664f95ad175892df176567ba81c34 GIT binary patch literal 20421 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 literal 0 HcmV?d00001 diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/list-basic/expected/Preview/PrvText.txt b/packages/core-rs/tests/fixtures/hwpx/approved/list-basic/expected/Preview/PrvText.txt new file mode 100644 index 0000000..fbff95c --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/list-basic/expected/Preview/PrvText.txt @@ -0,0 +1,3 @@ +Alpha +Beta +Gamma \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/list-basic/expected/mimetype b/packages/core-rs/tests/fixtures/hwpx/approved/list-basic/expected/mimetype new file mode 100644 index 0000000..24e7ded --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/list-basic/expected/mimetype @@ -0,0 +1 @@ +application/hwp+zip \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/list-basic/expected/settings.xml b/packages/core-rs/tests/fixtures/hwpx/approved/list-basic/expected/settings.xml new file mode 100644 index 0000000..862f65b --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/list-basic/expected/settings.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/list-basic/expected/version.xml b/packages/core-rs/tests/fixtures/hwpx/approved/list-basic/expected/version.xml new file mode 100644 index 0000000..549bb34 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/list-basic/expected/version.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/list-basic/fixture.toml b/packages/core-rs/tests/fixtures/hwpx/approved/list-basic/fixture.toml new file mode 100644 index 0000000..4ec74c9 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/list-basic/fixture.toml @@ -0,0 +1,4 @@ +title = "HWPX List Candidate" +author = "Fixture Author" +strict_mode = false +determinism = false diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/list-basic/golden.hwpx b/packages/core-rs/tests/fixtures/hwpx/approved/list-basic/golden.hwpx new file mode 100644 index 0000000000000000000000000000000000000000..e8d7a72f4661ce3855012990f2c6c011b845d8fc GIT binary patch literal 27557 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 literal 0 HcmV?d00001 diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/list-basic/hash.txt b/packages/core-rs/tests/fixtures/hwpx/approved/list-basic/hash.txt new file mode 100644 index 0000000..293d8db --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/list-basic/hash.txt @@ -0,0 +1 @@ +0c54ee85b4061f5c9a9d26869dd932a14c99f48896a4469d42ab7a15ca1ff16b \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/list-basic/input.md b/packages/core-rs/tests/fixtures/hwpx/approved/list-basic/input.md new file mode 100644 index 0000000..93b7a61 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/list-basic/input.md @@ -0,0 +1,3 @@ +- Alpha +- Beta +- Gamma \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/list-nested-depth-2/expected/Contents/content.hpf b/packages/core-rs/tests/fixtures/hwpx/approved/list-nested-depth-2/expected/Contents/content.hpf new file mode 100644 index 0000000..6ab014b --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/list-nested-depth-2/expected/Contents/content.hpf @@ -0,0 +1 @@ +HWPX Nested List Candidateko(주)한글과컴퓨터docxly2004-11-09T15:23:46Z2026-03-08T07:30:22Z2017년 8월 3일 목요일 오후 4:55:39 \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/list-nested-depth-2/expected/Contents/header.xml b/packages/core-rs/tests/fixtures/hwpx/approved/list-nested-depth-2/expected/Contents/header.xml new file mode 100644 index 0000000..53c3b1d --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/list-nested-depth-2/expected/Contents/header.xml @@ -0,0 +1 @@ +^1.^2.^3)^4)(^5)(^6)^7 \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/list-nested-depth-2/expected/Contents/section0.xml b/packages/core-rs/tests/fixtures/hwpx/approved/list-nested-depth-2/expected/Contents/section0.xml new file mode 100644 index 0000000..e5ca3bf --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/list-nested-depth-2/expected/Contents/section0.xml @@ -0,0 +1 @@ +ParentChild oneChild twoSibling \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/list-nested-depth-2/expected/META-INF/container.rdf b/packages/core-rs/tests/fixtures/hwpx/approved/list-nested-depth-2/expected/META-INF/container.rdf new file mode 100644 index 0000000..8df472d --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/list-nested-depth-2/expected/META-INF/container.rdf @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/list-nested-depth-2/expected/META-INF/container.xml b/packages/core-rs/tests/fixtures/hwpx/approved/list-nested-depth-2/expected/META-INF/container.xml new file mode 100644 index 0000000..e0cc34c --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/list-nested-depth-2/expected/META-INF/container.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/list-nested-depth-2/expected/META-INF/manifest.xml b/packages/core-rs/tests/fixtures/hwpx/approved/list-nested-depth-2/expected/META-INF/manifest.xml new file mode 100644 index 0000000..5530619 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/list-nested-depth-2/expected/META-INF/manifest.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/list-nested-depth-2/expected/Preview/PrvImage.png b/packages/core-rs/tests/fixtures/hwpx/approved/list-nested-depth-2/expected/Preview/PrvImage.png new file mode 100644 index 0000000000000000000000000000000000000000..43c6c2deb59664f95ad175892df176567ba81c34 GIT binary patch literal 20421 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 literal 0 HcmV?d00001 diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/list-nested-depth-2/expected/Preview/PrvText.txt b/packages/core-rs/tests/fixtures/hwpx/approved/list-nested-depth-2/expected/Preview/PrvText.txt new file mode 100644 index 0000000..fbda2e3 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/list-nested-depth-2/expected/Preview/PrvText.txt @@ -0,0 +1,4 @@ +Parent +Child one +Child two +Sibling \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/list-nested-depth-2/expected/mimetype b/packages/core-rs/tests/fixtures/hwpx/approved/list-nested-depth-2/expected/mimetype new file mode 100644 index 0000000..24e7ded --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/list-nested-depth-2/expected/mimetype @@ -0,0 +1 @@ +application/hwp+zip \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/list-nested-depth-2/expected/settings.xml b/packages/core-rs/tests/fixtures/hwpx/approved/list-nested-depth-2/expected/settings.xml new file mode 100644 index 0000000..ea050df --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/list-nested-depth-2/expected/settings.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/list-nested-depth-2/expected/version.xml b/packages/core-rs/tests/fixtures/hwpx/approved/list-nested-depth-2/expected/version.xml new file mode 100644 index 0000000..549bb34 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/list-nested-depth-2/expected/version.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/list-nested-depth-2/fixture.toml b/packages/core-rs/tests/fixtures/hwpx/approved/list-nested-depth-2/fixture.toml new file mode 100644 index 0000000..43b5910 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/list-nested-depth-2/fixture.toml @@ -0,0 +1,4 @@ +title = "HWPX Nested List Candidate" +author = "Fixture Author" +strict_mode = false +determinism = false diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/list-nested-depth-2/golden.hwpx b/packages/core-rs/tests/fixtures/hwpx/approved/list-nested-depth-2/golden.hwpx new file mode 100644 index 0000000000000000000000000000000000000000..42713c950b244aad19d323076494db699bdc2bbf GIT binary patch literal 27630 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 Tki)dgxY+6T1hAm%-rN5HZ7HWPX List Candidateko(주)한글과컴퓨터docxly2004-11-09T15:23:46Z2026-03-08T07:30:22Z2017년 8월 3일 목요일 오후 4:55:39 \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-basic/expected/Contents/header.xml b/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-basic/expected/Contents/header.xml new file mode 100644 index 0000000..27ba8d9 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-basic/expected/Contents/header.xml @@ -0,0 +1 @@ +^1.^2.^3)^4)(^5)(^6)^7^1.^2.^3)^4)(^5)(^6)^7^8 \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-basic/expected/Contents/section0.xml b/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-basic/expected/Contents/section0.xml new file mode 100644 index 0000000..7439f42 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-basic/expected/Contents/section0.xml @@ -0,0 +1 @@ +AlphaBetaGamma \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-basic/expected/META-INF/container.rdf b/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-basic/expected/META-INF/container.rdf new file mode 100644 index 0000000..8df472d --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-basic/expected/META-INF/container.rdf @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-basic/expected/META-INF/container.xml b/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-basic/expected/META-INF/container.xml new file mode 100644 index 0000000..e0cc34c --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-basic/expected/META-INF/container.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-basic/expected/META-INF/manifest.xml b/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-basic/expected/META-INF/manifest.xml new file mode 100644 index 0000000..5530619 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-basic/expected/META-INF/manifest.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-basic/expected/Preview/PrvImage.png b/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-basic/expected/Preview/PrvImage.png new file mode 100644 index 0000000000000000000000000000000000000000..43c6c2deb59664f95ad175892df176567ba81c34 GIT binary patch literal 20421 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 literal 0 HcmV?d00001 diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-basic/expected/Preview/PrvText.txt b/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-basic/expected/Preview/PrvText.txt new file mode 100644 index 0000000..fbff95c --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-basic/expected/Preview/PrvText.txt @@ -0,0 +1,3 @@ +Alpha +Beta +Gamma \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-basic/expected/mimetype b/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-basic/expected/mimetype new file mode 100644 index 0000000..24e7ded --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-basic/expected/mimetype @@ -0,0 +1 @@ +application/hwp+zip \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-basic/expected/settings.xml b/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-basic/expected/settings.xml new file mode 100644 index 0000000..168e1d5 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-basic/expected/settings.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-basic/expected/version.xml b/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-basic/expected/version.xml new file mode 100644 index 0000000..549bb34 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-basic/expected/version.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-basic/fixture.toml b/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-basic/fixture.toml new file mode 100644 index 0000000..4ec74c9 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-basic/fixture.toml @@ -0,0 +1,4 @@ +title = "HWPX List Candidate" +author = "Fixture Author" +strict_mode = false +determinism = false diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-basic/golden.hwpx b/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-basic/golden.hwpx new file mode 100644 index 0000000000000000000000000000000000000000..e97850a6d3e7b92c6e7651b5b12d45a9e48509ba GIT binary patch literal 27557 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 rNK^fH9KWxRUpPb_{tL&ySBsG@2^O0b<& literal 0 HcmV?d00001 diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-basic/hash.txt b/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-basic/hash.txt new file mode 100644 index 0000000..6109858 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-basic/hash.txt @@ -0,0 +1 @@ +65857b59e5d15ab4b28033d3d49b55ebd2514be107e844bed1e54ac0cd791abc \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-basic/input.md b/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-basic/input.md new file mode 100644 index 0000000..f45083c --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-basic/input.md @@ -0,0 +1,3 @@ +1. Alpha +2. Beta +3. Gamma \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-nested-depth-2/expected/Contents/content.hpf b/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-nested-depth-2/expected/Contents/content.hpf new file mode 100644 index 0000000..6ab014b --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-nested-depth-2/expected/Contents/content.hpf @@ -0,0 +1 @@ +HWPX Nested List Candidateko(주)한글과컴퓨터docxly2004-11-09T15:23:46Z2026-03-08T07:30:22Z2017년 8월 3일 목요일 오후 4:55:39 \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-nested-depth-2/expected/Contents/header.xml b/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-nested-depth-2/expected/Contents/header.xml new file mode 100644 index 0000000..0313b7b --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-nested-depth-2/expected/Contents/header.xml @@ -0,0 +1 @@ +^1.^2.^3)^4)(^5)(^6)^7^1.^2.^3)^4)(^5)(^6)^7^8^1.^2.^3)^4)(^5)(^6)^7^8 \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-nested-depth-2/expected/Contents/section0.xml b/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-nested-depth-2/expected/Contents/section0.xml new file mode 100644 index 0000000..150be35 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-nested-depth-2/expected/Contents/section0.xml @@ -0,0 +1 @@ +ParentChild oneChild twoSibling \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-nested-depth-2/expected/META-INF/container.rdf b/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-nested-depth-2/expected/META-INF/container.rdf new file mode 100644 index 0000000..8df472d --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-nested-depth-2/expected/META-INF/container.rdf @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-nested-depth-2/expected/META-INF/container.xml b/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-nested-depth-2/expected/META-INF/container.xml new file mode 100644 index 0000000..e0cc34c --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-nested-depth-2/expected/META-INF/container.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-nested-depth-2/expected/META-INF/manifest.xml b/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-nested-depth-2/expected/META-INF/manifest.xml new file mode 100644 index 0000000..5530619 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-nested-depth-2/expected/META-INF/manifest.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-nested-depth-2/expected/Preview/PrvImage.png b/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-nested-depth-2/expected/Preview/PrvImage.png new file mode 100644 index 0000000000000000000000000000000000000000..43c6c2deb59664f95ad175892df176567ba81c34 GIT binary patch literal 20421 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 literal 0 HcmV?d00001 diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-nested-depth-2/expected/Preview/PrvText.txt b/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-nested-depth-2/expected/Preview/PrvText.txt new file mode 100644 index 0000000..fbda2e3 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-nested-depth-2/expected/Preview/PrvText.txt @@ -0,0 +1,4 @@ +Parent +Child one +Child two +Sibling \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-nested-depth-2/expected/mimetype b/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-nested-depth-2/expected/mimetype new file mode 100644 index 0000000..24e7ded --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-nested-depth-2/expected/mimetype @@ -0,0 +1 @@ +application/hwp+zip \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-nested-depth-2/expected/settings.xml b/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-nested-depth-2/expected/settings.xml new file mode 100644 index 0000000..ea050df --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-nested-depth-2/expected/settings.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-nested-depth-2/expected/version.xml b/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-nested-depth-2/expected/version.xml new file mode 100644 index 0000000..549bb34 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-nested-depth-2/expected/version.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-nested-depth-2/fixture.toml b/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-nested-depth-2/fixture.toml new file mode 100644 index 0000000..43b5910 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-nested-depth-2/fixture.toml @@ -0,0 +1,4 @@ +title = "HWPX Nested List Candidate" +author = "Fixture Author" +strict_mode = false +determinism = false diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-nested-depth-2/golden.hwpx b/packages/core-rs/tests/fixtures/hwpx/approved/ordered-list-nested-depth-2/golden.hwpx new file mode 100644 index 0000000000000000000000000000000000000000..0994c50c9b7ca9b3efddee5e10d1c94426116da4 GIT binary patch literal 27665 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 Color Candidateko(주)한글과컴퓨터limchaesung2004-11-09T15:23:46Z2026-03-08T07:30:22Z2017년 8월 3일 목요일 오후 4:55:39 \ No newline at end of file +Brand Color Candidateko(주)한글과컴퓨터docxly2004-11-09T15:23:46Z2026-03-08T07:30:22Z2017년 8월 3일 목요일 오후 4:55:39 \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/style-brand-color/expected/Preview/PrvImage.png.sha256 b/packages/core-rs/tests/fixtures/hwpx/approved/style-brand-color/expected/Preview/PrvImage.png.sha256 index b52266d..21d6e53 100644 --- a/packages/core-rs/tests/fixtures/hwpx/approved/style-brand-color/expected/Preview/PrvImage.png.sha256 +++ b/packages/core-rs/tests/fixtures/hwpx/approved/style-brand-color/expected/Preview/PrvImage.png.sha256 @@ -1 +1 @@ -a3cc714d6645caa3b0735b03d85d295b46cc486218b1d676fb74adcf4019df24 +a3cc714d6645caa3b0735b03d85d295b46cc486218b1d676fb74adcf4019df24 \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/style-brand-color/expected/version.xml b/packages/core-rs/tests/fixtures/hwpx/approved/style-brand-color/expected/version.xml index 4ee1300..549bb34 100644 --- a/packages/core-rs/tests/fixtures/hwpx/approved/style-brand-color/expected/version.xml +++ b/packages/core-rs/tests/fixtures/hwpx/approved/style-brand-color/expected/version.xml @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/style-brand-color/golden.hwpx b/packages/core-rs/tests/fixtures/hwpx/approved/style-brand-color/golden.hwpx index 887ffe34a60543e73dbc98c48adc126ae6b8ce65..e747034e51f27bbe1085df2fb908f065ae4978ac 100644 GIT binary patch delta 7041 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 literal 73675 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 diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/style-brand-color/hash.txt b/packages/core-rs/tests/fixtures/hwpx/approved/style-brand-color/hash.txt index 4057b40..cd6d759 100644 --- a/packages/core-rs/tests/fixtures/hwpx/approved/style-brand-color/hash.txt +++ b/packages/core-rs/tests/fixtures/hwpx/approved/style-brand-color/hash.txt @@ -1 +1 @@ -d7817831c51ff7f09db2562ec08d6dc2b76b1dfe9c494e081476ab5a84d15c32 +2782296730e4652f9b6fd5b33b97c4f98b8e6e19fd7a02c5b5e5b50e1b578036 \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/style-centered-layout/expected/Contents/content.hpf b/packages/core-rs/tests/fixtures/hwpx/approved/style-centered-layout/expected/Contents/content.hpf index 481cb7f..2028259 100644 --- a/packages/core-rs/tests/fixtures/hwpx/approved/style-centered-layout/expected/Contents/content.hpf +++ b/packages/core-rs/tests/fixtures/hwpx/approved/style-centered-layout/expected/Contents/content.hpf @@ -1 +1 @@ -Centered Layout Candidateko(주)한글과컴퓨터limchaesung2004-11-09T15:23:46Z2026-03-08T07:30:22Z2017년 8월 3일 목요일 오후 4:55:39 \ No newline at end of file +Centered Layout Candidateko(주)한글과컴퓨터docxly2004-11-09T15:23:46Z2026-03-08T07:30:22Z2017년 8월 3일 목요일 오후 4:55:39 \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/style-centered-layout/expected/Preview/PrvImage.png.sha256 b/packages/core-rs/tests/fixtures/hwpx/approved/style-centered-layout/expected/Preview/PrvImage.png.sha256 index b52266d..21d6e53 100644 --- a/packages/core-rs/tests/fixtures/hwpx/approved/style-centered-layout/expected/Preview/PrvImage.png.sha256 +++ b/packages/core-rs/tests/fixtures/hwpx/approved/style-centered-layout/expected/Preview/PrvImage.png.sha256 @@ -1 +1 @@ -a3cc714d6645caa3b0735b03d85d295b46cc486218b1d676fb74adcf4019df24 +a3cc714d6645caa3b0735b03d85d295b46cc486218b1d676fb74adcf4019df24 \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/style-centered-layout/expected/version.xml b/packages/core-rs/tests/fixtures/hwpx/approved/style-centered-layout/expected/version.xml index 4ee1300..549bb34 100644 --- a/packages/core-rs/tests/fixtures/hwpx/approved/style-centered-layout/expected/version.xml +++ b/packages/core-rs/tests/fixtures/hwpx/approved/style-centered-layout/expected/version.xml @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/style-centered-layout/golden.hwpx b/packages/core-rs/tests/fixtures/hwpx/approved/style-centered-layout/golden.hwpx index 4215cc05e996f0059ba43c842f83f3a194b5500a..43c6a94aaa56438646b41aa80182a96492744e59 100644 GIT binary patch delta 6980 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 JTmNCp{{XBxrC9&~ literal 73766 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 su;Jl;vFMK#tc1n{Typography Candidateko(주)한글과컴퓨터limchaesung2004-11-09T15:23:46Z2026-03-08T07:30:22Z2017년 8월 3일 목요일 오후 4:55:39 \ No newline at end of file +Typography Candidateko(주)한글과컴퓨터docxly2004-11-09T15:23:46Z2026-03-08T07:30:22Z2017년 8월 3일 목요일 오후 4:55:39 \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/style-typography/expected/Preview/PrvImage.png.sha256 b/packages/core-rs/tests/fixtures/hwpx/approved/style-typography/expected/Preview/PrvImage.png.sha256 index b52266d..21d6e53 100644 --- a/packages/core-rs/tests/fixtures/hwpx/approved/style-typography/expected/Preview/PrvImage.png.sha256 +++ b/packages/core-rs/tests/fixtures/hwpx/approved/style-typography/expected/Preview/PrvImage.png.sha256 @@ -1 +1 @@ -a3cc714d6645caa3b0735b03d85d295b46cc486218b1d676fb74adcf4019df24 +a3cc714d6645caa3b0735b03d85d295b46cc486218b1d676fb74adcf4019df24 \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/style-typography/expected/version.xml b/packages/core-rs/tests/fixtures/hwpx/approved/style-typography/expected/version.xml index 4ee1300..549bb34 100644 --- a/packages/core-rs/tests/fixtures/hwpx/approved/style-typography/expected/version.xml +++ b/packages/core-rs/tests/fixtures/hwpx/approved/style-typography/expected/version.xml @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/style-typography/golden.hwpx b/packages/core-rs/tests/fixtures/hwpx/approved/style-typography/golden.hwpx index d609296e89fa659cd99d326a60c0cfe9e01aa14c..70f7ec54a524cf191f868e349b4bf41d29f24db9 100644 GIT binary patch delta 6907 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 Puqzq>{?k31Kau|*hHq}0 literal 73743 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 Table Candidateko(주)한글과컴퓨터docxly2004-11-09T15:23:46Z2026-03-08T07:30:22Z2017년 8월 3일 목요일 오후 4:55:39 \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/table-alignment/expected/Contents/header.xml b/packages/core-rs/tests/fixtures/hwpx/approved/table-alignment/expected/Contents/header.xml new file mode 100644 index 0000000..20b1833 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/table-alignment/expected/Contents/header.xml @@ -0,0 +1 @@ +^1.^2.^3)^4)(^5)(^6)^7 \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/table-alignment/expected/Contents/section0.xml b/packages/core-rs/tests/fixtures/hwpx/approved/table-alignment/expected/Contents/section0.xml new file mode 100644 index 0000000..5d70224 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/table-alignment/expected/Contents/section0.xml @@ -0,0 +1 @@ +NameRoleAWriterBReviewer \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/table-alignment/expected/META-INF/container.rdf b/packages/core-rs/tests/fixtures/hwpx/approved/table-alignment/expected/META-INF/container.rdf new file mode 100644 index 0000000..8df472d --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/table-alignment/expected/META-INF/container.rdf @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/table-alignment/expected/META-INF/container.xml b/packages/core-rs/tests/fixtures/hwpx/approved/table-alignment/expected/META-INF/container.xml new file mode 100644 index 0000000..e0cc34c --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/table-alignment/expected/META-INF/container.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/table-alignment/expected/META-INF/manifest.xml b/packages/core-rs/tests/fixtures/hwpx/approved/table-alignment/expected/META-INF/manifest.xml new file mode 100644 index 0000000..5530619 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/table-alignment/expected/META-INF/manifest.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/table-alignment/expected/Preview/PrvImage.png b/packages/core-rs/tests/fixtures/hwpx/approved/table-alignment/expected/Preview/PrvImage.png new file mode 100644 index 0000000000000000000000000000000000000000..43c6c2deb59664f95ad175892df176567ba81c34 GIT binary patch literal 20421 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 literal 0 HcmV?d00001 diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/table-alignment/expected/Preview/PrvText.txt b/packages/core-rs/tests/fixtures/hwpx/approved/table-alignment/expected/Preview/PrvText.txt new file mode 100644 index 0000000..972c5fd --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/table-alignment/expected/Preview/PrvText.txt @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/table-alignment/expected/mimetype b/packages/core-rs/tests/fixtures/hwpx/approved/table-alignment/expected/mimetype new file mode 100644 index 0000000..24e7ded --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/table-alignment/expected/mimetype @@ -0,0 +1 @@ +application/hwp+zip \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/table-alignment/expected/settings.xml b/packages/core-rs/tests/fixtures/hwpx/approved/table-alignment/expected/settings.xml new file mode 100644 index 0000000..a05a6f8 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/table-alignment/expected/settings.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/table-alignment/expected/version.xml b/packages/core-rs/tests/fixtures/hwpx/approved/table-alignment/expected/version.xml new file mode 100644 index 0000000..549bb34 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/table-alignment/expected/version.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/table-alignment/fixture.toml b/packages/core-rs/tests/fixtures/hwpx/approved/table-alignment/fixture.toml new file mode 100644 index 0000000..0a2ff7c --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/table-alignment/fixture.toml @@ -0,0 +1,4 @@ +title = "HWPX Table Candidate" +author = "Fixture Author" +strict_mode = false +determinism = false diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/table-alignment/golden.hwpx b/packages/core-rs/tests/fixtures/hwpx/approved/table-alignment/golden.hwpx new file mode 100644 index 0000000000000000000000000000000000000000..f3a675bb9ce795c0d5ff3f2ae37f22758b24f5ca GIT binary patch literal 28060 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 literal 0 HcmV?d00001 diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/table-alignment/hash.txt b/packages/core-rs/tests/fixtures/hwpx/approved/table-alignment/hash.txt new file mode 100644 index 0000000..1594d10 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/table-alignment/hash.txt @@ -0,0 +1 @@ +0293b64b18d990a04b6b153607a3199d4ae77a3bfc0fd8d6328e5a0578b2458f \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/table-alignment/input.md b/packages/core-rs/tests/fixtures/hwpx/approved/table-alignment/input.md new file mode 100644 index 0000000..2d56c2e --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/table-alignment/input.md @@ -0,0 +1,4 @@ +| Name | Role | +| --- | --- | +| A | Writer | +| B | Reviewer | \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/table-basic/expected/Contents/content.hpf b/packages/core-rs/tests/fixtures/hwpx/approved/table-basic/expected/Contents/content.hpf new file mode 100644 index 0000000..ec9e37e --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/table-basic/expected/Contents/content.hpf @@ -0,0 +1 @@ +HWPX Table Candidateko(주)한글과컴퓨터docxly2004-11-09T15:23:46Z2026-03-08T07:30:22Z2017년 8월 3일 목요일 오후 4:55:39 \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/table-basic/expected/Contents/header.xml b/packages/core-rs/tests/fixtures/hwpx/approved/table-basic/expected/Contents/header.xml new file mode 100644 index 0000000..20b1833 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/table-basic/expected/Contents/header.xml @@ -0,0 +1 @@ +^1.^2.^3)^4)(^5)(^6)^7 \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/table-basic/expected/Contents/section0.xml b/packages/core-rs/tests/fixtures/hwpx/approved/table-basic/expected/Contents/section0.xml new file mode 100644 index 0000000..5d70224 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/table-basic/expected/Contents/section0.xml @@ -0,0 +1 @@ +NameRoleAWriterBReviewer \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/table-basic/expected/META-INF/container.rdf b/packages/core-rs/tests/fixtures/hwpx/approved/table-basic/expected/META-INF/container.rdf new file mode 100644 index 0000000..8df472d --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/table-basic/expected/META-INF/container.rdf @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/table-basic/expected/META-INF/container.xml b/packages/core-rs/tests/fixtures/hwpx/approved/table-basic/expected/META-INF/container.xml new file mode 100644 index 0000000..e0cc34c --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/table-basic/expected/META-INF/container.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/table-basic/expected/META-INF/manifest.xml b/packages/core-rs/tests/fixtures/hwpx/approved/table-basic/expected/META-INF/manifest.xml new file mode 100644 index 0000000..5530619 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/table-basic/expected/META-INF/manifest.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/table-basic/expected/Preview/PrvImage.png b/packages/core-rs/tests/fixtures/hwpx/approved/table-basic/expected/Preview/PrvImage.png new file mode 100644 index 0000000000000000000000000000000000000000..43c6c2deb59664f95ad175892df176567ba81c34 GIT binary patch literal 20421 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 literal 0 HcmV?d00001 diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/table-basic/expected/Preview/PrvText.txt b/packages/core-rs/tests/fixtures/hwpx/approved/table-basic/expected/Preview/PrvText.txt new file mode 100644 index 0000000..972c5fd --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/table-basic/expected/Preview/PrvText.txt @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/table-basic/expected/mimetype b/packages/core-rs/tests/fixtures/hwpx/approved/table-basic/expected/mimetype new file mode 100644 index 0000000..24e7ded --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/table-basic/expected/mimetype @@ -0,0 +1 @@ +application/hwp+zip \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/table-basic/expected/settings.xml b/packages/core-rs/tests/fixtures/hwpx/approved/table-basic/expected/settings.xml new file mode 100644 index 0000000..a05a6f8 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/table-basic/expected/settings.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/table-basic/expected/version.xml b/packages/core-rs/tests/fixtures/hwpx/approved/table-basic/expected/version.xml new file mode 100644 index 0000000..549bb34 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/table-basic/expected/version.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/table-basic/fixture.toml b/packages/core-rs/tests/fixtures/hwpx/approved/table-basic/fixture.toml new file mode 100644 index 0000000..0a2ff7c --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/table-basic/fixture.toml @@ -0,0 +1,4 @@ +title = "HWPX Table Candidate" +author = "Fixture Author" +strict_mode = false +determinism = false diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/table-basic/golden.hwpx b/packages/core-rs/tests/fixtures/hwpx/approved/table-basic/golden.hwpx new file mode 100644 index 0000000000000000000000000000000000000000..f3a675bb9ce795c0d5ff3f2ae37f22758b24f5ca GIT binary patch literal 28060 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 literal 0 HcmV?d00001 diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/table-basic/hash.txt b/packages/core-rs/tests/fixtures/hwpx/approved/table-basic/hash.txt new file mode 100644 index 0000000..1594d10 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/table-basic/hash.txt @@ -0,0 +1 @@ +0293b64b18d990a04b6b153607a3199d4ae77a3bfc0fd8d6328e5a0578b2458f \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/approved/table-basic/input.md b/packages/core-rs/tests/fixtures/hwpx/approved/table-basic/input.md new file mode 100644 index 0000000..2d56c2e --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/approved/table-basic/input.md @@ -0,0 +1,4 @@ +| Name | Role | +| --- | --- | +| A | Writer | +| B | Reviewer | \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/provisional/README.md b/packages/core-rs/tests/fixtures/hwpx/provisional/README.md index c148170..23518df 100644 --- a/packages/core-rs/tests/fixtures/hwpx/provisional/README.md +++ b/packages/core-rs/tests/fixtures/hwpx/provisional/README.md @@ -13,4 +13,7 @@ Rules: Current status: -- No provisional fixtures remain. +- `image-data-uri-basic` +- `mixed-rich` +- `table-alignment` +- `thematic-break-basic` diff --git a/packages/core-rs/tests/fixtures/hwpx/provisional/image-data-uri-basic/expected/Contents/content.hpf b/packages/core-rs/tests/fixtures/hwpx/provisional/image-data-uri-basic/expected/Contents/content.hpf new file mode 100644 index 0000000..de63037 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/provisional/image-data-uri-basic/expected/Contents/content.hpf @@ -0,0 +1 @@ +HWPX Data Image Candidateko(주)한글과컴퓨터docxly2004-11-09T15:23:46Z2026-03-08T07:30:22Z2017년 8월 3일 목요일 오후 4:55:39 \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/provisional/image-data-uri-basic/expected/Contents/header.xml b/packages/core-rs/tests/fixtures/hwpx/provisional/image-data-uri-basic/expected/Contents/header.xml new file mode 100644 index 0000000..0d6bf34 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/provisional/image-data-uri-basic/expected/Contents/header.xml @@ -0,0 +1 @@ +^1.^2.^3)^4)(^5)(^6)^7 \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/provisional/image-data-uri-basic/expected/Contents/section0.xml b/packages/core-rs/tests/fixtures/hwpx/provisional/image-data-uri-basic/expected/Contents/section0.xml new file mode 100644 index 0000000..b8832ee --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/provisional/image-data-uri-basic/expected/Contents/section0.xml @@ -0,0 +1 @@ +tiny \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/provisional/image-data-uri-basic/expected/META-INF/container.rdf b/packages/core-rs/tests/fixtures/hwpx/provisional/image-data-uri-basic/expected/META-INF/container.rdf new file mode 100644 index 0000000..8df472d --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/provisional/image-data-uri-basic/expected/META-INF/container.rdf @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/provisional/image-data-uri-basic/expected/META-INF/container.xml b/packages/core-rs/tests/fixtures/hwpx/provisional/image-data-uri-basic/expected/META-INF/container.xml new file mode 100644 index 0000000..e0cc34c --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/provisional/image-data-uri-basic/expected/META-INF/container.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/provisional/image-data-uri-basic/expected/META-INF/manifest.xml b/packages/core-rs/tests/fixtures/hwpx/provisional/image-data-uri-basic/expected/META-INF/manifest.xml new file mode 100644 index 0000000..5530619 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/provisional/image-data-uri-basic/expected/META-INF/manifest.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/provisional/image-data-uri-basic/expected/Preview/PrvImage.png.sha256 b/packages/core-rs/tests/fixtures/hwpx/provisional/image-data-uri-basic/expected/Preview/PrvImage.png.sha256 new file mode 100644 index 0000000..b52266d --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/provisional/image-data-uri-basic/expected/Preview/PrvImage.png.sha256 @@ -0,0 +1 @@ +a3cc714d6645caa3b0735b03d85d295b46cc486218b1d676fb74adcf4019df24 diff --git a/packages/core-rs/tests/fixtures/hwpx/provisional/image-data-uri-basic/expected/Preview/PrvText.txt b/packages/core-rs/tests/fixtures/hwpx/provisional/image-data-uri-basic/expected/Preview/PrvText.txt new file mode 100644 index 0000000..18a34fb --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/provisional/image-data-uri-basic/expected/Preview/PrvText.txt @@ -0,0 +1 @@ +tiny \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/provisional/image-data-uri-basic/expected/mimetype b/packages/core-rs/tests/fixtures/hwpx/provisional/image-data-uri-basic/expected/mimetype new file mode 100644 index 0000000..24e7ded --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/provisional/image-data-uri-basic/expected/mimetype @@ -0,0 +1 @@ +application/hwp+zip \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/provisional/image-data-uri-basic/expected/settings.xml b/packages/core-rs/tests/fixtures/hwpx/provisional/image-data-uri-basic/expected/settings.xml new file mode 100644 index 0000000..5d51824 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/provisional/image-data-uri-basic/expected/settings.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/provisional/image-data-uri-basic/expected/version.xml b/packages/core-rs/tests/fixtures/hwpx/provisional/image-data-uri-basic/expected/version.xml new file mode 100644 index 0000000..549bb34 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/provisional/image-data-uri-basic/expected/version.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/provisional/image-data-uri-basic/fixture.toml b/packages/core-rs/tests/fixtures/hwpx/provisional/image-data-uri-basic/fixture.toml new file mode 100644 index 0000000..acc1a14 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/provisional/image-data-uri-basic/fixture.toml @@ -0,0 +1,4 @@ +title = "HWPX Data Image Candidate" +author = "Fixture Author" +strict_mode = false +determinism = false diff --git a/packages/core-rs/tests/fixtures/hwpx/provisional/image-data-uri-basic/golden.hwpx b/packages/core-rs/tests/fixtures/hwpx/provisional/image-data-uri-basic/golden.hwpx new file mode 100644 index 0000000000000000000000000000000000000000..8fd4babc8d53138c1613cb3a008513d08f44c437 GIT binary patch literal 27461 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 literal 0 HcmV?d00001 diff --git a/packages/core-rs/tests/fixtures/hwpx/provisional/image-data-uri-basic/hash.txt b/packages/core-rs/tests/fixtures/hwpx/provisional/image-data-uri-basic/hash.txt new file mode 100644 index 0000000..cad948e --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/provisional/image-data-uri-basic/hash.txt @@ -0,0 +1 @@ +934b64417159b0b81479018f6bf260cdaf41a4dd57c33a7f2022020b69d21fd4 diff --git a/packages/core-rs/tests/fixtures/hwpx/provisional/image-data-uri-basic/input.md b/packages/core-rs/tests/fixtures/hwpx/provisional/image-data-uri-basic/input.md new file mode 100644 index 0000000..251d167 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/provisional/image-data-uri-basic/input.md @@ -0,0 +1 @@ +![tiny](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAusB9Wn0gWQAAAAASUVORK5CYII=) \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/provisional/mixed-rich/expected/Contents/content.hpf b/packages/core-rs/tests/fixtures/hwpx/provisional/mixed-rich/expected/Contents/content.hpf new file mode 100644 index 0000000..88f92f1 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/provisional/mixed-rich/expected/Contents/content.hpf @@ -0,0 +1 @@ +HWPX Mixed Rich Candidateko(주)한글과컴퓨터docxly2004-11-09T15:23:46Z2026-03-08T07:30:22Z2017년 8월 3일 목요일 오후 4:55:39 \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/provisional/mixed-rich/expected/Contents/header.xml b/packages/core-rs/tests/fixtures/hwpx/provisional/mixed-rich/expected/Contents/header.xml new file mode 100644 index 0000000..74ff658 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/provisional/mixed-rich/expected/Contents/header.xml @@ -0,0 +1 @@ +^1.^2.^3)^4)(^5)(^6)^7 \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/provisional/mixed-rich/expected/Contents/section0.xml b/packages/core-rs/tests/fixtures/hwpx/provisional/mixed-rich/expected/Contents/section0.xml new file mode 100644 index 0000000..9004ffe --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/provisional/mixed-rich/expected/Contents/section0.xml @@ -0,0 +1 @@ +Rich MixQuoteOneTwocodeName | RoleA | Writer---tiny \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/provisional/mixed-rich/expected/META-INF/container.rdf b/packages/core-rs/tests/fixtures/hwpx/provisional/mixed-rich/expected/META-INF/container.rdf new file mode 100644 index 0000000..8df472d --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/provisional/mixed-rich/expected/META-INF/container.rdf @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/provisional/mixed-rich/expected/META-INF/container.xml b/packages/core-rs/tests/fixtures/hwpx/provisional/mixed-rich/expected/META-INF/container.xml new file mode 100644 index 0000000..e0cc34c --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/provisional/mixed-rich/expected/META-INF/container.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/provisional/mixed-rich/expected/META-INF/manifest.xml b/packages/core-rs/tests/fixtures/hwpx/provisional/mixed-rich/expected/META-INF/manifest.xml new file mode 100644 index 0000000..5530619 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/provisional/mixed-rich/expected/META-INF/manifest.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/provisional/mixed-rich/expected/Preview/PrvImage.png.sha256 b/packages/core-rs/tests/fixtures/hwpx/provisional/mixed-rich/expected/Preview/PrvImage.png.sha256 new file mode 100644 index 0000000..b52266d --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/provisional/mixed-rich/expected/Preview/PrvImage.png.sha256 @@ -0,0 +1 @@ +a3cc714d6645caa3b0735b03d85d295b46cc486218b1d676fb74adcf4019df24 diff --git a/packages/core-rs/tests/fixtures/hwpx/provisional/mixed-rich/expected/Preview/PrvText.txt b/packages/core-rs/tests/fixtures/hwpx/provisional/mixed-rich/expected/Preview/PrvText.txt new file mode 100644 index 0000000..8070c7a --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/provisional/mixed-rich/expected/Preview/PrvText.txt @@ -0,0 +1,9 @@ +Rich Mix +Quote +One +Two +code +Name | Role +A | Writer +--- +tiny \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/provisional/mixed-rich/expected/mimetype b/packages/core-rs/tests/fixtures/hwpx/provisional/mixed-rich/expected/mimetype new file mode 100644 index 0000000..24e7ded --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/provisional/mixed-rich/expected/mimetype @@ -0,0 +1 @@ +application/hwp+zip \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/provisional/mixed-rich/expected/settings.xml b/packages/core-rs/tests/fixtures/hwpx/provisional/mixed-rich/expected/settings.xml new file mode 100644 index 0000000..5d51824 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/provisional/mixed-rich/expected/settings.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/provisional/mixed-rich/expected/version.xml b/packages/core-rs/tests/fixtures/hwpx/provisional/mixed-rich/expected/version.xml new file mode 100644 index 0000000..549bb34 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/provisional/mixed-rich/expected/version.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/provisional/mixed-rich/fixture.toml b/packages/core-rs/tests/fixtures/hwpx/provisional/mixed-rich/fixture.toml new file mode 100644 index 0000000..ebf307d --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/provisional/mixed-rich/fixture.toml @@ -0,0 +1,4 @@ +title = "HWPX Mixed Rich Candidate" +author = "Fixture Author" +strict_mode = false +determinism = false diff --git a/packages/core-rs/tests/fixtures/hwpx/provisional/mixed-rich/golden.hwpx b/packages/core-rs/tests/fixtures/hwpx/provisional/mixed-rich/golden.hwpx new file mode 100644 index 0000000000000000000000000000000000000000..2f51e0e8e1e36218f3866b0bc59c0e06fceb22ae GIT binary patch literal 27897 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 Quote + +- One +- Two + +```txt +code +``` + +| Name | Role | +| --- | --- | +| A | Writer | + +--- + +![tiny](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAusB9Wn0gWQAAAAASUVORK5CYII=) \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/provisional/table-alignment/expected/Contents/content.hpf b/packages/core-rs/tests/fixtures/hwpx/provisional/table-alignment/expected/Contents/content.hpf new file mode 100644 index 0000000..fe7e634 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/provisional/table-alignment/expected/Contents/content.hpf @@ -0,0 +1 @@ +HWPX Table Alignment Candidateko(주)한글과컴퓨터docxly2004-11-09T15:23:46Z2026-03-08T07:30:22Z2017년 8월 3일 목요일 오후 4:55:39 \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/provisional/table-alignment/expected/Contents/header.xml b/packages/core-rs/tests/fixtures/hwpx/provisional/table-alignment/expected/Contents/header.xml new file mode 100644 index 0000000..0d6bf34 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/provisional/table-alignment/expected/Contents/header.xml @@ -0,0 +1 @@ +^1.^2.^3)^4)(^5)(^6)^7 \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/provisional/table-alignment/expected/Contents/section0.xml b/packages/core-rs/tests/fixtures/hwpx/provisional/table-alignment/expected/Contents/section0.xml new file mode 100644 index 0000000..f6afebb --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/provisional/table-alignment/expected/Contents/section0.xml @@ -0,0 +1 @@ +Left | Center | RightA | B | C \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/provisional/table-alignment/expected/META-INF/container.rdf b/packages/core-rs/tests/fixtures/hwpx/provisional/table-alignment/expected/META-INF/container.rdf new file mode 100644 index 0000000..8df472d --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/provisional/table-alignment/expected/META-INF/container.rdf @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/provisional/table-alignment/expected/META-INF/container.xml b/packages/core-rs/tests/fixtures/hwpx/provisional/table-alignment/expected/META-INF/container.xml new file mode 100644 index 0000000..e0cc34c --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/provisional/table-alignment/expected/META-INF/container.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/provisional/table-alignment/expected/META-INF/manifest.xml b/packages/core-rs/tests/fixtures/hwpx/provisional/table-alignment/expected/META-INF/manifest.xml new file mode 100644 index 0000000..5530619 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/provisional/table-alignment/expected/META-INF/manifest.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/provisional/table-alignment/expected/Preview/PrvImage.png.sha256 b/packages/core-rs/tests/fixtures/hwpx/provisional/table-alignment/expected/Preview/PrvImage.png.sha256 new file mode 100644 index 0000000..b52266d --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/provisional/table-alignment/expected/Preview/PrvImage.png.sha256 @@ -0,0 +1 @@ +a3cc714d6645caa3b0735b03d85d295b46cc486218b1d676fb74adcf4019df24 diff --git a/packages/core-rs/tests/fixtures/hwpx/provisional/table-alignment/expected/Preview/PrvText.txt b/packages/core-rs/tests/fixtures/hwpx/provisional/table-alignment/expected/Preview/PrvText.txt new file mode 100644 index 0000000..8f912f0 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/provisional/table-alignment/expected/Preview/PrvText.txt @@ -0,0 +1,2 @@ +Left | Center | Right +A | B | C \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/provisional/table-alignment/expected/mimetype b/packages/core-rs/tests/fixtures/hwpx/provisional/table-alignment/expected/mimetype new file mode 100644 index 0000000..24e7ded --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/provisional/table-alignment/expected/mimetype @@ -0,0 +1 @@ +application/hwp+zip \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/provisional/table-alignment/expected/settings.xml b/packages/core-rs/tests/fixtures/hwpx/provisional/table-alignment/expected/settings.xml new file mode 100644 index 0000000..5d51824 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/provisional/table-alignment/expected/settings.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/provisional/table-alignment/expected/version.xml b/packages/core-rs/tests/fixtures/hwpx/provisional/table-alignment/expected/version.xml new file mode 100644 index 0000000..549bb34 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/provisional/table-alignment/expected/version.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/provisional/table-alignment/fixture.toml b/packages/core-rs/tests/fixtures/hwpx/provisional/table-alignment/fixture.toml new file mode 100644 index 0000000..05e4cd8 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/provisional/table-alignment/fixture.toml @@ -0,0 +1,4 @@ +title = "HWPX Table Alignment Candidate" +author = "Fixture Author" +strict_mode = false +determinism = false diff --git a/packages/core-rs/tests/fixtures/hwpx/provisional/table-alignment/golden.hwpx b/packages/core-rs/tests/fixtures/hwpx/provisional/table-alignment/golden.hwpx new file mode 100644 index 0000000000000000000000000000000000000000..fe9022103aae483a7209a468cbef2ffc9a545ec7 GIT binary patch literal 27518 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≠6996~SBA>4|_b$Sncr>Jrprr)D=gb2oZG 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 Thematic Break Candidateko(주)한글과컴퓨터docxly2004-11-09T15:23:46Z2026-03-08T07:30:22Z2017년 8월 3일 목요일 오후 4:55:39 \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/provisional/thematic-break-basic/expected/Contents/header.xml b/packages/core-rs/tests/fixtures/hwpx/provisional/thematic-break-basic/expected/Contents/header.xml new file mode 100644 index 0000000..0d6bf34 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/provisional/thematic-break-basic/expected/Contents/header.xml @@ -0,0 +1 @@ +^1.^2.^3)^4)(^5)(^6)^7 \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/provisional/thematic-break-basic/expected/Contents/section0.xml b/packages/core-rs/tests/fixtures/hwpx/provisional/thematic-break-basic/expected/Contents/section0.xml new file mode 100644 index 0000000..1f09864 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/provisional/thematic-break-basic/expected/Contents/section0.xml @@ -0,0 +1 @@ +Before---After \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/provisional/thematic-break-basic/expected/META-INF/container.rdf b/packages/core-rs/tests/fixtures/hwpx/provisional/thematic-break-basic/expected/META-INF/container.rdf new file mode 100644 index 0000000..8df472d --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/provisional/thematic-break-basic/expected/META-INF/container.rdf @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/provisional/thematic-break-basic/expected/META-INF/container.xml b/packages/core-rs/tests/fixtures/hwpx/provisional/thematic-break-basic/expected/META-INF/container.xml new file mode 100644 index 0000000..e0cc34c --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/provisional/thematic-break-basic/expected/META-INF/container.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/provisional/thematic-break-basic/expected/META-INF/manifest.xml b/packages/core-rs/tests/fixtures/hwpx/provisional/thematic-break-basic/expected/META-INF/manifest.xml new file mode 100644 index 0000000..5530619 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/provisional/thematic-break-basic/expected/META-INF/manifest.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/provisional/thematic-break-basic/expected/Preview/PrvImage.png.sha256 b/packages/core-rs/tests/fixtures/hwpx/provisional/thematic-break-basic/expected/Preview/PrvImage.png.sha256 new file mode 100644 index 0000000..b52266d --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/provisional/thematic-break-basic/expected/Preview/PrvImage.png.sha256 @@ -0,0 +1 @@ +a3cc714d6645caa3b0735b03d85d295b46cc486218b1d676fb74adcf4019df24 diff --git a/packages/core-rs/tests/fixtures/hwpx/provisional/thematic-break-basic/expected/Preview/PrvText.txt b/packages/core-rs/tests/fixtures/hwpx/provisional/thematic-break-basic/expected/Preview/PrvText.txt new file mode 100644 index 0000000..1cdf911 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/provisional/thematic-break-basic/expected/Preview/PrvText.txt @@ -0,0 +1,3 @@ +Before +--- +After \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/provisional/thematic-break-basic/expected/mimetype b/packages/core-rs/tests/fixtures/hwpx/provisional/thematic-break-basic/expected/mimetype new file mode 100644 index 0000000..24e7ded --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/provisional/thematic-break-basic/expected/mimetype @@ -0,0 +1 @@ +application/hwp+zip \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/provisional/thematic-break-basic/expected/settings.xml b/packages/core-rs/tests/fixtures/hwpx/provisional/thematic-break-basic/expected/settings.xml new file mode 100644 index 0000000..5d51824 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/provisional/thematic-break-basic/expected/settings.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/provisional/thematic-break-basic/expected/version.xml b/packages/core-rs/tests/fixtures/hwpx/provisional/thematic-break-basic/expected/version.xml new file mode 100644 index 0000000..549bb34 --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/provisional/thematic-break-basic/expected/version.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core-rs/tests/fixtures/hwpx/provisional/thematic-break-basic/fixture.toml b/packages/core-rs/tests/fixtures/hwpx/provisional/thematic-break-basic/fixture.toml new file mode 100644 index 0000000..790468f --- /dev/null +++ b/packages/core-rs/tests/fixtures/hwpx/provisional/thematic-break-basic/fixture.toml @@ -0,0 +1,4 @@ +title = "HWPX Thematic Break Candidate" +author = "Fixture Author" +strict_mode = false +determinism = false diff --git a/packages/core-rs/tests/fixtures/hwpx/provisional/thematic-break-basic/golden.hwpx b/packages/core-rs/tests/fixtures/hwpx/provisional/thematic-break-basic/golden.hwpx new file mode 100644 index 0000000000000000000000000000000000000000..0d67fa560e1dfe40aa5d36e66208a6db5dbf9ceb GIT binary patch literal 27505 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")); + assert!(section.contains("Beta")); + assert!(section.contains("vertpos=\"0\"")); + assert!(section.contains("vertpos=\"2300\"")); + assert!(section.contains("horzpos=\"1100\" horzsize=\"41420\" flags=\"2490368\"")); + assert!(!section.contains("- Alpha")); + assert!(!section.contains("- Beta")); + assert!(!section.contains('•')); + assert!(!section.contains("paraPrIDRef=\"14\"")); +} + +#[test] +fn nested_unordered_lists_use_second_list_level() { + let markdown = "- Parent\n - Child one\n - Child two\n- Sibling"; + let generated = generate(markdown, HwpxOptions::default()).unwrap(); + + let section = generated.section_xml().unwrap(); + assert!(section.contains("paraPrIDRef=\"19\"")); + assert!(section.contains("paraPrIDRef=\"20\"")); + assert!(section.contains("styleIDRef=\"0\"")); + assert!(section.contains("Parent")); + assert!(section.contains("Child one")); + assert!(section.contains("Child two")); + assert!(section.contains("Sibling")); + assert!(section.contains("vertpos=\"6900\"")); + assert!(section.contains("horzpos=\"1100\" horzsize=\"41420\" flags=\"2490368\"")); + assert!(section.contains("horzpos=\"2200\" horzsize=\"40320\" flags=\"2490368\"")); + assert!(!section.contains("- Parent")); + assert!(!section.contains("- Child one")); + assert!(!section.contains("- Child two")); + assert!(!section.contains("- Sibling")); + assert!(!section.contains('•')); + assert!(!section.contains('◦')); + assert!(!section.contains("paraPrIDRef=\"14\"")); + assert!(!section.contains("paraPrIDRef=\"15\"")); +} + +#[test] +fn header_reuses_existing_list_paragraph_contract() { + let generated = generate("- Alpha\n - Child", HwpxOptions::default()).unwrap(); + let header = generated.text_entry("Contents/header.xml").unwrap(); + + assert!(header.contains("")); + assert!(header.contains("")); + assert!(header.contains("")); + assert!(header.contains("Name")); + assert!(section.contains("Reviewer")); + assert!(!section.contains("Name | Role")); +} + +#[test] +fn table_preview_text_matches_golden_contract() { + let markdown = "| Name | Role |\n| --- | --- |\n| A | Writer |\n| B | Reviewer |"; + let generated = generate(markdown, HwpxOptions::default()).unwrap(); + let preview = generated.text_entry("Preview/PrvText.txt").unwrap(); + let settings = generated.text_entry("settings.xml").unwrap(); + + assert_eq!(preview, "\n\n"); + assert!(settings.contains("pos=\"16\"")); +} + +#[test] +fn tables_add_table_border_fill_to_header() { + let markdown = "| Name | Role |\n| --- | --- |\n| A | Writer |"; + let generated = generate(markdown, HwpxOptions::default()).unwrap(); + let header = generated.text_entry("Contents/header.xml").unwrap(); + + assert!(header.contains("")); + assert!(header.contains("")); + assert!(!header.contains("first")); + assert!(section.contains("second")); + assert!(!section.contains("1. first")); + assert!(!section.contains("2. second")); + assert_eq!(preview, "first\nsecond"); + assert!(settings.contains("paraIDRef=\"0\"")); + assert!(settings.contains("pos=\"5\"")); +} + +#[test] +fn nested_ordered_lists_use_shape_specific_contract_in_non_strict_mode() { + let markdown = "1. Parent\n 1. Child one\n 2. Child two\n2. Sibling"; + let generated = generate( + markdown, + HwpxOptions { + strict_mode: false, + ..HwpxOptions::default() + }, + ) + .unwrap(); + + let section = generated.section_xml().unwrap(); + let preview = generated.text_entry("Preview/PrvText.txt").unwrap(); + let settings = generated.text_entry("settings.xml").unwrap(); + + assert!(section.contains("paraPrIDRef=\"20\"")); + assert!(section.contains("paraPrIDRef=\"19\"")); + assert!(section.contains("Parent")); + assert!(section.contains("Child one")); + assert!(section.contains("Child two")); + assert!(section.contains("Sibling")); + assert!(section.contains("horzpos=\"1100\" horzsize=\"41420\" flags=\"2490368\"")); + assert!(section.contains("horzpos=\"2200\" horzsize=\"40320\" flags=\"2490368\"")); + assert!(!section.contains("1. Parent")); + assert!(!section.contains("1. Child one")); + assert!(!section.contains("2. Child two")); + assert!(!section.contains("2. Sibling")); + assert_eq!(preview, "Parent\nChild one\nChild two\nSibling"); + assert!(settings.contains("paraIDRef=\"3\"")); + assert!(settings.contains("pos=\"7\"")); +} + +#[test] +fn ordered_list_headers_use_numbering_contract() { + let basic = generate( + "1. Alpha\n2. Beta\n3. Gamma", + HwpxOptions { + strict_mode: false, + ..HwpxOptions::default() + }, + ) + .unwrap(); + let basic_header = basic.text_entry("Contents/header.xml").unwrap(); + assert!(basic_header.contains("")); + assert!(basic_header.contains("")); + assert!(basic_header.contains("")); + assert!(basic_header.contains("")); + + let nested = generate( + "1. Parent\n 1. Child\n2. Sibling", + HwpxOptions { + strict_mode: false, + ..HwpxOptions::default() + }, + ) + .unwrap(); + let nested_header = nested.text_entry("Contents/header.xml").unwrap(); + assert!(nested_header.contains("")); + assert!(nested_header.contains("")); + assert!(nested_header.contains("")); + assert!(nested_header.contains("")); + assert!(nested_header.contains("")); + assert!(nested_header.contains("")); +} + +#[test] +fn nested_lists_over_depth_two_fail_in_strict_mode() { + let markdown = "- A\n - B\n - C"; + let error = generate_hwpx(markdown, HwpxOptions::default()).unwrap_err(); + assert!(matches!(error, CoreRsError::UnsupportedFeature(_))); +} + +#[test] +fn nested_lists_over_depth_two_fall_back_in_non_strict_mode() { + let markdown = "- A\n - B\n - C"; + let generated = generate( + markdown, + HwpxOptions { + strict_mode: false, + ..HwpxOptions::default() + }, + ) + .unwrap(); + + let section = generated.section_xml().unwrap(); + assert!(section.contains("A")); + assert!(section.contains("B")); + assert!(section.contains("C")); + assert!(section.contains("paraPrIDRef=\"20\"")); + assert!(!section.contains('•')); + assert!(!section.contains('◦')); } #[test] @@ -62,7 +289,10 @@ fn content_hpf_contains_metadata() { let content_hpf = generated.content_hpf().unwrap(); assert!(content_hpf.contains("Fixture Title")); - assert!(content_hpf.contains("(주)한글과컴퓨터")); + assert!( + content_hpf + .contains("(주)한글과컴퓨터") + ); assert!(content_hpf.contains("href=\"Contents/header.xml\"")); assert!(content_hpf.contains("href=\"Contents/section0.xml\"")); assert!(content_hpf.contains("href=\"settings.xml\"")); @@ -160,9 +390,21 @@ fn paragraph_reference_contract_matches_hancom_sample_shape() { let reference = snapshot_from_expected_tree(&reference_root).unwrap(); let diff = compare_contracts(&reference, &generated); - assert!(diff.missing_entries.is_empty(), "missing entries: {:?}", diff.missing_entries); - assert!(diff.extra_entries.is_empty(), "extra entries: {:?}", diff.extra_entries); - assert!(diff.mismatched_roots.is_empty(), "root mismatch: {:?}", diff.mismatched_roots); + assert!( + diff.missing_entries.is_empty(), + "missing entries: {:?}", + diff.missing_entries + ); + assert!( + diff.extra_entries.is_empty(), + "extra entries: {:?}", + diff.extra_entries + ); + assert!( + diff.mismatched_roots.is_empty(), + "root mismatch: {:?}", + diff.mismatched_roots + ); let runtime = generate( "안녕하세요", @@ -226,7 +468,11 @@ fn inline_styles_render_as_distinct_runs() { assert!(section.contains("굵게")); assert!(section.contains("기울임")); assert!(section.contains("코드")); - assert!(section.contains("링크 (https://example.com)")); + assert!( + section.contains( + "링크 (https://example.com)" + ) + ); } #[test] @@ -284,7 +530,11 @@ fn custom_style_keeps_run_ids_but_uses_updated_header_contract() { assert!(header.contains("굵게")); assert!(section.contains("기울임")); - assert!(section.contains("링크 (https://example.com)")); + assert!( + section.contains( + "링크 (https://example.com)" + ) + ); } #[test] @@ -404,3 +654,173 @@ fn invalid_hwpx_font_size_fails_fast() { assert!(matches!(error, CoreRsError::InvalidOption(_))); } + +#[test] +fn approved_style_fixtures_preserve_header_style_registry_contract() { + for fixture_name in [ + "core-heading", + "core-inline-style", + "core-link-text", + "style-typography", + "style-centered-layout", + "style-brand-color", + ] { + let fixture = approved_fixture(fixture_name); + let input = crate::read_fixture_input(&fixture).unwrap(); + let generated = generate_hwpx(&input, fixture.options()).unwrap(); + let generated_entries = crate::normalized_entries(&generated).unwrap(); + let expected_entries = read_expected_entries(&fixture.root.join("expected")).unwrap(); + + let generated_header = text_entry(&generated_entries, "Contents/header.xml"); + let expected_header = text_entry(&expected_entries, "Contents/header.xml"); + + assert_eq!( + header_style_contract(generated_header), + header_style_contract(expected_header), + "header style contract drifted for fixture {fixture_name}", + ); + } +} + +#[test] +fn approved_style_fixtures_preserve_section_style_references() { + for fixture_name in [ + "core-heading", + "core-inline-style", + "core-link-text", + "style-typography", + "style-centered-layout", + "style-brand-color", + ] { + let fixture = approved_fixture(fixture_name); + let input = crate::read_fixture_input(&fixture).unwrap(); + let generated = generate_hwpx(&input, fixture.options()).unwrap(); + let generated_entries = crate::normalized_entries(&generated).unwrap(); + let expected_entries = read_expected_entries(&fixture.root.join("expected")).unwrap(); + + let generated_section = text_entry(&generated_entries, "Contents/section0.xml"); + let expected_section = text_entry(&expected_entries, "Contents/section0.xml"); + + assert_eq!( + section_style_refs(generated_section), + section_style_refs(expected_section), + "section style references drifted for fixture {fixture_name}", + ); + } +} + +fn approved_fixture(name: &str) -> crate::hwpx_fixture::HwpxFixture { + discover_fixtures() + .unwrap() + .into_iter() + .find(|fixture| fixture.name == name) + .unwrap_or_else(|| panic!("missing approved HWPX fixture: {name}")) +} + +fn text_entry<'a>( + entries: &'a std::collections::BTreeMap, + path: &str, +) -> &'a str { + match entries + .get(path) + .unwrap_or_else(|| panic!("missing entry: {path}")) + { + crate::NormalizedEntry::Text(text) => text.as_str(), + crate::NormalizedEntry::BinaryHash(_) => panic!("{path} was normalized as binary"), + } +} + +type HeaderStyleContract = (Vec, Vec, Vec<(u32, u32, u32)>); + +fn header_style_contract(xml: &str) -> HeaderStyleContract { + let doc = XmlDocument::parse(xml).unwrap(); + let mut char_ids = Vec::new(); + let mut para_ids = Vec::new(); + let mut style_triplets = Vec::new(); + + for node in doc.descendants().filter(|node| node.is_element()) { + match node.tag_name().name() { + "charPr" if node.tag_name().namespace().is_some() => { + if let Some(id) = node + .attribute("id") + .and_then(|value| value.parse::().ok()) + { + char_ids.push(id); + } + } + "paraPr" if node.tag_name().namespace().is_some() => { + if let Some(id) = node + .attribute("id") + .and_then(|value| value.parse::().ok()) + { + para_ids.push(id); + } + } + "style" if node.tag_name().namespace().is_some() => { + let Some(id) = node + .attribute("id") + .and_then(|value| value.parse::().ok()) + else { + continue; + }; + let Some(para_pr) = node + .attribute("paraPrIDRef") + .and_then(|value| value.parse::().ok()) + else { + continue; + }; + let Some(char_pr) = node + .attribute("charPrIDRef") + .and_then(|value| value.parse::().ok()) + else { + continue; + }; + style_triplets.push((id, para_pr, char_pr)); + } + _ => {} + } + } + + char_ids.sort_unstable(); + char_ids.dedup(); + para_ids.sort_unstable(); + para_ids.dedup(); + style_triplets.sort_unstable(); + + (char_ids, para_ids, style_triplets) +} + +fn section_style_refs(xml: &str) -> Vec<(u32, u32, Vec)> { + let doc = XmlDocument::parse(xml).unwrap(); + let mut paragraphs = Vec::new(); + + for paragraph in doc + .descendants() + .filter(|node| node.is_element() && node.tag_name().name() == "p") + { + let Some(para_pr) = paragraph + .attribute("paraPrIDRef") + .and_then(|value| value.parse::().ok()) + else { + continue; + }; + let Some(style_id) = paragraph + .attribute("styleIDRef") + .and_then(|value| value.parse::().ok()) + else { + continue; + }; + + let mut char_refs = paragraph + .children() + .filter(|node| node.is_element() && node.tag_name().name() == "run") + .filter_map(|run| run.attribute("charPrIDRef")) + .filter_map(|value| value.parse::().ok()) + .collect::>(); + char_refs.sort_unstable(); + char_refs.dedup(); + paragraphs.push((para_pr, style_id, char_refs)); + } + + paragraphs +} diff --git a/packages/core-rs/tests/hwpx_fixture_refresh_test.rs b/packages/core-rs/tests/hwpx_fixture_refresh_test.rs new file mode 100644 index 0000000..1220453 --- /dev/null +++ b/packages/core-rs/tests/hwpx_fixture_refresh_test.rs @@ -0,0 +1,207 @@ +use std::fs; +use std::path::{Path, PathBuf}; +use std::process::Command; +use std::time::{SystemTime, UNIX_EPOCH}; + +#[path = "support/archive_fixture.rs"] +mod archive_fixture; + +use archive_fixture::{ + ArchiveEntry, hash_entries, normalized_entries, read_expected_entries, refresh_fixture_metadata, +}; + +const AUXILIARY_NAMES: &[&str] = &[".DS_Store", "README.md", "provenance.md"]; + +#[test] +fn refresh_rebuilds_sha256_expected_tree_and_hash() { + let temp = TestTempDir::new("hwpx-refresh-sha256"); + let fixture = copy_fixture_to_temp("core-paragraph", &temp); + + fs::remove_dir_all(fixture.join("expected")).unwrap(); + fs::write(fixture.join("hash.txt"), "stale").unwrap(); + + let hash = refresh_fixture_metadata( + &fixture, + "golden.hwpx", + "expected", + "hash.txt", + AUXILIARY_NAMES, + AUXILIARY_NAMES, + is_text_entry, + ) + .unwrap(); + + let golden = fs::read(fixture.join("golden.hwpx")).unwrap(); + let normalized = normalized_entries(&golden, is_text_entry).unwrap(); + let expected = + read_expected_entries(&fixture.join("expected"), AUXILIARY_NAMES, is_text_entry).unwrap(); + + assert_eq!(expected, normalized); + assert_eq!(fs::read_to_string(fixture.join("hash.txt")).unwrap(), hash); + assert_eq!(hash, hash_entries(&normalized)); + assert!( + fixture + .join("expected/Preview/PrvImage.png.sha256") + .is_file() + ); + assert!(!fixture.join("expected/Preview/PrvImage.png").exists()); +} + +#[test] +fn refresh_preserves_raw_binary_representation_when_fixture_already_uses_it() { + let temp = TestTempDir::new("hwpx-refresh-raw"); + let fixture = copy_fixture_to_temp("list-basic", &temp); + + fs::write(fixture.join("expected/Preview/PrvImage.png"), b"corrupted").unwrap(); + fs::write(fixture.join("hash.txt"), "stale").unwrap(); + + refresh_fixture_metadata( + &fixture, + "golden.hwpx", + "expected", + "hash.txt", + AUXILIARY_NAMES, + AUXILIARY_NAMES, + is_text_entry, + ) + .unwrap(); + + let golden = fs::read(fixture.join("golden.hwpx")).unwrap(); + let archive = archive_fixture::archive_entries(&golden, is_text_entry).unwrap(); + let preview = archive.get("Preview/PrvImage.png").unwrap(); + + match preview { + ArchiveEntry::Binary { bytes, .. } => { + assert_eq!( + fs::read(fixture.join("expected/Preview/PrvImage.png")).unwrap(), + *bytes + ); + } + ArchiveEntry::Text(_) => panic!("preview image unexpectedly normalized as text"), + } + + assert!( + !fixture + .join("expected/Preview/PrvImage.png.sha256") + .exists() + ); +} + +#[test] +fn refresh_removes_stale_outputs_and_preserves_auxiliary_files() { + let temp = TestTempDir::new("hwpx-refresh-stale"); + let fixture = copy_fixture_to_temp("core-paragraph", &temp); + + fs::create_dir_all(fixture.join("expected/Stale/Inner")).unwrap(); + fs::write(fixture.join("expected/Stale/Inner/ghost.txt"), "remove me").unwrap(); + fs::write( + fixture.join("expected/Preview/PrvImage.png"), + "wrong format", + ) + .unwrap(); + fs::write(fixture.join("expected/README.md"), "keep me").unwrap(); + fs::write(fixture.join("expected/provenance.md"), "keep me too").unwrap(); + + refresh_fixture_metadata( + &fixture, + "golden.hwpx", + "expected", + "hash.txt", + AUXILIARY_NAMES, + AUXILIARY_NAMES, + is_text_entry, + ) + .unwrap(); + + assert!(!fixture.join("expected/Stale").exists()); + assert!(!fixture.join("expected/Preview/PrvImage.png").exists()); + assert!( + fixture + .join("expected/Preview/PrvImage.png.sha256") + .is_file() + ); + assert_eq!( + fs::read_to_string(fixture.join("expected/README.md")).unwrap(), + "keep me" + ); + assert_eq!( + fs::read_to_string(fixture.join("expected/provenance.md")).unwrap(), + "keep me too" + ); +} + +#[test] +fn cli_fails_for_unknown_fixture_name() { + let output = Command::new(env!("CARGO_BIN_EXE_refresh_hwpx_fixture_metadata")) + .arg("does-not-exist") + .output() + .unwrap(); + + assert!(!output.status.success()); + let stderr = String::from_utf8(output.stderr).unwrap(); + assert!(stderr.contains("unknown HWPX approved fixture: does-not-exist")); +} + +fn fixture_root() -> PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")) + .join("tests") + .join("fixtures") + .join("hwpx") + .join("approved") +} + +fn copy_fixture_to_temp(name: &str, temp: &TestTempDir) -> PathBuf { + let source = fixture_root().join(name); + let destination = temp.path().join(name); + copy_dir_recursive(&source, &destination); + destination +} + +fn copy_dir_recursive(source: &Path, destination: &Path) { + fs::create_dir_all(destination).unwrap(); + for entry in fs::read_dir(source).unwrap() { + let entry = entry.unwrap(); + let source_path = entry.path(); + let destination_path = destination.join(entry.file_name()); + if source_path.is_dir() { + copy_dir_recursive(&source_path, &destination_path); + } else { + fs::copy(&source_path, &destination_path).unwrap(); + } + } +} + +fn is_text_entry(path: &str) -> bool { + path == "mimetype" + || path.ends_with(".xml") + || path.ends_with(".rdf") + || path.ends_with(".rels") + || path.ends_with(".txt") + || path.ends_with(".hpf") +} + +struct TestTempDir { + path: PathBuf, +} + +impl TestTempDir { + fn new(prefix: &str) -> Self { + let nanos = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_nanos(); + let path = std::env::temp_dir().join(format!("{prefix}-{nanos}-{}", std::process::id())); + fs::create_dir_all(&path).unwrap(); + Self { path } + } + + fn path(&self) -> &Path { + &self.path + } +} + +impl Drop for TestTempDir { + fn drop(&mut self) { + let _ = fs::remove_dir_all(&self.path); + } +} diff --git a/packages/core-rs/tests/hwpx_test.rs b/packages/core-rs/tests/hwpx_test.rs index 23ff2ce..f120a19 100644 --- a/packages/core-rs/tests/hwpx_test.rs +++ b/packages/core-rs/tests/hwpx_test.rs @@ -1,21 +1,20 @@ use std::collections::BTreeMap; use std::fs; -#[path = "support/hwpx_fixture.rs"] -mod hwpx_fixture; +#[path = "hwpx/fixture_suite.rs"] +mod fixture_suite; #[path = "support/hwpx_contract.rs"] mod hwpx_contract; +#[path = "support/hwpx_fixture.rs"] +mod hwpx_fixture; #[path = "support/hwpx_runtime.rs"] mod hwpx_runtime; -#[path = "hwpx/fixture_suite.rs"] -mod fixture_suite; #[path = "hwpx/regression.rs"] mod regression; use hwpx_fixture::{ HwpxFixture, NormalizedEntry, archive_entry_names, hash_entries, normalized_entries, - read_expected_entries, - read_fixture_input, + read_expected_entries, read_fixture_input, }; use roxmltree::Document as XmlDocument; diff --git a/packages/core-rs/tests/support/archive_fixture.rs b/packages/core-rs/tests/support/archive_fixture.rs index eca94e8..aa8e917 100644 --- a/packages/core-rs/tests/support/archive_fixture.rs +++ b/packages/core-rs/tests/support/archive_fixture.rs @@ -1,164 +1,11 @@ -#![allow(dead_code)] +#![allow(dead_code, unused_imports)] -use std::collections::BTreeMap; -use std::error::Error; -use std::fs; -use std::io::Read; -use std::path::{Path, PathBuf}; +#[path = "../../src/bin/support/archive_fixture.rs"] +mod shared_archive_fixture; -use sha2::{Digest, Sha256}; -use zip::ZipArchive; - -pub(crate) type FixtureResult = Result>; - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum NormalizedEntry { - Text(String), - BinaryHash(String), -} - -pub fn discover_fixture_dirs(root: &Path) -> FixtureResult> { - let mut fixtures = fs::read_dir(root)? - .map(|entry| entry.map(|entry| entry.path())) - .collect::, _>>()?; - fixtures.sort(); - Ok(fixtures) -} - -pub fn read_fixture_input(root: &Path) -> FixtureResult { - Ok(fs::read_to_string(root.join("input.md"))?) -} - -pub fn normalized_entries( - bytes: &[u8], - is_text_entry: fn(&str) -> bool, -) -> FixtureResult> { - let reader = std::io::Cursor::new(bytes.to_vec()); - let mut archive = ZipArchive::new(reader)?; - let mut entries = BTreeMap::new(); - - for index in 0..archive.len() { - let mut file = archive.by_index(index)?; - let mut contents = Vec::new(); - file.read_to_end(&mut contents)?; - entries.insert( - file.name().to_string(), - normalize_entry(file.name(), &contents, is_text_entry), - ); - } - - Ok(entries) -} - -pub fn archive_entry_names(bytes: &[u8]) -> FixtureResult> { - let reader = std::io::Cursor::new(bytes.to_vec()); - let mut archive = ZipArchive::new(reader)?; - let mut names = Vec::with_capacity(archive.len()); - - for index in 0..archive.len() { - let file = archive.by_index(index)?; - names.push(file.name().to_string()); - } - - Ok(names) -} - -pub fn read_expected_entries( - root: &Path, - ignored_names: &[&str], - is_text_entry: fn(&str) -> bool, -) -> FixtureResult> { - let mut entries = BTreeMap::new(); - let mut stack = vec![root.to_path_buf()]; - - while let Some(dir) = stack.pop() { - let mut children = fs::read_dir(&dir)? - .map(|entry| entry.map(|entry| entry.path())) - .collect::, _>>()?; - children.sort(); - - for child in children { - if child - .file_name() - .and_then(|name| name.to_str()) - .is_some_and(|name| ignored_names.contains(&name)) - { - continue; - } - if child.is_dir() { - stack.push(child); - continue; - } - - let relative = child - .strip_prefix(root) - .unwrap_or(&child) - .to_string_lossy() - .replace('\\', "/"); - if relative.ends_with(".sha256") { - let normalized_path = relative.trim_end_matches(".sha256").to_string(); - let contents = fs::read_to_string(&child)?; - entries.insert( - normalized_path, - NormalizedEntry::BinaryHash(contents.trim().to_string()), - ); - continue; - } - - let contents = fs::read(&child)?; - entries.insert(relative.clone(), normalize_entry(&relative, &contents, is_text_entry)); - } - } - - Ok(entries) -} - -pub fn hash_entries(entries: &BTreeMap) -> String { - let mut hasher = Sha256::new(); - for (path, contents) in entries { - hasher.update(path.as_bytes()); - hasher.update([0]); - match contents { - NormalizedEntry::Text(text) => { - hasher.update([0]); - hasher.update(text.as_bytes()); - } - NormalizedEntry::BinaryHash(hash) => { - hasher.update([1]); - hasher.update(hash.as_bytes()); - } - } - hasher.update([0]); - } - format!("{:x}", hasher.finalize()) -} - -fn normalize_entry( - path: &str, - contents: &[u8], - is_text_entry: fn(&str) -> bool, -) -> NormalizedEntry { - if is_text_entry(path) { - let text = String::from_utf8(contents.to_vec()).expect("text fixture entry must be UTF-8"); - return NormalizedEntry::Text(normalize_content(&text)); - } - - NormalizedEntry::BinaryHash(hash_bytes(contents)) -} - -fn normalize_content(contents: &str) -> String { - contents - .replace("\r\n", "\n") - .lines() - .map(str::trim_end) - .collect::>() - .join("\n") - .trim() - .to_string() -} - -fn hash_bytes(bytes: &[u8]) -> String { - let mut hasher = Sha256::new(); - hasher.update(bytes); - format!("{:x}", hasher.finalize()) -} +pub(crate) use shared_archive_fixture::{ + ArchiveEntry, BinaryRepresentation, FixtureResult, NormalizedEntry, archive_entries, + archive_entry_names, detect_binary_representations, discover_fixture_dirs, hash_entries, + normalized_entries, normalized_entries_from_archive, read_expected_entries, read_fixture_input, + refresh_fixture_metadata, +}; diff --git a/packages/core-rs/tests/support/hwpx_contract.rs b/packages/core-rs/tests/support/hwpx_contract.rs index 7f4d47e..81c3fc3 100644 --- a/packages/core-rs/tests/support/hwpx_contract.rs +++ b/packages/core-rs/tests/support/hwpx_contract.rs @@ -59,8 +59,16 @@ pub fn compare_contracts( expected: &HwpxContractSnapshot, actual: &HwpxContractSnapshot, ) -> HwpxContractDiff { - let expected_names = expected.entry_names.iter().cloned().collect::>(); - let actual_names = actual.entry_names.iter().cloned().collect::>(); + let expected_names = expected + .entry_names + .iter() + .cloned() + .collect::>(); + let actual_names = actual + .entry_names + .iter() + .cloned() + .collect::>(); let missing_entries = expected_names .difference(&actual_names) @@ -92,7 +100,9 @@ pub fn compare_contracts( } } -fn xml_roots(entries: &BTreeMap) -> FixtureResult> { +fn xml_roots( + entries: &BTreeMap, +) -> FixtureResult> { let mut roots = BTreeMap::new(); for (path, contents) in entries { diff --git a/packages/core-rs/tests/support/hwpx_runtime.rs b/packages/core-rs/tests/support/hwpx_runtime.rs index 1e4a817..1326cf9 100644 --- a/packages/core-rs/tests/support/hwpx_runtime.rs +++ b/packages/core-rs/tests/support/hwpx_runtime.rs @@ -33,5 +33,4 @@ impl GeneratedHwpx { pub fn content_hpf(&self) -> FixtureResult<&str> { self.text_entry("Contents/content.hpf") } - } From 2bc058da8db1646f8d2e1b33ff5d1e63ea8bc049 Mon Sep 17 00:00:00 2001 From: puleugo Date: Tue, 10 Mar 2026 22:37:16 +0900 Subject: [PATCH 2/4] chore: ignore hwpx samples and format docx generator --- .gitignore | 1 + packages/core-rs/src/generators/docx/mod.rs | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 3eb848f..ac04211 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ packages/npm-core-rs/site-dist/ packages/npm-core-rs/*.tgz packages/npm-core-rs/tmp/ target/ +packages/core-rs/src/generators/hwpx/reference/*-sample.hwpx diff --git a/packages/core-rs/src/generators/docx/mod.rs b/packages/core-rs/src/generators/docx/mod.rs index a05f42c..d32b40f 100644 --- a/packages/core-rs/src/generators/docx/mod.rs +++ b/packages/core-rs/src/generators/docx/mod.rs @@ -27,7 +27,10 @@ impl Generator for DocxGenerator { let max_heading_level = max_heading_level(&document.blocks); let mut entries = vec![ - ArchiveEntry::new_text("[Content_Types].xml", templates::content_types(&context.images)), + ArchiveEntry::new_text( + "[Content_Types].xml", + templates::content_types(&context.images), + ), ArchiveEntry::new_text("_rels/.rels", templates::root_relationships()), ArchiveEntry::new_text( "docProps/core.xml", From 0de0d93be9b90e291990e2f9557081596209c66e Mon Sep 17 00:00:00 2001 From: puleugo Date: Wed, 11 Mar 2026 02:19:58 +0900 Subject: [PATCH 3/4] feat(demo): add localized browser landing and benchmark proof --- README.md | 97 +++- docs/README.md | 15 + docs/ko/README.md | 31 ++ docs/ko/benchmark.md | 42 ++ docs/ko/design-system.md | 292 ++++++++++ docs/ko/getting-started.md | 52 ++ docs/ko/runtime.md | 45 ++ package.json | 2 + packages/core-rs/src/parser/markdown.rs | 25 + packages/core-rs/src/parser/markdown/tests.rs | 26 + packages/core-rs/src/wasm.rs | 21 +- packages/npm-core-rs/README.md | 4 +- packages/npm-core-rs/demo/assets/logo.png | Bin 0 -> 1736541 bytes .../npm-core-rs/demo/assets/og-image.html | 213 ++++++++ packages/npm-core-rs/demo/assets/og-image.png | Bin 0 -> 308552 bytes packages/npm-core-rs/demo/browser-client.js | 12 +- .../npm-core-rs/demo/comparison-data.json | 112 ++++ packages/npm-core-rs/demo/index.html | 237 +++++--- packages/npm-core-rs/demo/ko/index.html | 219 ++++++++ packages/npm-core-rs/demo/main.js | 397 ++++++++++++-- packages/npm-core-rs/demo/styles.css | 509 +++++++++++------- packages/npm-core-rs/package.json | 2 + .../scripts/_benchmark_docxly_once.mjs | 14 + .../scripts/_comparison-common.mjs | 158 ++++++ .../scripts/benchmark-pandoc-check.mjs | 19 + .../npm-core-rs/scripts/benchmark-pandoc.mjs | 378 +++++++++++++ packages/npm-core-rs/scripts/build-pages.mjs | 3 + packages/npm-core-rs/scripts/demo-server.mjs | 17 +- packages/npm-core-rs/src/browser.js | 12 +- packages/npm-core-rs/src/index.d.ts | 11 + packages/npm-core-rs/src/node.js | 12 +- 31 files changed, 2640 insertions(+), 337 deletions(-) create mode 100644 docs/README.md create mode 100644 docs/ko/README.md create mode 100644 docs/ko/benchmark.md create mode 100644 docs/ko/design-system.md create mode 100644 docs/ko/getting-started.md create mode 100644 docs/ko/runtime.md create mode 100644 packages/npm-core-rs/demo/assets/logo.png create mode 100644 packages/npm-core-rs/demo/assets/og-image.html create mode 100644 packages/npm-core-rs/demo/assets/og-image.png create mode 100644 packages/npm-core-rs/demo/comparison-data.json create mode 100644 packages/npm-core-rs/demo/ko/index.html create mode 100644 packages/npm-core-rs/scripts/_benchmark_docxly_once.mjs create mode 100644 packages/npm-core-rs/scripts/_comparison-common.mjs create mode 100644 packages/npm-core-rs/scripts/benchmark-pandoc-check.mjs create mode 100644 packages/npm-core-rs/scripts/benchmark-pandoc.mjs diff --git a/README.md b/README.md index 62cc793..7e41f90 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,66 @@ -# docxly core-rs + -`docxly/core-rs` is a Rust-first mono repo for a Markdown-based DOCX/HWPX generation library, plus an npm-facing WASM wrapper package. +Language: [English](/Users/limchaesung/Github/docxly/core-rs/README.md) · [한국어](/Users/limchaesung/Github/docxly/core-rs/docs/ko/README.md) · [Docs](/Users/limchaesung/Github/docxly/core-rs/docs/README.md) -The project is being developed with a TDD-first workflow. The current milestone implements the DOCX rich slice and an approved HWPX baseline backed by manually validated golden fixtures. +`docxly/core-rs` currently measures at an 80 ms cold start and a 2 ms steady median, versus 284 ms cold and 210 ms steady for Pandoc on the summary DOCX benchmark corpus, a 105x steady-state advantage while also exposing browser-local generation and HWPX support from the same Rust core. + +The project is developed with a TDD-first workflow. The current milestone implements the DOCX rich slice and an approved HWPX baseline backed by manually validated golden fixtures. + +## Install + +The fastest way to start using docxly today is the npm package: + +```bash +npm install @docxly/core-rs +``` + +The Rust crate is the source of truth in this repository and can be consumed from the workspace or as a path dependency: + +```toml +[dependencies] +core-rs = { path = "packages/core-rs" } +``` + +## Quick Start + +### Node + +```js +import { writeFile } from "node:fs/promises"; +import { generateDocx } from "@docxly/core-rs"; + +const bytes = await generateDocx("# Hello\n\nThis is **docxly**."); +await writeFile("output.docx", bytes); +``` + +### Rust + +```rust +use std::fs; + +use core_rs::{DocxOptions, generate_docx}; + +let docx = generate_docx("# Hello\n\nThis is **docxly**.", DocxOptions::default())?; +fs::write("output.docx", docx)?; +``` + +## Why docxly + +- Build document generation directly into a product instead of shelling out to a converter. +- On the current summary corpus, generate complex DOCX output in `2 ms` steady median versus Pandoc's `210 ms`, with `80 ms` versus `284 ms` cold start. +- Use the same Rust core across Node, browser, and HWPX workflows. +- Start with DOCX today and expand into HWPX from the same repository. +- Ship deterministic outputs backed by fixture-driven tests and normalized archive checks. ## Live Demo @@ -12,6 +70,39 @@ Try the browser demo on GitHub Pages: The live page uses the published WASM wrapper and downloads a real `.docx` file directly in the browser. + + +## Why docxly instead of Pandoc? + +Pandoc is a general-purpose converter; docxly is an embeddable generation engine. + +Use docxly when document generation must live inside a Node service, browser workflow, or product surface. Use Pandoc when you need broad format conversion and a CLI-first publishing workflow. + +| Corpus | docxly cold | Pandoc cold | docxly steady | Pandoc steady | Speed ratio | +| --- | --- | --- | --- | --- | --- | +| Small | 66 ms | 539 ms | 1 ms | 249 ms | 369.84x | +| Medium | 80 ms | 284 ms | 2 ms | 126 ms | 64.85x | +| Large | 88 ms | 219 ms | 4 ms | 210 ms | 57.60x | +| Summary | 80 ms | 284 ms | 2 ms | 210 ms | 105.00x | + +| Capability | docxly | Pandoc | +| --- | --- | --- | +| Embeddable in app | Yes, library-first for Node and browser bundlers | CLI-first with process invocation | +| Browser-local generation | First-party browser package and WASM path | Possible through pandoc.wasm, not the primary npm workflow | +| npm distribution | Published package | Not a first-party npm package | +| HWPX generation | Supported in the Rust core | Not supported | +| Broad format conversion | Focused on DOCX and HWPX generation | Wide multi-format conversion | +| DOCX reference-template workflow | Not a reference.docx workflow | Supported via reference.docx | + +Measured on darwin 25.2.0 / arm64 at 2026-03-10T14:29:12.752Z with Node v23.7.0 and Pandoc 3.9. + +- This benchmark measures DOCX generation only and does not compare HWPX. +- The numbers above come from an offline Node environment and are not browser runtime timings. +- Cold timings include WASM initialization for docxly and process startup for Pandoc. +- Steady timings are medians from 15 runs after one warm-up per corpus. + + + ## Workspace Layout ```text diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..c0afbc8 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,15 @@ +# Docs + +`docxly` documentation is organized by entrypoint, similar to a product docs hub. + +## Languages + +- [English (repository README)](/Users/limchaesung/Github/docxly/core-rs/README.md) +- [한국어](/Users/limchaesung/Github/docxly/core-rs/docs/ko/README.md) + +## Sections + +- [Getting Started](/Users/limchaesung/Github/docxly/core-rs/docs/ko/getting-started.md) +- [Runtime Guide](/Users/limchaesung/Github/docxly/core-rs/docs/ko/runtime.md) +- [Benchmark and Positioning](/Users/limchaesung/Github/docxly/core-rs/docs/ko/benchmark.md) +- [Design System](/Users/limchaesung/Github/docxly/core-rs/docs/ko/design-system.md) diff --git a/docs/ko/README.md b/docs/ko/README.md new file mode 100644 index 0000000..466d070 --- /dev/null +++ b/docs/ko/README.md @@ -0,0 +1,31 @@ +# docxly 문서 + +`docxly`는 DOCX와 HWPX 생성을 애플리케이션 내부에 직접 내장하기 위한 Rust/WASM 문서 생성 엔진입니다. + +Pandoc이 범용 문서 변환기라면, `docxly`는 Node 서비스, 브라우저 워크플로, 제품 UI 안에 직접 들어가는 라이브러리 경로에 초점을 맞춥니다. + +## 시작하기 + +- [빠른 시작](/Users/limchaesung/Github/docxly/core-rs/docs/ko/getting-started.md) +- [런타임 가이드](/Users/limchaesung/Github/docxly/core-rs/docs/ko/runtime.md) +- [성능 비교와 포지셔닝](/Users/limchaesung/Github/docxly/core-rs/docs/ko/benchmark.md) +- [디자인 시스템](/Users/limchaesung/Github/docxly/core-rs/docs/ko/design-system.md) + +## 핵심 수치 + +- 현재 summary benchmark 기준 `docxly`는 `80 ms cold / 2 ms steady` +- 같은 코퍼스에서 Pandoc은 `284 ms cold / 210 ms steady` +- steady 기준으로 `105x` 빠른 수치가 측정됨 + +## 언제 docxly를 선택해야 하나 + +- 문서 생성을 별도 CLI 프로세스가 아니라 애플리케이션 내부 라이브러리로 붙이고 싶을 때 +- Node와 브라우저에서 같은 Rust 코어를 재사용하고 싶을 때 +- DOCX뿐 아니라 HWPX까지 같은 저장소와 API 흐름으로 확장하고 싶을 때 +- 테스트 가능한 결정적 출력과 fixture 기반 검증이 필요할 때 + +## 바로 가기 + +- [루트 README](/Users/limchaesung/Github/docxly/core-rs/README.md) +- [npm package README](/Users/limchaesung/Github/docxly/core-rs/packages/npm-core-rs/README.md) +- [라이브 데모](https://docxly.github.io/core-rs/) diff --git a/docs/ko/benchmark.md b/docs/ko/benchmark.md new file mode 100644 index 0000000..83afccf --- /dev/null +++ b/docs/ko/benchmark.md @@ -0,0 +1,42 @@ +# 성능 비교와 포지셔닝 + +## 요약 + +`docxly`는 Pandoc의 대체 CLI가 아니라, 앱 내부에 문서 생성을 임베드하기 위한 라이브러리 경로를 제공합니다. + +- `docxly`: embeddable generation engine +- `Pandoc`: general-purpose converter + +## 현재 summary benchmark + +- `docxly`: `80 ms cold / 2 ms steady` +- `Pandoc`: `284 ms cold / 210 ms steady` +- steady median 기준 `105x` + +측정 조건: + +- 환경: `darwin 25.2.0 / arm64` +- Node: `v23.7.0` +- Pandoc: `3.9` +- 기준: complex DOCX benchmark corpus + +## 해석 + +- cold start는 초기화 비용을 포함합니다. + - `docxly`: WASM initialization 포함 + - `Pandoc`: process startup 포함 +- steady median은 warm-up 이후 반복 생성 비용을 의미합니다. +- 이 수치는 브라우저 실측이 아니라 offline Node benchmark입니다. + +## 언제 Pandoc이 더 적합한가 + +- 폭넓은 문서 포맷 변환이 필요할 때 +- CLI 중심 출판 파이프라인이 있을 때 +- `reference.docx` 기반 커스터마이징 흐름이 필요할 때 + +## 언제 docxly가 더 적합한가 + +- 앱 내부에 직접 문서 생성 기능을 붙여야 할 때 +- Node와 브라우저에서 같은 코어를 재사용해야 할 때 +- HWPX까지 같은 제품 흐름 안에서 다뤄야 할 때 +- 별도 프로세스 의존성 없이 라이브러리 형태로 배포하고 싶을 때 diff --git a/docs/ko/design-system.md b/docs/ko/design-system.md new file mode 100644 index 0000000..685bff7 --- /dev/null +++ b/docs/ko/design-system.md @@ -0,0 +1,292 @@ +# docxly 데모 디자인 시스템 + +`docxly` 데모 UI를 blue-first 제품 스타일로 재구성하기 위한 구현 기준 문서다. 이 문서는 데모 페이지를 바로 다시 설계할 수 있도록 토큰, 위계, 컴포넌트 규칙, 로고 슬롯 규칙을 결정 완료 상태로 정의한다. + +## Overview + +### 제품 성격 + +- embeddable document engine +- Node, browser, Rust 코어를 공유하는 문서 생성 제품 +- CLI 도구가 아니라 앱 내부에 들어가는 라이브러리 경험이 핵심 + +### 대상 사용자 + +- 앱과 서비스에 문서 생성을 직접 내장하려는 개발자 +- 브라우저와 Node에서 같은 코어를 쓰고 싶은 팀 +- DOCX뿐 아니라 HWPX까지 같은 제품 흐름으로 확장하려는 팀 + +### 디자인 목표 + +- product-like clarity +- generator-first usability +- blue-first technical brand identity + +## Brand Foundation + +### 핵심 브랜드 문장 + +- `docxly is an embeddable document generation engine.` +- `docxly brings DOCX and HWPX generation into the product surface, not a separate conversion step.` + +### 로고 슬롯 규칙 + +- 위치: hero 내부 맨 위 좌측 +- 형태: `logo mark + wordmark` 또는 `docxly` 텍스트 lockup +- 최소 높이: desktop 28px, mobile 24px +- clear space: 로고 높이의 0.5배 +- 기본 배경: 밝은 surface 위 단색 사용 +- dark surface 위 사용 시 단색 역상 버전만 허용 + +### 로고 없는 상태의 fallback + +- 실제 로고 자산이 없으면 `docxly` 워드마크 텍스트를 사용한다. +- fallback 서체는 sans-serif display 계열로 고정한다. +- fallback은 headline과 분리된 독립 요소여야 하며, hero heading 안에 합치지 않는다. + +## Color System + +### Core Tokens + +| Token | Value | Role | +| --- | --- | --- | +| `--color-primary-050` | `#eff6ff` | page tint, subtle highlight | +| `--color-primary-100` | `#dbeafe` | soft border, soft chip | +| `--color-primary-500` | `#2563eb` | selected state, active fill | +| `--color-primary-600` | `#1d4ed8` | primary CTA | +| `--color-primary-700` | `#1e40af` | hover / pressed CTA | +| `--color-neutral-950` | `#0f172a` | headline, strong surface | +| `--color-neutral-700` | `#334155` | body text | +| `--color-neutral-500` | `#64748b` | muted text | +| `--color-neutral-200` | `#e2e8f0` | border, divider | +| `--color-surface` | `#ffffff` | base panel surface | +| `--color-surface-muted` | `#f8fafc` | muted background | +| `--color-success` | `#15803d` | success status | +| `--color-error` | `#b91c1c` | error status | + +### Usage Rules + +- primary CTA는 `primary-600` +- primary CTA hover는 `primary-700` +- selected tab, selected chip, active proof highlight는 `primary-500` +- soft tint 배경은 `primary-050` +- border 기본값은 `neutral-200` +- body text는 `neutral-700` +- muted helper text는 `neutral-500` +- dark comparison/proof strip 표면은 `neutral-950` 기반으로 사용 + +### Explicit Constraints + +- 기존 warm/orange accent는 primary palette에서 제거한다. +- warning/emphasis 보조색도 이번 문서 기준에서는 정의하지 않는다. +- text on primary는 white only다. +- 본문 텍스트 대비는 WCAG AA 이상을 유지한다. + +## Typography + +### Type Roles + +- display: hero headline 전용 +- heading: section title, card title +- body: paragraph, helper, note +- mono: install command, textarea, generated status +- label: tab, eyebrow, compact metadata + +### Type Scale + +| Token | Desktop | Mobile | Usage | +| --- | --- | --- | --- | +| `display-1` | `56/1.0` | `40/1.02` | hero headline | +| `heading-2` | `32/1.05` | `26/1.08` | section heading | +| `body-1` | `16/1.6` | `16/1.6` | default paragraph | +| `body-2` | `14/1.55` | `14/1.55` | helper, note | +| `label` | `12/1.2` | `12/1.2` | uppercase label | + +### Type Rules + +- hero body는 최대 2문장 +- comparison meta는 1줄만 허용 +- install helper는 1문장만 허용 +- note/debug 문구는 body-2로만 표현 + +## Layout + +### Page Frame + +- page width: max 1120px +- panel radius: 20px +- hero top padding: 40px desktop, 24px mobile +- section gap: 24px + +### Spacing Scale + +- `8` +- `12` +- `16` +- `24` +- `32` +- `48` + +### Hero Layout + +- desktop: `content column + install card` 2열 +- content column 내부 순서: + - logo + - one-line value proposition + - short supporting sentence + - external links max 2개 + - compact proof strip +- comparison strip은 content column 내부에만 배치 +- install card는 독립 보조 카드 1개만 허용 + +### Mobile Layout + +- 모바일 1열 순서: + - logo/value proposition + - install card + - compact proof strip + - generator panel +- `390x844` 기준 first viewport 안에 `logo + headline + install action`이 보여야 한다. +- first viewport 안에 proof strip 전체가 보일 필요는 없지만, strip 시작부는 보여야 한다. + +## Component Rules + +### Hero + +- 최대 2개 text paragraph +- 외부 링크 최대 2개 +- hero 안에 독립 강조 카드 2개 초과 금지 +- value proposition은 1문장으로 끝낸다 + +### Install Card + +- command +- copy button +- 1-line helper +- 1-line proof copy using the benchmark summary headline number + +canonical install proof: + +- `Install the embeddable DOCX engine that measured 105x faster than Pandoc on the summary benchmark.` + +금지: + +- 여러 installation option 동시 노출 +- verbose explanation +- secondary CTA 추가 + +### Proof Strip + +- KPI 3개 고정 + - docxly steady + - pandoc steady + - speed ratio +- 1줄 해석 허용 +- badge 1개 허용 +- long metadata는 1줄만 허용 + +금지: + +- comparison table +- dual comparison cards +- 긴 explanatory paragraph +- feature matrix + +### Generator Panel + +- 데모의 가장 높은 interaction priority 유지 +- 유지 대상: + - format tabs + - markdown textarea + - title input + - author input + - strict mode toggle + - generate button + - status + - note + +### Tabs + +- active = filled blue +- inactive = neutral ghost +- uppercase label +- tab label은 한 단어 또는 짧은 약어만 허용 + +### Buttons + +- variants: + - primary + - secondary + - ghost +- primary는 blue fill +- secondary는 neutral tint +- ghost는 borderless text action + +## Content Hierarchy + +### First Screen Order + +1. logo +2. one-line value proposition +3. short supporting sentence +4. primary install action +5. compact proof strip + +install proof line은 comparison strip보다 먼저 읽히는 핵심 설치 유도 문장으로 배치한다. + +### Comparison Handling + +- comparison은 landing에서 제거하지 않는다. +- 하지만 역할은 `compact support proof`로만 제한한다. +- `Choose docxly / Choose Pandoc` 장문 카피는 기본 landing에서 제거 대상이다. +- feature matrix와 장문 포지셔닝 설명은 docs 또는 하단 secondary content로 이동한다. + +## Do / Don't + +### Do + +- hero에서 한 가지 핵심 행동만 강조한다. +- proof는 숫자 중심으로 압축한다. +- blue tokens만으로 CTA와 active state를 통일한다. +- generator panel을 가장 중요한 작업 영역으로 유지한다. +- 로고를 headline과 별도 계층으로 분리한다. + +### Don't + +- 상단에 독립 강조 카드 3개 이상 배치하지 않는다. +- comparison table을 landing first screen에 노출하지 않는다. +- warm/orange palette를 primary accent로 사용하지 않는다. +- 긴 비교 카피를 generator보다 먼저 배치하지 않는다. +- install card와 comparison strip을 같은 강도의 경쟁 블록으로 만들지 않는다. + +## Acceptance Criteria + +이 문서만 읽고 구현자는 추가 질문 없이 다음을 수행할 수 있어야 한다. + +- CSS custom property 정의 +- hero 구조 재배치 +- install card / compact proof strip / generator panel 위계 적용 +- 로고 자산 도입 전 fallback lockup 구현 + +추가 완료 조건: + +- 모바일 `390x844` 기준 first screen에 `logo + headline + install action`이 모두 보여야 한다는 기준이 명시돼 있어야 한다. +- comparison은 `support proof`로만 남고, 장문 설명과 표는 기본 landing에서 제거 대상으로 명시돼 있어야 한다. +- primary accent가 blue token family로 통일된다고 명시돼 있어야 한다. + +## Migration Notes + +현재 데모 UI에서 제거 또는 축소해야 하는 요소: + +- warm/orange 중심 accent +- landing 상단의 장문 comparison explanation +- feature matrix table +- dual comparison choice blocks +- hero에서 경쟁하는 다중 강조 카드 + +새 UI로 옮길 때 유지해야 하는 요소: + +- install command 복사 흐름 +- summary benchmark proof +- HWPX/DOCX format switching +- browser-local generation 메시지 diff --git a/docs/ko/getting-started.md b/docs/ko/getting-started.md new file mode 100644 index 0000000..c9323de --- /dev/null +++ b/docs/ko/getting-started.md @@ -0,0 +1,52 @@ +# 빠른 시작 + +## 설치 + +가장 빠른 시작 경로는 npm 패키지입니다. + +```bash +npm install @docxly/core-rs +``` + +Rust crate는 이 저장소의 워크스페이스에서 바로 사용할 수 있습니다. + +```toml +[dependencies] +core-rs = { path = "packages/core-rs" } +``` + +## Node 예제 + +```js +import { writeFile } from "node:fs/promises"; +import { generateDocx } from "@docxly/core-rs"; + +const bytes = await generateDocx("# Hello\n\nThis is **docxly**."); +await writeFile("output.docx", bytes); +``` + +## Rust 예제 + +```rust +use std::fs; + +use core_rs::{DocxOptions, generate_docx}; + +let docx = generate_docx("# Hello\n\nThis is **docxly**.", DocxOptions::default())?; +fs::write("output.docx", docx)?; +``` + +## 로컬 데모 실행 + +저장소 루트에서 아래 명령을 실행합니다. + +```bash +npm install +npm run demo +``` + +정적 Pages 산출물만 생성하려면: + +```bash +npm run build:pages +``` diff --git a/docs/ko/runtime.md b/docs/ko/runtime.md new file mode 100644 index 0000000..256e37d --- /dev/null +++ b/docs/ko/runtime.md @@ -0,0 +1,45 @@ +# 런타임 가이드 + +## 지원 런타임 + +- Node +- 브라우저 번들러 환경 +- Rust workspace/path dependency + +## Node + +`@docxly/core-rs`는 비동기 API로 DOCX 생성을 제공합니다. + +- 기본 엔트리포인트: `generateDocx(markdown, options)` +- 반환값: `Promise` +- 주요 옵션: `title`, `author`, `strictMode` + +## 브라우저 + +브라우저에서는 `.wasm` 자산을 처리할 수 있는 번들러가 필요합니다. + +- raw ` + + diff --git a/packages/npm-core-rs/demo/main.js b/packages/npm-core-rs/demo/main.js index d2f14e8..f75468e 100644 --- a/packages/npm-core-rs/demo/main.js +++ b/packages/npm-core-rs/demo/main.js @@ -1,44 +1,276 @@ -import { generateDocx } from "./browser-client.js"; +import { generateDocx, generateHwpx } from "./browser-client.js"; -const sampleMarkdown = `# Browser Demo +const locales = { + en: { + defaultTitle: "Docxly Adoption Report", + defaultFormat: "docx", + sampleMarkdown: `# Why Teams Should Install docxly -This DOCX file is generated in the browser with **docxly** and the same Rust core used by the npm package. +docxly is an **embeddable document generation engine** for teams that need DOCX or HWPX output inside a product, not as a separate conversion step. -> Markdown in. DOCX out. Browser only. +This report explains why installation is justified when product teams need faster document generation, browser-local workflows, and one shared Rust core across runtimes. -## Highlights +## Executive Summary -1. WebAssembly runtime -2. No backend conversion -3. Local download +> Install docxly when document generation must live inside your application, your browser workflow, or your delivery pipeline without depending on an external conversion service. -| Capability | Status | -| --- | --- | -| Markdown parsing | Browser | -| DOCX packaging | Browser | -| Open source package | npm + GitHub |`; +- **105x faster than Pandoc** on the current summary DOCX benchmark +- Browser-local generation with the same Rust core used in Node +- HWPX and DOCX supported from the same product surface +- Open-source package with [public repository](https://github.com/docxly/core-rs) + +## Why Installation Pays Off + +### 1. Product teams need an embeddable engine + +If your team generates proposals, reports, exports, or customer-facing files inside an application, the document engine should be part of the product stack. + +- No backend conversion dependency +- No separate CLI orchestration for the main flow +- One package to integrate into Node and browser contexts + +### 2. Speed changes user experience + +The current benchmark headline is simple: docxly measured **105x faster** than Pandoc on the summary DOCX benchmark. + +- Faster steady-state generation +- Better fit for interactive product workflows +- Lower friction for install justification + +### 3. One core supports multiple output paths + +docxly keeps the same core architecture across document workflows. + +- DOCX generation for broad office compatibility +- HWPX generation for Korean document workflows +- Shared Markdown-to-document model across runtimes + +## Recommended Installation Decision + +\`\`\`bash +npm install @docxly/core-rs +\`\`\` + +Install docxly if your team wants document generation to be a feature of the product instead of a separate conversion stage.`, + formatLabels: { + hwpx: "HWPX", + docx: "DOCX", + }, + defaultFilenames: { + hwpx: "docxly-browser-demo.hwpx", + docx: "docxly-browser-demo.docx", + }, + statusDescriptions: { + hwpx: "Generating HWPX in the browser...", + docx: "Generating DOCX in the browser...", + }, + comparison: { + unavailable: "Unavailable", + unavailableHeadline: "Offline benchmark data unavailable.", + availableHeadline: "Summary DOCX benchmark powered by the shared comparison dataset.", + fallbackProof: + "Install the embeddable DOCX engine backed by the same Rust core in Node, browser, and HWPX workflows.", + installProof: (ratio) => + `Install the embeddable DOCX engine that measured ${ratio} faster than Pandoc on the summary benchmark.`, + label: "Offline Node benchmark for library selection, not browser runtime timing.", + metaUnavailable: "comparison data unavailable", + meta: (data) => + `Measured on ${data.machine_label} at ${data.measured_at} with Node ${data.node_version} and Pandoc ${data.pandoc_version}.`, + badge: "HWPX support is docxly-only", + }, + copy: { + idle: "Copy", + success: "Copied", + fail: "Copy manually", + }, + formatSummary(activeFormat, defaultFormat) { + if (activeFormat === defaultFormat && defaultFormat === "hwpx") { + return "HWPX is the default path for Korean-language users in this demo. Switch tabs to generate DOCX instead."; + } + + if (activeFormat === defaultFormat) { + return "DOCX is the default path for non-Korean users in this demo. Switch tabs to generate HWPX instead."; + } + + if (defaultFormat === "hwpx") { + return "DOCX is one tab away from the Korean-language default. Switch back to HWPX to return to the default path."; + } + + return "HWPX is one tab away from the language-based default. Switch back to DOCX to return to the default path."; + }, + generateLabel: (formatLabel) => `Generate ${formatLabel}`, + ready: (formatLabel) => `Ready to generate ${formatLabel}.`, + success: (filename, elapsed, byteLength) => + `Generation started for ${filename} in ${elapsed} (${byteLength} bytes).`, + failed: (elapsed, message) => `Generation failed in ${elapsed}: ${message}`, + }, + ko: { + defaultTitle: "Docxly 도입 제안서", + defaultFormat: "hwpx", + sampleMarkdown: `# 왜 지금 docxly를 설치해야 하는가 + +docxly는 별도 변환 단계가 아니라 제품 안에서 직접 DOCX와 HWPX를 생성해야 하는 팀을 위한 **내장형 문서 생성 엔진**입니다. + +이 보고서는 제품 팀이 더 빠른 문서 생성, 브라우저 로컬 워크플로, 그리고 런타임 전반에서 공유되는 하나의 Rust 코어를 원할 때 왜 docxly 설치가 합리적인지 설명합니다. + +## 핵심 요약 + +> 문서 생성이 외부 변환 서비스가 아니라 애플리케이션과 브라우저 워크플로 안에 들어가야 한다면 docxly를 설치해야 합니다. + +- 현재 요약 DOCX 벤치마크에서 **Pandoc 대비 105배 빠른 속도** +- Node와 동일한 Rust 코어를 사용하는 브라우저 로컬 생성 +- 하나의 제품 표면에서 HWPX와 DOCX를 모두 지원 +- [공개 저장소](https://github.com/docxly/core-rs)를 가진 오픈소스 패키지 + +## 설치가 곧 제품 경쟁력이 되는 이유 + +### 1. 제품 안에 들어가는 엔진이 필요합니다 + +제안서, 보고서, 내보내기 문서, 고객용 결과물을 애플리케이션 안에서 생성한다면 문서 엔진도 제품 스택의 일부여야 합니다. + +- 메인 흐름에서 별도 백엔드 변환 의존성이 없습니다 +- 핵심 사용자 경험을 위해 별도의 CLI 오케스트레이션이 필요하지 않습니다 +- Node와 브라우저 컨텍스트를 하나의 패키지로 통합할 수 있습니다 + +### 2. 속도는 사용자 경험을 바꿉니다 + +현재 벤치마크의 핵심 문장은 명확합니다. docxly는 요약 DOCX 벤치마크에서 Pandoc보다 **105배 빠르게** 측정되었습니다. + +- 반복 생성 구간에서 더 빠른 steady-state 성능 +- 인터랙티브한 제품 워크플로에 더 적합한 응답성 +- 설치 의사결정을 쉽게 만드는 명확한 수치 + +### 3. 하나의 코어가 여러 문서 경로를 지원합니다 + +docxly는 문서 워크플로 전체에서 같은 코어 아키텍처를 유지합니다. + +- 광범위한 오피스 호환성을 위한 DOCX 생성 +- 한국 문서 워크플로를 위한 HWPX 생성 +- 런타임 전반에서 공유되는 Markdown 문서 모델 + +## 권장 설치 결정 + +\`\`\`bash +npm install @docxly/core-rs +\`\`\` + +문서 생성을 별도 변환 단계가 아니라 제품 기능으로 만들고 싶다면 docxly를 설치해야 합니다.`, + formatLabels: { + hwpx: "HWPX", + docx: "DOCX", + }, + defaultFilenames: { + hwpx: "docxly-브라우저-데모.hwpx", + docx: "docxly-브라우저-데모.docx", + }, + statusDescriptions: { + hwpx: "브라우저에서 HWPX를 생성하고 있습니다...", + docx: "브라우저에서 DOCX를 생성하고 있습니다...", + }, + comparison: { + unavailable: "준비 중", + unavailableHeadline: "오프라인 벤치마크 데이터를 불러오지 못했습니다.", + availableHeadline: "공유 비교 데이터셋으로 측정한 요약 DOCX 벤치마크입니다.", + fallbackProof: + "Node, 브라우저, HWPX 워크플로 전체에서 같은 Rust 코어를 사용하는 내장형 DOCX 엔진을 설치하세요.", + installProof: (ratio) => + `요약 벤치마크에서 Pandoc보다 ${ratio} 빠르게 측정된 내장형 DOCX 엔진을 설치하세요.`, + label: "브라우저 실행 시간이 아닌, 라이브러리 선택을 위한 Node 벤치마크입니다.", + metaUnavailable: "비교 데이터를 불러오지 못했습니다.", + meta: (data) => + `${data.machine_label}에서 ${data.measured_at}에 측정했으며, Node ${data.node_version} 및 Pandoc ${data.pandoc_version} 기준입니다.`, + badge: "HWPX 지원은 docxly 전용입니다", + }, + copy: { + idle: "복사", + success: "복사됨", + fail: "수동 복사", + }, + formatSummary(activeFormat, defaultFormat) { + if (activeFormat === defaultFormat && defaultFormat === "hwpx") { + return "한국어 사용자에게는 HWPX가 기본 경로입니다. 다른 형식이 필요하면 DOCX 탭으로 전환하세요."; + } + + if (activeFormat === defaultFormat) { + return "비한국어 사용자에게는 DOCX가 기본 경로입니다. HWPX가 필요하면 탭을 전환하세요."; + } + + if (defaultFormat === "hwpx") { + return "DOCX는 한국어 기본 경로에서 한 탭 떨어져 있습니다. 기본값으로 돌아가려면 HWPX를 선택하세요."; + } + + return "HWPX는 언어 기반 기본 경로에서 한 탭 떨어져 있습니다. 기본값으로 돌아가려면 DOCX를 선택하세요."; + }, + generateLabel: (formatLabel) => `${formatLabel} 생성`, + ready: (formatLabel) => `${formatLabel}를 생성할 준비가 되었습니다.`, + success: (filename, elapsed, byteLength) => + `${filename} 생성이 ${elapsed} 만에 시작되었습니다. (${byteLength} bytes)`, + failed: (elapsed, message) => `${elapsed} 후 생성에 실패했습니다: ${message}`, + }, +}; const markdownInput = document.querySelector("#markdown-input"); const titleInput = document.querySelector("#title-input"); const authorInput = document.querySelector("#author-input"); const strictModeInput = document.querySelector("#strict-mode-input"); const generateButton = document.querySelector("#generate-button"); -const downloadLink = document.querySelector("#download-link"); +const formatTabs = Array.from(document.querySelectorAll(".format-tab")); +const formatSummary = document.querySelector("#format-summary"); +const generateButtonLabel = document.querySelector("#generate-button-label"); const status = document.querySelector("#status"); const copyInstallButton = document.querySelector("#copy-install-button"); const installCommand = document.querySelector("#install-command"); -const defaultFilename = "docxly-browser-demo.docx"; -let activeObjectUrl = null; +const installProof = document.querySelector("#install-proof"); +const comparisonHeadline = document.querySelector("#comparison-headline"); +const comparisonDocxlyMs = document.querySelector("#comparison-docxly-ms"); +const comparisonPandocMs = document.querySelector("#comparison-pandoc-ms"); +const comparisonRatio = document.querySelector("#comparison-ratio"); +const comparisonLabel = document.querySelector("#comparison-label"); +const comparisonMeta = document.querySelector("#comparison-meta"); +const comparisonHwpxBadge = document.querySelector("#comparison-hwpx-badge"); +const mimeTypes = { + hwpx: "application/haansofthwp", + docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document", +}; +const pageLocale = (document.body.dataset.pageLocale || document.documentElement.lang || "en") + .toLowerCase() + .trim(); +const ui = pageLocale.startsWith("ko") ? locales.ko : locales.en; +const rootPath = document.body.dataset.rootPath || "."; +const comparisonDataUrl = new URL("comparison-data.json", new URL(`${rootPath}/`, window.location.href)); +const defaultFormat = ui.defaultFormat; +let activeFormat = defaultFormat; let copyResetTimer = null; -markdownInput.value = sampleMarkdown; +markdownInput.value = ui.sampleMarkdown; +titleInput.value = ui.defaultTitle; function setStatus(message, type = "idle") { status.textContent = message; status.dataset.state = type; } -function buildFilename(rawTitle) { +function formatElapsedMs(startedAt) { + return `${Math.round(performance.now() - startedAt)} ms`; +} + +function formatMetricMs(value) { + return Number.isFinite(value) ? `${Math.round(value)} ms` : ui.comparison.unavailable; +} + +function formatMetricRatio(value) { + if (!Number.isFinite(value)) { + return ui.comparison.unavailable; + } + + if (Math.abs(value - Math.round(value)) < 0.01) { + return `${Math.round(value)}x`; + } + + return value >= 10 ? `${value.toFixed(1)}x` : `${value.toFixed(2)}x`; +} + +function buildFilename(rawTitle, format) { const normalized = (rawTitle || "") .trim() .replace(/[<>:"/\\|?*\u0000-\u001f]/g, "") @@ -46,19 +278,35 @@ function buildFilename(rawTitle) { .replace(/-+/g, "-") .replace(/^-|-$/g, ""); - const base = normalized || "docxly-browser-demo"; - return base.toLowerCase().endsWith(".docx") ? base : `${base}.docx`; + const base = normalized || ui.defaultFilenames[format].replace(/\.[^.]+$/, ""); + const extension = `.${format}`; + return base.toLowerCase().endsWith(extension) ? base : `${base}${extension}`; } -function updateDownloadLinkLabel(filename) { - downloadLink.innerHTML = ` - - Download ${filename} again - `; +function setCopyButton(state) { + const labels = ui.copy; + const variants = { + idle: { icon: "⧉", label: labels.idle }, + success: { icon: "✓", label: labels.success }, + fail: { icon: "!", label: labels.fail }, + }; + const variant = variants[state]; + copyInstallButton.innerHTML = `${variant.label}`; +} + +function updateFormatUi() { + for (const tab of formatTabs) { + const isActive = tab.dataset.format === activeFormat; + tab.classList.toggle("is-active", isActive); + tab.setAttribute("aria-selected", String(isActive)); + } + + generateButtonLabel.textContent = ui.generateLabel(ui.formatLabels[activeFormat]); + formatSummary.textContent = ui.formatSummary(activeFormat, defaultFormat); } function resetCopyButton() { - copyInstallButton.innerHTML = 'Copy'; + setCopyButton("idle"); } async function copyInstallCommand() { @@ -66,73 +314,112 @@ async function copyInstallCommand() { try { await navigator.clipboard.writeText(command); - copyInstallButton.innerHTML = 'Copied'; + setCopyButton("success"); window.clearTimeout(copyResetTimer); copyResetTimer = window.setTimeout(resetCopyButton, 1600); } catch { - copyInstallButton.innerHTML = 'Copy manually'; + setCopyButton("fail"); window.clearTimeout(copyResetTimer); copyResetTimer = window.setTimeout(resetCopyButton, 2000); } } function triggerDownload(blob, filename) { - if (activeObjectUrl) { - URL.revokeObjectURL(activeObjectUrl); - } - - activeObjectUrl = URL.createObjectURL(blob); - downloadLink.href = activeObjectUrl; - downloadLink.download = filename; - updateDownloadLinkLabel(filename); - downloadLink.classList.remove("hidden"); - + const objectUrl = URL.createObjectURL(blob); const triggerLink = document.createElement("a"); - triggerLink.href = activeObjectUrl; + triggerLink.href = objectUrl; triggerLink.download = filename; - triggerLink.textContent = `Download ${filename}`; + triggerLink.textContent = `Generate ${filename}`; triggerLink.style.position = "fixed"; triggerLink.style.left = "-9999px"; triggerLink.style.top = "0"; document.body.append(triggerLink); triggerLink.click(); triggerLink.remove(); + window.setTimeout(() => URL.revokeObjectURL(objectUrl), 0); +} + +function renderComparisonUnavailable() { + comparisonHeadline.textContent = ui.comparison.unavailableHeadline; + comparisonDocxlyMs.textContent = ui.comparison.unavailable; + comparisonPandocMs.textContent = ui.comparison.unavailable; + comparisonRatio.textContent = ui.comparison.unavailable; + installProof.textContent = ui.comparison.fallbackProof; + comparisonLabel.textContent = ui.comparison.label; + comparisonMeta.textContent = ui.comparison.metaUnavailable; + comparisonHwpxBadge.textContent = ui.comparison.badge; } -window.addEventListener("pagehide", () => { - if (activeObjectUrl) { - URL.revokeObjectURL(activeObjectUrl); - activeObjectUrl = null; +function renderComparison(data) { + const summary = data?.benchmarks?.summary; + + if (!summary) { + renderComparisonUnavailable(); + return; } -}); + + comparisonHeadline.textContent = ui.comparison.availableHeadline; + comparisonDocxlyMs.textContent = formatMetricMs(summary.steady_median_ms?.docxly); + comparisonPandocMs.textContent = formatMetricMs(summary.steady_median_ms?.pandoc); + comparisonRatio.textContent = formatMetricRatio(summary.speed_ratio); + installProof.textContent = ui.comparison.installProof(formatMetricRatio(summary.speed_ratio)); + comparisonLabel.textContent = ui.comparison.label; + comparisonMeta.textContent = ui.comparison.meta(data); + comparisonHwpxBadge.textContent = ui.comparison.badge; +} + +async function loadComparison() { + try { + const response = await fetch(comparisonDataUrl, { cache: "no-store" }); + if (!response.ok) { + throw new Error(`failed to load comparison data: ${response.status}`); + } + + const data = await response.json(); + renderComparison(data); + } catch { + renderComparisonUnavailable(); + } +} copyInstallButton.addEventListener("click", () => { void copyInstallCommand(); }); +for (const tab of formatTabs) { + tab.addEventListener("click", () => { + activeFormat = tab.dataset.format; + updateFormatUi(); + setStatus(ui.ready(ui.formatLabels[activeFormat])); + }); +} + generateButton.addEventListener("click", async () => { generateButton.disabled = true; - downloadLink.classList.add("hidden"); - downloadLink.removeAttribute("href"); - setStatus("Generating DOCX in the browser...", "pending"); + setStatus(ui.statusDescriptions[activeFormat], "pending"); + const startedAt = performance.now(); try { - const bytes = await generateDocx(markdownInput.value, { + const generator = activeFormat === "hwpx" ? generateHwpx : generateDocx; + const bytes = await generator(markdownInput.value, { title: titleInput.value || undefined, author: authorInput.value || undefined, strictMode: strictModeInput.checked, }); - const blob = new Blob([bytes], { - type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document", - }); - const filename = buildFilename(titleInput.value || defaultFilename); + const blob = new Blob([bytes], { type: mimeTypes[activeFormat] }); + const filename = buildFilename(titleInput.value || ui.defaultFilenames[activeFormat], activeFormat); triggerDownload(blob, filename); - setStatus(`Download started for ${filename} (${bytes.length} bytes).`, "success"); + setStatus(ui.success(filename, formatElapsedMs(startedAt), bytes.length), "success"); } catch (error) { const message = error instanceof Error ? error.message : String(error); - setStatus(`Generation failed: ${message}`, "error"); + setStatus(ui.failed(formatElapsedMs(startedAt), message), "error"); } finally { generateButton.disabled = false; } }); + +updateFormatUi(); +resetCopyButton(); +setStatus(ui.ready(ui.formatLabels[activeFormat])); +void loadComparison(); diff --git a/packages/npm-core-rs/demo/styles.css b/packages/npm-core-rs/demo/styles.css index 4eac735..dd7b2cd 100644 --- a/packages/npm-core-rs/demo/styles.css +++ b/packages/npm-core-rs/demo/styles.css @@ -1,17 +1,19 @@ :root { color-scheme: light; - --bg: #f3ede0; - --panel: rgba(255, 252, 245, 0.94); - --ink: #18241f; - --muted: #5e675d; - --line: rgba(24, 36, 31, 0.15); - --accent: #d96f32; - --accent-strong: #bc5620; - --accent-soft: rgba(217, 111, 50, 0.14); - --accent-ink: #fff8f1; - --success: #1f7a4c; - --error: #a3301c; - --shadow: 0 24px 80px rgba(36, 28, 18, 0.14); + --color-primary-050: #eff6ff; + --color-primary-100: #dbeafe; + --color-primary-500: #2563eb; + --color-primary-600: #1d4ed8; + --color-primary-700: #1e40af; + --color-neutral-950: #0f172a; + --color-neutral-700: #334155; + --color-neutral-500: #64748b; + --color-neutral-200: #e2e8f0; + --color-surface: #ffffff; + --color-surface-muted: #f8fafc; + --color-success: #15803d; + --color-error: #b91c1c; + --shadow: 0 24px 80px rgba(15, 23, 42, 0.08); } * { @@ -22,229 +24,360 @@ body { margin: 0; min-height: 100vh; background: - radial-gradient(circle at top left, rgba(217, 111, 50, 0.16), transparent 28%), - radial-gradient(circle at top right, rgba(24, 36, 31, 0.08), transparent 24%), - linear-gradient(180deg, #f7f1e5 0%, var(--bg) 100%); - color: var(--ink); - font: 16px/1.5 "Iowan Old Style", "Palatino Linotype", "URW Palladio L", serif; + radial-gradient(circle at top left, rgba(37, 99, 235, 0.16), transparent 26%), + linear-gradient(180deg, var(--color-primary-050) 0%, #f8fbff 100%); + color: var(--color-neutral-700); + font: 16px/1.6 "Iowan Old Style", "Palatino Linotype", "URW Palladio L", serif; } .page { - width: min(980px, calc(100vw - 32px)); + width: min(1120px, calc(100vw - 32px)); margin: 0 auto; - padding: 48px 0 64px; + padding: 40px 0 64px; } -.hero { +.panel { + padding: 24px; + border: 1px solid var(--color-neutral-200); + border-radius: 20px; + background: rgba(255, 255, 255, 0.94); + box-shadow: var(--shadow); +} + +.hero-panel { margin-bottom: 24px; } -.hero-actions { +.hero-topbar { display: flex; - flex-wrap: wrap; - gap: 12px; - margin-top: 20px; + align-items: flex-start; + justify-content: space-between; + gap: 16px; + margin-bottom: 20px; } -.hero-link { +.brand-lockup { display: inline-flex; align-items: center; gap: 12px; - min-height: 56px; - padding: 12px 16px; - border: 1px solid rgba(24, 36, 31, 0.14); - border-radius: 20px; - background: rgba(255, 255, 255, 0.72); - color: var(--ink); - text-decoration: none; - font: 700 14px/1 "IBM Plex Sans", "Segoe UI", sans-serif; - box-shadow: 0 10px 24px rgba(36, 28, 18, 0.06); -} - -.hero-link-primary { - border-color: transparent; - background: var(--accent); - color: var(--accent-ink); } -.hero-link-secondary { - background: rgba(24, 36, 31, 0.92); - border-color: transparent; - color: #fff8f1; +.lang-switch { + display: inline-flex; + flex-wrap: wrap; + gap: 8px; } -.hero-link-icon { +.lang-switch-link { display: inline-flex; align-items: center; - justify-content: center; - width: 36px; - height: 36px; - border-radius: 12px; - background: rgba(255, 255, 255, 0.12); - flex: 0 0 auto; + min-height: 34px; + padding: 0 12px; + border: 1px solid var(--color-neutral-200); + border-radius: 999px; + background: var(--color-surface-muted); + color: var(--color-neutral-700); + text-decoration: none; + font: 700 12px/1 "IBM Plex Sans", "Segoe UI", sans-serif; + letter-spacing: 0.06em; } -.hero-link-secondary .hero-link-icon { - background: rgba(255, 255, 255, 0.14); +.lang-switch-link.is-active { + border-color: var(--color-primary-100); + background: var(--color-primary-050); + color: var(--color-primary-700); } -.hero-link-icon svg { - width: 20px; - height: 20px; +.brand-logo { + width: auto; + height: 28px; + object-fit: contain; } -.hero-link-copy { +.brand-copy { display: grid; - gap: 4px; + gap: 2px; } -.hero-link-copy strong { - font-size: 14px; - letter-spacing: 0.04em; +.brand-wordmark { + color: var(--color-neutral-950); + font: 700 22px/1 "IBM Plex Sans", "Segoe UI", sans-serif; + letter-spacing: -0.03em; +} + +.brand-tagline, +.panel-eyebrow, +.comparison-kpi-label, +.field span, +.checkbox span, +.install-card-eyebrow { + display: block; + margin: 0; + color: var(--color-primary-600); + font: 700 12px/1.2 "IBM Plex Sans", "Segoe UI", sans-serif; + letter-spacing: 0.14em; text-transform: uppercase; } -.hero-link-copy small { - font: 500 12px/1.2 "IBM Plex Sans", "Segoe UI", sans-serif; - letter-spacing: 0; - text-transform: none; - opacity: 0.82; +h1 { + max-width: 13ch; + margin: 0; + color: var(--color-neutral-950); + font-size: clamp(2.5rem, 5vw, 4.4rem); + line-height: 0.95; +} + +h2 { + margin: 0; + color: var(--color-neutral-950); + font-size: clamp(1.45rem, 2.5vw, 2rem); + line-height: 1.05; +} + +.lede, +.panel-copy, +.comparison-headline, +.comparison-meta, +.install-card-copy, +.note, +.format-summary { + color: var(--color-neutral-500); +} + +.lede { + max-width: 48rem; + margin: 16px 0 0; } -.hero-grid { +.hero-shell { display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 16px; + grid-template-columns: minmax(0, 1.45fr) minmax(320px, 0.82fr); + gap: 24px; margin-top: 24px; } -.hero-card { - padding: 18px; - border: 1px solid var(--line); - border-radius: 20px; - background: rgba(255, 251, 244, 0.82); - box-shadow: 0 14px 36px rgba(36, 28, 18, 0.08); +.hero-main { + display: grid; + gap: 16px; } -.hero-card h2 { - margin: 0 0 10px; - font: 700 14px/1.2 "IBM Plex Sans", "Segoe UI", sans-serif; +.hero-actions { + display: flex; + flex-wrap: wrap; + gap: 10px; +} + +.hero-link { + display: inline-flex; + align-items: center; + justify-content: center; + min-height: 44px; + padding: 0 16px; + border-radius: 999px; + text-decoration: none; + font: 700 13px/1 "IBM Plex Sans", "Segoe UI", sans-serif; letter-spacing: 0.08em; text-transform: uppercase; } -.hero-card p, -.hero-card pre { - margin: 0; +.hero-link-primary { + background: var(--color-primary-600); + color: #ffffff; } -.hero-card-code { - background: #18241f; - color: #f7f1e5; +.hero-link-primary:hover { + background: var(--color-primary-700); } -.hero-card-head { - display: flex; - align-items: center; - justify-content: space-between; +.hero-link-secondary { + background: var(--color-surface-muted); + color: var(--color-neutral-700); +} + +.comparison-strip { + padding: 18px; + border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: 20px; + background: linear-gradient(150deg, var(--color-neutral-950), #16284f); + color: #ffffff; +} + +.comparison-strip-head { + display: grid; + grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.9fr); + gap: 16px; + align-items: end; + margin-bottom: 16px; +} + +.comparison-strip-head h2 { + color: #ffffff; + font-size: clamp(1.2rem, 2vw, 1.55rem); +} + +.comparison-headline, +.comparison-meta { + margin: 0; + color: rgba(255, 255, 255, 0.72); +} + +.comparison-kpis { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; margin-bottom: 12px; } -.hero-card-head h2 { - margin: 0; +.comparison-kpi { + padding: 14px; + border: 1px solid rgba(255, 255, 255, 0.12); + border-radius: 16px; + background: rgba(255, 255, 255, 0.05); } -.copy-button { +.comparison-kpi-label { + margin-bottom: 6px; + color: rgba(255, 255, 255, 0.68); +} + +.comparison-kpi-value { + display: block; + font: 700 clamp(1.35rem, 2.5vw, 2rem)/1 "IBM Plex Sans", "Segoe UI", sans-serif; +} + +.comparison-label { + margin: 0 0 6px; + color: #bfdbfe; + font: 700 12px/1.2 "IBM Plex Sans", "Segoe UI", sans-serif; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +.comparison-meta { + margin-bottom: 14px; + font-size: 14px; +} + +.comparison-badge { display: inline-flex; align-items: center; - gap: 6px; min-height: 34px; - padding: 0 12px; - border: 1px solid rgba(255, 255, 255, 0.16); + padding: 0 14px; border-radius: 999px; - background: rgba(255, 255, 255, 0.08); - color: inherit; + background: rgba(59, 130, 246, 0.16); + color: #dbeafe; font: 700 12px/1 "IBM Plex Sans", "Segoe UI", sans-serif; - letter-spacing: 0.06em; + letter-spacing: 0.08em; text-transform: uppercase; - cursor: pointer; } -.copy-button:hover { - background: rgba(255, 255, 255, 0.14); +.install-card { + display: grid; + gap: 14px; + padding: 20px; + border-radius: 20px; + background: var(--color-surface); + border: 1px solid var(--color-primary-100); +} + +.install-card h2 { + max-width: 14ch; +} + +.install-proof { + margin: 0; + color: var(--color-neutral-950); + font: 700 18px/1.45 "IBM Plex Sans", "Segoe UI", sans-serif; } -.hero-card-code pre { +.install-card pre { overflow-x: auto; + margin: 0; padding: 14px 16px; border-radius: 16px; - background: rgba(255, 255, 255, 0.06); + background: var(--color-neutral-950); + color: #ffffff; white-space: pre-wrap; word-break: break-word; font: 15px/1.5 "SFMono-Regular", "Consolas", monospace; } -.hero-card-caption { - margin-top: 12px; - color: rgba(247, 241, 229, 0.72); - font-size: 14px; +.install-card-actions { + display: flex; + align-items: center; } -.eyebrow { - margin: 0 0 8px; - color: var(--accent); - font: 700 12px/1.2 "IBM Plex Sans", "Segoe UI", sans-serif; - letter-spacing: 0.16em; +.copy-button { + display: inline-flex; + align-items: center; + gap: 6px; + min-height: 40px; + padding: 0 14px; + border: 1px solid var(--color-primary-100); + border-radius: 999px; + background: var(--color-primary-050); + color: var(--color-primary-700); + font: 700 12px/1 "IBM Plex Sans", "Segoe UI", sans-serif; + letter-spacing: 0.06em; text-transform: uppercase; + cursor: pointer; } -h1 { - margin: 0; - font-size: clamp(2.4rem, 5vw, 4.2rem); - line-height: 0.94; +.copy-button:hover { + background: var(--color-primary-100); } -.lede { - max-width: 56rem; - margin: 16px 0 0; - color: var(--muted); +.install-card-copy { + margin: 0; } -.panel { - padding: 24px; - border: 1px solid var(--line); - border-radius: 24px; - background: var(--panel); - box-shadow: var(--shadow); - backdrop-filter: blur(16px); +.generator-panel { + display: grid; + gap: 18px; } .panel-heading { display: grid; - grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr); + grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr); gap: 16px; align-items: end; - margin-bottom: 20px; } -.panel-heading h2 { +.panel-copy { margin: 0; - font-size: clamp(1.6rem, 3vw, 2.3rem); - line-height: 0.98; } -.panel-eyebrow { - margin: 0 0 8px; - color: var(--accent); - font: 700 12px/1.2 "IBM Plex Sans", "Segoe UI", sans-serif; - letter-spacing: 0.16em; +.format-tabs { + display: inline-flex; + flex-wrap: wrap; + gap: 8px; + padding: 6px; + border: 1px solid var(--color-neutral-200); + border-radius: 999px; + background: var(--color-surface-muted); +} + +.format-tab { + min-height: 42px; + padding: 0 18px; + border: 0; + border-radius: 999px; + background: transparent; + color: var(--color-neutral-500); + font: 700 13px/1 "IBM Plex Sans", "Segoe UI", sans-serif; + letter-spacing: 0.08em; text-transform: uppercase; + cursor: pointer; } -.panel-copy { - margin: 0; - color: var(--muted); +.format-tab.is-active { + background: var(--color-primary-500); + color: #ffffff; +} + +.format-summary { + margin: -6px 0 0; + font-size: 14px; } .field, @@ -254,20 +387,16 @@ h1 { .field span, .checkbox span { - display: block; margin-bottom: 8px; - font: 700 12px/1.2 "IBM Plex Sans", "Segoe UI", sans-serif; - letter-spacing: 0.06em; - text-transform: uppercase; } textarea, input { width: 100%; - border: 1px solid var(--line); + border: 1px solid var(--color-neutral-200); border-radius: 16px; - background: rgba(255, 255, 255, 0.8); - color: var(--ink); + background: #ffffff; + color: var(--color-neutral-700); padding: 14px 16px; font: 15px/1.5 "SFMono-Regular", "Consolas", monospace; } @@ -275,18 +404,12 @@ input { textarea { min-height: 260px; resize: vertical; - margin-bottom: 20px; } .grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; - margin-bottom: 16px; -} - -.checkbox { - margin-bottom: 20px; } .checkbox input { @@ -301,13 +424,10 @@ textarea { .actions { display: flex; - flex-wrap: wrap; - gap: 12px; align-items: center; } -button, -.download { +.action-button { display: inline-flex; align-items: center; justify-content: center; @@ -316,37 +436,27 @@ button, padding: 0 18px; border: 0; border-radius: 999px; - text-decoration: none; + box-shadow: 0 14px 28px rgba(37, 99, 235, 0.16); font: 700 14px/1 "IBM Plex Sans", "Segoe UI", sans-serif; letter-spacing: 0.04em; text-transform: uppercase; } -.action-button, -.download { - box-shadow: 0 14px 28px rgba(36, 28, 18, 0.12); -} - .action-button-primary { - background: linear-gradient(180deg, var(--accent) 0%, var(--accent-strong) 100%); - color: var(--accent-ink); + background: var(--color-primary-600); + color: #ffffff; cursor: pointer; } .action-button-primary:hover:not(:disabled) { - transform: translateY(-1px); + background: var(--color-primary-700); } -button:disabled { +.action-button:disabled { opacity: 0.6; cursor: wait; } -.download { - background: var(--accent-soft); - color: var(--accent-strong); -} - .action-symbol { display: inline-flex; align-items: center; @@ -354,57 +464,70 @@ button:disabled { width: 22px; height: 22px; border-radius: 999px; - background: rgba(255, 255, 255, 0.16); + background: rgba(255, 255, 255, 0.18); font-size: 14px; line-height: 1; } -.download .action-symbol { - background: rgba(188, 86, 32, 0.14); -} - -.hidden { - display: none; -} - .status { min-height: 24px; - margin: 16px 0 0; - color: var(--muted); + margin: 0; + color: var(--color-neutral-500); } .status[data-state="pending"] { - color: var(--ink); + color: var(--color-neutral-700); } .status[data-state="success"] { - color: var(--success); + color: var(--color-success); } .status[data-state="error"] { - color: var(--error); + color: var(--color-error); } .note { - margin: 12px 0 0; - color: var(--muted); + margin: 0; font-size: 14px; } @media (max-width: 820px) { - .hero-grid, + .page { + width: min(100vw - 20px, 1120px); + padding: 24px 0 40px; + } + + .brand-logo { + height: 24px; + } + + .hero-topbar { + flex-direction: column; + align-items: flex-start; + } + + .hero-shell, + .comparison-strip-head, + .comparison-kpis, .panel-heading, .grid { grid-template-columns: 1fr; } - .page { - width: min(100vw - 20px, 980px); - padding: 24px 0 40px; + .hero-shell { + gap: 16px; + } + + .hero-main { + order: 2; + } + + .install-card { + order: 1; } - .panel, - .hero-card { + .panel { padding: 18px; } } diff --git a/packages/npm-core-rs/package.json b/packages/npm-core-rs/package.json index 923ba80..2f2b468 100644 --- a/packages/npm-core-rs/package.json +++ b/packages/npm-core-rs/package.json @@ -33,6 +33,8 @@ "access": "public" }, "scripts": { + "benchmark:pandoc": "node ./scripts/benchmark-pandoc.mjs", + "benchmark:pandoc:check": "node ./scripts/benchmark-pandoc-check.mjs", "clean": "rm -rf dist site-dist", "check:version": "node ./scripts/check-version.mjs", "build": "node ./scripts/build.mjs", diff --git a/packages/npm-core-rs/scripts/_benchmark_docxly_once.mjs b/packages/npm-core-rs/scripts/_benchmark_docxly_once.mjs new file mode 100644 index 0000000..22522c7 --- /dev/null +++ b/packages/npm-core-rs/scripts/_benchmark_docxly_once.mjs @@ -0,0 +1,14 @@ +import { readFile, writeFile } from "node:fs/promises"; + +import { generateDocx } from "../dist/node.js"; + +const [, , inputPath, outputPath] = process.argv; + +if (!inputPath || !outputPath) { + console.error("usage: node ./scripts/_benchmark_docxly_once.mjs "); + process.exit(1); +} + +const markdown = await readFile(inputPath, "utf8"); +const bytes = await generateDocx(markdown); +await writeFile(outputPath, bytes); diff --git a/packages/npm-core-rs/scripts/_comparison-common.mjs b/packages/npm-core-rs/scripts/_comparison-common.mjs new file mode 100644 index 0000000..bb8e6a5 --- /dev/null +++ b/packages/npm-core-rs/scripts/_comparison-common.mjs @@ -0,0 +1,158 @@ +import assert from "node:assert/strict"; +import { readFile } from "node:fs/promises"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +export const packageRoot = path.dirname(fileURLToPath(new URL("../package.json", import.meta.url))); +export const repoRoot = path.resolve(packageRoot, "..", ".."); +export const comparisonDataPath = path.join(packageRoot, "demo", "comparison-data.json"); +export const readmePath = path.join(repoRoot, "README.md"); +export const demoIndexPath = path.join(packageRoot, "demo", "index.html"); +export const demoMainPath = path.join(packageRoot, "demo", "main.js"); +export const comparisonStartMarker = ""; +export const comparisonEndMarker = ""; +const decoder = new TextDecoder(); + +export function formatMs(value) { + return Number.isFinite(value) ? `${Math.round(value)} ms` : "Unavailable"; +} + +export function formatRatio(value) { + return Number.isFinite(value) ? `${value.toFixed(2)}x` : "Unavailable"; +} + +export function decodeUtf8(bytes) { + return decoder.decode(bytes); +} + +export function validateComparisonData(data) { + assert.equal(typeof data, "object", "comparison data must be an object"); + assert.equal(typeof data.headline, "string", "headline must be a string"); + assert.equal(typeof data.measured_at, "string", "measured_at must be a string"); + assert.equal(typeof data.machine_label, "string", "machine_label must be a string"); + assert.equal(typeof data.node_version, "string", "node_version must be a string"); + assert.equal(typeof data.pandoc_version, "string", "pandoc_version must be a string"); + assert.equal(typeof data.docxly_version, "string", "docxly_version must be a string"); + assert.equal(typeof data.benchmarks, "object", "benchmarks must be present"); + assert.equal(Array.isArray(data.feature_matrix), true, "feature_matrix must be an array"); + assert.equal(Array.isArray(data.caveats), true, "caveats must be an array"); + + for (const key of ["small", "medium", "large", "summary"]) { + validateBenchmarkEntry(data.benchmarks[key], key); + } + + for (const entry of data.feature_matrix) { + assert.equal(typeof entry.feature, "string", "feature name must be a string"); + assert.equal(typeof entry.docxly, "string", "feature docxly value must be a string"); + assert.equal(typeof entry.pandoc, "string", "feature pandoc value must be a string"); + } + + for (const caveat of data.caveats) { + assert.equal(typeof caveat, "string", "caveat must be a string"); + } + + return data; +} + +function validateBenchmarkEntry(entry, name) { + assert.equal(typeof entry, "object", `${name} benchmark must be an object`); + assert.equal(typeof entry.label, "string", `${name}.label must be a string`); + validateDuelMetric(entry.cold_ms, `${name}.cold_ms`); + validateDuelMetric(entry.steady_median_ms, `${name}.steady_median_ms`); + validateDuelMetric(entry.output_bytes, `${name}.output_bytes`); + assert.equal(typeof entry.speed_ratio, "number", `${name}.speed_ratio must be a number`); +} + +function validateDuelMetric(metric, name) { + assert.equal(typeof metric, "object", `${name} must be an object`); + assert.equal(typeof metric.docxly, "number", `${name}.docxly must be a number`); + assert.equal(typeof metric.pandoc, "number", `${name}.pandoc must be a number`); +} + +export async function loadComparisonData() { + const raw = await readFile(comparisonDataPath, "utf8"); + return validateComparisonData(JSON.parse(raw)); +} + +export function renderReadmeBlock(data) { + validateComparisonData(data); + + const benchmarkRows = ["small", "medium", "large", "summary"] + .map((key) => { + const entry = data.benchmarks[key]; + return [ + entry.label, + formatMs(entry.cold_ms.docxly), + formatMs(entry.cold_ms.pandoc), + formatMs(entry.steady_median_ms.docxly), + formatMs(entry.steady_median_ms.pandoc), + formatRatio(entry.speed_ratio), + ].join(" | "); + }) + .map((row) => `| ${row} |`) + .join("\n"); + + const featureRows = data.feature_matrix + .map( + (entry) => + `| ${escapeCell(entry.feature)} | ${escapeCell(entry.docxly)} | ${escapeCell(entry.pandoc)} |`, + ) + .join("\n"); + + const caveatRows = data.caveats.map((caveat) => `- ${caveat}`).join("\n"); + + return [ + "## Why docxly instead of Pandoc?", + "", + "Pandoc is a general-purpose converter; docxly is an embeddable generation engine.", + "", + "Use docxly when document generation must live inside a Node service, browser workflow, or product surface. Use Pandoc when you need broad format conversion and a CLI-first publishing workflow.", + "", + "| Corpus | docxly cold | Pandoc cold | docxly steady | Pandoc steady | Speed ratio |", + "| --- | --- | --- | --- | --- | --- |", + benchmarkRows, + "", + "| Capability | docxly | Pandoc |", + "| --- | --- | --- |", + featureRows, + "", + `Measured on ${data.machine_label} at ${data.measured_at} with Node ${data.node_version} and Pandoc ${data.pandoc_version}.`, + "", + caveatRows, + "", + ].join("\n"); +} + +export function replaceReadmeBlock(readme, block) { + const start = readme.indexOf(comparisonStartMarker); + const end = readme.indexOf(comparisonEndMarker); + + assert.notEqual(start, -1, "README comparison start marker is missing"); + assert.notEqual(end, -1, "README comparison end marker is missing"); + assert.ok(end > start, "README comparison markers are out of order"); + + const prefix = readme.slice(0, start + comparisonStartMarker.length); + const suffix = readme.slice(end); + return `${prefix}\n\n${block}\n${suffix}`; +} + +export async function readPackageVersion() { + const packageJson = JSON.parse(await readFile(path.join(packageRoot, "package.json"), "utf8")); + return packageJson.version; +} + +export async function assertDemoWiring() { + const [indexHtml, mainJs] = await Promise.all([ + readFile(demoIndexPath, "utf8"), + readFile(demoMainPath, "utf8"), + ]); + + assert.ok(indexHtml.includes('id="comparison-section"'), "demo comparison section is missing"); + assert.ok(indexHtml.includes('id="comparison-ratio"'), "demo comparison KPI is missing"); + assert.ok(mainJs.includes('fetch("./comparison-data.json"'), "demo must fetch comparison JSON"); + assert.ok(mainJs.includes("comparison data unavailable"), "demo fallback copy is missing"); +} + +function escapeCell(value) { + return value.replaceAll("|", "\\|"); +} diff --git a/packages/npm-core-rs/scripts/benchmark-pandoc-check.mjs b/packages/npm-core-rs/scripts/benchmark-pandoc-check.mjs new file mode 100644 index 0000000..415ae16 --- /dev/null +++ b/packages/npm-core-rs/scripts/benchmark-pandoc-check.mjs @@ -0,0 +1,19 @@ +import assert from "node:assert/strict"; +import { readFile } from "node:fs/promises"; + +import { + assertDemoWiring, + loadComparisonData, + readmePath, + renderReadmeBlock, + replaceReadmeBlock, +} from "./_comparison-common.mjs"; + +const data = await loadComparisonData(); +const readme = await readFile(readmePath, "utf8"); +const expected = replaceReadmeBlock(readme, renderReadmeBlock(data)); + +assert.equal(readme, expected, "README comparison block is out of sync with comparison-data.json"); +await assertDemoWiring(); + +console.log("comparison data, README markers, and demo wiring are in sync"); diff --git a/packages/npm-core-rs/scripts/benchmark-pandoc.mjs b/packages/npm-core-rs/scripts/benchmark-pandoc.mjs new file mode 100644 index 0000000..289c8dc --- /dev/null +++ b/packages/npm-core-rs/scripts/benchmark-pandoc.mjs @@ -0,0 +1,378 @@ +import assert from "node:assert/strict"; +import { execFile } from "node:child_process"; +import { mkdir, readFile, rm, writeFile } from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import { performance } from "node:perf_hooks"; +import { promisify } from "node:util"; + +import { unzipSync } from "fflate"; + +import { + comparisonDataPath, + decodeUtf8, + formatMs, + packageRoot, + readPackageVersion, + readmePath, + renderReadmeBlock, + replaceReadmeBlock, +} from "./_comparison-common.mjs"; + +const execFileAsync = promisify(execFile); +const tmpRoot = path.join(packageRoot, "tmp", "pandoc-benchmark"); +const docxlyOnceRunner = path.join(packageRoot, "scripts", "_benchmark_docxly_once.mjs"); +const corpusEntries = buildCorpora(); + +await rm(tmpRoot, { recursive: true, force: true }); +await mkdir(tmpRoot, { recursive: true }); + +try { + await execFileAsync("npm", ["run", "build"], { cwd: packageRoot }); + + const { stdout: pandocStdout } = await execFileAsync("pandoc", ["--version"], { cwd: packageRoot }); + const pandocVersion = pandocStdout.split("\n")[0].replace(/^pandoc\s+/u, "").trim(); + assert.ok(pandocVersion, "failed to determine pandoc version"); + + const { generateDocx } = await import("../dist/node.js"); + const docxlyVersion = await readPackageVersion(); + + const benchmarks = {}; + for (const corpus of corpusEntries) { + benchmarks[corpus.key] = await benchmarkCorpus(corpus, generateDocx); + } + + benchmarks.summary = summarizeBenchmarks(benchmarks); + + const comparisonData = { + headline: "Offline Node benchmark for library selection.", + measured_at: new Date().toISOString(), + machine_label: `${os.platform()} ${os.release()} / ${os.arch()}`, + node_version: process.version, + pandoc_version: pandocVersion, + docxly_version: docxlyVersion, + benchmarks, + feature_matrix: [ + { + feature: "Embeddable in app", + docxly: "Yes, library-first for Node and browser bundlers", + pandoc: "CLI-first with process invocation", + }, + { + feature: "Browser-local generation", + docxly: "First-party browser package and WASM path", + pandoc: "Possible through pandoc.wasm, not the primary npm workflow", + }, + { + feature: "npm distribution", + docxly: "Published package", + pandoc: "Not a first-party npm package", + }, + { + feature: "HWPX generation", + docxly: "Supported in the Rust core", + pandoc: "Not supported", + }, + { + feature: "Broad format conversion", + docxly: "Focused on DOCX and HWPX generation", + pandoc: "Wide multi-format conversion", + }, + { + feature: "DOCX reference-template workflow", + docxly: "Not a reference.docx workflow", + pandoc: "Supported via reference.docx", + }, + ], + caveats: [ + "This benchmark measures DOCX generation only and does not compare HWPX.", + "The numbers above come from an offline Node environment and are not browser runtime timings.", + "Cold timings include WASM initialization for docxly and process startup for Pandoc.", + "Steady timings are medians from 15 runs after one warm-up per corpus.", + ], + }; + + await writeFile(comparisonDataPath, `${JSON.stringify(comparisonData, null, 2)}\n`); + + const readme = await readFile(readmePath, "utf8"); + const nextReadme = replaceReadmeBlock(readme, renderReadmeBlock(comparisonData)); + await writeFile(readmePath, nextReadme); + + for (const [key, entry] of Object.entries(benchmarks)) { + console.log( + `${key}: docxly ${formatMs(entry.steady_median_ms.docxly)} vs Pandoc ${formatMs(entry.steady_median_ms.pandoc)} (${entry.speed_ratio.toFixed(2)}x)`, + ); + } +} catch (error) { + if (isMissingPandoc(error)) { + console.error("pandoc is required for benchmark:pandoc. Install it first and rerun the command."); + process.exit(1); + } + + throw error; +} finally { + await rm(tmpRoot, { recursive: true, force: true }); +} + +async function benchmarkCorpus(corpus, generateDocx) { + const inputPath = path.join(tmpRoot, `${corpus.key}.md`); + const docxlyOutputPath = path.join(tmpRoot, `${corpus.key}-docxly.docx`); + const pandocOutputPath = path.join(tmpRoot, `${corpus.key}-pandoc.docx`); + + await writeFile(inputPath, corpus.markdown); + + const docxlyCold = await measureDocxlyCold(inputPath, docxlyOutputPath); + const pandocCold = await measurePandoc(inputPath, pandocOutputPath); + + verifyDocx(await readFile(docxlyOutputPath), corpus.tokens); + verifyDocx(await readFile(pandocOutputPath), corpus.tokens); + + await generateDocx(corpus.markdown); + await measurePandoc(inputPath, pandocOutputPath); + + const docxlySteadyRuns = []; + const pandocSteadyRuns = []; + + for (let index = 0; index < 15; index += 1) { + docxlySteadyRuns.push(await measureDocxlySteady(generateDocx, corpus.markdown, docxlyOutputPath)); + pandocSteadyRuns.push(await measurePandoc(inputPath, pandocOutputPath)); + } + + const docxlyBytes = (await readFile(docxlyOutputPath)).length; + const pandocBytes = (await readFile(pandocOutputPath)).length; + const docxlySteadyMedian = median(docxlySteadyRuns); + const pandocSteadyMedian = median(pandocSteadyRuns); + + return { + label: corpus.label, + cold_ms: { + docxly: Math.round(docxlyCold), + pandoc: Math.round(pandocCold), + }, + steady_median_ms: { + docxly: Math.round(docxlySteadyMedian), + pandoc: Math.round(pandocSteadyMedian), + }, + output_bytes: { + docxly: docxlyBytes, + pandoc: pandocBytes, + }, + speed_ratio: roundRatio(pandocSteadyMedian / docxlySteadyMedian), + }; +} + +async function measureDocxlyCold(inputPath, outputPath) { + const startedAt = performance.now(); + await execFileAsync("node", [docxlyOnceRunner, inputPath, outputPath], { cwd: packageRoot }); + return performance.now() - startedAt; +} + +async function measureDocxlySteady(generateDocx, markdown, outputPath) { + const startedAt = performance.now(); + const bytes = await generateDocx(markdown); + await writeFile(outputPath, bytes); + return performance.now() - startedAt; +} + +async function measurePandoc(inputPath, outputPath) { + const startedAt = performance.now(); + await execFileAsync("pandoc", ["-f", "markdown", "-t", "docx", "-o", outputPath, inputPath], { + cwd: packageRoot, + }); + return performance.now() - startedAt; +} + +function verifyDocx(bytes, tokens) { + const archive = unzipSync(bytes); + assert.ok(archive["[Content_Types].xml"], "DOCX archive must include [Content_Types].xml"); + assert.ok(archive["_rels/.rels"], "DOCX archive must include _rels/.rels"); + assert.ok(archive["word/document.xml"], "DOCX archive must include word/document.xml"); + + const documentXml = decodeUtf8(archive["word/document.xml"]); + for (const token of tokens) { + assert.ok(documentXml.includes(token), `DOCX output is missing token: ${token}`); + } +} + +function summarizeBenchmarks(benchmarks) { + const corpusKeys = ["small", "medium", "large"]; + return { + label: "Summary", + cold_ms: { + docxly: Math.round(median(corpusKeys.map((key) => benchmarks[key].cold_ms.docxly))), + pandoc: Math.round(median(corpusKeys.map((key) => benchmarks[key].cold_ms.pandoc))), + }, + steady_median_ms: { + docxly: Math.round(median(corpusKeys.map((key) => benchmarks[key].steady_median_ms.docxly))), + pandoc: Math.round(median(corpusKeys.map((key) => benchmarks[key].steady_median_ms.pandoc))), + }, + output_bytes: { + docxly: Math.round(median(corpusKeys.map((key) => benchmarks[key].output_bytes.docxly))), + pandoc: Math.round(median(corpusKeys.map((key) => benchmarks[key].output_bytes.pandoc))), + }, + speed_ratio: roundRatio( + median(corpusKeys.map((key) => benchmarks[key].steady_median_ms.pandoc)) / + median(corpusKeys.map((key) => benchmarks[key].steady_median_ms.docxly)), + ), + }; +} + +function median(values) { + const sorted = [...values].sort((left, right) => left - right); + const middle = Math.floor(sorted.length / 2); + return sorted.length % 2 === 0 + ? (sorted[middle - 1] + sorted[middle]) / 2 + : sorted[middle]; +} + +function roundRatio(value) { + return Number(value.toFixed(2)); +} + +function isMissingPandoc(error) { + return error?.code === "ENOENT" || error?.message?.includes("pandoc"); +} + +function buildCorpora() { + const smallMarkdown = `# Small Benchmark Title + +Small benchmark paragraph with **bold**, *italic*, \`inline code\`, and [an example link](https://example.com). + +Small benchmark keeps a second paragraph to force additional text runs and paragraph nodes in the DOCX tree. + +> Small benchmark quote for DOCX output checks. + +1. First benchmark ordered item +2. Second benchmark ordered item + +- First benchmark bullet + - First benchmark nested bullet +- Second benchmark bullet + +\`\`\`text +small-benchmark-code +\`\`\` + +| Capability | Value | Notes | +| --- | --- | --- | +| Engine | docxly | Small corpus | +| Scope | Small | Baseline | +| Depth | 2 | Nested list enabled |`; + + const mediumSections = Array.from({ length: 12 }, (_, index) => + buildBenchmarkSection({ + number: index + 1, + size: "Medium", + repeatParagraphs: 3, + tableRows: 6, + orderedCount: 3, + unorderedCount: 3, + }), + ).join("\n\n"); + + const largeSections = Array.from({ length: 28 }, (_, index) => + buildBenchmarkSection({ + number: index + 1, + size: "Large", + repeatParagraphs: 5, + tableRows: 10, + orderedCount: 4, + unorderedCount: 4, + }), + ).join("\n\n"); + + return [ + { + key: "small", + label: "Small", + markdown: smallMarkdown, + tokens: [ + "Small Benchmark Title", + "Small benchmark paragraph", + "First benchmark ordered item", + "small-benchmark-code", + ], + }, + { + key: "medium", + label: "Medium", + markdown: `# Medium Benchmark Title\n\n${buildCorpusLead("Medium", 12)}\n\n${mediumSections}\n\n${buildCorpusTail("Medium", 12)}`, + tokens: [ + "Medium Benchmark Title", + "Medium Section 1", + "Medium nested bullet 3-2", + "Medium code block 12", + ], + }, + { + key: "large", + label: "Large", + markdown: `# Large Benchmark Title\n\n${buildCorpusLead("Large", 28)}\n\n${largeSections}\n\n${buildCorpusTail("Large", 28)}`, + tokens: [ + "Large Benchmark Title", + "Large Section 1", + "Large ordered item 5-1", + "Large nested bullet 28-3", + ], + }, + ]; +} + +function buildBenchmarkSection({ + number, + size, + repeatParagraphs, + tableRows: tableRowCount, + orderedCount, + unorderedCount, +}) { + const paragraphs = Array.from({ length: repeatParagraphs }, (_, index) => { + const paragraphNumber = index + 1; + return `${size} paragraph ${number}-${paragraphNumber} combines **bold**, *italic*, \`code-${number}-${paragraphNumber}\`, and [links](https://example.com/${size.toLowerCase()}/${number}/${paragraphNumber}) for DOCX run generation.`; + }).join("\n\n"); + + const orderedList = Array.from({ length: orderedCount }, (_, index) => { + const itemNumber = index + 1; + return `${itemNumber}. ${size} ordered item ${number}-${itemNumber}`; + }).join("\n"); + + const unorderedList = Array.from({ length: unorderedCount }, (_, index) => { + const itemNumber = index + 1; + return `- ${size} unordered item ${number}-${itemNumber}\n - ${size} nested bullet ${number}-${itemNumber}`; + }).join("\n"); + + const tableRows = Array.from({ length: tableRowCount }, (_, index) => { + const rowNumber = index + 1; + return `| ${number}.${rowNumber} | ${size} table value ${number}-${rowNumber} | ${size} table note ${number}-${rowNumber} |`; + }).join("\n"); + + return `## ${size} Section ${number} + +${paragraphs} + +> ${size} quote ${number} keeps blockquote output active for the benchmark corpus. + +${orderedList} + +${unorderedList} + +\`\`\`json +{"section": ${number}, "kind": "${size.toLowerCase()}", "marker": "${size} code block ${number}"} +\`\`\` + +| Metric | Value | Notes | +| --- | --- | --- | +${tableRows}`; +} + +function buildCorpusLead(size, sectionCount) { + return `${size} benchmark lead paragraph with **bold**, *italic*, \`lead-code\`, and [overview link](https://example.com/${size.toLowerCase()}/overview). + +${size} benchmark includes ${sectionCount} sections to exercise repeated block generation, list rendering, table packaging, and code block serialization.`; +} + +function buildCorpusTail(size, sectionCount) { + return `## ${size} Closing Notes + +This closing section confirms that the ${size.toLowerCase()} benchmark rendered ${sectionCount} content sections and preserves a final paragraph for end-of-document verification.`; +} diff --git a/packages/npm-core-rs/scripts/build-pages.mjs b/packages/npm-core-rs/scripts/build-pages.mjs index 9c6f15b..e9fb268 100644 --- a/packages/npm-core-rs/scripts/build-pages.mjs +++ b/packages/npm-core-rs/scripts/build-pages.mjs @@ -28,6 +28,9 @@ await cp(distRoot, path.join(siteRoot, "dist"), { recursive: true }); await cp(path.join(demoRoot, "index.html"), path.join(siteRoot, "index.html")); await cp(path.join(demoRoot, "main.js"), path.join(siteRoot, "main.js")); await cp(path.join(demoRoot, "styles.css"), path.join(siteRoot, "styles.css")); +await cp(path.join(demoRoot, "comparison-data.json"), path.join(siteRoot, "comparison-data.json")); +await cp(path.join(demoRoot, "assets"), path.join(siteRoot, "assets"), { recursive: true }); +await cp(path.join(demoRoot, "ko"), path.join(siteRoot, "ko"), { recursive: true }); const browserClient = await readFile(browserClientSource, "utf8"); const siteBrowserClient = browserClient.replaceAll("../dist/generated/", "./dist/generated/"); diff --git a/packages/npm-core-rs/scripts/demo-server.mjs b/packages/npm-core-rs/scripts/demo-server.mjs index 27f5e20..7072ff2 100644 --- a/packages/npm-core-rs/scripts/demo-server.mjs +++ b/packages/npm-core-rs/scripts/demo-server.mjs @@ -14,6 +14,7 @@ const contentTypes = new Map([ [".html", "text/html; charset=utf-8"], [".js", "text/javascript; charset=utf-8"], [".json", "application/json; charset=utf-8"], + [".png", "image/png"], [".wasm", "application/wasm"], ]); @@ -41,8 +42,20 @@ const server = http.createServer(async (req, res) => { const fileStat = await stat(filePath); if (fileStat.isDirectory()) { - res.writeHead(404); - res.end("Not Found"); + const indexPath = path.join(filePath, "index.html"); + if (!existsSync(indexPath)) { + res.writeHead(404); + res.end("Not Found"); + return; + } + + const indexStat = await stat(indexPath); + res.writeHead(200, { + "Content-Length": indexStat.size, + "Content-Type": "text/html; charset=utf-8", + "Cache-Control": "no-cache", + }); + createReadStream(indexPath).pipe(res); return; } diff --git a/packages/npm-core-rs/src/browser.js b/packages/npm-core-rs/src/browser.js index 7b02971..4a4fc23 100644 --- a/packages/npm-core-rs/src/browser.js +++ b/packages/npm-core-rs/src/browser.js @@ -1,4 +1,4 @@ -import init, { generateDocxBytes } from "./generated/core_rs.js"; +import init, { generateDocxBytes, generateHwpxBytes } from "./generated/core_rs.js"; import wasmUrl from "./generated/core_rs_bg.wasm"; let initPromise; @@ -20,3 +20,13 @@ export async function generateDocx(markdown, options = {}) { options.strictMode ?? true, ); } + +export async function generateHwpx(markdown, options = {}) { + await ensureInit(); + return generateHwpxBytes( + markdown, + options.title ?? null, + options.author ?? null, + options.strictMode ?? true, + ); +} diff --git a/packages/npm-core-rs/src/index.d.ts b/packages/npm-core-rs/src/index.d.ts index 2228ffc..04d519e 100644 --- a/packages/npm-core-rs/src/index.d.ts +++ b/packages/npm-core-rs/src/index.d.ts @@ -4,7 +4,18 @@ export interface DocxOptions { strictMode?: boolean; } +export interface HwpxOptions { + title?: string; + author?: string; + strictMode?: boolean; +} + export declare function generateDocx( markdown: string, options?: DocxOptions, ): Promise; + +export declare function generateHwpx( + markdown: string, + options?: HwpxOptions, +): Promise; diff --git a/packages/npm-core-rs/src/node.js b/packages/npm-core-rs/src/node.js index 5ec4e27..182cea6 100644 --- a/packages/npm-core-rs/src/node.js +++ b/packages/npm-core-rs/src/node.js @@ -1,6 +1,6 @@ import { readFile } from "node:fs/promises"; -import init, { generateDocxBytes } from "./generated/core_rs.js"; +import init, { generateDocxBytes, generateHwpxBytes } from "./generated/core_rs.js"; let initPromise; @@ -24,3 +24,13 @@ export async function generateDocx(markdown, options = {}) { options.strictMode ?? true, ); } + +export async function generateHwpx(markdown, options = {}) { + await ensureInit(); + return generateHwpxBytes( + markdown, + options.title ?? null, + options.author ?? null, + options.strictMode ?? true, + ); +} From 61661f9389f67efca3367295cc8d40ea91349c2e Mon Sep 17 00:00:00 2001 From: puleugo Date: Wed, 11 Mar 2026 02:26:15 +0900 Subject: [PATCH 4/4] feat(demo): add favicon asset for landing pages --- packages/npm-core-rs/demo/assets/favicon.png | Bin 0 -> 4025 bytes packages/npm-core-rs/demo/index.html | 1 + packages/npm-core-rs/demo/ko/index.html | 1 + 3 files changed, 2 insertions(+) create mode 100644 packages/npm-core-rs/demo/assets/favicon.png diff --git a/packages/npm-core-rs/demo/assets/favicon.png b/packages/npm-core-rs/demo/assets/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..19a4ecafb580f2ca90d81abb37dbe47cadc7c953 GIT binary patch literal 4025 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 literal 0 HcmV?d00001 diff --git a/packages/npm-core-rs/demo/index.html b/packages/npm-core-rs/demo/index.html index a93996d..7cfe47e 100644 --- a/packages/npm-core-rs/demo/index.html +++ b/packages/npm-core-rs/demo/index.html @@ -29,6 +29,7 @@ + diff --git a/packages/npm-core-rs/demo/ko/index.html b/packages/npm-core-rs/demo/ko/index.html index dc92f87..a7d36cc 100644 --- a/packages/npm-core-rs/demo/ko/index.html +++ b/packages/npm-core-rs/demo/ko/index.html @@ -29,6 +29,7 @@ +