Support absolute TeX units (#732)

* Support absolute TeX units

* Implement @kohler's comments

* Rewrite unit documentation according to @kohler's comments
This commit is contained in:
Erik Demaine
2017-08-12 07:30:06 +09:00
committed by Kevin Barabash
parent dcdca732a3
commit a0bedcce8e
9 changed files with 149 additions and 47 deletions

View File

@@ -4,6 +4,7 @@ import environments from "./environments";
import MacroExpander from "./MacroExpander";
import symbols from "./symbols";
import utils from "./utils";
import units from "./units";
import { cjkRegex } from "./unicodeRegexes";
import ParseNode from "./ParseNode";
import ParseError from "./ParseError";
@@ -798,11 +799,11 @@ class Parser {
number: +(match[1] + match[2]), // sign + magnitude, cast to number
unit: match[3],
};
if (data.unit !== "em" && data.unit !== "ex" && data.unit !== "mu") {
if (!units.validUnit(data)) {
throw new ParseError("Invalid unit: '" + data.unit + "'", res);
}
return new ParseFuncOrArgument(
new ParseNode("color", data, this.mode),
new ParseNode("size", data, this.mode),
false);
}

View File

@@ -12,6 +12,7 @@ import Style from "./Style";
import buildCommon, { makeSpan } from "./buildCommon";
import delimiter from "./delimiter";
import domTree from "./domTree";
import units from "./units";
import utils from "./utils";
import stretchy from "./stretchy";
@@ -545,43 +546,6 @@ groupTypes.genfrac = function(group, options) {
options);
};
/**
* Parse a `sizeValue`, as parsed by functions.js argType "size", into
* a CSS em value. `options` gives the current options.
*/
const calculateSize = function(sizeValue, options) {
let scale;
// `mu` units scale with scriptstyle/scriptscriptstyle.
// Other units always refer to the *textstyle* font in the current size.
if (sizeValue.unit === "mu") {
scale = options.fontMetrics().cssEmPerMu;
} else {
let unitOptions;
if (options.style.isTight()) {
// isTight() means current style is script/scriptscript.
unitOptions = options.havingStyle(options.style.text());
} else {
unitOptions = options;
}
// TODO: In TeX these units are relative to the quad of the current
// *text* font, e.g. cmr10. KaTeX instead uses values from the
// comparably-sized *Computer Modern symbol* font. At 10pt, these
// match. At 7pt and 5pt, they differ: cmr7=1.138894, cmsy7=1.170641;
// cmr5=1.361133, cmsy5=1.472241. Consider $\scriptsize a\kern1emb$.
// TeX \showlists shows a kern of 1.13889 * fontsize;
// KaTeX shows a kern of 1.171 * fontsize.
if (sizeValue.unit === "ex") {
scale = unitOptions.fontMetrics().xHeight;
} else {
scale = unitOptions.fontMetrics().quad;
}
if (unitOptions !== options) {
scale *= unitOptions.sizeMultiplier / options.sizeMultiplier;
}
}
return sizeValue.number * scale;
};
groupTypes.array = function(group, options) {
let r;
let c;
@@ -629,7 +593,7 @@ groupTypes.array = function(group, options) {
let gap = 0;
if (group.value.rowGaps[r]) {
gap = calculateSize(group.value.rowGaps[r].value, options);
gap = units.calculateSize(group.value.rowGaps[r].value, options);
if (gap > 0) { // \@argarraycr
gap += arstrutDepth;
if (depth < gap) {
@@ -1330,11 +1294,11 @@ groupTypes.rule = function(group, 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);
shift = units.calculateSize(group.value.shift, options);
}
const width = calculateSize(group.value.width, options);
const height = calculateSize(group.value.height, options);
const width = units.calculateSize(group.value.width, options);
const height = units.calculateSize(group.value.height, options);
// Style the rule to the right size
rule.style.borderRightWidth = width + "em";
@@ -1358,7 +1322,7 @@ groupTypes.kern = function(group, options) {
const rule = makeSpan(["mord", "rule"], [], options);
if (group.value.dimension) {
const dimension = calculateSize(group.value.dimension, options);
const dimension = units.calculateSize(group.value.dimension, options);
rule.style.marginLeft = dimension + "em";
}

View File

@@ -15,6 +15,10 @@ defineMacro("\\egroup", "}");
defineMacro("\\begingroup", "{");
defineMacro("\\endgroup", "}");
// We don't distinguish between math and nonmath kerns.
// (In TeX, the mu unit works only with \mkern.)
defineMacro("\\mkern", "\\kern");
//////////////////////////////////////////////////////////////////////
// amsmath.sty

99
src/units.js Normal file
View File

@@ -0,0 +1,99 @@
/* eslint no-console:0 */
/**
* This file does conversion between units. In particular, it provides
* calculateSize to convert other units into ems.
*/
import ParseError from "./ParseError";
// This table gives the number of TeX pts in one of each *absolute* TeX unit.
// Thus, multiplying a length by this number converts the length from units
// into pts. Dividing the result by ptPerEm gives the number of ems
// *assuming* a font size of ptPerEm (normal size, normal style).
const ptPerUnit = {
// https://en.wikibooks.org/wiki/LaTeX/Lengths and
// https://tex.stackexchange.com/a/8263
"pt": 1, // TeX point
"mm": 7227 / 2540, // millimeter
"cm": 7227 / 254, // centimeter
"in": 72.27, // inch
"bp": 803 / 800, // big (PostScript) points
"pc": 12, // pica
"dd": 1238 / 1157, // didot
"cc": 14856 / 1157, // cicero (12 didot)
"nd": 685 / 642, // new didot
"nc": 1370 / 107, // new cicero (12 new didot)
"sp": 1 / 65536, // scaled point (TeX's internal smallest unit)
// https://tex.stackexchange.com/a/41371
"px": 803 / 800, // \pdfpxdimen defaults to 1 bp in pdfTeX and LuaTeX
};
// Dictionary of relative units, for fast validity testing.
const relativeUnit = {
"ex": true,
"em": true,
"mu": true,
};
/**
* Determine whether the specified unit (either a string defining the unit
* or a "size" parse node containing a unit field) is valid.
*/
const validUnit = function(unit) {
if (unit.unit) {
unit = unit.unit;
}
return (unit in ptPerUnit || unit in relativeUnit || unit === "ex");
};
/*
* Convert a "size" parse node (with numeric "number" and string "unit" fields,
* as parsed by functions.js argType "size") into a CSS em value for the
* current style/scale. `options` gives the current options.
*/
const calculateSize = function(sizeValue, options) {
let scale;
if (sizeValue.unit in ptPerUnit) {
// Absolute units
scale = ptPerUnit[sizeValue.unit] // Convert unit to pt
/ options.fontMetrics().ptPerEm // Convert pt to CSS em
/ options.sizeMultiplier; // Unscale to make absolute units
} else if (sizeValue.unit === "mu") {
// `mu` units scale with scriptstyle/scriptscriptstyle.
scale = options.fontMetrics().cssEmPerMu;
} else {
// Other relative units always refer to the *textstyle* font
// in the current size.
let unitOptions;
if (options.style.isTight()) {
// isTight() means current style is script/scriptscript.
unitOptions = options.havingStyle(options.style.text());
} else {
unitOptions = options;
}
// TODO: In TeX these units are relative to the quad of the current
// *text* font, e.g. cmr10. KaTeX instead uses values from the
// comparably-sized *Computer Modern symbol* font. At 10pt, these
// match. At 7pt and 5pt, they differ: cmr7=1.138894, cmsy7=1.170641;
// cmr5=1.361133, cmsy5=1.472241. Consider $\scriptsize a\kern1emb$.
// TeX \showlists shows a kern of 1.13889 * fontsize;
// KaTeX shows a kern of 1.171 * fontsize.
if (sizeValue.unit === "ex") {
scale = unitOptions.fontMetrics().xHeight;
} else if (sizeValue.unit === "em") {
scale = unitOptions.fontMetrics().quad;
} else {
throw new ParseError("Invalid unit: '" + sizeValue.unit + "'");
}
if (unitOptions !== options) {
scale *= unitOptions.sizeMultiplier / options.sizeMultiplier;
}
}
return sizeValue.number * scale;
};
module.exports = {
validUnit: validUnit,
calculateSize: calculateSize,
};