add initial marp implementation with sample content and build configuration

This commit is contained in:
2025-09-13 18:13:22 +02:00
parent dcacc9b409
commit e5f219507f
10319 changed files with 1402023 additions and 0 deletions
+65
View File
@@ -0,0 +1,65 @@
/*************************************************************
*
* Copyright (c) 2018-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 SVGTeXAtom wrapper for the MmlTeXAtom object
*
* @author dpvc@mathjax.org (Davide Cervone)
*/
import {SVGWrapper, SVGConstructor} from '../Wrapper.js';
import {CommonTeXAtomMixin} from '../../common/Wrappers/TeXAtom.js';
import {TeXAtom} from '../../../core/MmlTree/MmlNodes/TeXAtom.js';
import {TEXCLASS, TEXCLASSNAMES} from '../../../core/MmlTree/MmlNode.js';
/*****************************************************************/
/**
* The SVGTeXAtom wrapper for the TeXAtom object
*
* @template N The HTMLElement node class
* @template T The Text node class
* @template D The Document class
*/
// @ts-ignore
export class SVGTeXAtom<N, T, D> extends
CommonTeXAtomMixin<SVGConstructor<any, any, any>>(SVGWrapper) {
/**
* The TeXAtom wrapper
*/
public static kind = TeXAtom.prototype.kind;
/**
* @override
*/
public toSVG(parent: N) {
super.toSVG(parent);
this.adaptor.setAttribute(this.element, 'data-mjx-texclass', TEXCLASSNAMES[this.node.texClass]);
//
// Center VCENTER atoms vertically
//
if (this.node.texClass === TEXCLASS.VCENTER) {
const bbox = this.childNodes[0].getBBox(); // get unmodified bbox of children
const {h, d} = bbox;
const a = this.font.params.axis_height;
const dh = ((h + d) / 2 + a) - h; // new height minus old height
const translate = 'translate(0 ' + this.fixed(dh) + ')';
this.adaptor.setAttribute(this.element, 'transform', translate);
}
}
}
+76
View File
@@ -0,0 +1,76 @@
/*************************************************************
*
* 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 SVGTextNode wrapper for the TextNode object
*
* @author dpvc@mathjax.org (Davide Cervone)
*/
import {TextNode} from '../../../core/MmlTree/MmlNode.js';
import {SVGWrapper, SVGConstructor} from '../Wrapper.js';
import {CommonTextNodeMixin} from '../../common/Wrappers/TextNode.js';
import {StyleList} from '../../../util/StyleList.js';
/*****************************************************************/
/**
* The SVGTextNode wrapper for the TextNode object
*
* @template N The HTMLElement node class
* @template T The Text node class
* @template D The Document class
*/
// @ts-ignore
export class SVGTextNode<N, T, D> extends
CommonTextNodeMixin<SVGConstructor<any, any, any>>(SVGWrapper) {
/**
* The TextNode wrapper
*/
public static kind = TextNode.prototype.kind;
/**
* @override
*/
public static styles: StyleList = {
'mjx-container[jax="SVG"] path[data-c], mjx-container[jax="SVG"] use[data-c]': {
'stroke-width': 3
}
};
/**
* @override
*/
public toSVG(parent: N) {
const text = (this.node as TextNode).getText();
const variant = this.parent.variant;
if (text.length === 0) return;
if (variant === '-explicitFont') {
this.element = this.adaptor.append(parent, this.jax.unknownText(text, variant));
} else {
const chars = this.remappedText(text, variant);
if (this.parent.childNodes.length > 1) {
parent = this.element = this.adaptor.append(parent, this.svg('g', {'data-mml-node': 'text'}));
}
let x = 0;
for (const n of chars) {
x += this.placeChar(n, x, 0, parent, variant);
}
}
}
}
+228
View File
@@ -0,0 +1,228 @@
/*************************************************************
*
* Copyright (c) 2018-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 SVGmaction wrapper for the MmlMaction object
*
* @author dpvc@mathjax.org (Davide Cervone)
*/
import {SVGWrapper, SVGConstructor} from '../Wrapper.js';
import {CommonMactionMixin} from '../../common/Wrappers/maction.js';
import {ActionDef} from '../../common/Wrappers/maction.js';
import {EventHandler, TooltipData} from '../../common/Wrappers/maction.js';
import {MmlMaction} from '../../../core/MmlTree/MmlNodes/maction.js';
import {TextNode, AbstractMmlNode} from '../../../core/MmlTree/MmlNode.js';
import {StyleList} from '../../../util/StyleList.js';
/*****************************************************************/
/**
* The SVGmaction wrapper for the MmlMaction object
*
* @template N The HTMLElement node class
* @template T The Text node class
* @template D The Document class
*/
// @ts-ignore
export class SVGmaction<N, T, D> extends
CommonMactionMixin<SVGWrapper<any, any, any>, SVGConstructor<any, any, any>>(SVGWrapper) {
/**
* The maction wrapper
*/
public static kind = MmlMaction.prototype.kind;
/**
* @override
*/
public static styles: StyleList = {
'[jax="SVG"] mjx-tool': {
display: 'inline-block',
position: 'relative',
width: 0, height: 0
},
'[jax="SVG"] mjx-tool > mjx-tip': {
position: 'absolute',
top: 0, left: 0
},
'mjx-tool > mjx-tip': {
display: 'inline-block',
padding: '.2em',
border: '1px solid #888',
'font-size': '70%',
'background-color': '#F8F8F8',
color: 'black',
'box-shadow': '2px 2px 5px #AAAAAA'
},
'g[data-mml-node="maction"][data-toggle]': {
cursor: 'pointer'
},
'mjx-status': {
display: 'block',
position: 'fixed',
left: '1em',
bottom: '1em',
'min-width': '25%',
padding: '.2em .4em',
border: '1px solid #888',
'font-size': '90%',
'background-color': '#F8F8F8',
color: 'black'
}
};
/**
* The valid action types and their handlers
*/
public static actions = new Map([
['toggle', [(node, _data) => {
//
// Mark which child is selected
//
node.adaptor.setAttribute(node.element, 'data-toggle', node.node.attributes.get('selection') as string);
//
// Cache the data needed to select another node
//
const math = node.factory.jax.math;
const document = node.factory.jax.document;
const mml = node.node as MmlMaction;
//
// Add a click handler that changes the selection and rerenders the expression
//
node.setEventHandler('click', (event: Event) => {
if (!math.end.node) {
//
// If the MathItem was created by hand, it might not have a node
// telling it where to replace the existing math, so set it.
//
math.start.node = math.end.node = math.typesetRoot;
math.start.n = math.end.n = 0;
}
mml.nextToggleSelection();
math.rerender(document);
event.stopPropagation();
});
}, {}]],
['tooltip', [(node, data) => {
const tip = node.childNodes[1];
if (!tip) return;
const rect = node.firstChild();
if (tip.node.isKind('mtext')) {
//
// Text tooltips are handled through title attributes
//
const text = (tip.node as TextNode).getText();
node.adaptor.insert(node.svg('title', {}, [node.text(text)]), rect);
} else {
//
// Math tooltips are handled through hidden nodes and event handlers
//
const adaptor = node.adaptor;
const container = node.jax.container;
const math = (node.node as AbstractMmlNode).factory.create('math', {}, [node.childNodes[1].node]);
const tool = node.html('mjx-tool', {}, [node.html('mjx-tip')]);
const hidden = adaptor.append(rect, node.svg('foreignObject', {style: {display: 'none'}}, [tool]));
node.jax.processMath(math, adaptor.firstChild(tool));
node.childNodes[1].node.parent = node.node;
//
// Set up the event handlers to display and remove the tooltip
//
node.setEventHandler('mouseover', (event: Event) => {
data.stopTimers(node, data);
data.hoverTimer.set(node, setTimeout(() => {
adaptor.setStyle(tool, 'left', '0');
adaptor.setStyle(tool, 'top', '0');
adaptor.append(container, tool);
const tbox = adaptor.nodeBBox(tool);
const nbox = adaptor.nodeBBox(node.element);
const dx = (nbox.right - tbox.left) / node.metrics.em + node.dx;
const dy = (nbox.bottom - tbox.bottom) / node.metrics.em + node.dy;
adaptor.setStyle(tool, 'left', node.px(dx));
adaptor.setStyle(tool, 'top', node.px(dy));
}, data.postDelay));
event.stopPropagation();
});
node.setEventHandler('mouseout', (event: Event) => {
data.stopTimers(node, data);
const timer = setTimeout(() => adaptor.append(hidden, tool), data.clearDelay);
data.clearTimer.set(node, timer);
event.stopPropagation();
});
}
}, TooltipData]],
['statusline', [(node, data) => {
const tip = node.childNodes[1];
if (!tip) return;
if (tip.node.isKind('mtext')) {
const adaptor = node.adaptor;
const text = (tip.node as TextNode).getText();
adaptor.setAttribute(node.element, 'data-statusline', text);
//
// Set up event handlers to change the status window
//
node.setEventHandler('mouseover', (event: Event) => {
if (data.status === null) {
const body = adaptor.body(adaptor.document);
data.status = adaptor.append(body, node.html('mjx-status', {}, [node.text(text)]));
}
event.stopPropagation();
});
node.setEventHandler('mouseout', (event: Event) => {
if (data.status) {
adaptor.remove(data.status);
data.status = null;
}
event.stopPropagation();
});
}
}, {
status: null // cached status line
}]]
] as ActionDef<SVGmaction<any, any, any>>[]);
/*************************************************************/
/**
* @override
*/
public toSVG(parent: N) {
const svg = this.standardSVGnode(parent);
const child = this.selected;
const {h, d, w} = child.getOuterBBox();
this.adaptor.append(this.element, this.svg('rect', {
width: this.fixed(w), height: this.fixed(h + d), y: this.fixed(-d),
fill: 'none', 'pointer-events': 'all'
}));
child.toSVG(svg);
const bbox = child.getOuterBBox();
if (child.element) {
child.place(bbox.L * bbox.rscale, 0);
}
this.action(this, this.data);
}
/**
* Add an event handler to the output for this maction
*/
public setEventHandler(type: string, handler: EventHandler) {
(this.element as any).addEventListener(type, handler);
}
}
+145
View File
@@ -0,0 +1,145 @@
/*************************************************************
*
* Copyright (c) 2018-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 SVGmath wrapper for the MmlMath object
*
* @author dpvc@mathjax.org (Davide Cervone)
*/
import {SVGWrapper, SVGConstructor} from '../Wrapper.js';
import {CommonMathMixin} from '../../common/Wrappers/math.js';
import {MmlMath} from '../../../core/MmlTree/MmlNodes/math.js';
import {StyleList} from '../../../util/StyleList.js';
import {BBox} from '../../../util/BBox.js';
/*****************************************************************/
/**
* The SVGmath wrapper for the MmlMath object
*
* @template N The HTMLElement node class
* @template T The Text node class
* @template D The Document class
*/
// @ts-ignore
export class SVGmath<N, T, D> extends
CommonMathMixin<SVGConstructor<any, any, any>>(SVGWrapper) {
/**
* The math wrapper
*/
public static kind = MmlMath.prototype.kind;
/**
* @overreide
*/
public static styles: StyleList = {
'mjx-container[jax="SVG"][display="true"]': {
display: 'block',
'text-align': 'center',
margin: '1em 0'
},
'mjx-container[jax="SVG"][display="true"][width="full"]': {
display: 'flex'
},
'mjx-container[jax="SVG"][justify="left"]': {
'text-align': 'left'
},
'mjx-container[jax="SVG"][justify="right"]': {
'text-align': 'right'
}
};
/**
* @override
*/
public toSVG(parent: N) {
super.toSVG(parent);
const adaptor = this.adaptor;
const display = (this.node.attributes.get('display') === 'block');
if (display) {
adaptor.setAttribute(this.jax.container, 'display', 'true');
this.handleDisplay();
}
if (this.jax.document.options.internalSpeechTitles) {
this.handleSpeech();
}
}
/**
* Set the justification, and get the minwidth and shift needed
* for the displayed equation.
*/
protected handleDisplay() {
const [align, shift] = this.getAlignShift();
if (align !== 'center') {
this.adaptor.setAttribute(this.jax.container, 'justify', align);
}
if (this.bbox.pwidth === BBox.fullWidth) {
this.adaptor.setAttribute(this.jax.container, 'width', 'full');
if (this.jax.table) {
let {L, w, R} = this.jax.table.getOuterBBox();
if (align === 'right') {
R = Math.max(R || -shift, -shift);
} else if (align === 'left') {
L = Math.max(L || shift, shift);
} else if (align === 'center') {
w += 2 * Math.abs(shift);
}
this.jax.minwidth = Math.max(0, L + w + R);
}
} else {
this.jax.shift = shift;
}
}
/**
* Handle adding speech to the top-level node, if any.
*/
protected handleSpeech() {
const adaptor = this.adaptor;
const attributes = this.node.attributes;
const speech = (attributes.get('aria-label') || attributes.get('data-semantic-speech')) as string;
if (speech) {
const id = this.getTitleID();
const label = this.svg('title', {id}, [this.text(speech)]);
adaptor.insert(label, adaptor.firstChild(this.element));
adaptor.setAttribute(this.element, 'aria-labeledby', id);
adaptor.removeAttribute(this.element, 'aria-label');
for (const child of this.childNodes[0].childNodes) {
adaptor.setAttribute(child.element, 'aria-hidden', 'true');
}
}
}
/**
* @return {string} A unique ID to use for aria-labeledby title elements
*/
protected getTitleID(): string {
return 'mjx-svg-title-' + String(this.jax.options.titleID++);
}
/**
* @override
*/
public setChildPWidths(recompute: boolean, w: number = null, _clear: boolean = true) {
return super.setChildPWidths(recompute,
this.parent ? w : this.metrics.containerWidth / this.jax.pxPerEm,
false);
}
}
+364
View File
@@ -0,0 +1,364 @@
/*************************************************************
*
* Copyright (c) 2018-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 SVGmenclose wrapper for the MmlMenclose object
*
* @author dpvc@mathjax.org (Davide Cervone)
*/
import {SVGWrapper, SVGConstructor} from '../Wrapper.js';
import {CommonMencloseMixin} from '../../common/Wrappers/menclose.js';
import {SVGmsqrt} from './msqrt.js';
import * as Notation from '../Notation.js';
import {MmlMenclose} from '../../../core/MmlTree/MmlNodes/menclose.js';
import {OptionList} from '../../../util/Options.js';
/*****************************************************************/
/**
* The SVGmenclose wrapper for the MmlMenclose object
*
* @template N The HTMLElement node class
* @template T The Text node class
* @template D The Document class
*/
// @ts-ignore
export class SVGmenclose<N, T, D> extends CommonMencloseMixin<
SVGWrapper<any, any, any>,
SVGmsqrt<any, any, any>,
any,
SVGConstructor<any, any, any>
>(SVGWrapper) {
/**
* The menclose wrapper
*/
public static kind = MmlMenclose.prototype.kind;
/**
* The definitions of the various notations
*/
public static notations: Notation.DefList<SVGmenclose<any, any, any>, any> = new Map([
Notation.Border('top'),
Notation.Border('right'),
Notation.Border('bottom'),
Notation.Border('left'),
Notation.Border2('actuarial', 'top', 'right'),
Notation.Border2('madruwb', 'bottom', 'right'),
Notation.DiagonalStrike('up'),
Notation.DiagonalStrike('down'),
['horizontalstrike', {
renderer: Notation.RenderLine('horizontal', 'Y'),
bbox: (node) => [0, node.padding, 0, node.padding]
}],
['verticalstrike', {
renderer: Notation.RenderLine('vertical', 'X'),
bbox: (node) => [node.padding, 0, node.padding, 0]
}],
['box', {
renderer: (node, _child) => {
const {w, h, d} = node.getBBox();
node.adaptor.append(node.element, node.box(w, h, d));
},
bbox: Notation.fullBBox,
border: Notation.fullBorder,
remove: 'left right top bottom'
}],
['roundedbox', {
renderer: (node, _child) => {
const {w, h, d} = node.getBBox();
const r = node.thickness + node.padding;
node.adaptor.append(node.element, node.box(w, h, d, r));
},
bbox: Notation.fullBBox
}],
['circle', {
renderer: (node, _child) => {
const {w, h, d} = node.getBBox();
node.adaptor.append(node.element, node.ellipse(w, h, d));
},
bbox: Notation.fullBBox
}],
['phasorangle', {
//
// Use a bottom border and an upward strike properly angled
//
renderer: (node, _child) => {
const {w, h, d} = node.getBBox();
const a = node.getArgMod(1.75 * node.padding, h + d)[0];
const t = node.thickness / 2;
const HD = h + d;
const cos = Math.cos(a);
node.adaptor.append(
node.element,
node.path('mitre', 'M', w, t - d, 'L', t + cos * t, t - d, 'L' , cos * HD + t, HD - d - t)
);
},
bbox: (node) => {
const p = node.padding / 2;
const t = node.thickness;
return [2 * p, p, p + t, 3 * p + t];
},
border: (node) => [0, 0, node.thickness, 0],
remove: 'bottom'
}],
Notation.Arrow('up'),
Notation.Arrow('down'),
Notation.Arrow('left'),
Notation.Arrow('right'),
Notation.Arrow('updown'),
Notation.Arrow('leftright'),
Notation.DiagonalArrow('updiagonal'), // backward compatibility
Notation.DiagonalArrow('northeast'),
Notation.DiagonalArrow('southeast'),
Notation.DiagonalArrow('northwest'),
Notation.DiagonalArrow('southwest'),
Notation.DiagonalArrow('northeastsouthwest'),
Notation.DiagonalArrow('northwestsoutheast'),
['longdiv', {
//
// Use a line along the top followed by a half ellipse at the left
//
renderer: (node, _child) => {
const {w, h, d} = node.getBBox();
const t = node.thickness / 2;
const p = node.padding;
node.adaptor.append(
node.element,
node.path('round',
'M', t, t - d,
'a', p - t / 2, (h + d) / 2 - 4 * t, 0, '0,1', 0, h + d - 2 * t,
'L', w - t, h - t
)
);
},
bbox: (node) => {
const p = node.padding;
const t = node.thickness;
return [p + t, p, p, 2 * p + t / 2];
}
}],
['radical', {
//
// Use the msqrt rendering, but remove the extra space due to the radical
// (it is added in at the end, so other notations overlap the root)
//
renderer: (node, child) => {
node.msqrt.toSVG(child);
const left = node.sqrtTRBL()[3];
node.place(-left, 0, child);
},
//
// Create the needed msqrt wrapper
//
init: (node) => {
node.msqrt = node.createMsqrt(node.childNodes[0]);
},
//
// Add back in the padding for the square root
//
bbox: (node) => node.sqrtTRBL(),
//
// This notation replaces the child
//
renderChild: true
}]
] as Notation.DefPair<SVGmenclose<any, any, any>, any>[]);
/********************************************************/
/**
* @override
*/
public toSVG(parent: N) {
const svg = this.standardSVGnode(parent);
//
// Create a box at the correct position and add the children
//
const left = this.getBBoxExtenders()[3];
const def: OptionList = {};
if (left > 0) {
def.transform = 'translate(' + this.fixed(left) + ', 0)';
}
const block = this.adaptor.append(svg, this.svg('g', def));
if (this.renderChild) {
this.renderChild(this, block);
} else {
this.childNodes[0].toSVG(block);
}
//
// Render all the notations for this menclose element
//
for (const name of Object.keys(this.notations)) {
const notation = this.notations[name];
!notation.renderChild && notation.renderer(this, svg);
}
}
/********************************************************/
/**
* Create an arrow using SVG elements
*
* @param {number} W The length of the arrow
* @param {number} a The angle for the arrow
* @param {boolean} double True if this is a double-headed arrow
* @param {string} offset 'X' for vertical arrow, 'Y' for horizontal
* @param {number} dist Distance to translate in the offset direction
* @return {N} The newly created arrow
*/
public arrow(W: number, a: number, double: boolean, offset: string = '', dist: number = 0): N {
const {w, h, d} = this.getBBox();
const dw = (W - w) / 2;
const m = (h - d) / 2;
const t = this.thickness;
const t2 = t / 2;
const [x, y, dx] = [t * this.arrowhead.x, t * this.arrowhead.y, t * this.arrowhead.dx];
const arrow =
(double ?
this.fill(
'M', w + dw, m, // point of arrow
'l', -(x + dx), y, 'l', dx, t2 - y, // upper right head
'L', x - dw, m + t2, // upper side of shaft
'l', dx, y - t2, 'l', -(x + dx), - y, // left point
'l', x + dx, -y, 'l', -dx, y - t2, // lower left head
'L', w + dw - x, m - t2, // lower side of shaft
'l', -dx, t2 - y, 'Z' // lower head
) :
this.fill(
'M', w + dw, m, // point of arrow
'l', -(x + dx), y, 'l', dx, t2 - y, // upper head
'L', -dw, m + t2, 'l', 0, -t, // upper side of shaft
'L', w + dw - x, m - t2, // lower side of shaft
'l', -dx, t2 - y, 'Z' // lower head
));
const transform = [];
if (dist) {
transform.push(offset === 'X' ? `translate(${this.fixed(-dist)} 0)` : `translate(0 ${this.fixed(dist)})`);
}
if (a) {
const A = this.jax.fixed(-a * 180 / Math.PI);
transform.push(`rotate(${A} ${this.fixed(w / 2)} ${this.fixed(m)})`);
}
if (transform.length) {
this.adaptor.setAttribute(arrow, 'transform', transform.join(' '));
}
return arrow as N;
}
/********************************************************/
/**
* Create a line element
*
* @param {number[]} pq The coordinates of the endpoints, [x1, y1, x2, y2]
* @return {N} The newly created line element
*/
public line(pq: [number, number, number, number]): N {
const [x1, y1, x2, y2] = pq;
return this.svg('line', {
x1: this.fixed(x1), y1: this.fixed(y1),
x2: this.fixed(x2), y2: this.fixed(y2),
'stroke-width': this.fixed(this.thickness)
});
}
/**
* Create a rectangle element
*
* @param {number} w The width of the rectangle
* @param {number} h The height of the rectangle
* @param {number} d The depth of the rectangle
* @param {number=} r The corner radius for a rounded rectangle
* @return {N} The newly created line element
*/
public box(w: number, h: number, d: number, r: number = 0): N {
const t = this.thickness;
const def: OptionList = {
x: this.fixed(t / 2), y: this.fixed(t / 2 - d),
width: this.fixed(w - t), height: this.fixed(h + d - t),
fill: 'none', 'stroke-width': this.fixed(t)
};
if (r) {
def.rx = this.fixed(r);
}
return this.svg('rect', def);
}
/**
* Create an ellipse element
*
* @param {number} w The width of the ellipse
* @param {number} h The height of the ellipse
* @param {number} d The depth of the ellipse
* @return {N} The newly created ellipse node
*/
public ellipse(w: number, h: number, d: number): N {
const t = this.thickness;
return this.svg('ellipse', {
rx: this.fixed((w - t) / 2), ry: this.fixed((h + d - t) / 2),
cx: this.fixed(w / 2), cy: this.fixed((h - d) / 2),
'fill': 'none', 'stroke-width': this.fixed(t)
});
}
/**
* Create a path element from the commands that specify it
*
* @param {string} join The join style for the path
* @param {(string|number)[]} P The list of commands and coordinates for the path
* @return {N} The newly created path
*/
public path(join: string, ...P: (string | number)[]): N {
return this.svg('path', {
d: P.map(x => (typeof x === 'string' ? x : this.fixed(x))).join(' '),
style: {'stroke-width': this.fixed(this.thickness)},
'stroke-linecap': 'round', 'stroke-linejoin': join,
fill: 'none'
});
}
/**
* Create a filled path element from the commands the specify it
* (same as path above, but no thickness adjustments)
*
* @param {(string|number)[]} P The list of commands and coordinates for the path
* @return {N} The newly created path
*/
public fill(...P: (string | number)[]): N {
return this.svg('path', {
d: P.map(x => (typeof x === 'string' ? x : this.fixed(x))).join(' ')
});
}
}
+74
View File
@@ -0,0 +1,74 @@
/*************************************************************
*
* Copyright (c) 2018-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 SVGmerror wrapper for the MmlMerror object
*
* @author dpvc@mathjax.org (Davide Cervone)
*/
import {SVGWrapper} from '../Wrapper.js';
import {MmlMerror} from '../../../core/MmlTree/MmlNodes/merror.js';
import {StyleList} from '../../../util/StyleList.js';
/*****************************************************************/
/**
* The SVGmerror wrapper for the MmlMerror object
*
* @template N The HTMLElement node class
* @template T The Text node class
* @template D The Document class
*/
export class SVGmerror<N, T, D> extends SVGWrapper<N, T, D> {
/**
* The merror wrapper
*/
public static kind = MmlMerror.prototype.kind;
/**
* @override
*/
public static styles: StyleList = {
'g[data-mml-node="merror"] > g': {
fill: 'red',
stroke: 'red'
},
'g[data-mml-node="merror"] > rect[data-background]': {
fill: 'yellow',
stroke: 'none'
}
};
/**
* @override
*/
public toSVG(parent: N) {
const svg = this.standardSVGnode(parent);
const {h, d, w} = this.getBBox();
this.adaptor.append(this.element, this.svg('rect', {
'data-background': true,
width: this.fixed(w), height: this.fixed(h + d), y: this.fixed(-d)
}));
const title = this.node.attributes.get('title') as string;
if (title) {
this.adaptor.append(this.element, this.svg('title', {}, [this.adaptor.text(title)]));
}
this.addChildren(svg);
}
}
+68
View File
@@ -0,0 +1,68 @@
/*************************************************************
*
* Copyright (c) 2018-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 SVGmfenced wrapper for the MmlMfenced object
*
* @author dpvc@mathjax.org (Davide Cervone)
*/
import {SVGWrapper, SVGConstructor} from '../Wrapper.js';
import {CommonMfencedMixin} from '../../common/Wrappers/mfenced.js';
import {MmlMfenced} from '../../../core/MmlTree/MmlNodes/mfenced.js';
import {SVGinferredMrow} from './mrow.js';
/*****************************************************************/
/**
* The SVGmfenced wrapper for the MmlMfenced object
*
* @template N The HTMLElement node class
* @template T The Text node class
* @template D The Document class
*/
export class SVGmfenced<N, T, D> extends CommonMfencedMixin<SVGConstructor<any, any, any>>(SVGWrapper) {
/**
* The mfenced wrapper
*/
public static kind = MmlMfenced.prototype.kind;
/**
* An mrow used to render the result
*/
public mrow: SVGinferredMrow<N, T, D>;
/**
* @override
*/
public toSVG(parent: N) {
const svg = this.standardSVGnode(parent);
this.setChildrenParent(this.mrow); // temporarily change parents to the mrow
this.mrow.toSVG(svg);
this.setChildrenParent(this); // put back the correct parents
}
/**
* @param {SVGWrapper} parent The parent to use for the fenced children
*/
protected setChildrenParent(parent: SVGWrapper<N, T, D>) {
for (const child of this.childNodes) {
child.parent = parent;
}
}
}
+150
View File
@@ -0,0 +1,150 @@
/*************************************************************
*
* Copyright (c) 2018-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 SVGmfrac wrapper for the MmlMfrac object
*
* @author dpvc@mathjax.org (Davide Cervone)
*/
import {SVGWrapper, SVGConstructor} from '../Wrapper.js';
import {CommonMfracMixin} from '../../common/Wrappers/mfrac.js';
import {MmlMfrac} from '../../../core/MmlTree/MmlNodes/mfrac.js';
import {SVGmo} from './mo.js';
/*****************************************************************/
/**
* The SVGmfrac wrapper for the MmlMfrac object
*
* @template N The HTMLElement node class
* @template T The Text node class
* @template D The Document class
*/
export class SVGmfrac<N, T, D> extends CommonMfracMixin<SVGConstructor<any, any, any>>(SVGWrapper) {
/**
* The mfrac wrapper
*/
public static kind = MmlMfrac.prototype.kind;
/**
* An mo element used to render bevelled fractions
*/
public bevel: SVGmo<N, T, D>;
/************************************************/
/**
* @override
*/
public toSVG(parent: N) {
this.standardSVGnode(parent);
const {linethickness, bevelled} = this.node.attributes.getList('linethickness', 'bevelled');
const display = this.isDisplay();
if (bevelled) {
this.makeBevelled(display);
} else {
const thickness = this.length2em(String(linethickness), .06);
if (thickness === 0) {
this.makeAtop(display);
} else {
this.makeFraction(display, thickness);
}
}
}
/************************************************/
/**
* @param {boolean} display True when fraction is in display mode
* @param {number} t The rule line thickness
*/
protected makeFraction(display: boolean, t: number) {
const svg = this.element;
const {numalign, denomalign} = this.node.attributes.getList('numalign', 'denomalign');
const [num, den] = this.childNodes;
const nbox = num.getOuterBBox();
const dbox = den.getOuterBBox();
const tex = this.font.params;
const a = tex.axis_height;
const d = .1; // line's extra left- and right-padding
const pad = (this.node.getProperty('withDelims') ? 0 : tex.nulldelimiterspace);
const W = Math.max((nbox.L + nbox.w + nbox.R) * nbox.rscale,
(dbox.L + dbox.w + dbox.R) * dbox.rscale);
const nx = this.getAlignX(W, nbox, numalign as string) + d + pad;
const dx = this.getAlignX(W, dbox, denomalign as string) + d + pad;
const {T, u, v} = this.getTUV(display, t);
num.toSVG(svg);
num.place(nx, a + T + Math.max(nbox.d * nbox.rscale, u));
den.toSVG(svg);
den.place(dx, a - T - Math.max(dbox.h * dbox.rscale, v));
this.adaptor.append(svg, this.svg('rect', {
width: this.fixed(W + 2 * d), height: this.fixed(t),
x: this.fixed(pad), y: this.fixed(a - t / 2)
}));
}
/************************************************/
/**
* @param {boolean} display True when fraction is in display mode
*/
protected makeAtop(display: boolean) {
const svg = this.element;
const {numalign, denomalign} = this.node.attributes.getList('numalign', 'denomalign');
const [num, den] = this.childNodes;
const nbox = num.getOuterBBox();
const dbox = den.getOuterBBox();
const tex = this.font.params;
const pad = (this.node.getProperty('withDelims') ? 0 : tex.nulldelimiterspace);
const W = Math.max((nbox.L + nbox.w + nbox.R) * nbox.rscale,
(dbox.L + dbox.w + dbox.R) * dbox.rscale);
const nx = this.getAlignX(W, nbox, numalign as string) + pad;
const dx = this.getAlignX(W, dbox, denomalign as string) + pad;
const {u, v} = this.getUVQ(display);
num.toSVG(svg);
num.place(nx, u);
den.toSVG(svg);
den.place(dx, -v);
}
/************************************************/
/**
* @param {boolean} display True when fraction is in display mode
*/
protected makeBevelled(display: boolean) {
const svg = this.element;
const [num, den] = this.childNodes;
const {u, v, delta, nbox, dbox} = this.getBevelData(display);
const w = (nbox.L + nbox.w + nbox.R) * nbox.rscale;
num.toSVG(svg);
this.bevel.toSVG(svg);
den.toSVG(svg);
num.place(nbox.L * nbox.rscale, u);
this.bevel.place(w - delta / 2, 0);
den.place(w + this.bevel.getOuterBBox().w + dbox.L * dbox.rscale - delta, v);
}
}
+71
View File
@@ -0,0 +1,71 @@
/*************************************************************
*
* Copyright (c) 2018-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 SVGmglyph wrapper for the MmlMglyph object
*
* @author dpvc@mathjax.org (Davide Cervone)
*/
import {SVGWrapper, SVGConstructor} from '../Wrapper.js';
import {CommonMglyphMixin} from '../../common/Wrappers/mglyph.js';
import {MmlMglyph} from '../../../core/MmlTree/MmlNodes/mglyph.js';
import {SVGTextNode} from './TextNode.js';
import {OptionList} from '../../../util/Options.js';
/*****************************************************************/
/**
* The SVGmglyph wrapper for the MmlMglyph object
*
* @template N The HTMLElement node class
* @template T The Text node class
* @template D The Document class
*/
// @ts-ignore
export class SVGmglyph<N, T, D> extends
CommonMglyphMixin<SVGConstructor<any, any, any>>(SVGWrapper) {
/**
* The mglyph wrapper
*/
public static kind = MmlMglyph.prototype.kind;
/**
* @override
*/
public toSVG(parent: N) {
const svg = this.standardSVGnode(parent);
if (this.charWrapper) {
(this.charWrapper as SVGTextNode<N, T, D>).toSVG(svg);
return;
}
const {src, alt} = this.node.attributes.getList('src', 'alt');
const h = this.fixed(this.height);
const w = this.fixed(this.width);
const y = this.fixed(this.height + (this.valign || 0));
const properties: OptionList = {
width: w, height: h,
transform: 'translate(0 ' + y + ') matrix(1 0 0 -1 0 0)',
preserveAspectRatio: 'none',
'aria-label': alt,
href: src
};
const img = this.svg('image', properties);
this.adaptor.append(svg, img);
}
}
+45
View File
@@ -0,0 +1,45 @@
/*************************************************************
*
* Copyright (c) 2018-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 SVGmi wrapper for the MmlMi object
*
* @author dpvc@mathjax.org (Davide Cervone)
*/
import {SVGWrapper, SVGConstructor} from '../Wrapper.js';
import {CommonMiMixin} from '../../common/Wrappers/mi.js';
import {MmlMi} from '../../../core/MmlTree/MmlNodes/mi.js';
/*****************************************************************/
/**
* The SVGmi wrapper for the MmlMi object
*
* @template N The HTMLElement node class
* @template T The Text node class
* @template D The Document class
*/
// @ts-ignore
export class SVGmi<N, T, D> extends
CommonMiMixin<SVGConstructor<any, any, any>>(SVGWrapper) {
/**
* The mi wrapper
*/
public static kind = MmlMi.prototype.kind;
}
+133
View File
@@ -0,0 +1,133 @@
/*************************************************************
*
* Copyright (c) 2018-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 SVGmmultiscripts wrapper for the MmlMmultiscripts object
*
* @author dpvc@mathjax.org (Davide Cervone)
*/
import {SVGWrapper, Constructor} from '../Wrapper.js';
import {SVGmsubsup} from './msubsup.js';
import {CommonMmultiscriptsMixin} from '../../common/Wrappers/mmultiscripts.js';
import {MmlMmultiscripts} from '../../../core/MmlTree/MmlNodes/mmultiscripts.js';
import {split} from '../../../util/string.js';
/*****************************************************************/
/**
* A function taking two widths and returning an offset of the first in the second
*/
export type AlignFunction = (w: number, W: number) => number;
/**
* Get the function for aligning scripts horizontally (left, center, right)
*/
export function AlignX(align: string) {
return ({
left: (_w, _W) => 0,
center: (w, W) => (W - w) / 2,
right: (w, W) => W - w
} as {[name: string]: AlignFunction})[align] || ((_w, _W) => 0) as AlignFunction;
}
/*****************************************************************/
/**
* The SVGmmultiscripts wrapper for the MmlMmultiscripts object
*
* @template N The HTMLElement node class
* @template T The Text node class
* @template D The Document class
*/
// @ts-ignore
export class SVGmmultiscripts<N, T, D> extends
CommonMmultiscriptsMixin<SVGWrapper<any, any, any>, Constructor<SVGmsubsup<any, any, any>>>(SVGmsubsup) {
/**
* The mmultiscripts wrapper
*/
public static kind = MmlMmultiscripts.prototype.kind;
/**
* @override
*/
public toSVG(parent: N) {
const svg = this.standardSVGnode(parent);
const data = this.scriptData;
//
// Get the alignment for the scripts
//
const scriptalign = this.node.getProperty('scriptalign') || 'right left';
const [preAlign, postAlign] = split(scriptalign + ' ' + scriptalign);
//
// Combine the bounding boxes of the pre- and post-scripts,
// and get the resulting baseline offsets
//
const sub = this.combinePrePost(data.sub, data.psub);
const sup = this.combinePrePost(data.sup, data.psup);
const [u, v] = this.getUVQ(sub, sup);
//
// Place the pre-scripts, then the base, then the post-scripts
//
let x = 0; // scriptspace
if (data.numPrescripts) {
x = this.addScripts(.05, u, v, this.firstPrescript, data.numPrescripts, preAlign);
}
const base = this.baseChild;
base.toSVG(svg);
base.place(x, 0);
x += base.getOuterBBox().w;
if (data.numScripts) {
this.addScripts(x, u, v, 1, data.numScripts, postAlign);
}
}
/**
* Create a table with the super and subscripts properly separated and aligned.
*
* @param {number} x The x offset of the scripts
* @param {number} u The baseline offset for the superscripts
* @param {number} v The baseline offset for the subscripts
* @param {number} i The starting index for the scripts
* @param {number} n The number of sub/super-scripts
* @param {string} align The alignment for the scripts
* @return {number} The right-hand offset of the scripts
*/
protected addScripts(x: number, u: number, v: number, i: number, n: number, align: string): number {
const adaptor = this.adaptor;
const alignX = AlignX(align);
const supRow = adaptor.append(this.element, this.svg('g'));
const subRow = adaptor.append(this.element, this.svg('g'));
this.place(x, u, supRow);
this.place(x, v, subRow);
let m = i + 2 * n;
let dx = 0;
while (i < m) {
const [sub, sup] = [this.childNodes[i++], this.childNodes[i++]];
const [subbox, supbox] = [sub.getOuterBBox(), sup.getOuterBBox()];
const [subr, supr] = [subbox.rscale, supbox.rscale];
const w = Math.max(subbox.w * subr, supbox.w * supr);
sub.toSVG(subRow);
sup.toSVG(supRow);
sub.place(dx + alignX(subbox.w * subr, w), 0);
sup.place(dx + alignX(supbox.w * supr, w), 0);
dx += w;
}
return x + dx;
}
}
+45
View File
@@ -0,0 +1,45 @@
/*************************************************************
*
* Copyright (c) 2018-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 SVGmn wrapper for the MmlMn object
*
* @author dpvc@mathjax.org (Davide Cervone)
*/
import {SVGWrapper, SVGConstructor} from '../Wrapper.js';
import {CommonMnMixin} from '../../common/Wrappers/mn.js';
import {MmlMn} from '../../../core/MmlTree/MmlNodes/mn.js';
/*****************************************************************/
/**
* The SVGmn wrapper for the MmlMn object
*
* @template N The HTMLElement node class
* @template T The Text node class
* @template D The Document class
*/
// @ts-ignore
export class SVGmn<N, T, D> extends
CommonMnMixin<SVGConstructor<any, any, any>>(SVGWrapper) {
/**
* The mn wrapper
*/
public static kind = MmlMn.prototype.kind;
}
+306
View File
@@ -0,0 +1,306 @@
/*************************************************************
*
* Copyright (c) 2018-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 SVGmo wrapper for the MmlMo object
*
* @author dpvc@mathjax.org (Davide Cervone)
*/
import {SVGWrapper, SVGConstructor} from '../Wrapper.js';
import {CommonMoMixin} from '../../common/Wrappers/mo.js';
import {MmlMo} from '../../../core/MmlTree/MmlNodes/mo.js';
import {BBox} from '../../../util/BBox.js';
import {DIRECTION, SVGCharData, SVGCharOptions} from '../FontData.js';
/*****************************************************************/
const VFUZZ = 0.1; // overlap for vertical stretchy glyphs
const HFUZZ = 0.1; // overlap for horizontal stretchy glyphs
/*****************************************************************/
/**
* The SVGmo wrapper for the MmlMo object
*
* @template N The HTMLElement node class
* @template T The Text node class
* @template D The Document class
*/
// @ts-ignore
export class SVGmo<N, T, D> extends
CommonMoMixin<SVGConstructor<any, any, any>>(SVGWrapper) {
/**
* The mo wrapper
*/
public static kind = MmlMo.prototype.kind;
/**
* @override
*/
public toSVG(parent: N) {
const attributes = this.node.attributes;
const symmetric = (attributes.get('symmetric') as boolean) && this.stretch.dir !== DIRECTION.Horizontal;
const stretchy = this.stretch.dir !== DIRECTION.None;
if (stretchy && this.size === null) {
this.getStretchedVariant([]);
}
let svg = this.standardSVGnode(parent);
if (stretchy && this.size < 0) {
this.stretchSVG();
} else {
const u = (symmetric || attributes.get('largeop') ? this.fixed(this.getCenterOffset()) : '0');
const v = (this.node.getProperty('mathaccent') ? this.fixed(this.getAccentOffset()) : '0');
if (u !== '0' || v !== '0') {
this.adaptor.setAttribute(svg, 'transform', `translate(${v} ${u})`);
}
this.addChildren(svg);
}
}
/**
* Create the SVG for a multi-character stretchy delimiter
*/
protected stretchSVG() {
const stretch = this.stretch.stretch;
const variants = this.getStretchVariants();
const bbox = this.getBBox();
if (this.stretch.dir === DIRECTION.Vertical) {
this.stretchVertical(stretch, variants, bbox);
} else {
this.stretchHorizontal(stretch, variants, bbox);
}
}
/**
* Get the variant array for the assembly pieces
*/
protected getStretchVariants() {
const c = this.stretch.c || this.getText().codePointAt(0);
const variants = [] as string[];
for (const i of this.stretch.stretch.keys()) {
variants[i] = this.font.getStretchVariant(c, i);
}
return variants;
}
/**
* @param {number[]} stretch The characters to use for stretching
* @param {string[]} variant The variants for the parts to use for stretching
* @param {BBox} bbox The full size of the stretched character
*/
protected stretchVertical(stretch: number[], variant: string[], bbox: BBox) {
const {h, d, w} = bbox;
const T = this.addTop(stretch[0], variant[0], h, w);
const B = this.addBot(stretch[2], variant[2], d, w);
if (stretch.length === 4) {
const [H, D] = this.addMidV(stretch[3], variant[3], w);
this.addExtV(stretch[1], variant[1], h, 0, T, H, w);
this.addExtV(stretch[1], variant[1], 0, d, D, B, w);
} else {
this.addExtV(stretch[1], variant[1], h, d, T, B, w);
}
}
/**
* @param {number[]} stretch The characters to use for stretching
* @param {string[]} variant The variants for the parts to use for stretching
* @param {BBox} bbox The full size of the stretched character
*/
protected stretchHorizontal(stretch: number[], variant: string[], bbox: BBox) {
const w = bbox.w;
const L = this.addLeft(stretch[0], variant[0]);
const R = this.addRight(stretch[2], variant[2], w);
if (stretch.length === 4) {
const [x1, x2] = this.addMidH(stretch[3], variant[3], w);
const w2 = w / 2;
this.addExtH(stretch[1], variant[1], w2, L, w2 - x1);
this.addExtH(stretch[1], variant[1], w2, x2 - w2, R, w2);
} else {
this.addExtH(stretch[1], variant[1], w, L, R);
}
}
/***********************************************************/
/**
* @param {number} n The number of the character to look up
* @param {string} variant The variant for the character to look up
* @return {SVGCharData} The full CharData object, with CharOptions guaranteed to be defined
*/
protected getChar(n: number, variant: string): SVGCharData {
const char = this.font.getChar(variant, n) || [0, 0, 0, null];
return [char[0], char[1], char[2], char[3] || {}] as [number, number, number, SVGCharOptions];
}
/**
* @param {number} n The character code for the glyph
* @param {string} variant The variant for the glyph
* @param {number} x The x position of the glyph
* @param {number} y The y position of the glyph
* @param {N} parent The container for the glyph
* @return {number} The width of the character placed
*/
protected addGlyph(n: number, variant: string, x: number, y: number, parent: N = null): number {
return this.placeChar(n, x, y, parent || this.element, variant);
}
/***********************************************************/
/**
* @param {number} n The character number for the top glyph
* @param {string} v The variant for the top glyph
* @param {number} H The height of the stretched delimiter
* @param {number} W The width of the stretched delimiter
* @return {number} The total height of the top glyph
*/
protected addTop(n: number, v: string, H: number, W: number): number {
if (!n) return 0;
const [h, d, w] = this.getChar(n, v);
this.addGlyph(n, v, (W - w) / 2, H - h);
return h + d;
}
/**
* @param {number} n The character number for the extender glyph
* @param {string} v The variant for the extender glyph
* @param {number} H The height of the stretched delimiter
* @param {number} D The depth of the stretched delimiter
* @param {number} T The height of the top glyph in the delimiter
* @param {number} B The height of the bottom glyph in the delimiter
* @param {number} W The width of the stretched delimiter
*/
protected addExtV(n: number, v: string, H: number, D: number, T: number, B: number, W: number) {
if (!n) return;
T = Math.max(0, T - VFUZZ); // A little overlap on top
B = Math.max(0, B - VFUZZ); // A little overlap on bottom
const adaptor = this.adaptor;
const [h, d, w] = this.getChar(n, v);
const Y = H + D - T - B; // The height of the extender
const s = 1.5 * Y / (h + d); // Scale height by 1.5 to avoid bad ends
// (glyphs with rounded or anti-aliased ends don't stretch well,
// so this makes for sharper ends)
const y = (s * (h - d) - Y) / 2; // The bottom point to clip the extender
if (Y <= 0) return;
const svg = this.svg('svg', {
width: this.fixed(w), height: this.fixed(Y),
y: this.fixed(B - D), x: this.fixed((W - w) / 2),
viewBox: [0, y, w, Y].map(x => this.fixed(x)).join(' ')
});
this.addGlyph(n, v, 0, 0, svg);
const glyph = adaptor.lastChild(svg);
adaptor.setAttribute(glyph, 'transform', `scale(1,${this.jax.fixed(s)})`);
adaptor.append(this.element, svg);
}
/**
* @param {number} n The character number for the bottom glyph
* @param {string} v The variant for the bottom glyph
* @param {number} D The depth of the stretched delimiter
* @param {number} W The width of the stretched delimiter
* @return {number} The total height of the bottom glyph
*/
protected addBot(n: number, v: string, D: number, W: number): number {
if (!n) return 0;
const [h, d, w] = this.getChar(n, v);
this.addGlyph(n, v, (W - w) / 2, d - D);
return h + d;
}
/**
* @param {number} n The character number for the middle glyph
* @param {string} v The variant for the middle glyph
* @param {number} W The width of the stretched delimiter
* @return {[number, number]} The top and bottom positions of the middle glyph
*/
protected addMidV(n: number, v: string, W: number): [number, number] {
if (!n) return [0, 0];
const [h, d, w] = this.getChar(n, v);
const y = (d - h) / 2 + this.font.params.axis_height;
this.addGlyph(n, v, (W - w) / 2, y);
return [h + y, d - y];
}
/***********************************************************/
/**
* @param {number} n The character number for the left glyph of the stretchy character
* @param {string} v The variant for the left glyph
* @return {number} The width of the left glyph
*/
protected addLeft(n: number, v: string): number {
return (n ? this.addGlyph(n, v, 0, 0) : 0);
}
/**
* @param {number} n The character number for the extender glyph of the stretchy character
* @param {string} v The variant for the extender glyph
* @param {number} W The width of the stretched character
* @param {number} L The width of the left glyph of the stretchy character
* @param {number} R The width of the right glyph of the stretchy character
* @param {number} x The x-position of the extender (needed for ones with two extenders)
*/
protected addExtH(n: number, v: string, W: number, L: number, R: number, x: number = 0) {
if (!n) return;
R = Math.max(0, R - HFUZZ); // A little less than the width of the right glyph
L = Math.max(0, L - HFUZZ); // A little less than the width of the left glyph
const adaptor = this.adaptor;
const [h, d, w] = this.getChar(n, v);
const X = W - L - R; // The width of the extender
const Y = h + d + 2 * VFUZZ; // The height (plus some fuzz) of the extender
const s = 1.5 * (X / w); // Scale the width so that left- and right-bearing won't hurt us
const D = -(d + VFUZZ); // The bottom position of the glyph
if (X <= 0) return;
const svg = this.svg('svg', {
width: this.fixed(X), height: this.fixed(Y),
x: this.fixed(x + L), y: this.fixed(D),
viewBox: [(s * w - X) / 2, D, X, Y].map(x => this.fixed(x)).join(' ')
});
this.addGlyph(n, v, 0, 0, svg);
const glyph = adaptor.lastChild(svg);
adaptor.setAttribute(glyph, 'transform', 'scale(' + this.jax.fixed(s) + ',1)');
adaptor.append(this.element, svg);
}
/**
* @param {number} n The character number for the right glyph of the stretchy character
* @param {string} v The variant for the right glyph
* @param {number} W The width of the stretched character
* @return {number} The width of the right glyph
*/
protected addRight(n: number, v: string, W: number): number {
if (!n) return 0;
const w = this.getChar(n, v)[2];
return this.addGlyph(n, v, W - w, 0);
}
/**
* @param {number} n The character number for the middle glyph of the stretchy character
* @param {string} v The variant for the middle glyph
* @param {number} W The width of the stretched character
* @return {[number, number]} The positions of the left and right edges of the middle glyph
*/
protected addMidH(n: number, v: string, W: number): [number, number] {
if (!n) return [0, 0];
const w = this.getChar(n, v)[2];
this.addGlyph(n, v, (W - w) / 2, 0);
return [(W - w) / 2, (W + w) / 2];
}
}
+64
View File
@@ -0,0 +1,64 @@
/*************************************************************
*
* Copyright (c) 2018-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 SVGmpadded wrapper for the MmlMpadded object
*
* @author dpvc@mathjax.org (Davide Cervone)
*/
import {SVGWrapper, SVGConstructor} from '../Wrapper.js';
import {CommonMpaddedMixin} from '../../common/Wrappers/mpadded.js';
import {MmlMpadded} from '../../../core/MmlTree/MmlNodes/mpadded.js';
/*****************************************************************/
/**
* The SVGmpadded wrapper for the MmlMpadded object
*
* @template N The HTMLElement node class
* @template T The Text node class
* @template D The Document class
*/
// @ts-ignore
export class SVGmpadded<N, T, D> extends
CommonMpaddedMixin<SVGConstructor<any, any, any>>(SVGWrapper) {
/**
* The mpadded wrapper
*/
public static kind = MmlMpadded.prototype.kind;
/**
* @override
*/
public toSVG(parent: N) {
let svg = this.standardSVGnode(parent);
const [ , , , , , dw, x, y, dx] = this.getDimens();
const align = (this.node.attributes.get('data-align') as string) || 'left';
const X = x + dx - (dw < 0 && align !== 'left' ? align === 'center' ? dw / 2 : dw : 0);
//
// If there is a horizontal or vertical shift,
// use relative positioning to move the contents
//
if (X || y) {
svg = this.adaptor.append(svg, this.svg('g'));
this.place(X, y, svg);
}
this.addChildren(svg);
}
}
+49
View File
@@ -0,0 +1,49 @@
/*************************************************************
*
* Copyright (c) 2018-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 SVGmphantom wrapper for the MmlMphantom object
*
* @author dpvc@mathjax.org (Davide Cervone)
*/
import {SVGWrapper} from '../Wrapper.js';
import {MmlMphantom} from '../../../core/MmlTree/MmlNodes/mphantom.js';
/*****************************************************************/
/**
* The SVGmi wrapper for the MmlMi object
*
* @template N The HTMLElement node class
* @template T The Text node class
* @template D The Document class
*/
export class SVGmphantom<N, T, D> extends SVGWrapper<N, T, D> {
/**
* The mphantom wrapper
*/
public static kind = MmlMphantom.prototype.kind;
/**
* @override
*/
public toSVG(parent: N) {
this.standardSVGnode(parent);
}
}
+56
View File
@@ -0,0 +1,56 @@
/*************************************************************
*
* Copyright (c) 2018-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 SVGMroot wrapper for the MmlMroot object
*
* @author dpvc@mathjax.org (Davide Cervone)
*/
import {SVGWrapper, Constructor} from '../Wrapper.js';
import {SVGmsqrt} from './msqrt.js';
import {CommonMrootMixin} from '../../common/Wrappers/mroot.js';
import {BBox} from '../../../util/BBox.js';
import {MmlMroot} from '../../../core/MmlTree/MmlNodes/mroot.js';
/*****************************************************************/
/**
* The SVGmroot wrapper for the MmlMroot object (extends SVGmsqrt)
*
* @template N The HTMLElement node class
* @template T The Text node class
* @template D The Document class
*/
export class SVGmroot<N, T, D> extends CommonMrootMixin<Constructor<SVGmsqrt<any, any, any>>>(SVGmsqrt) {
/**
* The mroot wrapper
*/
public static kind = MmlMroot.prototype.kind;
/**
* @override
*/
protected addRoot(ROOT: N, root: SVGWrapper<N, T, D>, sbox: BBox, H: number) {
root.toSVG(ROOT);
const [x, h, dx] = this.getRootDimens(sbox, H);
const bbox = root.getOuterBBox();
root.place(dx * bbox.rscale, h);
this.dx = x;
}
}
+74
View File
@@ -0,0 +1,74 @@
/*************************************************************
*
* Copyright (c) 2018-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 SVGmrow wrapper for the MmlMrow object
*
* @author dpvc@mathjax.org (Davide Cervone)
*/
import {SVGWrapper, SVGConstructor, Constructor} from '../Wrapper.js';
import {CommonMrowMixin} from '../../common/Wrappers/mrow.js';
import {CommonInferredMrowMixin} from '../../common/Wrappers/mrow.js';
import {MmlMrow, MmlInferredMrow} from '../../../core/MmlTree/MmlNodes/mrow.js';
/*****************************************************************/
/**
* The SVGmrow wrapper for the MmlMrow object
*
* @template N The HTMLElement node class
* @template T The Text node class
* @template D The Document class
*/
// @ts-ignore
export class SVGmrow<N, T, D> extends
CommonMrowMixin<SVGConstructor<any, any, any>>(SVGWrapper) {
/**
* The mrow wrapper
*/
public static kind = MmlMrow.prototype.kind;
/**
* @override
*/
public toSVG(parent: N) {
const svg = (this.node.isInferred ? (this.element = parent) : this.standardSVGnode(parent));
this.addChildren(svg);
// FIXME: handle line breaks
}
}
/*****************************************************************/
/**
* The SVGinferredMrow wrapper for the MmlInferredMrow object
*
* @template N The HTMLElement node class
* @template T The Text node class
* @template D The Document class
*/
// @ts-ignore
export class SVGinferredMrow<N, T, D> extends
CommonInferredMrowMixin<Constructor<SVGmrow<any, any, any>>>(SVGmrow) {
/**
* The inferred-mrow wrapper
*/
public static kind = MmlInferredMrow.prototype.kind;
}
+45
View File
@@ -0,0 +1,45 @@
/*************************************************************
*
* Copyright (c) 2018-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 SVGms wrapper for the MmlMs object
*
* @author dpvc@mathjax.org (Davide Cervone)
*/
import {SVGWrapper, SVGConstructor} from '../Wrapper.js';
import {CommonMsMixin} from '../../common/Wrappers/ms.js';
import {MmlMs} from '../../../core/MmlTree/MmlNodes/ms.js';
/*****************************************************************/
/**
* The SVGms wrapper for the MmlMs object
*
* @template N The HTMLElement node class
* @template T The Text node class
* @template D The Document class
*/
// @ts-ignore
export class SVGms<N, T, D> extends
CommonMsMixin<SVGConstructor<any, any, any>>(SVGWrapper) {
/**
* The ms wrapper
*/
public static kind = MmlMs.prototype.kind;
}
+45
View File
@@ -0,0 +1,45 @@
/*************************************************************
*
* Copyright (c) 2018-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 SVGmspace wrapper for the MmlMspace object
*
* @author dpvc@mathjax.org (Davide Cervone)
*/
import {SVGWrapper, SVGConstructor} from '../Wrapper.js';
import {CommonMspaceMixin} from '../../common/Wrappers/mspace.js';
import {MmlMspace} from '../../../core/MmlTree/MmlNodes/mspace.js';
/*****************************************************************/
/**
* The SVGmspace wrapper for the MmlMspace object
*
* @template N The HTMLElement node class
* @template T The Text node class
* @template D The Document class
*/
// @ts-ignore
export class SVGmspace<N, T, D> extends
CommonMspaceMixin<SVGConstructor<any, any, any>>(SVGWrapper) {
/**
* The mspace wrapper
*/
public static kind = MmlMspace.prototype.kind;
}
+94
View File
@@ -0,0 +1,94 @@
/*************************************************************
*
* Copyright (c) 2018-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 SVGmsqrt wrapper for the MmlMsqrt object
*
* @author dpvc@mathjax.org (Davide Cervone)
*/
import {SVGWrapper, SVGConstructor} from '../Wrapper.js';
import {CommonMsqrtMixin} from '../../common/Wrappers/msqrt.js';
import {BBox} from '../../../util/BBox.js';
import {MmlMsqrt} from '../../../core/MmlTree/MmlNodes/msqrt.js';
/*****************************************************************/
/**
* The SVGmsqrt wrapper for the MmlMsqrt object
*
* @template N The HTMLElement node class
* @template T The Text node class
* @template D The Document class
*/
export class SVGmsqrt<N, T, D> extends CommonMsqrtMixin<SVGConstructor<any, any, any>>(SVGWrapper) {
/**
* The msqrt wrapper
*/
public static kind = MmlMsqrt.prototype.kind;
/**
* Indent due to root
*/
public dx: number = 0;
/**
* @override
*/
public toSVG(parent: N) {
const surd = this.childNodes[this.surd];
const base = this.childNodes[this.base];
const root = (this.root ? this.childNodes[this.root] : null);
//
// Get the parameters for the spacing of the parts
//
const sbox = surd.getBBox();
const bbox = base.getOuterBBox();
const q = this.getPQ(sbox)[1];
const t = this.font.params.rule_thickness * this.bbox.scale;
const H = bbox.h + q + t;
//
// Create the SVG structure for the root
//
const SVG = this.standardSVGnode(parent);
const BASE = this.adaptor.append(SVG, this.svg('g'));
//
// Place the children
//
this.addRoot(SVG, root, sbox, H);
surd.toSVG(SVG);
surd.place(this.dx, H - sbox.h);
base.toSVG(BASE);
base.place(this.dx + sbox.w, 0);
this.adaptor.append(SVG, this.svg('rect', {
width: this.fixed(bbox.w), height: this.fixed(t),
x: this.fixed(this.dx + sbox.w), y: this.fixed(H - t)
}));
}
/**
* Add root HTML (overridden in mroot)
*
* @param {N} ROOT The container for the root
* @param {SVGWrapper} root The wrapped MML root content
* @param {BBox} sbox The bounding box of the surd
* @param {number} H The height of the root as a whole
*/
protected addRoot(_ROOT: N, _root: SVGWrapper<N, T, D>, _sbox: BBox, _H: number) {
}
}
+105
View File
@@ -0,0 +1,105 @@
/*************************************************************
*
* Copyright (c) 2018-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 SVGmsubsup wrapper for the MmlMsubsup object
* and the special cases SVGmsub and SVGmsup
*
* @author dpvc@mathjax.org (Davide Cervone)
*/
import {SVGWrapper, Constructor} from '../Wrapper.js';
import {SVGscriptbase} from './scriptbase.js';
import {CommonMsubMixin} from '../../common/Wrappers/msubsup.js';
import {CommonMsupMixin} from '../../common/Wrappers/msubsup.js';
import {CommonMsubsupMixin} from '../../common/Wrappers/msubsup.js';
import {MmlMsubsup, MmlMsub, MmlMsup} from '../../../core/MmlTree/MmlNodes/msubsup.js';
/*****************************************************************/
/**
* The SVGmsub wrapper for the MmlMsub object
*
* @template N The HTMLElement node class
* @template T The Text node class
* @template D The Document class
*/
// @ts-ignore
export class SVGmsub<N, T, D> extends
CommonMsubMixin<SVGWrapper<any, any, any>, Constructor<SVGscriptbase<any, any, any>>>(SVGscriptbase) {
/**
* The msub wrapper
*/
public static kind = MmlMsub.prototype.kind;
}
/*****************************************************************/
/**
* The SVGmsup wrapper for the MmlMsup object
*
* @template N The HTMLElement node class
* @template T The Text node class
* @template D The Document class
*/
// @ts-ignore
export class SVGmsup<N, T, D> extends
CommonMsupMixin<SVGWrapper<any, any, any>, Constructor<SVGscriptbase<any, any, any>>>(SVGscriptbase) {
/**
* The msup wrapper
*/
public static kind = MmlMsup.prototype.kind;
}
/*****************************************************************/
/**
* The SVGmsubsup wrapper for the MmlMsubsup object
*
* @template N The HTMLElement node class
* @template T The Text node class
* @template D The Document class
*/
// @ts-ignore
export class SVGmsubsup<N, T, D> extends
CommonMsubsupMixin<SVGWrapper<any, any, any>, Constructor<SVGscriptbase<any, any, any>>>(SVGscriptbase) {
/**
* The msubsup wrapper
*/
public static kind = MmlMsubsup.prototype.kind;
/**
* @override
*/
public toSVG(parent: N) {
const svg = this.standardSVGnode(parent);
const [base, sup, sub] = [this.baseChild, this.supChild, this.subChild];
const w = this.getBaseWidth();
const x = this.getAdjustedIc();
const [u, v] = this.getUVQ();
base.toSVG(svg);
sup.toSVG(svg);
sub.toSVG(svg);
sub.place(w, v);
sup.place(w + x, u);
}
}
+418
View File
@@ -0,0 +1,418 @@
/*************************************************************
*
* Copyright (c) 2018-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 SVGmtable wrapper for the MmlMtable object
*
* @author dpvc@mathjax.org (Davide Cervone)
*/
import {SVGWrapper, SVGConstructor} from '../Wrapper.js';
import {SVGWrapperFactory} from '../WrapperFactory.js';
import {CommonMtableMixin} from '../../common/Wrappers/mtable.js';
import {SVGmtr} from './mtr.js';
import {SVGmtd} from './mtd.js';
import {MmlMtable} from '../../../core/MmlTree/MmlNodes/mtable.js';
import {MmlNode} from '../../../core/MmlTree/MmlNode.js';
import {OptionList} from '../../../util/Options.js';
import {StyleList} from '../../../util/StyleList.js';
const CLASSPREFIX = 'mjx-';
/*****************************************************************/
/**
* The SVGmtable wrapper for the MmlMtable object
*
* @template N The HTMLElement node class
* @template T The Text node class
* @template D The Document class
*/
export class SVGmtable<N, T, D> extends
CommonMtableMixin<SVGmtd<any, any, any>, SVGmtr<any, any, any>, SVGConstructor<any, any, any>>(SVGWrapper) {
/**
* The mtable wrapper
*/
public static kind = MmlMtable.prototype.kind;
/**
* @override
*/
public static styles: StyleList = {
'g[data-mml-node="mtable"] > line[data-line], svg[data-table] > g > line[data-line]': {
'stroke-width': '70px',
fill: 'none'
},
'g[data-mml-node="mtable"] > rect[data-frame], svg[data-table] > g > rect[data-frame]': {
'stroke-width': '70px',
fill: 'none'
},
'g[data-mml-node="mtable"] > .mjx-dashed, svg[data-table] > g > .mjx-dashed': {
'stroke-dasharray': '140'
},
'g[data-mml-node="mtable"] > .mjx-dotted, svg[data-table] > g > .mjx-dotted': {
'stroke-linecap': 'round',
'stroke-dasharray': '0,140'
},
'g[data-mml-node="mtable"] > g > svg': {
overflow: 'visible'
}
};
/**
* The column for labels
*/
public labels: N;
/******************************************************************/
/**
* @override
*/
constructor(factory: SVGWrapperFactory<N, T, D>, node: MmlNode, parent: SVGWrapper<N, T, D> = null) {
super(factory, node, parent);
const def: OptionList = {'data-labels': true};
if (this.isTop) {
def.transform = 'matrix(1 0 0 -1 0 0)';
}
this.labels = this.svg('g', def);
}
/**
* @override
*/
public toSVG(parent: N) {
const svg = this.standardSVGnode(parent);
this.placeRows(svg);
this.handleColumnLines(svg);
this.handleRowLines(svg);
this.handleFrame(svg);
const dx = this.handlePWidth(svg);
this.handleLabels(svg, parent, dx);
}
/**
* @param {N} svg The container in which to place the rows
*/
protected placeRows(svg: N) {
const equal = this.node.attributes.get('equalrows') as boolean;
const {H, D} = this.getTableData();
const HD = this.getEqualRowHeight();
const rSpace = this.getRowHalfSpacing();
const rLines = [this.fLine, ...this.rLines, this.fLine];
let y = this.getBBox().h - rLines[0];
for (let i = 0; i < this.numRows; i++) {
const row = this.childNodes[i];
[row.H, row.D] = this.getRowHD(equal, HD, H[i], D[i]);
[row.tSpace, row.bSpace] = [rSpace[i], rSpace[i + 1]];
[row.tLine, row.bLine] = [rLines[i], rLines[i + 1]];
row.toSVG(svg);
row.place(0, y - rSpace[i] - row.H);
y -= rSpace[i] + row.H + row.D + rSpace[i + 1] + rLines[i + 1];
}
}
/**
* @param {boolean} equal True for equal-height rows
* @param {number} HD The height of equal-height rows
* @param {number} H The natural height of the row
* @param {number} D The natural depth of the row
* @returns {number[]} The (possibly scaled) height and depth to use
*/
protected getRowHD(equal: boolean, HD: number, H: number, D: number): [number, number] {
return (equal ? [(HD + H - D) / 2, (HD - H + D) / 2] : [H, D]);
}
/******************************************************************/
/**
* @override
*/
public handleColor() {
super.handleColor();
const rect = this.firstChild();
if (rect) {
this.adaptor.setAttribute(rect, 'width', this.fixed(this.getWidth()));
}
}
/**
* Add vertical lines between columns
*
* @param {N} svg The container for the table
*/
protected handleColumnLines(svg: N) {
if (this.node.attributes.get('columnlines') === 'none') return;
const lines = this.getColumnAttributes('columnlines');
if (!lines) return;
const cSpace = this.getColumnHalfSpacing();
const cLines = this.cLines;
const cWidth = this.getComputedWidths();
let x = this.fLine;
for (let i = 0; i < lines.length; i++) {
x += cSpace[i] + cWidth[i] + cSpace[i + 1];
if (lines[i] !== 'none') {
this.adaptor.append(svg, this.makeVLine(x, lines[i], cLines[i]));
}
x += cLines[i];
}
}
/**
* Add horizontal lines between rows
*
* @param {N} svg The container for the table
*/
protected handleRowLines(svg: N) {
if (this.node.attributes.get('rowlines') === 'none') return;
const lines = this.getRowAttributes('rowlines');
if (!lines) return;
const equal = this.node.attributes.get('equalrows') as boolean;
const {H, D} = this.getTableData();
const HD = this.getEqualRowHeight();
const rSpace = this.getRowHalfSpacing();
const rLines = this.rLines;
let y = this.getBBox().h - this.fLine;
for (let i = 0; i < lines.length; i++) {
const [rH, rD] = this.getRowHD(equal, HD, H[i], D[i]);
y -= rSpace[i] + rH + rD + rSpace[i + 1];
if (lines[i] !== 'none') {
this.adaptor.append(svg, this.makeHLine(y, lines[i], rLines[i]));
}
y -= rLines[i];
}
}
/**
* Add a frame to the mtable, if needed
*
* @param {N} svg The container for the table
*/
protected handleFrame(svg: N) {
if (this.frame && this.fLine) {
const {h, d, w} = this.getBBox();
const style = this.node.attributes.get('frame') as string;
this.adaptor.append(svg, this.makeFrame(w, h, d, style));
}
}
/**
* @returns {number} The x-adjustement needed to handle the true size of percentage-width tables
*/
protected handlePWidth(svg: N): number {
if (!this.pWidth) {
return 0;
}
const {w, L, R} = this.getBBox();
const W = L + this.pWidth + R;
const align = this.getAlignShift()[0];
const CW = Math.max(this.isTop ? W : 0, this.container.getWrapWidth(this.containerI)) - L - R;
const dw = w - (this.pWidth > CW ? CW : this.pWidth);
const dx = (align === 'left' ? 0 : align === 'right' ? dw : dw / 2);
if (dx) {
const table = this.svg('g', {}, this.adaptor.childNodes(svg));
this.place(dx, 0, table);
this.adaptor.append(svg, table);
}
return dx;
}
/******************************************************************/
/**
* @param {string} style The line style whose class is to be obtained
* @returns {string} The class name for the style
*/
protected lineClass(style: string): string {
return CLASSPREFIX + style;
}
/**
* @param {number} w The width of the frame
* @param {number} h The height of the frame
* @param {number} d The depth of the frame
* @param {string} style The border style for the frame
* @returns {N} The SVG element for the frame
*/
protected makeFrame(w: number, h: number, d: number, style: string): N {
const t = this.fLine;
return this.svg('rect', this.setLineThickness(t, style, {
'data-frame': true, 'class': this.lineClass(style),
width: this.fixed(w - t), height: this.fixed(h + d - t),
x: this.fixed(t / 2), y: this.fixed(t / 2 - d)
}));
}
/**
* @param {number} x The x location of the line
* @param {string} style The border style for the line
* @param {number} t The line thickness
* @returns {N} The SVG element for the line
*/
protected makeVLine(x: number, style: string, t: number): N {
const {h, d} = this.getBBox();
const dt = (style === 'dotted' ? t / 2 : 0);
const X = this.fixed(x + t / 2);
return this.svg('line', this.setLineThickness(t, style, {
'data-line': 'v', 'class': this.lineClass(style),
x1: X, y1: this.fixed(dt - d), x2: X, y2: this.fixed(h - dt)
}));
}
/**
* @param {number} y The y location of the line
* @param {string} style The border style for the line
* @param {number} t The line thickness
* @returns {N} The SVG element for the line
*/
protected makeHLine(y: number, style: string, t: number): N {
const w = this.getBBox().w;
const dt = (style === 'dotted' ? t / 2 : 0);
const Y = this.fixed(y - t / 2);
return this.svg('line', this.setLineThickness(t, style, {
'data-line': 'h', 'class': this.lineClass(style),
x1: this.fixed(dt), y1: Y, x2: this.fixed(w - dt), y2: Y
}));
}
/**
* @param {number} t The thickness of the line
* @param {string} style The border style for the line
* @param {OptionList} properties The list of properties to modify
* @param {OptionList} The modified properties
*/
protected setLineThickness(t: number, style: string, properties: OptionList) {
if (t !== .07) {
properties['stroke-thickness'] = this.fixed(t);
if (style !== 'solid') {
properties['stroke-dasharray'] = (style === 'dotted' ? '0,' : '') + this.fixed(2 * t);
}
}
return properties;
}
/******************************************************************/
/**
* Handle addition of labels to the table
*
* @param {N} svg The container for the table contents
* @param {N} parent The parent containing the the table
* @param {number} dx The adjustement for percentage width tables
*/
protected handleLabels(svg: N, _parent: N, dx: number) {
if (!this.hasLabels) return;
const labels = this.labels;
const attributes = this.node.attributes;
//
// Set the side for the labels
//
const side = attributes.get('side') as string;
//
// Add the labels to the table
//
this.spaceLabels();
//
// Handle top-level table to make it adapt to container size
// but place subtables explicitly
//
this.isTop ? this.topTable(svg, labels, side) : this.subTable(svg, labels, side, dx);
}
/**
* Add spacing elements between the label rows to align them with the rest of the table
*/
protected spaceLabels() {
const adaptor = this.adaptor;
const h = this.getBBox().h;
const L = this.getTableData().L;
const space = this.getRowHalfSpacing();
//
// Start with frame size and add in spacing, height and depth,
// and line thickness for each non-labeled row.
//
let y = h - this.fLine;
let current = adaptor.firstChild(this.labels) as N;
for (let i = 0; i < this.numRows; i++) {
const row = this.childNodes[i] as SVGmtr<N, T, D>;
if (row.node.isKind('mlabeledtr')) {
const cell = row.childNodes[0];
y -= space[i] + row.H;
row.placeCell(cell, {x: 0, y: y, w: L, lSpace: 0, rSpace: 0, lLine: 0, rLine: 0});
y -= row.D + space[i + 1] + this.rLines[i];
current = adaptor.next(current) as N;
} else {
y -= space[i] + row.H + row.D + space[i + 1] + this.rLines[i];
}
}
}
/**
* Handles tables with labels so that the label will move with the size of the container
*
* @param {N} svg The SVG container for the table
* @param {N} labels The group of labels
* @param {string} side The side alignment (left or right)
*/
protected topTable(svg: N, labels: N, side: string) {
const adaptor = this.adaptor;
const {h, d, w, L, R} = this.getBBox();
const W = L + (this.pWidth || w) + R;
const LW = this.getTableData().L;
const [ , align, shift] = this.getPadAlignShift(side);
const dx = shift + (align === 'right' ? -W : align === 'center' ? -W / 2 : 0) + L;
const matrix = 'matrix(1 0 0 -1 0 0)';
const scale = `scale(${this.jax.fixed((this.font.params.x_height * 1000) / this.metrics.ex, 2)})`;
const transform = `translate(0 ${this.fixed(h)}) ${matrix} ${scale}`;
let table = this.svg('svg', {
'data-table': true,
preserveAspectRatio: (align === 'left' ? 'xMinYMid' : align === 'right' ? 'xMaxYMid' : 'xMidYMid'),
viewBox: [this.fixed(-dx), this.fixed(-h), 1, this.fixed(h + d)].join(' ')
}, [
this.svg('g', {transform: matrix}, adaptor.childNodes(svg))
]);
labels = this.svg('svg', {
'data-labels': true,
preserveAspectRatio: (side === 'left' ? 'xMinYMid' : 'xMaxYMid'),
viewBox: [side === 'left' ? 0 : this.fixed(LW), this.fixed(-h), 1, this.fixed(h + d)].join(' ')
}, [labels]);
adaptor.append(svg, this.svg('g', {transform: transform}, [table, labels]));
this.place(-L, 0, svg); // remove spacing for L, which is added by the parent during appending
}
/**
* @param {N} svg The SVG container for the table
* @param {N} labels The group of labels
* @param {string} side The side alignment (left or right)
* @param {number} dx The adjustement for percentage width tables
*/
protected subTable(svg: N, labels: N, side: string, dx: number) {
const adaptor = this.adaptor;
const {w, L, R} = this.getBBox();
const W = L + (this.pWidth || w) + R;
const labelW = this.getTableData().L;
const align = this.getAlignShift()[0];
const CW = Math.max(W, this.container.getWrapWidth(this.containerI));
this.place(side === 'left' ?
(align === 'left' ? 0 : align === 'right' ? W - CW + dx : (W - CW) / 2 + dx) - L :
(align === 'left' ? CW : align === 'right' ? W + dx : (CW + W) / 2 + dx) - L - labelW,
0, labels);
adaptor.append(svg, labels);
}
}
+82
View File
@@ -0,0 +1,82 @@
/*************************************************************
*
* Copyright (c) 2018-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 SVGmtd wrapper for the MmlMtd object
*
* @author dpvc@mathjax.org (Davide Cervone)
*/
import {SVGWrapper, SVGConstructor} from '../Wrapper.js';
import {CommonMtdMixin} from '../../common/Wrappers/mtd.js';
import {MmlMtd} from '../../../core/MmlTree/MmlNodes/mtd.js';
/*****************************************************************/
/**
* The SVGmtd wrapper for the MmlMtd object
*
* @template N The HTMLElement node class
* @template T The Text node class
* @template D The Document class
*/
// @ts-ignore
export class SVGmtd<N, T, D> extends
CommonMtdMixin<SVGConstructor<any, any, any>>(SVGWrapper) {
/**
* The mtd wrapper
*/
public static kind = MmlMtd.prototype.kind;
/**
* @param {number} x The x offset of the left side of the cell
* @param {number} y The y offset of the baseline of the cell
* @param {number} W The width of the cell
* @param {number} H The height of the cell
* @param {number} D The depth of the cell
* @return {[number, number]} The x and y offsets used
*/
public placeCell(x: number, y: number, W: number, H: number, D: number): [number, number] {
const bbox = this.getBBox();
const h = Math.max(bbox.h, .75);
const d = Math.max(bbox.d, .25);
const calign = this.node.attributes.get('columnalign') as string;
const ralign = this.node.attributes.get('rowalign') as string;
const alignX = this.getAlignX(W, bbox, calign);
const alignY = this.getAlignY(H, D, h, d, ralign);
this.place(x + alignX, y + alignY);
return [alignX, alignY];
}
/**
* @param {number} x The x offset of the left side of the cell
* @param {number} y The y position of the bottom of the cell
* @param {number} W The width of the cell
* @param {number} H The height + depth of the cell
*/
public placeColor(x: number, y: number, W: number, H: number) {
const adaptor = this.adaptor;
const child = this.firstChild();
if (child && adaptor.kind(child) === 'rect' && adaptor.getAttribute(child, 'data-bgcolor')) {
adaptor.setAttribute(child, 'x', this.fixed(x));
adaptor.setAttribute(child, 'y', this.fixed(y));
adaptor.setAttribute(child, 'width', this.fixed(W));
adaptor.setAttribute(child, 'height', this.fixed(H));
}
}
}
+45
View File
@@ -0,0 +1,45 @@
/*************************************************************
*
* Copyright (c) 2019-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 SVGmtext wrapper for the MmlMtext object
*
* @author dpvc@mathjax.org (Davide Cervone)
*/
import {SVGWrapper, SVGConstructor} from '../Wrapper.js';
import {CommonMtextMixin} from '../../common/Wrappers/mtext.js';
import {MmlMtext} from '../../../core/MmlTree/MmlNodes/mtext.js';
/*****************************************************************/
/**
* The SVGmtext wrapper for the MmlMtext object
*
* @template N The HTMLElement node class
* @template T The Text node class
* @template D The Document class
*/
// @ts-ignore
export class SVGmtext<N, T, D> extends
CommonMtextMixin<SVGConstructor<any, any, any>>(SVGWrapper) {
/**
* The mtext wrapper
*/
public static kind = MmlMtext.prototype.kind;
}
+179
View File
@@ -0,0 +1,179 @@
/*************************************************************
*
* Copyright (c) 2018-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 SVGmtr wrapper for the MmlMtr object
* and SVGmlabeledtr for MmlMlabeledtr
*
* @author dpvc@mathjax.org (Davide Cervone)
*/
import {SVGWrapper, SVGConstructor, Constructor} from '../Wrapper.js';
import {CommonMtrMixin} from '../../common/Wrappers/mtr.js';
import {CommonMlabeledtrMixin} from '../../common/Wrappers/mtr.js';
import {SVGmtd} from './mtd.js';
import {MmlMtr, MmlMlabeledtr} from '../../../core/MmlTree/MmlNodes/mtr.js';
/**
* The data needed for placeCell()
*/
export type SizeData = {
x: number,
y: number,
w: number,
lSpace: number,
rSpace: number,
lLine: number,
rLine: number
};
/*****************************************************************/
/**
* The SVGmtr wrapper for the MmlMtr object
*
* @template N The HTMLElement node class
* @template T The Text node class
* @template D The Document class
*/
export class SVGmtr<N, T, D> extends
CommonMtrMixin<SVGmtd<any, any, any>, SVGConstructor<any, any, any>>(SVGWrapper) {
/**
* The mtr wrapper
*/
public static kind = MmlMtr.prototype.kind;
/**
* The height of the row
*/
public H: number;
/**
* The depth of the row
*/
public D: number;
/**
* The space above the row
*/
public tSpace: number;
/**
* The space below the row
*/
public bSpace: number;
/**
* The line space above the row
*/
public tLine: number;
/**
* The line space below the row
*/
public bLine: number;
/**
* @override
*/
public toSVG(parent: N) {
const svg = this.standardSVGnode(parent);
this.placeCells(svg);
this.placeColor();
}
/**
* Set the location of the cell contents in the row and expand the cell background colors
*
* @param {N} svg The container for the table
*/
protected placeCells(svg: N) {
const cSpace = this.parent.getColumnHalfSpacing();
const cLines = [this.parent.fLine, ...this.parent.cLines, this.parent.fLine];
const cWidth = this.parent.getComputedWidths();
const scale = 1 / this.getBBox().rscale;
let x = cLines[0];
for (let i = 0; i < this.numCells; i++) {
const child = this.getChild(i);
child.toSVG(svg);
x += this.placeCell(child, {
x: x, y: 0, lSpace: cSpace[i] * scale, rSpace: cSpace[i + 1] * scale, w: cWidth[i] * scale,
lLine: cLines[i] * scale, rLine: cLines[i + 1] * scale
});
}
}
/**
* @param {SVGmtd} cell The cell to place
* @param {SizeData} sizes The positioning information
* @return {number} The new x position
*/
public placeCell(cell: SVGmtd<N, T, D>, sizes: SizeData): number {
const {x, y, lSpace, w, rSpace, lLine, rLine} = sizes;
const scale = 1 / this.getBBox().rscale;
const [h, d] = [this.H * scale, this.D * scale];
const [t, b] = [this.tSpace * scale, this.bSpace * scale];
const [dx, dy] = cell.placeCell(x + lSpace, y, w, h, d);
const W = lSpace + w + rSpace;
cell.placeColor(-(dx + lSpace + lLine / 2), -(d + b + dy), W + (lLine + rLine) / 2, h + d + t + b);
return W + rLine;
}
/**
* Expand the backgound color to fill the entire row
*/
protected placeColor() {
const scale = 1 / this.getBBox().rscale;
const adaptor = this.adaptor;
const child = this.firstChild();
if (child && adaptor.kind(child) === 'rect' && adaptor.getAttribute(child, 'data-bgcolor')) {
const [TL, BL] = [(this.tLine / 2) * scale, (this.bLine / 2) * scale];
const [TS, BS] = [this.tSpace * scale, this.bSpace * scale];
const [H, D] = [this.H * scale, this.D * scale];
adaptor.setAttribute(child, 'y', this.fixed(-(D + BS + BL)));
adaptor.setAttribute(child, 'width', this.fixed(this.parent.getWidth() * scale));
adaptor.setAttribute(child, 'height', this.fixed(TL + TS + H + D + BS + BL));
}
}
}
/*****************************************************************/
/**
* The SVGlabeledmtr wrapper for the MmlMlabeledtr object
*
* @template N The HTMLElement node class
* @template T The Text node class
* @template D The Document class
*/
// @ts-ignore
export class SVGmlabeledtr<N, T, D> extends
CommonMlabeledtrMixin<SVGmtd<any, any, any>, Constructor<SVGmtr<any, any, any>>>(SVGmtr) {
/**
* The mlabeledtr wrapper
*/
public static kind = MmlMlabeledtr.prototype.kind;
/**
* @override
*/
public toSVG(parent: N) {
super.toSVG(parent);
const child = this.childNodes[0];
if (child) {
child.toSVG(this.parent.labels);
}
}
}
+161
View File
@@ -0,0 +1,161 @@
/*************************************************************
*
* Copyright (c) 2018-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 SVGmunderover wrapper for the MmlMunderover object
* and the special cases SVGmunder and SVGmsup
*
* @author dpvc@mathjax.org (Davide Cervone)
*/
import {SVGWrapper, Constructor} from '../Wrapper.js';
import {SVGmsubsup, SVGmsub, SVGmsup} from './msubsup.js';
import {CommonMunderMixin} from '../../common/Wrappers/munderover.js';
import { CommonMoverMixin} from '../../common/Wrappers/munderover.js';
import {CommonMunderoverMixin} from '../../common/Wrappers/munderover.js';
import {MmlMunderover, MmlMunder, MmlMover} from '../../../core/MmlTree/MmlNodes/munderover.js';
/*****************************************************************/
/**
* The SVGmunder wrapper for the MmlMunder object
*
* @template N The HTMLElement node class
* @template T The Text node class
* @template D The Document class
*/
// @ts-ignore
export class SVGmunder<N, T, D> extends
CommonMunderMixin<SVGWrapper<any, any, any>, Constructor<SVGmsub<any, any, any>>>(SVGmsub) {
/**
* The munder wrapper
*/
public static kind = MmlMunder.prototype.kind;
/**
* @override
*/
public toSVG(parent: N) {
if (this.hasMovableLimits()) {
super.toSVG(parent);
return;
}
const svg = this.standardSVGnode(parent);
const [base, script] = [this.baseChild, this.scriptChild];
const [bbox, sbox] = [base.getOuterBBox(), script.getOuterBBox()];
base.toSVG(svg);
script.toSVG(svg);
const delta = (this.isLineBelow ? 0 : this.getDelta(true));
const v = this.getUnderKV(bbox, sbox)[1];
const [bx, sx] = this.getDeltaW([bbox, sbox], [0, -delta]);
base.place(bx, 0);
script.place(sx, v);
}
}
/*****************************************************************/
/**
* The SVGmover wrapper for the MmlMover object
*
* @template N The HTMLElement node class
* @template T The Text node class
* @template D The Document class
*/
// @ts-ignore
export class SVGmover<N, T, D> extends
CommonMoverMixin<SVGWrapper<any, any, any>, Constructor<SVGmsup<any, any, any>>>(SVGmsup) {
/**
* The mover wrapper
*/
public static kind = MmlMover.prototype.kind;
/**
* @override
*/
public toSVG(parent: N) {
if (this.hasMovableLimits()) {
super.toSVG(parent);
return;
}
const svg = this.standardSVGnode(parent);
const [base, script] = [this.baseChild, this.scriptChild];
const [bbox, sbox] = [base.getOuterBBox(), script.getOuterBBox()];
base.toSVG(svg);
script.toSVG(svg);
const delta = (this.isLineAbove ? 0 : this.getDelta());
const u = this.getOverKU(bbox, sbox)[1];
const [bx, sx] = this.getDeltaW([bbox, sbox], [0, delta]);
base.place(bx, 0);
script.place(sx, u);
}
}
/*****************************************************************/
/*
* The SVGmunderover wrapper for the MmlMunderover object
*
* @template N The HTMLElement node class
* @template T The Text node class
* @template D The Document class
*/
// @ts-ignore
export class SVGmunderover<N, T, D> extends
CommonMunderoverMixin<SVGWrapper<any, any, any>, Constructor<SVGmsubsup<any, any, any>>>(SVGmsubsup) {
/**
* The munderover wrapper
*/
public static kind = MmlMunderover.prototype.kind;
/**
* @override
*/
public toSVG(parent: N) {
if (this.hasMovableLimits()) {
super.toSVG(parent);
return;
}
const svg = this.standardSVGnode(parent);
const [base, over, under] = [this.baseChild, this.overChild, this.underChild];
const [bbox, obox, ubox] = [base.getOuterBBox(), over.getOuterBBox(), under.getOuterBBox()];
base.toSVG(svg);
under.toSVG(svg);
over.toSVG(svg);
const delta = this.getDelta();
const u = this.getOverKU(bbox, obox)[1];
const v = this.getUnderKV(bbox, ubox)[1];
const [bx, ux, ox] = this.getDeltaW([bbox, ubox, obox],
[0, this.isLineBelow ? 0 : -delta, this.isLineAbove ? 0 : delta]);
base.place(bx, 0);
under.place(ux, v);
over.place(ox, u);
}
}
+63
View File
@@ -0,0 +1,63 @@
/*************************************************************
*
* Copyright (c) 2018-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 a base class for SVGmsubsup, SVGmunderover
* and their relatives. (Since munderover can become msubsup
* when movablelimits is set, munderover needs to be able to
* do the same thing as msubsup in some cases.)
*
* @author dpvc@mathjax.org (Davide Cervone)
*/
import {SVGWrapper, SVGConstructor} from '../Wrapper.js';
import {CommonScriptbaseMixin} from '../../common/Wrappers/scriptbase.js';
/*****************************************************************/
/**
* A base class for msup/msub/msubsup and munder/mover/munderover
* wrapper implementations
*
* @template N The HTMLElement node class
* @template T The Text node class
* @template D The Document class
*/
// @ts-ignore
export class SVGscriptbase<N, T, D> extends
CommonScriptbaseMixin<SVGWrapper<any, any, any>, SVGConstructor<any, any, any>>(SVGWrapper) {
/**
* The scriptbase wrapper
*/
public static kind = 'scriptbase';
/**
* This gives the common output for msub and msup. It is overridden
* for all the others (msubsup, munder, mover, munderover).
*
* @override
*/
public toSVG(parent: N) {
const svg = this.standardSVGnode(parent);
const w = this.getBaseWidth();
const [x, v] = this.getOffset();
this.baseChild.toSVG(svg);
this.scriptChild.toSVG(svg);
this.scriptChild.place(w + x, v);
}
}
+184
View File
@@ -0,0 +1,184 @@
/*************************************************************
*
* Copyright (c) 2018-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 SVGsemantics wrapper for the MmlSemantics object
* and the associated wrappers for annotations
*
* @author dpvc@mathjax.org (Davide Cervone)
*/
import {SVGWrapper, SVGConstructor} from '../Wrapper.js';
import {CommonSemanticsMixin} from '../../common/Wrappers/semantics.js';
import {BBox} from '../../../util/BBox.js';
import {MmlSemantics, MmlAnnotation, MmlAnnotationXML} from '../../../core/MmlTree/MmlNodes/semantics.js';
import {XMLNode} from '../../../core/MmlTree/MmlNode.js';
import {StyleList} from '../../../util/StyleList.js';
/*****************************************************************/
/**
* The SVGsemantics wrapper for the MmlSemantics object
*
* @template N The HTMLElement node class
* @template T The Text node class
* @template D The Document class
*/
// @ts-ignore
export class SVGsemantics<N, T, D> extends
CommonSemanticsMixin<SVGConstructor<any, any, any>>(SVGWrapper) {
/**
* The semantics wrapper
*/
public static kind = MmlSemantics.prototype.kind;
/**
* @override
*/
public toSVG(parent: N) {
const svg = this.standardSVGnode(parent);
if (this.childNodes.length) {
this.childNodes[0].toSVG(svg);
}
}
}
/*****************************************************************/
/**
* The SVGannotation wrapper for the MmlAnnotation object
*
* @template N The HTMLElement node class
* @template T The Text node class
* @template D The Document class
*/
export class SVGannotation<N, T, D> extends SVGWrapper<N, T, D> {
/**
* The annotation wrapper
*/
public static kind = MmlAnnotation.prototype.kind;
/**
* @override
*/
public toSVG(parent: N) {
// FIXME: output as plain text
super.toSVG(parent);
}
/**
* @override
*/
public computeBBox() {
// FIXME: compute using the DOM, if possible
return this.bbox;
}
}
/*****************************************************************/
/**
* The SVGannotationXML wrapper for the MmlAnnotationXML object
*
* @template N The HTMLElement node class
* @template T The Text node class
* @template D The Document class
*/
export class SVGannotationXML<N, T, D> extends SVGWrapper<N, T, D> {
/**
* The annotation-xml wrapper
*/
public static kind = MmlAnnotationXML.prototype.kind;
/**
* @override
*/
public static styles: StyleList = {
'foreignObject[data-mjx-xml]': {
'font-family': 'initial',
'line-height': 'normal',
overflow: 'visible'
}
};
}
/*****************************************************************/
/**
* The SVGxml wrapper for the XMLNode object
*
* @template N The HTMLElement node class
* @template T The Text node class
* @template D The Document class
*/
export class SVGxml<N, T, D> extends SVGWrapper<N, T, D> {
/**
* The XMLNode wrapper
*/
public static kind = XMLNode.prototype.kind;
/**
* Don't include inline-block CSS for this element
*/
public static autoStyle = false;
/**
* @override
*/
public toSVG(parent: N) {
const xml = this.adaptor.clone((this.node as XMLNode).getXML() as N);
const em = this.jax.math.metrics.em * this.jax.math.metrics.scale;
const scale = this.fixed(1 / em);
const {w, h, d} = this.getBBox();
this.element = this.adaptor.append(parent, this.svg('foreignObject', {
'data-mjx-xml': true,
y: this.jax.fixed(-h * em) + 'px',
width: this.jax.fixed(w * em) + 'px',
height: this.jax.fixed((h + d) * em) + 'px',
transform: `scale(${scale}) matrix(1 0 0 -1 0 0)`
}, [xml]));
}
/**
* @override
*/
public computeBBox(bbox: BBox, _recompute: boolean = false) {
const {w, h, d} = this.jax.measureXMLnode((this.node as XMLNode).getXML() as N);
bbox.w = w;
bbox.h = h;
bbox.d = d;
}
/**
* @override
*/
protected getStyles() {}
/**
* @override
*/
protected getScale() {}
/**
* @override
*/
protected getVariant() {}
}