html_utils.js 16 KB

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