@adobe/helix-md2docx 1.4.26 → 2.0.1

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,22 @@
1
+ ## [2.0.1](https://github.com/adobe/helix-md2docx/compare/v2.0.0...v2.0.1) (2022-09-23)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * use @adobe/fetch ([c12b49e](https://github.com/adobe/helix-md2docx/commit/c12b49e9c79732e0a4c16564eca1363155190f96))
7
+
8
+ # [2.0.0](https://github.com/adobe/helix-md2docx/compare/v1.4.26...v2.0.0) (2022-09-23)
9
+
10
+
11
+ ### Features
12
+
13
+ * provide resource loader and converter plugin capability ([#136](https://github.com/adobe/helix-md2docx/issues/136)) ([0a6b703](https://github.com/adobe/helix-md2docx/commit/0a6b7034ca9118016a24197647d49835cddad83d)), closes [#133](https://github.com/adobe/helix-md2docx/issues/133) [#134](https://github.com/adobe/helix-md2docx/issues/134) [#135](https://github.com/adobe/helix-md2docx/issues/135)
14
+
15
+
16
+ ### BREAKING CHANGES
17
+
18
+ * svg2png was renamed to a more generic image2png
19
+
1
20
  ## [1.4.26](https://github.com/adobe/helix-md2docx/compare/v1.4.25...v1.4.26) (2022-09-12)
2
21
 
3
22
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/helix-md2docx",
3
- "version": "1.4.26",
3
+ "version": "2.0.1",
4
4
  "description": "Helix Service that converts markdown to word documents",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -27,8 +27,8 @@
27
27
  },
28
28
  "homepage": "https://github.com/adobe/helix-md2docx#readme",
29
29
  "dependencies": {
30
+ "@adobe/fetch": "3.1.4",
30
31
  "@adobe/helix-docx2md": "1.1.1",
31
- "@adobe/helix-fetch": "3.1.2",
32
32
  "@adobe/helix-markdown-support": "5.0.7",
33
33
  "@adobe/helix-shared-process-queue": "1.1.5",
34
34
  "docx": "7.4.1",
@@ -44,13 +44,13 @@
44
44
  },
45
45
  "devDependencies": {
46
46
  "@adobe/eslint-config-helix": "1.3.2",
47
- "@adobe/helix-mediahandler": "1.2.2",
47
+ "@adobe/helix-mediahandler": "1.2.4",
48
48
  "@semantic-release/changelog": "6.0.1",
49
49
  "@semantic-release/exec": "6.0.3",
50
50
  "@semantic-release/git": "10.0.1",
51
51
  "c8": "7.12.0",
52
52
  "dotenv": "16.0.2",
53
- "eslint": "8.23.0",
53
+ "eslint": "8.23.1",
54
54
  "eslint-import-resolver-exports": "1.0.0-beta.3",
55
55
  "eslint-plugin-header": "3.1.1",
56
56
  "eslint-plugin-import": "2.26.0",
@@ -59,6 +59,7 @@
59
59
  "lint-staged": "13.0.3",
60
60
  "mocha": "10.0.0",
61
61
  "mocha-multi-reporters": "1.5.1",
62
+ "nock": "13.2.9",
62
63
  "semantic-release": "19.0.5",
63
64
  "unist-util-inspect": "7.0.1",
64
65
  "yauzl": "2.10.0"
@@ -136,10 +136,10 @@ export default async function image(ctx, node) {
136
136
  }
137
137
  }
138
138
 
139
- if (data.ext === 'svg' || data.svgBuffer) {
139
+ if (data.originalType === 'image/svg') {
140
140
  // create a fake image run for the svg image
141
141
  const ir = new ImageRun({
142
- data: data.svgBuffer,
142
+ data: data.originalBuffer,
143
143
  transformation: data.dimensions,
144
144
  });
145
145
  ir.imageData.fileName = data.svgKey;
@@ -10,14 +10,38 @@
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
12
 
13
+ import {RequestOptions, Response} from "@adobe/helix-fetch";
14
+
15
+ declare interface ImageToPngOptions {
16
+ /**
17
+ * image data
18
+ */
19
+ data:Buffer|ArrayBuffer;
20
+ /**
21
+ * informational src information
22
+ */
23
+ src?:string;
24
+ /**
25
+ * image content type, if available.
26
+ */
27
+ type?:string;
28
+ }
29
+
30
+ declare interface ImageToPngResult {
31
+ data:Buffer|ArrayBuffer;
32
+ type?:string;
33
+ width:number;
34
+ height:number;
35
+ }
36
+
37
+ declare type ImageToPngConverter = (opts:ImageToPngOptions) => Promise<ImageToPngResult>;
38
+
13
39
  /**
14
- * Converts an SVG to a PNG.
15
- *
16
- * @function
17
- * @param {string} svg the input svg
18
- * @return {string} the base64 encoded png
40
+ * Loader used for loading resources for urls starting with `res:`
19
41
  */
20
- declare type Svg2PngConverter = (svg:string) => Promise<string>;
42
+ declare interface ResourceLoader {
43
+ fetch(url:string, opts:RequestOptions): Promise<Response>
44
+ }
21
45
 
22
46
  declare interface Mdast2DocxOptions {
23
47
  /**
@@ -30,7 +54,15 @@ declare interface Mdast2DocxOptions {
30
54
  */
31
55
  stylesXML: string;
32
56
 
33
- svg2png?: Svg2PngConverter;
57
+ /**
58
+ * Optional loader for (image) resources
59
+ */
60
+ resourceLoader?:ResourceLoader;
61
+
62
+ /**
63
+ * Optional image2png converter
64
+ */
65
+ image2png?: ImageToPngConverter
34
66
  }
35
67
 
36
68
  /**
@@ -27,7 +27,8 @@ const { Document, Packer } = docx;
27
27
  export default async function mdast2docx(mdast, opts = {}) {
28
28
  const {
29
29
  log = console,
30
- svg2png,
30
+ resourceLoader,
31
+ image2png,
31
32
  } = opts;
32
33
 
33
34
  let {
@@ -42,7 +43,8 @@ export default async function mdast2docx(mdast, opts = {}) {
42
43
  listLevel: -1,
43
44
  lists: [],
44
45
  log,
45
- svg2png,
46
+ image2png,
47
+ resourceLoader,
46
48
  };
47
49
 
48
50
  // eslint-disable-next-line no-param-reassign
@@ -11,7 +11,7 @@
11
11
  */
12
12
  /* eslint-disable no-param-reassign */
13
13
  import crypto from 'crypto';
14
- import { context as fetchAPI, h1 } from '@adobe/helix-fetch';
14
+ import { context as fetchAPI, h1 } from '@adobe/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';
@@ -40,6 +40,7 @@ function hsize(bytes, decimals = 2) {
40
40
  * @returns {Promise<void>}
41
41
  */
42
42
  export default async function downloadImages(ctx, tree) {
43
+ const { log, resourceLoader, image2png } = ctx;
43
44
  const context = createFetchContext();
44
45
  const { fetch } = context;
45
46
 
@@ -63,33 +64,77 @@ export default async function downloadImages(ctx, tree) {
63
64
  if (node.data) {
64
65
  return;
65
66
  }
66
-
67
+ const idx = String(count).padStart(2, ' ');
68
+ count += 1;
67
69
  let buffer;
68
70
  let type;
69
- if (node.url.startsWith('data:image/png;base64,')) {
70
- buffer = Buffer.from(node.url.split(',').pop(), 'base64');
71
- type = 'image/png';
71
+ let maybeConvert;
72
+ let dimensions = {
73
+ width: 100,
74
+ height: 100,
75
+ };
76
+ if (node.url.startsWith('data:')) {
77
+ const [prefix, data] = node.url.substring(5).split(',');
78
+ const [typ, enc] = prefix.split(';');
79
+ if (enc !== 'base64') {
80
+ log.warn(`[${idx}] Error decoding data url. unknown encoding: ${enc}`);
81
+ return;
82
+ }
83
+ buffer = Buffer.from(data, 'base64');
84
+ type = typ;
72
85
  } else {
73
- const idx = String(count).padStart(2, ' ');
74
- count += 1;
75
- ctx.log.info(`[${idx}] GET ${node.url}`);
76
- const ret = await fetch(node.url);
86
+ log.info(`[${idx}] GET ${node.url}`);
87
+
88
+ let doFetch = fetch;
89
+ if (node.url.startsWith('res:')) {
90
+ if (!resourceLoader) {
91
+ log.warn(`[${idx}] Error loading image ${node.url}. resource loader missing.`);
92
+ return;
93
+ }
94
+ doFetch = resourceLoader.fetch.bind(resourceLoader);
95
+ }
96
+
97
+ const ret = await doFetch(node.url);
77
98
  if (!ret.ok) {
78
99
  const text = await ret.text();
79
- ctx.log.error(`[${idx}] ${ret.status} ${text}`);
100
+ log.error(`[${idx}] ${ret.status} ${text}`);
80
101
  return;
81
102
  }
82
103
  buffer = await ret.buffer();
83
104
  type = ret.headers.get('content-type');
84
- ctx.log.info(`[${idx}] ${ret.status} ${hsize(buffer.length).padStart(10)} ${type}`);
105
+ log.info(`[${idx}] ${ret.status} ${hsize(buffer.length).padStart(10)} ${type}`);
85
106
  }
86
- const dimensions = getDimensions(buffer);
87
107
 
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';
108
+ maybeConvert = type !== 'image/png' && type !== 'image/jpg' && type !== 'image/jpeg' && type !== 'image/gif';
109
+ try {
110
+ dimensions = getDimensions(buffer);
111
+ } catch (e) {
112
+ maybeConvert = true;
113
+ log.warn(`[${idx}] Error detecting dimensions: ${e} ${type}`);
114
+ }
115
+
116
+ let originalBuffer;
117
+ let originalType;
118
+ if (maybeConvert && image2png) {
119
+ try {
120
+ const result = await image2png({
121
+ src: node.url,
122
+ data: buffer,
123
+ type,
124
+ });
125
+ if (result) {
126
+ originalBuffer = buffer;
127
+ originalType = type;
128
+ buffer = result.data;
129
+ type = result.type;
130
+ dimensions = {
131
+ width: result.width,
132
+ height: result.height,
133
+ };
134
+ }
135
+ } catch (e) {
136
+ log.warn(`[${idx}] Error to convert to png`, e);
137
+ }
93
138
  }
94
139
 
95
140
  const ext = mime.getExtension(type);
@@ -98,13 +143,14 @@ export default async function downloadImages(ctx, tree) {
98
143
  ext,
99
144
  key: `${ref}.${ext}`,
100
145
  buffer,
101
- svgBuffer,
102
- svgKey: `${ref}.svg`,
146
+ originalBuffer,
147
+ type,
148
+ originalType,
103
149
  dimensions,
104
150
  };
105
151
  ctx.images[ref] = node.data;
106
152
  } catch (error) {
107
- ctx.log.error(`Cannot download image ${node.url}: ${error.message}`);
153
+ log.error(`Cannot download image ${node.url}: ${error.message}`);
108
154
  }
109
155
  }, 8);
110
156