2
0

html_utils.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695
  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.computeBbox = computeBbox;
  27. exports.createWrapper = createWrapper;
  28. exports.fixDimensions = fixDimensions;
  29. exports.fixTextIndent = fixTextIndent;
  30. exports.fixURL = fixURL;
  31. exports.isPrintOnly = isPrintOnly;
  32. exports.layoutClass = layoutClass;
  33. exports.layoutNode = layoutNode;
  34. exports.measureToString = measureToString;
  35. exports.setAccess = setAccess;
  36. exports.setFontFamily = setFontFamily;
  37. exports.setMinMaxDimensions = setMinMaxDimensions;
  38. exports.setPara = setPara;
  39. exports.toStyle = toStyle;
  40. var _xfa_object = require("./xfa_object.js");
  41. var _util = require("../../shared/util.js");
  42. var _utils = require("./utils.js");
  43. var _fonts = require("./fonts.js");
  44. var _text = require("./text.js");
  45. function measureToString(m) {
  46. if (typeof m === "string") {
  47. return "0px";
  48. }
  49. return Number.isInteger(m) ? `${m}px` : `${m.toFixed(2)}px`;
  50. }
  51. const converters = {
  52. anchorType(node, style) {
  53. const parent = node[_xfa_object.$getSubformParent]();
  54. if (!parent || parent.layout && parent.layout !== "position") {
  55. return;
  56. }
  57. if (!("transform" in style)) {
  58. style.transform = "";
  59. }
  60. switch (node.anchorType) {
  61. case "bottomCenter":
  62. style.transform += "translate(-50%, -100%)";
  63. break;
  64. case "bottomLeft":
  65. style.transform += "translate(0,-100%)";
  66. break;
  67. case "bottomRight":
  68. style.transform += "translate(-100%,-100%)";
  69. break;
  70. case "middleCenter":
  71. style.transform += "translate(-50%,-50%)";
  72. break;
  73. case "middleLeft":
  74. style.transform += "translate(0,-50%)";
  75. break;
  76. case "middleRight":
  77. style.transform += "translate(-100%,-50%)";
  78. break;
  79. case "topCenter":
  80. style.transform += "translate(-50%,0)";
  81. break;
  82. case "topRight":
  83. style.transform += "translate(-100%,0)";
  84. break;
  85. }
  86. },
  87. dimensions(node, style) {
  88. const parent = node[_xfa_object.$getSubformParent]();
  89. let width = node.w;
  90. const height = node.h;
  91. if (parent.layout && parent.layout.includes("row")) {
  92. const extra = parent[_xfa_object.$extra];
  93. const colSpan = node.colSpan;
  94. let w;
  95. if (colSpan === -1) {
  96. w = extra.columnWidths.slice(extra.currentColumn).reduce((a, x) => a + x, 0);
  97. extra.currentColumn = 0;
  98. } else {
  99. w = extra.columnWidths.slice(extra.currentColumn, extra.currentColumn + colSpan).reduce((a, x) => a + x, 0);
  100. extra.currentColumn = (extra.currentColumn + node.colSpan) % extra.columnWidths.length;
  101. }
  102. if (!isNaN(w)) {
  103. width = node.w = w;
  104. }
  105. }
  106. if (width !== "") {
  107. style.width = measureToString(width);
  108. } else {
  109. style.width = "auto";
  110. }
  111. if (height !== "") {
  112. style.height = measureToString(height);
  113. } else {
  114. style.height = "auto";
  115. }
  116. },
  117. position(node, style) {
  118. const parent = node[_xfa_object.$getSubformParent]();
  119. if (parent && parent.layout && parent.layout !== "position") {
  120. return;
  121. }
  122. style.position = "absolute";
  123. style.left = measureToString(node.x);
  124. style.top = measureToString(node.y);
  125. },
  126. rotate(node, style) {
  127. if (node.rotate) {
  128. if (!("transform" in style)) {
  129. style.transform = "";
  130. }
  131. style.transform += `rotate(-${node.rotate}deg)`;
  132. style.transformOrigin = "top left";
  133. }
  134. },
  135. presence(node, style) {
  136. switch (node.presence) {
  137. case "invisible":
  138. style.visibility = "hidden";
  139. break;
  140. case "hidden":
  141. case "inactive":
  142. style.display = "none";
  143. break;
  144. }
  145. },
  146. hAlign(node, style) {
  147. if (node[_xfa_object.$nodeName] === "para") {
  148. switch (node.hAlign) {
  149. case "justifyAll":
  150. style.textAlign = "justify-all";
  151. break;
  152. case "radix":
  153. style.textAlign = "left";
  154. break;
  155. default:
  156. style.textAlign = node.hAlign;
  157. }
  158. } else {
  159. switch (node.hAlign) {
  160. case "left":
  161. style.alignSelf = "start";
  162. break;
  163. case "center":
  164. style.alignSelf = "center";
  165. break;
  166. case "right":
  167. style.alignSelf = "end";
  168. break;
  169. }
  170. }
  171. },
  172. margin(node, style) {
  173. if (node.margin) {
  174. style.margin = node.margin[_xfa_object.$toStyle]().margin;
  175. }
  176. }
  177. };
  178. function setMinMaxDimensions(node, style) {
  179. const parent = node[_xfa_object.$getSubformParent]();
  180. if (parent.layout === "position") {
  181. if (node.minW > 0) {
  182. style.minWidth = measureToString(node.minW);
  183. }
  184. if (node.maxW > 0) {
  185. style.maxWidth = measureToString(node.maxW);
  186. }
  187. if (node.minH > 0) {
  188. style.minHeight = measureToString(node.minH);
  189. }
  190. if (node.maxH > 0) {
  191. style.maxHeight = measureToString(node.maxH);
  192. }
  193. }
  194. }
  195. function layoutText(text, xfaFont, margin, lineHeight, fontFinder, width) {
  196. const measure = new _text.TextMeasure(xfaFont, margin, lineHeight, fontFinder);
  197. if (typeof text === "string") {
  198. measure.addString(text);
  199. } else {
  200. text[_xfa_object.$pushGlyphs](measure);
  201. }
  202. return measure.compute(width);
  203. }
  204. function layoutNode(node, availableSpace) {
  205. let height = null;
  206. let width = null;
  207. let isBroken = false;
  208. if ((!node.w || !node.h) && node.value) {
  209. let marginH = 0;
  210. let marginV = 0;
  211. if (node.margin) {
  212. marginH = node.margin.leftInset + node.margin.rightInset;
  213. marginV = node.margin.topInset + node.margin.bottomInset;
  214. }
  215. let lineHeight = null;
  216. let margin = null;
  217. if (node.para) {
  218. margin = Object.create(null);
  219. lineHeight = node.para.lineHeight === "" ? null : node.para.lineHeight;
  220. margin.top = node.para.spaceAbove === "" ? 0 : node.para.spaceAbove;
  221. margin.bottom = node.para.spaceBelow === "" ? 0 : node.para.spaceBelow;
  222. margin.left = node.para.marginLeft === "" ? 0 : node.para.marginLeft;
  223. margin.right = node.para.marginRight === "" ? 0 : node.para.marginRight;
  224. }
  225. let font = node.font;
  226. if (!font) {
  227. const root = node[_xfa_object.$getTemplateRoot]();
  228. let parent = node[_xfa_object.$getParent]();
  229. while (parent !== root) {
  230. if (parent.font) {
  231. font = parent.font;
  232. break;
  233. }
  234. parent = parent[_xfa_object.$getParent]();
  235. }
  236. }
  237. const maxWidth = (!node.w ? availableSpace.width : node.w) - marginH;
  238. const fontFinder = node[_xfa_object.$globalData].fontFinder;
  239. if (node.value.exData && node.value.exData[_xfa_object.$content] && node.value.exData.contentType === "text/html") {
  240. const res = layoutText(node.value.exData[_xfa_object.$content], font, margin, lineHeight, fontFinder, maxWidth);
  241. width = res.width;
  242. height = res.height;
  243. isBroken = res.isBroken;
  244. } else {
  245. const text = node.value[_xfa_object.$text]();
  246. if (text) {
  247. const res = layoutText(text, font, margin, lineHeight, fontFinder, maxWidth);
  248. width = res.width;
  249. height = res.height;
  250. isBroken = res.isBroken;
  251. }
  252. }
  253. if (width !== null && !node.w) {
  254. width += marginH;
  255. }
  256. if (height !== null && !node.h) {
  257. height += marginV;
  258. }
  259. }
  260. return {
  261. w: width,
  262. h: height,
  263. isBroken
  264. };
  265. }
  266. function computeBbox(node, html, availableSpace) {
  267. let bbox;
  268. if (node.w !== "" && node.h !== "") {
  269. bbox = [node.x, node.y, node.w, node.h];
  270. } else {
  271. if (!availableSpace) {
  272. return null;
  273. }
  274. let width = node.w;
  275. if (width === "") {
  276. if (node.maxW === 0) {
  277. const parent = node[_xfa_object.$getSubformParent]();
  278. if (parent.layout === "position" && parent.w !== "") {
  279. width = 0;
  280. } else {
  281. width = node.minW;
  282. }
  283. } else {
  284. width = Math.min(node.maxW, availableSpace.width);
  285. }
  286. html.attributes.style.width = measureToString(width);
  287. }
  288. let height = node.h;
  289. if (height === "") {
  290. if (node.maxH === 0) {
  291. const parent = node[_xfa_object.$getSubformParent]();
  292. if (parent.layout === "position" && parent.h !== "") {
  293. height = 0;
  294. } else {
  295. height = node.minH;
  296. }
  297. } else {
  298. height = Math.min(node.maxH, availableSpace.height);
  299. }
  300. html.attributes.style.height = measureToString(height);
  301. }
  302. bbox = [node.x, node.y, width, height];
  303. }
  304. return bbox;
  305. }
  306. function fixDimensions(node) {
  307. const parent = node[_xfa_object.$getSubformParent]();
  308. if (parent.layout && parent.layout.includes("row")) {
  309. const extra = parent[_xfa_object.$extra];
  310. const colSpan = node.colSpan;
  311. let width;
  312. if (colSpan === -1) {
  313. width = extra.columnWidths.slice(extra.currentColumn).reduce((a, w) => a + w, 0);
  314. } else {
  315. width = extra.columnWidths.slice(extra.currentColumn, extra.currentColumn + colSpan).reduce((a, w) => a + w, 0);
  316. }
  317. if (!isNaN(width)) {
  318. node.w = width;
  319. }
  320. }
  321. if (parent.layout && parent.layout !== "position") {
  322. node.x = node.y = 0;
  323. }
  324. if (node.layout === "table") {
  325. if (node.w === "" && Array.isArray(node.columnWidths)) {
  326. node.w = node.columnWidths.reduce((a, x) => a + x, 0);
  327. }
  328. }
  329. }
  330. function layoutClass(node) {
  331. switch (node.layout) {
  332. case "position":
  333. return "xfaPosition";
  334. case "lr-tb":
  335. return "xfaLrTb";
  336. case "rl-row":
  337. return "xfaRlRow";
  338. case "rl-tb":
  339. return "xfaRlTb";
  340. case "row":
  341. return "xfaRow";
  342. case "table":
  343. return "xfaTable";
  344. case "tb":
  345. return "xfaTb";
  346. default:
  347. return "xfaPosition";
  348. }
  349. }
  350. function toStyle(node, ...names) {
  351. const style = Object.create(null);
  352. for (const name of names) {
  353. const value = node[name];
  354. if (value === null) {
  355. continue;
  356. }
  357. if (converters.hasOwnProperty(name)) {
  358. converters[name](node, style);
  359. continue;
  360. }
  361. if (value instanceof _xfa_object.XFAObject) {
  362. const newStyle = value[_xfa_object.$toStyle]();
  363. if (newStyle) {
  364. Object.assign(style, newStyle);
  365. } else {
  366. (0, _util.warn)(`(DEBUG) - XFA - style for ${name} not implemented yet`);
  367. }
  368. }
  369. }
  370. return style;
  371. }
  372. function createWrapper(node, html) {
  373. const {
  374. attributes
  375. } = html;
  376. const {
  377. style
  378. } = attributes;
  379. const wrapper = {
  380. name: "div",
  381. attributes: {
  382. class: ["xfaWrapper"],
  383. style: Object.create(null)
  384. },
  385. children: []
  386. };
  387. attributes.class.push("xfaWrapped");
  388. if (node.border) {
  389. const {
  390. widths,
  391. insets
  392. } = node.border[_xfa_object.$extra];
  393. let width, height;
  394. let top = insets[0];
  395. let left = insets[3];
  396. const insetsH = insets[0] + insets[2];
  397. const insetsW = insets[1] + insets[3];
  398. switch (node.border.hand) {
  399. case "even":
  400. top -= widths[0] / 2;
  401. left -= widths[3] / 2;
  402. width = `calc(100% + ${(widths[1] + widths[3]) / 2 - insetsW}px)`;
  403. height = `calc(100% + ${(widths[0] + widths[2]) / 2 - insetsH}px)`;
  404. break;
  405. case "left":
  406. top -= widths[0];
  407. left -= widths[3];
  408. width = `calc(100% + ${widths[1] + widths[3] - insetsW}px)`;
  409. height = `calc(100% + ${widths[0] + widths[2] - insetsH}px)`;
  410. break;
  411. case "right":
  412. width = insetsW ? `calc(100% - ${insetsW}px)` : "100%";
  413. height = insetsH ? `calc(100% - ${insetsH}px)` : "100%";
  414. break;
  415. }
  416. const classNames = ["xfaBorder"];
  417. if (isPrintOnly(node.border)) {
  418. classNames.push("xfaPrintOnly");
  419. }
  420. const border = {
  421. name: "div",
  422. attributes: {
  423. class: classNames,
  424. style: {
  425. top: `${top}px`,
  426. left: `${left}px`,
  427. width,
  428. height
  429. }
  430. },
  431. children: []
  432. };
  433. for (const key of ["border", "borderWidth", "borderColor", "borderRadius", "borderStyle"]) {
  434. if (style[key] !== undefined) {
  435. border.attributes.style[key] = style[key];
  436. delete style[key];
  437. }
  438. }
  439. wrapper.children.push(border, html);
  440. } else {
  441. wrapper.children.push(html);
  442. }
  443. for (const key of ["background", "backgroundClip", "top", "left", "width", "height", "minWidth", "minHeight", "maxWidth", "maxHeight", "transform", "transformOrigin", "visibility"]) {
  444. if (style[key] !== undefined) {
  445. wrapper.attributes.style[key] = style[key];
  446. delete style[key];
  447. }
  448. }
  449. if (style.position === "absolute") {
  450. wrapper.attributes.style.position = "absolute";
  451. } else {
  452. wrapper.attributes.style.position = "relative";
  453. }
  454. delete style.position;
  455. if (style.alignSelf) {
  456. wrapper.attributes.style.alignSelf = style.alignSelf;
  457. delete style.alignSelf;
  458. }
  459. return wrapper;
  460. }
  461. function fixTextIndent(styles) {
  462. const indent = (0, _utils.getMeasurement)(styles.textIndent, "0px");
  463. if (indent >= 0) {
  464. return;
  465. }
  466. const align = styles.textAlign === "right" ? "right" : "left";
  467. const name = "padding" + (align === "left" ? "Left" : "Right");
  468. const padding = (0, _utils.getMeasurement)(styles[name], "0px");
  469. styles[name] = `${padding - indent}px`;
  470. }
  471. function setAccess(node, classNames) {
  472. switch (node.access) {
  473. case "nonInteractive":
  474. classNames.push("xfaNonInteractive");
  475. break;
  476. case "readOnly":
  477. classNames.push("xfaReadOnly");
  478. break;
  479. case "protected":
  480. classNames.push("xfaDisabled");
  481. break;
  482. }
  483. }
  484. function isPrintOnly(node) {
  485. return node.relevant.length > 0 && !node.relevant[0].excluded && node.relevant[0].viewname === "print";
  486. }
  487. function getCurrentPara(node) {
  488. const stack = node[_xfa_object.$getTemplateRoot]()[_xfa_object.$extra].paraStack;
  489. return stack.length ? stack[stack.length - 1] : null;
  490. }
  491. function setPara(node, nodeStyle, value) {
  492. if (value.attributes.class && value.attributes.class.includes("xfaRich")) {
  493. if (nodeStyle) {
  494. if (node.h === "") {
  495. nodeStyle.height = "auto";
  496. }
  497. if (node.w === "") {
  498. nodeStyle.width = "auto";
  499. }
  500. }
  501. const para = getCurrentPara(node);
  502. if (para) {
  503. const valueStyle = value.attributes.style;
  504. valueStyle.display = "flex";
  505. valueStyle.flexDirection = "column";
  506. switch (para.vAlign) {
  507. case "top":
  508. valueStyle.justifyContent = "start";
  509. break;
  510. case "bottom":
  511. valueStyle.justifyContent = "end";
  512. break;
  513. case "middle":
  514. valueStyle.justifyContent = "center";
  515. break;
  516. }
  517. const paraStyle = para[_xfa_object.$toStyle]();
  518. for (const [key, val] of Object.entries(paraStyle)) {
  519. if (!(key in valueStyle)) {
  520. valueStyle[key] = val;
  521. }
  522. }
  523. }
  524. }
  525. }
  526. function setFontFamily(xfaFont, node, fontFinder, style) {
  527. const name = (0, _utils.stripQuotes)(xfaFont.typeface);
  528. const typeface = fontFinder.find(name);
  529. style.fontFamily = `"${name}"`;
  530. if (typeface) {
  531. const {
  532. fontFamily
  533. } = typeface.regular.cssFontInfo;
  534. if (fontFamily !== name) {
  535. style.fontFamily = `"${fontFamily}"`;
  536. }
  537. const para = getCurrentPara(node);
  538. if (para && para.lineHeight !== "") {
  539. return;
  540. }
  541. if (style.lineHeight) {
  542. return;
  543. }
  544. const pdfFont = (0, _fonts.selectFont)(xfaFont, typeface);
  545. if (pdfFont) {
  546. style.lineHeight = Math.max(1.2, pdfFont.lineHeight);
  547. }
  548. }
  549. }
  550. function fixURL(str) {
  551. const absoluteUrl = (0, _util.createValidAbsoluteUrl)(str, null, {
  552. addDefaultProtocol: true,
  553. tryConvertEncoding: true
  554. });
  555. return absoluteUrl ? absoluteUrl.href : null;
  556. }