Skip to content

Commit

Permalink
Support absolute TeX units (#732)
Browse files Browse the repository at this point in the history
* Support absolute TeX units

* Implement @kohler's comments

* Rewrite unit documentation according to @kohler's comments
  • Loading branch information
edemaine authored and kevinbarabash committed Aug 11, 2017
1 parent dcdca73 commit a0bedcc
Show file tree
Hide file tree
Showing 9 changed files with 149 additions and 47 deletions.
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,25 @@ katex.render("c = \\pm\\sqrt{a^2 + b^2}\\in\\RR", element, {

Math on the page can be automatically rendered using the auto-render extension. See [the Auto-render README](contrib/auto-render/README.md) for more information.

#### Font size and lengths

By default, KaTeX math is rendered in a 1.21× larger font than the surrounding
context, which makes super- and subscripts easier to read. You can control
this using CSS, for example:

```css
.katex { font-size: 1.1em; }
```

KaTeX supports all TeX units, including absolute units like `cm` and `in`.
Absolute units are currently scaled relative to the default TeX font size of
10pt, so that `\kern1cm` produces the same results as `\kern2.845275em`.
As a result, relative and absolute units are both uniformly scaled relative
to LaTeX with a 10pt font; for example, the rectangle `\rule{1cm}{1em}` has
the same aspect ratio in KaTeX as in LaTeX. However, because most browsers
default to a larger font size, this typically means that a 1cm kern in KaTeX
will appear larger than 1cm in browser units.

## Contributing

See [CONTRIBUTING.md](CONTRIBUTING.md)
Expand Down
5 changes: 3 additions & 2 deletions src/Parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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);
}

Expand Down
48 changes: 6 additions & 42 deletions src/buildHTML.js
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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";
Expand All @@ -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";
}

Expand Down
4 changes: 4 additions & 0 deletions src/macros.js
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
99 changes: 99 additions & 0 deletions src/units.js
Original file line number Diff line number Diff line change
@@ -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,
};
6 changes: 3 additions & 3 deletions test/katex-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -931,7 +931,7 @@ describe("An overline parser", function() {
describe("A rule parser", function() {
const emRule = "\\rule{1em}{2em}";
const exRule = "\\rule{1ex}{2em}";
const badUnitRule = "\\rule{1px}{2em}";
const badUnitRule = "\\rule{1au}{2em}";
const noNumberRule = "\\rule{1em}{em}";
const incompleteRule = "\\rule{1em}";
const hardNumberRule = "\\rule{ 01.24ex}{2.450 em }";
Expand Down Expand Up @@ -988,7 +988,7 @@ describe("A kern parser", function() {
const exKern = "\\kern{1ex}";
const muKern = "\\kern{1mu}";
const abKern = "a\\kern{1em}b";
const badUnitRule = "\\kern{1px}";
const badUnitRule = "\\kern{1au}";
const noNumberRule = "\\kern{em}";

it("should list the correct units", function() {
Expand Down Expand Up @@ -1026,7 +1026,7 @@ describe("A non-braced kern parser", function() {
const abKern1 = "a\\mkern1mub";
const abKern2 = "a\\kern-1mub";
const abKern3 = "a\\kern-1mu b";
const badUnitRule = "\\kern1px";
const badUnitRule = "\\kern1au";
const noNumberRule = "\\kern em";

it("should list the correct units", function() {
Expand Down
Binary file added test/screenshotter/images/Units-chrome.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/screenshotter/images/Units-firefox.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 15 additions & 0 deletions test/screenshotter/ss_data.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,21 @@ Symbols1: |
Text: \frac{a}{b}\text{c~ {ab} \ e}+fg
TextWithMath: \text{for $a < b$ and $ c < d $}.
Unicode: \begin{matrix}\text{ÀàÇçÉéÏïÖöÛû} \\ \text{БГДЖЗЙЛФЦШЫЮЯ} \\ \text{여보세요} \\ \text{私はバナナです} \end{matrix}
Units: |
\begin{array}{ll}
\mathrm H\kern 1em\mathrm H \text{\tiny (1em)}
& \mathrm H\kern 1ex\mathrm H \text{\tiny (1ex)} \\
\mathrm H{\scriptstyle \kern 1em}\mathrm H \text{\tiny (ss 1em)}
& \mathrm H{\scriptstyle \kern 1ex}\mathrm H \text{\tiny (ss 1ex)} \\
\mathrm H{\small \kern 1em}\mathrm H \text{\tiny (sm 1em)}
& \mathrm H{\small \kern 1ex}\mathrm H \text{\tiny (sm 1ex)} \\
\mathrm H\mkern 18mu\mathrm H \text{\tiny (18mu)}
& \mathrm H\kern 1cm\mathrm H \text{\tiny (1cm)} \\
\mathrm H{\scriptstyle \mkern 18mu}\mathrm H \text{\tiny (ss 18mu)}
& \mathrm H{\scriptstyle \kern 1cm}\mathrm H \text{\tiny (ss 1cm)} \\
\mathrm H{\small \mkern 18mu}\mathrm H \text{\tiny (sm 18mu)}
& \mathrm H{\small \kern 1cm}\mathrm H \text{\tiny (sm 1cm)}
\end{array}
UnsupportedCmds:
tex: \err\,\frac\fracerr3\,2^\superr_\suberr\,\sqrt\sqrterr
noThrow: 1
Expand Down

0 comments on commit a0bedcc

Please sign in to comment.