Skip to content

Commit faeb8b0

Browse files
committed
fix(export): pin MathJax SVG viewport to stop squeezed PDF equations
Follow-up to #2701 and 48e77d3. Display equations whose MathJax root <svg> is sized in page-relative units (width="100%", height in ex) and wrap a nested <svg data-table> (matrices/aligned equations) rendered squeezed in exported PDFs: loaded as a standalone image the relative units have no containing block and collapse to a default size. - Narrow convertAllSvgToPng's selector to 'mjx-container > svg' so only each equation's root svg is rasterized; the nested inner svg is captured within the root and scales to fill its pinned viewport. - Measure getBoundingClientRect() first, then serialize a detached clone with width/height pinned to the measured CSS pixels (the export body is sized to the print page), recreating the exact on-page viewport. The live DOM is left untouched until replaceChild.
1 parent 3eb331a commit faeb8b0

1 file changed

Lines changed: 26 additions & 6 deletions

File tree

src/data/extra/web/js/mathjax.js

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,12 @@ class MathJaxRenderer extends VxWorker {
182182
// canvas, which would make toDataURL() throw.
183183
convertAllSvgToPng() {
184184
let container = this.vxcore.contentContainer;
185-
let svgs = container ? container.querySelectorAll('mjx-container svg') : [];
185+
// Only the root <svg> of each equation (the direct child of
186+
// mjx-container). A nested inner <svg> (e.g. MathJax's data-table for
187+
// matrices/aligned equations) must NOT be rasterized on its own: it is
188+
// captured as part of the root and, sized in relative units, scales to
189+
// fill the root's pinned viewport below.
190+
let svgs = container ? container.querySelectorAll('mjx-container > svg') : [];
186191
if (svgs.length == 0) {
187192
window.vxMarkdownAdapter.onPdfRenderReady();
188193
return;
@@ -202,15 +207,30 @@ class MathJaxRenderer extends VxWorker {
202207
svgs.forEach(function (svg) {
203208
let url = null;
204209
try {
205-
// Inline the resolved color so `fill: currentColor` keeps the
206-
// on-screen color once the SVG is loaded as a standalone image.
207-
svg.style.color = window.getComputedStyle(svg).color;
208-
let svgStr = new XMLSerializer().serializeToString(svg);
209-
210+
// Measure the live, laid-out viewport first. The export body is
211+
// sized to the PDF page (markdownviewercore.js setBodySize), so
212+
// this is the true CSS-pixel box the equation occupies.
210213
let bbox = svg.getBoundingClientRect();
211214
let width = Math.max(1, Math.ceil(bbox.width));
212215
let height = Math.max(1, Math.ceil(bbox.height));
213216

217+
// Serialize a clone so the live DOM stays untouched until the
218+
// final replaceChild in the onload handler below.
219+
let clone = svg.cloneNode(true);
220+
// Inline the resolved color so `fill: currentColor` keeps the
221+
// on-screen color once the SVG is loaded as a standalone image.
222+
// Read it from the LIVE node: a detached clone reports empty.
223+
clone.style.color = window.getComputedStyle(svg).color;
224+
// Pin the measured viewport onto the clone. MathJax's root <svg>
225+
// can be sized in page-relative units (e.g. width="100%" with a
226+
// height in ex) whose only containing block is the live page;
227+
// loaded as a standalone image those collapse and squeeze the
228+
// equation. Pinning the measured CSS pixels recreates the exact
229+
// on-page viewport so the raster matches the screen.
230+
clone.setAttribute('width', width + 'px');
231+
clone.setAttribute('height', height + 'px');
232+
let svgStr = new XMLSerializer().serializeToString(clone);
233+
214234
let canvas = document.createElement('canvas');
215235
canvas.width = width * scale;
216236
canvas.height = height * scale;

0 commit comments

Comments
 (0)