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
24 changes: 18 additions & 6 deletions .github/workflows/myst.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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.
Expand All @@ -64,8 +76,8 @@ jobs:
framed
iftex
imakeidx
pdfmanagement-testphase
sourceserifpro
pdfmanagement
sourceserif
tagpdf
textpos
titlesec
Expand Down
10 changes: 10 additions & 0 deletions _latex-template/template.tex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
32 changes: 32 additions & 0 deletions _support/plugins/latex-shims.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
});
},
};

Expand Down
Loading