From a9913fc98f1080fae759d6e39df37186fb563006 Mon Sep 17 00:00:00 2001 From: Franklin Koch Date: Fri, 17 Feb 2023 13:43:30 -0700 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=86=20Update=20image=20conversion=20tr?= =?UTF-8?q?ansform=20to=20handle=20EPS=20images=20(#228)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .changeset/sharp-cars-develop.md | 5 ++ packages/myst-cli/src/transforms/images.ts | 61 +++++++++++++++++- packages/myst-cli/src/utils/imagemagick.ts | 73 +++++++++++----------- packages/myst-cli/src/utils/inkscape.ts | 60 +++++++++++++----- 4 files changed, 146 insertions(+), 53 deletions(-) create mode 100644 .changeset/sharp-cars-develop.md diff --git a/.changeset/sharp-cars-develop.md b/.changeset/sharp-cars-develop.md new file mode 100644 index 000000000..78b4f1a9d --- /dev/null +++ b/.changeset/sharp-cars-develop.md @@ -0,0 +1,5 @@ +--- +'myst-cli': patch +--- + +Update image conversion transform to handle EPS images diff --git a/packages/myst-cli/src/transforms/images.ts b/packages/myst-cli/src/transforms/images.ts index 6a95af5a1..045c75f14 100644 --- a/packages/myst-cli/src/transforms/images.ts +++ b/packages/myst-cli/src/transforms/images.ts @@ -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); } @@ -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) => { diff --git a/packages/myst-cli/src/utils/imagemagick.ts b/packages/myst-cli/src/utils/imagemagick.ts index 1355d43dc..7baad8a0a 100644 --- a/packages/myst-cli/src/utils/imagemagick.ts +++ b/packages/myst-cli/src/utils/imagemagick.ts @@ -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; @@ -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( diff --git a/packages/myst-cli/src/utils/inkscape.ts b/packages/myst-cli/src/utils/inkscape.ts index 8c8b9d3ea..82a5b44cd 100644 --- a/packages/myst-cli/src/utils/inkscape.ts +++ b/packages/myst-cli/src/utils/inkscape.ts @@ -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; +// }