xhtml.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432
  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. Object.defineProperty(exports, "__esModule", {
  24. value: true
  25. });
  26. exports.XhtmlNamespace = void 0;
  27. var _xfa_object = require("./xfa_object.js");
  28. var _namespaces = require("./namespaces.js");
  29. var _html_utils = require("./html_utils.js");
  30. var _utils = require("./utils.js");
  31. const XHTML_NS_ID = _namespaces.NamespaceIds.xhtml.id;
  32. const $richText = Symbol();
  33. const VALID_STYLES = new Set(["color", "font", "font-family", "font-size", "font-stretch", "font-style", "font-weight", "margin", "margin-bottom", "margin-left", "margin-right", "margin-top", "letter-spacing", "line-height", "orphans", "page-break-after", "page-break-before", "page-break-inside", "tab-interval", "tab-stop", "text-align", "text-decoration", "text-indent", "vertical-align", "widows", "kerning-mode", "xfa-font-horizontal-scale", "xfa-font-vertical-scale", "xfa-spacerun", "xfa-tab-stops"]);
  34. const StyleMapping = new Map([["page-break-after", "breakAfter"], ["page-break-before", "breakBefore"], ["page-break-inside", "breakInside"], ["kerning-mode", value => value === "none" ? "none" : "normal"], ["xfa-font-horizontal-scale", value => `scaleX(${Math.max(0, Math.min(parseInt(value) / 100)).toFixed(2)})`], ["xfa-font-vertical-scale", value => `scaleY(${Math.max(0, Math.min(parseInt(value) / 100)).toFixed(2)})`], ["xfa-spacerun", ""], ["xfa-tab-stops", ""], ["font-size", (value, original) => {
  35. value = original.fontSize = (0, _utils.getMeasurement)(value);
  36. return (0, _html_utils.measureToString)(0.99 * value);
  37. }], ["letter-spacing", value => (0, _html_utils.measureToString)((0, _utils.getMeasurement)(value))], ["line-height", value => (0, _html_utils.measureToString)((0, _utils.getMeasurement)(value))], ["margin", value => (0, _html_utils.measureToString)((0, _utils.getMeasurement)(value))], ["margin-bottom", value => (0, _html_utils.measureToString)((0, _utils.getMeasurement)(value))], ["margin-left", value => (0, _html_utils.measureToString)((0, _utils.getMeasurement)(value))], ["margin-right", value => (0, _html_utils.measureToString)((0, _utils.getMeasurement)(value))], ["margin-top", value => (0, _html_utils.measureToString)((0, _utils.getMeasurement)(value))], ["text-indent", value => (0, _html_utils.measureToString)((0, _utils.getMeasurement)(value))], ["font-family", value => value], ["vertical-align", value => (0, _html_utils.measureToString)((0, _utils.getMeasurement)(value))]]);
  38. const spacesRegExp = /\s+/g;
  39. const crlfRegExp = /[\r\n]+/g;
  40. const crlfForRichTextRegExp = /\r\n?/g;
  41. function mapStyle(styleStr, node, richText) {
  42. const style = Object.create(null);
  43. if (!styleStr) {
  44. return style;
  45. }
  46. const original = Object.create(null);
  47. for (const [key, value] of styleStr.split(";").map(s => s.split(":", 2))) {
  48. const mapping = StyleMapping.get(key);
  49. if (mapping === "") {
  50. continue;
  51. }
  52. let newValue = value;
  53. if (mapping) {
  54. if (typeof mapping === "string") {
  55. newValue = mapping;
  56. } else {
  57. newValue = mapping(value, original);
  58. }
  59. }
  60. if (key.endsWith("scale")) {
  61. if (style.transform) {
  62. style.transform = `${style[key]} ${newValue}`;
  63. } else {
  64. style.transform = newValue;
  65. }
  66. } else {
  67. style[key.replaceAll(/-([a-zA-Z])/g, (_, x) => x.toUpperCase())] = newValue;
  68. }
  69. }
  70. if (style.fontFamily) {
  71. (0, _html_utils.setFontFamily)({
  72. typeface: style.fontFamily,
  73. weight: style.fontWeight || "normal",
  74. posture: style.fontStyle || "normal",
  75. size: original.fontSize || 0
  76. }, node, node[_xfa_object.$globalData].fontFinder, style);
  77. }
  78. if (richText && style.verticalAlign && style.verticalAlign !== "0px" && style.fontSize) {
  79. const SUB_SUPER_SCRIPT_FACTOR = 0.583;
  80. const VERTICAL_FACTOR = 0.333;
  81. const fontSize = (0, _utils.getMeasurement)(style.fontSize);
  82. style.fontSize = (0, _html_utils.measureToString)(fontSize * SUB_SUPER_SCRIPT_FACTOR);
  83. style.verticalAlign = (0, _html_utils.measureToString)(Math.sign((0, _utils.getMeasurement)(style.verticalAlign)) * fontSize * VERTICAL_FACTOR);
  84. }
  85. if (richText && style.fontSize) {
  86. style.fontSize = `calc(${style.fontSize} * var(--scale-factor))`;
  87. }
  88. (0, _html_utils.fixTextIndent)(style);
  89. return style;
  90. }
  91. function checkStyle(node) {
  92. if (!node.style) {
  93. return "";
  94. }
  95. return node.style.trim().split(/\s*;\s*/).filter(s => !!s).map(s => s.split(/\s*:\s*/, 2)).filter(([key, value]) => {
  96. if (key === "font-family") {
  97. node[_xfa_object.$globalData].usedTypefaces.add(value);
  98. }
  99. return VALID_STYLES.has(key);
  100. }).map(kv => kv.join(":")).join(";");
  101. }
  102. const NoWhites = new Set(["body", "html"]);
  103. class XhtmlObject extends _xfa_object.XmlObject {
  104. constructor(attributes, name) {
  105. super(XHTML_NS_ID, name);
  106. this[$richText] = false;
  107. this.style = attributes.style || "";
  108. }
  109. [_xfa_object.$clean](builder) {
  110. super[_xfa_object.$clean](builder);
  111. this.style = checkStyle(this);
  112. }
  113. [_xfa_object.$acceptWhitespace]() {
  114. return !NoWhites.has(this[_xfa_object.$nodeName]);
  115. }
  116. [_xfa_object.$onText](str, richText = false) {
  117. if (!richText) {
  118. str = str.replace(crlfRegExp, "");
  119. if (!this.style.includes("xfa-spacerun:yes")) {
  120. str = str.replace(spacesRegExp, " ");
  121. }
  122. } else {
  123. this[$richText] = true;
  124. }
  125. if (str) {
  126. this[_xfa_object.$content] += str;
  127. }
  128. }
  129. [_xfa_object.$pushGlyphs](measure, mustPop = true) {
  130. const xfaFont = Object.create(null);
  131. const margin = {
  132. top: NaN,
  133. bottom: NaN,
  134. left: NaN,
  135. right: NaN
  136. };
  137. let lineHeight = null;
  138. for (const [key, value] of this.style.split(";").map(s => s.split(":", 2))) {
  139. switch (key) {
  140. case "font-family":
  141. xfaFont.typeface = (0, _utils.stripQuotes)(value);
  142. break;
  143. case "font-size":
  144. xfaFont.size = (0, _utils.getMeasurement)(value);
  145. break;
  146. case "font-weight":
  147. xfaFont.weight = value;
  148. break;
  149. case "font-style":
  150. xfaFont.posture = value;
  151. break;
  152. case "letter-spacing":
  153. xfaFont.letterSpacing = (0, _utils.getMeasurement)(value);
  154. break;
  155. case "margin":
  156. const values = value.split(/ \t/).map(x => (0, _utils.getMeasurement)(x));
  157. switch (values.length) {
  158. case 1:
  159. margin.top = margin.bottom = margin.left = margin.right = values[0];
  160. break;
  161. case 2:
  162. margin.top = margin.bottom = values[0];
  163. margin.left = margin.right = values[1];
  164. break;
  165. case 3:
  166. margin.top = values[0];
  167. margin.bottom = values[2];
  168. margin.left = margin.right = values[1];
  169. break;
  170. case 4:
  171. margin.top = values[0];
  172. margin.left = values[1];
  173. margin.bottom = values[2];
  174. margin.right = values[3];
  175. break;
  176. }
  177. break;
  178. case "margin-top":
  179. margin.top = (0, _utils.getMeasurement)(value);
  180. break;
  181. case "margin-bottom":
  182. margin.bottom = (0, _utils.getMeasurement)(value);
  183. break;
  184. case "margin-left":
  185. margin.left = (0, _utils.getMeasurement)(value);
  186. break;
  187. case "margin-right":
  188. margin.right = (0, _utils.getMeasurement)(value);
  189. break;
  190. case "line-height":
  191. lineHeight = (0, _utils.getMeasurement)(value);
  192. break;
  193. }
  194. }
  195. measure.pushData(xfaFont, margin, lineHeight);
  196. if (this[_xfa_object.$content]) {
  197. measure.addString(this[_xfa_object.$content]);
  198. } else {
  199. for (const child of this[_xfa_object.$getChildren]()) {
  200. if (child[_xfa_object.$nodeName] === "#text") {
  201. measure.addString(child[_xfa_object.$content]);
  202. continue;
  203. }
  204. child[_xfa_object.$pushGlyphs](measure);
  205. }
  206. }
  207. if (mustPop) {
  208. measure.popFont();
  209. }
  210. }
  211. [_xfa_object.$toHTML](availableSpace) {
  212. const children = [];
  213. this[_xfa_object.$extra] = {
  214. children
  215. };
  216. this[_xfa_object.$childrenToHTML]({});
  217. if (children.length === 0 && !this[_xfa_object.$content]) {
  218. return _utils.HTMLResult.EMPTY;
  219. }
  220. let value;
  221. if (this[$richText]) {
  222. value = this[_xfa_object.$content] ? this[_xfa_object.$content].replace(crlfForRichTextRegExp, "\n") : undefined;
  223. } else {
  224. value = this[_xfa_object.$content] || undefined;
  225. }
  226. return _utils.HTMLResult.success({
  227. name: this[_xfa_object.$nodeName],
  228. attributes: {
  229. href: this.href,
  230. style: mapStyle(this.style, this, this[$richText])
  231. },
  232. children,
  233. value
  234. });
  235. }
  236. }
  237. class A extends XhtmlObject {
  238. constructor(attributes) {
  239. super(attributes, "a");
  240. this.href = (0, _html_utils.fixURL)(attributes.href) || "";
  241. }
  242. }
  243. class B extends XhtmlObject {
  244. constructor(attributes) {
  245. super(attributes, "b");
  246. }
  247. [_xfa_object.$pushGlyphs](measure) {
  248. measure.pushFont({
  249. weight: "bold"
  250. });
  251. super[_xfa_object.$pushGlyphs](measure);
  252. measure.popFont();
  253. }
  254. }
  255. class Body extends XhtmlObject {
  256. constructor(attributes) {
  257. super(attributes, "body");
  258. }
  259. [_xfa_object.$toHTML](availableSpace) {
  260. const res = super[_xfa_object.$toHTML](availableSpace);
  261. const {
  262. html
  263. } = res;
  264. if (!html) {
  265. return _utils.HTMLResult.EMPTY;
  266. }
  267. html.name = "div";
  268. html.attributes.class = ["xfaRich"];
  269. return res;
  270. }
  271. }
  272. class Br extends XhtmlObject {
  273. constructor(attributes) {
  274. super(attributes, "br");
  275. }
  276. [_xfa_object.$text]() {
  277. return "\n";
  278. }
  279. [_xfa_object.$pushGlyphs](measure) {
  280. measure.addString("\n");
  281. }
  282. [_xfa_object.$toHTML](availableSpace) {
  283. return _utils.HTMLResult.success({
  284. name: "br"
  285. });
  286. }
  287. }
  288. class Html extends XhtmlObject {
  289. constructor(attributes) {
  290. super(attributes, "html");
  291. }
  292. [_xfa_object.$toHTML](availableSpace) {
  293. const children = [];
  294. this[_xfa_object.$extra] = {
  295. children
  296. };
  297. this[_xfa_object.$childrenToHTML]({});
  298. if (children.length === 0) {
  299. return _utils.HTMLResult.success({
  300. name: "div",
  301. attributes: {
  302. class: ["xfaRich"],
  303. style: {}
  304. },
  305. value: this[_xfa_object.$content] || ""
  306. });
  307. }
  308. if (children.length === 1) {
  309. const child = children[0];
  310. if (child.attributes && child.attributes.class.includes("xfaRich")) {
  311. return _utils.HTMLResult.success(child);
  312. }
  313. }
  314. return _utils.HTMLResult.success({
  315. name: "div",
  316. attributes: {
  317. class: ["xfaRich"],
  318. style: {}
  319. },
  320. children
  321. });
  322. }
  323. }
  324. class I extends XhtmlObject {
  325. constructor(attributes) {
  326. super(attributes, "i");
  327. }
  328. [_xfa_object.$pushGlyphs](measure) {
  329. measure.pushFont({
  330. posture: "italic"
  331. });
  332. super[_xfa_object.$pushGlyphs](measure);
  333. measure.popFont();
  334. }
  335. }
  336. class Li extends XhtmlObject {
  337. constructor(attributes) {
  338. super(attributes, "li");
  339. }
  340. }
  341. class Ol extends XhtmlObject {
  342. constructor(attributes) {
  343. super(attributes, "ol");
  344. }
  345. }
  346. class P extends XhtmlObject {
  347. constructor(attributes) {
  348. super(attributes, "p");
  349. }
  350. [_xfa_object.$pushGlyphs](measure) {
  351. super[_xfa_object.$pushGlyphs](measure, false);
  352. measure.addString("\n");
  353. measure.addPara();
  354. measure.popFont();
  355. }
  356. [_xfa_object.$text]() {
  357. const siblings = this[_xfa_object.$getParent]()[_xfa_object.$getChildren]();
  358. if (siblings.at(-1) === this) {
  359. return super[_xfa_object.$text]();
  360. }
  361. return super[_xfa_object.$text]() + "\n";
  362. }
  363. }
  364. class Span extends XhtmlObject {
  365. constructor(attributes) {
  366. super(attributes, "span");
  367. }
  368. }
  369. class Sub extends XhtmlObject {
  370. constructor(attributes) {
  371. super(attributes, "sub");
  372. }
  373. }
  374. class Sup extends XhtmlObject {
  375. constructor(attributes) {
  376. super(attributes, "sup");
  377. }
  378. }
  379. class Ul extends XhtmlObject {
  380. constructor(attributes) {
  381. super(attributes, "ul");
  382. }
  383. }
  384. class XhtmlNamespace {
  385. static [_namespaces.$buildXFAObject](name, attributes) {
  386. if (XhtmlNamespace.hasOwnProperty(name)) {
  387. return XhtmlNamespace[name](attributes);
  388. }
  389. return undefined;
  390. }
  391. static a(attributes) {
  392. return new A(attributes);
  393. }
  394. static b(attributes) {
  395. return new B(attributes);
  396. }
  397. static body(attributes) {
  398. return new Body(attributes);
  399. }
  400. static br(attributes) {
  401. return new Br(attributes);
  402. }
  403. static html(attributes) {
  404. return new Html(attributes);
  405. }
  406. static i(attributes) {
  407. return new I(attributes);
  408. }
  409. static li(attributes) {
  410. return new Li(attributes);
  411. }
  412. static ol(attributes) {
  413. return new Ol(attributes);
  414. }
  415. static p(attributes) {
  416. return new P(attributes);
  417. }
  418. static span(attributes) {
  419. return new Span(attributes);
  420. }
  421. static sub(attributes) {
  422. return new Sub(attributes);
  423. }
  424. static sup(attributes) {
  425. return new Sup(attributes);
  426. }
  427. static ul(attributes) {
  428. return new Ul(attributes);
  429. }
  430. }
  431. exports.XhtmlNamespace = XhtmlNamespace;