Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
187 changes: 160 additions & 27 deletions bundle/latex2html5.bundle.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,16 @@ function render(_that) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = render;
/**
* A block, not an inline span: the parser now emits real paragraphs, and a
* `<p>` inside a `<span>` is invalid nesting that a browser silently hoists
* out, taking the text with it.
*/
function render(that) {
const span = document.createElement('span');
span.className = 'math';
span.innerHTML = that.lines.join('\n');
return span;
const div = document.createElement('div');
div.className = 'math';
div.innerHTML = that.lines.join('\n');
return div;
}

},{}],5:[function(require,module,exports){
Expand Down Expand Up @@ -2173,6 +2178,14 @@ const counters_1 = require("./counters");
* property, not a drawing option.
*/
const PSSET_NON_STYLE = new Set(['unit', 'runit', 'xunit', 'yunit', 'dialect']);
/**
* `\definecolor{name}{model}{spec}`.
*
* A preamble declaration rather than content, so it is intercepted where
* \psset is: the grammar delivers a command inside the line that holds it,
* not as a node of its own, and anything not intercepted is rendered as text.
*/
const DEFINECOLOR_RE = /\\definecolor\s*\{([^}]*)\}\s*\{([^}]*)\}\s*\{([^}]*)\}/;
/**
* The style defaults out of a parsed `\psset`.
*
Expand Down Expand Up @@ -2389,8 +2402,10 @@ class Parser {
parse(text) {
this.diagnostics = [];
// A parser instance is reused across documents; without this the second
// would continue the first one's numbering.
// would continue the first one's numbering, and inherit any colour the
// first defined for itself.
this.counters.reset();
(0, utils_1.resetDefinedColors)();
if (!text)
return [];
const tree = this.parseTree(text);
Expand Down Expand Up @@ -2638,6 +2653,10 @@ class Parser {
this.parseUnits(text);
return;
}
if (DEFINECOLOR_RE.test(text)) {
this.parseDefineColor(text);
return;
}
const processed = this.parseText(text);
if (processed.trim().length)
this.environment.lines.push(processed);
Expand All @@ -2655,6 +2674,9 @@ class Parser {
if (this.PSTricks.Expressions.psset.test(line)) {
this.parseUnits(line);
}
else if (DEFINECOLOR_RE.test(line)) {
this.parseDefineColor(line);
}
else {
this.environment.lines.push(line);
}
Expand All @@ -2667,39 +2689,62 @@ class Parser {
return this.isIgnored('\\begin{' + name + '}');
}
/**
* A blank source line becomes a `<br>`, but a heading already carries its own
* margins, so a `<br>` next to one stacks two gaps where the author asked for
* one. Dropping the adjacent break leaves the heading's own spacing to do the
* work — and a run of breaks collapses to a single paragraph gap.
* Groups lines into paragraphs, the way TeX does.
*
* TeX has no concept of a blank line as vertical space: a run of them, of
* any length, is a single `\par`, and the gap between paragraphs comes from
* `\parskip` — a style, set once for the document, not something an author
* dials in by pressing return more times. Two blank lines and one are the
* same input.
*
* This used to emit one `<br>` per blank line, so the gap was however many
* times the author happened to hit return, and no stylesheet could adjust
* it. Paragraphs are real elements now and the spacing is theirs, which is
* both what TeX means and the only version a theme can restyle.
*
* Block elements are passed through untouched: a heading, list or picture is
* not part of a paragraph and brings its own margins.
*
* @param lines - the environment's rendered lines
* @returns the lines with runs of text wrapped in paragraphs
*/
collapseBreaks(lines) {
const isBlock = (l) => /^\s*<(h[1-6]|ul|ol|li|p|div|table|blockquote)\b/i.test(l);
paragraphize(lines) {
const isBlock = (l) => /^\s*<(h[1-6]|ul|ol|li|p|div|table|blockquote|pre|figure)\b/i.test(l);
const out = [];
let para = [];
const flush = () => {
if (!para.length)
return;
out.push('<p class="para">' + para.join('\n') + '</p>');
para = [];
};
for (const line of lines) {
if (line !== '<br>') {
while (isBlock(line) && out[out.length - 1] === '<br>')
out.pop();
out.push(line);
// Any run of these ends the paragraph, and a run is one break however
// long it is — consecutive flushes after the first do nothing.
if (line === '<br>') {
flush();
continue;
}
if (!out.length)
continue;
if (isBlock(out[out.length - 1]))
continue;
if (out[out.length - 1] === '<br>')
if (isBlock(line)) {
flush();
out.push(line);
continue;
out.push(line);
}
para.push(line);
}
while (out[out.length - 1] === '<br>')
out.pop();
flush();
return out;
}
newEnvironment(type) {
if (this.environment &&
(this.environment.lines.length || this.environment.type !== 'math')) {
this.environment.settings = { ...this.settings };
if (!this.environment.type.match(/pspicture|verbatim/)) {
this.environment.lines = this.collapseBreaks(this.environment.lines);
// Only the plain text environment. A list keeps its \item lines for its
// own component to turn into <li>, verbatim is literal, a picture is
// commands, and a nicebox is a single inline run — wrapping any of those
// in paragraphs breaks the element that consumes them.
if (this.environment.type === 'math') {
this.environment.lines = this.paragraphize(this.environment.lines);
}
this.objects.push(this.environment);
}
Expand All @@ -2708,6 +2753,23 @@ class Parser {
lines: []
};
}
/**
* Records a `\definecolor{name}{model}{spec}`.
*
* xcolor lets a document define its own colours, and a document that wants a
* shade xcolor does not name — a browser colour such as `lightblue`, say —
* can define it rather than rely on the renderer guessing. That is what makes
* such a page valid LaTeX instead of only valid here.
*/
parseDefineColor(text, loc) {
const m = String(text || '').match(DEFINECOLOR_RE);
if (!m)
return;
if (!(0, utils_1.defineColor)(m[1], m[2], m[3])) {
this.diagnose('warning', `\\definecolor{${m[1]}}: the ${JSON.stringify(m[2])} model with ` +
`${JSON.stringify(m[3])} is not one this understands; the colour is left undefined`, loc);
}
}
parseUnits(line) {
var m = line.replace(/\n/g, ' ').match(this.PSTricks.Expressions.psset);
const declared = this.PSTricks.Functions.psset.call(this, m);
Expand Down Expand Up @@ -6557,7 +6619,7 @@ function parseExpression(source) {
},{}],25:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MATH_CONSTANTS = exports.MATH_FUNCTIONS = exports.ExpressionError = exports.parseExpression = exports.select = exports.SVGSelection = exports.dotType = exports.arrowType = exports.Yinv = exports.Y = exports.Xinv = exports.X = exports.evaluate = exports.normalizeArrows = exports.parseArrows = exports.parseOptions = exports.resolveColor = exports.RE = exports.convertUnits = exports.matchrepl = exports.simplerepl = void 0;
exports.MATH_CONSTANTS = exports.MATH_FUNCTIONS = exports.ExpressionError = exports.parseExpression = exports.select = exports.SVGSelection = exports.dotType = exports.arrowType = exports.Yinv = exports.Y = exports.Xinv = exports.X = exports.evaluate = exports.normalizeArrows = exports.parseArrows = exports.parseOptions = exports.resolveColor = exports.defineColor = exports.resetDefinedColors = exports.RE = exports.convertUnits = exports.matchrepl = exports.simplerepl = void 0;
const expression_1 = require("./expression");
const simplerepl = function (regex, replace) {
return function (_m, contents) {
Expand Down Expand Up @@ -6635,6 +6697,75 @@ const BASE_COLORS = {
pink: [255, 191, 191], purple: [191, 0, 64], teal: [0, 128, 128],
violet: [128, 0, 128], olive: [128, 128, 0],
};
/**
* Colours the document defined for itself with `\definecolor`.
*
* Kept apart from the xcolor base set so a document can shadow a built-in name
* — which is how a page written against browser colours can keep the exact
* shade it wants while staying valid LaTeX, instead of relying on a name
* xcolor never defined.
*/
const DEFINED_COLORS = {};
/** Clears the document-defined colours. Called once per parse. */
const resetDefinedColors = function () {
for (const name of Object.keys(DEFINED_COLORS))
delete DEFINED_COLORS[name];
};
exports.resetDefinedColors = resetDefinedColors;
const clamp255 = (n) => Math.max(0, Math.min(255, Math.round(n)));
/**
* Records a `\definecolor{name}{model}{spec}`.
*
* The models are xcolor's: `rgb` and `cmyk` take fractions, `RGB` takes
* 0-255, `gray` a single fraction, and `HTML` six hex digits.
*
* @param name - the colour's name
* @param model - the colour model the spec is written in
* @param spec - the model's components, comma separated
* @returns true when the definition was understood
*/
const defineColor = function (name, model, spec) {
const key = String(name ?? '').trim().toLowerCase();
if (!key)
return false;
const parts = String(spec ?? '').split(',').map((p) => Number(p.trim()));
const m = String(model ?? '').trim();
if (m === 'rgb' && parts.length >= 3 && parts.every(isFinite)) {
DEFINED_COLORS[key] = [clamp255(parts[0] * 255), clamp255(parts[1] * 255), clamp255(parts[2] * 255)];
return true;
}
if (m === 'RGB' && parts.length >= 3 && parts.every(isFinite)) {
DEFINED_COLORS[key] = [clamp255(parts[0]), clamp255(parts[1]), clamp255(parts[2])];
return true;
}
if (m === 'gray' && parts.length >= 1 && isFinite(parts[0])) {
const g = clamp255(parts[0] * 255);
DEFINED_COLORS[key] = [g, g, g];
return true;
}
if (m === 'cmyk' && parts.length >= 4 && parts.every(isFinite)) {
const [c, y2, y3, k] = parts;
DEFINED_COLORS[key] = [
clamp255(255 * (1 - Math.min(1, c + k))),
clamp255(255 * (1 - Math.min(1, y2 + k))),
clamp255(255 * (1 - Math.min(1, y3 + k))),
];
return true;
}
if (m === 'HTML') {
const hex = String(spec ?? '').trim().replace(/^#/, '');
if (/^[0-9a-fA-F]{6}$/.test(hex)) {
DEFINED_COLORS[key] = [
parseInt(hex.slice(0, 2), 16),
parseInt(hex.slice(2, 4), 16),
parseInt(hex.slice(4, 6), 16),
];
return true;
}
}
return false;
};
exports.defineColor = defineColor;
/**
* Resolves an xcolor tint expression to a CSS colour.
*
Expand All @@ -6648,7 +6779,9 @@ const BASE_COLORS = {
*/
const resolveColor = function (value) {
const parts = String(value).split('!').map((p) => p.trim());
const rgb = (name) => BASE_COLORS[name.toLowerCase()] ?? null;
// A document's own \definecolor wins over the built-in of the same name,
// as it does in xcolor.
const rgb = (name) => DEFINED_COLORS[name.toLowerCase()] ?? BASE_COLORS[name.toLowerCase()] ?? null;
// A plain name resolves too. Nine of xcolor's base colours name a different
// colour in CSS, so handing `green` straight to the browser drew the dark
// #008000 where the document asks for pure green.
Expand Down
30 changes: 28 additions & 2 deletions packages/css/latex2js.css
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,19 @@ svg {
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}

/*
* The gap TeX calls \parskip: the space between paragraphs, and between a
* paragraph and a figure. One value, set once, because that is what it is in
* TeX — a document-level style, not something an author adjusts by pressing
* return more times.
*/
:root {
--latex2js-parskip: 1.6em;
}

.pspicture {
position: relative;
margin: auto;
margin: var(--latex2js-parskip) auto;
}

.enumerate {
Expand Down Expand Up @@ -130,4 +140,20 @@ h4.theorem-head::after {

pre {
overflow: auto;
}
}
/*
* Paragraph spacing. A run of blank lines is one \par however long it is, so
* the gap belongs to the document rather than to how many times the author hit
* return. Setting it here is what makes it adjustable at all: it used to be
* one <br> per blank line, which no stylesheet could reach.
*
* Bottom margin only, so the value is the gap. Adjacent margins would collapse
* to the larger of the two and the arithmetic would stop being obvious.
*/
.math > p.para {
margin: 0 0 var(--latex2js-parskip);
}

.math > p.para:last-child {
margin-bottom: 0;
}
15 changes: 10 additions & 5 deletions packages/html5/src/components/math.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,14 @@ interface ComponentProps {
[key: string]: any;
}

export default function render(that: ComponentProps): HTMLSpanElement {
const span = document.createElement('span');
span.className = 'math';
span.innerHTML = that.lines.join('\n');
return span;
/**
* A block, not an inline span: the parser now emits real paragraphs, and a
* `<p>` inside a `<span>` is invalid nesting that a browser silently hoists
* out, taking the text with it.
*/
export default function render(that: ComponentProps): HTMLDivElement {
const div = document.createElement('div');
div.className = 'math';
div.innerHTML = that.lines.join('\n');
return div;
}
Loading
Loading