Skip to content

Commit

Permalink
🎆 Add static figure placeholder for images
Browse files Browse the repository at this point in the history
  • Loading branch information
fwkoch committed Jan 9, 2025
1 parent cefb2a1 commit 0cbc068
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 0 deletions.
7 changes: 7 additions & 0 deletions .changeset/kind-lies-kick.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'myst-directives': patch
'myst-transforms': patch
'myst-cli': patch
---

Add static figure placeholder for images that should be used only for PDF exports
5 changes: 5 additions & 0 deletions packages/myst-cli/src/process/mdast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ import {
transformFilterOutputStreams,
transformLiftCodeBlocksInJupytext,
transformMystXRefs,
removeStaticImages,
removeNonStaticImages,
} from '../transforms/index.js';
import type { ImageExtensions } from '../utils/resolveExtension.js';
import { logMessagesFromVFile } from '../utils/logging.js';
Expand Down Expand Up @@ -379,10 +381,13 @@ export async function finalizeMdast(
const vfile = new VFile(); // Collect errors on this file
vfile.path = file;
if (simplifyFigures) {
removeNonStaticImages(mdast);
// Transform output nodes to images / text
reduceOutputs(session, mdast, file, imageWriteFolder, {
altOutputFolder: simplifyFigures ? undefined : imageAltOutputFolder,
});
} else {
removeStaticImages(mdast);
}
transformOutputsToFile(session, mdast, imageWriteFolder, {
altOutputFolder: simplifyFigures ? undefined : imageAltOutputFolder,
Expand Down
39 changes: 39 additions & 0 deletions packages/myst-cli/src/transforms/images.ts
Original file line number Diff line number Diff line change
Expand Up @@ -746,6 +746,45 @@ export function transformPlaceholderImages(
remove(mdast, '__remove__');
}

/**
* Remove all static images
*
* This should only be run for web builds
*/
export function removeStaticImages(mdast: GenericParent) {
selectAll('image', mdast)
.filter((image: GenericNode) => image.static)
.forEach((image: GenericNode) => {
image.type = '__remove__';
});
remove(mdast, '__remove__');
}

/**
* Remove all figure content except static/placeholder, where present
*
* This should only be run for static builds
*/
export function removeNonStaticImages(mdast: GenericParent) {
const containers = selectAll('container', mdast);
containers.forEach((container: GenericNode) => {
const hasStatic = !!container.children?.find((child) => child.static);
const hasPlaceholder = !!container.children?.find((child) => child.placeholder);
if (!hasStatic && !hasPlaceholder) return;
container.children = container.children?.filter((child) => {
if (['caption', 'legend'].includes(child.type)) {
// Always keep caption/legend
return true;
}
if (hasStatic) {
// Prioritize static image over placeholder
return !!child.static;
}
return !!child.placeholder;
});
});
}

/**
* Trim base64 values for urlSource when they have been replaced by image urls
*/
Expand Down
15 changes: 15 additions & 0 deletions packages/myst-directives/src/figure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ export const figureDirective: DirectiveSpec = {
type: String,
doc: 'A placeholder image when using a notebook cell as the figure contents. This will be shown in place of the Jupyter output until an execution environment is attached. It will also be used in static outputs, such as a PDF output.',
},
static: {
type: String,
doc: 'An image only used in static outputs. This image will always take priority for static outputs, such as PDFs, and will never be used in dynamic outputs, such as web builds.',
},
'no-subfigures': {
type: Boolean,
doc: 'Disallow implicit subfigure creation from child nodes',
Expand Down Expand Up @@ -96,6 +100,17 @@ export const figureDirective: DirectiveSpec = {
align: data.options?.align as Image['align'],
});
}
if (data.options?.static) {
children.push({
type: 'image',
static: true,
url: data.options.static as string,
alt: data.options?.alt as string,
width: data.options?.width as string,
height: data.options?.height as string,
align: data.options?.align as Image['align'],
});
}
if (data.body) {
children.push(...(data.body as GenericNode[]));
}
Expand Down
16 changes: 16 additions & 0 deletions packages/myst-transforms/src/containers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ function isPlaceholder(node: GenericNode) {
return node.type === 'image' && node.placeholder;
}

function isStatic(node: GenericNode) {
return node.type === 'image' && node.static;
}

/** Nest node inside container */
function createSubfigure(node: GenericNode, parent: GenericParent): GenericParent {
const children = node.type === 'container' && node.children ? node.children : [node];
Expand Down Expand Up @@ -130,6 +134,7 @@ export function containerChildrenTransform(tree: GenericParent, vfile: VFile) {
hoistContentOutOfParagraphs(container);
let subfigures: GenericNode[] = [];
let placeholderImage: GenericNode | undefined;
let staticImage: GenericNode | undefined;
let caption: GenericNode | undefined;
let legend: GenericNode | undefined;
const otherNodes: GenericNode[] = [];
Expand Down Expand Up @@ -161,6 +166,15 @@ export function containerChildrenTransform(tree: GenericParent, vfile: VFile) {
} else {
placeholderImage = child;
}
} else if (isStatic(child)) {
if (staticImage) {
fileError(vfile, 'container has multiple static images', {
node: container,
ruleId: RuleId.containerChildrenValid,
});
} else {
staticImage = child;
}
} else if (SUBFIGURE_TYPES.includes(child.type)) {
subfigures.push(child);
} else {
Expand All @@ -186,6 +200,7 @@ export function containerChildrenTransform(tree: GenericParent, vfile: VFile) {
caption ? 'caption' : undefined,
legend ? 'legend' : undefined,
placeholderImage ? 'placeholder image' : undefined,
staticImage ? 'static image' : undefined,
]
.filter(Boolean)
.join(', ');
Expand All @@ -206,6 +221,7 @@ export function containerChildrenTransform(tree: GenericParent, vfile: VFile) {
}
const children: GenericNode[] = [...subfigures];
if (placeholderImage) children.push(placeholderImage);
if (staticImage) children.push(staticImage);
// Caption is above tables and below all other figures
if (container.kind === 'table') {
if (caption) children.unshift(caption);
Expand Down

0 comments on commit 0cbc068

Please sign in to comment.