Compare commits

..

1 Commits

Author SHA1 Message Date
7d53e5d5f2 Add table borders to table demo 2023-12-27 20:56:13 +00:00
110 changed files with 5316 additions and 8385 deletions

View File

@ -21,7 +21,6 @@
"iife",
"Initializable",
"iroha",
"JOHAB",
"jsonify",
"jszip",
"NUMPAGES",

View File

@ -13,10 +13,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout Repo
uses: actions/checkout@v4
uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- name: Archive Production Artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@master
with:
name: build
path: build
@ -25,24 +25,22 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout Repo
uses: actions/checkout@v4
uses: actions/checkout@master
- name: Install Dependencies
run: npm ci --force
- name: Test
run: npm run test:ci
- name: Codecov
uses: codecov/codecov-action@v4
uses: codecov/codecov-action@v3
with:
fail_ci_if_error: true
verbose: true
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- name: Checkout Repo
uses: actions/checkout@v4
uses: actions/checkout@master
- name: Install Dependencies
run: npm ci --force
- name: Lint
@ -52,7 +50,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout Repo
uses: actions/checkout@v4
uses: actions/checkout@master
- name: Install Dependencies
run: npm ci --force
- name: Prettier
@ -62,7 +60,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout Repo
uses: actions/checkout@v4
uses: actions/checkout@master
- name: Install Dependencies
run: npm ci --force
- name: Prettier

View File

@ -8,189 +8,777 @@ on:
- master
jobs:
demos:
name: Demos
build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@master
- name: Install Dependencies
run: npm ci --force
- name: Build
run: npm run build
- name: Archive Production Artifact
uses: actions/upload-artifact@master
with:
name: build
path: build
demo_1:
name: Run Demo 1 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- name: Run Demos
run: npm run run-ts -- ./demo/1-basic.ts
- run: npm run run-ts -- ./demo/1-basic.ts
- uses: "./.github/actions/validate-docx"
demo_2:
name: Run Demo 2 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/2-declaritive-styles.ts
- uses: "./.github/actions/validate-docx"
demo_3:
name: Run Demo 3 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/3-numbering-and-bullet-points.ts
- uses: "./.github/actions/validate-docx"
demo_4:
name: Run Demo 4 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/4-basic-table.ts
- uses: "./.github/actions/validate-docx"
demo_5:
name: Run Demo 5 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/5-images.ts
- uses: "./.github/actions/validate-docx"
demo_6:
name: Run Demo 6 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/6-page-borders.ts
- uses: "./.github/actions/validate-docx"
demo_7:
name: Run Demo 7 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/7-landscape.ts
- uses: "./.github/actions/validate-docx"
demo_8:
name: Run Demo 8 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/8-header-footer.ts
- uses: "./.github/actions/validate-docx"
demo_9:
name: Run Demo 9 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/9-images-in-header-and-footer.ts
- uses: "./.github/actions/validate-docx"
demo_10:
name: Run Demo 10 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/10-my-cv.ts
- uses: "./.github/actions/validate-docx"
demo_11:
name: Run Demo 11 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/11-declaritive-styles-2.ts
- uses: "./.github/actions/validate-docx"
demo_12:
name: Run Demo 12 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/12-scaling-images.ts
- uses: "./.github/actions/validate-docx"
demo_13:
name: Run Demo 13 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/13-xml-styles.ts
- uses: "./.github/actions/validate-docx"
demo_14:
name: Run Demo 14 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/14-page-numbers.ts
- uses: "./.github/actions/validate-docx"
demo_15:
name: Run Demo 15 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/15-page-break-before.ts
- uses: "./.github/actions/validate-docx"
demo_16:
name: Run Demo 16 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/16-multiple-sections.ts
- uses: "./.github/actions/validate-docx"
demo_17:
name: Run Demo 17 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/17-footnotes.ts
# element r: Schemas validity error : Element '{http://schemas.openxmlformats.org/wordprocessingml/2006/main}r': This element is not expected.
# - uses: "./.github/actions/validate-docx"
demo_18:
name: Run Demo 18 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/18-image-from-buffer.ts
- uses: "./.github/actions/validate-docx"
demo_19:
name: Run Demo 19 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/19-export-to-base64.ts
# Base 64 No longer works, abruptly. Node issue?
# - uses: "./.github/actions/validate-docx"
demo_20:
name: Run Demo 20 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/20-table-cell-borders.ts
- uses: "./.github/actions/validate-docx"
demo_21:
name: Run Demo 21 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/21-bookmarks.ts
# Bad ID - need numeric ID
# - uses: "./.github/actions/validate-docx"
demo_22:
name: Run Demo 22 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/22-right-to-left-text.ts
- uses: "./.github/actions/validate-docx"
demo_23:
name: Run Demo 23 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/23-base64-images.ts
- uses: "./.github/actions/validate-docx"
demo_24:
name: Run Demo 24 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/24-images-to-table-cell.ts
- uses: "./.github/actions/validate-docx"
demo_25:
name: Run Demo 25 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/25-table-xml-styles.ts
- uses: "./.github/actions/validate-docx"
demo_26:
name: Run Demo 26 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/26-paragraph-borders.ts
- uses: "./.github/actions/validate-docx"
demo_27:
name: Run Demo 27 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/27-declaritive-styles-3.ts
- uses: "./.github/actions/validate-docx"
demo_28:
name: Run Demo 28 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/28-table-of-contents.ts
- uses: "./.github/actions/validate-docx"
demo_29:
name: Run Demo 29 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/29-numbered-lists.ts
- uses: "./.github/actions/validate-docx"
demo_31:
name: Run Demo 31 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/31-tables.ts
- uses: "./.github/actions/validate-docx"
demo_32:
name: Run Demo 32 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/32-merge-and-shade-table-cells.ts
- uses: "./.github/actions/validate-docx"
demo_33:
name: Run Demo 33 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/33-sequential-captions.ts
- uses: "./.github/actions/validate-docx"
demo_34:
name: Run Demo 34 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/34-floating-tables.ts
# element tblpPr: Schemas validity error : Element '{http://schemas.openxmlformats.org/wordprocessingml/2006/main}tblpPr', attribute 'overlap': The attribute 'overlap' is not allowed.
# element tblpPr: Schemas validity error : Element '{http://schemas.openxmlformats.org/wordprocessingml/2006/main}tblpPr': Element content is not allowed, because the content type is empty.
# - uses: "./.github/actions/validate-docx"
demo_35:
name: Run Demo 35 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/35-hyperlinks.ts
- uses: "./.github/actions/validate-docx"
demo_36:
name: Run Demo 36 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/36-image-to-table-cell.ts
- uses: "./.github/actions/validate-docx"
demo_37:
name: Run Demo 37 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/37-images-to-header-and-footer.ts
- uses: "./.github/actions/validate-docx"
demo_38:
name: Run Demo 38 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/38-text-wrapping.ts
- uses: "./.github/actions/validate-docx"
demo_39:
name: Run Demo 39 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/39-page-numbers.ts
- uses: "./.github/actions/validate-docx"
demo_40:
name: Run Demo 40 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/40-line-numbers.ts
- uses: "./.github/actions/validate-docx"
demo_41:
name: Run Demo 41 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/41-merge-table-cells-2.ts
- uses: "./.github/actions/validate-docx"
demo_42:
name: Run Demo 42 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/42-restart-page-numbers.ts
- uses: "./.github/actions/validate-docx"
demo_43:
name: Run Demo 43 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/43-images-to-table-cell-2.ts
- uses: "./.github/actions/validate-docx"
demo_44:
name: Run Demo 44 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/44-multiple-columns.ts
- uses: "./.github/actions/validate-docx"
demo_45:
name: Run Demo 45 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/45-highlighting-text.ts
- uses: "./.github/actions/validate-docx"
demo_46:
name: Run Demo 46 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/46-shading-text.ts
- uses: "./.github/actions/validate-docx"
demo_47:
name: Run Demo 47 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/47-number-of-total-pages-section.ts
- uses: "./.github/actions/validate-docx"
demo_48:
name: Run Demo 48 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/48-vertical-align.ts
- uses: "./.github/actions/validate-docx"
demo_49:
name: Run Demo 49 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/49-table-borders.ts
- uses: "./.github/actions/validate-docx"
demo_50:
name: Run Demo 50 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/50-readme-demo.ts
- uses: "./.github/actions/validate-docx"
demo_51:
name: Run Demo 51 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/51-character-styles.ts
- uses: "./.github/actions/validate-docx"
demo_52:
name: Run Demo 52 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/52-japanese.ts
- uses: "./.github/actions/validate-docx"
demo_53:
name: Run Demo 53 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/53-chinese.ts
- uses: "./.github/actions/validate-docx"
demo_54:
name: Run Demo 54 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/54-custom-properties.ts
- uses: "./.github/actions/validate-docx"
demo_55:
name: Run Demo 55 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/55-math.ts
#: element subHide: Schemas validity error : Element '{http://schemas.openxmlformats.org/officeDocument/2006/math}subHide': This element is not expected. Expected is ( {http://schemas.openxmlformats.org/officeDocument/2006/math}ctrlPr ).
#: element e: Schemas validity error : Element '{http://schemas.openxmlformats.org/officeDocument/2006/math}e': This element is not expected. Expected is ( {http://schemas.openxmlformats.org/officeDocument/2006/math}sub ).
#: element e: Schemas validity error : Element '{http://schemas.openxmlformats.org/officeDocument/2006/math}e': This element is not expected. Expected is ( {http://schemas.openxmlformats.org/officeDocument/2006/math}sup ).
#: element e: Schemas validity error : Element '{http://schemas.openxmlformats.org/officeDocument/2006/math}e': This element is not expected. Expected is ( {http://schemas.openxmlformats.org/officeDocument/2006/math}sub ).
# - uses: "./.github/actions/validate-docx"
demo_56:
name: Run Demo 56 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/56-background-color.ts
- uses: "./.github/actions/validate-docx"
demo_57:
name: Run Demo 57 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/57-add-parent-numbered-lists.ts
- uses: "./.github/actions/validate-docx"
demo_58:
name: Run Demo 58 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/58-section-types.ts
- uses: "./.github/actions/validate-docx"
demo_59:
name: Run Demo 59 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/59-header-footer-margins.ts
- uses: "./.github/actions/validate-docx"
demo_60:
name: Run Demo 60 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/60-track-revisions.ts
# element r: Schemas validity error : Element '{http://schemas.openxmlformats.org/wordprocessingml/2006/main}r': This element is not expected.
# - uses: "./.github/actions/validate-docx"
demo_61:
name: Run Demo 61 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/61-text-frame.ts
# element left: Schemas validity error : Element '{http://schemas.openxmlformats.org/wordprocessingml/2006/main}left': This element is not expected. Expected is one of ( {http://schemas.openxmlformats.org/wordprocessingml/2006/main}right, {http://schemas.openxmlformats.org/wordprocessingml/2006/main}between, {http://schemas.openxmlformats.org/wordprocessingml/2006/main}bar ).
# - uses: "./.github/actions/validate-docx"
demo_62:
name: Run Demo 62 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/62-paragraph-spacing.ts
- uses: "./.github/actions/validate-docx"
demo_63:
name: Run Demo 63 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/63-odd-even-header-footer.ts
- uses: "./.github/actions/validate-docx"
demo_64:
name: Run Demo 64 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/64-complex-numbering-text.ts
- uses: "./.github/actions/validate-docx"
demo_65:
name: Run Demo 65 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/65-page-sizes.ts
- uses: "./.github/actions/validate-docx"
demo_66:
name: Run Demo 66 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/66-fields.ts
# element bookmarkStart: Schemas validity error : Element '{http://schemas.openxmlformats.org/wordprocessingml/2006/main}bookmarkStart', attribute '{http://schemas.openxmlformats.org/wordprocessingml/2006/main}id': '-irrswq-ln94j4fdgdjxs' is not a valid value of the atomic type '{http://schemas.openxmlformats.org/wordprocessingml/2006/main}ST_DecimalNumber'.
# element bookmarkEnd: Schemas validity error : Element '{http://schemas.openxmlformats.org/wordprocessingml/2006/main}bookmarkEnd', attribute '{http://schemas.openxmlformats.org/wordprocessingml/2006/main}id': '-irrswq-ln94j4fdgdjxs' is not a valid value of the atomic type '{http://schemas.openxmlformats.org/wordprocessingml/2006/main}ST_DecimalNumber'.
# - uses: "./.github/actions/validate-docx"
demo_67:
name: Run Demo 67 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/67-column-break.ts
- uses: "./.github/actions/validate-docx"
demo_68:
name: Run Demo 68 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/68-numbering-instances-and-starting-number.ts
- uses: "./.github/actions/validate-docx"
demo_69:
name: Run Demo 69 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/69-different-width-columns.ts
- uses: "./.github/actions/validate-docx"
demo_70:
name: Run Demo 70 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/70-line-numbers-suppression.ts
- uses: "./.github/actions/validate-docx"
demo_71:
name: Run Demo 71 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/71-page-borders-2.ts
- uses: "./.github/actions/validate-docx"
demo_72:
name: Run Demo 72 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/72-word-wrap.ts
- uses: "./.github/actions/validate-docx"
demo_73:
name: Run Demo 73 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/73-comments.ts
- uses: "./.github/actions/validate-docx"
demo_74:
name: Run Demo 74 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/74-nodejs-stream.ts
# - uses: "./.github/actions/validate-docx"
demo_75:
name: Run Demo 75 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
# run: npm run run-ts -- ./demo/75-tab-stops.ts
# - uses: "./.github/actions/validate-docx"
demo_76:
name: Run Demo 76 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/76-compatibility.ts
- uses: "./.github/actions/validate-docx"
demo_77:
name: Run Demo 77 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/77-side-by-side-tables.ts
- uses: "./.github/actions/validate-docx"
demo_78:
name: Run Demo 78 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/78-thai-distributed.ts
- uses: "./.github/actions/validate-docx"
demo_79:
name: Run Demo 79 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/79-table-from-data-source.ts
- uses: "./.github/actions/validate-docx"
demo_80:
name: Run Demo 80 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/80-thai-distributed.ts
- uses: "./.github/actions/validate-docx"
demo_81:
name: Run Demo 81 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/81-continuous-header.ts
- uses: "./.github/actions/validate-docx"
demo_82:
name: Run Demo 82 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/82-new-headers-new-section.ts
- uses: "./.github/actions/validate-docx"
demo_83:
name: Run Demo 83 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/83-setting-languages.ts
- uses: "./.github/actions/validate-docx"
demo_84:
name: Run Demo 84 and validate
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: "./.github/actions/install-and-build"
- run: npm run run-ts -- ./demo/84-positional-tabs.ts
- uses: "./.github/actions/validate-docx"

View File

@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout Repo
uses: actions/checkout@v4
uses: actions/checkout@master
- name: Install Dependencies
run: npm ci --force
- name: Build 🔧
@ -19,7 +19,7 @@ jobs:
echo "docx.js.org" > docs/.nojekyll
echo "docx.js.org" > docs/CNAME
- name: Archive Production Artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@master
with:
name: docs
path: docs
@ -28,11 +28,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout Repo 🛎️
uses: actions/checkout@v4
uses: actions/checkout@master
- name: Install Dependencies
run: npm ci --force
- name: Download Artifact
uses: actions/download-artifact@v4
uses: actions/download-artifact@master
with:
name: docs
path: docs

View File

@ -1,46 +0,0 @@
# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
# For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages
name: Node.js Package
on:
release:
types: [created]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20.x"
- run: npm ci --force
- run: npm run cspell
- run: npm run prettier
- run: npm run lint
- run: npm run test:ci
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20.x"
- run: npm ci --force
- run: npm run build
publish-npm:
needs: [test, build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20.x"
registry-url: https://registry.npmjs.org/
- run: npm ci --force
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{secrets.npm_token}}

View File

