@muhammara/native-core 7.0.0-beta.4 → 7.0.0-rc.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/README.md +57 -7
- package/THIRD_PARTY_NOTICES.md +39 -0
- package/index.js +120 -1
- package/lib/PDFRStreamForBuffer.js +52 -7
- package/lib/PDFRStreamForFile.js +56 -7
- package/lib/PDFStreamForResponse.js +22 -5
- package/lib/PDFWStreamForBuffer.js +49 -10
- package/lib/PDFWStreamForFile.js +26 -5
- package/lib/Recipe.js +79 -28
- package/lib/content-stream.js +503 -0
- package/lib/css-colors.js +149 -0
- package/lib/font-text.js +1461 -0
- package/lib/glyph-list.js +1127 -0
- package/lib/recipe/annotation.js +464 -101
- package/lib/recipe/appendPage.js +2 -0
- package/lib/recipe/colors.js +132 -35
- package/lib/recipe/coordinate.js +82 -16
- package/lib/recipe/encrypt.js +32 -13
- package/lib/recipe/font.js +70 -25
- package/lib/recipe/graphics-state.js +3 -2
- package/lib/recipe/htmlToTextObjects.js +121 -24
- package/lib/recipe/image.js +34 -9
- package/lib/recipe/info.js +93 -22
- package/lib/recipe/insertPage.js +12 -3
- package/lib/recipe/overlay.js +4 -2
- package/lib/recipe/page-references.js +436 -0
- package/lib/recipe/page.js +363 -262
- package/lib/recipe/parameters.js +8 -1
- package/lib/recipe/replaceText.js +464 -30
- package/lib/recipe/shapes.js +418 -45
- package/lib/recipe/split.js +6 -2
- package/lib/recipe/table.js +41 -8
- package/lib/recipe/text.helper.js +172 -8
- package/lib/recipe/text.js +294 -56
- package/lib/recipe/utils.js +17 -6
- package/lib/recipe/vector-line.js +101 -39
- package/lib/recipe/vector-polygon.js +18 -6
- package/lib/recipe/vector.helper.js +148 -6
- package/lib/recipe/vector.js +150 -51
- package/lib/recipe/xObjectForm.js +73 -10
- package/lib/recipe-constants.js +373 -0
- package/muhammara.d.ts +3469 -155
- package/package.json +1 -1
package/lib/recipe/font.js
CHANGED
|
@@ -1,23 +1,44 @@
|
|
|
1
1
|
const fs = require("fs");
|
|
2
2
|
const path = require("path");
|
|
3
|
+
const { FontStyle } = require("../recipe-constants");
|
|
4
|
+
|
|
5
|
+
// Keys of the per-style font files stored for each family in this.fonts.
|
|
6
|
+
const FontSlot = Object.freeze({
|
|
7
|
+
REGULAR: "r",
|
|
8
|
+
BOLD: "b",
|
|
9
|
+
ITALIC: "i",
|
|
10
|
+
BOLD_ITALIC: "bi",
|
|
11
|
+
});
|
|
12
|
+
|
|
3
13
|
/**
|
|
4
14
|
* Register a custom font
|
|
5
15
|
* @name registerFont
|
|
6
16
|
* @function
|
|
7
17
|
* @memberof Recipe#
|
|
8
|
-
* @param {string} [fontName=''] - The font name used in text.
|
|
18
|
+
* @param {string} [fontName=''] - The font name used in text, matched case-insensitively.
|
|
9
19
|
* @param {string} [fontSrcPath=''] - The path to the font file.
|
|
10
|
-
* @param {
|
|
20
|
+
* @param {Recipe.FontStyle} [type='regular'] - The style this file provides,
|
|
21
|
+
* one of the `Recipe.FontStyle` values or its short form r, b, i or bi.
|
|
22
|
+
* Any other value registers the regular style.
|
|
11
23
|
* @returns {Recipe} The recipe instance.
|
|
12
24
|
*/
|
|
13
25
|
exports.registerFont = function registerFont(
|
|
14
26
|
fontName = "",
|
|
15
27
|
fontSrcPath = "",
|
|
16
|
-
type =
|
|
28
|
+
type = FontStyle.REGULAR,
|
|
17
29
|
) {
|
|
18
30
|
return this._registerFont(fontName, fontSrcPath, type);
|
|
19
31
|
};
|
|
20
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Register every .ttf, .ttc and .otf file in the given directories, deriving
|
|
35
|
+
* the family and style from the file name (for example "Roboto-BoldItalic").
|
|
36
|
+
* Missing directories are skipped.
|
|
37
|
+
* @private
|
|
38
|
+
* @param {string|string[]} fontSrcPath - The font directory or directories.
|
|
39
|
+
* @returns {void}
|
|
40
|
+
* @throws {Error} If an existing directory cannot be read.
|
|
41
|
+
*/
|
|
21
42
|
exports._loadFonts = function _loadFonts(fontSrcPath) {
|
|
22
43
|
const fontTypes = [".ttf", ".ttc", ".otf"];
|
|
23
44
|
const fontPaths =
|
|
@@ -33,19 +54,19 @@ exports._loadFonts = function _loadFonts(fontSrcPath) {
|
|
|
33
54
|
.forEach((file) => {
|
|
34
55
|
let fontName = path.basename(file, path.extname(file)).toLowerCase();
|
|
35
56
|
// simple heuristics to make sure library fonts behave as expected
|
|
36
|
-
const hasBold = fontName.indexOf(
|
|
37
|
-
const hasItalic = fontName.indexOf(
|
|
38
|
-
let type =
|
|
57
|
+
const hasBold = fontName.indexOf(FontStyle.BOLD) !== -1;
|
|
58
|
+
const hasItalic = fontName.indexOf(FontStyle.ITALIC) !== -1;
|
|
59
|
+
let type = FontSlot.REGULAR;
|
|
39
60
|
if (hasBold && hasItalic) {
|
|
40
61
|
fontName = fontName.replace(/-*bold/, "");
|
|
41
62
|
fontName = fontName.replace(/-*italic/, "");
|
|
42
|
-
type =
|
|
63
|
+
type = FontSlot.BOLD_ITALIC;
|
|
43
64
|
} else if (hasBold) {
|
|
44
65
|
fontName = fontName.replace(/-*bold/, "");
|
|
45
|
-
type =
|
|
66
|
+
type = FontSlot.BOLD;
|
|
46
67
|
} else if (hasItalic) {
|
|
47
68
|
fontName = fontName.replace(/-*italic/, "");
|
|
48
|
-
type =
|
|
69
|
+
type = FontSlot.ITALIC;
|
|
49
70
|
}
|
|
50
71
|
return this._registerFont(fontName, path.join(fpath, file), type);
|
|
51
72
|
});
|
|
@@ -53,10 +74,19 @@ exports._loadFonts = function _loadFonts(fontSrcPath) {
|
|
|
53
74
|
}
|
|
54
75
|
};
|
|
55
76
|
|
|
77
|
+
/**
|
|
78
|
+
* Record a font file for a family and style.
|
|
79
|
+
* @private
|
|
80
|
+
* @param {string} fontName - The family name, stored lower-cased.
|
|
81
|
+
* @param {string} fontSrcPath - The font file path.
|
|
82
|
+
* @param {Recipe.FontStyle|string} [type='regular'] - A `Recipe.FontStyle`
|
|
83
|
+
* value or its short form; anything else is stored as regular.
|
|
84
|
+
* @returns {Recipe} The recipe instance.
|
|
85
|
+
*/
|
|
56
86
|
exports._registerFont = function _registerFont(
|
|
57
87
|
fontName,
|
|
58
88
|
fontSrcPath,
|
|
59
|
-
type =
|
|
89
|
+
type = FontStyle.REGULAR,
|
|
60
90
|
) {
|
|
61
91
|
this.fonts = this.fonts || {};
|
|
62
92
|
let family = fontName.toLowerCase();
|
|
@@ -64,19 +94,19 @@ exports._registerFont = function _registerFont(
|
|
|
64
94
|
|
|
65
95
|
switch (type) {
|
|
66
96
|
default:
|
|
67
|
-
font.
|
|
97
|
+
font[FontSlot.REGULAR] = fontSrcPath;
|
|
68
98
|
break;
|
|
69
|
-
case
|
|
70
|
-
case
|
|
71
|
-
font.
|
|
99
|
+
case FontStyle.BOLD:
|
|
100
|
+
case FontSlot.BOLD:
|
|
101
|
+
font[FontSlot.BOLD] = fontSrcPath;
|
|
72
102
|
break;
|
|
73
|
-
case
|
|
74
|
-
case
|
|
75
|
-
font.
|
|
103
|
+
case FontStyle.ITALIC:
|
|
104
|
+
case FontSlot.ITALIC:
|
|
105
|
+
font[FontSlot.ITALIC] = fontSrcPath;
|
|
76
106
|
break;
|
|
77
|
-
case
|
|
78
|
-
case
|
|
79
|
-
font.
|
|
107
|
+
case FontStyle.BOLD_ITALIC:
|
|
108
|
+
case FontSlot.BOLD_ITALIC:
|
|
109
|
+
font[FontSlot.BOLD_ITALIC] = fontSrcPath;
|
|
80
110
|
break;
|
|
81
111
|
}
|
|
82
112
|
this.fonts[family] = font;
|
|
@@ -84,18 +114,26 @@ exports._registerFont = function _registerFont(
|
|
|
84
114
|
return this;
|
|
85
115
|
};
|
|
86
116
|
|
|
117
|
+
/**
|
|
118
|
+
* Pick the registered font file for the family and bold/italic options,
|
|
119
|
+
* falling back to Helvetica when the family, style or file is unavailable.
|
|
120
|
+
* @private
|
|
121
|
+
* @param {Recipe} self - The recipe instance.
|
|
122
|
+
* @param {Object} [options] - Text options: font, and bold/isBold, italic/isItalic.
|
|
123
|
+
* @returns {string} The font file path.
|
|
124
|
+
*/
|
|
87
125
|
function _getFontFile(self, options = {}) {
|
|
88
126
|
let fontFile;
|
|
89
127
|
// Need to choose appropriate file based on bold/italic considerations
|
|
90
128
|
// Note, if this is not done explicitly, the font dimensions will be incorrect.
|
|
91
129
|
let type =
|
|
92
130
|
(options.bold || options.isBold) && (options.italic || options.isItalic)
|
|
93
|
-
?
|
|
131
|
+
? FontSlot.BOLD_ITALIC
|
|
94
132
|
: options.italic || options.isItalic
|
|
95
|
-
?
|
|
133
|
+
? FontSlot.ITALIC
|
|
96
134
|
: options.bold || options.isBold
|
|
97
|
-
?
|
|
98
|
-
:
|
|
135
|
+
? FontSlot.BOLD
|
|
136
|
+
: FontSlot.REGULAR;
|
|
99
137
|
|
|
100
138
|
if (options.font) {
|
|
101
139
|
const fontFamily = self.fonts[options.font.toLowerCase()];
|
|
@@ -110,12 +148,19 @@ function _getFontFile(self, options = {}) {
|
|
|
110
148
|
}
|
|
111
149
|
|
|
112
150
|
if (!fontFile) {
|
|
113
|
-
fontFile = self.fonts["helvetica"][
|
|
151
|
+
fontFile = self.fonts["helvetica"][FontSlot.REGULAR]; // use default font when otherwise unavailable.
|
|
114
152
|
}
|
|
115
153
|
|
|
116
154
|
return fontFile;
|
|
117
155
|
}
|
|
118
156
|
|
|
157
|
+
/**
|
|
158
|
+
* Load the writer font for text options once per font file.
|
|
159
|
+
* @private
|
|
160
|
+
* @param {Object} [options] - Text options: font, and bold/isBold, italic/isItalic.
|
|
161
|
+
* @returns {Object} The writer's used font.
|
|
162
|
+
* @throws {Error} If the font file cannot be loaded.
|
|
163
|
+
*/
|
|
119
164
|
exports._getFont = function _getFont(options) {
|
|
120
165
|
this.current = this.current || {};
|
|
121
166
|
|
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
* @function
|
|
5
5
|
* @memberof Recipe#
|
|
6
6
|
* @param {number} degrees - Clockwise rotation in degrees.
|
|
7
|
-
* @param {number} [x=0] - Rotation origin x coordinate.
|
|
8
|
-
* @param {number} [y=0] - Rotation origin y coordinate.
|
|
7
|
+
* @param {number|"center"} [x=0] - Rotation origin x coordinate.
|
|
8
|
+
* @param {number|"center"} [y=0] - Rotation origin y coordinate.
|
|
9
9
|
* @returns {Recipe} The recipe instance.
|
|
10
|
+
* @throws {TypeError} If no page is active.
|
|
10
11
|
*/
|
|
11
12
|
exports.rotateContent = function rotateContent(degrees, x = 0, y = 0) {
|
|
12
13
|
const radians = (degrees * Math.PI) / 180;
|
|
@@ -1,13 +1,52 @@
|
|
|
1
1
|
const DOMParser = require("@xmldom/xmldom").DOMParser;
|
|
2
2
|
|
|
3
|
+
// HTML attribute and inline style names the parser reads.
|
|
4
|
+
const HtmlAttribute = Object.freeze({
|
|
5
|
+
STYLE: "style",
|
|
6
|
+
HREF: "href",
|
|
7
|
+
});
|
|
8
|
+
const CssProperty = Object.freeze({
|
|
9
|
+
COLOR: "color",
|
|
10
|
+
OPACITY: "opacity",
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
// CSS color functions whose components the parser reads.
|
|
14
|
+
const CssColorFunction = Object.freeze({
|
|
15
|
+
RGB: "rgb",
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
// Lower-case names of the HTML elements the parser handles.
|
|
19
|
+
const HtmlTag = Object.freeze({
|
|
20
|
+
HTML: "html",
|
|
21
|
+
P: "p",
|
|
22
|
+
LI: "li",
|
|
23
|
+
UL: "ul",
|
|
24
|
+
OL: "ol",
|
|
25
|
+
H1: "h1",
|
|
26
|
+
H2: "h2",
|
|
27
|
+
H3: "h3",
|
|
28
|
+
SMALL: "small",
|
|
29
|
+
BR: "br",
|
|
30
|
+
B: "b",
|
|
31
|
+
STRONG: "strong",
|
|
32
|
+
I: "i",
|
|
33
|
+
EM: "em",
|
|
34
|
+
U: "u",
|
|
35
|
+
DEL: "del",
|
|
36
|
+
A: "a",
|
|
37
|
+
});
|
|
38
|
+
|
|
3
39
|
/**
|
|
4
40
|
* Convert HTML into Recipe text layout objects.
|
|
5
41
|
* @name htmlToTextObjects
|
|
6
42
|
* @function
|
|
7
43
|
* @memberof Recipe#
|
|
8
|
-
* @param {string} htmlCodes - The HTML source.
|
|
44
|
+
* @param {string} htmlCodes - The HTML source. Tag names are matched case-insensitively.
|
|
9
45
|
* @param {Object} [options] - Text options used to initialize the objects.
|
|
10
|
-
* @
|
|
46
|
+
* @param {string} [options.font] - The font of every object.
|
|
47
|
+
* @param {number} [options.size] - The base font size of every object.
|
|
48
|
+
* @returns {Object[]} The parsed text layout objects: one per child node,
|
|
49
|
+
* each with its value, tag, style flags, link, font size ratio and childs.
|
|
11
50
|
*/
|
|
12
51
|
exports.htmlToTextObjects = function (htmlCodes, options = {}) {
|
|
13
52
|
const nodes = new DOMParser().parseFromString(
|
|
@@ -18,13 +57,19 @@ exports.htmlToTextObjects = function (htmlCodes, options = {}) {
|
|
|
18
57
|
return textObjects;
|
|
19
58
|
};
|
|
20
59
|
|
|
60
|
+
/**
|
|
61
|
+
* The font size multiplier of an element.
|
|
62
|
+
* @private
|
|
63
|
+
* @param {string} [tagName=''] - The element name, matched case-insensitively.
|
|
64
|
+
* @returns {number} The multiplier; 1 for elements without one.
|
|
65
|
+
*/
|
|
21
66
|
function getFontSizeRatio(tagName = "") {
|
|
22
67
|
const fontSizeRatio = {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
68
|
+
[HtmlTag.P]: 1, // 14px
|
|
69
|
+
[HtmlTag.H1]: 2.57, // 36px
|
|
70
|
+
[HtmlTag.H2]: 2.14, // 30px
|
|
71
|
+
[HtmlTag.H3]: 1.71, // 24px
|
|
72
|
+
[HtmlTag.SMALL]: 0.7,
|
|
28
73
|
// h4: 1.12,
|
|
29
74
|
// h5: 0.83,
|
|
30
75
|
// h6: 0.75
|
|
@@ -33,9 +78,21 @@ function getFontSizeRatio(tagName = "") {
|
|
|
33
78
|
return matched ? matched : 1;
|
|
34
79
|
}
|
|
35
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Whether an element starts its content on a new line.
|
|
83
|
+
* @private
|
|
84
|
+
* @param {string} [tagName=''] - The element name, matched case-insensitively.
|
|
85
|
+
* @returns {boolean} True for p, li and h1 to h3.
|
|
86
|
+
*/
|
|
36
87
|
function needsLineBreaker(tagName = "") {
|
|
37
|
-
const lineBreakers = [
|
|
38
|
-
|
|
88
|
+
const lineBreakers = [
|
|
89
|
+
HtmlTag.P,
|
|
90
|
+
HtmlTag.LI,
|
|
91
|
+
HtmlTag.H1,
|
|
92
|
+
HtmlTag.H2,
|
|
93
|
+
HtmlTag.H3,
|
|
94
|
+
];
|
|
95
|
+
return lineBreakers.includes(String(tagName).toLowerCase());
|
|
39
96
|
}
|
|
40
97
|
|
|
41
98
|
/**
|
|
@@ -43,12 +100,18 @@ function needsLineBreaker(tagName = "") {
|
|
|
43
100
|
* block element, or at the start of a block. Only such text drops its
|
|
44
101
|
* leading whitespace; text that follows inline content keeps one space.
|
|
45
102
|
* @private
|
|
103
|
+
* @param {Object} node - The DOM text node.
|
|
104
|
+
* @returns {boolean} True when the text begins a visual line.
|
|
46
105
|
*/
|
|
47
106
|
function startsLine(node) {
|
|
48
107
|
const previous = node.previousSibling;
|
|
49
108
|
if (previous) {
|
|
50
109
|
const tag = (previous.tagName || "").toLowerCase();
|
|
51
|
-
return
|
|
110
|
+
return (
|
|
111
|
+
tag === HtmlTag.BR ||
|
|
112
|
+
needsLineBreaker(tag) ||
|
|
113
|
+
[HtmlTag.UL, HtmlTag.OL].includes(tag)
|
|
114
|
+
);
|
|
52
115
|
}
|
|
53
116
|
const parent = node.parentNode;
|
|
54
117
|
const parentTag = (
|
|
@@ -57,7 +120,7 @@ function startsLine(node) {
|
|
|
57
120
|
if (
|
|
58
121
|
!parent ||
|
|
59
122
|
!parentTag ||
|
|
60
|
-
parentTag ===
|
|
123
|
+
parentTag === HtmlTag.HTML ||
|
|
61
124
|
needsLineBreaker(parentTag)
|
|
62
125
|
) {
|
|
63
126
|
return true;
|
|
@@ -65,17 +128,37 @@ function startsLine(node) {
|
|
|
65
128
|
return startsLine(parent);
|
|
66
129
|
}
|
|
67
130
|
|
|
131
|
+
/**
|
|
132
|
+
* Whether an element makes its text bold.
|
|
133
|
+
* @private
|
|
134
|
+
* @param {string} [tagName=''] - The element name, matched case-insensitively.
|
|
135
|
+
* @returns {boolean} True for b and strong.
|
|
136
|
+
*/
|
|
68
137
|
function isBoldTag(tagName = "") {
|
|
69
|
-
const boldTags = [
|
|
70
|
-
return boldTags.includes(tagName);
|
|
138
|
+
const boldTags = [HtmlTag.B, HtmlTag.STRONG];
|
|
139
|
+
return boldTags.includes(String(tagName).toLowerCase());
|
|
71
140
|
}
|
|
72
141
|
|
|
142
|
+
/**
|
|
143
|
+
* Whether an element makes its text italic.
|
|
144
|
+
* @private
|
|
145
|
+
* @param {string} [tagName=''] - The element name, matched case-insensitively.
|
|
146
|
+
* @returns {boolean} True for i and em.
|
|
147
|
+
*/
|
|
73
148
|
function isItalicTag(tagName = "") {
|
|
74
|
-
const italicTags = [
|
|
75
|
-
return italicTags.includes(tagName);
|
|
149
|
+
const italicTags = [HtmlTag.I, HtmlTag.EM];
|
|
150
|
+
return italicTags.includes(String(tagName).toLowerCase());
|
|
76
151
|
}
|
|
77
152
|
|
|
153
|
+
/**
|
|
154
|
+
* Convert a DOM node and its children into a text layout object.
|
|
155
|
+
* @private
|
|
156
|
+
* @param {Object} node - The DOM node.
|
|
157
|
+
* @param {Object} options - The htmlToTextObjects() options.
|
|
158
|
+
* @returns {Object} The text layout object with its parsed childs.
|
|
159
|
+
*/
|
|
78
160
|
function parseNode(node, options) {
|
|
161
|
+
const tag = (node.tagName || "").toLowerCase();
|
|
79
162
|
const attributes = [];
|
|
80
163
|
const styles = {};
|
|
81
164
|
for (let i in node.attributes) {
|
|
@@ -84,22 +167,22 @@ function parseNode(node, options) {
|
|
|
84
167
|
name: node.attributes[i].nodeName,
|
|
85
168
|
value: node.attributes[i].nodeValue,
|
|
86
169
|
});
|
|
87
|
-
if (node.attributes[i].nodeName
|
|
170
|
+
if (node.attributes[i].nodeName === HtmlAttribute.STYLE) {
|
|
88
171
|
const styleValues = node.attributes[i].nodeValue.split(";");
|
|
89
172
|
styleValues.forEach((element) => {
|
|
90
173
|
if (element && element != "") {
|
|
91
174
|
element = element.split(":");
|
|
92
175
|
const key = element[0];
|
|
93
176
|
let value = element[1].replace(/ /g, "");
|
|
94
|
-
if (key
|
|
95
|
-
if (value.search(
|
|
177
|
+
if (key === CssProperty.COLOR) {
|
|
178
|
+
if (value.search(CssColorFunction.RGB) > -1) {
|
|
96
179
|
value = value
|
|
97
180
|
.replace(/rgba?\(/, "")
|
|
98
181
|
.replace(/\)/, "")
|
|
99
182
|
.split(",")
|
|
100
183
|
.map((item) => parseFloat(item));
|
|
101
184
|
if (value.length > 3) {
|
|
102
|
-
styles[
|
|
185
|
+
styles[CssProperty.OPACITY] = value.pop();
|
|
103
186
|
}
|
|
104
187
|
}
|
|
105
188
|
}
|
|
@@ -115,7 +198,11 @@ function parseNode(node, options) {
|
|
|
115
198
|
}
|
|
116
199
|
// Whitespace before a line break would only pad the end of the line.
|
|
117
200
|
const next = node.nextSibling;
|
|
118
|
-
if (
|
|
201
|
+
if (
|
|
202
|
+
value !== null &&
|
|
203
|
+
next &&
|
|
204
|
+
(next.tagName || "").toLowerCase() === HtmlTag.BR
|
|
205
|
+
) {
|
|
119
206
|
value = value.replace(/\s+$/, "");
|
|
120
207
|
}
|
|
121
208
|
if (value && value.charCodeAt(0) == 8203) {
|
|
@@ -128,17 +215,24 @@ function parseNode(node, options) {
|
|
|
128
215
|
font: options.font,
|
|
129
216
|
isBold: isBoldTag(node.tagName),
|
|
130
217
|
isItalic: isItalicTag(node.tagName),
|
|
131
|
-
underline:
|
|
132
|
-
strikeOut:
|
|
218
|
+
underline: tag === HtmlTag.U,
|
|
219
|
+
strikeOut: tag === HtmlTag.DEL,
|
|
133
220
|
attributes,
|
|
134
221
|
styles,
|
|
135
222
|
needsLineBreaker: needsLineBreaker(node.tagName),
|
|
136
223
|
// An explicit line break; text layout ends the current line here.
|
|
137
|
-
lineBreak:
|
|
224
|
+
lineBreak: tag === HtmlTag.BR,
|
|
138
225
|
size: options.size,
|
|
139
226
|
sizeRatio: getFontSizeRatio(node.tagName),
|
|
140
227
|
sizeRatios: [getFontSizeRatio(node.tagName)],
|
|
141
|
-
link:
|
|
228
|
+
link:
|
|
229
|
+
tag === HtmlTag.A
|
|
230
|
+
? (
|
|
231
|
+
attributes.find(
|
|
232
|
+
(attribute) => attribute.name === HtmlAttribute.HREF,
|
|
233
|
+
) || {}
|
|
234
|
+
).value || null
|
|
235
|
+
: null,
|
|
142
236
|
childs: [],
|
|
143
237
|
};
|
|
144
238
|
for (let num in node.childNodes) {
|
|
@@ -153,3 +247,6 @@ function parseNode(node, options) {
|
|
|
153
247
|
});
|
|
154
248
|
return parsedData;
|
|
155
249
|
}
|
|
250
|
+
|
|
251
|
+
// Shared with text layout; non-enumerable so it is not a Recipe method.
|
|
252
|
+
Object.defineProperty(exports, "HtmlTag", { value: HtmlTag });
|
package/lib/recipe/image.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
+
const muhammara = require("../muhammara");
|
|
2
|
+
const { HorizontalAlign, VerticalAlign } = require("../recipe-constants");
|
|
3
|
+
|
|
1
4
|
/**
|
|
2
5
|
* Place images to pdf
|
|
3
6
|
* @name image
|
|
4
7
|
* @function
|
|
5
8
|
* @memberof Recipe#
|
|
6
|
-
* @param {string} imgSrc - The path for the image. [JPEG, PNG, TIFF]
|
|
7
|
-
* @param {number} x - The coordinate x
|
|
8
|
-
* @param {number} y - The coordinate y
|
|
9
|
+
* @param {string} imgSrc - The path for the image. [JPEG, PNG, TIFF, PDF]
|
|
10
|
+
* @param {number|"center"} x - The coordinate x of the top-left corner
|
|
11
|
+
* @param {number|"center"} y - The coordinate y of the top-left corner
|
|
9
12
|
* @param {Object} [options] - The options
|
|
10
13
|
* @returns {Recipe} The recipe instance.
|
|
11
14
|
* @param {number} [options.width] - The new width
|
|
@@ -13,7 +16,14 @@
|
|
|
13
16
|
* @param {number} [options.scale] - Scale the image from the original width and height.
|
|
14
17
|
* @param {boolean} [options.keepAspectRatio=true] - Keep the aspect ratio.
|
|
15
18
|
* @param {number} [options.opacity] - The opacity.
|
|
16
|
-
* @param {string} [options.align] -
|
|
19
|
+
* @param {string} [options.align] - A `Recipe.HorizontalAlign` value, optionally
|
|
20
|
+
* followed by a space and a `Recipe.VerticalAlign` value, for example
|
|
21
|
+
* "center center". Horizontal center moves the image left by half its width
|
|
22
|
+
* and right moves it right by half; vertical center moves it up by half its
|
|
23
|
+
* height and bottom moves it down by half from its top-left placement.
|
|
24
|
+
* @param {string} [options.link] - Make the image open this URL.
|
|
25
|
+
* @throws {TypeError} If no page is active.
|
|
26
|
+
* @throws {Error} If the image cannot be read.
|
|
17
27
|
*/
|
|
18
28
|
exports.image = function image(imgSrc, x, y, options = {}) {
|
|
19
29
|
const { width, height, offsetX, offsetY } = this._getImgOffset(
|
|
@@ -22,7 +32,7 @@ exports.image = function image(imgSrc, x, y, options = {}) {
|
|
|
22
32
|
);
|
|
23
33
|
const imgOptions = {
|
|
24
34
|
transformation: {
|
|
25
|
-
fit:
|
|
35
|
+
fit: muhammara.ImageFit.ALWAYS,
|
|
26
36
|
// proportional: true,
|
|
27
37
|
width,
|
|
28
38
|
height,
|
|
@@ -65,11 +75,26 @@ exports.image = function image(imgSrc, x, y, options = {}) {
|
|
|
65
75
|
return this;
|
|
66
76
|
};
|
|
67
77
|
|
|
78
|
+
/**
|
|
79
|
+
* Compute the drawn size of an image and the offset its alignment applies.
|
|
80
|
+
* Sets `options.keepAspectRatio` to true when it is not given.
|
|
81
|
+
* @private
|
|
82
|
+
* @param {string} [imgSrc=''] - The image path.
|
|
83
|
+
* @param {Object} [options] - The image() options.
|
|
84
|
+
* @returns {{width: number, height: number, offsetX: number, offsetY: number}}
|
|
85
|
+
* The drawn size and the PDF offset from the placement point.
|
|
86
|
+
* @throws {Error} If the image cannot be read.
|
|
87
|
+
*/
|
|
68
88
|
exports._getImgOffset = function _getImgOffset(imgSrc = "", options = {}) {
|
|
69
89
|
// set default to true
|
|
70
90
|
options.keepAspectRatio =
|
|
71
91
|
options.keepAspectRatio == void 0 ? true : options.keepAspectRatio;
|
|
72
92
|
const dimensions = this.writer.getImageDimensions(imgSrc);
|
|
93
|
+
// An unreadable image measures 0 by 0. Drawing it would leave an image
|
|
94
|
+
// object that is never written, so endPDF() could not finish the PDF.
|
|
95
|
+
if (!(dimensions.width > 0 && dimensions.height > 0)) {
|
|
96
|
+
throw new Error(`Unknown image: ${imgSrc}`);
|
|
97
|
+
}
|
|
73
98
|
const ratio = dimensions.width / dimensions.height;
|
|
74
99
|
|
|
75
100
|
let width = dimensions.width;
|
|
@@ -105,10 +130,10 @@ exports._getImgOffset = function _getImgOffset(imgSrc = "", options = {}) {
|
|
|
105
130
|
const alignments = options.align.split(" ");
|
|
106
131
|
if (alignments[0]) {
|
|
107
132
|
switch (alignments[0]) {
|
|
108
|
-
case
|
|
133
|
+
case HorizontalAlign.CENTER:
|
|
109
134
|
offsetX = (-1 * width) / 2;
|
|
110
135
|
break;
|
|
111
|
-
case
|
|
136
|
+
case HorizontalAlign.RIGHT:
|
|
112
137
|
offsetX = width / 2;
|
|
113
138
|
break;
|
|
114
139
|
default:
|
|
@@ -116,10 +141,10 @@ exports._getImgOffset = function _getImgOffset(imgSrc = "", options = {}) {
|
|
|
116
141
|
}
|
|
117
142
|
if (alignments[1]) {
|
|
118
143
|
switch (alignments[1]) {
|
|
119
|
-
case
|
|
144
|
+
case VerticalAlign.CENTER:
|
|
120
145
|
offsetY = (-1 * height) / 2;
|
|
121
146
|
break;
|
|
122
|
-
case
|
|
147
|
+
case VerticalAlign.BOTTOM:
|
|
123
148
|
offsetY = height / 2;
|
|
124
149
|
break;
|
|
125
150
|
default:
|