mirror of
https://github.com/Smaug123/KaTeX
synced 2025-10-10 13:38:39 +00:00
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:
@@ -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,
|
||||||
};
|
};
|
||||||
|
@@ -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);
|
||||||
};
|
};
|
||||||
|
245
src/domTree.js
245
src/domTree.js
@@ -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,
|
||||||
};
|
};
|
||||||
|
@@ -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},
|
||||||
],
|
],
|
||||||
|
@@ -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(
|
||||||
|
236
src/stretchy.js
236
src/stretchy.js
@@ -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,
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user