From 1b120f47d39310570e29443ed39633cf6f6dd6c2 Mon Sep 17 00:00:00 2001 From: Michael Ball Date: Wed, 27 May 2026 06:40:59 +0000 Subject: [PATCH] fix: prevent unwanted line breaks after inline images in PDF Modified the LaTeX template and shim plugin to suppress paragraph breaks caused by myst-to-tex's image handler. A new TeX macro swallows the trailing \par for inline images that are followed by other content, ensuring text flows correctly around icons and small images. Co-authored-by: Claude Code --- _latex-template/template.tex | 19 +++++++++++++++++ _support/plugins/latex-shims.mjs | 36 ++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/_latex-template/template.tex b/_latex-template/template.tex index 9f817bb7..6ea14549 100644 --- a/_latex-template/template.tex +++ b/_latex-template/template.tex @@ -211,6 +211,25 @@ } \makeatother +% Inline-image \par swallow. myst-to-tex's image handler always emits +% "\n\n" after \includegraphics (its closeBlock), which LaTeX reads as +% \par and breaks the surrounding paragraph after an inline image. The +% latex-shims plugin inserts \snapinlineparhook just before every inline +% image that has more content following it in the same parent. The hook +% installs a one-shot redefinition of \par that fires for the very next +% \par, undoes the trailing space the end-of-line inserted, restores the +% real \par, and lets the paragraph continue. +\makeatletter +\newcommand{\snapinlineparhook}{% + \let\snap@par@orig\par% + \let\par\snap@par@swallow% +} +\def\snap@par@swallow{% + \let\par\snap@par@orig% + \unskip% +} +\makeatother + % --- rendering ---------------------------------------------------- % Used by the latex-shims plugin to render the MyST `keyboard` node. \newcommand{\kbd}[1]{% diff --git a/_support/plugins/latex-shims.mjs b/_support/plugins/latex-shims.mjs index e378cbe0..f51a0cc7 100644 --- a/_support/plugins/latex-shims.mjs +++ b/_support/plugins/latex-shims.mjs @@ -380,9 +380,45 @@ const latexShimsTransform = { imageNode.width = normalizeWidth(imageNode.width); } }); + + // 6. Inline-image paragraph-break suppression. + // myst-to-tex's image handler unconditionally emits "\n\n" after each + // \includegraphics, which LaTeX reads as \par and ends the surrounding + // paragraph. For inline images that have following content in the same + // parent (text, more inline images, etc.) that produces an unwanted + // line break right after the image. We can't override the handler from + // a plugin, so instead we insert a raw \snapinlineparhook node before + // each such image; the macro (defined in template.tex) installs a + // one-shot \par redefinition that swallows the next \par (and the + // space the end-of-line generates immediately before it). Inline + // images that are the last child of their parent are left alone so + // legitimate paragraph breaks still happen. + walkWithParent(tree, null, (node, parent) => { + if (node.type !== 'image') return; + if (inlineSentinel(node) == null) return; + if (!parent || !Array.isArray(parent.children)) return; + const idx = parent.children.indexOf(node); + if (idx < 0 || idx === parent.children.length - 1) return; + parent.children.splice(idx, 0, { + type: 'raw', + lang: 'tex', + tex: '\\snapinlineparhook ', + }); + }); }, }; +// Walk the tree calling fn(node, parent) on each node. The walk records +// children before recursing so mutations to parent.children inside fn (e.g., +// splicing a sibling before `node`) don't double-visit or skip nodes. +function walkWithParent(node, parent, fn) { + if (!node) return; + fn(node, parent); + const children = Array.isArray(node.children) ? node.children.slice() : null; + if (!children) return; + for (const child of children) walkWithParent(child, node, fn); +} + export default { name: 'LaTeX shims (kbd, grid, image, index, lightning)', transforms: [latexShimsTransform],