Let a formula be copied as the formula

Copying a formula from a page gave one character per line: the SGP4 a_1
block arrived as a hundred and thirty-two lines for a hundred and
thirty-two characters. That is how a browser copies MathML, which is one
element per token and is walked one element at a time.

The rendering was not the fault. The block was measured in place and the
six rows each hold their two cells side by side, with the tokens of a row
sharing a baseline, and a screenshot agrees. What was wrong is what a
reader gets when they take the formula away.

The source is therefore carried beside the MathML in an element that can
be selected, and the MathML is taken out of selection. A copy now yields
the notation the formula was written in, on one line, which is what a
reader can paste into a tool that understands it.

The source is trimmed when it is rendered, because the delimiters of a
display formula sit on their own lines and the newline after the opening
pair was arriving as a leading space.
This commit is contained in:
dev 2026-10-02 12:06:11 +08:00
parent 9caa542b52
commit ec78ac009b
2 changed files with 35 additions and 3 deletions

View File

@ -1542,6 +1542,31 @@ tbody tr:hover {
line-height: 1.5;
}
/*
* The MathML is taken out of text selection and the source is offered in its
* place.
*
* A formula is one element per token, and a browser copying a selection walks
* those elements one at a time: copying `a_1 = (x_ke / n_0)^{2/3}` from the
* page gave one character per line, 132 lines for 132 characters. What a
* reader wants on the clipboard is the formula, so the source is what can be
* selected and the markup cannot.
*/
.math math {
user-select: none;
}
/* Kept out of the layout but not out of a selection, so that a copy finds it
where the formula is. */
.math-source {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}
.math-display {
display: block;
margin: 1.1rem 0;

View File

@ -23,14 +23,21 @@ use crate::docs::markdown::{escape, escape_attr};
/// `display` is set when the formula stands on its own line, which makes it
/// larger and centred.
pub fn render(latex: &str, display: bool) -> String {
// The delimiters of a display formula are on their own lines, so the
// source arrives with the newline that followed the opening pair.
let latex = latex.trim();
let body = to_mathml(&normalize(latex));
let class = if display { "math math-display" } else { "math" };
// The original source is kept in a data attribute so that the formula
// can be recovered from the page, for copying and for the tests.
// The MathML is one element per token, and a browser copying a selection
// walks them one at a time: a formula copied from the page arrives as one
// character per line. The source is therefore carried beside it in an
// element a reader can select, and the MathML is taken out of selection
// so that what a copy yields is the formula as it was written.
format!(
"<span class=\"{class}\" data-tex=\"{}\"><math xmlns=\"http://www.w3.org/1998/Math/MathML\" \
display=\"{display}\">{body}</math></span>",
escape_attr(latex),
"<span class=\"{class}\" data-tex=\"{source}\"><math xmlns=\"http://www.w3.org/1998/Math/MathML\" display=\"{display}\">{body}</math><span class=\"math-source\">{source}</span></span>",
source = escape_attr(latex),
display = if display { "block" } else { "inline" }
)
}