123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845 |
- /*************************************************************
- *
- * Copyright (c) 2017-2022 The MathJax Consortium
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- /**
- * @fileoverview Implements the CommonWrapper class
- *
- * @author dpvc@mathjax.org (Davide Cervone)
- */
- import {AbstractWrapper, WrapperClass} from '../../core/Tree/Wrapper.js';
- import {PropertyList} from '../../core/Tree/Node.js';
- import {MmlNode, TextNode, AbstractMmlNode, indentAttributes} from '../../core/MmlTree/MmlNode.js';
- import {MmlMo} from '../../core/MmlTree/MmlNodes/mo.js';
- import {Property} from '../../core/Tree/Node.js';
- import {unicodeChars} from '../../util/string.js';
- import * as LENGTHS from '../../util/lengths.js';
- import {Styles} from '../../util/Styles.js';
- import {StyleList} from '../../util/StyleList.js';
- import {CommonOutputJax} from './OutputJax.js';
- import {CommonWrapperFactory} from './WrapperFactory.js';
- import {BBox} from '../../util/BBox.js';
- import {FontData, DelimiterData, CharData, CharOptions, DIRECTION, NOSTRETCH} from './FontData.js';
- /*****************************************************************/
- /**
- * Shorthand for a dictionary object (an object of key:value pairs)
- */
- export type StringMap = {[key: string]: string};
- /**
- * MathML spacing rules
- */
- /* tslint:disable-next-line:whitespace */
- const SMALLSIZE = 2/18;
- /**
- * @param {boolean} script The scriptlevel
- * @param {number} size The space size
- * @return {number} The size clamped to SMALLSIZE when scriptlevel > 0
- */
- function MathMLSpace(script: boolean, size: number): number {
- return (script ? size < SMALLSIZE ? 0 : SMALLSIZE : size);
- }
- export type Constructor<T> = new(...args: any[]) => T;
- /**
- * Shorthands for wrappers and their constructors
- */
- export type AnyWrapper = CommonWrapper<any, any, any, any, any, any>;
- export type AnyWrapperClass = CommonWrapperClass<any, any, any, any, any, any>;
- export type WrapperConstructor = Constructor<AnyWrapper>;
- /*********************************************************/
- /**
- * The CommonWrapper class interface
- *
- * @template J The OutputJax type
- * @template W The Wrapper type
- * @template C The WrapperClass type
- * @template CC The CharOptions type
- * @template FD The FontData type
- */
- export interface CommonWrapperClass<
- J extends CommonOutputJax<any, any, any, W, CommonWrapperFactory<J, W, C, CC, DD, FD>, FD, any>,
- W extends CommonWrapper<J, W, C, CC, DD, FD>,
- C extends CommonWrapperClass<J, W, C, CC, DD, FD>,
- CC extends CharOptions,
- DD extends DelimiterData,
- FD extends FontData<CC, any, DD>
- > extends WrapperClass<MmlNode, CommonWrapper<J, W, C, CC, DD, FD>> {
- /**
- * @override
- */
- new(factory: CommonWrapperFactory<J, W, C, CC, DD, FD>, node: MmlNode, ...args: any[]): W;
- }
- /*****************************************************************/
- /**
- * The base CommonWrapper class
- *
- * @template J The OutputJax type
- * @template W The Wrapper type
- * @template C The WrapperClass type
- * @template CC The CharOptions type
- * @template FD The FontData type
- */
- export class CommonWrapper<
- J extends CommonOutputJax<any, any, any, W, CommonWrapperFactory<J, W, C, CC, DD, FD>, FD, any>,
- W extends CommonWrapper<J, W, C, CC, DD, FD>,
- C extends CommonWrapperClass<J, W, C, CC, DD, FD>,
- CC extends CharOptions,
- DD extends DelimiterData,
- FD extends FontData<CC, any, DD>
- > extends AbstractWrapper<MmlNode, CommonWrapper<J, W, C, CC, DD, FD>> {
- /**
- * The wrapper kind
- */
- public static kind: string = 'unknown';
- /**
- * Any styles needed for the class
- */
- public static styles: StyleList = {};
- /**
- * Styles that should not be passed on from style attribute
- */
- public static removeStyles: string[] = [
- 'fontSize', 'fontFamily', 'fontWeight',
- 'fontStyle', 'fontVariant', 'font'
- ];
- /**
- * Non-MathML attributes on MathML elements NOT to be copied to the
- * corresponding DOM elements. If set to false, then the attribute
- * WILL be copied. Most of these (like the font attributes) are handled
- * in other ways.
- */
- public static skipAttributes: {[name: string]: boolean} = {
- fontfamily: true, fontsize: true, fontweight: true, fontstyle: true,
- color: true, background: true,
- 'class': true, href: true, style: true,
- xmlns: true
- };
- /**
- * The translation of mathvariant to bold styles, or to remove
- * bold from a mathvariant.
- */
- public static BOLDVARIANTS: {[name: string]: StringMap} = {
- bold: {
- normal: 'bold',
- italic: 'bold-italic',
- fraktur: 'bold-fraktur',
- script: 'bold-script',
- 'sans-serif': 'bold-sans-serif',
- 'sans-serif-italic': 'sans-serif-bold-italic'
- },
- normal: {
- bold: 'normal',
- 'bold-italic': 'italic',
- 'bold-fraktur': 'fraktur',
- 'bold-script': 'script',
- 'bold-sans-serif': 'sans-serif',
- 'sans-serif-bold-italic': 'sans-serif-italic'
- }
- };
- /**
- * The translation of mathvariant to italic styles, or to remove
- * italic from a mathvariant.
- */
- public static ITALICVARIANTS: {[name: string]: StringMap} = {
- italic: {
- normal: 'italic',
- bold: 'bold-italic',
- 'sans-serif': 'sans-serif-italic',
- 'bold-sans-serif': 'sans-serif-bold-italic'
- },
- normal: {
- italic: 'normal',
- 'bold-italic': 'bold',
- 'sans-serif-italic': 'sans-serif',
- 'sans-serif-bold-italic': 'bold-sans-serif'
- }
- };
- /**
- * The factory used to create more wrappers
- */
- protected factory: CommonWrapperFactory<J, W, C, CC, DD, FD>;
- /**
- * The parent of this node
- */
- public parent: W = null;
- /**
- * The children of this node
- */
- public childNodes: W[];
- /**
- * Styles that must be handled directly by the wrappers (mostly having to do with fonts)
- */
- protected removedStyles: StringMap = null;
- /**
- * The explicit styles set by the node
- */
- protected styles: Styles = null;
- /**
- * The mathvariant for this node
- */
- public variant: string = '';
- /**
- * The bounding box for this node
- */
- public bbox: BBox;
- /**
- * Whether the bounding box has been computed yet
- */
- protected bboxComputed: boolean = false;
- /**
- * Delimiter data for stretching this node (NOSTRETCH means not yet determined)
- */
- public stretch: DD = NOSTRETCH as DD;
- /**
- * Easy access to the font parameters
- */
- public font: FD = null;
- /**
- * Easy access to the output jax for this node
- */
- get jax() {
- return this.factory.jax;
- }
- /**
- * Easy access to the DOMAdaptor object
- */
- get adaptor() {
- return this.factory.jax.adaptor;
- }
- /**
- * Easy access to the metric data for this node
- */
- get metrics() {
- return this.factory.jax.math.metrics;
- }
- /**
- * True if children with percentage widths should be resolved by this container
- */
- get fixesPWidth() {
- return !this.node.notParent && !this.node.isToken;
- }
- /*******************************************************************/
- /**
- * @override
- */
- constructor(factory: CommonWrapperFactory<J, W, C, CC, DD, FD>, node: MmlNode, parent: W = null) {
- super(factory, node);
- this.parent = parent;
- this.font = factory.jax.font;
- this.bbox = BBox.zero();
- this.getStyles();
- this.getVariant();
- this.getScale();
- this.getSpace();
- this.childNodes = node.childNodes.map((child: MmlNode) => {
- const wrapped = this.wrap(child);
- if (wrapped.bbox.pwidth && (node.notParent || node.isKind('math'))) {
- this.bbox.pwidth = BBox.fullWidth;
- }
- return wrapped;
- });
- }
- /**
- * @param {MmlNode} node The node to the wrapped
- * @param {W} parent The wrapped parent node
- * @return {W} The newly wrapped node
- */
- public wrap(node: MmlNode, parent: W = null): W {
- const wrapped = this.factory.wrap(node, parent || this);
- if (parent) {
- parent.childNodes.push(wrapped);
- }
- this.jax.nodeMap.set(node, wrapped);
- return wrapped;
- }
- /*******************************************************************/
- /**
- * Return the wrapped node's bounding box, either the cached one, if it exists,
- * or computed directly if not.
- *
- * @param {boolean} save Whether to cache the bbox or not (used for stretchy elements)
- * @return {BBox} The computed bounding box
- */
- public getBBox(save: boolean = true): BBox {
- if (this.bboxComputed) {
- return this.bbox;
- }
- const bbox = (save ? this.bbox : BBox.zero());
- this.computeBBox(bbox);
- this.bboxComputed = save;
- return bbox;
- }
- /**
- * Return the wrapped node's bounding box that includes borders and padding
- *
- * @param {boolean} save Whether to cache the bbox or not (used for stretchy elements)
- * @return {BBox} The computed bounding box
- */
- public getOuterBBox(save: boolean = true): BBox {
- const bbox = this.getBBox(save);
- if (!this.styles) return bbox;
- const obox = new BBox();
- Object.assign(obox, bbox);
- for (const [name, side] of BBox.StyleAdjust) {
- const x = this.styles.get(name);
- if (x) {
- (obox as any)[side] += this.length2em(x, 1, obox.rscale);
- }
- }
- return obox;
- }
- /**
- * @param {BBox} bbox The bounding box to modify (either this.bbox, or an empty one)
- * @param {boolean} recompute True if we are recomputing due to changes in children that have percentage widths
- */
- protected computeBBox(bbox: BBox, recompute: boolean = false) {
- bbox.empty();
- for (const child of this.childNodes) {
- bbox.append(child.getOuterBBox());
- }
- bbox.clean();
- if (this.fixesPWidth && this.setChildPWidths(recompute)) {
- this.computeBBox(bbox, true);
- }
- }
- /**
- * Recursively resolve any percentage widths in the child nodes using the given
- * container width (or the child width, if none was passed).
- * Overriden for mtables in order to compute the width.
- *
- * @param {boolean} recompute True if we are recomputing due to changes in children
- * @param {(number|null)=} w The width of the container (from which percentages are computed)
- * @param {boolean=} clear True if pwidth marker is to be cleared
- * @return {boolean} True if a percentage width was found
- */
- public setChildPWidths(recompute: boolean, w: (number | null) = null, clear: boolean = true): boolean {
- if (recompute) {
- return false;
- }
- if (clear) {
- this.bbox.pwidth = '';
- }
- let changed = false;
- for (const child of this.childNodes) {
- const cbox = child.getOuterBBox();
- if (cbox.pwidth && child.setChildPWidths(recompute, w === null ? cbox.w : w, clear)) {
- changed = true;
- }
- }
- return changed;
- }
- /**
- * Mark BBox to be computed again (e.g., when an mo has stretched)
- */
- public invalidateBBox() {
- if (this.bboxComputed) {
- this.bboxComputed = false;
- if (this.parent) {
- this.parent.invalidateBBox();
- }
- }
- }
- /**
- * Copy child skew and italic correction
- *
- * @param {BBox} bbox The bounding box to modify
- */
- protected copySkewIC(bbox: BBox) {
- const first = this.childNodes[0];
- if (first?.bbox.sk) {
- bbox.sk = first.bbox.sk;
- }
- if (first?.bbox.dx) {
- bbox.dx = first.bbox.dx;
- }
- const last = this.childNodes[this.childNodes.length - 1];
- if (last?.bbox.ic) {
- bbox.ic = last.bbox.ic;
- bbox.w += bbox.ic;
- }
- }
- /*******************************************************************/
- /**
- * Add the style attribute, but remove any font-related styles
- * (since these are handled separately by the variant)
- */
- protected getStyles() {
- const styleString = this.node.attributes.getExplicit('style') as string;
- if (!styleString) return;
- const style = this.styles = new Styles(styleString);
- for (let i = 0, m = CommonWrapper.removeStyles.length; i < m; i++) {
- const id = CommonWrapper.removeStyles[i];
- if (style.get(id)) {
- if (!this.removedStyles) this.removedStyles = {};
- this.removedStyles[id] = style.get(id);
- style.set(id, '');
- }
- }
- }
- /**
- * Get the mathvariant (or construct one, if needed).
- */
- protected getVariant() {
- if (!this.node.isToken) return;
- const attributes = this.node.attributes;
- let variant = attributes.get('mathvariant') as string;
- if (!attributes.getExplicit('mathvariant')) {
- const values = attributes.getList('fontfamily', 'fontweight', 'fontstyle') as StringMap;
- if (this.removedStyles) {
- const style = this.removedStyles;
- if (style.fontFamily) values.family = style.fontFamily;
- if (style.fontWeight) values.weight = style.fontWeight;
- if (style.fontStyle) values.style = style.fontStyle;
- }
- if (values.fontfamily) values.family = values.fontfamily;
- if (values.fontweight) values.weight = values.fontweight;
- if (values.fontstyle) values.style = values.fontstyle;
- if (values.weight && values.weight.match(/^\d+$/)) {
- values.weight = (parseInt(values.weight) > 600 ? 'bold' : 'normal');
- }
- if (values.family) {
- variant = this.explicitVariant(values.family, values.weight, values.style);
- } else {
- if (this.node.getProperty('variantForm')) variant = '-tex-variant';
- variant = (CommonWrapper.BOLDVARIANTS[values.weight] || {})[variant] || variant;
- variant = (CommonWrapper.ITALICVARIANTS[values.style] || {})[variant] || variant;
- }
- }
- this.variant = variant;
- }
- /**
- * Set the CSS for a token element having an explicit font (rather than regular mathvariant).
- *
- * @param {string} fontFamily The font family to use
- * @param {string} fontWeight The font weight to use
- * @param {string} fontStyle The font style to use
- */
- protected explicitVariant(fontFamily: string, fontWeight: string, fontStyle: string) {
- let style = this.styles;
- if (!style) style = this.styles = new Styles();
- style.set('fontFamily', fontFamily);
- if (fontWeight) style.set('fontWeight', fontWeight);
- if (fontStyle) style.set('fontStyle', fontStyle);
- return '-explicitFont';
- }
- /**
- * Determine the scaling factor to use for this wrapped node, and set the styles for it.
- */
- protected getScale() {
- let scale = 1, parent = this.parent;
- let pscale = (parent ? parent.bbox.scale : 1);
- let attributes = this.node.attributes;
- let scriptlevel = Math.min(attributes.get('scriptlevel') as number, 2);
- let fontsize = attributes.get('fontsize');
- let mathsize = (this.node.isToken || this.node.isKind('mstyle') ?
- attributes.get('mathsize') : attributes.getInherited('mathsize'));
- //
- // If scriptsize is non-zero, set scale based on scriptsizemultiplier
- //
- if (scriptlevel !== 0) {
- scale = Math.pow(attributes.get('scriptsizemultiplier') as number, scriptlevel);
- let scriptminsize = this.length2em(attributes.get('scriptminsize'), .8, 1);
- if (scale < scriptminsize) scale = scriptminsize;
- }
- //
- // If there is style="font-size:...", and not fontsize attribute, use that as fontsize
- //
- if (this.removedStyles && this.removedStyles.fontSize && !fontsize) {
- fontsize = this.removedStyles.fontSize;
- }
- //
- // If there is a fontsize and no mathsize attribute, is that
- //
- if (fontsize && !attributes.getExplicit('mathsize')) {
- mathsize = fontsize;
- }
- //
- // Incorporate the mathsize, if any
- //
- if (mathsize !== '1') {
- scale *= this.length2em(mathsize, 1, 1);
- }
- //
- // Record the scaling factors and set the element's CSS
- //
- this.bbox.scale = scale;
- this.bbox.rscale = scale / pscale;
- }
- /**
- * Sets the spacing based on TeX or MathML algorithm
- */
- protected getSpace() {
- const isTop = this.isTopEmbellished();
- const hasSpacing = this.node.hasSpacingAttributes();
- if (this.jax.options.mathmlSpacing || hasSpacing) {
- isTop && this.getMathMLSpacing();
- } else {
- this.getTeXSpacing(isTop, hasSpacing);
- }
- }
- /**
- * Get the spacing using MathML rules based on the core MO
- */
- protected getMathMLSpacing() {
- const node = this.node.coreMO() as MmlMo;
- //
- // If the mo is not within a multi-node mrow, don't add space
- //
- const child = node.coreParent();
- const parent = child.parent;
- if (!parent || !parent.isKind('mrow') || parent.childNodes.length === 1) return;
- //
- // Get the lspace and rspace
- //
- const attributes = node.attributes;
- const isScript = (attributes.get('scriptlevel') > 0);
- this.bbox.L = (attributes.isSet('lspace') ?
- Math.max(0, this.length2em(attributes.get('lspace'))) :
- MathMLSpace(isScript, node.lspace));
- this.bbox.R = (attributes.isSet('rspace') ?
- Math.max(0, this.length2em(attributes.get('rspace'))) :
- MathMLSpace(isScript, node.rspace));
- //
- // If there are two adjacent <mo>, use enough left space to make it
- // the maximum of the rspace of the first and lspace of the second
- //
- const n = parent.childIndex(child);
- if (n === 0) return;
- const prev = parent.childNodes[n - 1] as AbstractMmlNode;
- if (!prev.isEmbellished) return;
- const bbox = this.jax.nodeMap.get(prev).getBBox();
- if (bbox.R) {
- this.bbox.L = Math.max(0, this.bbox.L - bbox.R);
- }
- }
- /**
- * Get the spacing using the TeX rules
- *
- * @parm {boolean} isTop True when this is a top-level embellished operator
- * @parm {boolean} hasSpacing True when there is an explicit or inherited 'form' attribute
- */
- protected getTeXSpacing(isTop: boolean, hasSpacing: boolean) {
- if (!hasSpacing) {
- const space = this.node.texSpacing();
- if (space) {
- this.bbox.L = this.length2em(space);
- }
- }
- if (isTop || hasSpacing) {
- const attributes = this.node.coreMO().attributes;
- if (attributes.isSet('lspace')) {
- this.bbox.L = Math.max(0, this.length2em(attributes.get('lspace')));
- }
- if (attributes.isSet('rspace')) {
- this.bbox.R = Math.max(0, this.length2em(attributes.get('rspace')));
- }
- }
- }
- /**
- * @return {boolean} True if this is the top-most container of an embellished operator that is
- * itself an embellished operator (the maximal embellished operator for its core)
- */
- protected isTopEmbellished(): boolean {
- return (this.node.isEmbellished &&
- !(this.node.parent && this.node.parent.isEmbellished));
- }
- /*******************************************************************/
- /**
- * @return {CommonWrapper} The wrapper for this node's core node
- */
- public core(): CommonWrapper<J, W, C, CC, DD, FD> {
- return this.jax.nodeMap.get(this.node.core());
- }
- /**
- * @return {CommonWrapper} The wrapper for this node's core <mo> node
- */
- public coreMO(): CommonWrapper<J, W, C, CC, DD, FD> {
- return this.jax.nodeMap.get(this.node.coreMO());
- }
- /**
- * @return {string} For a token node, the combined text content of the node's children
- */
- public getText(): string {
- let text = '';
- if (this.node.isToken) {
- for (const child of this.node.childNodes) {
- if (child instanceof TextNode) {
- text += child.getText();
- }
- }
- }
- return text;
- }
- /**
- * @param {DIRECTION} direction The direction to stretch this node
- * @return {boolean} Whether the node can stretch in that direction
- */
- public canStretch(direction: DIRECTION): boolean {
- this.stretch = NOSTRETCH as DD;
- if (this.node.isEmbellished) {
- let core = this.core();
- if (core && core.node !== this.node) {
- if (core.canStretch(direction)) {
- this.stretch = core.stretch;
- }
- }
- }
- return this.stretch.dir !== DIRECTION.None;
- }
- /**
- * @return {[string, number]} The alignment and indentation shift for the expression
- */
- protected getAlignShift(): [string, number] {
- let {indentalign, indentshift, indentalignfirst, indentshiftfirst} =
- this.node.attributes.getList(...indentAttributes) as StringMap;
- if (indentalignfirst !== 'indentalign') {
- indentalign = indentalignfirst;
- }
- if (indentalign === 'auto') {
- indentalign = this.jax.options.displayAlign;
- }
- if (indentshiftfirst !== 'indentshift') {
- indentshift = indentshiftfirst;
- }
- if (indentshift === 'auto') {
- indentshift = this.jax.options.displayIndent;
- if (indentalign === 'right' && !indentshift.match(/^\s*0[a-z]*\s*$/)) {
- indentshift = ('-' + indentshift.trim()).replace(/^--/, '');
- }
- }
- const shift = this.length2em(indentshift, this.metrics.containerWidth);
- return [indentalign, shift] as [string, number];
- }
- /**
- * @param {number} W The total width
- * @param {BBox} bbox The bbox to be aligned
- * @param {string} align How to align (left, center, right)
- * @return {number} The x position of the aligned width
- */
- protected getAlignX(W: number, bbox: BBox, align: string): number {
- return (align === 'right' ? W - (bbox.w + bbox.R) * bbox.rscale :
- align === 'left' ? bbox.L * bbox.rscale :
- (W - bbox.w * bbox.rscale) / 2);
- }
- /**
- * @param {number} H The total height
- * @param {number} D The total depth
- * @param {number} h The height to be aligned
- * @param {number} d The depth to be aligned
- * @param {string} align How to align (top, bottom, center, axis, baseline)
- * @return {number} The y position of the aligned baseline
- */
- protected getAlignY(H: number, D: number, h: number, d: number, align: string): number {
- return (align === 'top' ? H - h :
- align === 'bottom' ? d - D :
- align === 'center' ? ((H - h) - (D - d)) / 2 :
- 0); // baseline and axis
- }
- /**
- * @param {number} i The index of the child element whose container is needed
- * @return {number} The inner width as a container (for percentage widths)
- */
- public getWrapWidth(i: number): number {
- return this.childNodes[i].getBBox().w;
- }
- /**
- * @param {number} i The index of the child element whose container is needed
- * @return {string} The alignment child element
- */
- public getChildAlign(_i: number): string {
- return 'left';
- }
- /*******************************************************************/
- /*
- * Easy access to some utility routines
- */
- /**
- * @param {number} m A number to be shown as a percent
- * @return {string} The number m as a percent
- */
- protected percent(m: number): string {
- return LENGTHS.percent(m);
- }
- /**
- * @param {number} m A number to be shown in ems
- * @return {string} The number with units of ems
- */
- protected em(m: number): string {
- return LENGTHS.em(m);
- }
- /**
- * @param {number} m A number of em's to be shown as pixels
- * @param {number} M The minimum number of pixels to allow
- * @return {string} The number with units of px
- */
- protected px(m: number, M: number = -LENGTHS.BIGDIMEN): string {
- return LENGTHS.px(m, M, this.metrics.em);
- }
- /**
- * @param {Property} length A dimension (giving number and units) or number to be converted to ems
- * @param {number} size The default size of the dimension (for percentage values)
- * @param {number} scale The current scaling factor (to handle absolute units)
- * @return {number} The dimension converted to ems
- */
- protected length2em(length: Property, size: number = 1, scale: number = null): number {
- if (scale === null) {
- scale = this.bbox.scale;
- }
- return LENGTHS.length2em(length as string, size, scale, this.jax.pxPerEm);
- }
- /**
- * @param {string} text The text to turn into unicode locations
- * @param {string} name The name of the variant for the characters
- * @return {number[]} Array of numbers represeting the string's unicode character positions
- */
- protected unicodeChars(text: string, name: string = this.variant): number[] {
- let chars = unicodeChars(text);
- //
- // Remap to Math Alphanumerics block
- //
- const variant = this.font.getVariant(name);
- if (variant && variant.chars) {
- const map = variant.chars;
- //
- // Is map[n] doesn't exist, (map[n] || []) still gives an CharData array.
- // If the array doesn't have a CharOptions element use {} instead.
- // Then check if the options has an smp property, which gives
- // the Math Alphabet mapping for this character.
- // Otherwise use the original code point, n.
- //
- chars = chars.map((n) => ((map[n] || [])[3] || {}).smp || n);
- }
- return chars;
- }
- /**
- * @param {number[]} chars The array of unicode character numbers to remap
- * @return {number[]} The converted array
- */
- public remapChars(chars: number[]): number[] {
- return chars;
- }
- /**
- * @param {string} text The text from which to create a TextNode object
- * @return {TextNode} The TextNode with the given text
- */
- public mmlText(text: string): TextNode {
- return ((this.node as AbstractMmlNode).factory.create('text') as TextNode).setText(text);
- }
- /**
- * @param {string} kind The kind of MmlNode to create
- * @param {ProperyList} properties The properties to set initially
- * @param {MmlNode[]} children The child nodes to add to the created node
- * @return {MmlNode} The newly created MmlNode
- */
- public mmlNode(kind: string, properties: PropertyList = {}, children: MmlNode[] = []): MmlNode {
- return (this.node as AbstractMmlNode).factory.create(kind, properties, children);
- }
- /**
- * Create an mo wrapper with the given text,
- * link it in, and give it the right defaults.
- *
- * @param {string} text The text for the wrapped element
- * @return {CommonWrapper} The wrapped MmlMo node
- */
- protected createMo(text: string): CommonWrapper<J, W, C, CC, DD, FD> {
- const mmlFactory = (this.node as AbstractMmlNode).factory;
- const textNode = (mmlFactory.create('text') as TextNode).setText(text);
- const mml = mmlFactory.create('mo', {stretchy: true}, [textNode]);
- mml.inheritAttributesFrom(this.node);
- const node = this.wrap(mml);
- node.parent = this as any as W;
- return node;
- }
- /**
- * @param {string} variant The variant in which to look for the character
- * @param {number} n The number of the character to look up
- * @return {CharData} The full CharData object, with CharOptions guaranteed to be defined
- */
- protected getVariantChar(variant: string, n: number): CharData<CC> {
- const char = this.font.getChar(variant, n) || [0, 0, 0, {unknown: true} as CC];
- if (char.length === 3) {
- (char as any)[3] = {};
- }
- return char as [number, number, number, CC];
- }
- }
|