diff --git a/src/buildCommon.js b/src/buildCommon.js index 3a679703..3f53bf91 100644 --- a/src/buildCommon.js +++ b/src/buildCommon.js @@ -9,6 +9,7 @@ import domTree from "./domTree"; import fontMetrics from "./fontMetrics"; import symbols from "./symbols"; import utils from "./utils"; +import stretchy from "./stretchy"; import type Options from "./Options"; import type ParseNode from "./ParseNode"; @@ -274,6 +275,19 @@ const makeSpan = function( return span; }; +const makeLineSpan = function( + className: string, + options: Options, +) { + // Fill the entire span instead of just a border. That way, the min-height + // value in katex.less will ensure that at least one screen pixel displays. + const line = stretchy.ruleSpan(className, options); + line.height = options.fontMetrics().defaultRuleThickness; + line.style.height = line.height + "em"; + line.maxFontSize = 1.0; + return line; +}; + /** * Makes an anchor with the given href, list of classes, list of children, * and options. @@ -612,6 +626,7 @@ export default { makeSymbol, mathsym, makeSpan, + makeLineSpan, makeAnchor, makeFragment, makeVList, diff --git a/src/buildHTML.js b/src/buildHTML.js index d8a90e40..c1948441 100644 --- a/src/buildHTML.js +++ b/src/buildHTML.js @@ -474,63 +474,6 @@ groupTypes.spacing = function(group, options) { } }; -export const makeLineSpan = function(className, options, thickness) { - // Fill the entire span instead of just a border. That way, the min-height - // value in katex.less will ensure that at least one screen pixel displays. - const line = stretchy.ruleSpan(className, options); - line.height = thickness || options.fontMetrics().defaultRuleThickness; - line.style.height = line.height + "em"; - line.maxFontSize = 1.0; - return line; -}; - -groupTypes.overline = function(group, options) { - // Overlines are handled in the TeXbook pg 443, Rule 9. - - // Build the inner group in the cramped style. - const innerGroup = buildGroup(group.value.body, - options.havingCrampedStyle()); - - // Create the line above the body - const line = makeLineSpan("overline-line", options); - - // Generate the vlist, with the appropriate kerns - const vlist = buildCommon.makeVList({ - positionType: "firstBaseline", - children: [ - {type: "elem", elem: innerGroup}, - {type: "kern", size: 3 * line.height}, - {type: "elem", elem: line}, - {type: "kern", size: line.height}, - ], - }, options); - - return makeSpan(["mord", "overline"], [vlist], options); -}; - -groupTypes.underline = function(group, options) { - // Underlines are handled in the TeXbook pg 443, Rule 10. - // Build the inner group. - const innerGroup = buildGroup(group.value.body, options); - - // Create the line above the body - const line = makeLineSpan("underline-line", options); - - // Generate the vlist, with the appropriate kerns - const vlist = buildCommon.makeVList({ - positionType: "top", - positionData: innerGroup.height, - children: [ - {type: "kern", size: line.height}, - {type: "elem", elem: line}, - {type: "kern", size: 3 * line.height}, - {type: "elem", elem: innerGroup}, - ], - }, options); - - return makeSpan(["mord", "underline"], [vlist], options); -}; - groupTypes.sqrt = function(group, options) { // Square roots are handled in the TeXbook pg. 443, Rule 11. @@ -705,36 +648,6 @@ groupTypes.verb = function(group, options) { body, newOptions); }; -groupTypes.rule = function(group, options) { - // Make an empty span for the rule - const rule = makeSpan(["mord", "rule"], [], options); - - // Calculate the shift, width, and height of the rule, and account for units - let shift = 0; - if (group.value.shift) { - shift = calculateSize(group.value.shift, options); - } - - const width = calculateSize(group.value.width, options); - const height = calculateSize(group.value.height, options); - - // Style the rule to the right size - rule.style.borderRightWidth = width + "em"; - rule.style.borderTopWidth = height + "em"; - rule.style.bottom = shift + "em"; - - // Record the height and width - rule.width = width; - rule.height = height + shift; - rule.depth = -shift; - // Font size is the number large enough that the browser will - // reserve at least `absHeight` space above the baseline. - // The 1.125 factor was empirically determined - rule.maxFontSize = height * 1.125 * options.sizeMultiplier; - - return rule; -}; - groupTypes.accent = function(group, options) { // Accents are handled in the TeXbook pg. 443, rule 12. let base = group.value.base; diff --git a/src/buildMathML.js b/src/buildMathML.js index d3586224..57a4f330 100644 --- a/src/buildMathML.js +++ b/src/buildMathML.js @@ -359,32 +359,6 @@ groupTypes.verb = function(group, options) { return node; }; -groupTypes.overline = function(group, options) { - const operator = new mathMLTree.MathNode( - "mo", [new mathMLTree.TextNode("\u203e")]); - operator.setAttribute("stretchy", "true"); - - const node = new mathMLTree.MathNode( - "mover", - [buildGroup(group.value.body, options), operator]); - node.setAttribute("accent", "true"); - - return node; -}; - -groupTypes.underline = function(group, options) { - const operator = new mathMLTree.MathNode( - "mo", [new mathMLTree.TextNode("\u203e")]); - operator.setAttribute("stretchy", "true"); - - const node = new mathMLTree.MathNode( - "munder", - [buildGroup(group.value.body, options), operator]); - node.setAttribute("accentunder", "true"); - - return node; -}; - groupTypes.accentUnder = function(group, options) { const accentNode = stretchy.mathMLnode(group.value.label); const node = new mathMLTree.MathNode( @@ -460,14 +434,6 @@ groupTypes.xArrow = function(group, options) { return node; }; -groupTypes.rule = function(group) { - // TODO(emily): Figure out if there's an actual way to draw black boxes - // in MathML. - const node = new mathMLTree.MathNode("mrow"); - - return node; -}; - groupTypes.mclass = function(group, options) { const inner = buildExpression(group.value.value, options); return new mathMLTree.MathNode("mstyle", inner); diff --git a/src/domTree.js b/src/domTree.js index 651f95c8..b9a306f4 100644 --- a/src/domTree.js +++ b/src/domTree.js @@ -59,6 +59,7 @@ class span implements CombinableDomNode { children: DomChildNode[]; height: number; depth: number; + width: ?number; maxFontSize: number; style: {[string]: string}; attributes: {[string]: string}; diff --git a/src/functions.js b/src/functions.js index d02ac0fe..c42a225e 100644 --- a/src/functions.js +++ b/src/functions.js @@ -124,44 +124,11 @@ defineFunction(["\\fcolorbox"], { }; }); -// An overline -defineFunction(["\\overline"], { - numArgs: 1, -}, function(context, args) { - const body = args[0]; - return { - type: "overline", - body: body, - }; -}); +import "./functions/overline"; -// An underline -defineFunction(["\\underline"], { - numArgs: 1, -}, function(context, args) { - const body = args[0]; - return { - type: "underline", - body: body, - }; -}); +import "./functions/underline"; -// A box of the width and height -defineFunction(["\\rule"], { - numArgs: 2, - numOptionalArgs: 1, - argTypes: ["size", "size", "size"], -}, function(context, args, optArgs) { - const shift = optArgs[0]; - const width = args[0]; - const height = args[1]; - return { - type: "rule", - shift: shift && shift.value, - width: width.value, - height: height.value, - }; -}); +import "./functions/rule"; import "./functions/kern"; diff --git a/src/functions/genfrac.js b/src/functions/genfrac.js index ee1072e2..fff12fa9 100644 --- a/src/functions/genfrac.js +++ b/src/functions/genfrac.js @@ -93,7 +93,7 @@ defineFunction({ let ruleWidth; let ruleSpacing; if (group.value.hasBarLine) { - rule = html.makeLineSpan("frac-line", options); + rule = buildCommon.makeLineSpan("frac-line", options); ruleWidth = rule.height; ruleSpacing = rule.height; } else { diff --git a/src/functions/overline.js b/src/functions/overline.js new file mode 100644 index 00000000..e6f3eab5 --- /dev/null +++ b/src/functions/overline.js @@ -0,0 +1,57 @@ +// @flow +import defineFunction from "../defineFunction"; +import buildCommon from "../buildCommon"; +import mathMLTree from "../mathMLTree"; + +import * as html from "../buildHTML"; +import * as mml from "../buildMathML"; + +defineFunction({ + type: "overline", + names: ["\\overline"], + props: { + numArgs: 1, + }, + handler(context, args) { + const body = args[0]; + return { + type: "overline", + body: body, + }; + }, + htmlBuilder(group, options) { + // Overlines are handled in the TeXbook pg 443, Rule 9. + + // Build the inner group in the cramped style. + const innerGroup = html.buildGroup(group.value.body, + options.havingCrampedStyle()); + + // Create the line above the body + const line = buildCommon.makeLineSpan("overline-line", options); + + // Generate the vlist, with the appropriate kerns + const vlist = buildCommon.makeVList({ + positionType: "firstBaseline", + children: [ + {type: "elem", elem: innerGroup}, + {type: "kern", size: 3 * line.height}, + {type: "elem", elem: line}, + {type: "kern", size: line.height}, + ], + }, options); + + return buildCommon.makeSpan(["mord", "overline"], [vlist], options); + }, + mathmlBuilder(group, options) { + const operator = new mathMLTree.MathNode( + "mo", [new mathMLTree.TextNode("\u203e")]); + operator.setAttribute("stretchy", "true"); + + const node = new mathMLTree.MathNode( + "mover", + [mml.buildGroup(group.value.body, options), operator]); + node.setAttribute("accent", "true"); + + return node; + }, +}); diff --git a/src/functions/rule.js b/src/functions/rule.js new file mode 100644 index 00000000..528b5ae9 --- /dev/null +++ b/src/functions/rule.js @@ -0,0 +1,62 @@ +// @flow +import buildCommon from "../buildCommon"; +import defineFunction from "../defineFunction"; +import mathMLTree from "../mathMLTree"; +import {calculateSize} from "../units"; + +defineFunction({ + type: "rule", + names: ["\\rule"], + props: { + numArgs: 2, + numOptionalArgs: 1, + argTypes: ["size", "size", "size"], + }, + handler(context, args, optArgs) { + const shift = optArgs[0]; + const width = args[0]; + const height = args[1]; + return { + type: "rule", + shift: shift && shift.value, + width: width.value, + height: height.value, + }; + }, + htmlBuilder(group, options) { + // Make an empty span for the rule + const rule = buildCommon.makeSpan(["mord", "rule"], [], options); + + // Calculate the shift, width, and height of the rule, and account for units + let shift = 0; + if (group.value.shift) { + shift = calculateSize(group.value.shift, options); + } + + const width = calculateSize(group.value.width, options); + const height = calculateSize(group.value.height, options); + + // Style the rule to the right size + rule.style.borderRightWidth = width + "em"; + rule.style.borderTopWidth = height + "em"; + rule.style.bottom = shift + "em"; + + // Record the height and width + rule.width = width; + rule.height = height + shift; + rule.depth = -shift; + // Font size is the number large enough that the browser will + // reserve at least `absHeight` space above the baseline. + // The 1.125 factor was empirically determined + rule.maxFontSize = height * 1.125 * options.sizeMultiplier; + + return rule; + }, + mathmlBuilder(group, options) { + // TODO(emily): Figure out if there's an actual way to draw black boxes + // in MathML. + const node = new mathMLTree.MathNode("mrow"); + + return node; + }, +}); diff --git a/src/functions/underline.js b/src/functions/underline.js new file mode 100644 index 00000000..79cbb0f7 --- /dev/null +++ b/src/functions/underline.js @@ -0,0 +1,56 @@ +// @flow +import defineFunction from "../defineFunction"; +import buildCommon from "../buildCommon"; +import mathMLTree from "../mathMLTree"; + +import * as html from "../buildHTML"; +import * as mml from "../buildMathML"; + +defineFunction({ + type: "underline", + names: ["\\underline"], + props: { + numArgs: 1, + }, + handler(context, args) { + const body = args[0]; + return { + type: "underline", + body: body, + }; + }, + htmlBuilder(group, options) { + // Underlines are handled in the TeXbook pg 443, Rule 10. + // Build the inner group. + const innerGroup = html.buildGroup(group.value.body, options); + + // Create the line above the body + const line = buildCommon.makeLineSpan("underline-line", options); + + // Generate the vlist, with the appropriate kerns + const vlist = buildCommon.makeVList({ + positionType: "top", + positionData: innerGroup.height, + children: [ + {type: "kern", size: line.height}, + {type: "elem", elem: line}, + {type: "kern", size: 3 * line.height}, + {type: "elem", elem: innerGroup}, + ], + }, options); + + return buildCommon.makeSpan(["mord", "underline"], [vlist], options); + }, + mathmlBuilder(group, options) { + const operator = new mathMLTree.MathNode( + "mo", [new mathMLTree.TextNode("\u203e")]); + operator.setAttribute("stretchy", "true"); + + const node = new mathMLTree.MathNode( + "munder", + [mml.buildGroup(group.value.body, options), operator]); + node.setAttribute("accentunder", "true"); + + return node; + }, +});