From 27c699e06cc31a1e12a779500577de4e9f74b41a Mon Sep 17 00:00:00 2001 From: Michael Ball Date: Fri, 7 Aug 2026 09:18:49 +0000 Subject: [PATCH 1/2] fix: prevent line breaks after inline images in PDF exports The myst-to-tex serializer unconditionally appends a blank line after image nodes, which LaTeX interprets as a paragraph break (\par). This caused inline images to force a new line in the compiled PDF. This change introduces a LaTeX shim that brackets inline images with custom TeX commands (\snapinlineopen and \snapinlineclose). These commands locally redefine \par to be inert, ensuring that the serializer's trailing blank line does not break the flow of text. - Added \snapinlineopen and \snapinlineclose to the LaTeX template - Updated latex-shims plugin to wrap inline image nodes with raw TeX markers - Ensured HTML builds and image caching remain unaffected Co-authored-by: Claude Code --- _latex-template/template.tex | 10 ++++++++++ _support/plugins/latex-shims.mjs | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/_latex-template/template.tex b/_latex-template/template.tex index 9f817bb74..b51e9fc76 100644 --- a/_latex-template/template.tex +++ b/_latex-template/template.tex @@ -181,6 +181,16 @@ \snaporigincludegraphics[height=#1\baselineskip]{#2}% }% } +% myst-to-tex closes every image node with a blank line — a forced \par — +% which would break the paragraph after each inline image. The latex-shims +% plugin brackets inline images with \snapinlineopen/\snapinlineclose: +% inside the pair \par is a no-op, so the serializer's blank line is inert, +% and \unskip drops the stray inter-word glue from the newline the +% serializer emits right after \includegraphics (the following text node +% carries its own leading space when one belongs there). A real paragraph +% end is serialized after \snapinlineclose, so it still breaks normally. +\newcommand{\snapinlineopen}{\begingroup\let\par\relax} +\newcommand{\snapinlineclose}{\unskip\endgroup} \makeatletter \def\snap@inline@a{width=0.001\linewidth}% image-inline -> 1.5x \def\snap@inline@b{width=0.002\linewidth}% image-inline-tall -> 2.4x diff --git a/_support/plugins/latex-shims.mjs b/_support/plugins/latex-shims.mjs index e378cbe05..8f6c0127e 100644 --- a/_support/plugins/latex-shims.mjs +++ b/_support/plugins/latex-shims.mjs @@ -380,6 +380,38 @@ const latexShimsTransform = { imageNode.width = normalizeWidth(imageNode.width); } }); + + // 6. Keep inline images inline. myst-to-tex closes EVERY image node + // with a blank line (a forced \par in TeX), so an inline image + // mid-sentence would split its paragraph and put a line break + // after the image. We can't stop the serializer from emitting the + // blank line without forking myst-to-tex, but we can defuse it: + // bracket each inline image with raw-TeX siblings + // (\snapinlineopen / \snapinlineclose, defined in template.tex) + // that scope a no-op \par around the image. A genuine paragraph + // end still breaks normally because it is serialized after the + // closing marker. Nodes are tagged so re-runs of this transform + // (one per export pipeline) don't wrap twice. + walkAll(tree, (node) => { + if (!Array.isArray(node.children)) return; + for (let i = 0; i < node.children.length; i++) { + const child = node.children[i]; + if (child?.type !== 'image' || child._snapInlineWrapped) continue; + if (inlineSentinel(child) == null) continue; + child._snapInlineWrapped = true; + node.children.splice(i, 0, { + type: 'raw', + lang: 'tex', + tex: '\\snapinlineopen{}', + }); + node.children.splice(i + 2, 0, { + type: 'raw', + lang: 'tex', + tex: '\\snapinlineclose{}', + }); + i += 2; + } + }); }, }; From b6f74ff1e024214f1ad3b177fa4494708e65356f Mon Sep 17 00:00:00 2001 From: Michael Ball Date: Fri, 7 Aug 2026 23:03:47 +0000 Subject: [PATCH 2/2] chore: upgrade TeX Live to 2026 and fix package renames Updated the CI workflow to TeX Live 2026 to match the current CTAN repository. This fixes a build failure caused by the 2025 rollover, specifically renaming `pdfmanagement-testphase` to `pdfmanagement` and `sourceserifpro` to `sourceserif`. Co-authored-by: Claude Code --- .github/workflows/myst.yml | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/.github/workflows/myst.yml b/.github/workflows/myst.yml index 39a28be07..c6cd09d18 100644 --- a/.github/workflows/myst.yml +++ b/.github/workflows/myst.yml @@ -32,8 +32,16 @@ jobs: # The PDF builds compile under LuaLaTeX and rely on # \DocumentMetadata, which the LaTeX kernel only ships with - # TeX Live 2022+. Ubuntu's apt packages still ship 2023, so - # install TeX Live 2025 from upstream via zauguin's action. + # TeX Live 2022+. Ubuntu's apt packages lag well behind, so + # install current TeX Live from upstream via zauguin's action. + # + # NOTE: `repository` pins the live tlnet mirror, which always + # serves the *current* TeX Live release — so texlive_version must + # match it. When TeX Live rolls over each spring, tlnet switches + # to the new year, the old version pin stops matching, and + # renamed/removed packages fail `tlmgr install` (this exact + # combination broke the 2025→2026 rollover). At rollover: bump + # texlive_version and re-check the package list for renames. # # Package list rationale: the *recommended* collections cover # nearly everything we need (geometry, hyperref, koma-script, @@ -43,10 +51,14 @@ jobs: # those collections. We deliberately avoid collection-latexextra # / collection-fontsextra (~2,700 extra packages, ~12 min, and # one of them tripped up format-file generation in TL 2025). - - name: Install TeX Live 2025 + # + # TL 2026 renames: pdfmanagement-testphase -> pdfmanagement, + # sourceserifpro -> sourceserif (ships a sourceserifpro.sty + # compatibility alias, so template.tex is unchanged). + - name: Install TeX Live 2026 uses: zauguin/install-texlive@v4 with: - texlive_version: 2025 + texlive_version: 2026 # Pin a known-reliable US CTAN mirror. zauguin's default # mirror probe has been failing intermittently with # "No mirror available"; hardcoding RIT bypasses it. @@ -64,8 +76,8 @@ jobs: framed iftex imakeidx - pdfmanagement-testphase - sourceserifpro + pdfmanagement + sourceserif tagpdf textpos titlesec