Path: blob/main/src/command/render/output-typst.ts
3584 views
/*1* output-typst.ts2*3* Copyright (C) 2020-2022 Posit Software, PBC4*/56import { dirname, join, normalize, relative } from "../../deno_ral/path.ts";7import { ensureDirSync, safeRemoveSync } from "../../deno_ral/fs.ts";89import {10kFontPaths,11kKeepTyp,12kOutputExt,13kOutputFile,14kVariant,15} from "../../config/constants.ts";16import { Format } from "../../config/types.ts";17import { writeFileToStdout } from "../../core/console.ts";18import { dirAndStem, expandPath } from "../../core/path.ts";19import { kStdOut, replacePandocOutputArg } from "./flags.ts";20import { OutputRecipe, RenderOptions } from "./types.ts";21import { normalizeOutputPath } from "./output-shared.ts";22import {23typstCompile,24TypstCompileOptions,25validateRequiredTypstVersion,26} from "../../core/typst.ts";27import { asArray } from "../../core/array.ts";28import { ProjectContext } from "../../project/types.ts";2930export function useTypstPdfOutputRecipe(31format: Format,32) {33return format.pandoc.to === "typst" &&34format.render[kOutputExt] === "pdf";35}3637export function typstPdfOutputRecipe(38input: string,39finalOutput: string,40options: RenderOptions,41format: Format,42project?: ProjectContext,43): OutputRecipe {44// calculate output and args for pandoc (this is an intermediate file45// which we will then compile to a pdf and rename to .typ)46const [inputDir, inputStem] = dirAndStem(input);47const output = inputStem + ".typ";48let args = options.pandocArgs || [];49const pandoc = { ...format.pandoc };50if (options.flags?.output) {51args = replacePandocOutputArg(args, output);52} else {53pandoc[kOutputFile] = output;54}5556// when pandoc is done, we need to run the pdf generator and then copy the57// output to the user's requested destination58const complete = async () => {59// input file is pandoc's output60const input = join(inputDir, output);6162// run typst63await validateRequiredTypstVersion();64const pdfOutput = join(inputDir, inputStem + ".pdf");65const typstOptions: TypstCompileOptions = {66quiet: options.flags?.quiet,67fontPaths: asArray(format.metadata?.[kFontPaths]) as string[],68};69if (project?.dir) {70typstOptions.rootDir = project.dir;71}72const result = await typstCompile(73input,74pdfOutput,75typstOptions,76);77if (!result.success) {78throw new Error();79}8081// keep typ if requested82if (!format.render[kKeepTyp]) {83safeRemoveSync(input);84}8586// copy (or write for stdout) compiled pdf to final output location87if (finalOutput) {88if (finalOutput === kStdOut) {89writeFileToStdout(pdfOutput);90safeRemoveSync(pdfOutput);91} else {92const outputPdf = expandPath(finalOutput);9394if (normalize(pdfOutput) !== normalize(outputPdf)) {95// ensure the target directory exists96ensureDirSync(dirname(outputPdf));97Deno.renameSync(pdfOutput, outputPdf);98}99}100101// final output needs to either absolute or input dir relative102// (however it may be working dir relative when it is passed in)103return normalizeOutputPath(input, finalOutput);104} else {105return normalizeOutputPath(input, pdfOutput);106}107};108109const pdfOutput = finalOutput110? finalOutput === kStdOut111? undefined112: normalizeOutputPath(input, finalOutput)113: normalizeOutputPath(input, join(inputDir, inputStem + ".pdf"));114115// return recipe116const recipe: OutputRecipe = {117output,118keepYaml: false,119args,120format: { ...format, pandoc },121complete,122finalOutput: pdfOutput ? relative(inputDir, pdfOutput) : undefined,123};124125// if we have some variant declared, resolve it126// (use for opt-out citations extension)127if (format.render?.[kVariant]) {128const to = format.pandoc.to;129const variant = format.render[kVariant];130131recipe.format = {132...recipe.format,133pandoc: {134...recipe.format.pandoc,135to: `${to}${variant}`,136},137};138}139140return recipe;141}142143144