@ -1,24 +0,0 @@
# Security Policy
## Supported Versions
Use this section to tell people about which versions of your project are
currently being supported with security updates.
| Version | Supported |
| ------- | ------------------ |
| 9.0.x | :white_check_mark: |
## Reporting a Vulnerability
We encourage responsible disclosure of security vulnerabilities. If you believe you have found a security vulnerability in this project, please report it via the [Security Tab](https://github.com/dolanmiu/docx/security/advisories)
Please include the following information in your report:
* A description of the vulnerability
* Steps to reproduce the vulnerability
* Impact of the vulnerability
We will investigate all reported vulnerabilities and take appropriate action.
We appreciate your help in keeping this project secure.

View File

@ -3,6 +3,142 @@
import * as fs from "fs";
import { AlignmentType, Document, Footer, Header, Packer, PageBreak, PageNumber, Paragraph, TextRun } from "docx";
// cspell: disable
const createLoremIpsumParagraphs = () => [
new Paragraph(
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam accumsan condimentum elit ut placerat. Integer vitae justo est. Quisque tempus augue eu diam pulvinar aliquam. Pellentesque neque sem, posuere eget augue pretium, feugiat mattis diam. Mauris libero arcu, elementum sit amet nunc sed, vestibulum posuere sapien. Nullam",
),
new Paragraph(
"ultrices efficitur magna et commodo. Morbi vitae dolor vulputate, dapibus ipsum in, finibus enim. Aliquam dapibus tellus libero. Nullam nulla eros, ullamcorper eu risus at, luctus aliquet nunc. Nunc dictum turpis eu quam suscipit porta. In rutrum scelerisque nunc in consectetur. Pellentesque ut nibh eget neque congue auctor. Nunc dapibus massa elit, vel cursus metus condimentum et. Nunc venenatis dolor eu lobortis fringilla. Nulla sed risus id lectus scelerisque sollicitudin. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.",
),
new Paragraph(
"Nulla lobortis et purus convallis ullamcorper. Nunc scelerisque, urna eu vestibulum feugiat, orci turpis pulvinar odio, vitae faucibus elit tortor et urna. Curabitur eros mauris, mollis a vestibulum nec, vestibulum sed velit. Nam semper metus ut felis ultricies rutrum. Cras hendrerit eros vel placerat vulputate. Proin placerat ",
),
new Paragraph(
"mollis lacus a ultricies. Mauris vel turpis vitae purus suscipit dignissim. Donec egestas molestie libero in suscipit. Aenean auctor tellus convallis eros porttitor, id vehicula risus commodo. Sed accumsan turpis elit, eget molestie tortor efficitur eget. Aliquam ut lectus quis augue pellentesque tincidunt id id quam. Maecenas auctor, lorem eu ornare tempor, lacus metus ultrices turpis, nec feugiat nibh purus id justo.",
),
new Paragraph(
"Sed semper feugiat ante, sit amet accumsan lorem vulputate vel. Morbi interdum, mauris sit amet efficitur mattis, nunc sapien tempor ante, eget maximus ipsum arcu quis dolor. Suspendisse id consequat justo, quis sollicitudin nisi. Duis euismod, velit non faucibus placerat, eros sem fermentum lectus, ut egestas nisi ipsum eget mi. ",
),
new Paragraph(
"Donec vitae mollis libero. Etiam magna leo, auctor sit amet nibh sit amet, interdum finibus nunc. Quisque a pellentesque velit, a laoreet ante.",
),
new Paragraph(
"Quisque fringilla orci quis dui facilisis, quis auctor urna cursus. Mauris eget justo lacus. Integer placerat, leo vitae ullamcorper varius, nulla mauris gravida massa, ac dignissim odio erat eu ante. Duis non dui semper, eleifend neque nec, ultricies ligula. Sed nec sem nec dolor ultrices finibus. Praesent rutrum iaculis mollis. ",
),
new Paragraph(
"Fusce accumsan dui tortor, quis feugiat urna efficitur sed. Fusce viverra tristique lacinia. Sed nec faucibus ipsum, vel pulvinar ligula. Curabitur ut viverra nisl. Nulla nisi tortor, imperdiet et ipsum sit amet, egestas lacinia leo. Quisque interdum mauris non nunc egestas tempor a vehicula diam. Vestibulum convallis quam sit amet tincidunt posuere. Mauris velit sem, fermentum a diam sit amet, pulvinar iaculis lectus. In dolor turpis, cursus id libero sit amet, aliquet ullamcorper urna. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.",
),
new Paragraph(
"Etiam tincidunt euismod nisi, ut dignissim neque blandit sit amet. Etiam vel velit rhoncus, tincidunt diam quis, dignissim felis. Integer dolor urna, rutrum vitae ultricies ut, sagittis quis felis. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Donec pharetra aliquam augue. Donec ",
),
new Paragraph(
"imperdiet placerat augue, nec consequat leo placerat a. Mauris accumsan ornare massa vitae volutpat. Sed commodo purus ac fringilla ultricies. Donec quis urna hendrerit dolor efficitur sollicitudin. Pellentesque diam arcu, dapibus a nisl pretium, auctor lobortis augue.",
),
new Paragraph(
"Aliquam libero lorem, scelerisque a volutpat ac, venenatis eu nisl. Maecenas turpis diam, consequat eget elementum eget, venenatis at lacus. In maximus erat magna, ut hendrerit erat malesuada vel. Suspendisse iaculis, lacus posuere convallis gravida, nisi purus blandit nunc, imperdiet tempor nunc turpis et sapien. Duis euismod id ",
),
new Paragraph(
"ligula ac laoreet. Ut facilisis massa quis turpis imperdiet, eu ornare lorem placerat. Vestibulum pharetra feugiat eleifend. Quisque ornare pretium urna, lacinia aliquam eros placerat et. Integer sit amet auctor ipsum. Morbi imperdiet dictum ex sed lacinia. Curabitur interdum mattis nunc non vestibulum. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Pellentesque odio libero, viverra ac quam eleifend, dignissim euismod nibh. Etiam sit amet semper ante. Morbi in tellus lacinia, bibendum dolor quis, viverra nisi. Proin condimentum purus ipsum, et accumsan sapien finibus ac.",
),
new Paragraph(
"Maecenas congue in leo id faucibus. Etiam porta dapibus ultricies. Nunc ac volutpat magna. Nam pretium dolor ac ultrices tincidunt. Vestibulum pharetra elit vitae lacus pharetra euismod. Pellentesque fringilla lacus ac neque varius sagittis. Mauris tincidunt rutrum velit. Etiam pretium est vitae lacus ultricies, vitae viverra ",
),
new Paragraph(
"turpis auctor. Nulla at lectus pellentesque enim dapibus aliquet eleifend et quam. Suspendisse cursus sed velit et lobortis. Integer sed facilisis ligula, ultrices molestie neque. Nulla porta mauris vitae quam consequat, eget fringilla enim luctus. Integer vitae rhoncus nibh, ac ornare ligula. Sed sed placerat libero. Suspendisse laoreet erat lacus, et lacinia nibh maximus in. Maecenas vitae enim at urna gravida euismod.",
),
new Paragraph(
"Interdum et malesuada fames ac ante ipsum primis in faucibus. Nullam lacinia lobortis tortor mollis venenatis. Sed sodales iaculis justo in rhoncus. Proin orci sapien, fermentum nec eleifend in, tristique vel mi. Donec vitae ornare justo, sed rhoncus nibh. Quisque a interdum est, in scelerisque odio. Ut luctus eget ex non ",
),
new Paragraph(
"fringilla. Morbi nec iaculis nisi. Donec porta libero ac ex sollicitudin, vitae interdum erat faucibus. Donec ornare, arcu ullamcorper pretium euismod, nibh nisi consectetur justo, ac aliquet sem eros non nibh. Nulla vitae elementum arcu. Aenean ut consectetur dui. Donec posuere condimentum velit ac hendrerit. Sed aliquet aliquet mi, sed rutrum justo ultrices eget.",
),
new Paragraph(
"Donec volutpat libero dui, ac bibendum nunc eleifend a. Fusce ultricies ligula non sollicitudin lobortis. Integer sit amet elit sapien. Morbi rhoncus bibendum nibh et facilisis. Etiam consectetur elementum sem non elementum. Nunc rutrum sagittis ipsum non sodales. Orci varius natoque penatibus et magnis dis parturient montes, ",
),
new Paragraph(
"nascetur ridiculus mus. Quisque porttitor nulla ultrices mollis porta. Cras non metus sed quam rutrum ultricies. Curabitur aliquet in sem eget auctor. Ut sodales quis leo bibendum venenatis. Etiam pellentesque eros ut metus dignissim commodo. Aliquam vitae sem gravida, convallis ipsum at, imperdiet tellus. Pellentesque consectetur odio sit amet sapien vestibulum aliquam nec id libero. Proin a maximus felis. Aenean molestie vulputate massa, eu eleifend sem consequat id.",
),
new Paragraph(
"Etiam quis ante nec leo faucibus dignissim eu at mi. Nam nec ligula nec sapien rhoncus faucibus. Fusce vestibulum orci libero, vel commodo est congue iaculis. In hac habitasse platea dictumst. Sed lacinia magna eu arcu commodo pretium. Fusce id elementum enim. Mauris tristique tortor dolor, at pretium magna tempor nec. Vivamus non ",
),
new Paragraph(
"dui sit amet odio porttitor tincidunt. Nam pulvinar aliquet tortor tristique tempor. Duis finibus tincidunt elit, at viverra justo sagittis in. Morbi pellentesque gravida mi, in pulvinar metus molestie at. Fusce bibendum eleifend sapien a fermentum. Nam efficitur tellus dignissim, vehicula tortor ac, tristique enim. Aliquam pretium dui interdum varius elementum. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec a finibus elit.",
),
new Paragraph(
"Phasellus cursus tortor at justo bibendum aliquam. Sed nec hendrerit nibh, eu finibus tortor. Aenean sit amet dui rutrum, sollicitudin justo eu, condimentum ipsum. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Maecenas dictum neque lacus, vel posuere eros pretium nec. Morbi ac ante at ",
),
new Paragraph(
"ex semper ultricies ac quis augue. In hac habitasse platea dictumst. Mauris laoreet porta nisl. Sed augue lorem, aliquet in volutpat ut, rhoncus eget nibh. Sed rhoncus arcu diam, accumsan rhoncus sem iaculis ac. Duis dolor magna, semper et tellus non, condimentum volutpat nisi. Donec eget metus eget elit eleifend vestibulum et ut purus.",
),
new Paragraph(
"Nam a dui accumsan, efficitur ex at, commodo eros. Pellentesque sit amet nunc ac odio egestas suscipit id sodales ligula. Duis non mi vitae mi mollis fringilla at sit amet sem. Morbi laoreet mattis dolor sit amet tincidunt. Aliquam erat volutpat. Pellentesque porta sem odio, at lobortis quam commodo vitae. Curabitur ut urna dolor.",
),
new Paragraph(
"Nulla vitae pretium ex. Nulla facilisi. Vestibulum placerat odio eget enim ultrices, et imperdiet tellus consequat. Sed dignissim, erat ut dignissim interdum, dui mi rhoncus nunc, id rhoncus turpis nunc eu risus. Mauris leo orci, euismod sit amet velit ac, condimentum dictum dui. Cras cursus dolor augue, et vestibulum lorem ",
),
new Paragraph(
"fringilla in. Nulla fermentum odio vehicula justo placerat, et aliquet velit vulputate. Sed aliquam auctor dictum. Phasellus sit amet sollicitudin elit. Cras eget gravida ex. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Duis tortor erat, ornare id feugiat eget, pellentesque at lectus. Sed vitae nisi ullamcorper, tincidunt augue vitae, accumsan ligula. Curabitur ut lobortis lacus, imperdiet placerat arcu.",
),
new Paragraph(
"Sed vestibulum tempor nulla vel dignissim. Phasellus imperdiet, dolor nec mollis tempor, nulla nibh pretium nibh, nec elementum lacus neque in sapien. Nam et eleifend lorem. Nam pretium molestie enim quis porta. Proin eu pharetra enim. Etiam a velit eget augue congue tempor. Interdum et malesuada fames ac ante ipsum primis in ",
),
new Paragraph("faucibus. Sed urna tellus, euismod ac pharetra nec, ultrices vel nisi."),
new Paragraph(
"Phasellus at arcu eget tellus mattis ultricies non quis urna. Vestibulum non eros fringilla, porttitor massa id, ornare metus. Quisque lacinia, massa a ornare vulputate, ex lectus ullamcorper ligula, eget facilisis mi turpis at dolor. Curabitur posuere elementum enim, non placerat tortor tincidunt sit amet. Pellentesque habitant ",
),
new Paragraph(
"morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nam fermentum ut felis et dapibus. Ut consectetur finibus bibendum. Phasellus semper sapien neque, nec consequat nibh convallis et. Nunc nec egestas enim. Nulla facilisi.",
),
new Paragraph(
"Quisque sed nibh sed lorem placerat ornare sed nec sapien. Morbi et risus vitae magna varius varius eget at leo. Curabitur a eleifend elit. Mauris augue nulla, convallis vel ante in, consequat feugiat orci. Donec sagittis risus nibh, eget porta purus faucibus non. Curabitur ultrices, ex et placerat rutrum, velit odio accumsan ",
),
new Paragraph(
"nulla, et elementum mi leo in velit. Pellentesque auctor egestas ultricies. In viverra est a mauris sollicitudin, a laoreet augue cursus. Vestibulum non sollicitudin massa. Curabitur ac tellus metus. Pellentesque hendrerit dolor sed mi vestibulum imperdiet. Nulla vitae odio ultrices, tempor enim sed, vestibulum eros. Cras libero ex, malesuada nec porttitor sit amet, efficitur sit amet ligula.",
),
new Paragraph(
"Morbi pellentesque tempus felis, id iaculis quam euismod non. Sed scelerisque id massa eu elementum. Vestibulum id malesuada arcu. Maecenas eget placerat sem, at consectetur orci. Nullam interdum erat urna, ac rhoncus odio feugiat sed. Morbi rutrum auctor sem eget pulvinar. Suspendisse egestas tempor volutpat.",
),
new Paragraph(
"Vivamus euismod, sem eget molestie rhoncus, metus dui laoreet lacus, et lobortis est metus ut felis. Aenean imperdiet lacus nunc, vitae molestie orci ultrices nec. Cras egestas maximus diam, vitae efficitur nisl luctus imperdiet. Nulla pellentesque sodales ante, nec facilisis turpis. Vivamus at hendrerit enim, ac dictum lorem. Cras ",
),
new Paragraph(
"congue accumsan dui, non pretium sem auctor quis. Nunc a mauris vehicula, elementum ex vitae, sollicitudin eros. Nunc non sapien vitae justo sodales condimentum. Praesent nisl felis, tristique ac odio at, rhoncus porttitor orci. Morbi egestas placerat iaculis.",
),
new Paragraph(
"Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. In at lorem nec neque faucibus ultricies ut in ipsum.",
),
new Paragraph(
"Suspendisse fermentum feugiat augue eu convallis. Maecenas eros velit, efficitur sit amet posuere sed, tristique sit amet nisi. Donec vel convallis justo, id tempor neque. Nunc pulvinar maximus nulla, vitae congue lacus cursus ut. Morbi at mollis est, a vehicula nisi. Cras venenatis nibh vel massa interdum commodo. Nulla mattis ",
),
new Paragraph(
"neque sed sem bibendum, iaculis hendrerit neque fringilla. Sed a lobortis orci. Morbi in est sed libero vestibulum semper. Suspendisse potenti. Aliquam pretium erat tellus, in suscipit lorem aliquet iaculis. Aenean ac viverra ipsum. Sed at diam luctus, pharetra lorem vel, aliquam magna. Donec mollis orci eget enim efficitur ultricies. Proin neque diam, dignissim euismod ex vel, sollicitudin sodales sapien.",
),
new Paragraph(
"Sed a egestas nunc, a ullamcorper est. Aenean vulputate fringilla justo non vestibulum. Donec ac dolor in nisl finibus tristique. Donec sed turpis at felis congue porttitor sed sit amet metus. In in ex nulla. Donec sodales vel velit ut congue. Nullam vitae egestas purus. Vivamus non mi consequat, molestie enim nec, hendrerit mi.",
),
];
// cspell: enable
const doc = new Document({
sections: [
{
@ -49,6 +185,19 @@ const doc = new Document({
}),
],
}),
new Paragraph({
alignment: AlignmentType.RIGHT,
children: [
new TextRun({
children: [
"Footer - Page in section ",
PageNumber.CURRENT_SECTION,
" of ",
PageNumber.TOTAL_PAGES_IN_SECTION,
],
}),
],
}),
],
}),
first: new Footer({
@ -70,6 +219,96 @@ const doc = new Document({
children: [new TextRun("First Page"), new PageBreak()],
}),
new Paragraph("Second Page"),
...createLoremIpsumParagraphs(),
new Paragraph({
children: [new TextRun("Next Page"), new PageBreak()],
}),
...createLoremIpsumParagraphs(),
],
},
{
properties: {
titlePage: true,
},
headers: {
default: new Header({
children: [
new Paragraph({
alignment: AlignmentType.RIGHT,
children: [
new TextRun("My Title "),
new TextRun({
children: ["Page ", PageNumber.CURRENT],
}),
],
}),
],
}),
first: new Header({
children: [
new Paragraph({
alignment: AlignmentType.RIGHT,
children: [
new TextRun("First Page Header "),
new TextRun({
children: ["Page ", PageNumber.CURRENT],
}),
],
}),
],
}),
},
footers: {
default: new Footer({
children: [
new Paragraph({
alignment: AlignmentType.RIGHT,
children: [
new TextRun("My Title "),
new TextRun({
children: ["Footer - Page ", PageNumber.CURRENT, " of ", PageNumber.TOTAL_PAGES],
}),
],
}),
new Paragraph({
alignment: AlignmentType.RIGHT,
children: [
new TextRun({
children: [
"Footer - Page in section ",
PageNumber.CURRENT_SECTION,
" of ",
PageNumber.TOTAL_PAGES_IN_SECTION,
],
}),
],
}),
],
}),
first: new Footer({
children: [
new Paragraph({
alignment: AlignmentType.RIGHT,
children: [
new TextRun("First Page Footer "),
new TextRun({
children: ["Page ", PageNumber.CURRENT],
}),
],
}),
],
}),
},
children: [
new Paragraph({
children: [new TextRun("First Page"), new PageBreak()],
}),
new Paragraph("Second Page"),
...createLoremIpsumParagraphs(),
new Paragraph({
children: [new TextRun("Next Page"), new PageBreak()],
}),
...createLoremIpsumParagraphs(),
],
},
],

View File

@ -1,7 +1,7 @@
// Example of how you would create a table and add data to it
import * as fs from "fs";
import { Document, HeadingLevel, Packer, Paragraph, Table, TableCell, TableRow, VerticalAlign, TextDirection } from "docx";
import { BorderStyle, Document, HeadingLevel, Packer, Paragraph, Table, TableCell, TableRow, VerticalAlign, TextDirection } from "docx";
const doc = new Document({
sections: [
@ -67,6 +67,173 @@ const doc = new Document({
}),
],
}),
new Paragraph("Table with borders"),
new Table({
rows: [
new TableRow({
children: [
new TableCell({
borders: {
top: {
style: BorderStyle.DASH_SMALL_GAP,
size: 1,
color: "000000",
},
bottom: {
style: BorderStyle.DASH_SMALL_GAP,
size: 1,
color: "000000",
},
left: {
style: BorderStyle.DASH_SMALL_GAP,
size: 1,
color: "000000",
},
right: {
style: BorderStyle.DASH_SMALL_GAP,
size: 1,
color: "000000",
},
},
children: [new Paragraph("Dash small gap border")],
}),
new TableCell({
borders: {
top: {
style: BorderStyle.DASH_SMALL_GAP,
size: 1,
color: "000000",
},
bottom: {
style: BorderStyle.DASH_SMALL_GAP,
size: 1,
color: "000000",
},
left: {
style: BorderStyle.DASH_SMALL_GAP,
size: 1,
color: "000000",
},
right: {
style: BorderStyle.DASH_SMALL_GAP,
size: 1,
color: "000000",
},
},
children: [new Paragraph("Dash small gap border")],
}),
],
}),
new TableRow({
children: [
new TableCell({
borders: {
top: {
style: BorderStyle.DOUBLE,
size: 1,
color: "ff0000",
},
bottom: {
style: BorderStyle.DOUBLE,
size: 1,
color: "ff0000",
},
left: {
style: BorderStyle.DOUBLE,
size: 1,
color: "ff0000",
},
right: {
style: BorderStyle.DOUBLE,
size: 1,
color: "ff0000",
},
},
children: [new Paragraph("Double border")],
}),
new TableCell({
borders: {
top: {
style: BorderStyle.DOUBLE,
size: 1,
color: "ff0000",
},
bottom: {
style: BorderStyle.DOUBLE,
size: 1,
color: "ff0000",
},
left: {
style: BorderStyle.DOUBLE,
size: 1,
color: "ff0000",
},
right: {
style: BorderStyle.DOUBLE,
size: 1,
color: "ff0000",
},
},
children: [new Paragraph("Double border")],
}),
],
}),
new TableRow({
children: [
new TableCell({
borders: {
top: {
style: BorderStyle.NONE,
size: 0,
color: "ffffff",
},
bottom: {
style: BorderStyle.NONE,
size: 0,
color: "ffffff",
},
left: {
style: BorderStyle.NONE,
size: 0,
color: "ffffff",
},
right: {
style: BorderStyle.NONE,
size: 0,
color: "ffffff",
},
},
children: [new Paragraph("Should have no border")],
}),
new TableCell({
borders: {
top: {
style: BorderStyle.NONE,
size: 0,
color: "ffffff",
},
bottom: {
style: BorderStyle.NONE,
size: 0,
color: "ffffff",
},
left: {
style: BorderStyle.NONE,
size: 0,
color: "ffffff",
},
right: {
style: BorderStyle.NONE,
size: 0,
color: "ffffff",
},
},
children: [new Paragraph("Should have no border")],
}),
],
}),
],
}),
],
},
],

View File

