// @flow /** * This file does conversion between units. In particular, it provides * calculateSize to convert other units into ems. */ import ParseError from "./ParseError"; import Options from "./Options"; // 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, }; export type Measurement = {| number: number, unit: string |}; /** * Determine whether the specified unit (either a string defining the unit * or a "size" parse node containing a unit field) is valid. */ export const validUnit = function(unit: string | Measurement): boolean { if (typeof unit !== "string") { 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. */ export const calculateSize = function( sizeValue: Measurement, options: Options): number { 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 Math.min(sizeValue.number * scale, options.maxSize); };