@adobe/helix-md2docx 1.4.1 → 1.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,25 @@
1
+ ## [1.4.4](https://github.com/adobe/helix-md2docx/compare/v1.4.3...v1.4.4) (2022-04-13)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **deps:** update dependency @adobe/helix-docx2md to v1.0.11 ([2224d95](https://github.com/adobe/helix-md2docx/commit/2224d95859e2e2b38e2beec50dac68c709406458))
7
+
8
+ ## [1.4.3](https://github.com/adobe/helix-md2docx/compare/v1.4.2...v1.4.3) (2022-04-11)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * add support for svg images ([1f66ff4](https://github.com/adobe/helix-md2docx/commit/1f66ff4869a1bcd31e7cc092df623a55c8c83dab))
14
+ * **deps:** update dependency @adobe/helix-fetch to v3.0.9 ([35ffd0e](https://github.com/adobe/helix-md2docx/commit/35ffd0e867e61d4aa1ad0de50ef26ebb35d4fe1c))
15
+
16
+ ## [1.4.2](https://github.com/adobe/helix-md2docx/compare/v1.4.1...v1.4.2) (2022-04-02)
17
+
18
+
19
+ ### Bug Fixes
20
+
21
+ * **deps:** update adobe fixes ([#55](https://github.com/adobe/helix-md2docx/issues/55)) ([dc33fdc](https://github.com/adobe/helix-md2docx/commit/dc33fdcb4b55f4cc8aaf44be99f81bf9f8918fd1))
22
+
1
23
  ## [1.4.1](https://github.com/adobe/helix-md2docx/compare/v1.4.0...v1.4.1) (2022-03-24)
2
24
 
3
25
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/helix-md2docx",
3
- "version": "1.4.1",
3
+ "version": "1.4.4",
4
4
  "description": "Helix Service that converts markdown to word documents",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -27,14 +27,15 @@
27
27
  },
28
28
  "homepage": "https://github.com/adobe/helix-md2docx#readme",
29
29
  "dependencies": {
30
- "@adobe/helix-fetch": "3.0.7",
30
+ "@adobe/helix-docx2md": "1.0.11",
31
+ "@adobe/helix-fetch": "3.0.9",
31
32
  "@adobe/helix-markdown-support": "3.1.2",
32
33
  "@adobe/helix-shared-process-queue": "1.1.4",
33
- "@adobe/helix-docx2md": "1.0.10",
34
34
  "docx": "7.3.0",
35
35
  "hast-util-is-element": "2.1.2",
36
36
  "hast-util-to-mdast": "8.3.1",
37
37
  "image-size": "1.0.1",
38
+ "mime": "3.0.0",
38
39
  "rehype-parse": "8.0.4",
39
40
  "remark-gfm": "3.0.1",
40
41
  "remark-parse": "10.0.1",
@@ -43,7 +44,7 @@
43
44
  },
44
45
  "devDependencies": {
45
46
  "@adobe/eslint-config-helix": "1.3.2",
46
- "@adobe/helix-mediahandler": "1.0.13",
47
+ "@adobe/helix-mediahandler": "1.0.18",
47
48
  "@semantic-release/changelog": "6.0.1",
48
49
  "@semantic-release/exec": "6.0.3",
49
50
  "@semantic-release/git": "10.0.1",
@@ -51,9 +52,9 @@
51
52
  "chai": "4.3.6",
52
53
  "codecov": "3.8.3",
53
54
  "dotenv": "16.0.0",
54
- "eslint": "8.11.0",
55
+ "eslint": "8.13.0",
55
56
  "eslint-plugin-header": "3.1.1",
56
- "eslint-plugin-import": "2.25.4",
57
+ "eslint-plugin-import": "2.26.0",
57
58
  "fs-extra": "10.0.1",
58
59
  "husky": "7.0.4",
59
60
  "junit-report-builder": "3.0.0",
@@ -9,14 +9,13 @@
9
9
  * OF ANY KIND, either express or implied. See the License for the specific language
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
- declare interface Logger {}
12
+ import { Mdast2DocxOptions } from '../mdast2docx'
13
13
 
14
14
  /**
15
15
  * Converts the md to a word document (docx).
16
16
  *
17
- * @param {Node} md The md
18
- * @param {Logger} [log] a console like logger
19
- * @param {string} [stylesXML] The content of the styles.xml file of a Word template (to override provided default)
17
+ * @param {string} md The markdown document
18
+ * @param {Mdast2DocxOptions} [opts] options
20
19
  * @returns {Promise<Buffer>} the docx
21
20
  */
22
- export default function md2docx(mdast: object, log: Logger, stylesXML: string): Promise<Buffer>;
21
+ export default function md2docx(md: string, opts?: Mdast2DocxOptions): Promise<Buffer>;
@@ -15,12 +15,12 @@ import gfm from 'remark-gfm';
15
15
  import { remarkMatter } from '@adobe/helix-markdown-support';
16
16
  import mdast2docx from '../mdast2docx/index.js';
17
17
 
18
- export default async function md2docx(md, log = console, stylesXML = null) {
18
+ export default async function md2docx(md, opts) {
19
19
  const mdast = unified()
20
20
  .use(remark, { position: false })
21
21
  .use(gfm)
22
22
  .use(remarkMatter)
23
23
  .parse(md);
24
24
 
25
- return mdast2docx(mdast, log, stylesXML);
25
+ return mdast2docx(mdast, opts);
26
26
  }
@@ -12,7 +12,9 @@
12
12
  import docx from 'docx';
13
13
  import { findXMLComponent } from '../utils.js';
14
14
 
15
- const { Drawing, ImageRun } = docx;
15
+ const {
16
+ Drawing, ImageRun, XmlComponent, XmlAttributeComponent,
17
+ } = docx;
16
18
 
17
19
  // max image width (6.5") and height (2")
18
20
  const LIMITS = {
@@ -86,5 +88,89 @@ export default async function image(ctx, node) {
86
88
  pic.root.push(drawing);
87
89
  pic.key = data.key;
88
90
  pic.imageData = imageData;
91
+
92
+ // for SVGs, we need to generate a proper svgBlip
93
+ /*
94
+ <pic:blipFill>
95
+ <a:blip r:embed="rId4">
96
+ <a:extLst>
97
+ <a:ext uri="{28A0092B-C50C-407E-A947-70E740481C1C}">
98
+ <a14:useLocalDpi val="0" xmlns:a14="http://schemas.microsoft.com/office/drawing/2010/main"/>
99
+ </a:ext>
100
+ <a:ext uri="{96DAC541-7B7A-43D3-8B79-37D633B846F1}">
101
+ <asvg:svgBlip r:embed="rId5" xmlns:asvg="http://schemas.microsoft.com/office/drawing/2016/SVG/main"/>
102
+ </a:ext>
103
+ </a:extLst>
104
+ </a:blip>
105
+ <a:stretch>
106
+ <a:fillRect/>
107
+ </a:stretch>
108
+ </pic:blipFill>
109
+ */
110
+
111
+ class SvgBlip extends XmlComponent {
112
+ constructor(imgData) {
113
+ super('asvg:svgBlip');
114
+ this.imageData = imgData;
115
+ this.addChildElement(new XmlAttributeComponent({
116
+ 'xmlns:asvg': 'http://schemas.microsoft.com/office/drawing/2016/SVG/main',
117
+ 'r:embed': `rId{${imgData.fileName}}`,
118
+ }));
119
+ }
120
+
121
+ prepForXml(context) {
122
+ // add the svg data if it has a stream
123
+ if (this.imageData.stream) {
124
+ context.file.Media.addImage(this.imageData.fileName, this.imageData);
125
+ }
126
+
127
+ // add svg content type if missing
128
+ if (!context.file.contentTypes.root.find((entry) => entry.rootKey === 'Default'
129
+ && (entry.root[0].root.extension === 'svg' || entry.root[0].root.Extension === 'svg'))) {
130
+ context.file.contentTypes.root.push(new XmlComponent('Default').addChildElement(new XmlAttributeComponent({
131
+ ContentType: 'image/svg+xml',
132
+ Extension: 'svg',
133
+ })));
134
+ }
135
+ return super.prepForXml(context);
136
+ }
137
+ }
138
+
139
+ if (data.ext === 'svg' || data.svgBuffer) {
140
+ // create a fake image run for the svg image
141
+ const ir = new ImageRun({
142
+ data: data.svgBuffer,
143
+ transformation: data.dimensions,
144
+ });
145
+ ir.imageData.fileName = data.svgKey;
146
+
147
+ const blipFill = findXMLComponent(drawing, 'wp:inline/a:graphic/a:graphicData/pic:pic/pic:blipFill');
148
+ const blip = findXMLComponent(blipFill, 'a:blip');
149
+
150
+ // const blipAttrs = findXMLComponent(oldBlip, '_attr');
151
+ // blipAttrs.root.embed = `rId{${ir.imageData.fileName}}`;
152
+
153
+ // add svg stuff
154
+ // const newBlip = new XmlComponent('a:blip')
155
+ blip
156
+ .addChildElement(new XmlComponent('a:extLst')
157
+ .addChildElement(new XmlComponent('a:ext')
158
+ .addChildElement(new XmlAttributeComponent({
159
+ uri: '{28A0092B-C50C-407E-A947-70E740481C1C}',
160
+ }))
161
+ .addChildElement(new XmlComponent('a14:useLocalDpi')
162
+ .addChildElement(new XmlAttributeComponent({
163
+ 'xmlns:a14': 'http://schemas.microsoft.com/office/drawing/2010/main',
164
+ val: '0',
165
+ }))))
166
+ .addChildElement(new XmlComponent('a:ext')
167
+ .addChildElement(new XmlAttributeComponent({
168
+ uri: '{96DAC541-7B7A-43D3-8B79-37D633B846F1}',
169
+ }))
170
+ .addChildElement(new SvgBlip(ir.imageData))));
171
+
172
+ // replace blip
173
+ // blipFill.root.splice(blipFill.root.indexOf(oldBlip), 1, newBlip);
174
+ }
89
175
  return pic;
90
176
  }
@@ -9,14 +9,35 @@
9
9
  * OF ANY KIND, either express or implied. See the License for the specific language
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
- declare interface Logger {}
12
+
13
+ /**
14
+ * Converts an SVG to a PNG.
15
+ *
16
+ * @function
17
+ * @param {string} svg the input svg
18
+ * @return {string} the base64 encoded png
19
+ */
20
+ declare type Svg2PngConverter = (svg:string) => Promise<string>;
21
+
22
+ declare interface Mdast2DocxOptions {
23
+ /**
24
+ * A console like logger
25
+ */
26
+ log: Console;
27
+
28
+ /**
29
+ * The content of the styles.xml file of a Word template (to override provided default)
30
+ */
31
+ stylesXML: string;
32
+
33
+ svg2png?: Svg2PngConverter;
34
+ }
13
35
 
14
36
  /**
15
37
  * Converts the mdast to a word document (docx).
16
38
  *
17
39
  * @param {Node} mdast The mdast
18
- * @param {Logger} [log] a console like logger
19
- * @param {string} [stylesXML] The content of the styles.xml file of a Word template (to override provided default)
40
+ * @param {Mdast2DocxOptions} [opts] options
20
41
  * @returns {Promise<Buffer>} the docx
21
42
  */
22
- export default function mdast2docx(mdast: object, log: Logger, stylesXML: string): Promise<Buffer>;
43
+ export default function mdast2docx(mdast: object, opts?: Mdast2DocxOptions): Promise<Buffer>;
@@ -24,7 +24,16 @@ import downloadImages from './mdast-download-images.js';
24
24
 
25
25
  const { Document, Packer } = docx;
26
26
 
27
- export default async function mdast2docx(mdast, log = console, stylesXML = null) {
27
+ export default async function mdast2docx(mdast, opts = {}) {
28
+ const {
29
+ log = console,
30
+ svg2png,
31
+ } = opts;
32
+
33
+ let {
34
+ stylesXML = null,
35
+ } = opts;
36
+
28
37
  const ctx = {
29
38
  handlers,
30
39
  style: {},
@@ -33,6 +42,7 @@ export default async function mdast2docx(mdast, log = console, stylesXML = null)
33
42
  listLevel: -1,
34
43
  lists: [],
35
44
  log,
45
+ svg2png,
36
46
  };
37
47
 
38
48
  // eslint-disable-next-line no-param-reassign
@@ -15,6 +15,7 @@ import { context as fetchAPI, h1 } from '@adobe/helix-fetch';
15
15
  import processQueue from '@adobe/helix-shared-process-queue';
16
16
  import { visit } from 'unist-util-visit';
17
17
  import getDimensions from 'image-size';
18
+ import mime from 'mime';
18
19
 
19
20
  function createFetchContext() {
20
21
  return process.env.HELIX_FETCH_FORCE_HTTP1
@@ -33,6 +34,11 @@ function hsize(bytes, decimals = 2) {
33
34
  return `${parseFloat((bytes / k ** i).toFixed(dm))} ${sizes[i]}`;
34
35
  }
35
36
 
37
+ /**
38
+ * @param ctx
39
+ * @param tree
40
+ * @returns {Promise<void>}
41
+ */
36
42
  export default async function downloadImages(ctx, tree) {
37
43
  const context = createFetchContext();
38
44
  const { fetch } = context;
@@ -53,15 +59,16 @@ export default async function downloadImages(ctx, tree) {
53
59
  const ref = crypto.createHash('sha1')
54
60
  .update(node.url)
55
61
  .digest('hex');
56
- const key = `${ref}.png`;
57
- node.data = ctx.images[key];
62
+ node.data = ctx.images[ref];
58
63
  if (node.data) {
59
64
  return;
60
65
  }
61
66
 
62
67
  let buffer;
68
+ let type;
63
69
  if (node.url.startsWith('data:image/png;base64,')) {
64
70
  buffer = Buffer.from(node.url.split(',').pop(), 'base64');
71
+ type = 'image/png';
65
72
  } else {
66
73
  const idx = String(count).padStart(2, ' ');
67
74
  count += 1;
@@ -73,15 +80,29 @@ export default async function downloadImages(ctx, tree) {
73
80
  return;
74
81
  }
75
82
  buffer = await ret.buffer();
76
- ctx.log.info(`[${idx}] ${ret.status} ${hsize(buffer.length).padStart(10)} ${ret.headers.get('content-type')}`);
83
+ type = ret.headers.get('content-type');
84
+ ctx.log.info(`[${idx}] ${ret.status} ${hsize(buffer.length).padStart(10)} ${type}`);
85
+ }
86
+ const dimensions = getDimensions(buffer);
87
+
88
+ let svgBuffer;
89
+ if (type === 'image/svg+xml' && ctx.svg2png) {
90
+ svgBuffer = buffer;
91
+ buffer = Buffer.from(await ctx.svg2png(svgBuffer.toString('utf-8')), 'base64');
92
+ type = 'image/png';
77
93
  }
78
94
 
95
+ const ext = mime.getExtension(type);
96
+
79
97
  node.data = {
80
- key,
98
+ ext,
99
+ key: `${ref}.${ext}`,
81
100
  buffer,
82
- dimensions: getDimensions(buffer),
101
+ svgBuffer,
102
+ svgKey: `${ref}.svg`,
103
+ dimensions,
83
104
  };
84
- ctx.images[key] = node.data;
105
+ ctx.images[ref] = node.data;
85
106
  } catch (error) {
86
107
  ctx.log.error(`Cannot download image ${node.url}: ${error.message}`);
87
108
  }
@@ -10,6 +10,13 @@
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
12
 
13
+ /**
14
+ * @typedef {import('docx').XmlComponent} XmlComponent
15
+ *
16
+ * @param {XmlComponent} root
17
+ * @param path
18
+ * @returns {XmlComponent}
19
+ */
13
20
  // eslint-disable-next-line import/prefer-default-export
14
21
  export function findXMLComponent(root, path) {
15
22
  const segs = path.split('/');