Skip to content

Commit

Permalink
🎆 Update image conversion transform to handle EPS images (#228)
Browse files Browse the repository at this point in the history
  • Loading branch information
fwkoch authored Feb 17, 2023
1 parent 99948cc commit a9913fc
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 53 deletions.
5 changes: 5 additions & 0 deletions .changeset/sharp-cars-develop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'myst-cli': patch
---

Update image conversion transform to handle EPS images
61 changes: 59 additions & 2 deletions packages/myst-cli/src/transforms/images.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,19 +224,23 @@ export async function transformImageFormats(
const svgImages: GenericNode[] = [];
const gifImages: GenericNode[] = [];
const pdfImages: GenericNode[] = [];
const epsImages: GenericNode[] = [];
const unconvertableImages: GenericNode[] = [];

const allowConvertedSvg = validExts.includes('.png') || validExts.includes('.pdf');
const allowConvertedPdf = validExts.includes('.png') || !validExts.includes('.pdf');
const allowConvertedGif = validExts.includes('.png');
const allowConvertedEps = validExts.includes('.png'); // || validExts.includes('.pdf') || validExts.includes('.svg'); // inkscape

unsupportedImages.forEach((image) => {
if (allowConvertedSvg && path.extname(image.url) === '.svg') {
svgImages.push(image);
} else if (allowConvertedGif && path.extname(image.url) == '.gif') {
} else if (allowConvertedGif && path.extname(image.url) === '.gif') {
gifImages.push(image);
} else if (allowConvertedPdf && path.extname(image.url) == '.pdf') {
} else if (allowConvertedPdf && path.extname(image.url) === '.pdf') {
pdfImages.push(image);
} else if (allowConvertedEps && path.extname(image.url) === '.eps') {
epsImages.push(image);
} else {
unconvertableImages.push(image);
}
Expand Down Expand Up @@ -355,6 +359,59 @@ export async function transformImageFormats(
}
}

// Convert EPSs
// Currently the inkscpe CLI has a bug which prevents EPS conversions;
// once that is fixed, we may uncomment the rest of this section to
// enable better conversions, e.g. EPS -> SVG
// https://gitlab.com/inkscape/inkscape/-/issues/3524
let epsConversionFn: ConversionFn | undefined;
if (epsImages.length) {
// Decide if we convert to PDF, SVG, or PNG
// const inkscapeOutputExt = validExts.filter((ext) => ['.pdf', '.svg', '.png'].includes(ext))[0];
const imagemagickOutputExt = validExts.filter((ext) => ['.png'].includes(ext))[0];
const logPrefix = `🌠 Converting ${epsImages.length} EPS image${
epsImages.length > 1 ? 's' : ''
} to`;
// if (inkscapeAvailable && inkscapeOutputExt === '.pdf') {
// session.log.info(`${logPrefix} PDF using inkscape`);
// epsConversionFn = inkscape.convertEpsToPdf;
// } else if (inkscapeAvailable && inkscapeOutputExt === '.svg') {
// session.log.info(`${logPrefix} SVG using inkscape`);
// epsConversionFn = inkscape.convertEpsToSvg;
// } else if (inkscapeAvailable && inkscapeOutputExt === '.png') {
// session.log.info(`${logPrefix} PNG using inkscape`);
// epsConversionFn = inkscape.convertEpsToPng;
// } else
if (imagemagickAvailable && imagemagickOutputExt === '.png') {
// if (inkscapeOutputExt && inkscapeOutputExt !== '.png') {
// addWarningForFile(
// session,
// file,
// `To convert EPS images to ${inkscapeOutputExt
// .slice(1)
// .toUpperCase()}, you must install inkscape.`,
// 'warn',
// { note: 'Images converted to PNG as a fallback using imagemagick.' },
// );
// }
session.log.info(`${logPrefix} PNG using imagemagick`);
epsConversionFn = imagemagick.convertEpsToPng;
} else {
addWarningForFile(
session,
file,
'Cannot convert EPS images, they may not correctly render.',
'error',
{ note: 'To convert these images, you must install imagemagick' },
);
}
if (epsConversionFn) {
conversionPromises.push(
...epsImages.map(async (image) => await convert(image, epsConversionFn as ConversionFn)),
);
}
}

// Warn on unsupported, unconvertable images
if (unconvertableImages.length) {
unconvertableImages.forEach((image) => {
Expand Down
73 changes: 38 additions & 35 deletions packages/myst-cli/src/utils/imagemagick.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ export async function extractFirstFrameOfGif(session: ISession, gif: string, wri
} else {
const executable = `convert ${gif}[0] ${png}`;
session.log.debug(`Executing: ${executable}`);
const convert = makeExecutable(executable, session.log);
const exec = makeExecutable(executable, session.log);
try {
await convert();
await exec();
} catch (err) {
session.log.error(`Could not extract an image from gif: ${gif} - ${err}`);
return null;
Expand All @@ -40,48 +40,51 @@ export async function extractFirstFrameOfGif(session: ISession, gif: string, wri
return pngFile;
}

export async function convertSvgToPng(session: ISession, svg: string, writeFolder: string) {
if (!fs.existsSync(svg)) return null;
const { name, ext } = path.parse(svg);
if (ext !== '.svg') return null;
const pngFile = `${name}.png`;
const png = path.join(writeFolder, pngFile);
if (fs.existsSync(png)) {
session.log.debug(`Cached file found for converted SVG: ${svg}`);
async function convert(
inputExtension: string,
outputExtension: string,
session: ISession,
input: string,
writeFolder: string,
) {
if (!fs.existsSync(input)) return null;
const { name, ext } = path.parse(input);
if (ext !== inputExtension) return null;
const filename = `${name}${outputExtension}`;
const output = path.join(writeFolder, filename);
const inputFormatUpper = inputExtension.slice(1).toUpperCase();
const outputFormatUpper = outputExtension.slice(1).toUpperCase();
if (fs.existsSync(output)) {
session.log.debug(`Cached file found for converted ${inputFormatUpper}: ${input}`);
} else {
const executable = `convert -density 600 ${svg} ${png}`;
const executable = `convert -density 600 -colorspace RGB ${input} ${output}`;
session.log.debug(`Executing: ${executable}`);
const convert = makeExecutable(executable, session.log);
const exec = makeExecutable(executable, session.log);
try {
await convert();
await exec();
} catch (err) {
session.log.error(`Could not convert from SVG to PNG: ${svg} - ${err}`);
session.log.error(
`Could not convert from ${inputFormatUpper} to ${outputFormatUpper}: ${input} - ${err}`,
);
return null;
}
}
return pngFile;
return filename;
}

export async function convertPdfToPng(session: ISession, pdf: string, writeFolder: string) {
if (!fs.existsSync(pdf)) return null;
const { name, ext } = path.parse(pdf);
if (ext !== '.pdf') return null;
const pngFile = `${name}.png`;
const png = path.join(writeFolder, pngFile);
if (fs.existsSync(png)) {
session.log.debug(`Cached file found for converted PDF: ${pdf}`);
} else {
const executable = `convert -density 600 ${pdf} ${png}`;
session.log.debug(`Executing: ${executable}`);
const convert = makeExecutable(executable, session.log);
try {
await convert();
} catch (err) {
session.log.error(`Could not convert from PDF to PNG: ${pdf} - ${err}`);
return null;
}
}
return pngFile;
export async function convertSvgToPng(session: ISession, input: string, writeFolder: string) {
const output = await convert('.svg', '.png', session, input, writeFolder);
return output;
}

export async function convertPdfToPng(session: ISession, input: string, writeFolder: string) {
const output = await convert('.pdf', '.png', session, input, writeFolder);
return output;
}

export async function convertEpsToPng(session: ISession, input: string, writeFolder: string) {
const output = await convert('.eps', '.png', session, input, writeFolder);
return output;
}

export async function convertImageToWebp(
Expand Down
60 changes: 44 additions & 16 deletions packages/myst-cli/src/utils/inkscape.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,34 +29,62 @@ export function isInkscapeAvailable() {
return which('inkscape', { nothrow: true });
}

async function convertSvgTo(format: string, session: ISession, svg: string, writeFolder: string) {
if (!fs.existsSync(svg)) return null;
const { name, ext } = path.parse(svg);
if (ext !== '.svg') return null;
const filename = `${name}.${format}`;
async function convert(
inputExtension: string,
outputExtension: string,
session: ISession,
input: string,
writeFolder: string,
) {
if (!fs.existsSync(input)) return null;
const { name, ext } = path.parse(input);
if (ext !== inputExtension) return null;
const filename = `${name}${outputExtension}`;
const output = path.join(writeFolder, filename);
const inputFormatUpper = inputExtension.slice(1).toUpperCase();
const outputFormat = outputExtension.slice(1);
if (fs.existsSync(output)) {
session.log.debug(`Cached file found for converted SVG: ${svg}`);
session.log.debug(`Cached file found for converted ${inputFormatUpper}: ${input}`);
} else {
const inkscapeCommand = `inkscape ${svg} --export-area-drawing --export-type=${format} --export-filename=${output}`;
const inkscapeCommand = `inkscape ${input} --export-area-drawing --export-type=${outputFormat} --export-filename=${output}`;
session.log.debug(`Executing: ${inkscapeCommand}`);
const convert = makeExecutable(inkscapeCommand, createInkscpapeLogger(session));
const exec = makeExecutable(inkscapeCommand, createInkscpapeLogger(session));
try {
await convert();
await exec();
} catch (err) {
session.log.error(`Could not convert from SVG to ${format.toUpperCase()}: ${svg} - ${err}`);
session.log.error(
`Could not convert from ${inputFormatUpper} to ${outputFormat.toUpperCase()}: ${input} - ${err}`,
);
return null;
}
}
return filename;
}

export async function convertSvgToPng(session: ISession, svg: string, writeFolder: string) {
const pngFile = await convertSvgTo('png', session, svg, writeFolder);
return pngFile;
export async function convertSvgToPng(session: ISession, input: string, writeFolder: string) {
const output = await convert('.svg', '.png', session, input, writeFolder);
return output;
}

export async function convertSvgToPdf(session: ISession, svg: string, writeFolder: string) {
const pngFile = await convertSvgTo('pdf', session, svg, writeFolder);
return pngFile;
export async function convertSvgToPdf(session: ISession, input: string, writeFolder: string) {
const output = await convert('.svg', '.pdf', session, input, writeFolder);
return output;
}

// EPS conversion functions do not work from the inkscape cli:
// See: https://gitlab.com/inkscape/inkscape/-/issues/3524

// export async function convertEpsToPdf(session: ISession, input: string, writeFolder: string) {
// const output = await convert('.eps', '.pdf', session, input, writeFolder);
// return output;
// }

// export async function convertEpsToSvg(session: ISession, input: string, writeFolder: string) {
// const output = await convert('.eps', '.svg', session, input, writeFolder);
// return output;
// }

// export async function convertEpsToPng(session: ISession, input: string, writeFolder: string) {
// const output = await convert('.eps', '.png', session, input, writeFolder);
// return output;
// }

0 comments on commit a9913fc

Please sign in to comment.