Use JS for spacing between atoms instead of CSS (#1070)
* Use JS for spacing between atoms instead of CSS Summary: This is the first step towards creating an intermediate representation that can be used to generate HTML, SVG, and Canvas commands for rendering. By generating spans that contain the width of the spaces instead of relying on CSS sibling rules we'll be able to one day replaces the spans with intermeidate 'Glue' nodes (in a later PR). An added benefit of this approach is that is enables us to programmatically change the values for thinspace, mediumspace, and thickspace which will allow us to implement the \setlength command. Test Plan: - npm test - dockers/Screenshotter/screenshotter.sh --verify * fixed failures in BinCancellation, BoldSymbol, and OperatorName * update screenshots * don't use current size when determining size of spaces, update more screenshots * fix spacing in SizingBaseline and StyleSwitching * actually do the right thing for sizing groups * fix \not for Chrome and Firefox * do TODOs * address feedback from the code review * fix issue in delimsizing.js * add TODO to think about a better solution in href.js * fix typos, simplify href, be honest about paddingLeft for \not
@@ -47,6 +47,7 @@ export type OptionsData = {
|
||||
fontFamily?: string | void;
|
||||
fontWeight?: string;
|
||||
fontShape?: string;
|
||||
sizeMultiplier?: number;
|
||||
maxSize: number;
|
||||
};
|
||||
|
||||
@@ -150,6 +151,7 @@ class Options {
|
||||
style: this.style.text(),
|
||||
size: size,
|
||||
textSize: size,
|
||||
sizeMultiplier: sizeMultipliers[size - 1],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -10,12 +10,14 @@ import fontMetrics from "./fontMetrics";
|
||||
import symbols from "./symbols";
|
||||
import utils from "./utils";
|
||||
import stretchy from "./stretchy";
|
||||
import {calculateSize} from "./units";
|
||||
|
||||
import type Options from "./Options";
|
||||
import type ParseNode from "./ParseNode";
|
||||
import type {CharacterMetrics} from "./fontMetrics";
|
||||
import type {Mode} from "./types";
|
||||
import type {DomChildNode, CombinableDomNode, CssStyle} from "./domTree";
|
||||
import type {Measurement} from "./units";
|
||||
|
||||
// The following have to be loaded from Main-Italic font, using class mainit
|
||||
const mainitLetters = [
|
||||
@@ -359,19 +361,6 @@ const makeAnchor = function(
|
||||
return anchor;
|
||||
};
|
||||
|
||||
/**
|
||||
* Prepends the given children to the given span, updating height, depth, and
|
||||
* maxFontSize.
|
||||
*/
|
||||
const prependChildren = function(
|
||||
span: domTree.span,
|
||||
children: DomChildNode[],
|
||||
) {
|
||||
span.children = children.concat(span.children);
|
||||
|
||||
sizeElementFromChildren(span);
|
||||
};
|
||||
|
||||
/**
|
||||
* Makes a document fragment with the given list of children.
|
||||
*/
|
||||
@@ -596,6 +585,17 @@ const makeVerb = function(group: ParseNode, options: Options): string {
|
||||
return text;
|
||||
};
|
||||
|
||||
// Glue is a concept from TeX which is a flexible space between elements in
|
||||
// either a vertical or horizontal list. In KaTeX, at least for now, it's
|
||||
// static space between elements in a horizontal layout.
|
||||
const makeGlue = (measurement: Measurement, options: Options): domTree.span => {
|
||||
// Make an empty span for the rule
|
||||
const rule = makeSpan(["mord", "rule"], [], options);
|
||||
const size = calculateSize(measurement, options);
|
||||
rule.style.marginRight = `${size}em`;
|
||||
return rule;
|
||||
};
|
||||
|
||||
// Takes an Options object, and returns the appropriate fontLookup
|
||||
const retrieveTextFontName = function(
|
||||
fontFamily: string,
|
||||
@@ -765,9 +765,9 @@ export default {
|
||||
makeVList,
|
||||
makeOrd,
|
||||
makeVerb,
|
||||
makeGlue,
|
||||
staticSvg,
|
||||
svgData,
|
||||
tryCombineChars,
|
||||
prependChildren,
|
||||
spacingFunctions,
|
||||
};
|
||||
|
174
src/buildHTML.js
@@ -15,6 +15,7 @@ import domTree from "./domTree";
|
||||
import { calculateSize } from "./units";
|
||||
import utils from "./utils";
|
||||
import stretchy from "./stretchy";
|
||||
import {spacings, tightSpacings} from "./spacingData";
|
||||
|
||||
const makeSpan = buildCommon.makeSpan;
|
||||
|
||||
@@ -66,6 +67,13 @@ export const spliceSpaces = function(children, i) {
|
||||
}
|
||||
};
|
||||
|
||||
const styleMap = {
|
||||
"display": Style.DISPLAY,
|
||||
"text": Style.TEXT,
|
||||
"script": Style.SCRIPT,
|
||||
"scriptscript": Style.SCRIPTSCRIPT,
|
||||
};
|
||||
|
||||
/**
|
||||
* Take a list of nodes, build them in order, and return a list of the built
|
||||
* nodes. documentFragments are flattened into their contents, so the
|
||||
@@ -75,81 +83,91 @@ export const spliceSpaces = function(children, i) {
|
||||
*/
|
||||
export const buildExpression = function(expression, options, isRealGroup) {
|
||||
// Parse expressions into `groups`.
|
||||
const groups = [];
|
||||
const rawGroups = [];
|
||||
for (let i = 0; i < expression.length; i++) {
|
||||
const group = expression[i];
|
||||
const output = buildGroup(group, options);
|
||||
if (output instanceof domTree.documentFragment) {
|
||||
Array.prototype.push.apply(groups, output.children);
|
||||
rawGroups.push(...output.children);
|
||||
} else {
|
||||
groups.push(output);
|
||||
rawGroups.push(output);
|
||||
}
|
||||
}
|
||||
// At this point `groups` consists entirely of `symbolNode`s and `span`s.
|
||||
// At this point `rawGroups` consists entirely of `symbolNode`s and `span`s.
|
||||
|
||||
// Explicit spaces (e.g., \;, \,) should be ignored with respect to atom
|
||||
// spacing (e.g., "add thick space between mord and mrel"). Since CSS
|
||||
// adjacency rules implement atom spacing, spaces should be invisible to
|
||||
// CSS. So we splice them out of `groups` and into the atoms themselves.
|
||||
for (let i = 0; i < groups.length; i++) {
|
||||
const spaces = spliceSpaces(groups, i);
|
||||
if (spaces) {
|
||||
// Splicing of spaces may have removed all remaining groups.
|
||||
if (i < groups.length) {
|
||||
// If there is a following group, move space within it.
|
||||
if (groups[i] instanceof domTree.symbolNode) {
|
||||
groups[i] = makeSpan([].concat(groups[i].classes),
|
||||
[groups[i]]);
|
||||
}
|
||||
buildCommon.prependChildren(groups[i], spaces);
|
||||
} else {
|
||||
// Otherwise, put any spaces back at the end of the groups.
|
||||
Array.prototype.push.apply(groups, spaces);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Ignore explicit spaces (e.g., \;, \,) when determining what implicit
|
||||
// spacing should go between atoms of different classes.
|
||||
const nonSpaces =
|
||||
rawGroups.filter(group => group && group.classes[0] !== "mspace");
|
||||
|
||||
// Before determining what spaces to insert, perform bin cancellation.
|
||||
// Binary operators change to ordinary symbols in some contexts.
|
||||
for (let i = 0; i < groups.length; i++) {
|
||||
if (isBin(groups[i])
|
||||
&& (isBinLeftCanceller(groups[i - 1], isRealGroup)
|
||||
|| isBinRightCanceller(groups[i + 1], isRealGroup))) {
|
||||
groups[i].classes[0] = "mord";
|
||||
for (let i = 0; i < nonSpaces.length; i++) {
|
||||
if (isBin(nonSpaces[i])) {
|
||||
if (isBinLeftCanceller(nonSpaces[i - 1], isRealGroup)
|
||||
|| isBinRightCanceller(nonSpaces[i + 1], isRealGroup)) {
|
||||
nonSpaces[i].classes[0] = "mord";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const groups = [];
|
||||
let j = 0;
|
||||
for (let i = 0; i < rawGroups.length; i++) {
|
||||
groups.push(rawGroups[i]);
|
||||
|
||||
// For any group that is not a space, get the next non-space. Then
|
||||
// lookup what implicit space should be placed between those atoms and
|
||||
// add it to groups.
|
||||
if (rawGroups[i].classes[0] !== "mspace" && j < nonSpaces.length - 1) {
|
||||
// Get the type of the current non-space node. If it's a document
|
||||
// fragment, get the type of the rightmost node in the fragment.
|
||||
const left = getTypeOfDomTree(nonSpaces[j], "right");
|
||||
|
||||
// Get the type of the next non-space node. If it's a document
|
||||
// fragment, get the type of the leftmost node in the fragment.
|
||||
const right = getTypeOfDomTree(nonSpaces[j + 1], "left");
|
||||
|
||||
// We use buildExpression inside of sizingGroup, but it returns a
|
||||
// document fragment of elements. sizingGroup sets `isRealGroup`
|
||||
// to false to avoid processing spans multiple times.
|
||||
if (left && right && isRealGroup) {
|
||||
const space = isLeftTight(nonSpaces[j + 1])
|
||||
? tightSpacings[left][right]
|
||||
: spacings[left][right];
|
||||
|
||||
if (space) {
|
||||
let glueOptions = options;
|
||||
|
||||
if (expression.length === 1) {
|
||||
if (expression[0].type === "sizing") {
|
||||
glueOptions = options.havingSize(
|
||||
expression[0].value.size);
|
||||
} else if (expression[0].type === "styling") {
|
||||
glueOptions = options.havingStyle(
|
||||
styleMap[expression[0].value.style]);
|
||||
}
|
||||
}
|
||||
|
||||
const glue = buildCommon.makeGlue(
|
||||
spacings[left][right], glueOptions);
|
||||
|
||||
groups.push(glue);
|
||||
}
|
||||
}
|
||||
j++;
|
||||
}
|
||||
}
|
||||
|
||||
// Process \\not commands within the group.
|
||||
// TODO(kevinb): Handle multiple \\not commands in a row.
|
||||
// TODO(kevinb): Handle \\not{abc} correctly. The \\not should appear over
|
||||
// the 'a' instead of the 'c'.
|
||||
for (let i = 0; i < groups.length; i++) {
|
||||
if (groups[i].value === "\u0338" && i + 1 < groups.length) {
|
||||
const children = groups.slice(i, i + 2);
|
||||
|
||||
children[0].classes = ["mainrm"];
|
||||
// \u0338 is a combining glyph so we could reorder the children so
|
||||
// that it comes after the other glyph. This works correctly on
|
||||
// most browsers except for Safari. Instead we absolutely position
|
||||
// the glyph and set its right side to match that of the other
|
||||
// glyph which is visually equivalent.
|
||||
children[0].style.position = "absolute";
|
||||
children[0].style.right = "0";
|
||||
|
||||
// Copy the classes from the second glyph to the new container.
|
||||
// This is so it behaves the same as though there was no \\not.
|
||||
const classes = groups[i + 1].classes;
|
||||
const container = makeSpan(classes, children);
|
||||
|
||||
// LaTeX adds a space between ords separated by a \\not.
|
||||
if (classes.indexOf("mord") !== -1) {
|
||||
// \glue(\thickmuskip) 2.77771 plus 2.77771
|
||||
container.style.paddingLeft = "0.277771em";
|
||||
}
|
||||
|
||||
// Ensure that the \u0338 is positioned relative to the container.
|
||||
container.style.position = "relative";
|
||||
groups.splice(i, 2, container);
|
||||
if (groups[i].value === "\u0338") {
|
||||
groups[i].style.position = "absolute";
|
||||
// TODO(kevinb) fix this for Safari by switching to a non-combining
|
||||
// character for \not.
|
||||
// This value was determined empirically.
|
||||
// TODO(kevinb) figure out the real math for this value.
|
||||
groups[i].style.paddingLeft = "0.8em";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,13 +175,21 @@ export const buildExpression = function(expression, options, isRealGroup) {
|
||||
};
|
||||
|
||||
// Return math atom class (mclass) of a domTree.
|
||||
export const getTypeOfDomTree = function(node) {
|
||||
if (node instanceof domTree.documentFragment) {
|
||||
export const getTypeOfDomTree = function(node, side = "right") {
|
||||
if (node instanceof domTree.documentFragment ||
|
||||
node instanceof domTree.anchor) {
|
||||
if (node.children.length) {
|
||||
if (side === "right") {
|
||||
return getTypeOfDomTree(
|
||||
node.children[node.children.length - 1]);
|
||||
} else if (side === "left") {
|
||||
return getTypeOfDomTree(
|
||||
node.children[0]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// This makes a lot of assumptions as to where the type of atom
|
||||
// appears. We should do a better job of enforcing this.
|
||||
if (utils.contains([
|
||||
"mord", "mop", "mbin", "mrel", "mopen", "mclose",
|
||||
"mpunct", "minner",
|
||||
@@ -174,6 +200,21 @@ export const getTypeOfDomTree = function(node) {
|
||||
return null;
|
||||
};
|
||||
|
||||
// If `node` is an atom return whether it's been assigned the mtight class.
|
||||
// If `node` is a document fragment, return the value of isLeftTight() for the
|
||||
// leftmost node in the fragment.
|
||||
// 'mtight' indicates that the node is script or scriptscript style.
|
||||
export const isLeftTight = function(node) {
|
||||
if (node instanceof domTree.documentFragment) {
|
||||
if (node.children.length) {
|
||||
return isLeftTight(node.children[0]);
|
||||
}
|
||||
} else {
|
||||
return utils.contains(node.classes, "mtight");
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sometimes, groups perform special rules when they have superscripts or
|
||||
* subscripts attached to them. This function lets the `supsub` group know that
|
||||
@@ -440,15 +481,6 @@ groupTypes.sizing = function(group, options) {
|
||||
|
||||
groupTypes.styling = function(group, options) {
|
||||
// Style changes are handled in the TeXbook on pg. 442, Rule 3.
|
||||
|
||||
// Figure out what style we're changing to.
|
||||
const styleMap = {
|
||||
"display": Style.DISPLAY,
|
||||
"text": Style.TEXT,
|
||||
"script": Style.SCRIPT,
|
||||
"scriptscript": Style.SCRIPTSCRIPT,
|
||||
};
|
||||
|
||||
const newStyle = styleMap[group.value.style];
|
||||
const newOptions = options.havingStyle(newStyle);
|
||||
return sizingGroup(group.value.value, newOptions, options);
|
||||
|
@@ -5,6 +5,8 @@ import delimiter from "../delimiter";
|
||||
import mathMLTree from "../mathMLTree";
|
||||
import ParseError from "../ParseError";
|
||||
import utils from "../utils";
|
||||
import { calculateSize } from "../units";
|
||||
import { spacings, tightSpacings } from "../spacingData";
|
||||
|
||||
import * as html from "../buildHTML";
|
||||
import * as mml from "../buildMathML";
|
||||
@@ -204,13 +206,20 @@ defineFunction({
|
||||
inner[i] = delimiter.leftRightDelim(
|
||||
middleDelim.isMiddle.value, innerHeight, innerDepth,
|
||||
middleDelim.isMiddle.options, group.mode, []);
|
||||
// Add back spaces shifted into the delimiter
|
||||
const spaces = html.spliceSpaces(middleDelim.children, 0);
|
||||
if (spaces) {
|
||||
buildCommon.prependChildren(inner[i], spaces);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const lastChildType = html.getTypeOfDomTree(inner[inner.length - 1]);
|
||||
const activeSpacings = options.style.isTight() ? tightSpacings : spacings;
|
||||
|
||||
if (lastChildType && activeSpacings[lastChildType]["mclose"]) {
|
||||
const glue =
|
||||
buildCommon.makeSpan(["mord", "rule"], [], options);
|
||||
const dimension =
|
||||
calculateSize(activeSpacings[lastChildType]["mclose"], options);
|
||||
glue.style.marginRight = `${dimension}em`;
|
||||
inner.push(glue);
|
||||
}
|
||||
|
||||
let rightDelim;
|
||||
|
@@ -54,6 +54,8 @@ defineFunction({
|
||||
if (first === last) { // Case 2 : type of both ends coincides
|
||||
classes = [first];
|
||||
} else { // Case 3: both ends have different types.
|
||||
// TODO(kevinb): figure out a better way to communicate this
|
||||
// information to buildHTML.js#buildExpression.
|
||||
const anc = buildCommon.makeAnchor(href, [], elements, options);
|
||||
return new buildCommon.makeFragment([
|
||||
new buildCommon.makeSpan([first], [], options),
|
||||
|
@@ -43,23 +43,13 @@ defineFunction({
|
||||
};
|
||||
},
|
||||
htmlBuilder: (group, options) => {
|
||||
// Make an empty span for the rule
|
||||
const rule = buildCommon.makeSpan(["mord", "rule"], [], options);
|
||||
|
||||
if (group.value.dimension) {
|
||||
const dimension = calculateSize(group.value.dimension, options);
|
||||
rule.style.marginRight = dimension + "em";
|
||||
}
|
||||
|
||||
return rule;
|
||||
return buildCommon.makeGlue(group.value.dimension, options);
|
||||
},
|
||||
mathmlBuilder: (group, options) => {
|
||||
const node = new mathMLTree.MathNode("mspace");
|
||||
|
||||
if (group.value.dimension) {
|
||||
const dimension = calculateSize(group.value.dimension, options);
|
||||
node.setAttribute("width", dimension + "em");
|
||||
}
|
||||
|
||||
return node;
|
||||
},
|
||||
|
@@ -29,6 +29,14 @@ defineFunction({
|
||||
let letter = "";
|
||||
let mode = "";
|
||||
|
||||
for (const child of group.value.value) {
|
||||
// In the amsopn package, \newmcodes@ changes four
|
||||
// characters, *-/:’, from math operators back into text.
|
||||
if ("*-/:".indexOf(child.value) !== -1) {
|
||||
child.type = "textord";
|
||||
}
|
||||
}
|
||||
|
||||
// Consolidate Greek letter function names into symbol characters.
|
||||
const temp = html.buildExpression(
|
||||
group.value.value, options.withFontFamily("mathrm"), true);
|
||||
|
90
src/spacingData.js
Normal file
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* Describes spaces between different classes of atoms.
|
||||
*/
|
||||
|
||||
const thinspace = {
|
||||
number: 3,
|
||||
unit: "mu",
|
||||
};
|
||||
const mediumspace = {
|
||||
number: 4,
|
||||
unit: "mu",
|
||||
};
|
||||
const thickspace = {
|
||||
number: 5,
|
||||
unit: "mu",
|
||||
};
|
||||
|
||||
// Spacing relationships for display and text styles
|
||||
export const spacings = {
|
||||
mord: {
|
||||
mop: thinspace,
|
||||
mbin: mediumspace,
|
||||
mrel: thickspace,
|
||||
minner: thinspace,
|
||||
},
|
||||
mop: {
|
||||
mord: thinspace,
|
||||
mop: thinspace,
|
||||
mrel: thickspace,
|
||||
minner: thinspace,
|
||||
},
|
||||
mbin: {
|
||||
mord: mediumspace,
|
||||
mop: mediumspace,
|
||||
mopen: mediumspace,
|
||||
minner: mediumspace,
|
||||
},
|
||||
mrel: {
|
||||
mord: thickspace,
|
||||
mop: thickspace,
|
||||
mopen: thickspace,
|
||||
minner: thickspace,
|
||||
},
|
||||
mopen: {},
|
||||
mclose: {
|
||||
mop: thinspace,
|
||||
mbin: mediumspace,
|
||||
mrel: thickspace,
|
||||
minner: thinspace,
|
||||
},
|
||||
mpunct: {
|
||||
mord: thinspace,
|
||||
mop: thinspace,
|
||||
mrel: thickspace,
|
||||
mopen: thinspace,
|
||||
mclose: thinspace,
|
||||
mpunct: thinspace,
|
||||
minner: thinspace,
|
||||
},
|
||||
minner: {
|
||||
mord: thinspace,
|
||||
mop: thinspace,
|
||||
mbin: mediumspace,
|
||||
mrel: thickspace,
|
||||
mopen: thinspace,
|
||||
mpunct: thinspace,
|
||||
minner: thinspace,
|
||||
},
|
||||
};
|
||||
|
||||
// Spacing relationships for script and scriptscript styles
|
||||
export const tightSpacings = {
|
||||
mord: {
|
||||
mop: thinspace,
|
||||
},
|
||||
mop: {
|
||||
mord: thinspace,
|
||||
mop: thinspace,
|
||||
},
|
||||
mbin: {},
|
||||
mrel: {},
|
||||
mopen: {},
|
||||
mclose: {
|
||||
mop: thinspace,
|
||||
},
|
||||
mpunct: {},
|
||||
minner: {
|
||||
mop: thinspace,
|
||||
},
|
||||
};
|
@@ -149,122 +149,6 @@
|
||||
@mediumspace: 0.22222em; // 4mu
|
||||
@thickspace: 0.27778em; // 5mu
|
||||
|
||||
// These spacings apply in textstyle and displaystyle.
|
||||
.mord {
|
||||
& + .mord {}
|
||||
& + .mop { margin-left: @thinspace; }
|
||||
& + .mbin { margin-left: @mediumspace; }
|
||||
& + .mrel { margin-left: @thickspace; }
|
||||
& + .mopen {}
|
||||
& + .mclose {}
|
||||
& + .mpunct {}
|
||||
& + .minner { margin-left: @thinspace; }
|
||||
}
|
||||
|
||||
.mop {
|
||||
& + .mord { margin-left: @thinspace; }
|
||||
& + .mop { margin-left: @thinspace; }
|
||||
& + .mbin {}
|
||||
& + .mrel { margin-left: @thickspace; }
|
||||
& + .mopen {}
|
||||
& + .mclose {}
|
||||
& + .mpunct {}
|
||||
& + .minner { margin-left: @thinspace; }
|
||||
}
|
||||
|
||||
.mbin {
|
||||
& + .mord { margin-left: @mediumspace; }
|
||||
& + .mop { margin-left: @mediumspace; }
|
||||
& + .mbin {}
|
||||
& + .mrel {}
|
||||
& + .mopen { margin-left: @mediumspace; }
|
||||
& + .mclose {}
|
||||
& + .mpunct {}
|
||||
& + .minner { margin-left: @mediumspace; }
|
||||
}
|
||||
|
||||
.mrel {
|
||||
& + .mord { margin-left: @thickspace; }
|
||||
& + .mop { margin-left: @thickspace; }
|
||||
& + .mbin {}
|
||||
& + .mrel {}
|
||||
& + .mopen { margin-left: @thickspace; }
|
||||
& + .mclose {}
|
||||
& + .mpunct {}
|
||||
& + .minner { margin-left: @thickspace; }
|
||||
}
|
||||
|
||||
.mopen {
|
||||
& + .mord {}
|
||||
& + .mop {}
|
||||
& + .mbin {}
|
||||
& + .mrel {}
|
||||
& + .mopen {}
|
||||
& + .mclose {}
|
||||
& + .mpunct {}
|
||||
& + .minner {}
|
||||
}
|
||||
|
||||
.mclose {
|
||||
& + .mord {}
|
||||
& + .mop { margin-left: @thinspace; }
|
||||
& + .mbin { margin-left: @mediumspace; }
|
||||
& + .mrel { margin-left: @thickspace; }
|
||||
& + .mopen {}
|
||||
& + .mclose {}
|
||||
& + .mpunct {}
|
||||
& + .minner { margin-left: @thinspace; }
|
||||
}
|
||||
|
||||
.mpunct {
|
||||
& + .mord { margin-left: @thinspace; }
|
||||
& + .mop { margin-left: @thinspace; }
|
||||
& + .mbin {}
|
||||
& + .mrel { margin-left: @thinspace; }
|
||||
& + .mopen { margin-left: @thinspace; }
|
||||
& + .mclose { margin-left: @thinspace; }
|
||||
& + .mpunct { margin-left: @thinspace; }
|
||||
& + .minner { margin-left: @thinspace; }
|
||||
}
|
||||
|
||||
.minner {
|
||||
& + .mord { margin-left: @thinspace; }
|
||||
& + .mop { margin-left: @thinspace; }
|
||||
& + .mbin { margin-left: @mediumspace; }
|
||||
& + .mrel { margin-left: @thickspace; }
|
||||
& + .mopen { margin-left: @thinspace; }
|
||||
& + .mclose {}
|
||||
& + .mpunct { margin-left: @thinspace; }
|
||||
& + .minner { margin-left: @thinspace; }
|
||||
}
|
||||
|
||||
// These tighter spacings apply in scriptstyle and scriptscriptstyle.
|
||||
.mord.mtight { margin-left: 0; }
|
||||
.mop.mtight { margin-left: 0; }
|
||||
.mbin.mtight { margin-left: 0; }
|
||||
.mrel.mtight { margin-left: 0; }
|
||||
.mopen.mtight { margin-left: 0; }
|
||||
.mclose.mtight { margin-left: 0; }
|
||||
.mpunct.mtight { margin-left: 0; }
|
||||
.minner.mtight { margin-left: 0; }
|
||||
|
||||
.mord {
|
||||
& + .mop.mtight { margin-left: @thinspace; }
|
||||
}
|
||||
|
||||
.mop {
|
||||
& + .mord.mtight { margin-left: @thinspace; }
|
||||
& + .mop.mtight { margin-left: @thinspace; }
|
||||
}
|
||||
|
||||
.mclose {
|
||||
& + .mop.mtight { margin-left: @thinspace; }
|
||||
}
|
||||
|
||||
.minner {
|
||||
& + .mop.mtight { margin-left: @thinspace; }
|
||||
}
|
||||
|
||||
.vlist-t {
|
||||
display: inline-table;
|
||||
table-layout: fixed;
|
||||
|
@@ -1841,7 +1841,8 @@ describe("A bin builder", function() {
|
||||
it("should create mbins normally", function() {
|
||||
const built = getBuilt("x + y");
|
||||
|
||||
expect(built[1].classes).toContain("mbin");
|
||||
// we add glue elements around the '+'
|
||||
expect(built[2].classes).toContain("mbin");
|
||||
});
|
||||
|
||||
it("should create ords when at the beginning of lists", function() {
|
||||
@@ -1852,17 +1853,17 @@ describe("A bin builder", function() {
|
||||
});
|
||||
|
||||
it("should create ords after some other objects", function() {
|
||||
expect(getBuilt("x + + 2")[2].classes).toContain("mord");
|
||||
expect(getBuilt("( + 2")[1].classes).toContain("mord");
|
||||
expect(getBuilt("= + 2")[1].classes).toContain("mord");
|
||||
expect(getBuilt("\\sin + 2")[1].classes).toContain("mord");
|
||||
expect(getBuilt(", + 2")[1].classes).toContain("mord");
|
||||
expect(getBuilt("x + + 2")[4].classes).toContain("mord");
|
||||
expect(getBuilt("( + 2")[2].classes).toContain("mord");
|
||||
expect(getBuilt("= + 2")[2].classes).toContain("mord");
|
||||
expect(getBuilt("\\sin + 2")[2].classes).toContain("mord");
|
||||
expect(getBuilt(", + 2")[2].classes).toContain("mord");
|
||||
});
|
||||
|
||||
it("should correctly interact with color objects", function() {
|
||||
expect(getBuilt("\\blue{x}+y")[1].classes).toContain("mbin");
|
||||
expect(getBuilt("\\blue{x+}+y")[1].classes).toContain("mbin");
|
||||
expect(getBuilt("\\blue{x+}+y")[2].classes).toContain("mord");
|
||||
expect(getBuilt("\\blue{x}+y")[2].classes).toContain("mbin");
|
||||
expect(getBuilt("\\blue{x+}+y")[2].classes).toContain("mbin");
|
||||
expect(getBuilt("\\blue{x+}+y")[4].classes).toContain("mord");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2291,15 +2292,15 @@ describe("A phantom builder", function() {
|
||||
it("should make the children transparent", function() {
|
||||
const children = getBuilt("\\phantom{x+1}");
|
||||
expect(children[0].style.color).toBe("transparent");
|
||||
expect(children[1].style.color).toBe("transparent");
|
||||
expect(children[2].style.color).toBe("transparent");
|
||||
expect(children[4].style.color).toBe("transparent");
|
||||
});
|
||||
|
||||
it("should make all descendants transparent", function() {
|
||||
const children = getBuilt("\\phantom{x+\\blue{1}}");
|
||||
expect(children[0].style.color).toBe("transparent");
|
||||
expect(children[1].style.color).toBe("transparent");
|
||||
expect(children[2].style.color).toBe("transparent");
|
||||
expect(children[4].style.color).toBe("transparent");
|
||||
});
|
||||
});
|
||||
|
||||
|
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 9.5 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 9.9 KiB After Width: | Height: | Size: 9.9 KiB |
Before Width: | Height: | Size: 9.9 KiB After Width: | Height: | Size: 9.9 KiB |
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
@@ -5,6 +5,8 @@
|
||||
\usepackage{eufrak}
|
||||
\usepackage[papersize={133pt,100pt},margin=0.5pt]{geometry}
|
||||
\usepackage{color}
|
||||
\usepackage{cancel}
|
||||
\usepackage[normalem]{ulem}
|
||||
\usepackage{etoolbox}
|
||||
\setlength{\parindent}{0pt}
|
||||
\pagestyle{empty}
|
||||
|