scriptbase.ts 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709
  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 a base mixin for CommonMsubsup, CommonMunderover
  19. * and their relatives. (Since munderover can become msubsup
  20. * when movablelimits is set, munderover needs to be able to
  21. * do the same thing as msubsup in some cases.)
  22. *
  23. * @author dpvc@mathjax.org (Davide Cervone)
  24. */
  25. import {AnyWrapper, WrapperConstructor, Constructor, AnyWrapperClass} from '../Wrapper.js';
  26. import {CommonMo} from './mo.js';
  27. import {CommonMunderover} from './munderover.js';
  28. import {TEXCLASS} from '../../../core/MmlTree/MmlNode.js';
  29. import {MmlMsubsup} from '../../../core/MmlTree/MmlNodes/msubsup.js';
  30. import {MmlMo} from '../../../core/MmlTree/MmlNodes/mo.js';
  31. import {BBox} from '../../../util/BBox.js';
  32. import {DIRECTION} from '../FontData.js';
  33. /*****************************************************************/
  34. /**
  35. * The CommonScriptbase interface
  36. *
  37. * @template W The child-node Wrapper class
  38. */
  39. export interface CommonScriptbase<W extends AnyWrapper> extends AnyWrapper {
  40. /**
  41. * The core mi or mo of the base (or the base itself if there isn't one)
  42. */
  43. readonly baseCore: W;
  44. /**
  45. * The base element's wrapper
  46. */
  47. readonly baseChild: W;
  48. /**
  49. * The relative scaling of the base compared to the munderover/msubsup
  50. */
  51. readonly baseScale: number;
  52. /**
  53. * The italic correction of the base (if any)
  54. */
  55. readonly baseIc: number;
  56. /**
  57. * True if base italic correction should be removed (msub and msubsup or mathaccents)
  58. */
  59. readonly baseRemoveIc: boolean;
  60. /**
  61. * True if the base is a single character
  62. */
  63. readonly baseIsChar: boolean;
  64. /**
  65. * True if the base has an accent under or over
  66. */
  67. readonly baseHasAccentOver: boolean;
  68. readonly baseHasAccentUnder: boolean;
  69. /**
  70. * True if this is an overline or underline
  71. */
  72. readonly isLineAbove: boolean;
  73. readonly isLineBelow: boolean;
  74. /**
  75. * True if this is an msup with script that is a math accent
  76. */
  77. readonly isMathAccent: boolean;
  78. /**
  79. * The script element's wrapper (overridden in subclasses)
  80. */
  81. readonly scriptChild: W;
  82. /***************************************************************************/
  83. /*
  84. * Methods for information about the core element for the base
  85. */
  86. /**
  87. * @return {W} The wrapper for the base core mi or mo (or whatever)
  88. */
  89. getBaseCore(): W;
  90. /**
  91. * @return {W} The base fence item or null
  92. */
  93. getSemanticBase(): W;
  94. /**
  95. * Recursively retrieves an element for a given fencepointer.
  96. *
  97. * @param {W} fence The potential fence.
  98. * @param {string} id The fencepointer id.
  99. * @return {W} The original fence the scripts belong to.
  100. */
  101. getBaseFence(fence: W, id: string): W;
  102. /**
  103. * @return {number} The scaling factor for the base core relative to the munderover/msubsup
  104. */
  105. getBaseScale(): number;
  106. /**
  107. * The base's italic correction (properly scaled)
  108. */
  109. getBaseIc(): number;
  110. /**
  111. * An adjusted italic correction (for slightly better results)
  112. */
  113. getAdjustedIc(): number;
  114. /**
  115. * @return {boolean} True if the base is an mi, mn, or mo (not a largeop) consisting of
  116. * a single unstretched character
  117. */
  118. isCharBase(): boolean;
  119. /**
  120. * Determine if the under- and overscripts are under- or overlines.
  121. */
  122. checkLineAccents(): void;
  123. /**
  124. * @param {W} script The script node to check for being a line
  125. */
  126. isLineAccent(script: W): boolean;
  127. /***************************************************************************/
  128. /*
  129. * Methods for sub-sup nodes
  130. */
  131. /**
  132. * @return {number} The base child's width without the base italic correction (if not needed)
  133. */
  134. getBaseWidth(): number;
  135. /**
  136. * Get the shift for the script (implemented in subclasses)
  137. *
  138. * @return {number[]} The horizontal and vertical offsets for the script
  139. */
  140. getOffset(): number[];
  141. /**
  142. * @param {number} n The value to use if the base isn't a (non-large-op, unstretched) char
  143. * @return {number} Either n or 0
  144. */
  145. baseCharZero(n: number): number;
  146. /**
  147. * Get the shift for a subscript (TeXBook Appendix G 18ab)
  148. *
  149. * @return {number} The vertical offset for the script
  150. */
  151. getV(): number;
  152. /**
  153. * Get the shift for a superscript (TeXBook Appendix G 18acd)
  154. *
  155. * @return {number} The vertical offset for the script
  156. */
  157. getU(): number;
  158. /***************************************************************************/
  159. /*
  160. * Methods for under-over nodes
  161. */
  162. /**
  163. * @return {boolean} True if the base has movablelimits (needed by munderover)
  164. */
  165. hasMovableLimits(): boolean;
  166. /**
  167. * Get the separation and offset for overscripts (TeXBoox Appendix G 13, 13a)
  168. *
  169. * @param {BBox} basebox The bounding box of the base
  170. * @param {BBox} overbox The bounding box of the overscript
  171. * @return {number[]} The separation between their boxes, and the offset of the overscript
  172. */
  173. getOverKU(basebox: BBox, overbox: BBox): number[];
  174. /**
  175. * Get the separation and offset for underscripts (TeXBoox Appendix G 13, 13a)
  176. *
  177. * @param {BBox} basebox The bounding box of the base
  178. * @param {BBox} underbox The bounding box of the underscript
  179. * @return {number[]} The separation between their boxes, and the offset of the underscript
  180. */
  181. getUnderKV(basebox: BBox, underbox: BBox): number[];
  182. /**
  183. * @param {BBox[]} boxes The bounding boxes whose offsets are to be computed
  184. * @param {number[]=} delta The initial x offsets of the boxes
  185. * @return {number[]} The actual offsets needed to center the boxes in the stack
  186. */
  187. getDeltaW(boxes: BBox[], delta?: number[]): number[];
  188. /**
  189. * @param {boolean=} noskew Whether to ignore the skew amount
  190. * @return {number} The offset for under and over
  191. */
  192. getDelta(noskew?: boolean): number;
  193. /**
  194. * Handle horizontal stretching of children to match greatest width
  195. * of all children
  196. */
  197. stretchChildren(): void;
  198. }
  199. export interface CommonScriptbaseClass extends AnyWrapperClass {
  200. /**
  201. * Set to true for munderover/munder/mover/msup (Appendix G 13)
  202. */
  203. useIC: boolean;
  204. }
  205. /**
  206. * Shorthand for the CommonScriptbase constructor
  207. *
  208. * @template W The child-node Wrapper class
  209. */
  210. export type ScriptbaseConstructor<W extends AnyWrapper> = Constructor<CommonScriptbase<W>>;
  211. /*****************************************************************/
  212. /**
  213. * A base class for msup/msub/msubsup and munder/mover/munderover
  214. * wrapper mixin implementations
  215. *
  216. * @template W The child-node Wrapper class
  217. * @template T The Wrapper class constructor type
  218. */
  219. export function CommonScriptbaseMixin<
  220. W extends AnyWrapper,
  221. T extends WrapperConstructor
  222. >(Base: T): ScriptbaseConstructor<W> & T {
  223. return class extends Base {
  224. /**
  225. * Set to false for msubsup/msub (Appendix G 13)
  226. */
  227. public static useIC: boolean = true;
  228. /**
  229. * The core mi or mo of the base (or the base itself if there isn't one)
  230. */
  231. public baseCore: W;
  232. /**
  233. * The base element's wrapper
  234. */
  235. public baseScale: number = 1;
  236. /**
  237. * The relative scaling of the base compared to the munderover/msubsup
  238. */
  239. public baseIc: number = 0;
  240. /**
  241. * True if base italic correction should be removed (msub and msubsup or mathaccents)
  242. */
  243. public baseRemoveIc: boolean = false;
  244. /**
  245. * True if the base is a single character
  246. */
  247. public baseIsChar: boolean = false;
  248. /**
  249. * True if the base has an accent under or over
  250. */
  251. public baseHasAccentOver: boolean = null;
  252. public baseHasAccentUnder: boolean = null;
  253. /**
  254. * True if this is an overline or underline
  255. */
  256. public isLineAbove: boolean = false;
  257. public isLineBelow: boolean = false;
  258. /**
  259. * True if this is an msup with script that is a math accent
  260. */
  261. public isMathAccent: boolean = false;
  262. /**
  263. * @return {W} The base element's wrapper
  264. */
  265. public get baseChild(): W {
  266. return this.childNodes[(this.node as MmlMsubsup).base];
  267. }
  268. /**
  269. * @return {W} The script element's wrapper (overridden in subclasses)
  270. */
  271. public get scriptChild(): W {
  272. return this.childNodes[1];
  273. }
  274. /**
  275. * @override
  276. */
  277. constructor(...args: any[]) {
  278. super(...args);
  279. //
  280. // Find the base core
  281. //
  282. const core = this.baseCore = this.getBaseCore();
  283. if (!core) return;
  284. //
  285. // Get information about the base element
  286. //
  287. this.setBaseAccentsFor(core);
  288. this.baseScale = this.getBaseScale();
  289. this.baseIc = this.getBaseIc();
  290. this.baseIsChar = this.isCharBase();
  291. //
  292. // Determine if we are setting a mathaccent
  293. //
  294. this.isMathAccent = this.baseIsChar &&
  295. (this.scriptChild && !!this.scriptChild.coreMO().node.getProperty('mathaccent')) as boolean;
  296. //
  297. // Check for overline/underline accents
  298. //
  299. this.checkLineAccents();
  300. //
  301. // Check if the base is a mi or mo that needs italic correction removed
  302. //
  303. this.baseRemoveIc = !this.isLineAbove && !this.isLineBelow &&
  304. (!(this.constructor as CommonScriptbaseClass).useIC || this.isMathAccent);
  305. }
  306. /***************************************************************************/
  307. /*
  308. * Methods for information about the core element for the base
  309. */
  310. /**
  311. * @return {W} The wrapper for the base core mi or mo (or whatever)
  312. */
  313. public getBaseCore(): W {
  314. let core = this.getSemanticBase() || this.childNodes[0];
  315. while (core &&
  316. ((core.childNodes.length === 1 &&
  317. (core.node.isKind('mrow') ||
  318. (core.node.isKind('TeXAtom') && core.node.texClass !== TEXCLASS.VCENTER) ||
  319. core.node.isKind('mstyle') || core.node.isKind('mpadded') ||
  320. core.node.isKind('mphantom') || core.node.isKind('semantics'))) ||
  321. (core.node.isKind('munderover') && core.isMathAccent))) {
  322. this.setBaseAccentsFor(core);
  323. core = core.childNodes[0];
  324. }
  325. if (!core) {
  326. this.baseHasAccentOver = this.baseHasAccentUnder = false;
  327. }
  328. return core || this.childNodes[0];
  329. }
  330. /**
  331. * @param {W} core The element to check for accents
  332. */
  333. public setBaseAccentsFor(core: W) {
  334. if (core.node.isKind('munderover')) {
  335. if (this.baseHasAccentOver === null) {
  336. this.baseHasAccentOver = !!core.node.attributes.get('accent');
  337. }
  338. if (this.baseHasAccentUnder === null) {
  339. this.baseHasAccentUnder = !!core.node.attributes.get('accentunder');
  340. }
  341. }
  342. }
  343. /**
  344. * @return {W} The base fence item or null
  345. */
  346. public getSemanticBase(): W {
  347. let fence = this.node.attributes.getExplicit('data-semantic-fencepointer') as string;
  348. return this.getBaseFence(this.baseChild, fence);
  349. }
  350. /**
  351. * Recursively retrieves an element for a given fencepointer.
  352. *
  353. * @param {W} fence The potential fence.
  354. * @param {string} id The fencepointer id.
  355. * @return {W} The original fence the scripts belong to.
  356. */
  357. public getBaseFence(fence: W, id: string): W {
  358. if (!fence || !fence.node.attributes || !id) {
  359. return null;
  360. }
  361. if (fence.node.attributes.getExplicit('data-semantic-id') === id) {
  362. return fence;
  363. }
  364. for (const child of fence.childNodes) {
  365. const result = this.getBaseFence(child, id);
  366. if (result) {
  367. return result;
  368. }
  369. }
  370. return null;
  371. }
  372. /**
  373. * @return {number} The scaling factor for the base core relative to the munderover/msubsup
  374. */
  375. public getBaseScale(): number {
  376. let child = this.baseCore as any;
  377. let scale = 1;
  378. while (child && child !== this) {
  379. const bbox = child.getOuterBBox();
  380. scale *= bbox.rscale;
  381. child = child.parent;
  382. }
  383. return scale;
  384. }
  385. /**
  386. * The base's italic correction (properly scaled)
  387. */
  388. public getBaseIc(): number {
  389. return this.baseCore.getOuterBBox().ic * this.baseScale;
  390. }
  391. /**
  392. * An adjusted italic correction (for slightly better results)
  393. */
  394. public getAdjustedIc(): number {
  395. const bbox = this.baseCore.getOuterBBox();
  396. return (bbox.ic ? 1.05 * bbox.ic + .05 : 0) * this.baseScale;
  397. }
  398. /**
  399. * @return {boolean} True if the base is an mi, mn, or mo consisting of a single character
  400. */
  401. public isCharBase(): boolean {
  402. let base = this.baseCore;
  403. return (((base.node.isKind('mo') && (base as any).size === null) ||
  404. base.node.isKind('mi') || base.node.isKind('mn')) &&
  405. base.bbox.rscale === 1 && Array.from(base.getText()).length === 1);
  406. }
  407. /**
  408. * Determine if the under- and overscripts are under- or overlines.
  409. */
  410. public checkLineAccents() {
  411. if (!this.node.isKind('munderover')) return;
  412. if (this.node.isKind('mover')) {
  413. this.isLineAbove = this.isLineAccent(this.scriptChild);
  414. } else if (this.node.isKind('munder')) {
  415. this.isLineBelow = this.isLineAccent(this.scriptChild);
  416. } else {
  417. const mml = this as unknown as CommonMunderover<W>;
  418. this.isLineAbove = this.isLineAccent(mml.overChild);
  419. this.isLineBelow = this.isLineAccent(mml.underChild);
  420. }
  421. }
  422. /**
  423. * @param {W} script The script node to check for being a line
  424. * @return {boolean} True if the script is U+2015
  425. */
  426. public isLineAccent(script: W): boolean {
  427. const node = script.coreMO().node;
  428. return (node.isToken && (node as MmlMo).getText() === '\u2015');
  429. }
  430. /***************************************************************************/
  431. /*
  432. * Methods for sub-sup nodes
  433. */
  434. /**
  435. * @return {number} The base child's width without the base italic correction (if not needed)
  436. */
  437. public getBaseWidth(): number {
  438. const bbox = this.baseChild.getOuterBBox();
  439. return bbox.w * bbox.rscale - (this.baseRemoveIc ? this.baseIc : 0) + this.font.params.extra_ic;
  440. }
  441. /**
  442. * This gives the common bbox for msub and msup. It is overridden
  443. * for all the others (msubsup, munder, mover, munderover).
  444. *
  445. * @override
  446. */
  447. public computeBBox(bbox: BBox, recompute: boolean = false) {
  448. const w = this.getBaseWidth();
  449. const [x, y] = this.getOffset();
  450. bbox.append(this.baseChild.getOuterBBox());
  451. bbox.combine(this.scriptChild.getOuterBBox(), w + x, y);
  452. bbox.w += this.font.params.scriptspace;
  453. bbox.clean();
  454. this.setChildPWidths(recompute);
  455. }
  456. /**
  457. * Get the shift for the script (implemented in subclasses)
  458. *
  459. * @return {[number, number]} The horizontal and vertical offsets for the script
  460. */
  461. public getOffset(): [number, number] {
  462. return [0, 0];
  463. }
  464. /**
  465. * @param {number} n The value to use if the base isn't a (non-large-op, unstretched) char
  466. * @return {number} Either n or 0
  467. */
  468. public baseCharZero(n: number): number {
  469. const largeop = !!this.baseCore.node.attributes.get('largeop');
  470. const scale = this.baseScale;
  471. return (this.baseIsChar && !largeop && scale === 1 ? 0 : n);
  472. }
  473. /**
  474. * Get the shift for a subscript (TeXBook Appendix G 18ab)
  475. *
  476. * @return {number} The vertical offset for the script
  477. */
  478. public getV(): number {
  479. const bbox = this.baseCore.getOuterBBox();
  480. const sbox = this.scriptChild.getOuterBBox();
  481. const tex = this.font.params;
  482. const subscriptshift = this.length2em(this.node.attributes.get('subscriptshift'), tex.sub1);
  483. return Math.max(
  484. this.baseCharZero(bbox.d * this.baseScale + tex.sub_drop * sbox.rscale),
  485. subscriptshift,
  486. sbox.h * sbox.rscale - (4 / 5) * tex.x_height
  487. );
  488. }
  489. /**
  490. * Get the shift for a superscript (TeXBook Appendix G 18acd)
  491. *
  492. * @return {number} The vertical offset for the script
  493. */
  494. public getU(): number {
  495. const bbox = this.baseCore.getOuterBBox();
  496. const sbox = this.scriptChild.getOuterBBox();
  497. const tex = this.font.params;
  498. const attr = this.node.attributes.getList('displaystyle', 'superscriptshift');
  499. const prime = this.node.getProperty('texprimestyle');
  500. const p = prime ? tex.sup3 : (attr.displaystyle ? tex.sup1 : tex.sup2);
  501. const superscriptshift = this.length2em(attr.superscriptshift, p);
  502. return Math.max(
  503. this.baseCharZero(bbox.h * this.baseScale - tex.sup_drop * sbox.rscale),
  504. superscriptshift,
  505. sbox.d * sbox.rscale + (1 / 4) * tex.x_height
  506. );
  507. }
  508. /***************************************************************************/
  509. /*
  510. * Methods for under-over nodes
  511. */
  512. /**
  513. * @return {boolean} True if the base has movablelimits (needed by munderover)
  514. */
  515. public hasMovableLimits(): boolean {
  516. const display = this.node.attributes.get('displaystyle');
  517. const mo = this.baseChild.coreMO().node;
  518. return (!display && !!mo.attributes.get('movablelimits'));
  519. }
  520. /**
  521. * Get the separation and offset for overscripts (TeXBoox Appendix G 13, 13a)
  522. *
  523. * @param {BBox} basebox The bounding box of the base
  524. * @param {BBox} overbox The bounding box of the overscript
  525. * @return {[number, number]} The separation between their boxes, and the offset of the overscript
  526. */
  527. public getOverKU(basebox: BBox, overbox: BBox): [number, number] {
  528. const accent = this.node.attributes.get('accent') as boolean;
  529. const tex = this.font.params;
  530. const d = overbox.d * overbox.rscale;
  531. const t = tex.rule_thickness * tex.separation_factor;
  532. const delta = (this.baseHasAccentOver ? t : 0);
  533. const T = (this.isLineAbove ? 3 * tex.rule_thickness : t);
  534. const k = (accent ? T : Math.max(tex.big_op_spacing1, tex.big_op_spacing3 - Math.max(0, d))) - delta;
  535. return [k, basebox.h * basebox.rscale + k + d];
  536. }
  537. /**
  538. * Get the separation and offset for underscripts (TeXBoox Appendix G 13, 13a)
  539. *
  540. * @param {BBox} basebox The bounding box of the base
  541. * @param {BBox} underbox The bounding box of the underscript
  542. * @return {[number, number]} The separation between their boxes, and the offset of the underscript
  543. */
  544. public getUnderKV(basebox: BBox, underbox: BBox): [number, number] {
  545. const accent = this.node.attributes.get('accentunder') as boolean;
  546. const tex = this.font.params;
  547. const h = underbox.h * underbox.rscale;
  548. const t = tex.rule_thickness * tex.separation_factor;
  549. const delta = (this.baseHasAccentUnder ? t : 0);
  550. const T = (this.isLineBelow ? 3 * tex.rule_thickness : t);
  551. const k = (accent ? T : Math.max(tex.big_op_spacing2, tex.big_op_spacing4 - h)) - delta;
  552. return [k, -(basebox.d * basebox.rscale + k + h)];
  553. }
  554. /**
  555. * @param {BBox[]} boxes The bounding boxes whose offsets are to be computed
  556. * @param {number[]=} delta The initial x offsets of the boxes
  557. * @return {number[]} The actual offsets needed to center the boxes in the stack
  558. */
  559. public getDeltaW(boxes: BBox[], delta: number[] = [0, 0, 0]): number[] {
  560. const align = this.node.attributes.get('align');
  561. const widths = boxes.map(box => box.w * box.rscale);
  562. widths[0] -= (this.baseRemoveIc && !this.baseCore.node.attributes.get('largeop') ? this.baseIc : 0);
  563. const w = Math.max(...widths);
  564. const dw = [] as number[];
  565. let m = 0;
  566. for (const i of widths.keys()) {
  567. dw[i] = (align === 'center' ? (w - widths[i]) / 2 :
  568. align === 'right' ? w - widths[i] : 0) + delta[i];
  569. if (dw[i] < m) {
  570. m = -dw[i];
  571. }
  572. }
  573. if (m) {
  574. for (const i of dw.keys()) {
  575. dw[i] += m;
  576. }
  577. }
  578. [1, 2].map(i => dw[i] += (boxes[i] ? boxes[i].dx * boxes[0].scale : 0));
  579. return dw;
  580. }
  581. /**
  582. * @param {boolean=} noskew Whether to ignore the skew amount
  583. * @return {number} The offset for under and over
  584. */
  585. public getDelta(noskew: boolean = false): number {
  586. const accent = this.node.attributes.get('accent');
  587. const {sk, ic} = this.baseCore.getOuterBBox();
  588. return ((accent && !noskew ? sk : 0) + this.font.skewIcFactor * ic) * this.baseScale;
  589. }
  590. /**
  591. * Handle horizontal stretching of children to match greatest width
  592. * of all children
  593. */
  594. public stretchChildren() {
  595. let stretchy: AnyWrapper[] = [];
  596. //
  597. // Locate and count the stretchy children
  598. //
  599. for (const child of this.childNodes) {
  600. if (child.canStretch(DIRECTION.Horizontal)) {
  601. stretchy.push(child);
  602. }
  603. }
  604. let count = stretchy.length;
  605. let nodeCount = this.childNodes.length;
  606. if (count && nodeCount > 1) {
  607. let W = 0;
  608. //
  609. // If all the children are stretchy, find the largest one,
  610. // otherwise, find the width of the non-stretchy children.
  611. //
  612. let all = (count > 1 && count === nodeCount);
  613. for (const child of this.childNodes) {
  614. const noStretch = (child.stretch.dir === DIRECTION.None);
  615. if (all || noStretch) {
  616. const {w, rscale} = child.getOuterBBox(noStretch);
  617. if (w * rscale > W) W = w * rscale;
  618. }
  619. }
  620. //
  621. // Stretch the stretchable children
  622. //
  623. for (const child of stretchy) {
  624. (child.coreMO() as CommonMo).getStretchedVariant([W / child.bbox.rscale]);
  625. }
  626. }
  627. }
  628. };
  629. }