2
0

text.js 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. /**
  2. * @licstart The following is the entire license notice for the
  3. * Javascript code in this page
  4. *
  5. * Copyright 2021 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. Object.defineProperty(exports, "__esModule", {
  24. value: true
  25. });
  26. exports.TextMeasure = void 0;
  27. var _fonts = require("./fonts.js");
  28. const WIDTH_FACTOR = 1.02;
  29. class FontInfo {
  30. constructor(xfaFont, margin, lineHeight, fontFinder) {
  31. this.lineHeight = lineHeight;
  32. this.paraMargin = margin || {
  33. top: 0,
  34. bottom: 0,
  35. left: 0,
  36. right: 0
  37. };
  38. if (!xfaFont) {
  39. [this.pdfFont, this.xfaFont] = this.defaultFont(fontFinder);
  40. return;
  41. }
  42. this.xfaFont = {
  43. typeface: xfaFont.typeface,
  44. posture: xfaFont.posture,
  45. weight: xfaFont.weight,
  46. size: xfaFont.size,
  47. letterSpacing: xfaFont.letterSpacing
  48. };
  49. const typeface = fontFinder.find(xfaFont.typeface);
  50. if (!typeface) {
  51. [this.pdfFont, this.xfaFont] = this.defaultFont(fontFinder);
  52. return;
  53. }
  54. this.pdfFont = (0, _fonts.selectFont)(xfaFont, typeface);
  55. if (!this.pdfFont) {
  56. [this.pdfFont, this.xfaFont] = this.defaultFont(fontFinder);
  57. }
  58. }
  59. defaultFont(fontFinder) {
  60. const font = fontFinder.find("Helvetica", false) || fontFinder.find("Myriad Pro", false) || fontFinder.find("Arial", false) || fontFinder.getDefault();
  61. if (font && font.regular) {
  62. const pdfFont = font.regular;
  63. const info = pdfFont.cssFontInfo;
  64. const xfaFont = {
  65. typeface: info.fontFamily,
  66. posture: "normal",
  67. weight: "normal",
  68. size: 10,
  69. letterSpacing: 0
  70. };
  71. return [pdfFont, xfaFont];
  72. }
  73. const xfaFont = {
  74. typeface: "Courier",
  75. posture: "normal",
  76. weight: "normal",
  77. size: 10,
  78. letterSpacing: 0
  79. };
  80. return [null, xfaFont];
  81. }
  82. }
  83. class FontSelector {
  84. constructor(defaultXfaFont, defaultParaMargin, defaultLineHeight, fontFinder) {
  85. this.fontFinder = fontFinder;
  86. this.stack = [new FontInfo(defaultXfaFont, defaultParaMargin, defaultLineHeight, fontFinder)];
  87. }
  88. pushData(xfaFont, margin, lineHeight) {
  89. const lastFont = this.stack[this.stack.length - 1];
  90. for (const name of ["typeface", "posture", "weight", "size", "letterSpacing"]) {
  91. if (!xfaFont[name]) {
  92. xfaFont[name] = lastFont.xfaFont[name];
  93. }
  94. }
  95. for (const name of ["top", "bottom", "left", "right"]) {
  96. if (isNaN(margin[name])) {
  97. margin[name] = lastFont.paraMargin[name];
  98. }
  99. }
  100. const fontInfo = new FontInfo(xfaFont, margin, lineHeight || lastFont.lineHeight, this.fontFinder);
  101. if (!fontInfo.pdfFont) {
  102. fontInfo.pdfFont = lastFont.pdfFont;
  103. }
  104. this.stack.push(fontInfo);
  105. }
  106. popFont() {
  107. this.stack.pop();
  108. }
  109. topFont() {
  110. return this.stack[this.stack.length - 1];
  111. }
  112. }
  113. class TextMeasure {
  114. constructor(defaultXfaFont, defaultParaMargin, defaultLineHeight, fonts) {
  115. this.glyphs = [];
  116. this.fontSelector = new FontSelector(defaultXfaFont, defaultParaMargin, defaultLineHeight, fonts);
  117. this.extraHeight = 0;
  118. }
  119. pushData(xfaFont, margin, lineHeight) {
  120. this.fontSelector.pushData(xfaFont, margin, lineHeight);
  121. }
  122. popFont(xfaFont) {
  123. return this.fontSelector.popFont();
  124. }
  125. addPara() {
  126. const lastFont = this.fontSelector.topFont();
  127. this.extraHeight += lastFont.paraMargin.top + lastFont.paraMargin.bottom;
  128. }
  129. addString(str) {
  130. if (!str) {
  131. return;
  132. }
  133. const lastFont = this.fontSelector.topFont();
  134. const fontSize = lastFont.xfaFont.size;
  135. if (lastFont.pdfFont) {
  136. const letterSpacing = lastFont.xfaFont.letterSpacing;
  137. const pdfFont = lastFont.pdfFont;
  138. const fontLineHeight = pdfFont.lineHeight || 1.2;
  139. const lineHeight = lastFont.lineHeight || Math.max(1.2, fontLineHeight) * fontSize;
  140. const lineGap = pdfFont.lineGap === undefined ? 0.2 : pdfFont.lineGap;
  141. const noGap = fontLineHeight - lineGap;
  142. const firstLineHeight = Math.max(1, noGap) * fontSize;
  143. const scale = fontSize / 1000;
  144. const fallbackWidth = pdfFont.defaultWidth || pdfFont.charsToGlyphs(" ")[0].width;
  145. for (const line of str.split(/[\u2029\n]/)) {
  146. const encodedLine = pdfFont.encodeString(line).join("");
  147. const glyphs = pdfFont.charsToGlyphs(encodedLine);
  148. for (const glyph of glyphs) {
  149. const width = glyph.width || fallbackWidth;
  150. this.glyphs.push([width * scale + letterSpacing, lineHeight, firstLineHeight, glyph.unicode, false]);
  151. }
  152. this.glyphs.push([0, 0, 0, "\n", true]);
  153. }
  154. this.glyphs.pop();
  155. return;
  156. }
  157. for (const line of str.split(/[\u2029\n]/)) {
  158. for (const char of line.split("")) {
  159. this.glyphs.push([fontSize, 1.2 * fontSize, fontSize, char, false]);
  160. }
  161. this.glyphs.push([0, 0, 0, "\n", true]);
  162. }
  163. this.glyphs.pop();
  164. }
  165. compute(maxWidth) {
  166. let lastSpacePos = -1,
  167. lastSpaceWidth = 0,
  168. width = 0,
  169. height = 0,
  170. currentLineWidth = 0,
  171. currentLineHeight = 0;
  172. let isBroken = false;
  173. let isFirstLine = true;
  174. for (let i = 0, ii = this.glyphs.length; i < ii; i++) {
  175. const [glyphWidth, lineHeight, firstLineHeight, char, isEOL] = this.glyphs[i];
  176. const isSpace = char === " ";
  177. const glyphHeight = isFirstLine ? firstLineHeight : lineHeight;
  178. if (isEOL) {
  179. width = Math.max(width, currentLineWidth);
  180. currentLineWidth = 0;
  181. height += currentLineHeight;
  182. currentLineHeight = glyphHeight;
  183. lastSpacePos = -1;
  184. lastSpaceWidth = 0;
  185. isFirstLine = false;
  186. continue;
  187. }
  188. if (isSpace) {
  189. if (currentLineWidth + glyphWidth > maxWidth) {
  190. width = Math.max(width, currentLineWidth);
  191. currentLineWidth = 0;
  192. height += currentLineHeight;
  193. currentLineHeight = glyphHeight;
  194. lastSpacePos = -1;
  195. lastSpaceWidth = 0;
  196. isBroken = true;
  197. isFirstLine = false;
  198. } else {
  199. currentLineHeight = Math.max(glyphHeight, currentLineHeight);
  200. lastSpaceWidth = currentLineWidth;
  201. currentLineWidth += glyphWidth;
  202. lastSpacePos = i;
  203. }
  204. continue;
  205. }
  206. if (currentLineWidth + glyphWidth > maxWidth) {
  207. height += currentLineHeight;
  208. currentLineHeight = glyphHeight;
  209. if (lastSpacePos !== -1) {
  210. i = lastSpacePos;
  211. width = Math.max(width, lastSpaceWidth);
  212. currentLineWidth = 0;
  213. lastSpacePos = -1;
  214. lastSpaceWidth = 0;
  215. } else {
  216. width = Math.max(width, currentLineWidth);
  217. currentLineWidth = glyphWidth;
  218. }
  219. isBroken = true;
  220. isFirstLine = false;
  221. continue;
  222. }
  223. currentLineWidth += glyphWidth;
  224. currentLineHeight = Math.max(glyphHeight, currentLineHeight);
  225. }
  226. width = Math.max(width, currentLineWidth);
  227. height += currentLineHeight + this.extraHeight;
  228. return {
  229. width: WIDTH_FACTOR * width,
  230. height,
  231. isBroken
  232. };
  233. }
  234. }
  235. exports.TextMeasure = TextMeasure;