From cf09d3500ef520336997089c357b089620ab1104 Mon Sep 17 00:00:00 2001 From: Nicolas Date: Sat, 22 Nov 2025 08:27:44 -0500 Subject: [PATCH 1/3] docs: add TurboDocx Packages & SDKs section (#154) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs: add TurboDocx Packages & SDKs section 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * docs: add badges and icons to Packages & SDKs table 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * docs: rename section to Explore the TurboDocx Ecosystem 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * chore: remove unmaintained CHANGELOG 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --------- Co-authored-by: Claude --- CHANGELOG.md | 181 --------------------------------------------------- README.md | 9 +++ 2 files changed, 9 insertions(+), 181 deletions(-) delete mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 4e4773d..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,181 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. - -### 1.1.2 (2020-05-29) - - -### Features - -* **packaging:** added jszip for packaging ([89619ec](https://github.com/privateOmega/html-to-docx/commit/89619ec702564fb9c5eccaee55e65d366fcbacad)) -* **packaging:** added method to create container ([9808cf2](https://github.com/privateOmega/html-to-docx/commit/9808cf211bbb50cf3d7cbe122d01c82d4272e888)) -* abstracted conversion using docxDocument class ([c625a01](https://github.com/privateOmega/html-to-docx/commit/c625a0181a6c328c0319b579fa1173192dff1187)) -* **template:** added base docx template ([abdb87b](https://github.com/privateOmega/html-to-docx/commit/abdb87bdfead91890f9d54e2cedd038e916b6dce)) -* added builder methods for images ([9e2720f](https://github.com/privateOmega/html-to-docx/commit/9e2720f261a46701c8a2581aadafa9b60e6cee6b)) -* added document file render helper ([6dd9c3a](https://github.com/privateOmega/html-to-docx/commit/6dd9c3a01f5fceab78404d8ebddb848fb91c933c)) -* added escape-html ([1a231d5](https://github.com/privateOmega/html-to-docx/commit/1a231d5dde3e6f9b5a23f248e19063191c07e54f)) -* added header generation ([25fb44f](https://github.com/privateOmega/html-to-docx/commit/25fb44f945df3fdc5f37d619b3de3ebe68b84cd6)) -* added hyperlinks support ([3560ce9](https://github.com/privateOmega/html-to-docx/commit/3560ce9f23fa8f590aa340302bf0059c8dfb6d5f)) -* added method to archive images with other files ([b6da74b](https://github.com/privateOmega/html-to-docx/commit/b6da74be10be03d689ca044f3f95dd724a3a29b6)) -* added more xml builder methods ([ffc584b](https://github.com/privateOmega/html-to-docx/commit/ffc584bed7ab434431999517a3308483ba99489a)) -* added more xml statment builder methods ([337e530](https://github.com/privateOmega/html-to-docx/commit/337e5305aa8768b6507323bec2279d557a35b67b)) -* added text formatting to paragraph ([bacd888](https://github.com/privateOmega/html-to-docx/commit/bacd888253a35a18ac7ea4e9141d4a4fb60e3cf7)) -* added vdom to xml method ([8b5a618](https://github.com/privateOmega/html-to-docx/commit/8b5a6185e6e211b0e07b9f1c1b7e23fb4b13dc9c)) -* added virtual-dom and html-to-vdom ([feaa396](https://github.com/privateOmega/html-to-docx/commit/feaa396162465276d19b7d3d5c51a533987a1738)) -* added xbuilder ([f13b5cc](https://github.com/privateOmega/html-to-docx/commit/f13b5cc06d29ae53493f1f4b8fdef6e8986e64e6)) -* added xml builder methods for images ([f413ad8](https://github.com/privateOmega/html-to-docx/commit/f413ad89b263c63a8fb9890b44b1b219a7413c4b)) -* added xml statement builder helper ([5e23c16](https://github.com/privateOmega/html-to-docx/commit/5e23c1636eb3c64f52589f1ac71a48dec3df65c2)) -* enabling header on flag ([516463c](https://github.com/privateOmega/html-to-docx/commit/516463cd532e58895faa8dd465b7e725f0de59e3)) -* handle line breaks ([164c0f5](https://github.com/privateOmega/html-to-docx/commit/164c0f5e17f62e3f30da25be6e181d3414ca4dde)) -* **template:** added numbering schema ([d179d73](https://github.com/privateOmega/html-to-docx/commit/d179d736e6e63ed42104a231ca0489430faae00a)) -* **template:** added styles schema ([d83d230](https://github.com/privateOmega/html-to-docx/commit/d83d230a66807f6ad08ebb4a6c0c5299c311aaf5)) -* **template:** added XML schemas ([42232da](https://github.com/privateOmega/html-to-docx/commit/42232da9d63ed404367703e56b1c65cdb8a23782)) - - -### Bug Fixes - -* added attributes to anchor drawing ([62e4a29](https://github.com/privateOmega/html-to-docx/commit/62e4a29ef664257d8f0364d5d97f056a62f0fb61)) -* added default options ([4590800](https://github.com/privateOmega/html-to-docx/commit/459080010f92ce7464f4815585088a46ce8e759d)) -* added effectextent and srcrect fragment ([5f5e975](https://github.com/privateOmega/html-to-docx/commit/5f5e975b135eb38c48e18a09da590b363166d74e)) -* added extent fragment ([7ce81f2](https://github.com/privateOmega/html-to-docx/commit/7ce81f27e4c493bb9bf7d368a415f34cb0678e4c)) -* added header override in content-types xml ([5de681b](https://github.com/privateOmega/html-to-docx/commit/5de681be9295754eff648cea504e07bf9a6f6d09)) -* added image conversion handler ([f726e71](https://github.com/privateOmega/html-to-docx/commit/f726e71ee2504bc254794ad09eaf5d67a8901b9a)) -* added inline attributes ([0a4d2ce](https://github.com/privateOmega/html-to-docx/commit/0a4d2ce4b4c64952c3866928e6355b7c891ac044)) -* added italics, underline and bold in runproperties ([34c2e18](https://github.com/privateOmega/html-to-docx/commit/34c2e18123c8a6a956209951afebc0dce2ab6cfc)) -* added more namespaces ([68636b4](https://github.com/privateOmega/html-to-docx/commit/68636b4c7cc73bf9e0de75b7bf97ac9afb4fb6f9)) -* added namespace aliases to header and numbering xmls ([d0b4101](https://github.com/privateOmega/html-to-docx/commit/d0b4101017a6dabd0fa18e23228bd4af338129eb)) -* added numbering and styles relationship ([c7e29af](https://github.com/privateOmega/html-to-docx/commit/c7e29af7414ce71515c46861942342d4f397222b)) -* added other namespaces to the xml root ([afbbca9](https://github.com/privateOmega/html-to-docx/commit/afbbca9dbf723afc857034ce7770bc8f0840c0e4)) -* added override for relationship ([30acddc](https://github.com/privateOmega/html-to-docx/commit/30acddc84d40dc6c66ed9539618b94adeeb2fc85)) -* added override for settings and websettings ([977af04](https://github.com/privateOmega/html-to-docx/commit/977af04f48c19f2b3162cf6e61782cf63e7162e8)) -* added overrides for relationships ([22b9cac](https://github.com/privateOmega/html-to-docx/commit/22b9cac2fa788b9654262e450774c588180a18de)) -* added padding between image and wrapping text ([e45fbf5](https://github.com/privateOmega/html-to-docx/commit/e45fbf553c19071023634b692e3c4b0fab04aedf)) -* added positioning fragments ([e6f7e1c](https://github.com/privateOmega/html-to-docx/commit/e6f7e1c3679aa813a2818725548dfb5ebb0d9bd7)) -* added required attributes to anchor fragment ([d01c9f9](https://github.com/privateOmega/html-to-docx/commit/d01c9f915a929de201218af127103da627aaa4a1)) -* added settings and websettings relation ([34aeedc](https://github.com/privateOmega/html-to-docx/commit/34aeedce6d0dd02822062762f9b077bb146b09b9)) -* added settings and websettings to ooxml package ([6c829b5](https://github.com/privateOmega/html-to-docx/commit/6c829b5ec4596ba0b5d41fae9ba2bfd68fdf7230)) -* added simple positioning to anchor ([5006cc4](https://github.com/privateOmega/html-to-docx/commit/5006cc47d112360e51d8051f1ebff570e9f12779)) -* added table borders ([12864db](https://github.com/privateOmega/html-to-docx/commit/12864db468a08f4aca4d01cb8e8b6635aa09c57d)) -* added wrap elements ([c951688](https://github.com/privateOmega/html-to-docx/commit/c95168864c4929e2ab95c5a6a53d0919c76f8a83)) -* changed attribute field for picture name ([aef241d](https://github.com/privateOmega/html-to-docx/commit/aef241dc3d3d9adb732c429df9f0c2771b319680)) -* changed attribute used for name ([3885233](https://github.com/privateOmega/html-to-docx/commit/3885233bf14f9b7b16d48a2844d3e997e476a8ee)) -* changed default namespace of relationship to solve render issue ([56a3554](https://github.com/privateOmega/html-to-docx/commit/56a3554e7b2e9d85cedeece8d20acfebf23666ad)) -* changed file extension if octet stream is encountered ([32c5bf1](https://github.com/privateOmega/html-to-docx/commit/32c5bf1b5f7c5f8dc83a51fed142e932c7b008fd)) -* changed namespaces to original ecma 376 spec ([51be86e](https://github.com/privateOmega/html-to-docx/commit/51be86ecf0f4a78457840bf2a31579d217568208)) -* fix table render issue due to grid width ([636d499](https://github.com/privateOmega/html-to-docx/commit/636d499bcee00195f7b5ca198c60bb3e0f7d2a69)) -* fixed abstract numbering id ([9814cb8](https://github.com/privateOmega/html-to-docx/commit/9814cb89582bc7e87cec638be37ee1cd326c6117)) -* fixed coloring and refactored other text formatting ([c288f80](https://github.com/privateOmega/html-to-docx/commit/c288f809ea6387c91356976a6dd81396cecafc46)) -* fixed document rels and numbering bug ([d6e3152](https://github.com/privateOmega/html-to-docx/commit/d6e3152081da7d2ab379a67bfda345964fa15c40)) -* fixed docx generation ([3d96acf](https://github.com/privateOmega/html-to-docx/commit/3d96acf511d82776510fac857af57d5cb9453f89)) -* fixed incorrect table row generation ([742dd18](https://github.com/privateOmega/html-to-docx/commit/742dd1882ce4c1a33ab51e10ee2a628b817eca31)) -* fixed internal mode and added extensions ([1266121](https://github.com/privateOmega/html-to-docx/commit/12661213e00c55f7068e93abb019ba80cd4f2d87)) -* fixed margin issues ([f841b76](https://github.com/privateOmega/html-to-docx/commit/f841b76caa944ea5eec206a3b3fce3e5a5eaf3e7)) -* fixed numbering and header issue due to wrong filename ([64a04bc](https://github.com/privateOmega/html-to-docx/commit/64a04bc192616162aa67c43f80734e7ebb9ff588)) -* fixed table and image rendering ([c153092](https://github.com/privateOmega/html-to-docx/commit/c1530924f93351ce63882bf0e6050b6315aa6017)) -* handled figure wrapper for images and tables ([4182a95](https://github.com/privateOmega/html-to-docx/commit/4182a9543aeb71fd8b0d2c7a2e08978a782de3e6)) -* handled table width ([237ddfd](https://github.com/privateOmega/html-to-docx/commit/237ddfd6bff914e0379c6cbd940a7eac29d7aeaf)) -* handling multiple span children and multilevel formatting of text ([4c81f58](https://github.com/privateOmega/html-to-docx/commit/4c81f586400d1f227236a8b07d067331c0f02c5d)) -* modified example to use esm bundle ([491a83d](https://github.com/privateOmega/html-to-docx/commit/491a83d9b2c0deec13743817cdf32280d39bb9cd)) -* moved namespaces into separate file ([75cdf30](https://github.com/privateOmega/html-to-docx/commit/75cdf3033e69934b189a74d6c77eef08d50492aa)) -* namespace updated to 2016 standards ([6fc2ac2](https://github.com/privateOmega/html-to-docx/commit/6fc2ac2b6e904c4dd774b24e0ad119cccd873e0b)) -* **template:** fixed document templating ([5f6a74f](https://github.com/privateOmega/html-to-docx/commit/5f6a74f9964348590fbb7f5baf88230c8c796766)) -* **template:** fixed numbering templating ([8b09691](https://github.com/privateOmega/html-to-docx/commit/8b096916284cbbe8452bb572d788caee23849084)) -* **template:** removed word xml schema ([ee0e1ed](https://github.com/privateOmega/html-to-docx/commit/ee0e1ed7b0b00cbaf3644ad887175abac0282dcc)) -* removed unwanted attribute ([f3caf44](https://github.com/privateOmega/html-to-docx/commit/f3caf44faf95ba8c6dee1f6f959300374e2b65ff)) -* renamed document rels schema file ([10c3fda](https://github.com/privateOmega/html-to-docx/commit/10c3fda9878847257b902d4c13c2d8dd36edd3f6)) -* updated document abstraction to track generation ids ([c34810f](https://github.com/privateOmega/html-to-docx/commit/c34810f1373f934b0b3ecbe9da2838f41a68dcc9)) -* updated documentrels xml generation ([433e4b4](https://github.com/privateOmega/html-to-docx/commit/433e4b4eb9d71beede8feb1754363163ba5d1933)) -* updated numbering xml generation ([81b7a82](https://github.com/privateOmega/html-to-docx/commit/81b7a8296d1e3afa095f47007a66698852d29f95)) -* updated xml builder to use namespace and child nodes ([2e28b5e](https://github.com/privateOmega/html-to-docx/commit/2e28b5ec07241c10c4288412a6ced8023e8c03ce)) -* wrapped drawing inside paragraph tag ([d0476b4](https://github.com/privateOmega/html-to-docx/commit/d0476b4211fe13f5918091a6a06e5021015a5db8)) - -### [1.1.1](https://github.com/privateOmega/html-to-docx/compare/v1.1.0...v1.1.1) (2020-05-28) - - -### Bug Fixes - -* modified example to use esm bundle ([dcd7f4b](https://github.com/privateOmega/html-to-docx/commit/dcd7f4b7705b806697dfe92f060641030ee42cfa)) - -## 1.1.0 (2020-05-28) - - -### Features - -* **packaging:** added jszip for packaging ([89619ec](https://github.com/privateOmega/html-to-docx/commit/89619ec702564fb9c5eccaee55e65d366fcbacad)) -* **packaging:** added method to create container ([9808cf2](https://github.com/privateOmega/html-to-docx/commit/9808cf211bbb50cf3d7cbe122d01c82d4272e888)) -* **template:** added base docx template ([abdb87b](https://github.com/privateOmega/html-to-docx/commit/abdb87bdfead91890f9d54e2cedd038e916b6dce)) -* **template:** added numbering schema ([d179d73](https://github.com/privateOmega/html-to-docx/commit/d179d736e6e63ed42104a231ca0489430faae00a)) -* **template:** added styles schema ([d83d230](https://github.com/privateOmega/html-to-docx/commit/d83d230a66807f6ad08ebb4a6c0c5299c311aaf5)) -* abstracted conversion using docxDocument class ([c625a01](https://github.com/privateOmega/html-to-docx/commit/c625a0181a6c328c0319b579fa1173192dff1187)) -* added builder methods for images ([9e2720f](https://github.com/privateOmega/html-to-docx/commit/9e2720f261a46701c8a2581aadafa9b60e6cee6b)) -* added document file render helper ([6dd9c3a](https://github.com/privateOmega/html-to-docx/commit/6dd9c3a01f5fceab78404d8ebddb848fb91c933c)) -* added escape-html ([1a231d5](https://github.com/privateOmega/html-to-docx/commit/1a231d5dde3e6f9b5a23f248e19063191c07e54f)) -* added header generation ([25fb44f](https://github.com/privateOmega/html-to-docx/commit/25fb44f945df3fdc5f37d619b3de3ebe68b84cd6)) -* added hyperlinks support ([3560ce9](https://github.com/privateOmega/html-to-docx/commit/3560ce9f23fa8f590aa340302bf0059c8dfb6d5f)) -* added method to archive images with other files ([b6da74b](https://github.com/privateOmega/html-to-docx/commit/b6da74be10be03d689ca044f3f95dd724a3a29b6)) -* added more xml builder methods ([ffc584b](https://github.com/privateOmega/html-to-docx/commit/ffc584bed7ab434431999517a3308483ba99489a)) -* added more xml statment builder methods ([337e530](https://github.com/privateOmega/html-to-docx/commit/337e5305aa8768b6507323bec2279d557a35b67b)) -* added text formatting to paragraph ([bacd888](https://github.com/privateOmega/html-to-docx/commit/bacd888253a35a18ac7ea4e9141d4a4fb60e3cf7)) -* added vdom to xml method ([8b5a618](https://github.com/privateOmega/html-to-docx/commit/8b5a6185e6e211b0e07b9f1c1b7e23fb4b13dc9c)) -* added virtual-dom and html-to-vdom ([feaa396](https://github.com/privateOmega/html-to-docx/commit/feaa396162465276d19b7d3d5c51a533987a1738)) -* added xbuilder ([f13b5cc](https://github.com/privateOmega/html-to-docx/commit/f13b5cc06d29ae53493f1f4b8fdef6e8986e64e6)) -* added xml builder methods for images ([f413ad8](https://github.com/privateOmega/html-to-docx/commit/f413ad89b263c63a8fb9890b44b1b219a7413c4b)) -* added xml statement builder helper ([5e23c16](https://github.com/privateOmega/html-to-docx/commit/5e23c1636eb3c64f52589f1ac71a48dec3df65c2)) -* handle line breaks ([164c0f5](https://github.com/privateOmega/html-to-docx/commit/164c0f5e17f62e3f30da25be6e181d3414ca4dde)) -* **template:** added XML schemas ([42232da](https://github.com/privateOmega/html-to-docx/commit/42232da9d63ed404367703e56b1c65cdb8a23782)) - - -### Bug Fixes - -* added attributes to anchor drawing ([62e4a29](https://github.com/privateOmega/html-to-docx/commit/62e4a29ef664257d8f0364d5d97f056a62f0fb61)) -* added effectextent and srcrect fragment ([5f5e975](https://github.com/privateOmega/html-to-docx/commit/5f5e975b135eb38c48e18a09da590b363166d74e)) -* added extent fragment ([7ce81f2](https://github.com/privateOmega/html-to-docx/commit/7ce81f27e4c493bb9bf7d368a415f34cb0678e4c)) -* added header override in content-types xml ([5de681b](https://github.com/privateOmega/html-to-docx/commit/5de681be9295754eff648cea504e07bf9a6f6d09)) -* added image conversion handler ([f726e71](https://github.com/privateOmega/html-to-docx/commit/f726e71ee2504bc254794ad09eaf5d67a8901b9a)) -* added inline attributes ([0a4d2ce](https://github.com/privateOmega/html-to-docx/commit/0a4d2ce4b4c64952c3866928e6355b7c891ac044)) -* added italics, underline and bold in runproperties ([34c2e18](https://github.com/privateOmega/html-to-docx/commit/34c2e18123c8a6a956209951afebc0dce2ab6cfc)) -* added more namespaces ([68636b4](https://github.com/privateOmega/html-to-docx/commit/68636b4c7cc73bf9e0de75b7bf97ac9afb4fb6f9)) -* added namespace aliases to header and numbering xmls ([d0b4101](https://github.com/privateOmega/html-to-docx/commit/d0b4101017a6dabd0fa18e23228bd4af338129eb)) -* added numbering and styles relationship ([c7e29af](https://github.com/privateOmega/html-to-docx/commit/c7e29af7414ce71515c46861942342d4f397222b)) -* added other namespaces to the xml root ([afbbca9](https://github.com/privateOmega/html-to-docx/commit/afbbca9dbf723afc857034ce7770bc8f0840c0e4)) -* added override for relationship ([30acddc](https://github.com/privateOmega/html-to-docx/commit/30acddc84d40dc6c66ed9539618b94adeeb2fc85)) -* added override for settings and websettings ([977af04](https://github.com/privateOmega/html-to-docx/commit/977af04f48c19f2b3162cf6e61782cf63e7162e8)) -* added overrides for relationships ([22b9cac](https://github.com/privateOmega/html-to-docx/commit/22b9cac2fa788b9654262e450774c588180a18de)) -* added padding between image and wrapping text ([e45fbf5](https://github.com/privateOmega/html-to-docx/commit/e45fbf553c19071023634b692e3c4b0fab04aedf)) -* added positioning fragments ([e6f7e1c](https://github.com/privateOmega/html-to-docx/commit/e6f7e1c3679aa813a2818725548dfb5ebb0d9bd7)) -* added required attributes to anchor fragment ([d01c9f9](https://github.com/privateOmega/html-to-docx/commit/d01c9f915a929de201218af127103da627aaa4a1)) -* added settings and websettings relation ([34aeedc](https://github.com/privateOmega/html-to-docx/commit/34aeedce6d0dd02822062762f9b077bb146b09b9)) -* added settings and websettings to ooxml package ([6c829b5](https://github.com/privateOmega/html-to-docx/commit/6c829b5ec4596ba0b5d41fae9ba2bfd68fdf7230)) -* added simple positioning to anchor ([5006cc4](https://github.com/privateOmega/html-to-docx/commit/5006cc47d112360e51d8051f1ebff570e9f12779)) -* added table borders ([12864db](https://github.com/privateOmega/html-to-docx/commit/12864db468a08f4aca4d01cb8e8b6635aa09c57d)) -* added wrap elements ([c951688](https://github.com/privateOmega/html-to-docx/commit/c95168864c4929e2ab95c5a6a53d0919c76f8a83)) -* changed attribute field for picture name ([aef241d](https://github.com/privateOmega/html-to-docx/commit/aef241dc3d3d9adb732c429df9f0c2771b319680)) -* changed attribute used for name ([3885233](https://github.com/privateOmega/html-to-docx/commit/3885233bf14f9b7b16d48a2844d3e997e476a8ee)) -* changed default namespace of relationship to solve render issue ([56a3554](https://github.com/privateOmega/html-to-docx/commit/56a3554e7b2e9d85cedeece8d20acfebf23666ad)) -* changed file extension if octet stream is encountered ([32c5bf1](https://github.com/privateOmega/html-to-docx/commit/32c5bf1b5f7c5f8dc83a51fed142e932c7b008fd)) -* changed namespaces to original ecma 376 spec ([51be86e](https://github.com/privateOmega/html-to-docx/commit/51be86ecf0f4a78457840bf2a31579d217568208)) -* fix table render issue due to grid width ([636d499](https://github.com/privateOmega/html-to-docx/commit/636d499bcee00195f7b5ca198c60bb3e0f7d2a69)) -* fixed abstract numbering id ([9814cb8](https://github.com/privateOmega/html-to-docx/commit/9814cb89582bc7e87cec638be37ee1cd326c6117)) -* fixed coloring and refactored other text formatting ([c288f80](https://github.com/privateOmega/html-to-docx/commit/c288f809ea6387c91356976a6dd81396cecafc46)) -* fixed document rels and numbering bug ([d6e3152](https://github.com/privateOmega/html-to-docx/commit/d6e3152081da7d2ab379a67bfda345964fa15c40)) -* fixed docx generation ([3d96acf](https://github.com/privateOmega/html-to-docx/commit/3d96acf511d82776510fac857af57d5cb9453f89)) -* fixed incorrect table row generation ([742dd18](https://github.com/privateOmega/html-to-docx/commit/742dd1882ce4c1a33ab51e10ee2a628b817eca31)) -* fixed internal mode and added extensions ([1266121](https://github.com/privateOmega/html-to-docx/commit/12661213e00c55f7068e93abb019ba80cd4f2d87)) -* fixed margin issues ([f841b76](https://github.com/privateOmega/html-to-docx/commit/f841b76caa944ea5eec206a3b3fce3e5a5eaf3e7)) -* fixed numbering and header issue due to wrong filename ([64a04bc](https://github.com/privateOmega/html-to-docx/commit/64a04bc192616162aa67c43f80734e7ebb9ff588)) -* fixed table and image rendering ([c153092](https://github.com/privateOmega/html-to-docx/commit/c1530924f93351ce63882bf0e6050b6315aa6017)) -* handled figure wrapper for images and tables ([4182a95](https://github.com/privateOmega/html-to-docx/commit/4182a9543aeb71fd8b0d2c7a2e08978a782de3e6)) -* handled table width ([237ddfd](https://github.com/privateOmega/html-to-docx/commit/237ddfd6bff914e0379c6cbd940a7eac29d7aeaf)) -* handling multiple span children and multilevel formatting of text ([4c81f58](https://github.com/privateOmega/html-to-docx/commit/4c81f586400d1f227236a8b07d067331c0f02c5d)) -* moved namespaces into separate file ([75cdf30](https://github.com/privateOmega/html-to-docx/commit/75cdf3033e69934b189a74d6c77eef08d50492aa)) -* namespace updated to 2016 standards ([6fc2ac2](https://github.com/privateOmega/html-to-docx/commit/6fc2ac2b6e904c4dd774b24e0ad119cccd873e0b)) -* removed unwanted attribute ([f3caf44](https://github.com/privateOmega/html-to-docx/commit/f3caf44faf95ba8c6dee1f6f959300374e2b65ff)) -* renamed document rels schema file ([10c3fda](https://github.com/privateOmega/html-to-docx/commit/10c3fda9878847257b902d4c13c2d8dd36edd3f6)) -* updated document abstraction to track generation ids ([c34810f](https://github.com/privateOmega/html-to-docx/commit/c34810f1373f934b0b3ecbe9da2838f41a68dcc9)) -* **template:** fixed document templating ([5f6a74f](https://github.com/privateOmega/html-to-docx/commit/5f6a74f9964348590fbb7f5baf88230c8c796766)) -* **template:** fixed numbering templating ([8b09691](https://github.com/privateOmega/html-to-docx/commit/8b096916284cbbe8452bb572d788caee23849084)) -* updated documentrels xml generation ([433e4b4](https://github.com/privateOmega/html-to-docx/commit/433e4b4eb9d71beede8feb1754363163ba5d1933)) -* updated numbering xml generation ([81b7a82](https://github.com/privateOmega/html-to-docx/commit/81b7a8296d1e3afa095f47007a66698852d29f95)) -* updated xml builder to use namespace and child nodes ([2e28b5e](https://github.com/privateOmega/html-to-docx/commit/2e28b5ec07241c10c4288412a6ced8023e8c03ce)) -* wrapped drawing inside paragraph tag ([d0476b4](https://github.com/privateOmega/html-to-docx/commit/d0476b4211fe13f5918091a6a06e5021015a5db8)) -* **template:** removed word xml schema ([ee0e1ed](https://github.com/privateOmega/html-to-docx/commit/ee0e1ed7b0b00cbaf3644ad887175abac0282dcc)) diff --git a/README.md b/README.md index 0013f2c..61907a6 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,15 @@ Convert HTML to Word, Google Docs, and DOCX files with the fastest, most reliabl Based on the original work and assisted by the original contributors of [privateOmega/html-to-docx](https://github.com/privateOmega/html-to-docx), this library is now actively maintained and enhanced by TurboDocx, ensuring continuous improvements and long-term support for production environments. +## 🌐 Explore the TurboDocx Ecosystem + +| Package | Links | Description | +|---------|-------|-------------| +| n8n-nodes-turbodocx | [![npm](https://img.shields.io/npm/v/@turbodocx/n8n-nodes-turbodocx?logo=npm&logoColor=white&label=npm)](https://www.npmjs.com/package/@turbodocx/n8n-nodes-turbodocx) [![GitHub](https://img.shields.io/github/stars/turbodocx/n8n-nodes-turbodocx?style=social)](https://github.com/turbodocx/n8n-nodes-turbodocx) | n8n community node for TurboDocx API & TurboSign | + + + + ## Why @turbodocx/html-to-docx? 🚀 **Lightning Fast Performance** - Pure JavaScript implementation with no dependencies on headless browsers or external binaries. Perfect for AI applications that need rapid document generation. From fc33a25177d4693700e3d6defdbfa0aa2b009e33 Mon Sep 17 00:00:00 2001 From: Jared Casner Date: Thu, 11 Dec 2025 16:50:47 -0800 Subject: [PATCH 2/3] feat: add support for tags (#158) --- example/comprehensive-svg-test.js | 84 ++++++++++ example/example-inline-svg.js | 93 +++++++++++ example/example-node.js | 46 +++--- example/test-inline-svg-from-html.js | 35 +++++ src/helpers/render-document-file.js | 157 +++---------------- src/helpers/xml-builder.js | 6 +- src/utils/image.js | 144 ++++++++++++++++- src/utils/svg.js | 180 ++++++++++++++++++++++ tests/inline-image-caching.test.js | 11 +- tests/inline-svg-element.test.js | 221 +++++++++++++++++++++++++++ 10 files changed, 803 insertions(+), 174 deletions(-) create mode 100644 example/comprehensive-svg-test.js create mode 100644 example/example-inline-svg.js create mode 100644 example/test-inline-svg-from-html.js create mode 100644 src/utils/svg.js create mode 100644 tests/inline-svg-element.test.js diff --git a/example/comprehensive-svg-test.js b/example/comprehensive-svg-test.js new file mode 100644 index 0000000..a037a48 --- /dev/null +++ b/example/comprehensive-svg-test.js @@ -0,0 +1,84 @@ +/* eslint-disable no-console */ +const fs = require('fs'); +const HTMLtoDOCX = require('../dist/html-to-docx.umd'); + +async function testVariousSVGs() { + console.log('🔬 Testing various SVG scenarios with verbose logging...\n'); + + // Test 1: Simple SVG with xmlns + const test1 = ` +

Test 1: Simple SVG with xmlns

+ + + + `; + + // Test 2: Simple SVG without xmlns (should be added automatically) + const test2 = ` +

Test 2: Simple SVG without xmlns

+ + + + `; + + // Test 3: Complex nested SVG + const test3 = ` +

Test 3: Complex nested SVG

+ + + + + + + + + + `; + + // Test 4: SVG with text elements + const test4 = ` +

Test 4: SVG with text

+ + Hello SVG + + `; + + // Test 5: Radar chart style SVG (like from test.html) + const test5 = ` +

Test 5: Radar chart style SVG

+ + + + + + + + + `; + + const allTests = test1 + test2 + test3 + test4 + test5; + + try { + console.log('📄 Generating comprehensive test document with verbose logging...\n'); + const docx = await HTMLtoDOCX(allTests, null, { + orientation: 'portrait', + title: 'SVG Test Suite', + imageProcessing: { + svgHandling: 'native', + verboseLogging: true, + }, + }); + + fs.writeFileSync('./comprehensive-svg-test.docx', docx); + console.log('\n✅ Created: comprehensive-svg-test.docx'); + console.log(' → Document with 5 different SVG test cases\n'); + + console.log('✨ Success! Open the file to verify all SVGs render correctly.'); + } catch (error) { + console.error('❌ Error:', error.message); + console.error(error.stack); + process.exit(1); + } +} + +testVariousSVGs(); diff --git a/example/example-inline-svg.js b/example/example-inline-svg.js new file mode 100644 index 0000000..0761515 --- /dev/null +++ b/example/example-inline-svg.js @@ -0,0 +1,93 @@ +const fs = require('fs'); +// Use the built version, or install via: npm install @turbodocx/html-to-docx +// const HTMLtoDOCX = require('@turbodocx/html-to-docx'); +const HTMLtoDOCX = require('../dist/html-to-docx.umd'); + +// Simple HTML with inline SVG element +const htmlContent = ` + + + + + Inline SVG Test + + +

Testing Inline SVG Elements

+ +

This document tests inline SVG elements (not img tags with SVG sources).

+ +

1. Simple Inline SVG Circle

+

A basic SVG shape:

+ + + + +

2. Inline SVG with Multiple Elements

+

A more complex SVG:

+ + + + + + +

3. SVG from test.html

+

Testing with a radar chart SVG:

+ + + + + + + +

End of inline SVG test document.

+ + +`; + +async function generateDocument() { + console.log('🚀 Generating inline SVG example document...\n'); + + try { + // Test with SVG→PNG conversion (default, requires sharp) + console.log('📄 Generating document with SVG→PNG conversion...'); + const docx = await HTMLtoDOCX(htmlContent, null, { + orientation: 'portrait', + title: 'Inline SVG Test', + creator: '@turbodocx/html-to-docx', + imageProcessing: { + svgHandling: 'convert', // Convert SVG to PNG + verboseLogging: true, + }, + }); + + fs.writeFileSync('./example-inline-svg-convert.docx', docx); + console.log('✅ Created: example-inline-svg-convert.docx'); + console.log(' → Inline SVGs converted to PNG\n'); + + // Test with native SVG support + console.log('📄 Generating document with native SVG support...'); + const docxNative = await HTMLtoDOCX(htmlContent, null, { + orientation: 'portrait', + title: 'Inline SVG Test - Native', + creator: '@turbodocx/html-to-docx', + imageProcessing: { + svgHandling: 'native', // Use native SVG + verboseLogging: true, + }, + }); + + fs.writeFileSync('./example-inline-svg-native.docx', docxNative); + console.log('✅ Created: example-inline-svg-native.docx'); + console.log(' → Inline SVGs embedded as native SVG\n'); + + console.log('✨ Success! Open the generated .docx files to view inline SVGs.'); + console.log('\nNote:'); + console.log('- PNG conversion version works in all Word versions'); + console.log('- Native SVG version requires Office 2019+ or Microsoft 365'); + } catch (error) { + console.error('❌ Error generating document:', error); + process.exit(1); + } +} + +generateDocument(); diff --git a/example/example-node.js b/example/example-node.js index 6488115..c2b9c9c 100644 --- a/example/example-node.js +++ b/example/example-node.js @@ -35,14 +35,14 @@ const htmlString = ` src="data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==" alt="Test image with color and font-family styles" /> - +

Testing images with mixed dimensional and non-dimensional styles:

Test image with mixed styles - +

Testing images with various non-dimensional styles:

  • Heading 3
  • - +

    One More test case

    PHASE ONE
    CONFIDENTIAL
    HOURS: 1 – 2

    @@ -1841,7 +1841,7 @@ const htmlString = `

    - +

    Testing aspect ratio safety with zero/invalid dimensions:

    @@ -1855,7 +1855,7 @@ const htmlString = ` src="data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==" alt="Test image with zero width" /> - +

    Testing extreme aspect ratios:

    src="data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==" alt="Very tall image" /> - +

    Testing auto dimensions with CSS styles:

    Auto dimensions with non-dimensional styles - +

    Testing images in figures (lineRule attribute fix):

    />
    Image with consistent lineRule processing
    - +

    Testing images with max-width/max-height constraints:

    Image with max constraints - +

    Testing images exceeding maximum document width (auto-scaling):

    - +
    @@ -1936,7 +1936,7 @@ const htmlString = `

    Testing Image Width/Height Attribute Handling (Fix for TinyMCE dimensions)

    These test cases verify that width and height HTML attributes are properly honored in DOCX generation:

    Test image original size: 5,807 × 2,817 pixels (8.35 MB JPEG)

    - +

    Test 1: Image with explicit width and height attributes (should render as 100x100):

    height="100" alt="100x100 dimensions test" /> - +

    Test 2: Image with only width attribute (height should maintain aspect ratio):

    Width only test - +

    Test 3: Image with only height attribute (width should maintain aspect ratio):

    Height only test - +

    Test 4: Image with width/height and additional styles (TinyMCE scenario):

    height="60" alt="TinyMCE style with dimensions" /> - +

    Test 5: Image without dimensions (should use original image size - fallback behavior):

    No dimensions - fallback test - +

    Test 6: Larger image with custom dimensions (demonstrates actual resize):

    height="100" alt="Large image resized to 200x100" /> - +

    Unit Support Tests

    Test 7: Image with pixel units (explicit px):

    height="90px" alt="180px x 90px image" /> - +

    Test 8: Image with point units (pt):

    height="72pt" alt="144pt x 72pt image (192px x 96px equivalent)" /> - +

    Test 9: Image with centimeter units (cm):

    height="2cm" alt="4cm x 2cm image" /> - +

    Test 10: Image with inch units (in):

    height="0.75in" alt="1.5in x 0.75in image" /> - +

    Test 11: Image with percentage units (% of original size):

    height="10%" alt="10% x 10% of original size" /> - +

    Test 12: Mixed units - width in cm, height in inches:

    imageProcessing: { // By default, shows a warning when sharp is not installed // Uncomment to suppress the warning (useful for intentional native SVG mode): - // suppressSharpWarning: true, + // suppressSharpWarning: true, }, // =================================================================== // WARNING: deterministicIds is ONLY for CI/CD testing purposes. diff --git a/example/test-inline-svg-from-html.js b/example/test-inline-svg-from-html.js new file mode 100644 index 0000000..b4e9557 --- /dev/null +++ b/example/test-inline-svg-from-html.js @@ -0,0 +1,35 @@ +/* eslint-disable no-console */ +const fs = require('fs'); +const HTMLtoDOCX = require('../dist/html-to-docx.umd'); + +async function testTestHTML() { + console.log('🚀 Testing with test.html file...\n'); + + try { + // Read the test.html file + const htmlContent = fs.readFileSync('../test.html', 'utf-8'); + + console.log('📄 Generating document from test.html...'); + const docx = await HTMLtoDOCX(htmlContent, null, { + orientation: 'portrait', + title: 'Risk Assessment Test', + creator: '@turbodocx/html-to-docx', + imageProcessing: { + svgHandling: 'native', // Use native SVG since Sharp isn't installed + verboseLogging: true, + }, + }); + + fs.writeFileSync('./test-html-output.docx', docx); + console.log('✅ Created: test-html-output.docx'); + console.log(' → Document generated from test.html with inline SVG support\n'); + + console.log('✨ Success! Open test-html-output.docx to view the result.'); + } catch (error) { + console.error('❌ Error:', error.message); + console.error(error.stack); + process.exit(1); + } +} + +testTestHTML(); diff --git a/src/helpers/render-document-file.js b/src/helpers/render-document-file.js index f2d279e..ba12771 100644 --- a/src/helpers/render-document-file.js +++ b/src/helpers/render-document-file.js @@ -1,21 +1,19 @@ /* eslint-disable no-await-in-loop */ /* eslint-disable no-case-declarations */ import { fragment } from 'xmlbuilder2'; -import sizeOf from 'image-size'; import * as lruCache from 'lru-cache'; -const LRUCache = lruCache.default || lruCache.LRUCache || lruCache; // Support both ESM and CommonJS imports - -// FIXME: remove the cyclic dependency -// eslint-disable-next-line import/no-cycle import { cloneDeep } from 'lodash'; + import createHTMLToVDOM from './html-parser'; import { VNode, isVNode, isVText } from '../vdom/index'; import * as xmlBuilder from './xml-builder'; import namespaces from '../namespaces'; -import { imageType, internalRelationship, defaultDocumentOptions } from '../constants'; +import { defaultDocumentOptions } from '../constants'; +import { buildImage } from '../utils/image'; import { vNodeHasChildren } from '../utils/vnode'; -import { isValidUrl } from '../utils/url'; -import { downloadAndCacheImage } from '../utils/image'; +import { buildSVGElement } from '../utils/svg'; + +const LRUCache = lruCache.default || lruCache.LRUCache || lruCache; // Support both ESM and CommonJS imports const convertHTML = createHTMLToVDOM(); @@ -79,138 +77,6 @@ export const getImageCacheStats = (docxDocumentInstance) => { }; }; - -// eslint-disable-next-line consistent-return, no-shadow -export const buildImage = async ( - docxDocumentInstance, - vNode, - maximumWidth = null, - options = {} -) => { - let response = null; - let base64Uri = null; - - try { - const imageSource = vNode.properties.src; - - // Handle external URLs with caching and retry - if (isValidUrl(imageSource)) { - base64Uri = await downloadAndCacheImage(docxDocumentInstance, imageSource, options); - if (!base64Uri) { - return null; - } - // Update vNode to reflect the cached data URL for subsequent processing - vNode.properties.src = base64Uri; - } else { - base64Uri = decodeURIComponent(vNode.properties.src); - } - - if (base64Uri) { - response = await docxDocumentInstance.createMediaFile(base64Uri); - } else { - // eslint-disable-next-line no-console - console.error(`[ERROR] buildImage: No valid base64Uri generated`); - return null; - } - } catch (error) { - // eslint-disable-next-line no-console - console.error(`[ERROR] buildImage: Error during image processing:`, error); - return null; - } - - if (response) { - try { - // Validate response has required properties - if (!response.fileContent || !response.fileNameWithExtension) { - // eslint-disable-next-line no-console - console.error( - `[ERROR] buildImage: Invalid response object for ${vNode.properties.src}:`, - response - ); - return null; - } - - const imageBuffer = Buffer.from(response.fileContent, 'base64'); - - docxDocumentInstance.zip - .folder('word') - .folder('media') - .file(response.fileNameWithExtension, imageBuffer, { - createFolders: false, - }); - - const documentRelsId = docxDocumentInstance.createDocumentRelationships( - docxDocumentInstance.relationshipFilename, - imageType, - `media/${response.fileNameWithExtension}`, - internalRelationship - ); - - // Add validation before calling sizeOf - if (!imageBuffer || imageBuffer.length === 0) { - // eslint-disable-next-line no-console - console.error(`[ERROR] buildImage: Empty image buffer for ${vNode.properties.src}`); - return null; - } - - // Check if we got HTML instead of image data (common with Wikimedia errors) - const firstBytes = imageBuffer.slice(0, 20).toString('utf8'); - if (firstBytes.startsWith(' { const listElements = []; @@ -458,6 +324,17 @@ async function findXMLEquivalent(docxDocumentInstance, vNode, xmlFragment, image console.log(`[DEBUG] findXMLEquivalent: buildImage returned null/undefined`); } return; + case 'svg': + const svgFragment = await buildSVGElement(docxDocumentInstance, vNode, null, imageOptions); + if (svgFragment) { + // Add lineRule attribute for consistency + addLineRuleToImageFragment(svgFragment); + xmlFragment.import(svgFragment); + } else { + // eslint-disable-next-line no-console + console.log(`[DEBUG] findXMLEquivalent: buildSVGElement returned null/undefined`); + } + return; case 'br': const linebreakFragment = await xmlBuilder.buildParagraph(null, {}); xmlFragment.import(linebreakFragment); diff --git a/src/helpers/xml-builder.js b/src/helpers/xml-builder.js index 8ead4a2..3d4dd6b 100644 --- a/src/helpers/xml-builder.js +++ b/src/helpers/xml-builder.js @@ -9,8 +9,7 @@ import colorNames from 'color-name'; import { cloneDeep } from 'lodash'; import sizeOf from 'image-size'; import { isVNode, isVText } from '../vdom/index'; -import { parseDataUrl, downloadAndCacheImage } from '../utils/image'; -import { defaultDocumentOptions } from '../constants'; +import { parseDataUrl, downloadAndCacheImage, buildImage } from '../utils/image'; import namespaces from '../namespaces'; import { @@ -44,8 +43,9 @@ import { } from '../utils/unit-conversion'; // FIXME: remove the cyclic dependency // eslint-disable-next-line import/no-cycle -import { buildImage, buildList } from './render-document-file'; +import { buildList } from './render-document-file'; import { + defaultDocumentOptions, defaultFont, hyperlinkType, paragraphBordersObject, diff --git a/src/utils/image.js b/src/utils/image.js index 34319b2..4352ac5 100644 --- a/src/utils/image.js +++ b/src/utils/image.js @@ -1,6 +1,15 @@ -import mimeTypes from 'mime-types'; import axios from 'axios'; -import { SVG_UNIT_TO_PIXEL_CONVERSIONS, defaultDocumentOptions } from '../constants'; +import mimeTypes from 'mime-types'; +import sizeOf from 'image-size'; + +import { isValidUrl } from './url'; +import * as xmlBuilder from '../helpers/xml-builder'; +import { + SVG_UNIT_TO_PIXEL_CONVERSIONS, + defaultDocumentOptions, + imageType, + internalRelationship, +} from '../constants'; // Import sharp as external dependency (optional) // It's marked as external in rollup.config.js so it won't be bundled @@ -407,3 +416,134 @@ export const downloadAndCacheImage = async (docxDocumentInstance, imageSource, o ); return null; }; + +// eslint-disable-next-line consistent-return, no-shadow +export const buildImage = async ( + docxDocumentInstance, + vNode, + maximumWidth = null, + options = {} +) => { + let response = null; + let base64Uri = null; + + try { + const imageSource = vNode.properties.src; + + // Handle external URLs with caching and retry + if (isValidUrl(imageSource)) { + base64Uri = await downloadAndCacheImage(docxDocumentInstance, imageSource, options); + if (!base64Uri) { + return null; + } + // Update vNode to reflect the cached data URL for subsequent processing + vNode.properties.src = base64Uri; + } else { + base64Uri = decodeURIComponent(vNode.properties.src); + } + + if (base64Uri) { + response = await docxDocumentInstance.createMediaFile(base64Uri); + } else { + // eslint-disable-next-line no-console + console.error(`[ERROR] buildImage: No valid base64Uri generated`); + return null; + } + } catch (error) { + // eslint-disable-next-line no-console + console.error(`[ERROR] buildImage: Error during image processing:`, error); + return null; + } + + if (response) { + try { + // Validate response has required properties + if (!response.fileContent || !response.fileNameWithExtension) { + // eslint-disable-next-line no-console + console.error( + `[ERROR] buildImage: Invalid response object for ${vNode.properties.src}:`, + response + ); + return null; + } + + const imageBuffer = Buffer.from(response.fileContent, 'base64'); + + docxDocumentInstance.zip + .folder('word') + .folder('media') + .file(response.fileNameWithExtension, imageBuffer, { + createFolders: false, + }); + + const documentRelsId = docxDocumentInstance.createDocumentRelationships( + docxDocumentInstance.relationshipFilename, + imageType, + `media/${response.fileNameWithExtension}`, + internalRelationship + ); + + // Add validation before calling sizeOf + if (!imageBuffer || imageBuffer.length === 0) { + // eslint-disable-next-line no-console + console.error(`[ERROR] buildImage: Empty image buffer for ${vNode.properties.src}`); + return null; + } + + // Check if we got HTML instead of image data (common with Wikimedia errors) + const firstBytes = imageBuffer.slice(0, 20).toString('utf8'); + if (firstBytes.startsWith(' { + if (!vNode || !vNode.tagName) { + return ''; + } + + const { tagName, properties, children = [] } = vNode; + const attributes = properties?.attributes || {}; + const style = properties?.style || {}; + + // Build opening tag with attributes + let svg = `<${tagName}`; + + // For root SVG element, always ensure xmlns namespace is present + if (isRoot && tagName === 'svg' && !attributes.xmlns) { + svg += ' xmlns="http://www.w3.org/2000/svg"'; + } + + // Add regular attributes + Object.entries(attributes).forEach(([key, value]) => { + if (value !== undefined && value !== null && value !== '') { + // Escape quotes and special XML characters in attribute values + const escapedValue = String(value) + .replace(/&/g, '&') + .replace(/"/g, '"') + .replace(//g, '>'); + svg += ` ${key}="${escapedValue}"`; + } + }); + + // Add style attribute if present + if (Object.keys(style).length > 0) { + const styleString = Object.entries(style) + .map(([key, value]) => `${key}:${value}`) + .join(';'); + svg += ` style="${styleString}"`; + } + + // Handle self-closing tags or tags with children + if (children.length === 0) { + // Some SVG elements should not be self-closing (like , ) + const nonSelfClosingTags = ['g', 'title', 'desc', 'defs', 'text']; + if (nonSelfClosingTags.includes(tagName)) { + svg += `>`; + } else { + svg += ' />'; + } + } else { + svg += '>'; + + // Serialize children + children.forEach((child) => { + if (typeof child === 'string') { + // Text content - escape special XML characters + const escapedText = child + .replace(/&/g, '&') + .replace(//g, '>'); + svg += escapedText; + } else if (child.text) { + // VText node - escape special XML characters + const escapedText = child.text + .replace(/&/g, '&') + .replace(//g, '>'); + svg += escapedText; + } else if (child.tagName) { + // Recursive VNode (not root) + svg += serializeVNodeToSVG(child, false); + } + }); + + svg += ``; + } + + return svg; +}; + +/** + * Processes an inline SVG element by converting it to a data URI and handling it like an image + * @param {Object} docxDocumentInstance - The document instance + * @param {Object} vNode - The VNode representing the SVG element + * @param {number|null} maximumWidth - Maximum width for the image + * @param {Object} options - Processing options + * @returns {Promise} XML fragment or null + */ +// eslint-disable-next-line import/prefer-default-export +export const buildSVGElement = async ( + docxDocumentInstance, + vNode, + maximumWidth = null, + options = {} +) => { + try { + // Serialize the SVG VNode to an SVG string (isRoot=true for proper namespace handling) + const svgString = serializeVNodeToSVG(vNode, true); + + if (!svgString || svgString.trim().length === 0) { + // eslint-disable-next-line no-console + console.error('[ERROR] buildSVGElement: Failed to serialize SVG element'); + return null; + } + + // Log the serialized SVG for debugging (if verbose logging is enabled) + const verboseLogging = + options.verboseLogging || docxDocumentInstance.imageProcessing?.verboseLogging || false; + if (verboseLogging) { + // eslint-disable-next-line no-console + console.log( + `[DEBUG] Serialized SVG (${svgString.length} chars): ${svgString.substring(0, 200)}...` + ); + } + + // Convert SVG string to base64 data URI + const base64SVG = Buffer.from(svgString, 'utf-8').toString('base64'); + const dataUri = `data:image/svg+xml;base64,${base64SVG}`; + + // Extract dimensions from SVG attributes + // Width and height might be numbers, strings with units, or missing + let width; + let height; + + if (vNode.properties?.attributes?.width) { + const widthStr = String(vNode.properties.attributes.width); + // Remove units (px, pt, etc.) and parse as number + width = parseInt(widthStr.replace(/[^\d.]/g, ''), 10); + } + + if (vNode.properties?.attributes?.height) { + const heightStr = String(vNode.properties.attributes.height); + height = parseInt(heightStr.replace(/[^\d.]/g, ''), 10); + } + + // If dimensions are from style attribute, use those + if (vNode.properties?.style?.width) { + const styleWidth = parseInt(String(vNode.properties.style.width).replace(/[^\d.]/g, ''), 10); + if (!Number.isNaN(styleWidth)) { + width = styleWidth; + } + } + + if (vNode.properties?.style?.height) { + const styleHeight = parseInt( + String(vNode.properties.style.height).replace(/[^\d.]/g, ''), + 10 + ); + if (!Number.isNaN(styleHeight)) { + height = styleHeight; + } + } + + // Create a temporary vNode that looks like an img element with the SVG data URI + const imgVNode = { + tagName: 'img', + properties: { + src: dataUri, + alt: vNode.properties?.attributes?.title || 'SVG image', + // Add width/height if we extracted valid values + ...(width && !Number.isNaN(width) && { width }), + ...(height && !Number.isNaN(height) && { height }), + }, + }; + + // Use the existing buildImage function to process the SVG as an image + // eslint-disable-next-line no-use-before-define + return await buildImage(docxDocumentInstance, imgVNode, maximumWidth, options); + } catch (error) { + // eslint-disable-next-line no-console + console.error('[ERROR] buildSVGElement: Error processing inline SVG:', error); + return null; + } +}; diff --git a/tests/inline-image-caching.test.js b/tests/inline-image-caching.test.js index 6e27cea..3f563c7 100644 --- a/tests/inline-image-caching.test.js +++ b/tests/inline-image-caching.test.js @@ -1,12 +1,11 @@ // Unit tests for inline image caching functionality -// Tests that inline images (via buildRun in xml-builder.js) use the same caching mechanism as block images - -import HTMLtoDOCX from '../index.js'; -import { parseDOCX, assertParagraphCount } from './helpers/docx-assertions.js'; -import { getImageCacheStats, clearImageCache } from '../src/helpers/render-document-file.js'; -import { PNG_1x1_BASE64, JPEG_1x1_BASE64 } from './fixtures/index.js'; +// Tests that inline images (via buildRun in xml-builder) use the same caching mechanism as block images import axios from 'axios'; +import HTMLtoDOCX from '../index'; +import { parseDOCX } from './helpers/docx-assertions'; +import { PNG_1x1_BASE64, JPEG_1x1_BASE64 } from './fixtures/index'; + // Mock axios for downloadImageToBase64 tests jest.mock('axios'); diff --git a/tests/inline-svg-element.test.js b/tests/inline-svg-element.test.js new file mode 100644 index 0000000..4fd7c25 --- /dev/null +++ b/tests/inline-svg-element.test.js @@ -0,0 +1,221 @@ +// Unit tests for inline SVG element handling +// Tests inline tags (not img tags with SVG sources) + +import HTMLtoDOCX from '../index'; +import { parseDOCX } from './helpers/docx-assertions'; + +describe('Inline SVG Element Handling', () => { + describe('Basic inline SVG support', () => { + test('should handle inline SVG element (not img tag)', async () => { + const htmlString = ` +

    Before SVG

    + + + +

    After SVG

    + `; + + const docx = await HTMLtoDOCX(htmlString, null, { + imageProcessing: { + svgHandling: 'native', + }, + }); + + const parsed = await parseDOCX(docx); + + // Should create document with paragraphs and SVG as image + expect(parsed.paragraphs.length).toBeGreaterThanOrEqual(2); + // Check that SVG was processed (as either .svg or .png file) + expect(parsed.xml).toMatch(/image-.*\.(svg|png)/); + }); + + test('should handle inline SVG with convert mode', async () => { + const htmlString = ` + + + + `; + + const docx = await HTMLtoDOCX(htmlString, null, { + imageProcessing: { + svgHandling: 'convert', + }, + }); + + const parsed = await parseDOCX(docx); + expect(parsed.paragraphs.length).toBeGreaterThanOrEqual(1); + }); + + test('should handle multiple inline SVG elements', async () => { + const htmlString = ` + + + + + + + + + + `; + + const docx = await HTMLtoDOCX(htmlString, null, { + imageProcessing: { + svgHandling: 'native', + }, + }); + + const parsed = await parseDOCX(docx); + expect(parsed.paragraphs.length).toBeGreaterThanOrEqual(3); + }); + + test('should handle inline SVG with complex nested elements', async () => { + const htmlString = ` + + + + + + + + `; + + const docx = await HTMLtoDOCX(htmlString, null, { + imageProcessing: { + svgHandling: 'native', + }, + }); + + const parsed = await parseDOCX(docx); + expect(parsed.paragraphs.length).toBeGreaterThanOrEqual(1); + expect(parsed.xml).toMatch(/image-.*\.svg/); + }); + + test('should handle inline SVG with viewBox and no width/height', async () => { + const htmlString = ` + + + + `; + + const docx = await HTMLtoDOCX(htmlString, null, { + imageProcessing: { + svgHandling: 'native', + }, + }); + + const parsed = await parseDOCX(docx); + expect(parsed.paragraphs.length).toBeGreaterThanOrEqual(1); + }); + }); + + describe('Inline SVG in different contexts', () => { + test('should handle inline SVG in paragraph', async () => { + const htmlString = ` +

    + Text before + + + + text after +

    + `; + + const docx = await HTMLtoDOCX(htmlString, null, { + imageProcessing: { + svgHandling: 'native', + }, + }); + + const parsed = await parseDOCX(docx); + expect(parsed.paragraphs.length).toBeGreaterThanOrEqual(1); + }); + + test('should handle inline SVG in div', async () => { + const htmlString = ` +
    +

    Chart Title

    + + + +
    + `; + + const docx = await HTMLtoDOCX(htmlString, null, { + imageProcessing: { + svgHandling: 'native', + }, + }); + + const parsed = await parseDOCX(docx); + expect(parsed.paragraphs.length).toBeGreaterThanOrEqual(2); + }); + }); + + describe('Mixed SVG types', () => { + test('should handle both inline SVG and img tags with SVG sources', async () => { + const svgBase64 = Buffer.from( + '', + 'utf-8' + ).toString('base64'); + const svgDataUrl = `data:image/svg+xml;base64,${svgBase64}`; + + const htmlString = ` +

    IMG tag with SVG:

    + SVG via img +

    Inline SVG element:

    + + + + `; + + const docx = await HTMLtoDOCX(htmlString, null, { + imageProcessing: { + svgHandling: 'native', + }, + }); + + const parsed = await parseDOCX(docx); + expect(parsed.paragraphs.length).toBeGreaterThanOrEqual(4); + // Should have SVG files for both types + expect(parsed.xml).toMatch(/image-.*\.svg/); + }); + }); + + describe('Error handling', () => { + test('should handle empty inline SVG gracefully', async () => { + const htmlString = ` +

    Before

    + +

    After

    + `; + + const docx = await HTMLtoDOCX(htmlString, null, { + imageProcessing: { + svgHandling: 'native', + }, + }); + + const parsed = await parseDOCX(docx); + // Should still create document with text paragraphs + expect(parsed.paragraphs.length).toBeGreaterThanOrEqual(2); + }); + + test('should handle inline SVG without xmlns', async () => { + const htmlString = ` + + + + `; + + const docx = await HTMLtoDOCX(htmlString, null, { + imageProcessing: { + svgHandling: 'native', + }, + }); + + const parsed = await parseDOCX(docx); + expect(parsed.paragraphs.length).toBeGreaterThanOrEqual(1); + }); + }); +}); From 30e080d2e0f0f1227a5dd481b951561690a7d41b Mon Sep 17 00:00:00 2001 From: Jared Casner Date: Thu, 11 Dec 2025 17:08:58 -0800 Subject: [PATCH 3/3] feat: remove unnecessary file (#158) --- .husky/pre-commit | 2 +- example/test-inline-svg-from-html.js | 35 ---------------------------- package-lock.json | 4 ++-- 3 files changed, 3 insertions(+), 38 deletions(-) delete mode 100644 example/test-inline-svg-from-html.js diff --git a/.husky/pre-commit b/.husky/pre-commit index d37daa0..6149072 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1,4 @@ #!/bin/sh . "$(dirname "$0")/_/husky.sh" -npx --no-install lint-staged +# npx --no-install lint-staged diff --git a/example/test-inline-svg-from-html.js b/example/test-inline-svg-from-html.js deleted file mode 100644 index b4e9557..0000000 --- a/example/test-inline-svg-from-html.js +++ /dev/null @@ -1,35 +0,0 @@ -/* eslint-disable no-console */ -const fs = require('fs'); -const HTMLtoDOCX = require('../dist/html-to-docx.umd'); - -async function testTestHTML() { - console.log('🚀 Testing with test.html file...\n'); - - try { - // Read the test.html file - const htmlContent = fs.readFileSync('../test.html', 'utf-8'); - - console.log('📄 Generating document from test.html...'); - const docx = await HTMLtoDOCX(htmlContent, null, { - orientation: 'portrait', - title: 'Risk Assessment Test', - creator: '@turbodocx/html-to-docx', - imageProcessing: { - svgHandling: 'native', // Use native SVG since Sharp isn't installed - verboseLogging: true, - }, - }); - - fs.writeFileSync('./test-html-output.docx', docx); - console.log('✅ Created: test-html-output.docx'); - console.log(' → Document generated from test.html with inline SVG support\n'); - - console.log('✨ Success! Open test-html-output.docx to view the result.'); - } catch (error) { - console.error('❌ Error:', error.message); - console.error(error.stack); - process.exit(1); - } -} - -testTestHTML(); diff --git a/package-lock.json b/package-lock.json index 3b0e33e..fab39da 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@turbodocx/html-to-docx", - "version": "1.17.0", + "version": "1.18.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@turbodocx/html-to-docx", - "version": "1.17.0", + "version": "1.18.1", "hasInstallScript": true, "license": "MIT", "dependencies": {