Remove codes that require expensive polyfill (#1469)

* Revert "Cleanup domTree.js to re-use code (#1305)"

This reverts commit 9bb48b83f1.

* Cleanup domTree.js to re-use code

* Extract common constructor/methods into functions

* Remove for...of

* Added eslint rule to disallow for...of

* Remove array destructuring

* Added eslint rule to disallow array destructuring

* Add eslint rule to disallow class inheritance

* Remove Object.keys and Object.assign

* Do not polyfill Object.freeze

* Babel: enable loose mode

* Undo disabling `linebreak-style`

* Move `children` initialization out of `initNode`

* Blacklist files for `no-restricted-syntax`

* Revert "Remove array destructuring"

This reverts commit c9d52c2db31c68cca77fea6ad774ee58b0632ff3.
This commit is contained in:
ylemkimon
2018-07-22 10:57:07 +09:00
committed by Kevin Barabash
parent 8eed4e2795
commit c9947220b6
11 changed files with 189 additions and 160 deletions

View File

@@ -1,7 +1,8 @@
{
"presets": [
["es2015", {
"modules": false
"modules": false,
"loose": true
}],
"flow"
],

View File

@@ -70,6 +70,13 @@
"valid-jsdoc": 0,
"require-jsdoc": 0
},
"overrides": [{
"files": ["katex.js", "src/**/*.js"],
"excludedFiles": "unicodeMake.js",
"rules": {
"no-restricted-syntax": [2, "ForOfStatement", "ClassDeclaration[superClass]", "ClassExpression[superClass]"]
}
}],
"env": {
"es6": true,
"node": true,

View File

@@ -46,11 +46,13 @@ export default class Namespace<Value> {
"to pop global namespace; please report this as a bug");
}
const undefs = this.undefStack.pop();
for (const undef of Object.getOwnPropertyNames(undefs)) {
if (undefs[undef] === undefined) {
delete this.current[undef];
} else {
this.current[undef] = undefs[undef];
for (const undef in undefs) {
if (undefs.hasOwnProperty(undef)) {
if (undefs[undef] === undefined) {
delete this.current[undef];
} else {
this.current[undef] = undefs[undef];
}
}
}
}

View File

@@ -14,7 +14,8 @@ export default class SourceLocation {
this.lexer = lexer;
this.start = start;
this.end = end;
Object.freeze(this); // Immutable to allow sharing in range().
// $FlowFixMe, do not polyfill
Object["freeze"](this); // Immutable to allow sharing in range().
}
/**
@@ -40,4 +41,3 @@ export default class SourceLocation {
}
}
}

View File

@@ -315,7 +315,8 @@ const sizeElementFromChildren = function(
let depth = 0;
let maxFontSize = 0;
for (const child of elem.children) {
for (let i = 0; i < elem.children.length; i++) {
const child = elem.children[i];
if (child.height > height) {
height = child.height;
}
@@ -490,7 +491,8 @@ const getVListChildrenAndDepth = function(params: VListParam): {
// We always start at the bottom, so calculate the bottom by adding up
// all the sizes
let bottom = params.positionData;
for (const child of params.children) {
for (let i = 0; i < params.children.length; i++) {
const child = params.children[i];
bottom -= child.type === "kern"
? child.size
: child.elem.height + child.elem.depth;
@@ -531,7 +533,8 @@ const makeVList = function(params: VListParam, options: Options): DomSpan {
// be positioned precisely without worrying about font ascent and
// line-height.
let pstrutSize = 0;
for (const child of children) {
for (let i = 0; i < children.length; i++) {
const child = children[i];
if (child.type === "elem") {
const elem = child.elem;
pstrutSize = Math.max(pstrutSize, elem.maxFontSize, elem.height);
@@ -546,7 +549,8 @@ const makeVList = function(params: VListParam, options: Options): DomSpan {
let minPos = depth;
let maxPos = depth;
let currPos = depth;
for (const child of children) {
for (let i = 0; i < children.length; i++) {
const child = children[i];
if (child.type === "kern") {
currPos += child.size;
} else {

View File

@@ -7,6 +7,9 @@
* work with the DOM.
*
* Similar functions for working with MathML nodes exist in mathMLTree.js.
*
* TODO: refactor `span` and `anchor` into common superclass when
* target environments support class inheritance
*/
import {scriptFromCodepoint} from "./unicodeScripts";
import utils from "./utils";
@@ -25,6 +28,103 @@ const createClass = function(classes: string[]): string {
return classes.filter(cls => cls).join(" ");
};
const initNode = function(
classes?: string[],
options?: Options,
style?: CssStyle,
) {
this.classes = classes || [];
this.attributes = {};
this.height = 0;
this.depth = 0;
this.maxFontSize = 0;
this.style = style || {};
if (options) {
if (options.style.isTight()) {
this.classes.push("mtight");
}
const color = options.getColor();
if (color) {
this.style.color = color;
}
}
};
/**
* Convert into an HTML node
*/
const toNode = function(tagName: string): HTMLElement {
const node = document.createElement(tagName);
// Apply the class
node.className = createClass(this.classes);
// Apply inline styles
for (const style in this.style) {
if (this.style.hasOwnProperty(style)) {
// $FlowFixMe Flow doesn't seem to understand span.style's type.
node.style[style] = this.style[style];
}
}
// Apply attributes
for (const attr in this.attributes) {
if (this.attributes.hasOwnProperty(attr)) {
node.setAttribute(attr, this.attributes[attr]);
}
}
// Append the children, also as HTML nodes
for (let i = 0; i < this.children.length; i++) {
node.appendChild(this.children[i].toNode());
}
return node;
};
/**
* Convert into an HTML markup string
*/
const toMarkup = function(tagName: string): string {
let markup = `<${tagName}`;
// Add the class
if (this.classes.length) {
markup += ` class="${utils.escape(createClass(this.classes))}"`;
}
let styles = "";
// Add the styles, after hyphenation
for (const style in this.style) {
if (this.style.hasOwnProperty(style)) {
styles += `${utils.hyphenate(style)}:${this.style[style]};`;
}
}
if (styles) {
markup += ` style="${utils.escape(styles)}"`;
}
// Add the attributes
for (const attr in this.attributes) {
if (this.attributes.hasOwnProperty(attr)) {
markup += ` ${attr}="${utils.escape(this.attributes[attr])}"`;
}
}
markup += ">";
// Add the markup of the children, also as markup
for (let i = 0; i < this.children.length; i++) {
markup += this.children[i].toMarkup();
}
markup += `</${tagName}>`;
return markup;
};
export type CssStyle = {[name: string]: string};
export interface HtmlDomNode extends VirtualNode {
@@ -47,8 +147,16 @@ export type SvgChildNode = pathNode | lineNode;
export type documentFragment = tree.documentFragment<HtmlDomNode>;
export class HtmlDomContainer<ChildType: VirtualNode>
implements HtmlDomNode {
/**
* 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
* maxFontSize.
*
* Represents two types with different uses: SvgSpan to wrap an SVG and DomSpan
* otherwise. This typesafety is important when HTML builders access a span's
* children.
*/
class span<ChildType: VirtualNode> implements HtmlDomNode {
children: ChildType[];
attributes: {[string]: string};
classes: string[];
@@ -64,26 +172,12 @@ export class HtmlDomContainer<ChildType: VirtualNode>
options?: Options,
style?: CssStyle,
) {
this.classes = classes || [];
initNode.call(this, classes, options, style);
this.children = children || [];
this.attributes = {};
this.height = 0;
this.depth = 0;
this.maxFontSize = 0;
this.style = Object.assign({}, style);
if (options) {
if (options.style.isTight()) {
this.classes.push("mtight");
}
const color = options.getColor();
if (color) {
this.style.color = color;
}
}
}
/**
* Sets an arbitrary attribute on the node. Warning: use this wisely. Not
* Sets an arbitrary attribute on the span. Warning: use this wisely. Not
* all browsers support attributes the same, and having too many custom
* attributes is probably bad.
*/
@@ -104,119 +198,27 @@ export class HtmlDomContainer<ChildType: VirtualNode>
return false;
}
tagName(): string {
throw new Error("use of generic HtmlDomContainer tagName");
}
/**
* Convert into an HTML node
*/
toNode(): HTMLElement {
const node = document.createElement(this.tagName());
// Apply the class
node.className = createClass(this.classes);
// Apply inline styles
for (const style in this.style) {
if (Object.prototype.hasOwnProperty.call(this.style, style)) {
// $FlowFixMe Flow doesn't seem to understand node.style's type.
node.style[style] = this.style[style];
}
}
// Apply attributes
for (const attr in this.attributes) {
if (this.attributes.hasOwnProperty(attr)) {
node.setAttribute(attr, this.attributes[attr]);
}
}
// Append the children, also as HTML nodes
for (let i = 0; i < this.children.length; i++) {
node.appendChild(this.children[i].toNode());
}
return node;
return toNode.call(this, "span");
}
/**
* Convert into an HTML markup string
*/
toMarkup(): string {
let markup = "<" + this.tagName();
// Add the class
if (this.classes.length) {
markup += ` class="${utils.escape(createClass(this.classes))}"`;
}
let styles = "";
// Add the styles, after hyphenation
for (const style in this.style) {
if (this.style.hasOwnProperty(style)) {
styles += utils.hyphenate(style) + ":" + this.style[style] + ";";
}
}
if (styles) {
markup += ` style="${utils.escape(styles)}"`;
}
// Add the attributes
for (const attr in this.attributes) {
if (this.attributes.hasOwnProperty(attr)) {
markup += " " + attr + "=\"";
markup += utils.escape(this.attributes[attr]);
markup += "\"";
}
}
markup += ">";
// Add the markup of the children, also as markup
for (let i = 0; i < this.children.length; i++) {
markup += this.children[i].toMarkup();
}
markup += `</${this.tagName()}>`;
return markup;
return toMarkup.call(this, "span");
}
}
/**
* 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
* maxFontSize.
*
* Represents two types with different uses: SvgSpan to wrap an SVG and DomSpan
* otherwise. This typesafety is important when HTML builders access a span's
* children.
* This node represents an anchor (<a>) element with a hyperlink. See `span`
* for further details.
*/
class span<ChildType: VirtualNode> extends HtmlDomContainer<ChildType> {
constructor(
classes?: string[],
children?: ChildType[],
options?: Options,
style?: CssStyle,
) {
super(classes, children, options, style);
}
tagName() {
return "span";
}
}
/**
* This node represents an anchor (<a>) element with a hyperlink, a list of classes,
* a list of children, and an inline style. It also contains information about its
* height, depth, and maxFontSize.
*/
class anchor extends HtmlDomContainer<HtmlDomNode> {
href: string;
class anchor implements HtmlDomNode {
children: HtmlDomNode[];
attributes: {[string]: string};
classes: string[];
height: number;
depth: number;
maxFontSize: number;
style: CssStyle;
constructor(
href: string,
@@ -224,12 +226,29 @@ class anchor extends HtmlDomContainer<HtmlDomNode> {
children: HtmlDomNode[],
options: Options,
) {
super(classes, children, options);
initNode.call(this, classes, options);
this.children = children || [];
this.setAttribute('href', href);
}
tagName() {
return "a";
setAttribute(attribute: string, value: string) {
this.attributes[attribute] = value;
}
hasClass(className: string): boolean {
return utils.contains(this.classes, className);
}
tryCombine(sibling: HtmlDomNode): boolean {
return false;
}
toNode(): HTMLElement {
return toNode.call(this, "a");
}
toMarkup(): string {
return toMarkup.call(this, "a");
}
}
@@ -274,7 +293,7 @@ class symbolNode implements HtmlDomNode {
this.skew = skew || 0;
this.width = width || 0;
this.classes = classes || [];
this.style = Object.assign({}, style);
this.style = style || {};
this.maxFontSize = 0;
// Mark text from non-Latin scripts with specific classes so that we
@@ -534,13 +553,13 @@ export function assertSymbolDomNode(
}
}
export function assertDomContainer(
export function assertSpan(
group: HtmlDomNode,
): HtmlDomContainer<HtmlDomNode> {
if (group instanceof HtmlDomContainer) {
): span<HtmlDomNode> {
if (group instanceof span) {
return group;
} else {
throw new Error(`Expected HtmlDomContainer but got ${String(group)}.`);
throw new Error(`Expected span<HtmlDomNode> but got ${String(group)}.`);
}
}

View File

@@ -5,7 +5,7 @@ import mathMLTree from "../mathMLTree";
import utils from "../utils";
import stretchy from "../stretchy";
import ParseNode, {assertNodeType, checkNodeType} from "../ParseNode";
import {assertDomContainer, assertSymbolDomNode} from "../domTree";
import {assertSpan, assertSymbolDomNode} from "../domTree";
import * as html from "../buildHTML";
import * as mml from "../buildMathML";
@@ -40,7 +40,7 @@ export const htmlBuilder: HtmlBuilderSupSub<"accent"> = (grp, options) => {
// Rerender the supsub group with its new base, and store that
// result.
supSubGroup = assertDomContainer(html.buildGroup(supSub, options));
supSubGroup = assertSpan(html.buildGroup(supSub, options));
// reset original base
supSub.value.base = group;

View File

@@ -86,18 +86,10 @@ defineFunction({
},
});
const oldFontFuncsMap = {
"\\rm": "mathrm",
"\\sf": "mathsf",
"\\tt": "mathtt",
"\\bf": "mathbf",
"\\it": "mathit",
};
// Old font changing functions
defineFunction({
type: "font",
names: Object.keys(oldFontFuncsMap),
names: ["\\rm", "\\sf", "\\tt", "\\bf", "\\it"],
props: {
numArgs: 0,
allowedInText: true,
@@ -106,7 +98,7 @@ defineFunction({
const {mode} = parser;
parser.consumeSpaces();
const body = parser.parseExpression(true, breakOnTokenText);
const style = oldFontFuncsMap[funcName];
const style = `math${funcName.slice(1)}`;
return new ParseNode("font", {
type: "font",

View File

@@ -39,7 +39,8 @@ defineFunction({
const expression = html.buildExpression(
groupValue, options.withFont("mathrm"), true);
for (const child of expression) {
for (let i = 0; i < expression.length; i++) {
const child = expression[i];
if (child instanceof domTree.symbolNode) {
// Per amsopn package,
// change minus to hyphen and \ast to asterisk
@@ -60,7 +61,8 @@ defineFunction({
// Is expression a string or has it something like a fraction?
let isAllString = true; // default
for (const node of expression) {
for (let i = 0; i < expression.length; i++) {
const node = expression[i];
if (node instanceof mathMLTree.SpaceNode) {
// Do nothing
} else if (node instanceof mathMLTree.MathNode) {

View File

@@ -80,8 +80,8 @@ export class MathNode implements MathDomNode {
}
}
for (const child of this.children) {
node.appendChild(child.toNode());
for (let i = 0; i < this.children.length; i++) {
node.appendChild(this.children[i].toNode());
}
return node;

View File

@@ -84,8 +84,10 @@ const scriptData: Array<Script> = [
* it is from, or null if it is not part of a known block
*/
export function scriptFromCodepoint(codepoint: number): ?string {
for (const script of scriptData) {
for (const block of script.blocks) {
for (let i = 0; i < scriptData.length; i++) {
const script = scriptData[i];
for (let i = 0; i < script.blocks.length; i++) {
const block = script.blocks[i];
if (codepoint >= block[0] && codepoint <= block[1]) {
return script.name;
}