Port domTree and buildCommon to @flow. (#938)

* Port domTree to @flow.
* Port buildCommon to @flow.
* Change domTree attribute arrays to attribute objects.
This commit is contained in:
Ashish Myles
2017-11-24 14:31:49 -05:00
committed by GitHub
parent a02859033a
commit c8249c389f
6 changed files with 537 additions and 339 deletions

View File

@@ -1,3 +1,4 @@
// @flow
/* eslint no-console:0 */ /* eslint no-console:0 */
/** /**
* This module contains general functions that can be used for building * This module contains general functions that can be used for building
@@ -9,6 +10,12 @@ import fontMetrics from "./fontMetrics";
import symbols from "./symbols"; import symbols from "./symbols";
import utils from "./utils"; import utils from "./utils";
import type Options from "./Options";
import type ParseNode from "./ParseNode";
import type {CharacterMetrics} from "./fontMetrics";
import type {Mode} from "./types";
import type {DomChildNode, CombinableDomNode} from "./domTree";
// The following have to be loaded from Main-Italic font, using class mainit // The following have to be loaded from Main-Italic font, using class mainit
const mainitLetters = [ const mainitLetters = [
"\\imath", // dotless i "\\imath", // dotless i
@@ -20,7 +27,12 @@ const mainitLetters = [
* Looks up the given symbol in fontMetrics, after applying any symbol * Looks up the given symbol in fontMetrics, after applying any symbol
* replacements defined in symbol.js * replacements defined in symbol.js
*/ */
const lookupSymbol = function(value, fontFamily, mode) { const lookupSymbol = function(
value: string,
// TODO(#963): Use a union type for this.
fontFamily: string,
mode: Mode,
): {value: string, metrics: ?CharacterMetrics} {
// Replace the value with its replaced value from symbol.js // Replace the value with its replaced value from symbol.js
if (symbols[mode][value] && symbols[mode][value].replace) { if (symbols[mode][value] && symbols[mode][value].replace) {
value = symbols[mode][value].replace; value = symbols[mode][value].replace;
@@ -39,8 +51,15 @@ const lookupSymbol = function(value, fontFamily, mode) {
* TODO: make argument order closer to makeSpan * TODO: make argument order closer to makeSpan
* TODO: add a separate argument for math class (e.g. `mop`, `mbin`), which * TODO: add a separate argument for math class (e.g. `mop`, `mbin`), which
* should if present come first in `classes`. * should if present come first in `classes`.
* TODO(#953): Make `options` mandatory and always pass it in.
*/ */
const makeSymbol = function(value, fontFamily, mode, options, classes) { const makeSymbol = function(
value: string,
fontFamily: string,
mode: Mode,
options?: Options,
classes?: string[],
): domTree.symbolNode {
const lookup = lookupSymbol(value, fontFamily, mode); const lookup = lookupSymbol(value, fontFamily, mode);
const metrics = lookup.metrics; const metrics = lookup.metrics;
value = lookup.value; value = lookup.value;
@@ -67,8 +86,9 @@ const makeSymbol = function(value, fontFamily, mode, options, classes) {
if (options.style.isTight()) { if (options.style.isTight()) {
symbolNode.classes.push("mtight"); symbolNode.classes.push("mtight");
} }
if (options.getColor()) { const color = options.getColor();
symbolNode.style.color = options.getColor(); if (color) {
symbolNode.style.color = color;
} }
} }
@@ -78,8 +98,15 @@ const makeSymbol = function(value, fontFamily, mode, options, classes) {
/** /**
* Makes a symbol in Main-Regular or AMS-Regular. * Makes a symbol in Main-Regular or AMS-Regular.
* Used for rel, bin, open, close, inner, and punct. * Used for rel, bin, open, close, inner, and punct.
*
* TODO(#953): Make `options` mandatory and always pass it in.
*/ */
const mathsym = function(value, mode, options, classes) { const mathsym = function(
value: string,
mode: Mode,
options?: Options,
classes?: string[] = [],
): domTree.symbolNode {
// Decide what font to render the symbol in by its entry in the symbols // Decide what font to render the symbol in by its entry in the symbols
// table. // table.
// Have a special case for when the value = \ because the \ is used as a // Have a special case for when the value = \ because the \ is used as a
@@ -97,7 +124,13 @@ const mathsym = function(value, mode, options, classes) {
/** /**
* Makes a symbol in the default font for mathords and textords. * Makes a symbol in the default font for mathords and textords.
*/ */
const mathDefault = function(value, mode, options, classes, type) { const mathDefault = function(
value: string,
mode: Mode,
options: Options,
classes: string[],
type: string, // TODO(#892): Use ParseNode type here.
): domTree.symbolNode {
if (type === "mathord") { if (type === "mathord") {
const fontLookup = mathit(value, mode, options, classes); const fontLookup = mathit(value, mode, options, classes);
return makeSymbol(value, fontLookup.fontName, mode, options, return makeSymbol(value, fontLookup.fontName, mode, options,
@@ -123,7 +156,12 @@ const mathDefault = function(value, mode, options, classes, type) {
* depending on the symbol. Use this function instead of fontMap for font * depending on the symbol. Use this function instead of fontMap for font
* "mathit". * "mathit".
*/ */
const mathit = function(value, mode, options, classes) { const mathit = function(
value: string,
mode: Mode,
options: Options,
classes: string[],
): {| fontName: string, fontClass: string |} {
if (/[0-9]/.test(value.charAt(0)) || if (/[0-9]/.test(value.charAt(0)) ||
// glyphs for \imath and \jmath do not exist in Math-Italic so we // glyphs for \imath and \jmath do not exist in Math-Italic so we
// need to use Main-Italic instead // need to use Main-Italic instead
@@ -143,7 +181,11 @@ const mathit = function(value, mode, options, classes) {
/** /**
* Makes either a mathord or textord in the correct font and color. * Makes either a mathord or textord in the correct font and color.
*/ */
const makeOrd = function(group, options, type) { const makeOrd = function(
group: ParseNode,
options: Options,
type: string, // TODO(#892): Use ParseNode type here.
): domTree.symbolNode {
const mode = group.mode; const mode = group.mode;
const value = group.value; const value = group.value;
@@ -172,7 +214,9 @@ const makeOrd = function(group, options, type) {
* Combine as many characters as possible in the given array of characters * Combine as many characters as possible in the given array of characters
* via their tryCombine method. * via their tryCombine method.
*/ */
const tryCombineChars = function(chars) { const tryCombineChars = function(
chars: CombinableDomNode[],
): CombinableDomNode[] {
for (let i = 0; i < chars.length - 1; i++) { for (let i = 0; i < chars.length - 1; i++) {
if (chars[i].tryCombine(chars[i + 1])) { if (chars[i].tryCombine(chars[i + 1])) {
chars.splice(i + 1, 1); chars.splice(i + 1, 1);
@@ -186,22 +230,22 @@ const tryCombineChars = function(chars) {
* Calculate the height, depth, and maxFontSize of an element based on its * Calculate the height, depth, and maxFontSize of an element based on its
* children. * children.
*/ */
const sizeElementFromChildren = function(elem) { const sizeElementFromChildren = function(
elem: domTree.span | domTree.anchor | domTree.documentFragment,
) {
let height = 0; let height = 0;
let depth = 0; let depth = 0;
let maxFontSize = 0; let maxFontSize = 0;
if (elem.children) { for (const child of elem.children) {
for (let i = 0; i < elem.children.length; i++) { if (child.height > height) {
if (elem.children[i].height > height) { height = child.height;
height = elem.children[i].height; }
} if (child.depth > depth) {
if (elem.children[i].depth > depth) { depth = child.depth;
depth = elem.children[i].depth; }
} if (child.maxFontSize > maxFontSize) {
if (elem.children[i].maxFontSize > maxFontSize) { maxFontSize = child.maxFontSize;
maxFontSize = elem.children[i].maxFontSize;
}
} }
} }
@@ -213,12 +257,16 @@ const sizeElementFromChildren = function(elem) {
/** /**
* Makes a span with the given list of classes, list of children, and options. * Makes a span with the given list of classes, list of children, and options.
* *
* TODO: Ensure that `options` is always provided (currently some call sites * TODO(#953): Ensure that `options` is always provided (currently some call
* don't pass it). * sites don't pass it) and make the type below mandatory.
* TODO: add a separate argument for math class (e.g. `mop`, `mbin`), which * TODO: add a separate argument for math class (e.g. `mop`, `mbin`), which
* should if present come first in `classes`. * should if present come first in `classes`.
*/ */
const makeSpan = function(classes, children, options) { const makeSpan = function(
classes?: string[],
children?: DomChildNode[],
options?: Options,
): domTree.span {
const span = new domTree.span(classes, children, options); const span = new domTree.span(classes, children, options);
sizeElementFromChildren(span); sizeElementFromChildren(span);
@@ -230,7 +278,12 @@ const makeSpan = function(classes, children, options) {
* Makes an anchor with the given href, list of classes, list of children, * Makes an anchor with the given href, list of classes, list of children,
* and options. * and options.
*/ */
const makeAnchor = function(href, classes, children, options) { const makeAnchor = function(
href: string,
classes: string[],
children: DomChildNode[],
options: Options,
) {
const anchor = new domTree.anchor(href, classes, children, options); const anchor = new domTree.anchor(href, classes, children, options);
sizeElementFromChildren(anchor); sizeElementFromChildren(anchor);
@@ -242,7 +295,10 @@ const makeAnchor = function(href, classes, children, options) {
* Prepends the given children to the given span, updating height, depth, and * Prepends the given children to the given span, updating height, depth, and
* maxFontSize. * maxFontSize.
*/ */
const prependChildren = function(span, children) { const prependChildren = function(
span: domTree.span,
children: DomChildNode[],
) {
span.children = children.concat(span.children); span.children = children.concat(span.children);
sizeElementFromChildren(span); sizeElementFromChildren(span);
@@ -251,7 +307,9 @@ const prependChildren = function(span, children) {
/** /**
* Makes a document fragment with the given list of children. * Makes a document fragment with the given list of children.
*/ */
const makeFragment = function(children) { const makeFragment = function(
children: DomChildNode[],
): domTree.documentFragment {
const fragment = new domTree.documentFragment(children); const fragment = new domTree.documentFragment(children);
sizeElementFromChildren(fragment); sizeElementFromChildren(fragment);
@@ -260,11 +318,21 @@ const makeFragment = function(children) {
}; };
// TODO(#939): Uncomment and use VListParam as the type of makeVList's first param. // These are exact object types to catch typos in the names of the optional fields.
/* type VListElem = {|
type VListElem = type: "elem",
{type: "elem", elem: DomChildNode, marginLeft?: string, marginRight?: string}; elem: DomChildNode,
type VListKern = {type: "kern", size: number}; marginLeft?: string,
marginRight?: string,
|};
type VListElemAndShift = {|
type: "elem",
elem: DomChildNode,
shift: number,
marginLeft?: string,
marginRight?: string,
|};
type VListKern = {| type: "kern", size: number |};
// A list of child or kern nodes to be stacked on top of each other (i.e. the // A list of child or kern nodes to be stacked on top of each other (i.e. the
// first element will be at the bottom, and the last at the top). // first element will be at the bottom, and the last at the top).
@@ -273,45 +341,44 @@ type VListChild = VListElem | VListKern;
type VListParam = {| type VListParam = {|
// Each child contains how much it should be shifted downward. // Each child contains how much it should be shifted downward.
positionType: "individualShift", positionType: "individualShift",
children: (VListElem & {shift: number})[], children: VListElemAndShift[],
|} | {| |} | {|
// "top": The positionData specifies the topmost point of the vlist (note this // "top": The positionData specifies the topmost point of the vlist (note this
// is expected to be a height, so positive values move up). // is expected to be a height, so positive values move up).
// "bottom": The positionData specifies the bottommost point of the vlist (note // "bottom": The positionData specifies the bottommost point of the vlist (note
// this is expected to be a depth, so positive values move down). // this is expected to be a depth, so positive values move down).
// "shift": The vlist will be positioned such that its baseline is positionData // "shift": The vlist will be positioned such that its baseline is positionData
// away from the baseline of the first child. Positive values move // away from the baseline of the first child which MUST be an
// downwards. // "elem". Positive values move downwards.
positionType: "top" | "bottom" | "shift", positionType: "top" | "bottom" | "shift",
positionData: number, positionData: number,
children: VListChild[], children: VListChild[],
|} | {| |} | {|
// The vlist is positioned so that its baseline is aligned with the baseline // The vlist is positioned so that its baseline is aligned with the baseline
// of the first child. This is equivalent to "shift" with positionData=0. // of the first child which MUST be an "elem". This is equivalent to "shift"
// with positionData=0.
positionType: "firstBaseline", positionType: "firstBaseline",
children: VListChild[], children: VListChild[],
|}; |};
*/
/**
* Makes a vertical list by stacking elements and kerns on top of each other.
* Allows for many different ways of specifying the positioning method.
*
* See parameter documentation on the type documentation above.
*/
const makeVList = function({positionType, positionData, children}, options) {
let depth;
let currPos;
let i;
if (positionType === "individualShift") {
const oldChildren = children;
children = [oldChildren[0]];
// Add in kerns to the list of children to get each element to be // Computes the updated `children` list and the overall depth.
//
// This helper function for makeVList makes it easier to enforce type safety by
// allowing early exits (returns) in the logic.
const getVListChildrenAndDepth = function(params: VListParam): {
children: (VListChild | VListElemAndShift)[] | VListChild[],
depth: number,
} {
if (params.positionType === "individualShift") {
const oldChildren = params.children;
const children: (VListChild | VListElemAndShift)[] = [oldChildren[0]];
// Add in kerns to the list of params.children to get each element to be
// shifted to the correct specified shift // shifted to the correct specified shift
depth = -oldChildren[0].shift - oldChildren[0].elem.depth; const depth = -oldChildren[0].shift - oldChildren[0].elem.depth;
currPos = depth; let currPos = depth;
for (i = 1; i < oldChildren.length; i++) { for (let i = 1; i < oldChildren.length; i++) {
const diff = -oldChildren[i].shift - currPos - const diff = -oldChildren[i].shift - currPos -
oldChildren[i].elem.depth; oldChildren[i].elem.depth;
const size = diff - const size = diff -
@@ -320,30 +387,50 @@ const makeVList = function({positionType, positionData, children}, options) {
currPos = currPos + diff; currPos = currPos + diff;
children.push({type: "kern", size: size}); children.push({type: "kern", size});
children.push(oldChildren[i]); children.push(oldChildren[i]);
} }
} else if (positionType === "top") {
return {children, depth};
}
let depth;
if (params.positionType === "top") {
// We always start at the bottom, so calculate the bottom by adding up // We always start at the bottom, so calculate the bottom by adding up
// all the sizes // all the sizes
let bottom = positionData; let bottom = params.positionData;
for (i = 0; i < children.length; i++) { for (const child of params.children) {
if (children[i].type === "kern") { bottom -= child.type === "kern"
bottom -= children[i].size; ? child.size
} else { : child.elem.height + child.elem.depth;
bottom -= children[i].elem.height + children[i].elem.depth;
}
} }
depth = bottom; depth = bottom;
} else if (positionType === "bottom") { } else if (params.positionType === "bottom") {
depth = -positionData; depth = -params.positionData;
} else if (positionType === "shift") {
depth = -children[0].elem.depth - positionData;
} else if (positionType === "firstBaseline") {
depth = -children[0].elem.depth;
} else { } else {
depth = 0; const firstChild = params.children[0];
if (firstChild.type !== "elem") {
throw new Error('First child must have type "elem".');
}
if (params.positionType === "shift") {
depth = -firstChild.elem.depth - params.positionData;
} else if (params.positionType === "firstBaseline") {
depth = -firstChild.elem.depth;
} else {
throw new Error(`Invalid positionType ${params.positionType}.`);
}
} }
return {children: params.children, depth};
};
/**
* Makes a vertical list by stacking elements and kerns on top of each other.
* Allows for many different ways of specifying the positioning method.
*
* See VListParam documentation above.
*/
const makeVList = function(params: VListParam, options: Options): domTree.span {
const {children, depth} = getVListChildrenAndDepth(params);
// Create a strut that is taller than any list item. The strut is added to // Create a strut that is taller than any list item. The strut is added to
// each item, where it will determine the item's baseline. Since it has // each item, where it will determine the item's baseline. Since it has
@@ -353,10 +440,10 @@ const makeVList = function({positionType, positionData, children}, options) {
// be positioned precisely without worrying about font ascent and // be positioned precisely without worrying about font ascent and
// line-height. // line-height.
let pstrutSize = 0; let pstrutSize = 0;
for (i = 0; i < children.length; i++) { for (const child of children) {
if (children[i].type === "elem") { if (child.type === "elem") {
const child = children[i].elem; const elem = child.elem;
pstrutSize = Math.max(pstrutSize, child.maxFontSize, child.height); pstrutSize = Math.max(pstrutSize, elem.maxFontSize, elem.height);
} }
} }
pstrutSize += 2; pstrutSize += 2;
@@ -367,24 +454,24 @@ const makeVList = function({positionType, positionData, children}, options) {
const realChildren = []; const realChildren = [];
let minPos = depth; let minPos = depth;
let maxPos = depth; let maxPos = depth;
currPos = depth; let currPos = depth;
for (i = 0; i < children.length; i++) { for (const child of children) {
if (children[i].type === "kern") { if (child.type === "kern") {
currPos += children[i].size; currPos += child.size;
} else { } else {
const child = children[i].elem; const elem = child.elem;
const childWrap = makeSpan([], [pstrut, child]); const childWrap = makeSpan([], [pstrut, elem]);
childWrap.style.top = (-pstrutSize - currPos - child.depth) + "em"; childWrap.style.top = (-pstrutSize - currPos - elem.depth) + "em";
if (children[i].marginLeft) { if (child.marginLeft) {
childWrap.style.marginLeft = children[i].marginLeft; childWrap.style.marginLeft = child.marginLeft;
} }
if (children[i].marginRight) { if (child.marginRight) {
childWrap.style.marginRight = children[i].marginRight; childWrap.style.marginRight = child.marginRight;
} }
realChildren.push(childWrap); realChildren.push(childWrap);
currPos += child.height + child.depth; currPos += elem.height + elem.depth;
} }
minPos = Math.min(minPos, currPos); minPos = Math.min(minPos, currPos);
maxPos = Math.max(maxPos, currPos); maxPos = Math.max(maxPos, currPos);
@@ -422,7 +509,9 @@ const makeVList = function({positionType, positionData, children}, options) {
}; };
// Converts verb group into body string, dealing with \verb* form // Converts verb group into body string, dealing with \verb* form
const makeVerb = function(group, options) { const makeVerb = function(group: ParseNode, options: Options): string {
// TODO(#892): Make ParseNode type-safe and confirm `group.type` to guarantee
// that `group.value.body` is of type string.
let text = group.value.body; let text = group.value.body;
if (group.value.star) { if (group.value.star) {
text = text.replace(/ /g, '\u2423'); // Open Box text = text.replace(/ /g, '\u2423'); // Open Box
@@ -435,7 +524,7 @@ const makeVerb = function(group, options) {
// A map of spacing functions to their attributes, like size and corresponding // A map of spacing functions to their attributes, like size and corresponding
// CSS class // CSS class
const spacingFunctions = { const spacingFunctions: {[string]: {| size: string, className: string |}} = {
"\\qquad": { "\\qquad": {
size: "2em", size: "2em",
className: "qquad", className: "qquad",
@@ -472,7 +561,7 @@ const spacingFunctions = {
* - fontName: the "style" parameter to fontMetrics.getCharacterMetrics * - fontName: the "style" parameter to fontMetrics.getCharacterMetrics
*/ */
// A map between tex font commands an MathML mathvariant attribute values // A map between tex font commands an MathML mathvariant attribute values
const fontMap = { const fontMap: {[string]: {| variant: string, fontName: string |}} = {
// styles // styles
"mathbf": { "mathbf": {
variant: "bold", variant: "bold",
@@ -519,16 +608,16 @@ const fontMap = {
}; };
export default { export default {
fontMap: fontMap, fontMap,
makeSymbol: makeSymbol, makeSymbol,
mathsym: mathsym, mathsym,
makeSpan: makeSpan, makeSpan,
makeAnchor: makeAnchor, makeAnchor,
makeFragment: makeFragment, makeFragment,
makeVList: makeVList, makeVList,
makeOrd: makeOrd, makeOrd,
makeVerb: makeVerb, makeVerb,
tryCombineChars: tryCombineChars, tryCombineChars,
prependChildren: prependChildren, prependChildren,
spacingFunctions: spacingFunctions, spacingFunctions,
}; };

View File

@@ -333,11 +333,13 @@ const sqrtSvg = function(sqrtName, height, viewBoxHeight, options) {
} }
const pathNode = new domTree.pathNode(sqrtName, alternate); const pathNode = new domTree.pathNode(sqrtName, alternate);
// Note: 1000:1 ratio of viewBox to document em width. const svg = new domTree.svgNode([pathNode], {
const attributes = [["width", "400em"], ["height", height + "em"]]; // Note: 1000:1 ratio of viewBox to document em width.
attributes.push(["viewBox", "0 0 400000 " + viewBoxHeight]); "width": "400em",
attributes.push(["preserveAspectRatio", "xMinYMin slice"]); "height": height + "em",
const svg = new domTree.svgNode([pathNode], attributes); "viewBox": "0 0 400000 " + viewBoxHeight,
"preserveAspectRatio": "xMinYMin slice",
});
return buildCommon.makeSpan(["hide-tail"], [svg], options); return buildCommon.makeSpan(["hide-tail"], [svg], options);
}; };

View File

@@ -1,3 +1,4 @@
// @flow
/** /**
* These objects store the data about the DOM nodes we create, as well as some * These objects store the data about the DOM nodes we create, as well as some
* extra data. They can then be transformed into real DOM nodes with the * extra data. They can then be transformed into real DOM nodes with the
@@ -10,12 +11,13 @@
import {cjkRegex, hangulRegex} from "./unicodeRegexes"; import {cjkRegex, hangulRegex} from "./unicodeRegexes";
import utils from "./utils"; import utils from "./utils";
import svgGeometry from "./svgGeometry"; import svgGeometry from "./svgGeometry";
import type Options from "./Options";
/** /**
* Create an HTML className based on a list of classes. In addition to joining * Create an HTML className based on a list of classes. In addition to joining
* with spaces, we also remove null or empty classes. * with spaces, we also remove null or empty classes.
*/ */
const createClass = function(classes) { const createClass = function(classes: string[]): string {
classes = classes.slice(); classes = classes.slice();
for (let i = classes.length - 1; i >= 0; i--) { for (let i = classes.length - 1; i >= 0; i--) {
if (!classes[i]) { if (!classes[i]) {
@@ -26,13 +28,46 @@ const createClass = function(classes) {
return classes.join(" "); return classes.join(" ");
}; };
// To ensure that all nodes have compatible signatures for these methods.
interface VirtualDomNode {
toNode(): Node;
toMarkup(): string;
}
export interface CombinableDomNode extends VirtualDomNode {
tryCombine(sibling: CombinableDomNode): boolean;
}
/**
* All `DomChildNode`s MUST have `height`, `depth`, and `maxFontSize` numeric
* fields.
*
* `DomChildNode` is not defined as an interface since `documentFragment` also
* has these fields but should not be considered a `DomChildNode`.
*/
export type DomChildNode = span | anchor | svgNode | symbolNode;
export type SvgChildNode = pathNode | lineNode;
/** /**
* This node represents a span node, with a className, a list of children, and * This node represents a span node, with a className, a list of children, and
* an inline style. It also contains information about its height, depth, and * an inline style. It also contains information about its height, depth, and
* maxFontSize. * maxFontSize.
*/ */
class span { class span implements CombinableDomNode {
constructor(classes, children, options) { classes: string[];
children: DomChildNode[];
height: number;
depth: number;
maxFontSize: number;
style: {[string]: string};
attributes: {[string]: string};
constructor(
classes?: string[],
children?: DomChildNode[],
options?: Options,
) {
this.classes = classes || []; this.classes = classes || [];
this.children = children || []; this.children = children || [];
this.height = 0; this.height = 0;
@@ -44,8 +79,9 @@ class span {
if (options.style.isTight()) { if (options.style.isTight()) {
this.classes.push("mtight"); this.classes.push("mtight");
} }
if (options.getColor()) { const color = options.getColor();
this.style.color = options.getColor(); if (color) {
this.style.color = color;
} }
} }
} }
@@ -55,18 +91,18 @@ class span {
* browsers support attributes the same, and having too many custom attributes * browsers support attributes the same, and having too many custom attributes
* is probably bad. * is probably bad.
*/ */
setAttribute(attribute, value) { setAttribute(attribute: string, value: string) {
this.attributes[attribute] = value; this.attributes[attribute] = value;
} }
tryCombine(sibling) { tryCombine(sibling: CombinableDomNode): boolean {
return false; return false;
} }
/** /**
* Convert the span into an HTML node * Convert the span into an HTML node
*/ */
toNode() { toNode(): HTMLSpanElement {
const span = document.createElement("span"); const span = document.createElement("span");
// Apply the class // Apply the class
@@ -75,6 +111,7 @@ class span {
// Apply inline styles // Apply inline styles
for (const style in this.style) { for (const style in this.style) {
if (Object.prototype.hasOwnProperty.call(this.style, style)) { if (Object.prototype.hasOwnProperty.call(this.style, style)) {
// $FlowFixMe Flow doesn't seem to understand span.style's type.
span.style[style] = this.style[style]; span.style[style] = this.style[style];
} }
} }
@@ -97,7 +134,7 @@ class span {
/** /**
* Convert the span into an HTML markup string * Convert the span into an HTML markup string
*/ */
toMarkup() { toMarkup(): string {
let markup = "<span"; let markup = "<span";
// Add the class // Add the class
@@ -147,23 +184,36 @@ class span {
* a list of children, and an inline style. It also contains information about its * a list of children, and an inline style. It also contains information about its
* height, depth, and maxFontSize. * height, depth, and maxFontSize.
*/ */
class anchor { class anchor implements CombinableDomNode {
constructor(href, classes, children, options) { href: string;
this.href = href || ""; classes: string[];
this.classes = classes || []; children: DomChildNode[];
this.children = children || []; height: number;
depth: number;
maxFontSize: number;
style: {[string]: string};
attributes: {[string]: string};
constructor(
href: string,
classes: string[],
children: DomChildNode[],
options: Options,
) {
this.href = href;
this.classes = classes;
this.children = children;
this.height = 0; this.height = 0;
this.depth = 0; this.depth = 0;
this.maxFontSize = 0; this.maxFontSize = 0;
this.style = {}; this.style = {};
this.attributes = {}; this.attributes = {};
if (options) { if (options.style.isTight()) {
if (options.style.isTight()) { this.classes.push("mtight");
this.classes.push("mtight"); }
} const color = options.getColor();
if (options.getColor()) { if (color) {
this.style.color = options.getColor(); this.style.color = color;
}
} }
} }
@@ -172,18 +222,18 @@ class anchor {
* browsers support attributes the same, and having too many custom attributes * browsers support attributes the same, and having too many custom attributes
* is probably bad. * is probably bad.
*/ */
setAttribute(attribute, value) { setAttribute(attribute: string, value: string) {
this.attributes[attribute] = value; this.attributes[attribute] = value;
} }
tryCombine(sibling) { tryCombine(sibling: CombinableDomNode): boolean {
return false; return false;
} }
/** /**
* Convert the anchor into an HTML node * Convert the anchor into an HTML node
*/ */
toNode() { toNode(): HTMLAnchorElement {
const a = document.createElement("a"); const a = document.createElement("a");
// Apply the href // Apply the href
@@ -197,6 +247,7 @@ class anchor {
// Apply inline styles // Apply inline styles
for (const style in this.style) { for (const style in this.style) {
if (Object.prototype.hasOwnProperty.call(this.style, style)) { if (Object.prototype.hasOwnProperty.call(this.style, style)) {
// $FlowFixMe Flow doesn't seem to understand a.style's type.
a.style[style] = this.style[style]; a.style[style] = this.style[style];
} }
} }
@@ -219,7 +270,7 @@ class anchor {
/** /**
* Convert the a into an HTML markup string * Convert the a into an HTML markup string
*/ */
toMarkup() { toMarkup(): string {
let markup = "<a"; let markup = "<a";
// Add the href // Add the href
@@ -269,8 +320,13 @@ class anchor {
* contains children and doesn't have any HTML properties. It also keeps track * contains children and doesn't have any HTML properties. It also keeps track
* of a height, depth, and maxFontSize. * of a height, depth, and maxFontSize.
*/ */
class documentFragment { class documentFragment implements VirtualDomNode {
constructor(children) { children: DomChildNode[];
height: number;
depth: number;
maxFontSize: number;
constructor(children?: DomChildNode[]) {
this.children = children || []; this.children = children || [];
this.height = 0; this.height = 0;
this.depth = 0; this.depth = 0;
@@ -280,7 +336,7 @@ class documentFragment {
/** /**
* Convert the fragment into a node * Convert the fragment into a node
*/ */
toNode() { toNode(): Node {
// Create a fragment // Create a fragment
const frag = document.createDocumentFragment(); const frag = document.createDocumentFragment();
@@ -295,7 +351,7 @@ class documentFragment {
/** /**
* Convert the fragment into HTML markup * Convert the fragment into HTML markup
*/ */
toMarkup() { toMarkup(): string {
let markup = ""; let markup = "";
// Simply concatenate the markup for the children together // Simply concatenate the markup for the children together
@@ -320,9 +376,26 @@ const iCombinations = {
* to a single text node, or a span with a single text node in it, depending on * to a single text node, or a span with a single text node in it, depending on
* whether it has CSS classes, styles, or needs italic correction. * whether it has CSS classes, styles, or needs italic correction.
*/ */
class symbolNode { class symbolNode implements CombinableDomNode {
constructor(value, height, depth, italic, skew, classes, style) { value: string;
this.value = value || ""; height: number;
depth: number;
italic: number;
skew: number;
maxFontSize: number;
classes: string[];
style: {[string]: string};
constructor(
value: string,
height?: number,
depth?: number,
italic?: number,
skew?: number,
classes?: string[],
style?: {[string]: string},
) {
this.value = value;
this.height = height || 0; this.height = height || 0;
this.depth = depth || 0; this.depth = depth || 0;
this.italic = italic || 0; this.italic = italic || 0;
@@ -335,11 +408,11 @@ class symbolNode {
// fonts to use. This allows us to render these characters with a serif // fonts to use. This allows us to render these characters with a serif
// font in situations where the browser would either default to a sans serif // font in situations where the browser would either default to a sans serif
// or render a placeholder character. // or render a placeholder character.
if (cjkRegex.test(value)) { if (cjkRegex.test(this.value)) {
// I couldn't find any fonts that contained Hangul as well as all of // I couldn't find any fonts that contained Hangul as well as all of
// the other characters we wanted to test there for it gets its own // the other characters we wanted to test there for it gets its own
// CSS class. // CSS class.
if (hangulRegex.test(value)) { if (hangulRegex.test(this.value)) {
this.classes.push('hangul_fallback'); this.classes.push('hangul_fallback');
} else { } else {
this.classes.push('cjk_fallback'); this.classes.push('cjk_fallback');
@@ -351,7 +424,7 @@ class symbolNode {
} }
} }
tryCombine(sibling) { tryCombine(sibling: CombinableDomNode): boolean {
if (!sibling if (!sibling
|| !(sibling instanceof symbolNode) || !(sibling instanceof symbolNode)
|| this.italic > 0 || this.italic > 0
@@ -383,7 +456,7 @@ class symbolNode {
* Creates a text node or span from a symbol node. Note that a span is only * Creates a text node or span from a symbol node. Note that a span is only
* created if it is needed. * created if it is needed.
*/ */
toNode() { toNode(): Node {
const node = document.createTextNode(this.value); const node = document.createTextNode(this.value);
let span = null; let span = null;
@@ -400,6 +473,7 @@ class symbolNode {
for (const style in this.style) { for (const style in this.style) {
if (this.style.hasOwnProperty(style)) { if (this.style.hasOwnProperty(style)) {
span = span || document.createElement("span"); span = span || document.createElement("span");
// $FlowFixMe Flow doesn't seem to understand span.style's type.
span.style[style] = this.style[style]; span.style[style] = this.style[style];
} }
} }
@@ -415,7 +489,7 @@ class symbolNode {
/** /**
* Creates markup for a symbol node. * Creates markup for a symbol node.
*/ */
toMarkup() { toMarkup(): string {
// TODO(alpert): More duplication than I'd like from // TODO(alpert): More duplication than I'd like from
// span.prototype.toMarkup and symbolNode.prototype.toNode... // span.prototype.toMarkup and symbolNode.prototype.toNode...
let needsSpan = false; let needsSpan = false;
@@ -460,20 +534,31 @@ class symbolNode {
/** /**
* SVG nodes are used to render stretchy wide elements. * SVG nodes are used to render stretchy wide elements.
*/ */
class svgNode { class svgNode implements VirtualDomNode {
constructor(children, attributes) { children: SvgChildNode[];
attributes: {[string]: string};
// Required for all `DomChildNode`s. Are always 0 for svgNode.
height: number;
depth: number;
maxFontSize: number;
constructor(children?: SvgChildNode[], attributes?: {[string]: string}) {
this.children = children || []; this.children = children || [];
this.attributes = attributes || []; this.attributes = attributes || {};
this.height = 0;
this.depth = 0;
this.maxFontSize = 0;
} }
toNode() { toNode(): Node {
const svgNS = "http://www.w3.org/2000/svg"; const svgNS = "http://www.w3.org/2000/svg";
const node = document.createElementNS(svgNS, "svg"); const node = document.createElementNS(svgNS, "svg");
// Apply attributes // Apply attributes
for (let i = 0; i < this.attributes.length; i++) { for (const attr in this.attributes) {
const [name, value] = this.attributes[i]; if (Object.prototype.hasOwnProperty.call(this.attributes, attr)) {
node.setAttribute(name, value); node.setAttribute(attr, this.attributes[attr]);
}
} }
for (let i = 0; i < this.children.length; i++) { for (let i = 0; i < this.children.length; i++) {
@@ -482,13 +567,14 @@ class svgNode {
return node; return node;
} }
toMarkup() { toMarkup(): string {
let markup = "<svg"; let markup = "<svg";
// Apply attributes // Apply attributes
for (let i = 0; i < this.attributes.length; i++) { for (const attr in this.attributes) {
const [name, value] = this.attributes[i]; if (Object.prototype.hasOwnProperty.call(this.attributes, attr)) {
markup += ` ${name}='${value}'`; markup += ` ${attr}='${this.attributes[attr]}'`;
}
} }
markup += ">"; markup += ">";
@@ -504,58 +590,65 @@ class svgNode {
} }
} }
class pathNode { class pathNode implements VirtualDomNode {
constructor(pathName, alternate) { pathName: string;
alternate: ?string;
constructor(pathName: string, alternate?: string) {
this.pathName = pathName; this.pathName = pathName;
this.alternate = alternate; // Used only for tall \sqrt this.alternate = alternate; // Used only for tall \sqrt
} }
toNode() { toNode(): Node {
const svgNS = "http://www.w3.org/2000/svg"; const svgNS = "http://www.w3.org/2000/svg";
const node = document.createElementNS(svgNS, "path"); const node = document.createElementNS(svgNS, "path");
if (this.pathName !== "sqrtTall") { if (this.alternate) {
node.setAttribute("d", svgGeometry.path[this.pathName]);
} else {
node.setAttribute("d", this.alternate); node.setAttribute("d", this.alternate);
} else {
node.setAttribute("d", svgGeometry.path[this.pathName]);
} }
return node; return node;
} }
toMarkup() { toMarkup(): string {
if (this.pathName !== "sqrtTall") { if (this.alternate) {
return `<path d='${svgGeometry.path[this.pathName]}'/>`;
} else {
return `<path d='${this.alternate}'/>`; return `<path d='${this.alternate}'/>`;
} else {
return `<path d='${svgGeometry.path[this.pathName]}'/>`;
} }
} }
} }
class lineNode { class lineNode implements VirtualDomNode {
constructor(attributes) { attributes: {[string]: string};
this.attributes = attributes || [];
constructor(attributes?: {[string]: string}) {
this.attributes = attributes || {};
} }
toNode() { toNode(): Node {
const svgNS = "http://www.w3.org/2000/svg"; const svgNS = "http://www.w3.org/2000/svg";
const node = document.createElementNS(svgNS, "line"); const node = document.createElementNS(svgNS, "line");
// Apply attributes // Apply attributes
for (let i = 0; i < this.attributes.length; i++) { for (const attr in this.attributes) {
const [name, value] = this.attributes[i]; if (Object.prototype.hasOwnProperty.call(this.attributes, attr)) {
node.setAttribute(name, value); node.setAttribute(attr, this.attributes[attr]);
}
} }
return node; return node;
} }
toMarkup() { toMarkup(): string {
let markup = "<line"; let markup = "<line";
for (let i = 0; i < this.attributes.length; i++) { for (const attr in this.attributes) {
const [name, value] = this.attributes[i]; if (Object.prototype.hasOwnProperty.call(this.attributes, attr)) {
markup += ` ${name}='${value}'`; markup += ` ${attr}='${this.attributes[attr]}'`;
}
} }
markup += "/>"; markup += "/>";
@@ -565,11 +658,11 @@ class lineNode {
} }
export default { export default {
span: span, span,
anchor: anchor, anchor,
documentFragment: documentFragment, documentFragment,
symbolNode: symbolNode, symbolNode,
svgNode: svgNode, svgNode,
pathNode: pathNode, pathNode,
lineNode: lineNode, lineNode,
}; };

View File

@@ -166,6 +166,7 @@ defineFunction({
positionType: "individualShift", positionType: "individualShift",
children: [ children: [
{type: "elem", elem: denomm, shift: denomShift}, {type: "elem", elem: denomm, shift: denomShift},
// $FlowFixMe `rule` cannot be `null` here.
{type: "elem", elem: rule, shift: midShift}, {type: "elem", elem: rule, shift: midShift},
{type: "elem", elem: numerm, shift: -numShift}, {type: "elem", elem: numerm, shift: -numShift},
], ],

View File

@@ -90,38 +90,58 @@ const htmlBuilder = (group, options) => {
// in a new span so it is an inline, and works. // in a new span so it is an inline, and works.
base = buildCommon.makeSpan([], [base]); base = buildCommon.makeSpan([], [base]);
let supm; let sub;
let supKern; let sup;
let subm = {height: 0, depth: 0}; // Make flow happy
let subKern;
let newOptions;
// We manually have to handle the superscripts and subscripts. This, // We manually have to handle the superscripts and subscripts. This,
// aside from the kern calculations, is copied from supsub. // aside from the kern calculations, is copied from supsub.
if (supGroup) { if (supGroup) {
newOptions = options.havingStyle(style.sup()); const elem = html.buildGroup(
supm = html.buildGroup(supGroup, newOptions, options); supGroup, options.havingStyle(style.sup()), options);
supKern = Math.max( sup = {
options.fontMetrics().bigOpSpacing1, elem,
options.fontMetrics().bigOpSpacing3 - supm.depth); kern: Math.max(
options.fontMetrics().bigOpSpacing1,
options.fontMetrics().bigOpSpacing3 - elem.depth),
};
} }
if (subGroup) { if (subGroup) {
newOptions = options.havingStyle(style.sub()); const elem = html.buildGroup(
subm = html.buildGroup(subGroup, newOptions, options); subGroup, options.havingStyle(style.sub()), options);
subKern = Math.max( sub = {
options.fontMetrics().bigOpSpacing2, elem,
options.fontMetrics().bigOpSpacing4 - subm.height); kern: Math.max(
options.fontMetrics().bigOpSpacing2,
options.fontMetrics().bigOpSpacing4 - elem.height),
};
} }
// Build the final group as a vlist of the possible subscript, base, // Build the final group as a vlist of the possible subscript, base,
// and possible superscript. // and possible superscript.
let finalGroup; let finalGroup;
let top; if (sup && sub) {
let bottom; const bottom = options.fontMetrics().bigOpSpacing5 +
if (!supGroup) { sub.elem.height + sub.elem.depth +
top = base.height - baseShift; sub.kern +
base.depth + baseShift;
finalGroup = buildCommon.makeVList({
positionType: "bottom",
positionData: bottom,
children: [
{type: "kern", size: options.fontMetrics().bigOpSpacing5},
{type: "elem", elem: sub.elem, marginLeft: -slant + "em"},
{type: "kern", size: sub.kern},
{type: "elem", elem: base},
{type: "kern", size: sup.kern},
{type: "elem", elem: sup.elem, marginLeft: slant + "em"},
{type: "kern", size: options.fontMetrics().bigOpSpacing5},
],
}, options);
} else if (sub) {
const top = base.height - baseShift;
// Shift the limits by the slant of the symbol. Note // Shift the limits by the slant of the symbol. Note
// that we are supposed to shift the limits by 1/2 of the slant, // that we are supposed to shift the limits by 1/2 of the slant,
@@ -132,48 +152,29 @@ const htmlBuilder = (group, options) => {
positionData: top, positionData: top,
children: [ children: [
{type: "kern", size: options.fontMetrics().bigOpSpacing5}, {type: "kern", size: options.fontMetrics().bigOpSpacing5},
{type: "elem", elem: subm, marginLeft: -slant + "em"}, {type: "elem", elem: sub.elem, marginLeft: -slant + "em"},
{type: "kern", size: subKern}, {type: "kern", size: sub.kern},
{type: "elem", elem: base}, {type: "elem", elem: base},
], ],
}, options); }, options);
} else if (!subGroup) { } else if (sup) {
bottom = base.depth + baseShift; const bottom = base.depth + baseShift;
finalGroup = buildCommon.makeVList({ finalGroup = buildCommon.makeVList({
positionType: "bottom", positionType: "bottom",
positionData: bottom, positionData: bottom,
children: [ children: [
{type: "elem", elem: base}, {type: "elem", elem: base},
{type: "kern", size: supKern}, {type: "kern", size: sup.kern},
{type: "elem", elem: supm, marginLeft: slant + "em"}, {type: "elem", elem: sup.elem, marginLeft: slant + "em"},
{type: "kern", size: options.fontMetrics().bigOpSpacing5}, {type: "kern", size: options.fontMetrics().bigOpSpacing5},
], ],
}, options); }, options);
} else if (!supGroup && !subGroup) { } else {
// This case probably shouldn't occur (this would mean the // This case probably shouldn't occur (this would mean the
// supsub was sending us a group with no superscript or // supsub was sending us a group with no superscript or
// subscript) but be safe. // subscript) but be safe.
return base; return base;
} else {
bottom = options.fontMetrics().bigOpSpacing5 +
subm.height + subm.depth +
subKern +
base.depth + baseShift;
finalGroup = buildCommon.makeVList({
positionType: "bottom",
positionData: bottom,
children: [
{type: "kern", size: options.fontMetrics().bigOpSpacing5},
{type: "elem", elem: subm, marginLeft: -slant + "em"},
{type: "kern", size: subKern},
{type: "elem", elem: base},
{type: "kern", size: supKern},
{type: "elem", elem: supm, marginLeft: slant + "em"},
{type: "kern", size: options.fontMetrics().bigOpSpacing5},
],
}, options);
} }
return buildCommon.makeSpan( return buildCommon.makeSpan(

View File

@@ -12,7 +12,6 @@ import utils from "./utils";
import type Options from "./Options"; import type Options from "./Options";
import type ParseNode from "./ParseNode"; import type ParseNode from "./ParseNode";
import type {span} from "./domTree";
const stretchyCodePoint: {[string]: string} = { const stretchyCodePoint: {[string]: string} = {
widehat: "^", widehat: "^",
@@ -154,98 +153,107 @@ const groupLength = function(arg: ParseNode): number {
} }
}; };
const svgSpan = function(group: ParseNode, options: Options): span { const svgSpan = function(group: ParseNode, options: Options): domTree.span {
// Create a span with inline SVG for the element. // Create a span with inline SVG for the element.
const label = group.value.label.substr(1); function buildSvgSpan_(): {
let attributes = []; span: domTree.span,
let height; minWidth: number,
let viewBoxWidth = 400000; // default height: number,
let viewBoxHeight = 0; } {
let minWidth = 0; let viewBoxWidth = 400000; // default
let path; const label = group.value.label.substr(1);
let paths; if (utils.contains(["widehat", "widetilde", "utilde"], label)) {
let pathName; // There are four SVG images available for each function.
let svgNode; // Choose a taller image when there are more characters.
let span; const numChars = groupLength(group.value.base);
let viewBoxHeight;
let pathName;
let height;
if (utils.contains(["widehat", "widetilde", "utilde"], label)) { if (numChars > 5) {
// There are four SVG images available for each function. viewBoxHeight = (label === "widehat" ? 420 : 312);
// Choose a taller image when there are more characters. viewBoxWidth = (label === "widehat" ? 2364 : 2340);
const numChars = groupLength(group.value.base); // Next get the span height, in 1000 ems
let viewBoxHeight; height = (label === "widehat" ? 0.42 : 0.34);
pathName = (label === "widehat" ? "widehat" : "tilde") + "4";
if (numChars > 5) { } else {
viewBoxHeight = (label === "widehat" ? 420 : 312); const imgIndex = [1, 1, 2, 2, 3, 3][numChars];
viewBoxWidth = (label === "widehat" ? 2364 : 2340); if (label === "widehat") {
// Next get the span height, in 1000 ems viewBoxWidth = [0, 1062, 2364, 2364, 2364][imgIndex];
height = (label === "widehat" ? 0.42 : 0.34); viewBoxHeight = [0, 239, 300, 360, 420][imgIndex];
pathName = (label === "widehat" ? "widehat" : "tilde") + "4"; height = [0, 0.24, 0.3, 0.3, 0.36, 0.42][imgIndex];
pathName = "widehat" + imgIndex;
} else {
viewBoxWidth = [0, 600, 1033, 2339, 2340][imgIndex];
viewBoxHeight = [0, 260, 286, 306, 312][imgIndex];
height = [0, 0.26, 0.286, 0.3, 0.306, 0.34][imgIndex];
pathName = "tilde" + imgIndex;
}
}
const path = new domTree.pathNode(pathName);
const svgNode = new domTree.svgNode([path], {
"width": "100%",
"height": height + "em",
"viewBox": `0 0 ${viewBoxWidth} ${viewBoxHeight}`,
"preserveAspectRatio": "none",
});
return {
span: buildCommon.makeSpan([], [svgNode], options),
minWidth: 0,
height,
};
} else { } else {
const imgIndex = [1, 1, 2, 2, 3, 3][numChars]; const spans = [];
if (label === "widehat") {
viewBoxWidth = [0, 1062, 2364, 2364, 2364][imgIndex];
viewBoxHeight = [0, 239, 300, 360, 420][imgIndex];
height = [0, 0.24, 0.3, 0.3, 0.36, 0.42][imgIndex];
pathName = "widehat" + imgIndex;
} else {
viewBoxWidth = [0, 600, 1033, 2339, 2340][imgIndex];
viewBoxHeight = [0, 260, 286, 306, 312][imgIndex];
height = [0, 0.26, 0.286, 0.3, 0.306, 0.34][imgIndex];
pathName = "tilde" + imgIndex;
}
}
path = new domTree.pathNode(pathName);
attributes.push(["width", "100%"]);
attributes.push(["height", height + "em"]);
attributes.push(["viewBox", `0 0 ${viewBoxWidth} ${viewBoxHeight}`]);
attributes.push(["preserveAspectRatio", "none"]);
svgNode = new domTree.svgNode([path], attributes); const [paths, minWidth, viewBoxHeight, align1] = katexImagesData[label];
span = buildCommon.makeSpan([], [svgNode], options); const height = viewBoxHeight / 1000;
} else {
let widthClass;
let align;
const spans = [];
[paths, minWidth, viewBoxHeight, align] = katexImagesData[label];
const numSvgChildren = paths.length;
if (1 > numSvgChildren || numSvgChildren > 3) {
throw new Error(
`Correct katexImagesData or update code below to support
${numSvgChildren} children.`);
}
height = viewBoxHeight / 1000;
for (let i = 0; i < numSvgChildren; i++) {
path = new domTree.pathNode(paths[i]);
attributes = [["width", "400em"], ["height", height + "em"]];
attributes.push(["viewBox", `0 0 ${viewBoxWidth} ${viewBoxHeight}`]);
if (numSvgChildren === 2) {
widthClass = ["halfarrow-left", "halfarrow-right"][i];
align = ["xMinYMin", "xMaxYMin"][i];
} else if (numSvgChildren === 3) {
widthClass = ["brace-left", "brace-center", "brace-right"][i];
align = ["xMinYMin", "xMidYMin", "xMaxYMin"][i];
}
attributes.push(["preserveAspectRatio", align + " slice"]);
svgNode = new domTree.svgNode([path], attributes);
const numSvgChildren = paths.length;
let widthClasses;
let aligns;
if (numSvgChildren === 1) { if (numSvgChildren === 1) {
spans.push(buildCommon.makeSpan(["hide-tail"], [svgNode], options)); widthClasses = ["hide-tail"];
aligns = [align1];
} else if (numSvgChildren === 2) {
widthClasses = ["halfarrow-left", "halfarrow-right"];
aligns = ["xMinYMin", "xMaxYMin"];
} else if (numSvgChildren === 3) {
widthClasses = ["brace-left", "brace-center", "brace-right"];
aligns = ["xMinYMin", "xMidYMin", "xMaxYMin"];
} else { } else {
const span = buildCommon.makeSpan([widthClass], [svgNode], options); throw new Error(
span.style.height = height + "em"; `Correct katexImagesData or update code here to support
spans.push(span); ${numSvgChildren} children.`);
} }
}
span = numSvgChildren === 1 ? spans[0] : for (let i = 0; i < numSvgChildren; i++) {
buildCommon.makeSpan(["stretchy"], spans, options); const path = new domTree.pathNode(paths[i]);
}
const svgNode = new domTree.svgNode([path], {
"width": "400em",
"height": height + "em",
"viewBox": `0 0 ${viewBoxWidth} ${viewBoxHeight}`,
"preserveAspectRatio": aligns[i] + " slice",
});
const span =
buildCommon.makeSpan([widthClasses[i]], [svgNode], options);
if (numSvgChildren === 1) {
return {span, minWidth, height};
} else {
span.style.height = height + "em";
spans.push(span);
}
}
return {
span: buildCommon.makeSpan(["stretchy"], spans, options),
minWidth,
height,
};
}
} // buildSvgSpan_()
const {span, minWidth, height} = buildSvgSpan_();
// Note that we are returning span.depth = 0. // Note that we are returning span.depth = 0.
// Any adjustments relative to the baseline must be done in buildHTML. // Any adjustments relative to the baseline must be done in buildHTML.
@@ -259,11 +267,11 @@ const svgSpan = function(group: ParseNode, options: Options): span {
}; };
const encloseSpan = function( const encloseSpan = function(
inner: span, inner: domTree.span,
label: string, label: string,
pad: number, pad: number,
options: Options, options: Options,
): span { ): domTree.span {
// Return an image span for \cancel, \bcancel, \xcancel, or \fbox // Return an image span for \cancel, \bcancel, \xcancel, or \fbox
let img; let img;
const totalHeight = inner.height + inner.depth + 2 * pad; const totalHeight = inner.height + inner.depth + 2 * pad;
@@ -271,8 +279,11 @@ const encloseSpan = function(
if (/(fbox)|(color)/.test(label)) { if (/(fbox)|(color)/.test(label)) {
img = buildCommon.makeSpan(["stretchy", label], [], options); img = buildCommon.makeSpan(["stretchy", label], [], options);
if (label === "fbox" && options.color) { if (label === "fbox") {
img.style.borderColor = options.getColor(); const color = options.color && options.getColor();
if (color) {
img.style.borderColor = color;
}
} }
} else { } else {
@@ -280,31 +291,33 @@ const encloseSpan = function(
// Since \cancel's SVG is inline and it omits the viewBox attribute, // Since \cancel's SVG is inline and it omits the viewBox attribute,
// its stroke-width will not vary with span area. // its stroke-width will not vary with span area.
let attributes = [["x1", "0"]]; let attributes: {[string]: string} = {"x1": "0"};
const lines = []; const lines = [];
if (label !== "cancel") { if (label !== "cancel") {
attributes.push(["y1", "0"]); attributes["y1"] = "0";
attributes.push(["x2", "100%"]); attributes["x2"] = "100%";
attributes.push(["y2", "100%"]); attributes["y2"] = "100%";
attributes.push(["stroke-width", "0.046em"]); attributes["stroke-width"] = "0.046em";
lines.push(new domTree.lineNode(attributes)); lines.push(new domTree.lineNode(attributes));
} }
if (label === "xcancel") { if (label === "xcancel") {
attributes = [["x1", "0"]]; // start a second line. attributes = {"x1": "0"}; // start a second line.
} }
if (label !== "bcancel") { if (label !== "bcancel") {
attributes.push(["y1", "100%"]); attributes["y1"] = "100%";
attributes.push(["x2", "100%"]); attributes["x2"] = "100%";
attributes.push(["y2", "0"]); attributes["y2"] = "0";
attributes.push(["stroke-width", "0.046em"]); attributes["stroke-width"] = "0.046em";
lines.push(new domTree.lineNode(attributes)); lines.push(new domTree.lineNode(attributes));
} }
attributes = [["width", "100%"], ["height", totalHeight + "em"]]; const svgNode = new domTree.svgNode(lines, {
const svgNode = new domTree.svgNode(lines, attributes); "width": "100%",
"height": totalHeight + "em",
});
img = buildCommon.makeSpan([], [svgNode], options); img = buildCommon.makeSpan([], [svgNode], options);
} }
@@ -315,22 +328,21 @@ const encloseSpan = function(
return img; return img;
}; };
const ruleSpan = function(className: string, options: Options): span { const ruleSpan = function(className: string, options: Options): domTree.span {
// Get a big square image. The parent span will hide the overflow. // Get a big square image. The parent span will hide the overflow.
const pathNode = new domTree.pathNode('bigRule'); const pathNode = new domTree.pathNode('bigRule');
const attributes = [ const svg = new domTree.svgNode([pathNode], {
["width", "400em"], "width": "400em",
["height", "400em"], "height": "400em",
["viewBox", "0 0 400000 400000"], "viewBox": "0 0 400000 400000",
["preserveAspectRatio", "xMinYMin slice"], "preserveAspectRatio": "xMinYMin slice",
]; });
const svg = new domTree.svgNode([pathNode], attributes);
return buildCommon.makeSpan([className, "hide-tail"], [svg], options); return buildCommon.makeSpan([className, "hide-tail"], [svg], options);
}; };
export default { export default {
encloseSpan: encloseSpan, encloseSpan,
mathMLnode: mathMLnode, mathMLnode,
ruleSpan: ruleSpan, ruleSpan,
svgSpan: svgSpan, svgSpan,
}; };