UNPKG

7.79 kBJavaScriptView Raw
1/**
2 * @licstart The following is the entire license notice for the
3 * JavaScript code in this page
4 *
5 * Copyright 2022 Mozilla Foundation
6 *
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 *
19 * @licend The above is the entire license notice for the
20 * JavaScript code in this page
21 */
22"use strict";
23
24Object.defineProperty(exports, "__esModule", {
25 value: true
26});
27exports.TextMeasure = void 0;
28
29var _fonts = require("./fonts.js");
30
31const WIDTH_FACTOR = 1.02;
32
33class FontInfo {
34 constructor(xfaFont, margin, lineHeight, fontFinder) {
35 this.lineHeight = lineHeight;
36 this.paraMargin = margin || {
37 top: 0,
38 bottom: 0,
39 left: 0,
40 right: 0
41 };
42
43 if (!xfaFont) {
44 [this.pdfFont, this.xfaFont] = this.defaultFont(fontFinder);
45 return;
46 }
47
48 this.xfaFont = {
49 typeface: xfaFont.typeface,
50 posture: xfaFont.posture,
51 weight: xfaFont.weight,
52 size: xfaFont.size,
53 letterSpacing: xfaFont.letterSpacing
54 };
55 const typeface = fontFinder.find(xfaFont.typeface);
56
57 if (!typeface) {
58 [this.pdfFont, this.xfaFont] = this.defaultFont(fontFinder);
59 return;
60 }
61
62 this.pdfFont = (0, _fonts.selectFont)(xfaFont, typeface);
63
64 if (!this.pdfFont) {
65 [this.pdfFont, this.xfaFont] = this.defaultFont(fontFinder);
66 }
67 }
68
69 defaultFont(fontFinder) {
70 const font = fontFinder.find("Helvetica", false) || fontFinder.find("Myriad Pro", false) || fontFinder.find("Arial", false) || fontFinder.getDefault();
71
72 if (font && font.regular) {
73 const pdfFont = font.regular;
74 const info = pdfFont.cssFontInfo;
75 const xfaFont = {
76 typeface: info.fontFamily,
77 posture: "normal",
78 weight: "normal",
79 size: 10,
80 letterSpacing: 0
81 };
82 return [pdfFont, xfaFont];
83 }
84
85 const xfaFont = {
86 typeface: "Courier",
87 posture: "normal",
88 weight: "normal",
89 size: 10,
90 letterSpacing: 0
91 };
92 return [null, xfaFont];
93 }
94
95}
96
97class FontSelector {
98 constructor(defaultXfaFont, defaultParaMargin, defaultLineHeight, fontFinder) {
99 this.fontFinder = fontFinder;
100 this.stack = [new FontInfo(defaultXfaFont, defaultParaMargin, defaultLineHeight, fontFinder)];
101 }
102
103 pushData(xfaFont, margin, lineHeight) {
104 const lastFont = this.stack.at(-1);
105
106 for (const name of ["typeface", "posture", "weight", "size", "letterSpacing"]) {
107 if (!xfaFont[name]) {
108 xfaFont[name] = lastFont.xfaFont[name];
109 }
110 }
111
112 for (const name of ["top", "bottom", "left", "right"]) {
113 if (isNaN(margin[name])) {
114 margin[name] = lastFont.paraMargin[name];
115 }
116 }
117
118 const fontInfo = new FontInfo(xfaFont, margin, lineHeight || lastFont.lineHeight, this.fontFinder);
119
120 if (!fontInfo.pdfFont) {
121 fontInfo.pdfFont = lastFont.pdfFont;
122 }
123
124 this.stack.push(fontInfo);
125 }
126
127 popFont() {
128 this.stack.pop();
129 }
130
131 topFont() {
132 return this.stack.at(-1);
133 }
134
135}
136
137class TextMeasure {
138 constructor(defaultXfaFont, defaultParaMargin, defaultLineHeight, fonts) {
139 this.glyphs = [];
140 this.fontSelector = new FontSelector(defaultXfaFont, defaultParaMargin, defaultLineHeight, fonts);
141 this.extraHeight = 0;
142 }
143
144 pushData(xfaFont, margin, lineHeight) {
145 this.fontSelector.pushData(xfaFont, margin, lineHeight);
146 }
147
148 popFont(xfaFont) {
149 return this.fontSelector.popFont();
150 }
151
152 addPara() {
153 const lastFont = this.fontSelector.topFont();
154 this.extraHeight += lastFont.paraMargin.top + lastFont.paraMargin.bottom;
155 }
156
157 addString(str) {
158 if (!str) {
159 return;
160 }
161
162 const lastFont = this.fontSelector.topFont();
163 const fontSize = lastFont.xfaFont.size;
164
165 if (lastFont.pdfFont) {
166 const letterSpacing = lastFont.xfaFont.letterSpacing;
167 const pdfFont = lastFont.pdfFont;
168 const fontLineHeight = pdfFont.lineHeight || 1.2;
169 const lineHeight = lastFont.lineHeight || Math.max(1.2, fontLineHeight) * fontSize;
170 const lineGap = pdfFont.lineGap === undefined ? 0.2 : pdfFont.lineGap;
171 const noGap = fontLineHeight - lineGap;
172 const firstLineHeight = Math.max(1, noGap) * fontSize;
173 const scale = fontSize / 1000;
174 const fallbackWidth = pdfFont.defaultWidth || pdfFont.charsToGlyphs(" ")[0].width;
175
176 for (const line of str.split(/[\u2029\n]/)) {
177 const encodedLine = pdfFont.encodeString(line).join("");
178 const glyphs = pdfFont.charsToGlyphs(encodedLine);
179
180 for (const glyph of glyphs) {
181 const width = glyph.width || fallbackWidth;
182 this.glyphs.push([width * scale + letterSpacing, lineHeight, firstLineHeight, glyph.unicode, false]);
183 }
184
185 this.glyphs.push([0, 0, 0, "\n", true]);
186 }
187
188 this.glyphs.pop();
189 return;
190 }
191
192 for (const line of str.split(/[\u2029\n]/)) {
193 for (const char of line.split("")) {
194 this.glyphs.push([fontSize, 1.2 * fontSize, fontSize, char, false]);
195 }
196
197 this.glyphs.push([0, 0, 0, "\n", true]);
198 }
199
200 this.glyphs.pop();
201 }
202
203 compute(maxWidth) {
204 let lastSpacePos = -1,
205 lastSpaceWidth = 0,
206 width = 0,
207 height = 0,
208 currentLineWidth = 0,
209 currentLineHeight = 0;
210 let isBroken = false;
211 let isFirstLine = true;
212
213 for (let i = 0, ii = this.glyphs.length; i < ii; i++) {
214 const [glyphWidth, lineHeight, firstLineHeight, char, isEOL] = this.glyphs[i];
215 const isSpace = char === " ";
216 const glyphHeight = isFirstLine ? firstLineHeight : lineHeight;
217
218 if (isEOL) {
219 width = Math.max(width, currentLineWidth);
220 currentLineWidth = 0;
221 height += currentLineHeight;
222 currentLineHeight = glyphHeight;
223 lastSpacePos = -1;
224 lastSpaceWidth = 0;
225 isFirstLine = false;
226 continue;
227 }
228
229 if (isSpace) {
230 if (currentLineWidth + glyphWidth > maxWidth) {
231 width = Math.max(width, currentLineWidth);
232 currentLineWidth = 0;
233 height += currentLineHeight;
234 currentLineHeight = glyphHeight;
235 lastSpacePos = -1;
236 lastSpaceWidth = 0;
237 isBroken = true;
238 isFirstLine = false;
239 } else {
240 currentLineHeight = Math.max(glyphHeight, currentLineHeight);
241 lastSpaceWidth = currentLineWidth;
242 currentLineWidth += glyphWidth;
243 lastSpacePos = i;
244 }
245
246 continue;
247 }
248
249 if (currentLineWidth + glyphWidth > maxWidth) {
250 height += currentLineHeight;
251 currentLineHeight = glyphHeight;
252
253 if (lastSpacePos !== -1) {
254 i = lastSpacePos;
255 width = Math.max(width, lastSpaceWidth);
256 currentLineWidth = 0;
257 lastSpacePos = -1;
258 lastSpaceWidth = 0;
259 } else {
260 width = Math.max(width, currentLineWidth);
261 currentLineWidth = glyphWidth;
262 }
263
264 isBroken = true;
265 isFirstLine = false;
266 continue;
267 }
268
269 currentLineWidth += glyphWidth;
270 currentLineHeight = Math.max(glyphHeight, currentLineHeight);
271 }
272
273 width = Math.max(width, currentLineWidth);
274 height += currentLineHeight + this.extraHeight;
275 return {
276 width: WIDTH_FACTOR * width,
277 height,
278 isBroken
279 };
280 }
281
282}
283
284exports.TextMeasure = TextMeasure;
\No newline at end of file