mirror of
https://github.com/Smaug123/KaTeX
synced 2025-10-05 03:08:40 +00:00
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:
committed by
Kevin Barabash
parent
dcdca732a3
commit
a0bedcce8e
19
README.md
19
README.md
@@ -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.
|
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
|
## Contributing
|
||||||
|
|
||||||
See [CONTRIBUTING.md](CONTRIBUTING.md)
|
See [CONTRIBUTING.md](CONTRIBUTING.md)
|
||||||
|
@@ -4,6 +4,7 @@ import environments from "./environments";
|
|||||||
import MacroExpander from "./MacroExpander";
|
import MacroExpander from "./MacroExpander";
|
||||||
import symbols from "./symbols";
|
import symbols from "./symbols";
|
||||||
import utils from "./utils";
|
import utils from "./utils";
|
||||||
|
import units from "./units";
|
||||||
import { cjkRegex } from "./unicodeRegexes";
|
import { cjkRegex } from "./unicodeRegexes";
|
||||||
import ParseNode from "./ParseNode";
|
import ParseNode from "./ParseNode";
|
||||||
import ParseError from "./ParseError";
|
import ParseError from "./ParseError";
|
||||||
@@ -798,11 +799,11 @@ class Parser {
|
|||||||
number: +(match[1] + match[2]), // sign + magnitude, cast to number
|
number: +(match[1] + match[2]), // sign + magnitude, cast to number
|
||||||
unit: match[3],
|
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);
|
throw new ParseError("Invalid unit: '" + data.unit + "'", res);
|
||||||
}
|
}
|
||||||
return new ParseFuncOrArgument(
|
return new ParseFuncOrArgument(
|
||||||
new ParseNode("color", data, this.mode),
|
new ParseNode("size", data, this.mode),
|
||||||
false);
|
false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -12,6 +12,7 @@ import Style from "./Style";
|
|||||||
import buildCommon, { makeSpan } from "./buildCommon";
|
import buildCommon, { makeSpan } from "./buildCommon";
|
||||||
import delimiter from "./delimiter";
|
import delimiter from "./delimiter";
|
||||||
import domTree from "./domTree";
|
import domTree from "./domTree";
|
||||||
|
import units from "./units";
|
||||||
import utils from "./utils";
|
import utils from "./utils";
|
||||||
import stretchy from "./stretchy";
|
import stretchy from "./stretchy";
|
||||||
|
|
||||||
@@ -545,43 +546,6 @@ groupTypes.genfrac = function(group, options) {
|
|||||||
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) {
|
groupTypes.array = function(group, options) {
|
||||||
let r;
|
let r;
|
||||||
let c;
|
let c;
|
||||||
@@ -629,7 +593,7 @@ groupTypes.array = function(group, options) {
|
|||||||
|
|
||||||
let gap = 0;
|
let gap = 0;
|
||||||
if (group.value.rowGaps[r]) {
|
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
|
if (gap > 0) { // \@argarraycr
|
||||||
gap += arstrutDepth;
|
gap += arstrutDepth;
|
||||||
if (depth < gap) {
|
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
|
// Calculate the shift, width, and height of the rule, and account for units
|
||||||
let shift = 0;
|
let shift = 0;
|
||||||
if (group.value.shift) {
|
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 width = units.calculateSize(group.value.width, options);
|
||||||
const height = calculateSize(group.value.height, options);
|
const height = units.calculateSize(group.value.height, options);
|
||||||
|
|
||||||
// Style the rule to the right size
|
// Style the rule to the right size
|
||||||
rule.style.borderRightWidth = width + "em";
|
rule.style.borderRightWidth = width + "em";
|
||||||
@@ -1358,7 +1322,7 @@ groupTypes.kern = function(group, options) {
|
|||||||
const rule = makeSpan(["mord", "rule"], [], options);
|
const rule = makeSpan(["mord", "rule"], [], options);
|
||||||
|
|
||||||
if (group.value.dimension) {
|
if (group.value.dimension) {
|
||||||
const dimension = calculateSize(group.value.dimension, options);
|
const dimension = units.calculateSize(group.value.dimension, options);
|
||||||
rule.style.marginLeft = dimension + "em";
|
rule.style.marginLeft = dimension + "em";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -15,6 +15,10 @@ defineMacro("\\egroup", "}");
|
|||||||
defineMacro("\\begingroup", "{");
|
defineMacro("\\begingroup", "{");
|
||||||
defineMacro("\\endgroup", "}");
|
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
|
// amsmath.sty
|
||||||
|
|
||||||
|
99
src/units.js
Normal file
99
src/units.js
Normal 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,
|
||||||
|
};
|
@@ -931,7 +931,7 @@ describe("An overline parser", function() {
|
|||||||
describe("A rule parser", function() {
|
describe("A rule parser", function() {
|
||||||
const emRule = "\\rule{1em}{2em}";
|
const emRule = "\\rule{1em}{2em}";
|
||||||
const exRule = "\\rule{1ex}{2em}";
|
const exRule = "\\rule{1ex}{2em}";
|
||||||
const badUnitRule = "\\rule{1px}{2em}";
|
const badUnitRule = "\\rule{1au}{2em}";
|
||||||
const noNumberRule = "\\rule{1em}{em}";
|
const noNumberRule = "\\rule{1em}{em}";
|
||||||
const incompleteRule = "\\rule{1em}";
|
const incompleteRule = "\\rule{1em}";
|
||||||
const hardNumberRule = "\\rule{ 01.24ex}{2.450 em }";
|
const hardNumberRule = "\\rule{ 01.24ex}{2.450 em }";
|
||||||
@@ -988,7 +988,7 @@ describe("A kern parser", function() {
|
|||||||
const exKern = "\\kern{1ex}";
|
const exKern = "\\kern{1ex}";
|
||||||
const muKern = "\\kern{1mu}";
|
const muKern = "\\kern{1mu}";
|
||||||
const abKern = "a\\kern{1em}b";
|
const abKern = "a\\kern{1em}b";
|
||||||
const badUnitRule = "\\kern{1px}";
|
const badUnitRule = "\\kern{1au}";
|
||||||
const noNumberRule = "\\kern{em}";
|
const noNumberRule = "\\kern{em}";
|
||||||
|
|
||||||
it("should list the correct units", function() {
|
it("should list the correct units", function() {
|
||||||
@@ -1026,7 +1026,7 @@ describe("A non-braced kern parser", function() {
|
|||||||
const abKern1 = "a\\mkern1mub";
|
const abKern1 = "a\\mkern1mub";
|
||||||
const abKern2 = "a\\kern-1mub";
|
const abKern2 = "a\\kern-1mub";
|
||||||
const abKern3 = "a\\kern-1mu b";
|
const abKern3 = "a\\kern-1mu b";
|
||||||
const badUnitRule = "\\kern1px";
|
const badUnitRule = "\\kern1au";
|
||||||
const noNumberRule = "\\kern em";
|
const noNumberRule = "\\kern em";
|
||||||
|
|
||||||
it("should list the correct units", function() {
|
it("should list the correct units", function() {
|
||||||
|
BIN
test/screenshotter/images/Units-chrome.png
Normal file
BIN
test/screenshotter/images/Units-chrome.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 26 KiB |
BIN
test/screenshotter/images/Units-firefox.png
Normal file
BIN
test/screenshotter/images/Units-firefox.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
@@ -223,6 +223,21 @@ Symbols1: |
|
|||||||
Text: \frac{a}{b}\text{c~ {ab} \ e}+fg
|
Text: \frac{a}{b}\text{c~ {ab} \ e}+fg
|
||||||
TextWithMath: \text{for $a < b$ and $ c < d $}.
|
TextWithMath: \text{for $a < b$ and $ c < d $}.
|
||||||
Unicode: \begin{matrix}\text{ÀàÇçÉéÏïÖöÛû} \\ \text{БГДЖЗЙЛФЦШЫЮЯ} \\ \text{여보세요} \\ \text{私はバナナです} \end{matrix}
|
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:
|
UnsupportedCmds:
|
||||||
tex: \err\,\frac\fracerr3\,2^\superr_\suberr\,\sqrt\sqrterr
|
tex: \err\,\frac\fracerr3\,2^\superr_\suberr\,\sqrt\sqrterr
|
||||||
noThrow: 1
|
noThrow: 1
|
||||||
|
Reference in New Issue
Block a user