@ -21,7 +21,6 @@ const doc = new Document({
new Paragraph({
children: [
new ImageRun({
type: "jpg",
data: fs.readFileSync("./demo/images/image1.jpeg"),
transformation: {
width: 100,
@ -38,7 +37,6 @@ const doc = new Document({
new Paragraph({
children: [
new ImageRun({
type: "png",
data: fs.readFileSync("./demo/images/dog.png").toString("base64"),
transformation: {
width: 100,
@ -55,7 +53,6 @@ const doc = new Document({
new Paragraph({
children: [
new ImageRun({
type: "jpg",
data: fs.readFileSync("./demo/images/cat.jpg"),
transformation: {
width: 100,
@ -76,7 +73,6 @@ const doc = new Document({
new Paragraph({
children: [
new ImageRun({
type: "bmp",
data: fs.readFileSync("./demo/images/parrots.bmp"),
transformation: {
width: 150,
@ -92,7 +88,6 @@ const doc = new Document({
new Paragraph({
children: [
new ImageRun({
type: "gif",
data: fs.readFileSync("./demo/images/pizza.gif"),
transformation: {
width: 200,
@ -108,7 +103,6 @@ const doc = new Document({
new Paragraph({
children: [
new ImageRun({
type: "gif",
data: fs.readFileSync("./demo/images/pizza.gif"),
transformation: {
width: 200,
@ -130,7 +124,6 @@ const doc = new Document({
new Paragraph({
children: [
new ImageRun({
type: "jpg",
data: fs.readFileSync("./demo/images/cat.jpg"),
transformation: {
width: 200,
@ -150,22 +143,6 @@ const doc = new Document({
}),
],
}),
new Paragraph({
children: [
new ImageRun({
type: "svg",
data: fs.readFileSync("./demo/images/linux-svg.svg"),
transformation: {
width: 200,
height: 200,
},
fallback: {
type: "png",
data: fs.readFileSync("./demo/images/linux-png.png"),
},
}),
],
}),
],
},
],

View File

@ -21,8 +21,6 @@ import {
Packer,
Paragraph,
TextRun,
MathLimitLower,
MathLimitUpper,
} from "docx";
const doc = new Document({
@ -318,23 +316,6 @@ const doc = new Document({
}),
],
}),
new Paragraph({
children: [
new Math({
children: [
new MathLimitUpper({
children: [new MathRun("x")],
limit: [new MathRun("-")],
}),
new MathRun("="),
new MathLimitLower({
children: [new MathRun("lim")],
limit: [new MathRun("x→0")],
}),
],
}),
],
}),
],
},
],

View File

@ -2,7 +2,6 @@
import * as fs from "fs";
import {
AlignmentType,
BorderStyle,
Document,
FrameAnchorType,
@ -21,7 +20,6 @@ const doc = new Document({
children: [
new Paragraph({
frame: {
type: "absolute",
position: {
x: 1000,
y: 3000,
@ -32,54 +30,6 @@ const doc = new Document({
horizontal: FrameAnchorType.MARGIN,
vertical: FrameAnchorType.MARGIN,
},
},
border: {
top: {
color: "auto",
space: 1,
style: BorderStyle.SINGLE,
size: 6,
},
bottom: {
color: "auto",
space: 1,
style: BorderStyle.SINGLE,
size: 6,
},
left: {
color: "auto",
space: 1,
style: BorderStyle.SINGLE,
size: 6,
},
right: {
color: "auto",
space: 1,
style: BorderStyle.SINGLE,
size: 6,
},
},
children: [
new TextRun("Hello World"),
new TextRun({
text: "Foo Bar",
bold: true,
}),
new TextRun({
children: [new Tab(), "Github is the best"],
bold: true,
}),
],
}),
new Paragraph({
frame: {
type: "alignment",
width: 4000,
height: 1000,
anchor: {
horizontal: FrameAnchorType.MARGIN,
vertical: FrameAnchorType.MARGIN,
},
alignment: {
x: HorizontalPositionAlign.CENTER,
y: VerticalPositionAlign.TOP,
@ -123,59 +73,6 @@ const doc = new Document({
}),
],
}),
new Paragraph({
frame: {
type: "alignment",
width: 4000,
height: 1000,
anchor: {
horizontal: FrameAnchorType.MARGIN,
vertical: FrameAnchorType.MARGIN,
},
alignment: {
x: HorizontalPositionAlign.CENTER,
y: VerticalPositionAlign.BOTTOM,
},
},
border: {
top: {
color: "auto",
space: 1,
style: BorderStyle.SINGLE,
size: 6,
},
bottom: {
color: "auto",
space: 1,
style: BorderStyle.SINGLE,
size: 6,
},
left: {
color: "auto",
space: 1,
style: BorderStyle.SINGLE,
size: 6,
},
right: {
color: "auto",
space: 1,
style: BorderStyle.SINGLE,
size: 6,
},
},
alignment: AlignmentType.RIGHT,
children: [
new TextRun("Hello World"),
new TextRun({
text: "Foo Bar",
bold: true,
}),
new TextRun({
children: [new Tab(), "Github is the best"],
bold: true,
}),
],
}),
],
},
],

View File

@ -16,9 +16,7 @@ import {
VerticalAlign,
} from "docx";
patchDocument({
outputType: "nodebuffer",
data: fs.readFileSync("demo/assets/simple-template.docx"),
patchDocument(fs.readFileSync("demo/assets/simple-template.docx"), {
patches: {
name: {
type: PatchType.PARAGRAPH,
@ -58,11 +56,7 @@ patchDocument({
],
link: "https://www.google.co.uk",
}),
new ImageRun({
type: "png",
data: fs.readFileSync("./demo/images/dog.png"),
transformation: { width: 100, height: 100 },
}),
new ImageRun({ data: fs.readFileSync("./demo/images/dog.png"), transformation: { width: 100, height: 100 } }),
],
}),
],
@ -88,13 +82,7 @@ patchDocument({
},
image_test: {
type: PatchType.PARAGRAPH,
children: [
new ImageRun({
type: "jpg",
data: fs.readFileSync("./demo/images/image1.jpeg"),
transformation: { width: 100, height: 100 },
}),
],
children: [new ImageRun({ data: fs.readFileSync("./demo/images/image1.jpeg"), transformation: { width: 100, height: 100 } })],
},
table: {
type: PatchType.DOCUMENT,

View File

@ -3,9 +3,7 @@
import * as fs from "fs";
import { patchDocument, PatchType, TextRun } from "docx";
patchDocument({
outputType: "nodebuffer",
data: fs.readFileSync("demo/assets/simple-template-2.docx"),
patchDocument(fs.readFileSync("demo/assets/simple-template-2.docx"), {
patches: {
name: {
type: PatchType.PARAGRAPH,

View File

@ -24,9 +24,7 @@ const patches = getPatches({
paragraph_replace: "Lorem ipsum paragraph",
});
patchDocument({
outputType: "nodebuffer",
data: fs.readFileSync("demo/assets/simple-template.docx"),
patchDocument(fs.readFileSync("demo/assets/simple-template.docx"), {
patches,
}).then((doc) => {
fs.writeFileSync("My Document.docx", doc);

View File

@ -22,11 +22,8 @@ const patches = getPatches({
"first-name": "John",
});
patchDocument({
outputType: "nodebuffer",
data: fs.readFileSync("demo/assets/simple-template-3.docx"),
patchDocument(fs.readFileSync("demo/assets/simple-template-3.docx"), {
patches,
keepOriginalStyles: true,
}).then((doc) => {
fs.writeFileSync("My Document.docx", doc);
});

View File

@ -1,40 +0,0 @@
// Simple example to add text to a document
import * as fs from "fs";
import { CharacterSet, Document, Packer, Paragraph, Tab, TextRun } from "docx";
const font = fs.readFileSync("./demo/assets/Pacifico.ttf");
const doc = new Document({
sections: [
{
properties: {},
children: [
new Paragraph({
run: {
font: "Pacifico",
},
children: [
new TextRun("Hello World"),
new TextRun({
text: "Foo Bar",
bold: true,
size: 40,
font: "Pacifico",
}),
new TextRun({
children: [new Tab(), "Github is the best"],
bold: true,
font: "Pacifico",
}),
],
}),
],
},
],
fonts: [{ name: "Pacifico", data: font, characterSet: CharacterSet.ANSI }],
});
Packer.toBuffer(doc).then((buffer) => {
fs.writeFileSync("My Document.docx", buffer);
});

View File

@ -1,44 +0,0 @@
// Simple example to add text to a document
import * as fs from "fs";
import { Document, Packer, Paragraph, Tab, TextRun } from "docx";
const font = fs.readFileSync("./demo/assets/Pacifico.ttf");
const doc = new Document({
styles: {
default: {
document: {
run: {
font: "Pacifico",
},
},
},
},
sections: [
{
properties: {},
children: [
new Paragraph({
children: [
new TextRun("Hello World"),
new TextRun({
text: "Foo Bar",
bold: true,
size: 40,
}),
new TextRun({
children: [new Tab(), "Github is the best"],
bold: true,
}),
],
}),
],
},
],
fonts: [{ name: "Pacifico", data: font, characterSet: "00" }],
});
Packer.toBuffer(doc).then((buffer) => {
fs.writeFileSync("My Document.docx", buffer);
});

View File

@ -1,72 +0,0 @@
// Patch a document with patches
import * as fs from "fs";
import { patchDocument, PatchType, TextRun } from "docx";
patchDocument({
outputType: "nodebuffer",
data: fs.readFileSync("demo/assets/field-trip.docx"),
patches: {
todays_date: {
type: PatchType.PARAGRAPH,
children: [new TextRun({ text: new Date().toLocaleDateString() })],
},
school_name: {
type: PatchType.PARAGRAPH,
children: [new TextRun({ text: "test" })],
},
address: {
type: PatchType.PARAGRAPH,
children: [new TextRun({ text: "blah blah" })],
},
city: {
type: PatchType.PARAGRAPH,
children: [new TextRun({ text: "test" })],
},
state: {
type: PatchType.PARAGRAPH,
children: [new TextRun({ text: "test" })],
},
zip: {
type: PatchType.PARAGRAPH,
children: [new TextRun({ text: "test" })],
},
phone: {
type: PatchType.PARAGRAPH,
children: [new TextRun({ text: "test" })],
},
first_name: {
type: PatchType.PARAGRAPH,
children: [new TextRun({ text: "test" })],
},
last_name: {
type: PatchType.PARAGRAPH,
children: [new TextRun({ text: "test" })],
},
email_address: {
type: PatchType.PARAGRAPH,
children: [new TextRun({ text: "test" })],
},
ft_dates: {
type: PatchType.PARAGRAPH,
children: [new TextRun({ text: "test" })],
},
grade: {
type: PatchType.PARAGRAPH,
children: [new TextRun({ text: "test" })],
},
},
}).then((doc) => {
fs.writeFileSync("My Document.docx", doc);
});

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

View File

@ -1,183 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="500pt" height="600pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://web.resource.org/cc/">
<defs>
<linearGradient id="linearGradient172">
<stop style="stop-color:#3f2600;stop-opacity:0.6;" offset="0" id="stop173" />
<stop style="stop-color:#3f2600;stop-opacity:0;" offset="1" id="stop174" />
</linearGradient>
<linearGradient id="linearGradient167">
<stop style="stop-color:#ffffff;stop-opacity:0.65;" offset="0" id="stop168" />
<stop style="stop-color:#ffffff;stop-opacity:0;" offset="1" id="stop169" />
</linearGradient>
<linearGradient id="linearGradient162">
<stop style="stop-color:#ffa63f;stop-opacity:1;" offset="0" id="stop163" />
<stop style="stop-color:#ffff00;stop-opacity:1;" offset="1" id="stop164" />
</linearGradient>
<linearGradient id="linearGradient153">
<stop style="stop-color:#ffeed7;stop-opacity:1;" offset="0" id="stop154" />
<stop style="stop-color:#bdbfc2;stop-opacity:1;" offset="1" id="stop155" /></linearGradient>
<linearGradient id="linearGradient138">
<stop style="stop-color:#ffffff;stop-opacity:0.8;" offset="0" id="stop139" />
<stop style="stop-color:#ffffff;stop-opacity:0;" offset="1" id="stop140" />
</linearGradient>
<linearGradient xlink:href="#linearGradient138" id="linearGradient141" x1="0.47424799" y1="0.020191999" x2="0.417539" y2="0.90125799" gradientUnits="objectBoundingBox" />
<linearGradient xlink:href="#linearGradient167" id="linearGradient142" x1="0.55880702" y1="0.031192999" x2="0.553922" y2="0.94531101" gradientUnits="objectBoundingBox" />
<linearGradient xlink:href="#linearGradient167" id="linearGradient143" x1="0.46557701" y1="0.028819799" x2="0.41365999" y2="0.93366498" gradientUnits="objectBoundingBox"/>
<linearGradient xlink:href="#linearGradient167" id="linearGradient144" x1="0.70346397" y1="0.059404202" x2="0.64553201" y2="0.94063401" gradientUnits="objectBoundingBox" />
<linearGradient xlink:href="#linearGradient167" id="linearGradient145" x1="0.46741399" y1="-0.036155298" x2="0.86741799" y2="0.75857902" gradientUnits="objectBoundingBox" />
<linearGradient xlink:href="#linearGradient167" id="linearGradient146" x1="0.57152498" y1="0.023441499" x2="0.57143003" y2="0.71875" gradientUnits="objectBoundingBox" />
<linearGradient xlink:href="#linearGradient167" id="linearGradient147" x1="0.5" y1="0.0234362" x2="0.5" y2="0.8125" gradientUnits="objectBoundingBox" />
<linearGradient xlink:href="#linearGradient167" id="linearGradient148" x1="0.50799799" y1="0.37435901" x2="0.51599997" y2="0.92820501" gradientUnits="objectBoundingBox" />
<linearGradient xlink:href="#linearGradient138" id="linearGradient149" x1="0.5" y1="0.131707" x2="0.50400001" y2="0.94634098" gradientUnits="objectBoundingBox" />
<linearGradient xlink:href="#linearGradient167" id="linearGradient150" x1="-0.30509499" y1="0.099496603" x2="0.156323" y2="0.94191301" gradientUnits="objectBoundingBox" gradientTransform="matrix(-0.928523,0.283938,0.435332,0.943857,-1.91327e-7,5.49908e-8)" />
<linearGradient xlink:href="#linearGradient167" id="linearGradient151" x1="0.433979" y1="0.022184599" x2="0.487055" y2="1.02569" gradientUnits="objectBoundingBox" />
<linearGradient xlink:href="#linearGradient153" id="linearGradient152" x1="0.5" y1="0.89842999" x2="0.5" y2="0.40625" gradientUnits="objectBoundingBox" spreadMethod="reflect" />
<linearGradient xlink:href="#linearGradient153" id="linearGradient156" x1="0.43568701" y1="0.98882002" x2="0.453989" y2="0.23093501" gradientUnits="objectBoundingBox" />
<linearGradient xlink:href="#linearGradient153" id="linearGradient157" x1="0.49180499" y1="1.15284" x2="0.49482101" y2="0.41252401" gradientUnits="objectBoundingBox" />
<linearGradient xlink:href="#linearGradient153" id="linearGradient158" x1="0.51730198" y1="0.85418499" x2="0.49843901" y2="0.136172" gradientUnits="objectBoundingBox" />
<linearGradient xlink:href="#linearGradient153" id="linearGradient159" x1="0.46201" y1="0.87917101" x2="0.49215299" y2="0.096282303" gradientUnits="objectBoundingBox" />
<linearGradient xlink:href="#linearGradient162" id="linearGradient161" x1="0.50086302" y1="0.34872901" x2="0.41209599" y2="0.98558098" gradientUnits="objectBoundingBox" />
<linearGradient xlink:href="#linearGradient162" id="linearGradient165" x1="0.60399801" y1="0.51020199" x2="0.46399999" y2="0.98367399" gradientUnits="objectBoundingBox" />
<linearGradient xlink:href="#linearGradient162" id="linearGradient166" x1="0.50000501" y1="0.191616" x2="0.50800002" y2="0.97005898" gradientUnits="objectBoundingBox" />
<radialGradient xlink:href="#linearGradient172" id="radialGradient171" cx="0.5" cy="0.5" fx="0.5" fy="0.5" r="0.5" gradientUnits="objectBoundingBox" />
<radialGradient xlink:href="#linearGradient172" id="radialGradient176" />
<linearGradient xlink:href="#linearGradient153" id="linearGradient178" x1="0.94027299" y1="1.2934099" x2="0.19452" y2="-0.675295" gradientUnits="objectBoundingBox" />
<radialGradient xlink:href="#linearGradient172" id="radialGradient1399" gradientTransform="scale(1.045233,0.956725)" cx="446.77762" cy="1219.4125" fx="446.77762" fy="1219.4125" r="195.07191" gradientUnits="userSpaceOnUse" />
<linearGradient xlink:href="#linearGradient153" id="linearGradient1401" gradientUnits="userSpaceOnUse" x1="400.57785" y1="369.53015" x2="400.84448" y2="304.07886" gradientTransform="scale(0.575262,1.738339)" />
<linearGradient xlink:href="#linearGradient138" id="linearGradient1403" gradientUnits="userSpaceOnUse" x1="303.01761" y1="237.93179" x2="297.0856" y2="330.09561" gradientTransform="scale(1.116071,0.896001)" />
<linearGradient xlink:href="#linearGradient153" id="linearGradient1405" gradientUnits="userSpaceOnUse" gradientTransform="scale(0.816497,1.224744)" x1="378.93771" y1="278.60202" x2="380.27319" y2="243.91606" />
<linearGradient xlink:href="#linearGradient153" id="linearGradient1407" gradientUnits="userSpaceOnUse" x1="381.38742" y1="277.495" x2="380.5517" y2="245.68338" gradientTransform="scale(0.816497,1.224744)" />
<linearGradient xlink:href="#linearGradient167" id="linearGradient1409" gradientUnits="userSpaceOnUse" gradientTransform="scale(0.816497,1.224744)" x1="379.09573" y1="240.92712" x2="376.79556" y2="281.01636" />
<linearGradient xlink:href="#linearGradient167" id="linearGradient1411" gradientUnits="userSpaceOnUse" x1="389.63535" y1="242.28218" x2="387.06866" y2="281.32513" gradientTransform="scale(0.816497,1.224744)" />
<linearGradient xlink:href="#linearGradient153" id="linearGradient1413" gradientUnits="userSpaceOnUse" spreadMethod="reflect" x1="437.57941" y1="528.87177" x2="437.57941" y2="394.10361" gradientTransform="scale(0.812855,1.230232)" />
<linearGradient xlink:href="#linearGradient153" id="linearGradient1415" gradientUnits="userSpaceOnUse" x1="375.17325" y1="419.78485" x2="377.48541" y2="324.03815" gradientTransform="scale(0.649784,1.538974)" />
<linearGradient xlink:href="#linearGradient138" id="linearGradient1417" gradientUnits="userSpaceOnUse" x1="320.75104" y1="498.17776" x2="321.32224" y2="614.50439" gradientTransform="scale(1.074798,0.930408)" />
<linearGradient xlink:href="#linearGradient167" id="linearGradient1419" gradientUnits="userSpaceOnUse" x1="322.48257" y1="435.26761" x2="323.2514" y2="488.48251" gradientTransform="scale(1.077001,0.928504)" />
<linearGradient xlink:href="#linearGradient167" id="linearGradient1421" gradientUnits="userSpaceOnUse" x1="411.2215" y1="242.94365" x2="411.2215" y2="331.44858" gradientTransform="scale(0.571707,1.749147)" />
<linearGradient xlink:href="#linearGradient167" id="linearGradient1423" gradientUnits="userSpaceOnUse" x1="867.34546" y1="234.73897" x2="867.33453" y2="314.83911" gradientTransform="scale(0.572667,1.746214)" />
<linearGradient xlink:href="#linearGradient162" id="linearGradient1425" gradientUnits="userSpaceOnUse" x1="236.25362" y1="657.11133" x2="212.5099" y2="737.41229" gradientTransform="scale(1.011514,0.988617)" />
<linearGradient xlink:href="#linearGradient153" id="linearGradient1427" gradientUnits="userSpaceOnUse" x1="381.56607" y1="655.73102" x2="279.64313" y2="386.66583" gradientTransform="scale(1.065499,0.938527)" />
<linearGradient xlink:href="#linearGradient162" id="linearGradient1429" gradientUnits="userSpaceOnUse" x1="218.11714" y1="630.30475" x2="203.12654" y2="737.8537" gradientTransform="scale(1.009851,0.990245)" />
<linearGradient xlink:href="#linearGradient167" id="linearGradient1431" gradientUnits="userSpaceOnUse" gradientTransform="scale(1.007724,0.992335)" x1="117.88966" y1="587.23602" x2="182.24524" y2="704.73077" />
<linearGradient xlink:href="#linearGradient167" id="linearGradient1433" gradientUnits="userSpaceOnUse" x1="223.10072" y1="570.41809" x2="230.53499" y2="710.97723" gradientTransform="scale(0.999504,1.000496)" />
<linearGradient xlink:href="#linearGradient167" id="linearGradient1435" gradientUnits="userSpaceOnUse" x1="316.93988" y1="474.01779" x2="371.60889" y2="582.63507" gradientTransform="scale(1.065499,0.938527)" />
<linearGradient xlink:href="#linearGradient162" id="linearGradient1437" gradientUnits="userSpaceOnUse" x1="284.68652" y1="410.46326" x2="285.45923" y2="485.69934" gradientTransform="scale(1.218684,0.820557)" />
<linearGradient xlink:href="#linearGradient167" id="linearGradient1439" gradientUnits="userSpaceOnUse" x1="288.82358" y1="398.85422" x2="288.37628" y2="482.55939" gradientTransform="scale(1.221941,0.81837)" />
</defs>
<g id="g1369" transform="translate(-310.7524,-64.25268)">
<path transform="matrix(1.4177,0,0,0.414745,-38.7944,222.194)" d="M 670.88202 1166.6423 A 203.89551 186.63016 0 1 1 263.091,1166.6423 A 203.89551 186.63016 0 1 1 670.88202 1166.6423 z" id="path175" style="fill:url(#radialGradient1399);stroke:none;stroke-width:1pt;stroke-linecap:butt;stroke-linejoin:miter" />
<path transform="matrix(1.25,0,0,1.25,185.454,-167.505)" id="path106" d="M 223.627,632.24 C 201.239,600.017 196.873,495.256 249.114,430.81 C 275,399.892 281.604,378.345 283.645,349.417 C 285.034,316.438 260.32,217.975 353.528,210.473 C 447.934,202.941 442.864,296.133 442.321,345.448 C 441.87,387.088 472.895,410.689 494.117,443.143 C 533.396,502.773 530.074,605.443 486.718,661.015 C 431.801,730.583 384.765,700.413 353.528,702.945 C 295.035,706.147 293.101,737.336 223.627,632.24 z " style="fill:#000000;stroke:none;stroke-width:1.25;" />
<path transform="matrix(-1.67739,-2.24516e-2,-2.11236e-2,1.4709,1173.58,-293.017)" id="path113" d="M 246.571,470.864 C 234.332,483.36 202.175,539.956 251.44,576.224 C 268.809,588.857 235.063,635.719 219.435,612.532 C 191.865,570.914 210.604,505.591 227.75,482.344 C 239.402,465.857 256.98,459.668 246.571,470.864 z " style="fill:url(#linearGradient1401);stroke:none;stroke-width:0.99464899;" />
<path transform="matrix(-1.67755,0,0,1.52374,1174.62,-318.082)" id="path111" d="M 256.513,459.837 C 236.598,477.554 200.337,539.928 253.225,580.443 C 270.595,593.075 237.832,632.906 219.435,612.532 C 155.472,541.712 221.104,460.278 243.697,432.282 C 263.889,407.935 281.775,438.034 256.513,459.837 z " style="fill:#000000;stroke:#000000;stroke-width:0.97729802;" />
<path transform="matrix(1.26626,-7.13667e-2,-4.59795e-2,1.19574,202.143,-125.761)" d="M 399.56879 258.15753 A 58.37323 46.863022 0 1 1 282.82233,258.15753 A 58.37323 46.863022 0 1 1 399.56879 258.15753 z" id="path114" style="fill:url(#linearGradient1403);stroke:none;stroke-width:1.26498997;" />
<path transform="matrix(1.30445,-7.55326e-2,7.71251e-2,1.34257,144.757,-177.617)" d="M 328.86324 320.64151 A 18.087479 27.131195 0 1 1 292.68828,320.64151 A 18.087479 27.131195 0 1 1 328.86324 320.64151 z" id="path115" style="fill:url(#linearGradient1405);stroke:none;stroke-width:1.17873001;" />
<path transform="matrix(-1.81082,4.95107e-2,3.17324e-2,1.55333,1207.46,-284.777)" d="M 328.86324 320.64151 A 18.087479 27.131195 0 1 1 292.68828,320.64151 A 18.087479 27.131195 0 1 1 328.86324 320.64151 z" id="path116" style="fill:url(#linearGradient1407);stroke:none;stroke-width:0.93138498;" />
<path transform="matrix(-0.823196,-1.76123e-3,-1.82321e-2,0.852662,913.674,-37.9902)" d="M 328.86324 320.64151 A 18.087479 27.131195 0 1 1 292.68828,320.64151 A 18.087479 27.131195 0 1 1 328.86324 320.64151 z" id="path117" style="fill:#000000;stroke:none;stroke-width:1.86495996;" />
<path transform="matrix(0.59438,-7.22959e-2,6.88176e-2,0.705838,367.448,32.4186)" d="M 328.86324 320.64151 A 18.087479 27.131195 0 1 1 292.68828,320.64151 A 18.087479 27.131195 0 1 1 328.86324 320.64151 z" id="path118" style="fill:#000000;stroke:none;stroke-width:2.39814997;" />
<path transform="matrix(-0.480323,-3.6454e-2,-4.67935e-2,0.475606,813.496,87.0124)" d="M 328.86324 320.64151 A 18.087479 27.131195 0 1 1 292.68828,320.64151 A 18.087479 27.131195 0 1 1 328.86324 320.64151 z" id="path121" style="fill:url(#linearGradient1409);stroke:none;stroke-width:3.1916101;" />
<path transform="matrix(0.35691,-4.08211e-2,4.13232e-2,0.398544,449.334,114.991)" d="M 328.86324 320.64151 A 18.087479 27.131195 0 1 1 292.68828,320.64151 A 18.087479 27.131195 0 1 1 328.86324 320.64151 z" id="path122" style="fill:url(#linearGradient1411);stroke:none;stroke-width:4.12025976;" />
<path transform="matrix(1.25,0,0,1.25,185.454,-168.23)" id="path128" d="M 258.702,495.425 C 271.538,466.322 298.816,415.199 299.397,375.667 C 299.397,344.225 393.576,336.716 401.134,368.109 C 408.692,399.502 427.875,446.592 440.084,469.265 C 452.292,491.937 487.893,563.96 449.968,626.811 C 415.811,682.455 312.243,726.477 256.958,619.254 C 238.355,582.047 241.673,535.939 258.702,495.425 z " style="fill:url(#linearGradient1413);stroke:none;stroke-width:1.25;" />
<path transform="matrix(1.38936,-0.111074,0.102211,1.30214,108.413,-165.938)" id="path112" d="M 242.905,473.815 C 231.642,492.782 207.405,543.124 255.042,575.862 C 306.353,610.682 301.515,672.924 239.435,637.817 C 182.658,606.028 216.59,500.039 234.925,475.551 C 247.032,458.337 264.822,437.52 242.905,473.815 z " style="fill:url(#linearGradient1415);stroke:none;stroke-width:1.15804005;" />
<path transform="matrix(1.25,0,0,1.25,185.454,-167.505)" id="path109" d="M 256.513,449.72 C 239.048,478.228 197.136,545.533 253.225,580.443 C 328.794,626.798 307.398,673.154 238.426,631.417 C 141.317,573.153 226.601,455.801 265.557,411.079 C 310.001,360.879 274.111,420.166 256.513,449.72 z " style="fill:#000000;stroke:#000000;stroke-width:1.25;" />
<path id="path125" d="M 421.481,504.727 C 421.481,537.139 392.209,579.243 341.953,578.865 C 290.125,579.32 268.004,537.139 268.004,504.727 C 268.004,472.315 302.383,446.01 344.743,446.01 C 387.102,446.01 421.481,472.315 421.481,504.727 z " style="font-size:12px;fill:url(#linearGradient1417);stroke:none;stroke-width:1.23705006;stroke-dasharray:none" transform="matrix(1.30209,0,0,1.22525,170.042,-153.557)" />
<path id="path127" d="M 398.227,412.292 C 397.615,450.864 375.047,459.963 346.487,459.963 C 317.926,459.963 297.195,454.269 294.746,412.292 C 294.746,385.978 317.926,370.75 346.487,370.75 C 375.047,370.75 398.227,385.978 398.227,412.292 z " style="font-size:12px;fill:url(#linearGradient1419);stroke:none;stroke-width:1.38846004;stroke-dasharray:none" transform="matrix(1.1868,0,0,1.06708,210.623,-100.078)" />
<path transform="matrix(1.25,0,0,1.25,185.454,-167.505)" id="path129" d="M 234.285,456.475 C 252.001,429.479 289.3,388.111 241.262,462.288 C 202.311,523.331 226.859,562.561 239.518,573.327 C 276.045,605.889 274.484,627.676 245.913,610.533 C 184.288,573.907 197.078,512.285 234.285,456.475 z " style="fill:url(#linearGradient1421);stroke:none;stroke-width:1.25;" />
<path transform="matrix(1.25,0,0,1.25,185.454,-167.505)" id="path131" d="M 490.662,467.52 C 475.343,435.819 426.528,355.618 492.988,448.917 C 553.449,533.214 511.01,591.93 503.452,597.744 C 495.895,603.557 470.315,615.184 477.873,594.837 C 485.43,574.49 523.107,535.864 490.662,467.52 z " style="fill:url(#linearGradient1423);stroke:none;stroke-width:1.25;" />
<path transform="matrix(1.25,0,0,1.25,185.454,-167.505)" id="path132" d="M 220.915,716.921 C 180.473,695.505 121.663,721.045 143.013,662.855 C 147.289,649.617 136.638,629.847 143.594,616.929 C 151.733,601.231 169.174,604.72 179.639,594.255 C 189.957,583.364 196.498,564.606 215.683,567.513 C 234.867,570.42 247.628,593.974 261.027,622.742 C 270.91,643.38 305.968,672.406 303.677,695.5 C 300.981,731 260.65,737.69 220.915,716.921 z " style="fill:url(#linearGradient1425);stroke:#e68c3f;stroke-width:6.25;" />
<path id="path177" d="M 415.072,495.764 C 412.065,520.67 379.259,572.391 345.554,577.298 C 311.294,582.634 279.122,543.238 271.407,506.184 C 261.518,464.978 293.994,448.584 343.345,449.557 C 396.646,451.211 417.466,463.448 415.072,495.764 z " style="font-size:12px;fill:url(#linearGradient1427);stroke:none;stroke-width:2.85509992;stroke-dasharray:none" transform="matrix(0.598206,0.268584,-0.239623,0.617213,700.568,140.464)" />
<path transform="matrix(-1.1685,0.423145,0.475283,1.16478,728.343,-213.821)" id="path133" d="M 220.274,718.402 C 178.947,694.812 120.38,724.007 143.013,662.855 C 147.749,649.787 136.417,629.303 143.373,616.385 C 151.512,600.687 169.174,604.72 179.639,594.255 C 189.957,583.364 198.466,566.387 217.651,569.294 C 236.835,572.201 247.628,593.974 261.027,622.742 C 270.91,643.38 304.442,671.713 302.151,694.807 C 299.455,730.307 259.427,740.278 220.274,718.402 z " style="fill:url(#linearGradient1429);stroke:#e68c3f;stroke-width:6.25067997;" />
<path transform="matrix(-0.945096,0.343745,0.424076,0.956058,714.328,-64.342)" id="path134" d="M 216.482,675.68 C 129.951,618.177 169.174,604.72 179.639,594.255 C 189.957,583.364 198.466,566.387 217.651,569.294 C 236.835,572.201 247.628,593.974 261.027,622.742 C 270.91,643.38 304.087,671.66 302.151,694.807 C 299.535,721.917 253.961,700.294 216.482,675.68 z " style="fill:url(#linearGradient1431);stroke:none;stroke-width:1.52532005;" />
<path transform="matrix(1.00431,-5.2286e-2,-1.74e-2,1.04575,244.191,-28.4653)" id="path135" d="M 216.506,677.071 C 129.975,619.568 169.709,603.501 182.56,595.791 C 197.959,585.849 197.718,564.96 216.903,567.867 C 236.087,570.774 247.628,593.974 261.027,622.742 C 270.91,643.38 304.087,671.66 302.151,694.807 C 299.535,721.917 253.985,701.685 216.506,677.071 z " style="fill:url(#linearGradient1433);stroke:none;stroke-width:1.52532005;" />
<path id="path136" d="M 415.072,495.764 C 412.065,520.67 379.259,572.391 345.554,577.298 C 311.294,582.634 279.122,543.238 271.407,506.184 C 261.518,464.978 293.994,448.584 343.345,449.557 C 396.646,451.211 417.466,463.448 415.072,495.764 z " style="font-size:12px;fill:#000000;stroke:none;stroke-width:2.85509992;" transform="matrix(0.515584,0.215259,-0.206526,0.49467,713.3,222.559)" />
<path id="path137" d="M 415.072,495.764 C 412.065,520.67 379.259,572.391 345.554,577.298 C 311.294,582.634 279.122,543.238 271.407,506.184 C 261.518,464.978 293.994,448.584 343.345,449.557 C 396.646,451.211 417.466,463.448 415.072,495.764 z " style="font-size:12px;fill:url(#linearGradient1435);stroke:none;stroke-width:2.85509992;" transform="matrix(0.351231,0.149463,-0.128856,0.343469,724.522,318.291)" />
<path transform="matrix(1.25,0,0,1.25,185.454,-167.505)" id="path119" d="M 309.954,338.729 C 317.101,331.959 334.765,311.663 367.915,332.974 C 374.077,336.984 379.077,337.351 390.936,342.429 C 414.662,352.178 403.318,375.688 378.192,383.537 C 367.434,387.026 357.656,400.093 338.063,398.976 C 321.329,397.999 316.944,387.102 306.665,381.07 C 288.396,370.759 285.7,356.816 295.565,349.417 C 305.431,342.018 309.29,339.358 309.954,338.729 z " style="fill:url(#linearGradient1437);stroke:#e68c3f;stroke-width:3.75;" />
<path transform="matrix(1.25,0,0,1.25,185.454,-167.505)" id="path120" d="M 391.251,357.645 C 381.368,358.226 359.858,379.736 337.185,379.736 C 314.512,379.736 301.141,358.807 297.653,358.807" style="fill:none;stroke:#e68c3f;stroke-width:2.5;" />
<path transform="matrix(0.627885,0,0,0.595666,392.366,51.8173)" id="path123" d="M 309.954,338.729 C 317.101,331.959 339.645,313.381 369.542,332.401 C 375.841,336.167 382.346,340.266 392.02,345.865 C 411.182,357.613 401.691,374.543 378.734,385.255 C 368.316,389.75 351.141,399.67 338.063,398.976 C 323.53,397.568 314.128,387.577 304.496,381.07 C 286.826,368.767 287.899,358.833 296.107,350.562 C 302.312,344.883 309.29,339.358 309.954,338.729 z " style="fill:url(#linearGradient1439);stroke:none;" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 21 KiB

View File

@ -1,10 +0,0 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"rootDir": "./",
"paths": {
"docx": ["../build"]
}
},
"include": ["../demo"]
}

View File

@ -4,7 +4,7 @@
- Simple, declarative API
- 80+ usage examples
- Battle tested, mature, 100% coverage (yes, every line is tested)
- Battle tested, mature, 99.9%+ coverage
[GitHub](https://github.com/dolanmiu/docx)
[Get Started](#Welcome)

View File

@ -22,7 +22,7 @@ const doc = new Document({
}
})
],
}]
}];
});
```

View File

@ -6,7 +6,6 @@ To create a `floating` image on top of text:
```ts
const image = new ImageRun({
type: 'gif',
data: fs.readFileSync("./demo/images/pizza.gif"),
transformation: {
width: 200,
@ -27,7 +26,6 @@ By default with no arguments, its an `inline` image:
```ts
const image = new ImageRun({
type: 'gif',
data: fs.readFileSync("./demo/images/pizza.gif"),
transformation: {
width: 100,
@ -61,7 +59,6 @@ const doc = new Document({
new Paragraph({
children: [
new ImageRun({
type: [IMAGE_TYPE],
data: [IMAGE_BUFFER],
transformation: {
width: [IMAGE_SIZE],
@ -100,7 +97,6 @@ To change the position the image to be on top of the text, simply add the `float
```ts
const image = new ImageRun({
type: 'png',
data: buffer,
transformation: {
width: 903,
@ -119,7 +115,6 @@ const image = new ImageRun({
```ts
const image = new ImageRun({
type: 'png',
data: buffer,
transformation: {
width: 903,
@ -185,7 +180,6 @@ For example:
```ts
const image = new ImageRun({
type: 'gif',
data: fs.readFileSync("./demo/images/pizza.gif"),
transformation: {
width: 200,
@ -234,7 +228,6 @@ For example:
```ts
const image = new ImageRun({
type: 'gif',
data: fs.readFileSync("./demo/images/pizza.gif"),
transformation: {
width: 200,
@ -265,7 +258,6 @@ Specifies common non-visual DrawingML properties. A name, title and description
```ts
const image = new ImageRun({
type: 'gif',
data: fs.readFileSync("./demo/images/pizza.gif"),
altText: {
title: "This is an ultimate title",

View File

@ -263,23 +263,3 @@ new MathAngledBrackets({
],
}),
```
### Limit
#### Limit Upper
```ts
new MathLimitUpper({
children: [new MathRun("x")],
limit: [new MathRun("-")],
}),
```
#### Limit Lower
```ts
new MathLimitLower({
children: [new MathRun("lim")],
limit: [new MathRun("x→0")],
}),
```

View File

@ -130,62 +130,6 @@ new Paragraph({
}),
```
## Disabling numbering inherited from paragraph style
If the numbering is set on a paragraph style, you may wish to disable it for a specific paragraph:
```ts
const doc = new Document({
...
numbering: {
config: [
{
reference: "my-bullet-points",
levels: [
{
level: 0,
format: LevelFormat.BULLET,
text: "\u1F60",
alignment: AlignmentType.LEFT,
style: {
paragraph: {
indent: { left: convertInchesToTwip(0.5), hanging: convertInchesToTwip(0.25) },
},
},
},
],
},
],
},
styles: {
paragraphStyles: [
{
id: 'bullet',
name: 'Bullet',
basedOn: 'Normal',
next: 'Normal',
run: {},
paragraph: {
numbering: {
reference: 'my-bullet-points',
level: 0,
},
},
},
],
},
...
});
```
```ts
new Paragraph({
text: "No bullet points!",
style: "Bullet",
numbering: false,
}),
```
## Full Example
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/3-numbering-and-bullet-points.ts ":include")

View File

@ -35,9 +35,6 @@ interface Patch {
| type | `PatchType` | Required | `DOCUMENT`, `PARAGRAPH` |
| children | `FileChild[] or ParagraphChild[]` | Required | The contents to replace with. A `FileChild` is a `Paragraph` or `Table`, whereas a `ParagraphChild` is typical `Paragraph` children. |
The patcher also takes in a `keepOriginalStyles` boolean, which will preserve the styles of the patched text when set to true.
### How to patch existing document
1. Open your existing word document in your favorite Word Processor
@ -79,7 +76,7 @@ patchDocument(fs.readFileSync("My Document.docx"), {
],
link: "https://www.google.co.uk",
}),
new ImageRun({ type: 'png', data: fs.readFileSync("./demo/images/dog.png"), transformation: { width: 100, height: 100 } }),
new ImageRun({ data: fs.readFileSync("./demo/images/dog.png"), transformation: { width: 100, height: 100 } }),
],
}),
],

View File

@ -126,10 +126,10 @@ const doc = new Document({
next: "Normal",
quickFormat: true,
run: {
size: 26,
size: 26
bold: true,
color: "999999",
underline: {
{
type: UnderlineType.DOUBLE,
color: "FF0000",
},

View File

@ -22,7 +22,7 @@ Then add the table in the `section`
const doc = new Document({
sections: [{
children: [table],
}],
}];
});
```

8420
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "docx",
"version": "9.0.2",
"version": "8.4.0",
"description": "Easily generate .docx files with JS/TS with a nice declarative API. Works for Node and on the Browser.",
"type": "module",
"main": "build/index.umd.js",
@ -54,8 +54,7 @@
"clippy"
],
"dependencies": {
"@types/node": "^22.7.5",
"hash.js": "^1.1.7",
"@types/node": "^20.3.1",
"jszip": "^3.10.1",
"nanoid": "^5.0.4",
"xml": "^1.0.1",
@ -72,34 +71,34 @@
"@types/prompt": "^1.1.1",
"@types/unzipper": "^0.10.4",
"@types/xml": "^1.0.8",
"@typescript-eslint/eslint-plugin": "^7.0.0",
"@typescript-eslint/parser": "^8.8.1",
"@typescript-eslint/eslint-plugin": "^6.9.1",
"@typescript-eslint/parser": "^6.9.1",
"@vitest/coverage-v8": "^1.1.0",
"@vitest/ui": "^2.1.2",
"@vitest/ui": "^1.1.0",
"cspell": "^8.2.3",
"docsify-cli": "^4.3.0",
"eslint": "^8.23.0",
"eslint-plugin-functional": "^6.0.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-jsdoc": "^50.3.1",
"eslint-plugin-jsdoc": "^46.2.6",
"eslint-plugin-no-null": "^1.0.2",
"eslint-plugin-prefer-arrow": "^1.2.3",
"eslint-plugin-unicorn": "^56.0.0",
"eslint-plugin-unicorn": "^50.0.1",
"execa": "^8.0.1",
"glob": "^11.0.0",
"glob": "^10.2.7",
"inquirer": "^9.2.7",
"jsdom": "^25.0.1",
"jsdom": "^23.0.1",
"pre-commit": "^1.2.2",
"prettier": "^3.1.1",
"tsconfig-paths": "^4.0.0",
"tsx": "^4.7.0",
"typedoc": "^0.25.4",
"typescript": "5.3.3",
"unzipper": "^0.12.3",
"unzipper": "^0.10.11",
"vite": "^5.0.10",
"vite-plugin-dts": "^4.2.4",
"vite-plugin-node-polyfills": "^0.19.0",
"vite-tsconfig-paths": "^5.0.1",
"vite-plugin-dts": "^3.3.1",
"vite-plugin-node-polyfills": "^0.9.0",
"vite-tsconfig-paths": "^4.2.0",
"vitest": "^1.1.0"
},
"engines": {

View File

@ -12,8 +12,7 @@ describe("ImageReplacer", () => {
"test {test-image.png} test",
[
{
type: "png",
data: Buffer.from(""),
stream: Buffer.from(""),
fileName: "test-image.png",
transformation: {
pixels: {

View File

@ -36,7 +36,7 @@ describe("Compiler", () => {
const fileNames = Object.keys(zipFile.files).map((f) => zipFile.files[f].name);
expect(fileNames).is.an.instanceof(Array);
expect(fileNames).has.length(19);
expect(fileNames).has.length(17);
expect(fileNames).to.include("word/document.xml");
expect(fileNames).to.include("word/styles.xml");
expect(fileNames).to.include("docProps/core.xml");
@ -47,9 +47,7 @@ describe("Compiler", () => {
expect(fileNames).to.include("word/_rels/footnotes.xml.rels");
expect(fileNames).to.include("word/settings.xml");
expect(fileNames).to.include("word/comments.xml");
expect(fileNames).to.include("word/fontTable.xml");
expect(fileNames).to.include("word/_rels/document.xml.rels");
expect(fileNames).to.include("word/_rels/fontTable.xml.rels");
expect(fileNames).to.include("[Content_Types].xml");
expect(fileNames).to.include("_rels/.rels");
},
@ -96,7 +94,7 @@ describe("Compiler", () => {
const fileNames = Object.keys(zipFile.files).map((f) => zipFile.files[f].name);
expect(fileNames).is.an.instanceof(Array);
expect(fileNames).has.length(27);
expect(fileNames).has.length(25);
expect(fileNames).to.include("word/header1.xml");
expect(fileNames).to.include("word/_rels/header1.xml.rels");
@ -129,10 +127,12 @@ describe("Compiler", () => {
const spy = vi.spyOn(compiler["formatter"], "format");
compiler.compile(file);
expect(spy).toBeCalledTimes(15);
expect(spy).toBeCalledTimes(13);
});
it("should work with media datas", () => {
// This test is required because before, there was a case where Document was formatted twice, which was inefficient
// This also caused issues such as running prepForXml multiple times as format() was ran multiple times.
const file = new File({
sections: [
{
@ -150,25 +150,12 @@ describe("Compiler", () => {
new Paragraph({
children: [
new ImageRun({
type: "png",
data: Buffer.from("", "base64"),
transformation: {
width: 100,
height: 100,
},
}),
new ImageRun({
type: "svg",
data: Buffer.from("", "base64"),
transformation: {
width: 100,
height: 100,
},
fallback: {
type: "png",
data: Buffer.from("", "base64"),
},
}),
],
}),
],
@ -178,8 +165,7 @@ describe("Compiler", () => {
vi.spyOn(compiler["imageReplacer"], "getMediaData").mockReturnValue([
{
type: "png",
data: Buffer.from(""),
stream: Buffer.from(""),
fileName: "test",
transformation: {
pixels: {
@ -192,48 +178,9 @@ describe("Compiler", () => {
},
},
},
{
type: "svg",
data: Buffer.from(""),
fileName: "test",
transformation: {
pixels: {
x: 100,
y: 100,
},
emus: {
x: 100,
y: 100,
},
},
fallback: {
type: "png",
data: Buffer.from(""),
fileName: "test",
transformation: {
pixels: {
x: 100,
y: 100,
},
emus: {
x: 100,
y: 100,
},
},
},
},
]);
compiler.compile(file);
});
it("should work with fonts", () => {
const file = new File({
sections: [],
fonts: [{ name: "Pacifico", data: Buffer.from("") }],
});
compiler.compile(file);
});
});
});

View File

@ -2,7 +2,6 @@ import JSZip from "jszip";
import xml from "xml";
import { File } from "@file/file";
import { obfuscate } from "@file/fonts/obfuscate-ttf-to-odttf";
import { Formatter } from "../formatter";
import { ImageReplacer } from "./image-replacer";
@ -32,8 +31,6 @@ interface IXmlifyedFileMapping {
readonly FootNotesRelationships: IXmlifyedFile;
readonly Settings: IXmlifyedFile;
readonly Comments?: IXmlifyedFile;
readonly FontTable?: IXmlifyedFile;
readonly FontTableRelationships?: IXmlifyedFile;
}
export class Compiler {
@ -62,18 +59,8 @@ export class Compiler {
}
}
for (const data of file.Media.Array) {
if (data.type !== "svg") {
zip.file(`word/media/${data.fileName}`, data.data);
} else {
zip.file(`word/media/${data.fileName}`, data.data);
zip.file(`word/media/${data.fallback.fileName}`, data.fallback.data);
}
}
for (const { data: buffer, name, fontKey } of file.FontTable.fontOptionsWithKey) {
const [nameWithoutExtension] = name.split(".");
zip.file(`word/fonts/${nameWithoutExtension}.odttf`, obfuscate(buffer, fontKey));
for (const { stream, fileName } of file.Media.Array) {
zip.file(`word/media/${fileName}`, stream);
}
return zip;
@ -452,40 +439,6 @@ export class Compiler {
),
path: "word/comments.xml",
},
FontTable: {
data: xml(
this.formatter.format(file.FontTable.View, {
viewWrapper: file.Document,
file,
stack: [],
}),
{
indent: prettify,
declaration: {
standalone: "yes",
encoding: "UTF-8",
},
},
),
path: "word/fontTable.xml",
},
FontTableRelationships: {
data: (() =>
xml(
this.formatter.format(file.FontTable.Relationships, {
viewWrapper: file.Document,
file,
stack: [],
}),
{
indent: prettify,
declaration: {
encoding: "UTF-8",
},
},
))(),
path: "word/_rels/fontTable.xml.rels",
},
};
}
}

View File

@ -25,21 +25,11 @@ describe("ContentTypes", () => {
expect(tree["Types"][3]).to.deep.equal({ Default: { _attr: { ContentType: "image/jpeg", Extension: "jpg" } } });
expect(tree["Types"][4]).to.deep.equal({ Default: { _attr: { ContentType: "image/bmp", Extension: "bmp" } } });
expect(tree["Types"][5]).to.deep.equal({ Default: { _attr: { ContentType: "image/gif", Extension: "gif" } } });
expect(tree["Types"][6]).to.deep.equal({ Default: { _attr: { ContentType: "image/svg+xml", Extension: "svg" } } });
expect(tree["Types"][7]).to.deep.equal({
expect(tree["Types"][6]).to.deep.equal({
Default: { _attr: { ContentType: "application/vnd.openxmlformats-package.relationships+xml", Extension: "rels" } },
});
expect(tree["Types"][8]).to.deep.equal({ Default: { _attr: { ContentType: "application/xml", Extension: "xml" } } });
expect(tree["Types"][9]).to.deep.equal({
Default: {
_attr: {
ContentType: "application/vnd.openxmlformats-officedocument.obfuscatedFont",
Extension: "odttf",
},
},
});
expect(tree["Types"][10]).to.deep.equal({
expect(tree["Types"][7]).to.deep.equal({ Default: { _attr: { ContentType: "application/xml", Extension: "xml" } } });
expect(tree["Types"][8]).to.deep.equal({
Override: {
_attr: {
ContentType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml",
@ -47,7 +37,7 @@ describe("ContentTypes", () => {
},
},
});
expect(tree["Types"][11]).to.deep.equal({
expect(tree["Types"][9]).to.deep.equal({
Override: {
_attr: {
ContentType: "application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml",
@ -55,7 +45,7 @@ describe("ContentTypes", () => {
},
},
});
expect(tree["Types"][12]).to.deep.equal({
expect(tree["Types"][10]).to.deep.equal({
Override: {
_attr: {
ContentType: "application/vnd.openxmlformats-package.core-properties+xml",
@ -63,7 +53,7 @@ describe("ContentTypes", () => {
},
},
});
expect(tree["Types"][13]).to.deep.equal({
expect(tree["Types"][11]).to.deep.equal({
Override: {
_attr: {
ContentType: "application/vnd.openxmlformats-officedocument.custom-properties+xml",
@ -71,7 +61,7 @@ describe("ContentTypes", () => {
},
},
});
expect(tree["Types"][14]).to.deep.equal({
expect(tree["Types"][12]).to.deep.equal({
Override: {
_attr: {
ContentType: "application/vnd.openxmlformats-officedocument.extended-properties+xml",
@ -79,7 +69,7 @@ describe("ContentTypes", () => {
},
},
});
expect(tree["Types"][15]).to.deep.equal({
expect(tree["Types"][13]).to.deep.equal({
Override: {
_attr: {
ContentType: "application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml",
@ -87,7 +77,7 @@ describe("ContentTypes", () => {
},
},
});
expect(tree["Types"][16]).to.deep.equal({
expect(tree["Types"][14]).to.deep.equal({
Override: {
_attr: {
ContentType: "application/vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml",
@ -95,7 +85,7 @@ describe("ContentTypes", () => {
},
},
});
expect(tree["Types"][17]).to.deep.equal({
expect(tree["Types"][15]).to.deep.equal({
Override: {
_attr: {
ContentType: "application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml",
@ -112,7 +102,7 @@ describe("ContentTypes", () => {
contentTypes.addFooter(102);
const tree = new Formatter().format(contentTypes);
expect(tree["Types"][20]).to.deep.equal({
expect(tree["Types"][17]).to.deep.equal({
Override: {
_attr: {
ContentType: "application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml",
@ -121,7 +111,7 @@ describe("ContentTypes", () => {
},
});
expect(tree["Types"][21]).to.deep.equal({
expect(tree["Types"][18]).to.deep.equal({
Override: {
_attr: {
ContentType: "application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml",
@ -138,7 +128,7 @@ describe("ContentTypes", () => {
contentTypes.addHeader(202);
const tree = new Formatter().format(contentTypes);
expect(tree["Types"][20]).to.deep.equal({
expect(tree["Types"][17]).to.deep.equal({
Override: {
_attr: {
ContentType: "application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml",
@ -147,7 +137,7 @@ describe("ContentTypes", () => {
},
});
expect(tree["Types"][21]).to.deep.equal({
expect(tree["Types"][18]).to.deep.equal({
Override: {
_attr: {
ContentType: "application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml",

View File

@ -18,10 +18,8 @@ export class ContentTypes extends XmlComponent {
this.root.push(new Default("image/jpeg", "jpg"));
this.root.push(new Default("image/bmp", "bmp"));
this.root.push(new Default("image/gif", "gif"));
this.root.push(new Default("image/svg+xml", "svg"));
this.root.push(new Default("application/vnd.openxmlformats-package.relationships+xml", "rels"));
this.root.push(new Default("application/xml", "xml"));
this.root.push(new Default("application/vnd.openxmlformats-officedocument.obfuscatedFont", "odttf"));
this.root.push(
new Override("application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml", "/word/document.xml"),
@ -35,7 +33,6 @@ export class ContentTypes extends XmlComponent {
this.root.push(new Override("application/vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml", "/word/footnotes.xml"));
this.root.push(new Override("application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml", "/word/settings.xml"));
this.root.push(new Override("application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml", "/word/comments.xml"));
this.root.push(new Override("application/vnd.openxmlformats-officedocument.wordprocessingml.fontTable+xml", "/word/fontTable.xml"));
}
public addFooter(index: number): void {

View File

@ -1,6 +1,5 @@
import { ICommentsOptions } from "@file/paragraph/run/comment-run";
import { ICompatibilityOptions } from "@file/settings/compatibility";
import { FontOptions } from "@file/fonts/font-table";
import { StringContainer, XmlComponent } from "@file/xml-components";
import { dateTimeValue } from "@util/values";
@ -41,7 +40,6 @@ export interface IPropertiesOptions {
readonly customProperties?: readonly ICustomPropertyOptions[];
readonly evenAndOddHeaderAndFooters?: boolean;
readonly defaultTabStop?: number;
readonly fonts?: readonly FontOptions[];
}
// <xs:element name="coreProperties" type="CT_CoreProperties"/>

View File

@ -1,4 +1,3 @@
import { XmlComponent } from "./xml-components";
import { Document, IDocumentOptions } from "./document";
import { Footer } from "./footer/footer";
import { FootNotes } from "./footnotes";
@ -6,7 +5,7 @@ import { Header } from "./header/header";
import { Relationships } from "./relationships";
export interface IViewWrapper {
readonly View: Document | Footer | Header | FootNotes | XmlComponent;
readonly View: Document | Footer | Header | FootNotes;
readonly Relationships: Relationships;
}

View File

@ -11,9 +11,8 @@ import { Anchor } from "./anchor";
const createAnchor = (drawingOptions: IDrawingOptions): Anchor =>
new Anchor({
mediaData: {
type: "png",
fileName: "test.png",
data: Buffer.from(""),
stream: Buffer.from(""),
transformation: {
pixels: {
x: 0,

View File

@ -11,9 +11,8 @@ const imageBase64Data = `iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAMAAAD04JH5AAACzVBMVEU
const createDrawing = (drawingOptions?: IDrawingOptions): Drawing =>
new Drawing(
{
type: "jpg",
fileName: "test.jpg",
data: Buffer.from(imageBase64Data, "base64"),
stream: Buffer.from(imageBase64Data, "base64"),
transformation: {
pixels: {
x: 100,

View File

@ -7,12 +7,6 @@ export type EffectExtentAttributes = {
readonly left: number;
};
// <xsd:complexType name="CT_EffectExtent">
// <xsd:attribute name="l" type="a:ST_Coordinate" use="required"/>
// <xsd:attribute name="t" type="a:ST_Coordinate" use="required"/>
// <xsd:attribute name="r" type="a:ST_Coordinate" use="required"/>
// <xsd:attribute name="b" type="a:ST_Coordinate" use="required"/>
// </xsd:complexType>
export const createEffectExtent = ({ top, right, bottom, left }: EffectExtentAttributes): XmlComponent =>
new BuilderElement<EffectExtentAttributes>({
name: "wp:effectExtent",

View File

@ -1,35 +0,0 @@
import { BuilderElement, XmlComponent } from "@file/xml-components";
import { IMediaData } from "@file/media";
const createSvgBlip = (mediaData: IMediaData): XmlComponent =>
new BuilderElement({
name: "asvg:svgBlip",
attributes: {
asvg: {
key: "xmlns:asvg",
value: "http://schemas.microsoft.com/office/drawing/2016/SVG/main",
},
embed: {
key: "r:embed",
value: `rId{${mediaData.fileName}}`,
},
},
});
const createExtention = (mediaData: IMediaData): XmlComponent =>
new BuilderElement({
name: "a:ext",
attributes: {
uri: {
key: "uri",
value: "{96DAC541-7B7A-43D3-8B79-37D633B846F1}",
},
},
children: [createSvgBlip(mediaData)],
});
export const createExtentionList = (mediaData: IMediaData): XmlComponent =>
new BuilderElement({
name: "a:extLst",
children: [createExtention(mediaData)],
});

View File

@ -1,7 +1,7 @@
import { IMediaData } from "@file/media";
import { XmlComponent } from "@file/xml-components";
import { createBlip } from "./blip";
import { Blip } from "./blip";
import { SourceRectangle } from "./source-rectangle";
import { Stretch } from "./stretch";
@ -9,7 +9,7 @@ export class BlipFill extends XmlComponent {
public constructor(mediaData: IMediaData) {
super("pic:blipFill");
this.root.push(createBlip(mediaData));
this.root.push(new Blip(mediaData));
this.root.push(new SourceRectangle());
this.root.push(new Stretch());
}

View File

@ -1,24 +1,24 @@
import { IMediaData } from "@file/media";
import { BuilderElement, XmlComponent } from "@file/xml-components";
import { createExtentionList } from "./blip-extentions";
import { XmlAttributeComponent, XmlComponent } from "@file/xml-components";
type BlipAttributes = {
class BlipAttributes extends XmlAttributeComponent<{
readonly embed: string;
readonly cstate: string;
}> {
protected readonly xmlKeys = {
embed: "r:embed",
cstate: "cstate",
};
}
export const createBlip = (mediaData: IMediaData): XmlComponent =>
new BuilderElement<BlipAttributes>({
name: "a:blip",
attributes: {
embed: {
key: "r:embed",
value: `rId{${mediaData.type === "svg" ? mediaData.fallback.fileName : mediaData.fileName}}`,
},
cstate: {
key: "cstate",
value: "none",
},
},
children: mediaData.type === "svg" ? [createExtentionList(mediaData)] : [],
});
export class Blip extends XmlComponent {
public constructor(mediaData: IMediaData) {
super("a:blip");
this.root.push(
new BlipAttributes({
embed: `rId{${mediaData.fileName}}`,
cstate: "none",
}),
);
}
}

View File

@ -8,9 +8,8 @@ describe("Inline", () => {
const tree = new Formatter().format(
createInline({
mediaData: {
type: "png",
fileName: "test.png",
data: Buffer.from(""),
stream: Buffer.from(""),
transformation: {
pixels: {
x: 0,

View File

@ -436,44 +436,7 @@ describe("File", () => {
it("should work with external styles", () => {
const doc = new File({
sections: [],
externalStyles: `
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:styles xmlns:mc="first" xmlns:r="second">
<w:docDefaults>
<w:rPrDefault>
<w:rPr>
<w:rFonts w:ascii="Arial" w:eastAsiaTheme="minorHAnsi" w:hAnsi="Arial" w:cstheme="minorHAnsi"/>
<w:lang w:val="en-US" w:eastAsia="en-US" w:bidi="ar-SA"/>
</w:rPr>
</w:rPrDefault>
<w:pPrDefault>
<w:pPr>
<w:spacing w:after="160" w:line="259" w:lineRule="auto"/>
</w:pPr>
</w:pPrDefault>
</w:docDefaults>
<w:latentStyles w:defLockedState="1" w:defUIPriority="99">
</w:latentStyles>
<w:style w:type="paragraph" w:default="1" w:styleId="Normal">
<w:name w:val="Normal"/>
<w:qFormat/>
</w:style>
<w:style w:type="paragraph" w:styleId="Heading1">
<w:name w:val="heading 1"/>
<w:basedOn w:val="Normal"/>
<w:pPr>
<w:keepNext/>
<w:keepLines/>
<w:pBdr>
<w:bottom w:val="single" w:sz="4" w:space="1" w:color="auto"/>
</w:pBdr>
</w:pPr>
</w:style>
</w:styles>`,
externalStyles: "",
});
expect(doc.Styles).to.not.be.undefined;

View File

@ -17,7 +17,6 @@ import { Styles } from "./styles";
import { ExternalStylesFactory } from "./styles/external-styles-factory";
import { DefaultStylesFactory } from "./styles/factory";
import { FileChild } from "./file-child";
import { FontWrapper } from "./fonts/font-wrapper";
export interface ISectionOptions {
readonly headers?: {
@ -54,7 +53,6 @@ export class File {
private readonly appProperties: AppProperties;
private readonly styles: Styles;
private readonly comments: Comments;
private readonly fontWrapper: FontWrapper;
public constructor(options: IPropertiesOptions) {
this.coreProperties = new CoreProperties({
@ -84,7 +82,7 @@ export class File {
this.media = new Media();
if (options.externalStyles !== undefined) {
if (options.externalStyles) {
const stylesFactory = new ExternalStylesFactory();
this.styles = stylesFactory.newInstance(options.externalStyles);
} else if (options.styles) {
@ -111,8 +109,6 @@ export class File {
this.footnotesWrapper.View.createFootNote(parseFloat(key), options.footnotes[key].children);
}
}
this.fontWrapper = new FontWrapper(options.fonts ?? []);
}
private addSection({ headers = {}, footers = {}, children, properties }: ISectionOptions): void {
@ -296,8 +292,4 @@ export class File {
public get Comments(): Comments {
return this.comments;
}
public get FontTable(): FontWrapper {
return this.fontWrapper;
}
}

View File

@ -1,33 +0,0 @@
import { XmlComponent } from "@file/xml-components";
import { CharacterSet, createFont } from "./font";
export const createRegularFont = ({
name,
index,
fontKey,
characterSet,
}: {
readonly name: string;
readonly index: number;
readonly fontKey: string;
readonly characterSet?: (typeof CharacterSet)[keyof typeof CharacterSet];
}): XmlComponent =>
createFont({
name,
sig: {
usb0: "E0002AFF",
usb1: "C000247B",
usb2: "00000009",
usb3: "00000000",
csb0: "000001FF",
csb1: "00000000",
},
charset: characterSet,
family: "auto",
pitch: "variable",
embedRegular: {
fontKey,
id: `rId${index}`,
},
});

View File

@ -1,44 +0,0 @@
import { BuilderElement, XmlComponent } from "@file/xml-components";
import { createRegularFont } from "./create-regular-font";
import { FontOptionsWithKey } from "./font-wrapper";
import { CharacterSet } from "./font";
// <xsd:complexType name="CT_FontsList">
// <xsd:sequence>
// <xsd:element name="font" type="CT_Font" minOccurs="0" maxOccurs="unbounded"/>
// </xsd:sequence>
// </xsd:complexType>
export type FontOptions = {
readonly name: string;
readonly data: Buffer;
readonly characterSet?: (typeof CharacterSet)[keyof typeof CharacterSet];
};
export const createFontTable = (fonts: readonly FontOptionsWithKey[]): XmlComponent =>
// https://c-rex.net/projects/samples/ooxml/e1/Part4/OOXML_P4_DOCX_Font_topic_ID0ERNCU.html
// http://www.datypic.com/sc/ooxml/e-w_fonts.html
new BuilderElement({
name: "w:fonts",
attributes: {
mc: { key: "xmlns:mc", value: "http://schemas.openxmlformats.org/markup-compatibility/2006" },
r: { key: "xmlns:r", value: "http://schemas.openxmlformats.org/officeDocument/2006/relationships" },
w: { key: "xmlns:w", value: "http://schemas.openxmlformats.org/wordprocessingml/2006/main" },
w14: { key: "xmlns:w14", value: "http://schemas.microsoft.com/office/word/2010/wordml" },
w15: { key: "xmlns:w15", value: "http://schemas.microsoft.com/office/word/2012/wordml" },
w16cex: { key: "xmlns:w16cex", value: "http://schemas.microsoft.com/office/word/2018/wordml/cex" },
w16cid: { key: "xmlns:w16cid", value: "http://schemas.microsoft.com/office/word/2016/wordml/cid" },
w16: { key: "xmlns:w16", value: "http://schemas.microsoft.com/office/word/2018/wordml" },
w16sdtdh: { key: "xmlns:w16sdtdh", value: "http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash" },
w16se: { key: "xmlns:w16se", value: "http://schemas.microsoft.com/office/word/2015/wordml/symex" },
Ignorable: { key: "mc:Ignorable", value: "w14 w15 w16se w16cid w16 w16cex w16sdtdh" },
},
children: fonts.map((font, i) =>
createRegularFont({
name: font.name,
index: i + 1,
fontKey: font.fontKey,
}),
),
});

View File

@ -1,36 +0,0 @@
import { IViewWrapper } from "@file/document-wrapper";
import { Relationships } from "@file/relationships";
import { XmlComponent } from "@file/xml-components";
import { uniqueUuid } from "@util/convenience-functions";
import { FontOptions, createFontTable } from "./font-table";
export type FontOptionsWithKey = FontOptions & { readonly fontKey: string };
export class FontWrapper implements IViewWrapper {
private readonly fontTable: XmlComponent;
private readonly relationships: Relationships;
public readonly fontOptionsWithKey: readonly FontOptionsWithKey[] = [];
public constructor(public readonly options: readonly FontOptions[]) {
this.fontOptionsWithKey = options.map((o) => ({ ...o, fontKey: uniqueUuid() }));
this.fontTable = createFontTable(this.fontOptionsWithKey);
this.relationships = new Relationships();
for (let i = 0; i < options.length; i++) {
this.relationships.createRelationship(
i + 1,
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/font",
`fonts/${options[i].name}.odttf`,
);
}
}
public get View(): XmlComponent {
return this.fontTable;
}
public get Relationships(): Relationships {
return this.relationships;
}
}

View File

@ -1,223 +0,0 @@
import { describe, expect, it } from "vitest";
import { Formatter } from "@export/formatter";
import { createFont } from "./font";
describe("font", () => {
it("should work", () => {
const tree = new Formatter().format(
createFont({
name: "Times New Roman",
altName: "Times New Roman",
family: "roman",
charset: "00",
panose1: "02020603050405020304",
pitch: "variable",
embedRegular: {
id: "rId0",
fontKey: "00000000-0000-0000-0000-000000000000",
},
}),
);
expect(tree).to.deep.equal({
"w:font": [
{
_attr: {
"w:name": "Times New Roman",
},
},
{
"w:altName": {
_attr: {
"w:val": "Times New Roman",
},
},
},
{
"w:panose1": {
_attr: {
"w:val": "02020603050405020304",
},
},
},
{
"w:charset": {
_attr: {
"w:val": "00",
},
},
},
{
"w:family": {
_attr: {
"w:val": "roman",
},
},
},
{
"w:pitch": {
_attr: {
"w:val": "variable",
},
},
},
{
"w:embedRegular": {
_attr: {
"r:id": "rId0",
"w:fontKey": "{00000000-0000-0000-0000-000000000000}",
},
},
},
],
});
});
it("should work for embedBold", () => {
const tree = new Formatter().format(
createFont({
name: "Times New Roman",
embedBold: {
id: "rId0",
fontKey: "00000000-0000-0000-0000-000000000000",
},
}),
);
expect(tree).toStrictEqual({
"w:font": expect.arrayContaining([
{
"w:embedBold": {
_attr: {
"r:id": "rId0",
"w:fontKey": "{00000000-0000-0000-0000-000000000000}",
},
},
},
]),
});
});
it("should work for embedBoldItalic", () => {
const tree = new Formatter().format(
createFont({
name: "Times New Roman",
embedBoldItalic: {
id: "rId0",
fontKey: "00000000-0000-0000-0000-000000000000",
},
}),
);
expect(tree).toStrictEqual({
"w:font": expect.arrayContaining([
{
"w:embedBoldItalic": {
_attr: {
"r:id": "rId0",
"w:fontKey": "{00000000-0000-0000-0000-000000000000}",
},
},
},
]),
});
});
it("should work for embedItalic", () => {
const tree = new Formatter().format(
createFont({
name: "Times New Roman",
embedItalic: {
id: "rId0",
fontKey: "00000000-0000-0000-0000-000000000000",
},
}),
);
expect(tree).toStrictEqual({
"w:font": expect.arrayContaining([
{
"w:embedItalic": {
_attr: {
"r:id": "rId0",
"w:fontKey": "{00000000-0000-0000-0000-000000000000}",
},
},
},
]),
});
});
it("should work for notTrueType", () => {
const tree = new Formatter().format(
createFont({
name: "Times New Roman",
embedRegular: {
id: "rId0",
fontKey: "00000000-0000-0000-0000-000000000000",
subsetted: true,
},
}),
);
expect(tree).toStrictEqual({
"w:font": expect.arrayContaining([
{
"w:embedRegular": [
{
_attr: {
"r:id": "rId0",
"w:fontKey": "{00000000-0000-0000-0000-000000000000}",
},
},
{
"w:subsetted": {},
},
],
},
]),
});
});
it("should work for subsetted", () => {
const tree = new Formatter().format(
createFont({
name: "Times New Roman",
notTrueType: true,
}),
);
expect(tree).toStrictEqual({
"w:font": expect.arrayContaining([
{
"w:notTrueType": {},
},
]),
});
});
it("should work without fontKey", () => {
const tree = new Formatter().format(
createFont({
name: "Times New Roman",
embedItalic: {
id: "rId0",
},
}),
);
expect(tree).toStrictEqual({
"w:font": expect.arrayContaining([
{
"w:embedItalic": {
_attr: {
"r:id": "rId0",
},
},
},
]),
});
});
});

View File

@ -1,156 +0,0 @@
import { BuilderElement, createStringElement, OnOffElement, XmlComponent } from "@file/xml-components";
// <xsd:complexType name="CT_Font">
// <xsd:sequence>
// <xsd:element name="altName" type="CT_String" minOccurs="0" maxOccurs="1"/>
// <xsd:element name="panose1" type="CT_Panose" minOccurs="0" maxOccurs="1"/>
// <xsd:element name="charset" type="CT_Charset" minOccurs="0" maxOccurs="1"/>
// <xsd:element name="family" type="CT_FontFamily" minOccurs="0" maxOccurs="1"/>
// <xsd:element name="notTrueType" type="CT_OnOff" minOccurs="0" maxOccurs="1"/>
// <xsd:element name="pitch" type="CT_Pitch" minOccurs="0" maxOccurs="1"/>
// <xsd:element name="sig" type="CT_FontSig" minOccurs="0" maxOccurs="1"/>
// <xsd:element name="embedRegular" type="CT_FontRel" minOccurs="0" maxOccurs="1"/>
// <xsd:element name="embedBold" type="CT_FontRel" minOccurs="0" maxOccurs="1"/>
// <xsd:element name="embedItalic" type="CT_FontRel" minOccurs="0" maxOccurs="1"/>
// <xsd:element name="embedBoldItalic" type="CT_FontRel" minOccurs="0" maxOccurs="1"/>
// </xsd:sequence>
// <xsd:attribute name="name" type="s:ST_String" use="required"/>
// </xsd:complexType>
// <xsd:complexType name="CT_FontRel">
// <xsd:complexContent>
// <xsd:extension base="CT_Rel">
// <xsd:attribute name="fontKey" type="s:ST_Guid" />
// <xsd:attribute name="subsetted" type="s:ST_OnOff" />
// </xsd:extension>
// </xsd:complexContent>
// </xsd:complexType>
// http://www.datypic.com/sc/ooxml/e-w_embedRegular-1.html
export interface IFontRelationshipOptions {
/**
* Relationship to Part
*/
readonly id: string;
/**
* Embedded Font Obfuscation Key
*/
readonly fontKey?: string;
/**
* Embedded Font Is Subsetted
*/
readonly subsetted?: boolean;
}
export const CharacterSet = {
ANSI: "00",
DEFAULT: "01",
SYMBOL: "02",
MAC: "4D",
JIS: "80",
HANGUL: "81",
JOHAB: "82",
GB_2312: "86",
CHINESEBIG5: "88",
GREEK: "A1",
TURKISH: "A2",
VIETNAMESE: "A3",
HEBREW: "B1",
ARABIC: "B2",
BALTIC: "BA",
RUSSIAN: "CC",
THAI: "DE",
EASTEUROPE: "EE",
OEM: "FF",
} as const;
export type FontOptions = {
readonly name: string;
readonly altName?: string;
readonly panose1?: string;
readonly charset?: (typeof CharacterSet)[keyof typeof CharacterSet];
readonly family?: string;
readonly notTrueType?: boolean;
readonly pitch?: string;
readonly sig?: {
readonly usb0: string;
readonly usb1: string;
readonly usb2: string;
readonly usb3: string;
readonly csb0: string;
readonly csb1: string;
};
readonly embedRegular?: IFontRelationshipOptions;
readonly embedBold?: IFontRelationshipOptions;
readonly embedItalic?: IFontRelationshipOptions;
readonly embedBoldItalic?: IFontRelationshipOptions;
};
const createFontRelationship = ({ id, fontKey, subsetted }: IFontRelationshipOptions, name: string): XmlComponent =>
new BuilderElement({
name,
attributes: {
id: { key: "r:id", value: id },
...(fontKey ? { fontKey: { key: "w:fontKey", value: `{${fontKey}}` } } : {}),
},
children: [...(subsetted ? [new OnOffElement("w:subsetted", subsetted)] : [])],
});
export const createFont = ({
name,
altName,
panose1,
charset,
family,
notTrueType,
pitch,
sig,
embedRegular,
embedBold,
embedItalic,
embedBoldItalic,
}: FontOptions): XmlComponent =>
// http://www.datypic.com/sc/ooxml/e-w_font-1.html
new BuilderElement({
name: "w:font",
attributes: {
name: { key: "w:name", value: name },
},
children: [
// http://www.datypic.com/sc/ooxml/e-w_altName-1.html
...(altName ? [createStringElement("w:altName", altName)] : []),
// http://www.datypic.com/sc/ooxml/e-w_panose1-1.html
...(panose1 ? [createStringElement("w:panose1", panose1)] : []),
// http://www.datypic.com/sc/ooxml/e-w_charset-1.html
...(charset ? [createStringElement("w:charset", charset)] : []),
// http://www.datypic.com/sc/ooxml/e-w_family-1.html
...(family ? [createStringElement("w:family", family)] : []),
// http://www.datypic.com/sc/ooxml/e-w_notTrueType-1.html
...(notTrueType ? [new OnOffElement("w:notTrueType", notTrueType)] : []),
...(pitch ? [createStringElement("w:pitch", pitch)] : []),
// http://www.datypic.com/sc/ooxml/e-w_sig-1.html
...(sig
? [
new BuilderElement({
name: "w:sig",
attributes: {
usb0: { key: "w:usb0", value: sig.usb0 },
usb1: { key: "w:usb1", value: sig.usb1 },
usb2: { key: "w:usb2", value: sig.usb2 },
usb3: { key: "w:usb3", value: sig.usb3 },
csb0: { key: "w:csb0", value: sig.csb0 },
csb1: { key: "w:csb1", value: sig.csb1 },
},
}),
]
: []),
// http://www.datypic.com/sc/ooxml/e-w_embedRegular-1.html
...(embedRegular ? [createFontRelationship(embedRegular, "w:embedRegular")] : []),
// http://www.datypic.com/sc/ooxml/e-w_embedBold-1.html
...(embedBold ? [createFontRelationship(embedBold, "w:embedBold")] : []),
// http://www.datypic.com/sc/ooxml/e-w_embedItalic-1.html
...(embedItalic ? [createFontRelationship(embedItalic, "w:embedItalic")] : []),
// http://www.datypic.com/sc/ooxml/e-w_embedBoldItalic-1.html
...(embedBoldItalic ? [createFontRelationship(embedBoldItalic, "w:embedBoldItalic")] : []),
],
});

View File

@ -1 +0,0 @@
export { CharacterSet } from "./font";

View File

@ -1,22 +0,0 @@
const obfuscatedStartOffset = 0;
const obfuscatedEndOffset = 32;
const guidSize = 32;
export const obfuscate = (buf: Buffer, fontKey: string): Buffer => {
const guid = fontKey.replace(/-/g, "");
if (guid.length !== guidSize) {
throw new Error(`Error: Cannot extract GUID from font filename: ${fontKey}`);
}
const hexStrings = guid.replace(/(..)/g, "$1 ").trim().split(" ");
const hexNumbers = hexStrings.map((hexString) => parseInt(hexString, 16));
// eslint-disable-next-line functional/immutable-data
hexNumbers.reverse();
const bytesToObfuscate = buf.slice(obfuscatedStartOffset, obfuscatedEndOffset);
// eslint-disable-next-line no-bitwise
const obfuscatedBytes = bytesToObfuscate.map((byte, i) => byte ^ hexNumbers[i % hexNumbers.length]);
const out = Buffer.concat([buf.slice(0, obfuscatedStartOffset), obfuscatedBytes, buf.slice(obfuscatedEndOffset)]);
return out;
};

View File

@ -1,14 +0,0 @@
import { describe, expect, it } from "vitest";
import { obfuscate } from "./obfuscate-ttf-to-odttf";
describe("obfuscate", () => {
it("should work", () => {
const buffer = obfuscate(Buffer.from(""), "00000000-0000-0000-0000-000000000000");
expect(buffer).toBeDefined();
});
it("should throw error if uuid is not correct", () => {
expect(() => obfuscate(Buffer.from(""), "bad-uuid")).toThrowError();
});
});

View File

@ -1,7 +1,6 @@
export * from "./paragraph";
export * from "./table";
export * from "./file";
export * from "./file-child";
export * from "./numbering";
export * from "./media";
export * from "./drawing";
@ -19,4 +18,3 @@ export * from "./shared";
export * from "./border";
export * from "./vertical-align";
export * from "./checkbox";
export * from "./fonts";

View File

@ -14,25 +14,11 @@ export interface IMediaDataTransformation {
readonly rotation?: number;
}
type CoreMediaData = {
export interface IMediaData {
readonly stream: Buffer | Uint8Array | ArrayBuffer;
readonly fileName: string;
readonly transformation: IMediaDataTransformation;
readonly data: Buffer | Uint8Array | ArrayBuffer;
};
type RegularMediaData = {
readonly type: "jpg" | "png" | "gif" | "bmp";
};
type SvgMediaData = {
readonly type: "svg";
/**
* Required in case the Word processor does not support SVG.
*/
readonly fallback: RegularMediaData & CoreMediaData;
};
export type IMediaData = (RegularMediaData | SvgMediaData) & CoreMediaData;
}
// Needed because of: https://github.com/s-panferov/awesome-typescript-loader/issues/432
/**

View File

@ -19,8 +19,7 @@ describe("Media", () => {
const media = new Media();
media.addImage("test2.png", {
type: "png",
data: Buffer.from(""),
stream: Buffer.from(""),
fileName: "test.png",
transformation: {
pixels: {

View File

@ -4,7 +4,6 @@ import { Formatter } from "@export/formatter";
import { AlignmentType, EmphasisMarkType, TabStopPosition } from "../paragraph";
import { UnderlineType } from "../paragraph/run/underline";
import { HighlightColor } from "../paragraph/run/properties";
import { ShadingType } from "../shading";
import { AbstractNumbering } from "./abstract-numbering";
import { LevelFormat, LevelSuffix } from "./level";
@ -2049,23 +2048,23 @@ describe("AbstractNumbering", () => {
const highlightTests = [
{
highlight: HighlightColor.YELLOW,
expected: [{ "w:highlight": { _attr: { "w:val": "yellow" } } }, { "w:highlightCs": { _attr: { "w:val": "yellow" } } }],
highlight: "005599",
expected: [{ "w:highlight": { _attr: { "w:val": "005599" } } }, { "w:highlightCs": { _attr: { "w:val": "005599" } } }],
},
{
highlight: HighlightColor.YELLOW,
highlight: "005599",
highlightComplexScript: true,
expected: [{ "w:highlight": { _attr: { "w:val": "yellow" } } }, { "w:highlightCs": { _attr: { "w:val": "yellow" } } }],
expected: [{ "w:highlight": { _attr: { "w:val": "005599" } } }, { "w:highlightCs": { _attr: { "w:val": "005599" } } }],
},
{
highlight: HighlightColor.YELLOW,
highlight: "005599",
highlightComplexScript: false,
expected: [{ "w:highlight": { _attr: { "w:val": "yellow" } } }],
expected: [{ "w:highlight": { _attr: { "w:val": "005599" } } }],
},
{
highlight: HighlightColor.YELLOW,
highlight: "005599",
highlightComplexScript: "550099",
expected: [{ "w:highlight": { _attr: { "w:val": "yellow" } } }, { "w:highlightCs": { _attr: { "w:val": "550099" } } }],
expected: [{ "w:highlight": { _attr: { "w:val": "005599" } } }, { "w:highlightCs": { _attr: { "w:val": "550099" } } }],
},
];
highlightTests.forEach(({ highlight, highlightComplexScript, expected }) => {

View File

@ -24,8 +24,6 @@ class SpacingAttributes extends XmlAttributeComponent<ISpacingProperties> {
before: "w:before",
line: "w:line",
lineRule: "w:lineRule",
beforeAutoSpacing: "w:beforeAutospacing",
afterAutoSpacing: "w:afterAutoSpacing",
};
}

View File

@ -3,12 +3,12 @@ import { describe, expect, it } from "vitest";
import { Formatter } from "@export/formatter";
import { HorizontalPositionAlign, VerticalPositionAlign } from "@file/shared";
import { FrameAnchorType, createFrameProperties } from "./frame-properties";
import { FrameAnchorType, FrameProperties } from "./frame-properties";
describe("createFrameProperties", () => {
describe("FrameProperties", () => {
describe("#constructor()", () => {
it("should create", () => {
const currentFrameProperties = createFrameProperties({
type: "absolute",
const currentFrameProperties = new FrameProperties({
position: {
x: 1000,
y: 3000,
@ -19,6 +19,10 @@ describe("createFrameProperties", () => {
horizontal: FrameAnchorType.MARGIN,
vertical: FrameAnchorType.MARGIN,
},
alignment: {
x: HorizontalPositionAlign.CENTER,
y: VerticalPositionAlign.TOP,
},
});
const tree = new Formatter().format(currentFrameProperties);
@ -30,15 +34,16 @@ describe("createFrameProperties", () => {
"w:vAnchor": "margin",
"w:w": 4000,
"w:x": 1000,
"w:xAlign": "center",
"w:y": 3000,
"w:yAlign": "top",
},
},
});
});
it("should create with the space attribute", () => {
const currentFrameProperties = createFrameProperties({
type: "absolute",
const currentFrameProperties = new FrameProperties({
position: {
x: 1000,
y: 3000,
@ -49,6 +54,10 @@ describe("createFrameProperties", () => {
horizontal: FrameAnchorType.MARGIN,
vertical: FrameAnchorType.MARGIN,
},
alignment: {
x: HorizontalPositionAlign.CENTER,
y: VerticalPositionAlign.TOP,
},
space: {
horizontal: 100,
vertical: 200,
@ -64,7 +73,9 @@ describe("createFrameProperties", () => {
"w:vAnchor": "margin",
"w:w": 4000,
"w:x": 1000,
"w:xAlign": "center",
"w:y": 3000,
"w:yAlign": "top",
"w:hSpace": 100,
"w:vSpace": 200,
},
@ -73,8 +84,7 @@ describe("createFrameProperties", () => {
});
it("should create without x and y", () => {
const currentFrameProperties = createFrameProperties({
type: "alignment",
const currentFrameProperties = new FrameProperties({
width: 4000,
height: 1000,
anchor: {
@ -109,8 +119,7 @@ describe("createFrameProperties", () => {
});
it("should create without alignments", () => {
const currentFrameProperties = createFrameProperties({
type: "absolute",
const currentFrameProperties = new FrameProperties({
position: {
x: 1000,
y: 3000,
@ -144,3 +153,4 @@ describe("createFrameProperties", () => {
});
});
});
});

View File

@ -1,7 +1,7 @@
// http://officeopenxml.com/WPparagraph-textFrames.php
import { HorizontalPositionAlign, VerticalPositionAlign } from "@file/shared/alignment";
import { HeightRule } from "@file/table";
import { BuilderElement, XmlComponent } from "@file/xml-components";
import { XmlAttributeComponent, XmlComponent } from "@file/xml-components";
export const DropCapType = {
NONE: "none",
@ -44,7 +44,6 @@ interface IBaseFrameOptions {
}
export interface IXYFrameOptions extends IBaseFrameOptions {
readonly type: "absolute";
readonly position: {
readonly x: number;
readonly y: number;
@ -52,7 +51,6 @@ export interface IXYFrameOptions extends IBaseFrameOptions {
}
export interface IAlignmentFrameOptions extends IBaseFrameOptions {
readonly type: "alignment";
readonly alignment: {
readonly x: (typeof HorizontalPositionAlign)[keyof typeof HorizontalPositionAlign];
readonly y: (typeof VerticalPositionAlign)[keyof typeof VerticalPositionAlign];
@ -63,24 +61,7 @@ export interface IAlignmentFrameOptions extends IBaseFrameOptions {
// https://stackoverflow.com/q/46370222/3481582
export type IFrameOptions = IXYFrameOptions | IAlignmentFrameOptions;
// <xsd:complexType name="CT_FramePr">
// <xsd:attribute name="dropCap" type="ST_DropCap" use="optional"/>
// <xsd:attribute name="lines" type="ST_DecimalNumber" use="optional"/>
// <xsd:attribute name="w" type="s:ST_TwipsMeasure" use="optional"/>
// <xsd:attribute name="h" type="s:ST_TwipsMeasure" use="optional"/>
// <xsd:attribute name="vSpace" type="s:ST_TwipsMeasure" use="optional"/>
// <xsd:attribute name="hSpace" type="s:ST_TwipsMeasure" use="optional"/>
// <xsd:attribute name="wrap" type="ST_Wrap" use="optional"/>
// <xsd:attribute name="hAnchor" type="ST_HAnchor" use="optional"/>
// <xsd:attribute name="vAnchor" type="ST_VAnchor" use="optional"/>
// <xsd:attribute name="x" type="ST_SignedTwipsMeasure" use="optional"/>
// <xsd:attribute name="xAlign" type="s:ST_XAlign" use="optional"/>
// <xsd:attribute name="y" type="ST_SignedTwipsMeasure" use="optional"/>
// <xsd:attribute name="yAlign" type="s:ST_YAlign" use="optional"/>
// <xsd:attribute name="hRule" type="ST_HeightRule" use="optional"/>
// <xsd:attribute name="anchorLock" type="s:ST_OnOff" use="optional"/>
// </xsd:complexType>
type FramePropertiesAttributes = {
export class FramePropertiesAttributes extends XmlAttributeComponent<{
readonly anchorLock?: boolean;
readonly dropCap?: (typeof DropCapType)[keyof typeof DropCapType];
readonly width: number;
@ -96,71 +77,47 @@ type FramePropertiesAttributes = {
readonly rule?: (typeof HeightRule)[keyof typeof HeightRule];
readonly alignmentX?: (typeof HorizontalPositionAlign)[keyof typeof HorizontalPositionAlign];
readonly alignmentY?: (typeof VerticalPositionAlign)[keyof typeof VerticalPositionAlign];
}> {
protected readonly xmlKeys = {
anchorLock: "w:anchorLock",
dropCap: "w:dropCap",
width: "w:w",
height: "w:h",
x: "w:x",
y: "w:y",
anchorHorizontal: "w:hAnchor",
anchorVertical: "w:vAnchor",
spaceHorizontal: "w:hSpace",
spaceVertical: "w:vSpace",
rule: "w:hRule",
alignmentX: "w:xAlign",
alignmentY: "w:yAlign",
lines: "w:lines",
wrap: "w:wrap",
};
}
export const createFrameProperties = (options: IFrameOptions): XmlComponent =>
new BuilderElement<FramePropertiesAttributes>({
name: "w:framePr",
attributes: {
anchorLock: {
key: "w:anchorLock",
value: options.anchorLock,
},
dropCap: {
key: "w:dropCap",
value: options.dropCap,
},
width: {
key: "w:w",
value: options.width,
},
height: {
key: "w:h",
value: options.height,
},
x: {
key: "w:x",
value: (options as IXYFrameOptions).position ? (options as IXYFrameOptions).position.x : undefined,
},
y: {
key: "w:y",
value: (options as IXYFrameOptions).position ? (options as IXYFrameOptions).position.y : undefined,
},
anchorHorizontal: {
key: "w:hAnchor",
value: options.anchor.horizontal,
},
anchorVertical: {
key: "w:vAnchor",
value: options.anchor.vertical,
},
spaceHorizontal: {
key: "w:hSpace",
value: options.space?.horizontal,
},
spaceVertical: {
key: "w:vSpace",
value: options.space?.vertical,
},
rule: {
key: "w:hRule",
value: options.rule,
},
alignmentX: {
key: "w:xAlign",
value: (options as IAlignmentFrameOptions).alignment ? (options as IAlignmentFrameOptions).alignment.x : undefined,
},
alignmentY: {
key: "w:yAlign",
value: (options as IAlignmentFrameOptions).alignment ? (options as IAlignmentFrameOptions).alignment.y : undefined,
},
lines: {
key: "w:lines",
value: options.lines,
},
wrap: {
key: "w:wrap",
value: options.wrap,
},
},
});
export class FrameProperties extends XmlComponent {
public constructor(options: IFrameOptions) {
super("w:framePr");
this.root.push(
new FramePropertiesAttributes({
anchorLock: options.anchorLock,
dropCap: options.dropCap,
width: options.width,
height: options.height,
x: (options as IXYFrameOptions).position ? (options as IXYFrameOptions).position.x : undefined,
y: (options as IXYFrameOptions).position ? (options as IXYFrameOptions).position.y : undefined,
anchorHorizontal: options.anchor.horizontal,
anchorVertical: options.anchor.vertical,
spaceHorizontal: options.space?.horizontal,
spaceVertical: options.space?.vertical,
rule: options.rule,
alignmentX: (options as IAlignmentFrameOptions).alignment ? (options as IAlignmentFrameOptions).alignment.x : undefined,
alignmentY: (options as IAlignmentFrameOptions).alignment ? (options as IAlignmentFrameOptions).alignment.y : undefined,
lines: options.lines,
wrap: options.wrap,
}),
);
}
}

View File

@ -6,6 +6,3 @@ export * from "./math-sub-script";
export * from "./math-sum";
export * from "./math-integral";
export * from "./math-super-script";
export * from "./math-limit";
export * from "./math-limit-upper";
export * from "./math-limit-lower";

View File

@ -22,7 +22,7 @@ describe("MathIntegral", () => {
{
"m:limLoc": {
_attr: {
"m:val": "subSup",
"m:val": "undOvr",
},
},
},
@ -78,7 +78,7 @@ describe("MathIntegral", () => {
{
"m:limLoc": {
_attr: {
"m:val": "subSup",
"m:val": "undOvr",
},
},
},

View File

@ -16,7 +16,7 @@ export class MathIntegral extends XmlComponent {
public constructor(options: IMathIntegralOptions) {
super("m:nary");
this.root.push(new MathNAryProperties("", !!options.superScript, !!options.subScript, "subSup"));
this.root.push(new MathNAryProperties("", !!options.superScript, !!options.subScript));
if (!!options.subScript) {
this.root.push(new MathSubScriptElement(options.subScript));

View File

@ -6,9 +6,9 @@ class MathLimitLocationAttributes extends XmlAttributeComponent<{ readonly value
}
export class MathLimitLocation extends XmlComponent {
public constructor(value?: string) {
public constructor() {
super("m:limLoc");
this.root.push(new MathLimitLocationAttributes({ value: value || "undOvr" }));
this.root.push(new MathLimitLocationAttributes({ value: "undOvr" }));
}
}

View File

@ -1,45 +0,0 @@
import { describe, expect, it } from "vitest";
import { Formatter } from "@export/formatter";
import { MathRun } from "../math-run";
import { MathLimitLower } from "./math-limit-lower";
describe("MathLimitLower", () => {
describe("#constructor()", () => {
it("should create a MathLimitLower with correct root key", () => {
const mathLimitLower = new MathLimitLower({
children: [new MathRun("lim")],
limit: [new MathRun("x→0")],
});
const tree = new Formatter().format(mathLimitLower);
expect(tree).to.deep.equal({
"m:limLow": [
{
"m:e": [
{
"m:r": [
{
"m:t": ["lim"],
},
],
},
],
},
{
"m:lim": [
{
"m:r": [
{
"m:t": ["x→0"],
},
],
},
],
},
],
});
});
});
});

View File

@ -1,19 +0,0 @@
// http://www.datypic.com/sc/ooxml/e-m_limLow-1.html
import { XmlComponent } from "@file/xml-components";
import { MathComponent } from "../math-component";
import { MathBase } from "./math-base";
import { MathLimit } from "./math-limit";
export interface IMathLimitLowerOptions {
readonly children: readonly MathComponent[];
readonly limit: readonly MathComponent[];
}
export class MathLimitLower extends XmlComponent {
public constructor(options: IMathLimitLowerOptions) {
super("m:limLow");
this.root.push(new MathBase(options.children));
this.root.push(new MathLimit(options.limit));
}
}

View File

@ -1,45 +0,0 @@
import { describe, expect, it } from "vitest";
import { Formatter } from "@export/formatter";
import { MathRun } from "../math-run";
import { MathLimitUpper } from "./math-limit-upper";
describe("MathLimitUpper", () => {
describe("#constructor()", () => {
it("should create a MathLimitUpper with correct root key", () => {
const mathLimitUpper = new MathLimitUpper({
children: [new MathRun("x")],
limit: [new MathRun("-")],
});
const tree = new Formatter().format(mathLimitUpper);
expect(tree).to.deep.equal({
"m:limUpp": [
{
"m:e": [
{
"m:r": [
{
"m:t": ["x"],
},
],
},
],
},
{
"m:lim": [
{
"m:r": [
{
"m:t": ["-"],
},
],
},
],
},
],
});
});
});
});

View File

@ -1,19 +0,0 @@
// http://www.datypic.com/sc/ooxml/e-m_limUpp-1.html
import { XmlComponent } from "@file/xml-components";
import { MathComponent } from "../math-component";
import { MathBase } from "./math-base";
import { MathLimit } from "./math-limit";
export interface IMathLimitUpperOptions {
readonly children: readonly MathComponent[];
readonly limit: readonly MathComponent[];
}
export class MathLimitUpper extends XmlComponent {
public constructor(options: IMathLimitUpperOptions) {
super("m:limUpp");
this.root.push(new MathBase(options.children));
this.root.push(new MathLimit(options.limit));
}
}

View File

@ -1,27 +0,0 @@
import { describe, expect, it } from "vitest";
import { Formatter } from "@export/formatter";
import { MathRun } from "../math-run";
import { MathLimit } from "./math-limit";
describe("MathLimit", () => {
describe("#constructor()", () => {
it("should create a MathLimit with correct root key", () => {
const mathLimit = new MathLimit([new MathRun("x→0")]);
const tree = new Formatter().format(mathLimit);
expect(tree).to.deep.equal({
"m:lim": [
{
"m:r": [
{
"m:t": ["x→0"],
},
],
},
],
});
});
});
});

View File

@ -1,13 +0,0 @@
// http://www.datypic.com/sc/ooxml/e-m_lim-1.html
import { XmlComponent } from "@file/xml-components";
import { MathComponent } from "../math-component";
export class MathLimit extends XmlComponent {
public constructor(children: readonly MathComponent[]) {
super("m:lim");
for (const child of children) {
this.root.push(child);
}
}
}

View File

@ -7,13 +7,13 @@ import { MathSubScriptHide } from "./math-sub-script-hide";
import { MathSuperScriptHide } from "./math-super-script-hide";
export class MathNAryProperties extends XmlComponent {
public constructor(accent: string, hasSuperScript: boolean, hasSubScript: boolean, limitLocationVal?: string) {
public constructor(accent: string, hasSuperScript: boolean, hasSubScript: boolean) {
super("m:naryPr");
if (!!accent) {
this.root.push(new MathAccentCharacter(accent));
}
this.root.push(new MathLimitLocation(limitLocationVal));
this.root.push(new MathLimitLocation());
if (!hasSuperScript) {
this.root.push(new MathSuperScriptHide());

View File

@ -890,7 +890,10 @@ describe("Paragraph", () => {
it("should set frame attribute", () => {
const paragraph = new Paragraph({
frame: {
type: "alignment",
position: {
x: 1000,
y: 3000,
},
width: 4000,
height: 1000,
anchor: {
@ -915,7 +918,9 @@ describe("Paragraph", () => {
"w:hAnchor": "margin",
"w:vAnchor": "margin",
"w:w": 4000,
"w:x": 1000,
"w:xAlign": "center",
"w:y": 3000,
"w:yAlign": "top",
},
},

View File

@ -65,36 +65,6 @@ describe("ParagraphProperties", () => {
});
});
it("should create with numbering disabled", () => {
const properties = new ParagraphProperties({
numbering: false,
});
const tree = new Formatter().format(properties);
expect(tree).to.deep.equal({
"w:pPr": [
{
"w:numPr": [
{
"w:ilvl": {
_attr: {
"w:val": 0,
},
},
},
{
"w:numId": {
_attr: {
"w:val": 0,
},
},
},
],
},
],
});
});
it("should create with widowControl", () => {
const properties = new ParagraphProperties({
widowControl: true,

View File

@ -13,7 +13,7 @@ import { HeadingLevel, Style } from "./formatting/style";
import { TabStop, TabStopDefinition, TabStopType } from "./formatting/tab-stop";
import { NumberProperties } from "./formatting/unordered-list";
import { WordWrap } from "./formatting/word-wrap";
import { createFrameProperties, IFrameOptions } from "./frame/frame-properties";
import { FrameProperties, IFrameOptions } from "./frame/frame-properties";
import { OutlineLevel } from "./links";
import { IRunOptions, RunProperties } from ".";
@ -37,14 +37,12 @@ export interface ILevelParagraphStylePropertiesOptions {
}
export interface IParagraphStylePropertiesOptions extends ILevelParagraphStylePropertiesOptions {
readonly numbering?:
| {
readonly numbering?: {
readonly reference: string;
readonly level: number;
readonly instance?: number;
readonly custom?: boolean;
}
| false;
};
}
export interface IParagraphPropertiesOptions extends IParagraphStylePropertiesOptions {
@ -119,7 +117,7 @@ export class ParagraphProperties extends IgnoreIfEmptyXmlComponent {
}
if (options.frame) {
this.push(createFrameProperties(options.frame));
this.push(new FrameProperties(options.frame));
}
if (options.widowControl !== undefined) {
@ -137,8 +135,6 @@ export class ParagraphProperties extends IgnoreIfEmptyXmlComponent {
});
this.push(new NumberProperties(`${options.numbering.reference}-${options.numbering.instance ?? 0}`, options.numbering.level));
} else if (options.numbering === false) {
this.push(new NumberProperties(0, 0));
}
if (options.border) {

View File

@ -1,16 +1,24 @@
import { describe, expect, it, vi } from "vitest";
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
import { Formatter } from "@export/formatter";
import { IViewWrapper } from "@file/document-wrapper";
import { File } from "@file/file";
import * as convenienceFunctions from "@util/convenience-functions";
import { ImageRun } from "./image-run";
describe("ImageRun", () => {
beforeAll(() => {
vi.spyOn(convenienceFunctions, "uniqueId").mockReturnValue("test-unique-id");
});
afterAll(() => {
vi.resetAllMocks();
});
describe("#constructor()", () => {
it("should create with Buffer", () => {
const currentImageRun = new ImageRun({
type: "png",
data: Buffer.from(""),
transformation: {
width: 200,
@ -31,7 +39,8 @@ describe("ImageRun", () => {
const tree = new Formatter().format(currentImageRun, {
file: {
Media: {
addImage: vi.fn(),
// eslint-disable-next-line @typescript-eslint/no-empty-function
addImage: () => {},
},
} as unknown as File,
viewWrapper: {} as unknown as IViewWrapper,
@ -184,8 +193,7 @@ describe("ImageRun", () => {
"a:blip": {
_attr: {
cstate: "none",
"r:embed":
"rId{da39a3ee5e6b4b0d3255bfef95601890afd80709.png}",
"r:embed": "rId{test-unique-id.png}",
},
},
},
@ -263,7 +271,6 @@ describe("ImageRun", () => {
it("should create with string", () => {
const currentImageRun = new ImageRun({
type: "png",
data: "",
transformation: {
width: 200,
@ -284,7 +291,8 @@ describe("ImageRun", () => {
const tree = new Formatter().format(currentImageRun, {
file: {
Media: {
addImage: vi.fn(),
// eslint-disable-next-line @typescript-eslint/no-empty-function
addImage: () => {},
},
} as unknown as File,
viewWrapper: {} as unknown as IViewWrapper,
@ -437,8 +445,7 @@ describe("ImageRun", () => {
"a:blip": {
_attr: {
cstate: "none",
"r:embed":
"rId{da39a3ee5e6b4b0d3255bfef95601890afd80709.png}",
"r:embed": "rId{test-unique-id.png}",
},
},
},
@ -515,10 +522,10 @@ describe("ImageRun", () => {
});
it("should return UInt8Array if atob is present", () => {
vi.spyOn(global, "atob").mockReturnValue("atob result");
// eslint-disable-next-line functional/immutable-data
global.atob = () => "atob result";
const currentImageRun = new ImageRun({
type: "png",
data: "",
transformation: {
width: 200,
@ -539,7 +546,8 @@ describe("ImageRun", () => {
const tree = new Formatter().format(currentImageRun, {
file: {
Media: {
addImage: vi.fn(),
// eslint-disable-next-line @typescript-eslint/no-empty-function
addImage: () => {},
},
} as unknown as File,
viewWrapper: {} as unknown as IViewWrapper,
@ -693,8 +701,7 @@ describe("ImageRun", () => {
"a:blip": {
_attr: {
cstate: "none",
"r:embed":
"rId{da39a3ee5e6b4b0d3255bfef95601890afd80709.png}",
"r:embed": "rId{test-unique-id.png}",
},
},
},
@ -768,13 +775,16 @@ describe("ImageRun", () => {
},
],
});
// eslint-disable-next-line @typescript-eslint/no-explicit-any, functional/immutable-data
(global as any).atob = undefined;
});
it("should use data as is if its not a string", () => {
vi.spyOn(global, "atob").mockReturnValue("atob result");
// eslint-disable-next-line functional/immutable-data
global.atob = () => "atob result";
const currentImageRun = new ImageRun({
type: "png",
data: "",
transformation: {
width: 200,
@ -795,7 +805,8 @@ describe("ImageRun", () => {
const tree = new Formatter().format(currentImageRun, {
file: {
Media: {
addImage: vi.fn(),
// eslint-disable-next-line @typescript-eslint/no-empty-function
addImage: () => {},
},
} as unknown as File,
viewWrapper: {} as unknown as IViewWrapper,
@ -949,8 +960,7 @@ describe("ImageRun", () => {
"a:blip": {
_attr: {
cstate: "none",
"r:embed":
"rId{da39a3ee5e6b4b0d3255bfef95601890afd80709.png}",
"r:embed": "rId{test-unique-id.png}",
},
},
},
@ -1024,158 +1034,9 @@ describe("ImageRun", () => {
},
],
});
});
it("should strip base64 marker", () => {
const spy = vi.spyOn(global, "atob").mockReturnValue("atob result");
new ImageRun({
type: "png",
data: ";base64,",
transformation: {
width: 200,
height: 200,
rotation: 45,
},
});
expect(spy).toBeCalledWith("");
});
it("should work with svgs", () => {
const currentImageRun = new ImageRun({
type: "svg",
data: Buffer.from(""),
transformation: {
width: 200,
height: 200,
},
fallback: {
type: "png",
data: Buffer.from(""),
},
});
const tree = new Formatter().format(currentImageRun, {
file: {
Media: {
addImage: vi.fn(),
},
} as unknown as File,
viewWrapper: {} as unknown as IViewWrapper,
stack: [],
});
expect(tree).toStrictEqual({
"w:r": [
{
"w:drawing": [
{
"wp:inline": expect.arrayContaining([
{
"a:graphic": expect.arrayContaining([
{
"a:graphicData": expect.arrayContaining([
{
"pic:pic": expect.arrayContaining([
{
"pic:blipFill": expect.arrayContaining([
{
"a:blip": [
{
_attr: {
cstate: "none",
"r:embed":
"rId{da39a3ee5e6b4b0d3255bfef95601890afd80709.png}",
},
},
{
"a:extLst": [
{
"a:ext": [
{
_attr: {
uri: "{96DAC541-7B7A-43D3-8B79-37D633B846F1}",
},
},
{
"asvg:svgBlip": {
_attr: expect.objectContaining({
"r:embed":
"rId{da39a3ee5e6b4b0d3255bfef95601890afd80709.svg}",
}),
},
},
],
},
],
},
],
},
]),
},
]),
},
]),
},
]),
},
]),
},
],
},
],
});
});
it("using same data twice should use same media key", () => {
const imageRunStringData = new ImageRun({
type: "png",
data: "DATA",
transformation: {
width: 100,
height: 100,
rotation: 42,
},
});
const imageRunBufferData = new ImageRun({
type: "png",
data: Buffer.from("DATA"),
transformation: {
width: 200,
height: 200,
rotation: 45,
},
});
const addImageSpy = vi.fn();
const context = {
file: {
Media: {
addImage: addImageSpy,
},
} as unknown as File,
viewWrapper: {} as unknown as IViewWrapper,
stack: [],
};
new Formatter().format(imageRunStringData, context);
new Formatter().format(imageRunBufferData, context);
const expectedHash = "580393f5a94fb469585f5dd2a6859a4aab899f37";
expect(addImageSpy).toHaveBeenCalledTimes(2);
expect(addImageSpy).toHaveBeenNthCalledWith(
1,
`${expectedHash}.png`,
expect.objectContaining({ fileName: `${expectedHash}.png` }),
);
expect(addImageSpy).toHaveBeenNthCalledWith(
2,
`${expectedHash}.png`,
expect.objectContaining({ fileName: `${expectedHash}.png` }),
);
// eslint-disable-next-line @typescript-eslint/no-explicit-any, functional/immutable-data
(global as any).atob = undefined;
});
});
});

View File

@ -1,4 +1,4 @@
import { hashedId } from "@util/convenience-functions";
import { uniqueId } from "@util/convenience-functions";
import { IContext, IXmlableObject } from "@file/xml-components";
import { DocPropertiesOptions } from "@file/drawing/doc-properties/doc-properties";
@ -9,30 +9,54 @@ import { IMediaTransformation } from "../../media";
import { IMediaData } from "../../media/data";
import { Run } from "../run";
type CoreImageOptions = {
export interface IImageOptions {
readonly data: Buffer | string | Uint8Array | ArrayBuffer;
readonly transformation: IMediaTransformation;
readonly floating?: IFloating;
readonly altText?: DocPropertiesOptions;
readonly outline?: OutlineOptions;
}
export class ImageRun extends Run {
private readonly key = `${uniqueId()}.png`;
private readonly imageData: IMediaData;
public constructor(options: IImageOptions) {
super({});
const newData = typeof options.data === "string" ? this.convertDataURIToBinary(options.data) : options.data;
this.imageData = {
stream: newData,
fileName: this.key,
transformation: {
pixels: {
x: Math.round(options.transformation.width),
y: Math.round(options.transformation.height),
},
emus: {
x: Math.round(options.transformation.width * 9525),
y: Math.round(options.transformation.height * 9525),
},
flip: options.transformation.flip,
rotation: options.transformation.rotation ? options.transformation.rotation * 60000 : undefined,
},
};
const drawing = new Drawing(this.imageData, {
floating: options.floating,
docProperties: options.altText,
outline: options.outline,
});
type RegularImageOptions = {
readonly type: "jpg" | "png" | "gif" | "bmp";
readonly data: Buffer | string | Uint8Array | ArrayBuffer;
};
this.root.push(drawing);
}
type SvgMediaOptions = {
readonly type: "svg";
readonly data: Buffer | string | Uint8Array | ArrayBuffer;
/**
* Required in case the Word processor does not support SVG.
*/
readonly fallback: RegularImageOptions;
};
public prepForXml(context: IContext): IXmlableObject | undefined {
context.file.Media.addImage(this.key, this.imageData);
export type IImageOptions = (RegularImageOptions | SvgMediaOptions) & CoreImageOptions;
return super.prepForXml(context);
}
const convertDataURIToBinary = (dataURI: string): Uint8Array => {
private convertDataURIToBinary(dataURI: string): Uint8Array {
if (typeof atob === "function") {
// https://gist.github.com/borismus/1032746
// https://github.com/mafintosh/base64-to-uint8array
@ -46,80 +70,12 @@ const convertDataURIToBinary = (dataURI: string): Uint8Array => {
.split("")
.map((c) => c.charCodeAt(0)),
);
/* c8 ignore next 6 */
} else {
/* c8 ignore next 4 */
// Not possible to test this branch in NodeJS
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
const b = require("buf" + "fer");
return new b.Buffer(dataURI, "base64");
}
};
const standardizeData = (data: string | Buffer | Uint8Array | ArrayBuffer): Buffer | Uint8Array | ArrayBuffer =>
typeof data === "string" ? convertDataURIToBinary(data) : data;
const createImageData = (options: IImageOptions, key: string): Pick<IMediaData, "data" | "fileName" | "transformation"> => ({
data: standardizeData(options.data),
fileName: key,
transformation: {
pixels: {
x: Math.round(options.transformation.width),
y: Math.round(options.transformation.height),
},
emus: {
x: Math.round(options.transformation.width * 9525),
y: Math.round(options.transformation.height * 9525),
},
flip: options.transformation.flip,
rotation: options.transformation.rotation ? options.transformation.rotation * 60000 : undefined,
},
});
export class ImageRun extends Run {
private readonly imageData: IMediaData;
public constructor(options: IImageOptions) {
super({});
const hash = hashedId(options.data);
const key = `${hash}.${options.type}`;
this.imageData =
options.type === "svg"
? {
type: options.type,
...createImageData(options, key),
fallback: {
type: options.fallback.type,
...createImageData(
{
...options.fallback,
transformation: options.transformation,
},
`${hashedId(options.fallback.data)}.${options.fallback.type}`,
),
},
}
: {
type: options.type,
...createImageData(options, key),
};
const drawing = new Drawing(this.imageData, {
floating: options.floating,
docProperties: options.altText,
outline: options.outline,
});
this.root.push(drawing);
}
public prepForXml(context: IContext): IXmlableObject | undefined {
context.file.Media.addImage(this.imageData.fileName, this.imageData);
if (this.imageData.type === "svg") {
context.file.Media.addImage(this.imageData.fallback.fileName, this.imageData.fallback);
}
return super.prepForXml(context);
}
}

View File

@ -2,11 +2,11 @@ import { describe, expect, it } from "vitest";
import { Formatter } from "@export/formatter";
import { CurrentSection, NumberOfPages, NumberOfPagesSection, Page } from "./page-number";
import { NumberOfPages, NumberOfPagesSection, Page } from "./page-number";
describe("Page", () => {
describe("#constructor()", () => {
it("should work", () => {
it("uses the font name for both ascii and hAnsi", () => {
const tree = new Formatter().format(new Page());
expect(tree).to.deep.equal({ "w:instrText": [{ _attr: { "xml:space": "preserve" } }, "PAGE"] });
});
@ -15,7 +15,7 @@ describe("Page", () => {
describe("NumberOfPages", () => {
describe("#constructor()", () => {
it("should work", () => {
it("uses the font name for both ascii and hAnsi", () => {
const tree = new Formatter().format(new NumberOfPages());
expect(tree).to.deep.equal({ "w:instrText": [{ _attr: { "xml:space": "preserve" } }, "NUMPAGES"] });
});
@ -24,18 +24,9 @@ describe("NumberOfPages", () => {
describe("NumberOfPagesSection", () => {
describe("#constructor()", () => {
it("should work", () => {
it("uses the font name for both ascii and hAnsi", () => {
const tree = new Formatter().format(new NumberOfPagesSection());
expect(tree).to.deep.equal({ "w:instrText": [{ _attr: { "xml:space": "preserve" } }, "SECTIONPAGES"] });
});
});
});
describe("CurrentSection", () => {
describe("#constructor()", () => {
it("should work", () => {
const tree = new Formatter().format(new CurrentSection());
expect(tree).to.deep.equal({ "w:instrText": [{ _attr: { "xml:space": "preserve" } }, "SECTION"] });
});
});
});

View File

@ -27,7 +27,7 @@ export class NumberOfPagesSection extends XmlComponent {
}
}
export class CurrentSection extends XmlComponent {
export class CurrentPageInSection extends XmlComponent {
public constructor() {
super("w:instrText");
this.root.push(new TextAttributes({ space: SpaceType.PRESERVE }));

View File

@ -36,33 +36,6 @@ export const TextEffect = {
NONE: "none",
} as const;
/*
* http://officeopenxml.com/WPtextShading.php
*
* Limit the list of supported highlight colors
*
* */
export const HighlightColor = {
BLACK: "black",
BLUE: "blue",
CYAN: "cyan",
DARK_BLUE: "darkBlue",
DARK_CYAN: "darkCyan",
DARK_GRAY: "darkGray",
DARK_GREEN: "darkGreen",
DARK_MAGENTA: "darkMagenta",
DARK_RED: "darkRed",
DARK_YELLOW: "darkYellow",
GREEN: "green",
LIGHT_GRAY: "lightGray",
MAGENTA: "magenta",
NONE: "none",
RED: "red",
WHITE: "white",
YELLOW: "yellow",
} as const;
/* eslint-enable */
export interface IRunStylePropertiesOptions {
@ -92,7 +65,7 @@ export interface IRunStylePropertiesOptions {
readonly subScript?: boolean;
readonly superScript?: boolean;
readonly font?: string | IFontOptions | IFontAttributesProperties;
readonly highlight?: (typeof HighlightColor)[keyof typeof HighlightColor];
readonly highlight?: string;
readonly highlightComplexScript?: boolean | string;
readonly characterSpacing?: number;
readonly shading?: IShadingAttributesProperties;

View File

@ -23,9 +23,11 @@ export class Text extends XmlComponent {
if (typeof options === "string") {
this.root.push(new TextAttributes({ space: SpaceType.PRESERVE }));
this.root.push(options);
return this;
} else {
this.root.push(new TextAttributes({ space: options.space ?? SpaceType.DEFAULT }));
this.root.push(options.text);
return this;
}
}
}

View File

@ -7,7 +7,7 @@ import { ShadingType } from "@file/shading";
import { EmphasisMarkType } from "./emphasis-mark";
import { PageNumber, Run } from "./run";
import { UnderlineType } from "./underline";
import { HighlightColor, TextEffect } from "./properties";
import { TextEffect } from "./properties";
describe("Run", () => {
describe("#noProof()", () => {
it("turns off spelling and grammar checkers for a run", () => {
@ -215,18 +215,18 @@ describe("Run", () => {
describe("#highlight()", () => {
it("it should add highlight to the properties", () => {
const run = new Run({
highlight: HighlightColor.YELLOW,
highlight: "005599",
});
const tree = new Formatter().format(run);
expect(tree).to.deep.equal({
"w:r": [
{
"w:rPr": [
{ "w:highlight": { _attr: { "w:val": "yellow" } } },
{ "w:highlight": { _attr: { "w:val": "005599" } } },
{
"w:highlightCs": {
_attr: {
"w:val": "yellow",
"w:val": "005599",
},
},
},

View File

@ -6,7 +6,7 @@ import { FieldInstruction } from "@file/table-of-contents/field-instruction";
import { Break } from "./break";
import { Begin, End, Separate } from "./field";
import { NumberOfPages, NumberOfPagesSection, Page, CurrentSection } from "./page-number";
import { NumberOfPages, NumberOfPagesSection, Page, CurrentPageInSection } from "./page-number";
import { IRunPropertiesOptions, RunProperties } from "./properties";
import { Text } from "./run-components/text";
import {
@ -146,7 +146,7 @@ export class Run extends XmlComponent {
break;
case PageNumber.CURRENT_SECTION:
this.root.push(new Begin());
this.root.push(new CurrentSection());
this.root.push(new CurrentPageInSection());
this.root.push(new Separate());
this.root.push(new End());
break;

View File

@ -17,8 +17,7 @@ export type RelationshipType =
| "http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties"
| "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink"
| "http://schemas.openxmlformats.org/officeDocument/2006/relationships/footnotes"
| "http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments"
| "http://schemas.openxmlformats.org/officeDocument/2006/relationships/font";
| "http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments";
export const TargetModeType = {
EXTERNAL: "External",

View File

@ -3,7 +3,6 @@ import { describe, expect, it } from "vitest";
import { Formatter } from "@export/formatter";
import { EmphasisMarkType } from "@file/paragraph/run/emphasis-mark";
import { UnderlineType } from "@file/paragraph/run/underline";
import { HighlightColor } from "@file/paragraph/run/properties";
import { ShadingType } from "@file/shading";
import { EMPTY_OBJECT } from "@file/xml-components";
@ -729,23 +728,23 @@ describe("CharacterStyle", () => {
const highlightTests = [
{
highlight: HighlightColor.YELLOW,
expected: [{ "w:highlight": { _attr: { "w:val": "yellow" } } }, { "w:highlightCs": { _attr: { "w:val": "yellow" } } }],
highlight: "005599",
expected: [{ "w:highlight": { _attr: { "w:val": "005599" } } }, { "w:highlightCs": { _attr: { "w:val": "005599" } } }],
},
{
highlight: HighlightColor.YELLOW,
highlight: "005599",
highlightComplexScript: true,
expected: [{ "w:highlight": { _attr: { "w:val": "yellow" } } }, { "w:highlightCs": { _attr: { "w:val": "yellow" } } }],
expected: [{ "w:highlight": { _attr: { "w:val": "005599" } } }, { "w:highlightCs": { _attr: { "w:val": "005599" } } }],
},
{
highlight: HighlightColor.YELLOW,
highlight: "005599",
highlightComplexScript: false,
expected: [{ "w:highlight": { _attr: { "w:val": "yellow" } } }],
expected: [{ "w:highlight": { _attr: { "w:val": "005599" } } }],
},
{
highlight: HighlightColor.YELLOW,
highlight: "005599",
highlightComplexScript: "550099",
expected: [{ "w:highlight": { _attr: { "w:val": "yellow" } } }, { "w:highlightCs": { _attr: { "w:val": "550099" } } }],
expected: [{ "w:highlight": { _attr: { "w:val": "005599" } } }, { "w:highlightCs": { _attr: { "w:val": "550099" } } }],
},
];
highlightTests.forEach(({ highlight, highlightComplexScript, expected }) => {

View File

@ -3,7 +3,6 @@ import { describe, expect, it } from "vitest";
import { Formatter } from "@export/formatter";
import { AlignmentType, EmphasisMarkType, TabStopPosition } from "@file/paragraph";
import { UnderlineType } from "@file/paragraph/run/underline";
import { HighlightColor } from "@file/paragraph/run";
import { ShadingType } from "@file/shading";
import { EMPTY_OBJECT } from "@file/xml-components";
@ -616,23 +615,23 @@ describe("ParagraphStyle", () => {
const highlightTests = [
{
highlight: HighlightColor.YELLOW,
expected: [{ "w:highlight": { _attr: { "w:val": "yellow" } } }, { "w:highlightCs": { _attr: { "w:val": "yellow" } } }],
highlight: "005599",
expected: [{ "w:highlight": { _attr: { "w:val": "005599" } } }, { "w:highlightCs": { _attr: { "w:val": "005599" } } }],
},
{
highlight: HighlightColor.YELLOW,
highlight: "005599",
highlightComplexScript: true,
expected: [{ "w:highlight": { _attr: { "w:val": "yellow" } } }, { "w:highlightCs": { _attr: { "w:val": "yellow" } } }],
expected: [{ "w:highlight": { _attr: { "w:val": "005599" } } }, { "w:highlightCs": { _attr: { "w:val": "005599" } } }],
},
{
highlight: HighlightColor.YELLOW,
highlight: "005599",
highlightComplexScript: false,
expected: [{ "w:highlight": { _attr: { "w:val": "yellow" } } }],
expected: [{ "w:highlight": { _attr: { "w:val": "005599" } } }],
},
{
highlight: HighlightColor.YELLOW,
highlight: "005599",
highlightComplexScript: "550099",
expected: [{ "w:highlight": { _attr: { "w:val": "yellow" } } }, { "w:highlightCs": { _attr: { "w:val": "550099" } } }],
expected: [{ "w:highlight": { _attr: { "w:val": "005599" } } }, { "w:highlightCs": { _attr: { "w:val": "550099" } } }],
},
];
highlightTests.forEach(({ highlight, highlightComplexScript, expected }) => {

View File

@ -1,13 +1,12 @@
import { BaseXmlComponent, IContext } from "./base";
import { IXmlableObject, IXmlAttribute } from "./xmlable-object";
type AttributeMap<T> = Record<keyof T, string>;
export type AttributeMap<T> = { readonly [P in keyof T]: string };
export type AttributeData = { readonly [key: string]: boolean | number | string };
export type AttributePayload<T> = { readonly [P in keyof T]: { readonly key: string; readonly value: T[P] } };
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export abstract class XmlAttributeComponent<T extends { readonly [key: string]: any }> extends BaseXmlComponent {
export abstract class XmlAttributeComponent<T extends object> extends BaseXmlComponent {
protected readonly xmlKeys?: AttributeMap<T>;
public constructor(private readonly root: T) {
@ -17,11 +16,12 @@ export abstract class XmlAttributeComponent<T extends { readonly [key: string]:
public prepForXml(_: IContext): IXmlableObject {
// eslint-disable-next-line functional/prefer-readonly-type
const attrs: { [key: string]: string } = {};
Object.entries(this.root).forEach(([key, value]) => {
Object.keys(this.root).forEach((key) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const value = (this.root as any)[key];
if (value !== undefined) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const newKey = (this.xmlKeys && this.xmlKeys[key]) || key;
const newKey = (this.xmlKeys && (this.xmlKeys as any)[key]) || key;
// eslint-disable-next-line functional/immutable-data
attrs[newKey] = value;
}

View File

@ -30,7 +30,6 @@ export const convertToXmlComponent = (element: XmlElement): ImportedXmlComponent
return element.text as string;
default:
return undefined;
/* c8 ignore next 2 */
}
};

View File

@ -55,14 +55,6 @@ export class StringValueElement extends XmlComponent {
}
}
export const createStringElement = (name: string, value: string): XmlComponent =>
new BuilderElement({
name,
attributes: {
value: { key: "w:val", value },
},
});
// This represents various number element types.
export class NumberValueElement extends XmlComponent {
public constructor(name: string, val: number) {
@ -90,23 +82,19 @@ export class StringContainer extends XmlComponent {
}
export class BuilderElement<T extends AttributeData> extends XmlComponent {
public constructor({
name,
attributes,
children,
}: {
public constructor(options: {
readonly name: string;
readonly attributes?: AttributePayload<T>;
readonly children?: readonly XmlComponent[];
}) {
super(name);
super(options.name);
if (attributes) {
this.root.push(new NextAttributeComponent(attributes));
if (options.attributes) {
this.root.push(new NextAttributeComponent(options.attributes));
}
if (children) {
this.root.push(...children);
for (const child of options.children ?? []) {
this.root.push(child);
}
}
}

View File

@ -218,9 +218,7 @@ describe("from-docx", () => {
});
it("should patch the document", async () => {
const output = await patchDocument({
outputType: "uint8array",
data: Buffer.from(""),
const output = await patchDocument(Buffer.from(""), {
patches: {
name: {
type: PatchType.PARAGRAPH,
@ -256,7 +254,6 @@ describe("from-docx", () => {
link: "https://www.google.co.uk",
}),
new ImageRun({
type: "png",
data: Buffer.from(""),
transformation: { width: 100, height: 100 },
}),
@ -269,7 +266,6 @@ describe("from-docx", () => {
type: PatchType.PARAGRAPH,
children: [
new ImageRun({
type: "png",
data: Buffer.from(""),
transformation: { width: 100, height: 100 },
}),
@ -281,9 +277,7 @@ describe("from-docx", () => {
});
it("should patch the document", async () => {
const output = await patchDocument({
outputType: "uint8array",
data: Buffer.from(""),
const output = await patchDocument(Buffer.from(""), {
patches: {},
});
expect(output).to.not.be.undefined;
@ -309,16 +303,13 @@ describe("from-docx", () => {
});
it("should use the relationships file rather than create one", async () => {
const output = await patchDocument({
outputType: "uint8array",
data: Buffer.from(""),
const output = await patchDocument(Buffer.from(""), {
patches: {
// eslint-disable-next-line @typescript-eslint/naming-convention
image_test: {
type: PatchType.PARAGRAPH,
children: [
new ImageRun({
type: "png",
data: Buffer.from(""),
transformation: { width: 100, height: 100 },
}),
@ -356,16 +347,13 @@ describe("from-docx", () => {
it("should throw an error if the content types is not found", () =>
expect(
patchDocument({
outputType: "uint8array",
data: Buffer.from(""),
patchDocument(Buffer.from(""), {
patches: {
// eslint-disable-next-line @typescript-eslint/naming-convention
image_test: {
type: PatchType.PARAGRAPH,
children: [
new ImageRun({
type: "png",
data: Buffer.from(""),
transformation: { width: 100, height: 100 },
}),
@ -396,16 +384,13 @@ describe("from-docx", () => {
it("should throw an error if the content types is not found", () =>
expect(
patchDocument({
outputType: "uint8array",
data: Buffer.from(""),
patchDocument(Buffer.from(""), {
patches: {
// eslint-disable-next-line @typescript-eslint/naming-convention
image_test: {
type: PatchType.PARAGRAPH,
children: [
new ImageRun({
type: "png",
data: Buffer.from(""),
transformation: { width: 100, height: 100 },
}),

View File

@ -12,12 +12,13 @@ import { TargetModeType } from "@file/relationships/relationship/relationship";
import { uniqueId } from "@util/convenience-functions";
import { replacer } from "./replacer";
import { findLocationOfText } from "./traverser";
import { toJson } from "./util";
import { appendRelationship, getNextRelationshipIndex } from "./relationship-manager";
import { appendContentType } from "./content-types-manager";
// eslint-disable-next-line functional/prefer-readonly-type
export type InputDataType = Buffer | string | number[] | Uint8Array | ArrayBuffer | Blob | NodeJS.ReadableStream;
type InputDataType = Buffer | string | number[] | Uint8Array | ArrayBuffer | Blob | NodeJS.ReadableStream;
export const PatchType = {
DOCUMENT: "file",
@ -46,37 +47,14 @@ interface IHyperlinkRelationshipAddition {
export type IPatch = ParagraphPatch | FilePatch;
// From JSZip
type OutputByType = {
readonly base64: string;
// eslint-disable-next-line id-denylist
readonly string: string;
readonly text: string;
readonly binarystring: string;
readonly array: readonly number[];
readonly uint8array: Uint8Array;
readonly arraybuffer: ArrayBuffer;
readonly blob: Blob;
readonly nodebuffer: Buffer;
};
export type PatchDocumentOutputType = keyof OutputByType;
export type PatchDocumentOptions<T extends PatchDocumentOutputType = PatchDocumentOutputType> = {
readonly outputType: T;
readonly data: InputDataType;
export interface PatchDocumentOptions {
readonly patches: { readonly [key: string]: IPatch };
readonly keepOriginalStyles?: boolean;
};
}
const imageReplacer = new ImageReplacer();
export const patchDocument = async <T extends PatchDocumentOutputType = PatchDocumentOutputType>({
outputType,
data,
patches,
keepOriginalStyles,
}: PatchDocumentOptions<T>): Promise<OutputByType[T]> => {
export const patchDocument = async (data: InputDataType, options: PatchDocumentOptions): Promise<Uint8Array> => {
const zipContent = await JSZip.loadAsync(data);
const contexts = new Map<string, IContext>();
const file = {
@ -126,20 +104,13 @@ export const patchDocument = async <T extends PatchDocumentOutputType = PatchDoc
};
contexts.set(key, context);
for (const [patchKey, patchValue] of Object.entries(patches)) {
for (const [patchKey, patchValue] of Object.entries(options.patches)) {
const patchText = `{{${patchKey}}}`;
const renderedParagraphs = findLocationOfText(json, patchText);
// TODO: mutates json. Make it immutable
// We need to loop through to catch every occurrence of the patch text
// It is possible that the patch text is in the same run
// This algorithm is limited to one patch per text run
// Once it cannot find any more occurrences, it will throw an error, and then we break out of the loop
// https://github.com/dolanmiu/docx/issues/2267
// eslint-disable-next-line no-constant-condition
while (true) {
try {
replacer({
replacer(
json,
patch: {
{
...patchValue,
children: patchValue.children.map((element) => {
// We need to replace external hyperlinks with concrete hyperlinks
@ -161,13 +132,10 @@ export const patchDocument = async <T extends PatchDocumentOutputType = PatchDoc
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any,
patchText,
renderedParagraphs,
context,
keepOriginalStyles,
});
} catch {
break;
}
}
options.keepOriginalStyles,
);
}
const mediaDatas = imageReplacer.getMediaData(JSON.stringify(json), context.file.Media);
@ -233,7 +201,6 @@ export const patchDocument = async <T extends PatchDocumentOutputType = PatchDoc
appendContentType(contentTypesJson, "image/jpeg", "jpg");
appendContentType(contentTypesJson, "image/bmp", "bmp");
appendContentType(contentTypesJson, "image/gif", "gif");
appendContentType(contentTypesJson, "image/svg+xml", "svg");
}
const zip = new JSZip();
@ -248,12 +215,12 @@ export const patchDocument = async <T extends PatchDocumentOutputType = PatchDoc
zip.file(key, value);
}
for (const { data: stream, fileName } of file.Media.Array) {
for (const { stream, fileName } of file.Media.Array) {
zip.file(`word/media/${fileName}`, stream);
}
return zip.generateAsync({
type: outputType,
type: "uint8array",
mimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
compression: "DEFLATE",
});

View File

@ -1,2 +1 @@
export * from "./from-docx";
export * from "./patch-detector";

View File

@ -273,65 +273,5 @@ describe("paragraph-split-inject", () => {
},
});
});
it("should create an empty end element if it is at the end", () => {
const output = splitRunElement(
{
type: "element",
name: "w:r",
elements: [
{
type: "element",
name: "w:rPr",
elements: [
{ type: "element", name: "w:rFonts", attributes: { "w:eastAsia": "Times New Roman" } },
{ type: "element", name: "w:kern", attributes: { "w:val": "0" } },
{ type: "element", name: "w:sz", attributes: { "w:val": "20" } },
{
type: "element",
name: "w:lang",
attributes: { "w:val": "en-US", "w:eastAsia": "en-US", "w:bidi": "ar-SA" },
},
],
},
{ type: "element", name: "w:t", elements: [], attributes: { "xml:space": "preserve" } },
{ type: "element", name: "w:br" },
{ type: "element", name: "w:t", elements: [{ type: "text", text: "ɵ" }] },
],
},
"ɵ",
);
expect(output).to.deep.equal({
left: {
type: "element",
name: "w:r",
elements: [
{
type: "element",
name: "w:rPr",
elements: [
{ type: "element", name: "w:rFonts", attributes: { "w:eastAsia": "Times New Roman" } },
{ type: "element", name: "w:kern", attributes: { "w:val": "0" } },
{ type: "element", name: "w:sz", attributes: { "w:val": "20" } },
{
type: "element",
name: "w:lang",
attributes: { "w:val": "en-US", "w:eastAsia": "en-US", "w:bidi": "ar-SA" },
},
],
},
{ type: "element", name: "w:t", elements: [], attributes: { "xml:space": "preserve" } },
{ type: "element", name: "w:br" },
{ type: "element", name: "w:t", elements: [], attributes: { "xml:space": "preserve" } },
],
},
right: {
type: "element",
name: "w:r",
elements: [{ type: "element", name: "w:t", elements: [], attributes: { "xml:space": "preserve" } }],
},
});
});
});
});

View File

@ -29,7 +29,7 @@ export const splitRunElement = (runElement: Element, token: string): { readonly
runElement.elements
?.map((e, i) => {
if (e.type === "element" && e.name === "w:t") {
const text = (e.elements?.[0]?.text as string) ?? "";
const text = (e.elements?.[0].text as string) ?? "";
const splitText = text.split(token);
const newElements = splitText.map((t) => ({
...e,

View File

@ -27,7 +27,7 @@ describe("paragraph-token-replacer", () => {
},
renderedParagraph: {
index: 0,
pathToParagraph: [0],
path: [0],
runs: [
{
end: 4,
@ -128,7 +128,7 @@ describe("paragraph-token-replacer", () => {
{ text: "World", parts: [{ text: "World", index: 0, start: 15, end: 19 }], index: 3, start: 15, end: 19 },
],
index: 0,
pathToParagraph: [0, 1, 0, 0],
path: [0, 1, 0, 0],
},
originalText: "{{name}}",
replacementText: "John",

View File

@ -1,393 +0,0 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import JSZip from "jszip";
import { patchDetector } from "./patch-detector";
const MOCK_XML = `
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:document xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas"
xmlns:cx="http://schemas.microsoft.com/office/drawing/2014/chartex"
xmlns:cx1="http://schemas.microsoft.com/office/drawing/2015/9/8/chartex"
xmlns:cx2="http://schemas.microsoft.com/office/drawing/2015/10/21/chartex"
xmlns:cx3="http://schemas.microsoft.com/office/drawing/2016/5/9/chartex"
xmlns:cx4="http://schemas.microsoft.com/office/drawing/2016/5/10/chartex"
xmlns:cx5="http://schemas.microsoft.com/office/drawing/2016/5/11/chartex"
xmlns:cx6="http://schemas.microsoft.com/office/drawing/2016/5/12/chartex"
xmlns:cx7="http://schemas.microsoft.com/office/drawing/2016/5/13/chartex"
xmlns:cx8="http://schemas.microsoft.com/office/drawing/2016/5/14/chartex"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:aink="http://schemas.microsoft.com/office/drawing/2016/ink"
xmlns:am3d="http://schemas.microsoft.com/office/drawing/2017/model3d"
xmlns:o="urn:schemas-microsoft-com:office:office"
xmlns:oel="http://schemas.microsoft.com/office/2019/extlst"
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math"
xmlns:v="urn:schemas-microsoft-com:vml"
xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing"
xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"
xmlns:w10="urn:schemas-microsoft-com:office:word"
xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml"
xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml"
xmlns:w16cex="http://schemas.microsoft.com/office/word/2018/wordml/cex"
xmlns:w16cid="http://schemas.microsoft.com/office/word/2016/wordml/cid"
xmlns:w16="http://schemas.microsoft.com/office/word/2018/wordml"
xmlns:w16sdtdh="http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash"
xmlns:w16se="http://schemas.microsoft.com/office/word/2015/wordml/symex"
xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup"
xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk"
xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml"
xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape">
<w:body>
<w:p w14:paraId="2499FE9F" w14:textId="0A3D130F" w:rsidR="00B51233"
w:rsidRDefault="007B52ED" w:rsidP="007B52ED">
<w:pPr>
<w:pStyle w:val="Title" />
</w:pPr>
<w:r>
<w:t>Hello World</w:t>
</w:r>
</w:p>
<w:p w14:paraId="6410D9A0" w14:textId="7579AB49" w:rsidR="007B52ED"
w:rsidRDefault="007B52ED" />
<w:p w14:paraId="57ACF964" w14:textId="315D7A05" w:rsidR="007B52ED"
w:rsidRDefault="007B52ED">
<w:r>
<w:t>Hello {{name}},</w:t>
</w:r>
<w:r w:rsidR="008126CB">
<w:t xml:space="preserve"> how are you?</w:t>
</w:r>
</w:p>
<w:p w14:paraId="38C7DF4A" w14:textId="66CDEC9A" w:rsidR="007B52ED"
w:rsidRDefault="007B52ED" />
<w:p w14:paraId="04FABE2B" w14:textId="3DACA001" w:rsidR="007B52ED"
w:rsidRDefault="007B52ED">
<w:r>
<w:t>{{paragraph_replace}}</w:t>
</w:r>
</w:p>
<w:p w14:paraId="7AD7975D" w14:textId="77777777" w:rsidR="00EF161F"
w:rsidRDefault="00EF161F" />
<w:p w14:paraId="3BD6D75A" w14:textId="19AE3121" w:rsidR="00EF161F"
w:rsidRDefault="00EF161F">
<w:r>
<w:t>{{table}}</w:t>
</w:r>
</w:p>
<w:p w14:paraId="76023962" w14:textId="4E606AB9" w:rsidR="007B52ED"
w:rsidRDefault="007B52ED" />
<w:tbl>
<w:tblPr>
<w:tblStyle w:val="TableGrid" />
<w:tblW w:w="0" w:type="auto" />
<w:tblLook w:val="04A0" w:firstRow="1" w:lastRow="0" w:firstColumn="1"
w:lastColumn="0" w:noHBand="0" w:noVBand="1" />
</w:tblPr>
<w:tblGrid>
<w:gridCol w:w="3003" />
<w:gridCol w:w="3003" />
<w:gridCol w:w="3004" />
</w:tblGrid>
<w:tr w:rsidR="00EF161F" w14:paraId="1DEC5955" w14:textId="77777777" w:rsidTr="00EF161F">
<w:tc>
<w:tcPr>
<w:tcW w:w="3003" w:type="dxa" />
</w:tcPr>
<w:p w14:paraId="54DA5587" w14:textId="625BAC60" w:rsidR="00EF161F"
w:rsidRDefault="00EF161F">
<w:r>
<w:t>{{table_heading_1}}</w:t>
</w:r>
</w:p>
</w:tc>
<w:tc>
<w:tcPr>
<w:tcW w:w="3003" w:type="dxa" />
</w:tcPr>
<w:p w14:paraId="57100910" w14:textId="71FD5616" w:rsidR="00EF161F"
w:rsidRDefault="00EF161F" />
</w:tc>
<w:tc>
<w:tcPr>
<w:tcW w:w="3004" w:type="dxa" />
</w:tcPr>
<w:p w14:paraId="1D388FAB" w14:textId="77777777" w:rsidR="00EF161F"
w:rsidRDefault="00EF161F" />
</w:tc>
</w:tr>
<w:tr w:rsidR="00EF161F" w14:paraId="0F53D2DC" w14:textId="77777777" w:rsidTr="00EF161F">
<w:tc>
<w:tcPr>
<w:tcW w:w="3003" w:type="dxa" />
</w:tcPr>
<w:p w14:paraId="0F2BCCED" w14:textId="3C3B6706" w:rsidR="00EF161F"
w:rsidRDefault="00EF161F">
<w:r>
<w:t>Item: {{item_1}}</w:t>
</w:r>
</w:p>
</w:tc>
<w:tc>
<w:tcPr>
<w:tcW w:w="3003" w:type="dxa" />
</w:tcPr>
<w:p w14:paraId="1E6158AC" w14:textId="77777777" w:rsidR="00EF161F"
w:rsidRDefault="00EF161F" />
</w:tc>
<w:tc>
<w:tcPr>
<w:tcW w:w="3004" w:type="dxa" />
</w:tcPr>
<w:p w14:paraId="17937748" w14:textId="77777777" w:rsidR="00EF161F"
w:rsidRDefault="00EF161F" />
</w:tc>
</w:tr>
<w:tr w:rsidR="00EF161F" w14:paraId="781DAC1A" w14:textId="77777777" w:rsidTr="00EF161F">
<w:tc>
<w:tcPr>
<w:tcW w:w="3003" w:type="dxa" />
</w:tcPr>
<w:p w14:paraId="1DCD0343" w14:textId="77777777" w:rsidR="00EF161F"
w:rsidRDefault="00EF161F" />
</w:tc>
<w:tc>
<w:tcPr>
<w:tcW w:w="3003" w:type="dxa" />
</w:tcPr>
<w:p w14:paraId="5D02E3CD" w14:textId="77777777" w:rsidR="00EF161F"
w:rsidRDefault="00EF161F" />
</w:tc>
<w:tc>
<w:tcPr>
<w:tcW w:w="3004" w:type="dxa" />
</w:tcPr>
<w:p w14:paraId="52EA0DBB" w14:textId="77777777" w:rsidR="00EF161F"
w:rsidRDefault="00EF161F" />
</w:tc>
</w:tr>
</w:tbl>
<w:p w14:paraId="47CD1FBC" w14:textId="23474CBC" w:rsidR="007B52ED"
w:rsidRDefault="007B52ED" />
<w:p w14:paraId="0ACCEE90" w14:textId="67907499" w:rsidR="00EF161F"
w:rsidRDefault="0077578F">
<w:r>
<w:t>{{image_test}}</w:t>
</w:r>
</w:p>
<w:p w14:paraId="23FA9862" w14:textId="77777777" w:rsidR="0077578F"
w:rsidRDefault="0077578F" />
<w:p w14:paraId="01578F2F" w14:textId="3BDC6C85" w:rsidR="007B52ED"
w:rsidRDefault="007B52ED">
<w:r>
<w:t>Thank you</w:t>
</w:r>
</w:p>
<w:sectPr w:rsidR="007B52ED" w:rsidSect="0072043F">
<w:headerReference w:type="default" r:id="rId6" />
<w:footerReference w:type="default" r:id="rId7" />
<w:pgSz w:w="11900" w:h="16840" />
<w:pgMar w:top="1440" w:right="1440" w:bottom="1440" w:left="1440" w:header="708"
w:footer="708" w:gutter="0" />
<w:cols w:space="708" />
<w:docGrid w:linePitch="360" />
</w:sectPr>
</w:body>
</w:document>
`;
// cspell:disable
const MOCK_XML_2 = `
<w:body>
<w:tbl>
<w:tblPr>
<w:tblStyle w:val="TableGrid" />
<w:tblW w:w="9350" w:type="dxa" />
<w:jc w:val="left" />
<w:tblInd w:w="0" w:type="dxa" />
<w:tblLayout w:type="fixed" />
<w:tblCellMar>
<w:top w:w="0" w:type="dxa" />
<w:left w:w="108" w:type="dxa" />
<w:bottom w:w="0" w:type="dxa" />
<w:right w:w="108" w:type="dxa" />
</w:tblCellMar>
<w:tblLook w:firstRow="1" w:noVBand="1" w:lastRow="0" w:firstColumn="1"
w:lastColumn="0" w:noHBand="0" w:val="04a0" />
</w:tblPr>
<w:tblGrid>
<w:gridCol w:w="3119" />
<w:gridCol w:w="3141" />
<w:gridCol w:w="3090" />
</w:tblGrid>
<w:tr>
<w:trPr></w:trPr>
<w:tc>
<w:tcPr>
<w:tcW w:w="3119" w:type="dxa" />
<w:tcBorders>
<w:right w:val="nil" />
</w:tcBorders>
<w:shd w:color="auto" w:fill="D9D9D9" w:themeFill="background1"
w:themeFillShade="d9" w:val="clear" />
</w:tcPr>
<w:p>
<w:pPr>
<w:pStyle w:val="NormalSpaceAboveandBelow" />
<w:widowControl />
<w:spacing w:before="120" w:after="120" />
<w:jc w:val="left" />
<w:rPr>
<w:rFonts w:eastAsia="Times New Roman" />
<w:kern w:val="0" />
<w:sz w:val="20" />
<w:lang w:val="en-US" w:eastAsia="en-US" w:bidi="ar-SA" />
</w:rPr>
</w:pPr>
<w:r>
<w:rPr>
<w:rFonts w:eastAsia="Times New Roman" />
<w:kern w:val="0" />
<w:sz w:val="20" />
<w:lang w:val="en-US" w:eastAsia="en-US" w:bidi="ar-SA" />
</w:rPr>
<w:t>{{</w:t>
</w:r>
<w:r>
<w:rPr>
<w:rFonts w:eastAsia="Times New Roman" />
<w:kern w:val="0" />
<w:sz w:val="20" />
<w:lang w:val="en-US" w:eastAsia="en-US" w:bidi="ar-SA" />
</w:rPr>
<w:t>s</w:t>
</w:r>
<w:r>
<w:rPr>
<w:rFonts w:eastAsia="Times New Roman" />
<w:kern w:val="0" />
<w:sz w:val="20" />
<w:lang w:val="en-US" w:eastAsia="en-US" w:bidi="ar-SA" />
</w:rPr>
<w:t>chool_</w:t>
</w:r>
<w:r>
<w:rPr>
<w:rFonts w:eastAsia="Times New Roman" />
<w:kern w:val="0" />
<w:sz w:val="20" />
<w:lang w:val="en-US" w:eastAsia="en-US" w:bidi="ar-SA" />
</w:rPr>
<w:t>n</w:t>
</w:r>
<w:r>
<w:rPr>
<w:rFonts w:eastAsia="Times New Roman" />
<w:kern w:val="0" />
<w:sz w:val="20" />
<w:lang w:val="en-US" w:eastAsia="en-US" w:bidi="ar-SA" />
</w:rPr>
<w:t>ame}}</w:t>
<w:br />
<w:t>{{</w:t>
</w:r>
<w:r>
<w:rPr>
<w:rFonts w:eastAsia="Times New Roman" />
<w:kern w:val="0" />
<w:sz w:val="20" />
<w:lang w:val="en-US" w:eastAsia="en-US" w:bidi="ar-SA" />
</w:rPr>
<w:t>a</w:t>
</w:r>
<w:r>
<w:rPr>
<w:rFonts w:eastAsia="Times New Roman" />
<w:kern w:val="0" />
<w:sz w:val="20" />
<w:lang w:val="en-US" w:eastAsia="en-US" w:bidi="ar-SA" />
</w:rPr>
<w:t>ddr</w:t>
</w:r>
<w:r>
<w:rPr>
<w:rFonts w:eastAsia="Times New Roman" />
<w:kern w:val="0" />
<w:sz w:val="20" />
<w:lang w:val="en-US" w:eastAsia="en-US" w:bidi="ar-SA" />
</w:rPr>
<w:t>ess</w:t>
</w:r>
<w:r>
<w:rPr>
<w:rFonts w:eastAsia="Times New Roman" />
<w:kern w:val="0" />
<w:sz w:val="20" />
<w:lang w:val="en-US" w:eastAsia="en-US" w:bidi="ar-SA" />
</w:rPr>
<w:t>}}</w:t>
<w:br />
<w:t>{{</w:t>
</w:r>
</w:p>
</w:tc>
</w:tr>
</w:tbl>
</w:body>
`;
// cspell:enable
describe("patch-detector", () => {
describe("patchDetector", () => {
describe("document.xml and [Content_Types].xml", () => {
beforeEach(() => {
vi.spyOn(JSZip, "loadAsync").mockReturnValue(
new Promise<JSZip>((resolve) => {
const zip = new JSZip();
zip.file("word/document.xml", MOCK_XML);
zip.file("[Content_Types].xml", `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>`);
resolve(zip);
}),
);
});
afterEach(() => {
vi.restoreAllMocks();
});
it("should patch the document", async () => {
const output = await patchDetector({
data: Buffer.from(""),
});
expect(output).toMatchObject(["name", "paragraph_replace", "table", "image_test", "table_heading_1", "item_1"]);
});
});
});
describe("patchDetector", () => {
describe("document.xml and [Content_Types].xml", () => {
beforeEach(() => {
vi.spyOn(JSZip, "loadAsync").mockReturnValue(
new Promise<JSZip>((resolve) => {
const zip = new JSZip();
zip.file("word/document.xml", MOCK_XML_2);
zip.file("[Content_Types].xml", `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>`);
resolve(zip);
}),
);
});
afterEach(() => {
vi.restoreAllMocks();
});
it("should patch the document", async () => {
const output = await patchDetector({
data: Buffer.from(""),
});
expect(output).toMatchObject(["school_name", "address"]);
});
});
});
});

Some files were not shown because too many files have changed in this diff Show More