OutputJax.ts 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661
  1. /*************************************************************
  2. *
  3. * Copyright (c) 2017-2022 The MathJax Consortium
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. /**
  18. * @fileoverview Implements the abstract class for the CommonOutputJax
  19. *
  20. * @author dpvc@mathjax.org (Davide Cervone)
  21. */
  22. import {AbstractOutputJax} from '../../core/OutputJax.js';
  23. import {MathDocument} from '../../core/MathDocument.js';
  24. import {MathItem, Metrics, STATE} from '../../core/MathItem.js';
  25. import {MmlNode} from '../../core/MmlTree/MmlNode.js';
  26. import {FontData, FontDataClass, CharOptions, DelimiterData, CssFontData} from './FontData.js';
  27. import {OptionList, separateOptions} from '../../util/Options.js';
  28. import {CommonWrapper, AnyWrapper, AnyWrapperClass} from './Wrapper.js';
  29. import {CommonWrapperFactory, AnyWrapperFactory} from './WrapperFactory.js';
  30. import {percent} from '../../util/lengths.js';
  31. import {StyleList, Styles} from '../../util/Styles.js';
  32. import {StyleList as CssStyleList, CssStyles} from '../../util/StyleList.js';
  33. /*****************************************************************/
  34. export interface ExtendedMetrics extends Metrics {
  35. family: string; // the font family for the surrounding text
  36. }
  37. /**
  38. * Maps linking a node to the test node it contains,
  39. * and a map linking a node to the metrics within that node.
  40. */
  41. export type MetricMap<N> = Map<N, ExtendedMetrics>;
  42. type MetricDomMap<N> = Map<N, N>;
  43. /**
  44. * Maps for unknown characters
  45. */
  46. export type UnknownBBox = {w: number, h: number, d: number};
  47. export type UnknownMap = Map<string, UnknownBBox>;
  48. export type UnknownVariantMap = Map<string, UnknownMap>;
  49. /*****************************************************************/
  50. /**
  51. * The CommonOutputJax class on which the CHTML and SVG jax are built
  52. *
  53. * @template N The HTMLElement node class
  54. * @template T The Text node class
  55. * @template D The Document class
  56. * @template W The Wrapper class
  57. * @template F The WrapperFactory class
  58. * @template FD The FontData class
  59. * @template FC The FontDataClass object
  60. */
  61. export abstract class CommonOutputJax<
  62. N, T, D,
  63. W extends AnyWrapper,
  64. F extends AnyWrapperFactory,
  65. FD extends FontData<any, any, any>,
  66. FC extends FontDataClass<any, any, any>
  67. > extends AbstractOutputJax<N, T, D> {
  68. /**
  69. * The name of this output jax
  70. */
  71. public static NAME: string = 'Common';
  72. /**
  73. * @override
  74. */
  75. public static OPTIONS: OptionList = {
  76. ...AbstractOutputJax.OPTIONS,
  77. scale: 1, // global scaling factor for all expressions
  78. minScale: .5, // smallest scaling factor to use
  79. mtextInheritFont: false, // true to make mtext elements use surrounding font
  80. merrorInheritFont: false, // true to make merror text use surrounding font
  81. mtextFont: '', // font to use for mtext, if not inheriting (empty means use MathJax fonts)
  82. merrorFont: 'serif', // font to use for merror, if not inheriting (empty means use MathJax fonts)
  83. mathmlSpacing: false, // true for MathML spacing rules, false for TeX rules
  84. skipAttributes: {}, // RFDa and other attributes NOT to copy to the output
  85. exFactor: .5, // default size of ex in em units
  86. displayAlign: 'center', // default for indentalign when set to 'auto'
  87. displayIndent: '0', // default for indentshift when set to 'auto'
  88. wrapperFactory: null, // The wrapper factory to use
  89. font: null, // The FontData object to use
  90. cssStyles: null // The CssStyles object to use
  91. };
  92. /**
  93. * The default styles for the output jax
  94. */
  95. public static commonStyles: CssStyleList = {};
  96. /**
  97. * Used for collecting styles needed for the output jax
  98. */
  99. public cssStyles: CssStyles;
  100. /**
  101. * The MathDocument for the math we find
  102. */
  103. public document: MathDocument<N, T, D>;
  104. /**
  105. * the MathItem currently being processed
  106. */
  107. public math: MathItem<N, T, D>;
  108. /**
  109. * The container element for the math
  110. */
  111. public container: N;
  112. /**
  113. * The top-level table, if any
  114. */
  115. public table: AnyWrapper;
  116. /**
  117. * The pixels per em for the math item being processed
  118. */
  119. public pxPerEm: number;
  120. /**
  121. * The data for the font in use
  122. */
  123. public font: FD;
  124. /**
  125. * The wrapper factory for the MathML nodes
  126. */
  127. public factory: F;
  128. /**
  129. * A map from the nodes in the expression currently being processed to the
  130. * wrapper nodes for them (used by functions like core() to locate the wrappers
  131. * from the core nodes)
  132. */
  133. public nodeMap: Map<MmlNode, W>;
  134. /**
  135. * Node used to test for in-line metric data
  136. */
  137. public testInline: N;
  138. /**
  139. * Node used to test for display metric data
  140. */
  141. public testDisplay: N;
  142. /**
  143. * Cache of unknonw character bounding boxes for this element
  144. */
  145. protected unknownCache: UnknownVariantMap;
  146. /*****************************************************************/
  147. /**
  148. * Get the WrapperFactory and connect it to this output jax
  149. * Get the cssStyle and font objects
  150. *
  151. * @param {OptionList} options The configuration options
  152. * @param {CommonWrapperFactory} defaultFactory The default wrapper factory class
  153. * @param {FC} defaultFont The default FontData constructor
  154. * @constructor
  155. */
  156. constructor(options: OptionList = null,
  157. defaultFactory: typeof CommonWrapperFactory = null,
  158. defaultFont: FC = null) {
  159. const [jaxOptions, fontOptions] = separateOptions(options, defaultFont.OPTIONS);
  160. super(jaxOptions);
  161. this.factory = this.options.wrapperFactory ||
  162. new defaultFactory<CommonOutputJax<N, T, D, W, F, FD, FC>, W,
  163. AnyWrapperClass, CharOptions, DelimiterData, FD>();
  164. this.factory.jax = this;
  165. this.cssStyles = this.options.cssStyles || new CssStyles();
  166. this.font = this.options.font || new defaultFont(fontOptions);
  167. this.unknownCache = new Map();
  168. }
  169. /*****************************************************************/
  170. /**
  171. * Save the math document
  172. * Create the mjx-container node
  173. * Create the DOM output for the root MathML math node in the container
  174. * Return the container node
  175. *
  176. * @override
  177. */
  178. public typeset(math: MathItem<N, T, D>, html: MathDocument<N, T, D>) {
  179. this.setDocument(html);
  180. let node = this.createNode();
  181. this.toDOM(math, node, html);
  182. return node;
  183. }
  184. /**
  185. * @return {N} The container DOM node for the typeset math
  186. */
  187. protected createNode(): N {
  188. const jax = (this.constructor as typeof CommonOutputJax).NAME;
  189. return this.html('mjx-container', {'class': 'MathJax', jax: jax});
  190. }
  191. /**
  192. * @param {N} node The container whose scale is to be set
  193. */
  194. protected setScale(node: N) {
  195. const scale = this.math.metrics.scale * this.options.scale;
  196. if (scale !== 1) {
  197. this.adaptor.setStyle(node, 'fontSize', percent(scale));
  198. }
  199. }
  200. /**
  201. * Save the math document, if any, and the math item
  202. * Set the document where HTML nodes will be created via the adaptor
  203. * Recursively set the TeX classes for the nodes
  204. * Set the scaling for the DOM node
  205. * Create the nodeMap (maps MathML nodes to corresponding wrappers)
  206. * Create the HTML output for the root MathML node in the container
  207. * Clear the nodeMape
  208. * Execute the post-filters
  209. *
  210. * @param {MathItem} math The math item to convert
  211. * @param {N} node The contaier to place the result into
  212. * @param {MathDocument} html The document containing the math
  213. */
  214. public toDOM(math: MathItem<N, T, D>, node: N, html: MathDocument<N, T, D> = null) {
  215. this.setDocument(html);
  216. this.math = math;
  217. this.pxPerEm = math.metrics.ex / this.font.params.x_height;
  218. math.root.setTeXclass(null);
  219. this.setScale(node);
  220. this.nodeMap = new Map<MmlNode, W>();
  221. this.container = node;
  222. this.processMath(math.root, node);
  223. this.nodeMap = null;
  224. this.executeFilters(this.postFilters, math, html, node);
  225. }
  226. /**
  227. * This is the actual typesetting function supplied by the subclass
  228. *
  229. * @param {MmlNode} math The intenral MathML node of the root math element to process
  230. * @param {N} node The container node where the math is to be typeset
  231. */
  232. protected abstract processMath(math: MmlNode, node: N): void;
  233. /*****************************************************************/
  234. /**
  235. * @param {MathItem} math The MathItem to get the bounding box for
  236. * @param {MathDocument} html The MathDocument for the math
  237. */
  238. public getBBox(math: MathItem<N, T, D>, html: MathDocument<N, T, D>) {
  239. this.setDocument(html);
  240. this.math = math;
  241. math.root.setTeXclass(null);
  242. this.nodeMap = new Map<MmlNode, W>();
  243. let bbox = this.factory.wrap(math.root).getOuterBBox();
  244. this.nodeMap = null;
  245. return bbox;
  246. }
  247. /*****************************************************************/
  248. /**
  249. * @override
  250. */
  251. public getMetrics(html: MathDocument<N, T, D>) {
  252. this.setDocument(html);
  253. const adaptor = this.adaptor;
  254. const maps = this.getMetricMaps(html);
  255. for (const math of html.math) {
  256. const parent = adaptor.parent(math.start.node);
  257. if (math.state() < STATE.METRICS && parent) {
  258. const map = maps[math.display ? 1 : 0];
  259. const {em, ex, containerWidth, lineWidth, scale, family} = map.get(parent);
  260. math.setMetrics(em, ex, containerWidth, lineWidth, scale);
  261. if (this.options.mtextInheritFont) {
  262. math.outputData.mtextFamily = family;
  263. }
  264. if (this.options.merrorInheritFont) {
  265. math.outputData.merrorFamily = family;
  266. }
  267. math.state(STATE.METRICS);
  268. }
  269. }
  270. }
  271. /**
  272. * @param {N} node The container node whose metrics are to be measured
  273. * @param {boolean} display True if the metrics are for displayed math
  274. * @return {Metrics} Object containing em, ex, containerWidth, etc.
  275. */
  276. public getMetricsFor(node: N, display: boolean): ExtendedMetrics {
  277. const getFamily = (this.options.mtextInheritFont || this.options.merrorInheritFont);
  278. const test = this.getTestElement(node, display);
  279. const metrics = this.measureMetrics(test, getFamily);
  280. this.adaptor.remove(test);
  281. return metrics;
  282. }
  283. /**
  284. * Get a MetricMap for the math list
  285. *
  286. * @param {MathDocument} html The math document whose math list is to be processed.
  287. * @return {MetricMap[]} The node-to-metrics maps for all the containers that have math
  288. */
  289. protected getMetricMaps(html: MathDocument<N, T, D>): MetricMap<N>[] {
  290. const adaptor = this.adaptor;
  291. const domMaps = [new Map() as MetricDomMap<N>, new Map() as MetricDomMap<N>];
  292. //
  293. // Add the test elements all at once (so only one reflow)
  294. // Currently, we do one test for each container element for in-line and one for display math
  295. // (since we need different techniques for the two forms to avoid a WebKit bug).
  296. // This may need to be changed to handle floating elements better, since that has to be
  297. // done at the location of the math itself, not necessarily the end of the container.
  298. //
  299. for (const math of html.math) {
  300. const node = adaptor.parent(math.start.node);
  301. if (node && math.state() < STATE.METRICS) {
  302. const map = domMaps[math.display ? 1 : 0];
  303. if (!map.has(node)) {
  304. map.set(node, this.getTestElement(node, math.display));
  305. }
  306. }
  307. }
  308. //
  309. // Measure the metrics for all the mapped elements
  310. //
  311. const getFamily = this.options.mtextInheritFont || this.options.merrorInheritFont;
  312. const maps = [new Map() as MetricMap<N>, new Map() as MetricMap<N>];
  313. for (const i of maps.keys()) {
  314. for (const node of domMaps[i].keys()) {
  315. maps[i].set(node, this.measureMetrics(domMaps[i].get(node), getFamily));
  316. }
  317. }
  318. //
  319. // Remove the test elements
  320. //
  321. for (const i of maps.keys()) {
  322. for (const node of domMaps[i].values()) {
  323. adaptor.remove(node);
  324. }
  325. }
  326. return maps;
  327. }
  328. /**
  329. * @param {N} node The math element to be measured
  330. * @return {N} The test elements that were added
  331. */
  332. protected getTestElement(node: N, display: boolean): N {
  333. const adaptor = this.adaptor;
  334. if (!this.testInline) {
  335. this.testInline = this.html('mjx-test', {style: {
  336. display: 'inline-block',
  337. width: '100%',
  338. 'font-style': 'normal',
  339. 'font-weight': 'normal',
  340. 'font-size': '100%',
  341. 'font-size-adjust': 'none',
  342. 'text-indent': 0,
  343. 'text-transform': 'none',
  344. 'letter-spacing': 'normal',
  345. 'word-spacing': 'normal',
  346. overflow: 'hidden',
  347. height: '1px',
  348. 'margin-right': '-1px'
  349. }}, [
  350. this.html('mjx-left-box', {style: {
  351. display: 'inline-block',
  352. width: 0,
  353. 'float': 'left'
  354. }}),
  355. this.html('mjx-ex-box', {style: {
  356. position: 'absolute',
  357. overflow: 'hidden',
  358. width: '1px', height: '60ex'
  359. }}),
  360. this.html('mjx-right-box', {style: {
  361. display: 'inline-block',
  362. width: 0,
  363. 'float': 'right'
  364. }})
  365. ]);
  366. this.testDisplay = adaptor.clone(this.testInline);
  367. adaptor.setStyle(this.testDisplay, 'display', 'table');
  368. adaptor.setStyle(this.testDisplay, 'margin-right', '');
  369. adaptor.setStyle(adaptor.firstChild(this.testDisplay) as N, 'display', 'none');
  370. const right = adaptor.lastChild(this.testDisplay) as N;
  371. adaptor.setStyle(right, 'display', 'table-cell');
  372. adaptor.setStyle(right, 'width', '10000em');
  373. adaptor.setStyle(right, 'float', '');
  374. }
  375. return adaptor.append(node, adaptor.clone(display ? this.testDisplay : this.testInline) as N) as N;
  376. }
  377. /**
  378. * @param {N} node The test node to measure
  379. * @param {boolean} getFamily True if font family of surroundings is to be determined
  380. * @return {ExtendedMetrics} The metric data for the given node
  381. */
  382. protected measureMetrics(node: N, getFamily: boolean): ExtendedMetrics {
  383. const adaptor = this.adaptor;
  384. const family = (getFamily ? adaptor.fontFamily(node) : '');
  385. const em = adaptor.fontSize(node);
  386. const [w, h] = adaptor.nodeSize(adaptor.childNode(node, 1) as N);
  387. const ex = (w ? h / 60 : em * this.options.exFactor);
  388. const containerWidth = (!w ? 1000000 : adaptor.getStyle(node, 'display') === 'table' ?
  389. adaptor.nodeSize(adaptor.lastChild(node) as N)[0] - 1 :
  390. adaptor.nodeBBox(adaptor.lastChild(node) as N).left -
  391. adaptor.nodeBBox(adaptor.firstChild(node) as N).left - 2);
  392. const scale = Math.max(this.options.minScale,
  393. this.options.matchFontHeight ? ex / this.font.params.x_height / em : 1);
  394. const lineWidth = 1000000; // no linebreaking (otherwise would be a percentage of cwidth)
  395. return {em, ex, containerWidth, lineWidth, scale, family};
  396. }
  397. /*****************************************************************/
  398. /**
  399. * @override
  400. */
  401. public styleSheet(html: MathDocument<N, T, D>) {
  402. this.setDocument(html);
  403. //
  404. // Start with the common styles
  405. //
  406. this.cssStyles.clear();
  407. this.cssStyles.addStyles((this.constructor as typeof CommonOutputJax).commonStyles);
  408. //
  409. // Add document-specific styles
  410. //
  411. if ('getStyles' in html) {
  412. for (const styles of ((html as any).getStyles() as CssStyleList[])) {
  413. this.cssStyles.addStyles(styles);
  414. }
  415. }
  416. //
  417. // Gather the CSS from the classes and font
  418. //
  419. this.addWrapperStyles(this.cssStyles);
  420. this.addFontStyles(this.cssStyles);
  421. //
  422. // Create the stylesheet for the CSS
  423. //
  424. const sheet = this.html('style', {id: 'MJX-styles'}, [this.text('\n' + this.cssStyles.cssText + '\n')]);
  425. return sheet as N;
  426. }
  427. /**
  428. * @param {CssStyles} styles The style object to add to
  429. */
  430. protected addFontStyles(styles: CssStyles) {
  431. styles.addStyles(this.font.styles);
  432. }
  433. /**
  434. * @param {CssStyles} styles The style object to add to
  435. */
  436. protected addWrapperStyles(styles: CssStyles) {
  437. for (const kind of this.factory.getKinds()) {
  438. this.addClassStyles(this.factory.getNodeClass(kind), styles);
  439. }
  440. }
  441. /**
  442. * @param {typeof CommonWrapper} CLASS The Wrapper class whose styles are to be added
  443. * @param {CssStyles} styles The style object to add to.
  444. */
  445. protected addClassStyles(CLASS: typeof CommonWrapper, styles: CssStyles) {
  446. styles.addStyles(CLASS.styles);
  447. }
  448. /*****************************************************************/
  449. /**
  450. * @param {MathDocument} html The document to be used
  451. */
  452. protected setDocument(html: MathDocument<N, T, D>) {
  453. if (html) {
  454. this.document = html;
  455. this.adaptor.document = html.document;
  456. }
  457. }
  458. /**
  459. * @param {string} type The type of HTML node to create
  460. * @param {OptionList} def The properties to set on the HTML node
  461. * @param {(N|T)[]} content Array of child nodes to set for the HTML node
  462. * @param {string} ns The namespace for the element
  463. * @return {N} The newly created DOM tree
  464. */
  465. public html(type: string, def: OptionList = {}, content: (N | T)[] = [], ns?: string): N {
  466. return this.adaptor.node(type, def, content, ns);
  467. }
  468. /**
  469. * @param {string} text The text string for which to make a text node
  470. *
  471. * @return {T} A text node with the given text
  472. */
  473. public text(text: string): T {
  474. return this.adaptor.text(text);
  475. }
  476. /**
  477. * @param {number} m A number to be shown with a fixed number of digits
  478. * @param {number=} n The number of digits to use
  479. * @return {string} The formatted number
  480. */
  481. public fixed(m: number, n: number = 3): string {
  482. if (Math.abs(m) < .0006) {
  483. return '0';
  484. }
  485. return m.toFixed(n).replace(/\.?0+$/, '');
  486. }
  487. /*****************************************************************/
  488. /*
  489. * Methods for handling text that is not in the current MathJax font
  490. */
  491. /**
  492. * Create a DOM node for text from a specific CSS font, or that is
  493. * not in the current MathJax font
  494. *
  495. * @param {string} text The text to be displayed
  496. * @param {string} variant The name of the variant for the text
  497. * @return {N} The text element containing the text
  498. */
  499. public abstract unknownText(text: string, variant: string): N;
  500. /**
  501. * Measure text from a specific font, or that isn't in the MathJax font
  502. *
  503. * @param {string} text The text to measure
  504. * @param {string} variant The variant for the text
  505. * @param {CssFontData} font The family, italic, and bold data for explicit fonts
  506. * @return {UnknownBBox} The width, height, and depth of the text (in ems)
  507. */
  508. public measureText(text: string, variant: string, font: CssFontData = ['', false, false]): UnknownBBox {
  509. const node = this.unknownText(text, variant);
  510. if (variant === '-explicitFont') {
  511. const styles = this.cssFontStyles(font);
  512. this.adaptor.setAttributes(node, {style: styles});
  513. }
  514. return this.measureTextNodeWithCache(node, text, variant, font);
  515. }
  516. /**
  517. * Get the size of a text node, caching the result, and using
  518. * a cached result, if there is one.
  519. *
  520. * @param {N} text The text element to measure
  521. * @param {string} chars The string contained in the text node
  522. * @param {string} variant The variant for the text
  523. * @param {CssFontData} font The family, italic, and bold data for explicit fonts
  524. * @return {UnknownBBox} The width, height and depth for the text
  525. */
  526. public measureTextNodeWithCache(
  527. text: N, chars: string, variant: string,
  528. font: CssFontData = ['', false, false]
  529. ): UnknownBBox {
  530. if (variant === '-explicitFont') {
  531. variant = [font[0], font[1] ? 'T' : 'F', font[2] ? 'T' : 'F', ''].join('-');
  532. }
  533. if (!this.unknownCache.has(variant)) {
  534. this.unknownCache.set(variant, new Map());
  535. }
  536. const map = this.unknownCache.get(variant);
  537. const cached = map.get(chars);
  538. if (cached) return cached;
  539. const bbox = this.measureTextNode(text);
  540. map.set(chars, bbox);
  541. return bbox;
  542. }
  543. /**
  544. * Measure the width of a text element by placing it in the page
  545. * and looking up its size (fake the height and depth, since we can't measure that)
  546. *
  547. * @param {N} text The text element to measure
  548. * @return {UnknownBBox} The width, height and depth for the text (in ems)
  549. */
  550. public abstract measureTextNode(text: N): UnknownBBox;
  551. /**
  552. * Measure the width, height and depth of an annotation-xml node's content
  553. *
  554. * @param{N} xml The xml content node to be measured
  555. * @return {UnknownBBox} The width, height, and depth of the content
  556. */
  557. public measureXMLnode(xml: N): UnknownBBox {
  558. const adaptor = this.adaptor;
  559. const content = this.html('mjx-xml-block', {style: {display: 'inline-block'}}, [adaptor.clone(xml)]);
  560. const base = this.html('mjx-baseline', {style: {display: 'inline-block', width: 0, height: 0}});
  561. const style = {
  562. position: 'absolute',
  563. display: 'inline-block',
  564. 'font-family': 'initial',
  565. 'line-height': 'normal'
  566. };
  567. const node = this.html('mjx-measure-xml', {style}, [base, content]);
  568. adaptor.append(adaptor.parent(this.math.start.node), this.container);
  569. adaptor.append(this.container, node);
  570. const em = this.math.metrics.em * this.math.metrics.scale;
  571. const {left, right, bottom, top} = adaptor.nodeBBox(content);
  572. const w = (right - left) / em;
  573. const h = (adaptor.nodeBBox(base).top - top) / em;
  574. const d = (bottom - top) / em - h;
  575. adaptor.remove(this.container);
  576. adaptor.remove(node);
  577. return {w, h, d};
  578. }
  579. /**
  580. * @param {CssFontData} font The family, style, and weight for the given font
  581. * @param {StyleList} styles The style object to add the font data to
  582. * @return {StyleList} The modified (or initialized) style object
  583. */
  584. public cssFontStyles(font: CssFontData, styles: StyleList = {}): StyleList {
  585. const [family, italic, bold] = font;
  586. styles['font-family'] = this.font.getFamily(family);
  587. if (italic) styles['font-style'] = 'italic';
  588. if (bold) styles['font-weight'] = 'bold';
  589. return styles;
  590. }
  591. /**
  592. * @param {Styles} styles The style object to query
  593. * @return {CssFontData} The family, italic, and boolean values
  594. */
  595. public getFontData(styles: Styles): CssFontData {
  596. if (!styles) {
  597. styles = new Styles();
  598. }
  599. return [this.font.getFamily(styles.get('font-family')),
  600. styles.get('font-style') === 'italic',
  601. styles.get('font-weight') === 'bold'] as CssFontData;
  602. }
  603. }