diff --git a/configuration b/configuration index 0b183b53b3c..a2b9d59e68a 100644 --- a/configuration +++ b/configuration @@ -10,7 +10,7 @@ # Binary dependencies export DENO=v1.28.2 export DENO_DOM=v0.1.35-alpha-artifacts -export PANDOC=3.1.1 +export PANDOC=3.1.2 export DARTSASS=1.55.0 export ESBUILD=0.15.6 diff --git a/package/src/common/dependencies/pandoc.ts b/package/src/common/dependencies/pandoc.ts index 4018ba1135e..d001bc21d87 100644 --- a/package/src/common/dependencies/pandoc.ts +++ b/package/src/common/dependencies/pandoc.ts @@ -26,7 +26,8 @@ export function pandoc(version: string): Dependency { `https://github.com/jgm/pandoc/releases/download/${version}/${filename}`, configure: async (config: Configuration, path: string) => { const dir = dirname(path); - const pandocSubdir = join(dir, `pandoc-${version}`); + // TODO: deal with aarch64 pandoc + const pandocSubdir = join(dir, `pandoc-${version}${(config.os === "darwin" ) ? ("-" + "x86_64") : ""}`); const vendor = Deno.env.get("QUARTO_VENDOR_BINARIES"); if (vendor === undefined || vendor === "true") { // Clean pandoc interim dir @@ -85,7 +86,7 @@ export function pandoc(version: string): Dependency { "pandoc", ), "darwin": pandocRelease( - `pandoc-${version}-macOS.zip`, + `pandoc-${version}-x86_64-macOS.zip`, "pandoc", ), }, diff --git a/src/command/render/cleanup.ts b/src/command/render/cleanup.ts index ec775e5c454..09e57873ceb 100644 --- a/src/command/render/cleanup.ts +++ b/src/command/render/cleanup.ts @@ -19,7 +19,7 @@ import { figuresDir, inputFilesDir } from "../../core/render.ts"; import { Format } from "../../config/types.ts"; import { isHtmlFileOutput, isLatexOutput } from "../../config/format.ts"; -import { kKeepMd, kKeepTex } from "../../config/constants.ts"; +import { kKeepMd, kKeepTex, kKeepTyp } from "../../config/constants.ts"; import { filesDirLibDir, filesDirMediabagDir } from "./render-paths.ts"; @@ -48,7 +48,10 @@ export function renderCleanup( // if we aren't keeping the markdown or text and we are instructed to // clean supporting files then do it if ( - !format.execute[kKeepMd] && !format.render[kKeepTex] && supporting + !format.execute[kKeepMd] && + !format.render[kKeepTex] && + !format.render[kKeepTyp] && + supporting ) { // ammend supporting with lib dir (if it exists) for html formats if (isHtmlFileOutput(format.pandoc)) { diff --git a/src/command/render/output-tex.ts b/src/command/render/output-tex.ts index d60bee7469c..42ec1ddf64e 100644 --- a/src/command/render/output-tex.ts +++ b/src/command/render/output-tex.ts @@ -5,7 +5,7 @@ * */ -import { dirname, isAbsolute, join, normalize, relative } from "path/mod.ts"; +import { dirname, join, normalize, relative } from "path/mod.ts"; import { ensureDirSync } from "fs/mod.ts"; import { writeFileToStdout } from "../../core/console.ts"; @@ -21,6 +21,7 @@ import { OutputRecipe } from "./types.ts"; import { pdfEngine } from "../../config/pdf.ts"; import { execProcess } from "../../core/process.ts"; import { parseFormatString } from "../../core/pandoc/pandoc-formats.ts"; +import { normalizeOutputPath } from "./output.ts"; export interface PdfGenerator { generate: ( @@ -109,15 +110,17 @@ export function texToPdfOutputRecipe( // final output needs to either absolute or input dir relative // (however it may be working dir relative when it is passed in) - return texNormalizePath(input, finalOutput); + return normalizeOutputPath(input, finalOutput); } else { - return texNormalizePath(input, pdfOutput); + return normalizeOutputPath(input, pdfOutput); } }; const pdfOutput = finalOutput - ? finalOutput === kStdOut ? undefined : texNormalizePath(input, finalOutput) - : texNormalizePath(input, pdfGenerator.computePath(input, format)); + ? finalOutput === kStdOut + ? undefined + : normalizeOutputPath(input, finalOutput) + : normalizeOutputPath(input, pdfGenerator.computePath(input, format)); // tweak writer if it's pdf const to = format.pandoc.to === "pdf" ? pdfIntermediateTo : format.pandoc.to; @@ -209,14 +212,3 @@ export function contextPdfOutputRecipe( }, ); } - -const texNormalizePath = (input: string, output: string) => { - if (isAbsolute(output)) { - return output; - } else { - return relative( - dirname(input), - output, - ); - } -}; diff --git a/src/command/render/output-typst.ts b/src/command/render/output-typst.ts new file mode 100644 index 00000000000..0ebbf014615 --- /dev/null +++ b/src/command/render/output-typst.ts @@ -0,0 +1,101 @@ +/* +* output-typst.ts +* +* Copyright (C) 2020-2022 Posit Software, PBC +* +*/ + +import { dirname, join, normalize, relative } from "path/mod.ts"; +import { ensureDirSync } from "fs/mod.ts"; + +import { kKeepTyp, kOutputExt, kOutputFile } from "../../config/constants.ts"; +import { Format } from "../../config/types.ts"; +import { writeFileToStdout } from "../../core/console.ts"; +import { dirAndStem, expandPath } from "../../core/path.ts"; +import { kStdOut, replacePandocOutputArg } from "./flags.ts"; +import { OutputRecipe, RenderOptions } from "./types.ts"; +import { normalizeOutputPath } from "./output.ts"; +import { typstCompile } from "../../core/typst.ts"; + +export function useTypstPdfOutputRecipe( + format: Format, +) { + return format.pandoc.to === "typst" && + format.render[kOutputExt] === "pdf"; +} + +export function typstPdfOutputRecipe( + input: string, + finalOutput: string, + options: RenderOptions, + format: Format, +): OutputRecipe { + // cacluate output and args for pandoc (this is an intermediate file + // which we will then compile to a pdf and rename to .typ) + const [inputDir, inputStem] = dirAndStem(input); + const output = inputStem + ".typ"; + let args = options.pandocArgs || []; + const pandoc = { ...format.pandoc }; + if (options.flags?.output) { + args = replacePandocOutputArg(args, output); + } else { + pandoc[kOutputFile] = output; + } + + // when pandoc is done, we need to run the pdf generator and then copy the + // ouptut to the user's requested destination + const complete = async () => { + // input file is pandoc's output + const input = join(inputDir, output); + + // run typst + const pdfOutput = join(inputDir, inputStem + ".pdf"); + const result = await typstCompile(input, pdfOutput, options.flags?.quiet); + if (!result.success) { + throw new Error(); + } + + // keep typ if requested + if (!format.render[kKeepTyp]) { + Deno.removeSync(input); + } + + // copy (or write for stdout) compiled pdf to final output location + if (finalOutput) { + if (finalOutput === kStdOut) { + writeFileToStdout(pdfOutput); + Deno.removeSync(pdfOutput); + } else { + const outputPdf = expandPath(finalOutput); + + if (normalize(pdfOutput) !== normalize(outputPdf)) { + // ensure the target directory exists + ensureDirSync(dirname(outputPdf)); + Deno.renameSync(pdfOutput, outputPdf); + } + } + + // final output needs to either absolute or input dir relative + // (however it may be working dir relative when it is passed in) + return normalizeOutputPath(input, finalOutput); + } else { + return normalizeOutputPath(input, pdfOutput); + } + }; + + const pdfOutput = finalOutput + ? finalOutput === kStdOut + ? undefined + : normalizeOutputPath(input, finalOutput) + : normalizeOutputPath(input, join(inputDir, inputStem + ".pdf")); + + // return recipe + return { + output, + keepYaml: false, + args, + format: { ...format, pandoc }, + complete, + finalOutput: pdfOutput ? relative(inputDir, pdfOutput) : undefined, + }; +} diff --git a/src/command/render/output.ts b/src/command/render/output.ts index 19ee9bae397..5782866084c 100644 --- a/src/command/render/output.ts +++ b/src/command/render/output.ts @@ -39,6 +39,10 @@ import { } from "./output-tex.ts"; import { formatOutputFile } from "../../core/render.ts"; import { kYamlMetadataBlock } from "../../core/pandoc/pandoc-formats.ts"; +import { + typstPdfOutputRecipe, + useTypstPdfOutputRecipe, +} from "./output-typst.ts"; // render commands imply the --output argument for pandoc and the final // output file to create for the user, but we need a 'recipe' to go from @@ -77,6 +81,8 @@ export function outputRecipe( return quartoLatexmkOutputRecipe(input, output, options, format); } else if (useContextPdfOutputRecipe(format, options.flags)) { return contextPdfOutputRecipe(input, output, options, format); + } else if (useTypstPdfOutputRecipe(format)) { + return typstPdfOutputRecipe(input, output, options, format); } else { // default recipe spec based on user input const completeActions: VoidFunction[] = []; @@ -198,3 +204,14 @@ export function outputRecipe( return recipe; } } + +export function normalizeOutputPath(input: string, output: string) { + if (isAbsolute(output)) { + return output; + } else { + return relative( + dirname(input), + output, + ); + } +} diff --git a/src/command/render/pandoc.ts b/src/command/render/pandoc.ts index 1f7f614ed27..0a281e1cb70 100644 --- a/src/command/render/pandoc.ts +++ b/src/command/render/pandoc.ts @@ -43,6 +43,7 @@ import { isIpynbOutput, isLatexOutput, isMarkdownOutput, + isTypstOutput, } from "../../config/format.ts"; import { isIncludeMetadata, @@ -735,6 +736,7 @@ export async function runPandoc( // crossref filter so we only do this if the user hasn't disabled the crossref filter if ( !isLatexOutput(options.format.pandoc) && + !isTypstOutput(options.format.pandoc) && !isMarkdownOutput(options.format) && crossrefFilterActive(options) ) { delete allDefaults[kNumberSections]; diff --git a/src/config/constants.ts b/src/config/constants.ts index e085123d99d..df63222cfa5 100644 --- a/src/config/constants.ts +++ b/src/config/constants.ts @@ -59,6 +59,7 @@ export const kShortcodes = "shortcodes"; export const kKeepMd = "keep-md"; export const kKeepTex = "keep-tex"; +export const kKeepTyp = "keep-typ"; export const kKeepIpynb = "keep-ipynb"; export const kKeepSource = "keep-source"; export const kVariant = "variant"; @@ -140,6 +141,7 @@ export const kExecuteDefaultsKeys = [ export const kRenderDefaultsKeys = [ kKeepTex, + kKeepTyp, kKeepSource, kKeepHidden, kVariant, @@ -429,6 +431,7 @@ export const kPdfEngineOpts = "pdf-engine-opts"; export const kPdfEngineOpt = "pdf-engine-opt"; export const kListings = "listings"; export const kNumberSections = "number-sections"; +export const kSectionNumbering = "section-numbering"; export const kNumberOffset = "number-offset"; export const kShiftHeadingLevelBy = "shift-heading-level-by"; export const kNumberDepth = "number-depth"; diff --git a/src/config/format.ts b/src/config/format.ts index a17c4953913..08c35d17003 100644 --- a/src/config/format.ts +++ b/src/config/format.ts @@ -20,6 +20,15 @@ export function isLatexOutput(format: FormatPandoc) { return ["pdf", "latex", "beamer"].includes(format.to || ""); } +export function isTypstOutput(format: string): boolean; +export function isTypstOutput(format: FormatPandoc): boolean; +export function isTypstOutput(format: string | FormatPandoc) { + if (typeof (format) !== "string") { + format = format?.to || "html"; + } + return format === "typst"; +} + export function isBeamerOutput(format: FormatPandoc) { return ["beamer"].includes(format.to || ""); } diff --git a/src/config/metadata.ts b/src/config/metadata.ts index 977fd19ca08..d2994476626 100644 --- a/src/config/metadata.ts +++ b/src/config/metadata.ts @@ -27,6 +27,7 @@ import { kIpynbFilters, kKeepMd, kKeepTex, + kKeepTyp, kLanguageDefaults, kLanguageDefaultsKeys, kMetadataFile, @@ -126,6 +127,7 @@ export function formatFromMetadata( if (debug) { mergedFormat.execute[kKeepMd] = true; mergedFormat.render[kKeepTex] = true; + mergedFormat.render[kKeepTyp] = true; } return mergedFormat; diff --git a/src/config/types.ts b/src/config/types.ts index be91183fca5..443c61152da 100644 --- a/src/config/types.ts +++ b/src/config/types.ts @@ -93,6 +93,7 @@ import { kKeepMd, kKeepSource, kKeepTex, + kKeepTyp, kLatexAutoInstall, kLatexAutoMk, kLatexClean, @@ -378,6 +379,7 @@ export interface Format { export interface FormatRender { [kKeepTex]?: boolean; + [kKeepTyp]?: boolean; [kKeepSource]?: boolean; [kKeepHidden]?: boolean; [kPreferHtml]?: boolean; diff --git a/src/core/handlers/dot.ts b/src/core/handlers/dot.ts index 9d3ffa6c6ce..b7095edcc06 100644 --- a/src/core/handlers/dot.ts +++ b/src/core/handlers/dot.ts @@ -12,6 +12,7 @@ import { isJavascriptCompatible, isLatexOutput, isRevealjsOutput, + isTypstOutput, } from "../../config/format.ts"; import { QuartoMdCell } from "../lib/break-quarto-md.ts"; import { mappedConcat, mappedIndexToLineCol } from "../lib/mapped-text.ts"; @@ -106,6 +107,9 @@ const dotHandler: LanguageHandler = { isLatexOutput(handlerContext.options.format.pandoc) ? ` fig-env='${cell.options?.["fig-env"] || "figure"}'` : ""; + const heightOffset = isTypstOutput(handlerContext.options.format.pandoc) + ? 0.1 + : 0.0; let posSpecifier = ""; if ( isLatexOutput(handlerContext.options.format.pandoc) && @@ -123,7 +127,7 @@ const dotHandler: LanguageHandler = { ? `width="${Math.round(width * 100) / 100}in"` : ""; const heightSpecifier = height - ? ` height="${Math.round(height * 100) / 100}in"` + ? ` height="${(Math.round(height * 100) / 100) + heightOffset}in"` : ""; const captionSpecifier = includeCaption ? (cell.options?.["fig-cap"] || "") diff --git a/src/core/mime.ts b/src/core/mime.ts index f2df321110d..7066ef0b473 100644 --- a/src/core/mime.ts +++ b/src/core/mime.ts @@ -111,4 +111,5 @@ const MEDIA_TYPES: Record = { ".mediawiki": kTextPlain, ".xwiki": kTextPlain, ".zim": kTextPlain, + ".typ": kTextPlain, }; diff --git a/src/core/typst.ts b/src/core/typst.ts new file mode 100644 index 00000000000..0e98a31d107 --- /dev/null +++ b/src/core/typst.ts @@ -0,0 +1,84 @@ +/* +* typst.ts +* +* Copyright (C) 2022 Posit Software, PBC +* +*/ + +import { info } from "log/mod.ts"; +import { basename, dirname } from "path/mod.ts"; +import { execProcess } from "./process.ts"; + +export async function typstCompile( + input: string, + output: string, + quiet = false, +) { + if (!quiet) { + typstProgress(input, output); + } + const cmd = ["typst", input, output]; + const result = await execProcess({ cmd }); + if (!quiet && result.success) { + typstProgressDone(); + } + return result; +} + +// TODO: this doesn't yet work correclty (typst exits on the first change to the typ file) +// leaving the code here anyway as a foundation for getting it to work later +export async function typstWatch( + input: string, + output: string, + quiet = false, +) { + if (!quiet) { + typstProgress(input, output); + } + + // abort controller + const controller = new AbortController(); + + // setup command + const cmd = new Deno.Command("typst", { + args: [input, output, "--watch"], + cwd: dirname(input), + stdout: "piped", + stderr: "piped", + signal: controller.signal, + }); + + // spawn it + cmd.spawn(); + + // wait for ready + let allOutput = ""; + const decoder = new TextDecoder(); + for await (const chunk of cmd.stderr) { + const text = decoder.decode(chunk); + allOutput += text; + if (allOutput.includes("compiled successfully")) { + if (!quiet) { + typstProgressDone(); + } + cmd.status.then((status) => { + console.log(`typst exited with status ${status.code}`); + }); + break; + } + } + + // return the abort controller + return controller; +} + +function typstProgress(input: string, output: string) { + info( + `[typst]: Compiling ${basename(input)} to ${basename(output)}...`, + { newline: false }, + ); +} + +function typstProgressDone() { + info("DONE\n"); +} diff --git a/src/format/formats-shared.ts b/src/format/formats-shared.ts index c93ace972a8..e3421791686 100644 --- a/src/format/formats-shared.ts +++ b/src/format/formats-shared.ts @@ -43,6 +43,7 @@ import { kKeepMd, kKeepSource, kKeepTex, + kKeepTyp, kLang, kLatexAutoInstall, kLatexAutoMk, @@ -220,6 +221,7 @@ function defaultFormat(displayName: string): Format { }, render: { [kKeepTex]: false, + [kKeepTyp]: false, [kKeepSource]: false, [kKeepHidden]: false, [kPreferHtml]: false, diff --git a/src/format/formats.ts b/src/format/formats.ts index 2be07d85159..f649748f55e 100644 --- a/src/format/formats.ts +++ b/src/format/formats.ts @@ -46,6 +46,7 @@ import { pandocMarkdownFormat, } from "./markdown/format-markdown.ts"; import { jatsFormat } from "./jats/format-jats.ts"; +import { typstFormat } from "./typst/format-typst.ts"; import { mergePandocVariant } from "../config/metadata.ts"; import { writerFormatHandlers } from "./format-handlers.ts"; @@ -224,6 +225,10 @@ export function defaultWriterFormat(to: string): Format { writerFormat = bibliographyFormat("CSL-JSON", "csl"); break; + case "typst": + writerFormat = typstFormat(); + break; + case "texttile": writerFormat = plaintextFormat("Textile", to); break; diff --git a/src/format/typst/format-typst.ts b/src/format/typst/format-typst.ts new file mode 100644 index 00000000000..35a58a95863 --- /dev/null +++ b/src/format/typst/format-typst.ts @@ -0,0 +1,73 @@ +/* +* format-typst.ts +* +* Copyright (C) 2020-2022 Posit Software, PBC +* +*/ + +// TODO: incremental compile for preview + +import { RenderServices } from "../../command/render/types.ts"; +import { + kDefaultImageExtension, + kFigFormat, + kFigHeight, + kFigWidth, + kNumberSections, + kSectionNumbering, + kShiftHeadingLevelBy, +} from "../../config/constants.ts"; +import { Format, FormatExtras, PandocFlags } from "../../config/types.ts"; +import { createFormat } from "../formats-shared.ts"; + +export function typstFormat(): Format { + return createFormat("Typst", "pdf", { + execute: { + [kFigWidth]: 5.5, + [kFigHeight]: 3.5, + [kFigFormat]: "svg", + }, + pandoc: { + standalone: true, + [kDefaultImageExtension]: "svg", + }, + formatExtras: ( + _input: string, + markdown: string, + flags: PandocFlags, + format: Format, + _libDir: string, + _services: RenderServices, + ) => { + const extras: FormatExtras = {}; + + if ( + (flags?.[kNumberSections] === true || + format.pandoc[kNumberSections] === true) + ) { + // number-sections imples section-numbering + if (!format.metadata?.[kSectionNumbering]) { + extras.metadata = { + [kSectionNumbering]: "1.1.a", + }; + } + + // pdfs with numbered sections and no other level oriented options get + // their heading level shifted by -1. also don't shift if there are h1 + // headings (nowhere to shift to!) + const hasLevelOneHeadings = !!markdown.match(/\n^#\s.*$/gm); + if ( + !hasLevelOneHeadings && + flags?.[kShiftHeadingLevelBy] === undefined && + format.pandoc?.[kShiftHeadingLevelBy] === undefined + ) { + extras.pandoc = { + [kShiftHeadingLevelBy]: -1, + }; + } + } + + return extras; + }, + }); +} diff --git a/src/resources/editor/tools/vs-code.mjs b/src/resources/editor/tools/vs-code.mjs index efdc6683b06..3b938757354 100644 --- a/src/resources/editor/tools/vs-code.mjs +++ b/src/resources/editor/tools/vs-code.mjs @@ -12701,7 +12701,8 @@ var require_yaml_intelligence_resources = __commonJS({ formats: [ "$html-doc", "context", - "$pdf-all" + "$pdf-all", + "typst" ] }, description: { @@ -12731,7 +12732,8 @@ var require_yaml_intelligence_resources = __commonJS({ formats: [ "$html-doc", "context", - "$pdf-all" + "$pdf-all", + "typst" ] }, description: { @@ -13909,10 +13911,11 @@ var require_yaml_intelligence_resources = __commonJS({ schema: "string", tags: { formats: [ - "$pdf-all" + "$pdf-all", + "typst" ] }, - description: "The paper size for the document." + description: "The paper size for the document.\n" }, { name: "layout", @@ -14735,6 +14738,16 @@ var require_yaml_intelligence_resources = __commonJS({ long: 'Offset for section headings in output (offsets are 0 by default)\nThe first number is added to the section number for\ntop-level headings, the second for second-level headings, and so on.\nSo, for example, if you want the first top-level heading in your\ndocument to be numbered "6", specify `number-offset: 5`. If your\ndocument starts with a level-2 heading which you want to be numbered\n"1.5", specify `number-offset: [1,4]`. Implies `number-sections`\n' } }, + { + name: "section-numbering", + tags: { + formats: [ + "typst" + ] + }, + schema: "string", + description: "Schema to use for numbering sections, e.g. `1.A.1`" + }, { name: "shift-heading-level-by", schema: "number", @@ -15629,6 +15642,17 @@ var require_yaml_intelligence_resources = __commonJS({ }, description: "Filters to pre-process ipynb files before rendering to markdown" }, + { + name: "keep-typ", + tags: { + formats: [ + "typst" + ] + }, + schema: "boolean", + default: false, + description: "Keep the intermediate typst file used during render." + }, { name: "keep-tex", tags: { @@ -15839,12 +15863,54 @@ var require_yaml_intelligence_resources = __commonJS({ name: "margin", tags: { formats: [ - "revealjs" + "revealjs", + "typst" + ] + }, + schema: { + anyOf: [ + "number", + { + object: { + closed: true, + properties: { + x: { + string: { + description: "Horizontal margin (e.g. 5cm)" + } + }, + y: { + string: { + description: "Vertical margin (e.g. 5cm)" + } + }, + top: { + string: { + description: "Top margin (e.g. 5cm)" + } + }, + bottom: { + string: { + description: "Bottom margin (e.g. 5cm)" + } + }, + left: { + string: { + description: "Left margin (e.g. 5cm)" + } + }, + right: { + string: { + description: "Right margin (e.g. 5cm)" + } + } + } + } + } ] }, - schema: "number", default: 0.1, - description: "Factor of the display size that should remain empty around the content" + description: "For `revealjs`, the factor of the display size that should remain empty around the content (e.g. 0.1).\n\nFor `typst`, a dictionary with the fields defined in the Typst documentation:\n`x`, `y`, `top`, `bottom`, `left`, `right` (margins are specified in `cm` units,\ne.g. `5cm`).\n" }, { name: "min-scale", @@ -16933,13 +16999,14 @@ var require_yaml_intelligence_resources = __commonJS({ "!$office-all", "!$odt-all", "!$html-all", - "!$docbook-all" + "!$docbook-all", + "typst" ] }, schema: "number", description: { - short: "Specify length of lines in characters.", - long: "Specify length of lines in characters. This affects text wrapping in generated source\ncode (see `wrap`). It also affects calculation of column widths for plain text\ntables.\n" + short: "For text formats, specify length of lines in characters. For `typst`, number of columns for body text.", + long: "Specify length of lines in characters. This affects text wrapping in generated source\ncode (see `wrap`). It also affects calculation of column widths for plain text\ntables. \n\nFor `typst`, number of columns for body text.\n" } }, { @@ -17372,6 +17439,7 @@ var require_yaml_intelligence_resources = __commonJS({ "tei", "texinfo", "textile", + "typst", "xwiki", "zimwiki", "md" @@ -19647,6 +19715,7 @@ var require_yaml_intelligence_resources = __commonJS({ "Whether cross references should be hyper-linked.", "The title used for appendix.", "The delimiter beween appendix number and title.", + "Enables a hover popup for cross references that shows the item being\nreferenced.", "Visual editor configuration", "Default editing mode for document", "Markdown writing options for visual editor", @@ -21104,7 +21173,8 @@ var require_yaml_intelligence_resources = __commonJS({ }, "Disambiguating year suffix in author-date styles (e.g. \u201Ca\u201D in \u201CDoe,\n1999a\u201D).", "internal-schema-hack", - "Enables a hover popup for cross references that shows the item being\nreferenced." + "Schema to use for numbering sections, e.g. 1.A.1", + "Keep the intermediate typst file used during render." ], "schema/external-schemas.yml": [ { @@ -21328,12 +21398,12 @@ var require_yaml_intelligence_resources = __commonJS({ mermaid: "%%" }, "handlers/mermaid/schema.yml": { - _internalId: 153686, + _internalId: 153849, type: "object", description: "be an object", properties: { "mermaid-format": { - _internalId: 153678, + _internalId: 153841, type: "enum", enum: [ "png", @@ -21349,7 +21419,7 @@ var require_yaml_intelligence_resources = __commonJS({ exhaustiveCompletions: true }, theme: { - _internalId: 153685, + _internalId: 153848, type: "anyOf", anyOf: [ { diff --git a/src/resources/editor/tools/yaml/web-worker.js b/src/resources/editor/tools/yaml/web-worker.js index 5d7530fe378..bb61fd5e6b4 100644 --- a/src/resources/editor/tools/yaml/web-worker.js +++ b/src/resources/editor/tools/yaml/web-worker.js @@ -12702,7 +12702,8 @@ try { formats: [ "$html-doc", "context", - "$pdf-all" + "$pdf-all", + "typst" ] }, description: { @@ -12732,7 +12733,8 @@ try { formats: [ "$html-doc", "context", - "$pdf-all" + "$pdf-all", + "typst" ] }, description: { @@ -13910,10 +13912,11 @@ try { schema: "string", tags: { formats: [ - "$pdf-all" + "$pdf-all", + "typst" ] }, - description: "The paper size for the document." + description: "The paper size for the document.\n" }, { name: "layout", @@ -14736,6 +14739,16 @@ try { long: 'Offset for section headings in output (offsets are 0 by default)\nThe first number is added to the section number for\ntop-level headings, the second for second-level headings, and so on.\nSo, for example, if you want the first top-level heading in your\ndocument to be numbered "6", specify `number-offset: 5`. If your\ndocument starts with a level-2 heading which you want to be numbered\n"1.5", specify `number-offset: [1,4]`. Implies `number-sections`\n' } }, + { + name: "section-numbering", + tags: { + formats: [ + "typst" + ] + }, + schema: "string", + description: "Schema to use for numbering sections, e.g. `1.A.1`" + }, { name: "shift-heading-level-by", schema: "number", @@ -15630,6 +15643,17 @@ try { }, description: "Filters to pre-process ipynb files before rendering to markdown" }, + { + name: "keep-typ", + tags: { + formats: [ + "typst" + ] + }, + schema: "boolean", + default: false, + description: "Keep the intermediate typst file used during render." + }, { name: "keep-tex", tags: { @@ -15840,12 +15864,54 @@ try { name: "margin", tags: { formats: [ - "revealjs" + "revealjs", + "typst" + ] + }, + schema: { + anyOf: [ + "number", + { + object: { + closed: true, + properties: { + x: { + string: { + description: "Horizontal margin (e.g. 5cm)" + } + }, + y: { + string: { + description: "Vertical margin (e.g. 5cm)" + } + }, + top: { + string: { + description: "Top margin (e.g. 5cm)" + } + }, + bottom: { + string: { + description: "Bottom margin (e.g. 5cm)" + } + }, + left: { + string: { + description: "Left margin (e.g. 5cm)" + } + }, + right: { + string: { + description: "Right margin (e.g. 5cm)" + } + } + } + } + } ] }, - schema: "number", default: 0.1, - description: "Factor of the display size that should remain empty around the content" + description: "For `revealjs`, the factor of the display size that should remain empty around the content (e.g. 0.1).\n\nFor `typst`, a dictionary with the fields defined in the Typst documentation:\n`x`, `y`, `top`, `bottom`, `left`, `right` (margins are specified in `cm` units,\ne.g. `5cm`).\n" }, { name: "min-scale", @@ -16934,13 +17000,14 @@ try { "!$office-all", "!$odt-all", "!$html-all", - "!$docbook-all" + "!$docbook-all", + "typst" ] }, schema: "number", description: { - short: "Specify length of lines in characters.", - long: "Specify length of lines in characters. This affects text wrapping in generated source\ncode (see `wrap`). It also affects calculation of column widths for plain text\ntables.\n" + short: "For text formats, specify length of lines in characters. For `typst`, number of columns for body text.", + long: "Specify length of lines in characters. This affects text wrapping in generated source\ncode (see `wrap`). It also affects calculation of column widths for plain text\ntables. \n\nFor `typst`, number of columns for body text.\n" } }, { @@ -17373,6 +17440,7 @@ try { "tei", "texinfo", "textile", + "typst", "xwiki", "zimwiki", "md" @@ -19648,6 +19716,7 @@ try { "Whether cross references should be hyper-linked.", "The title used for appendix.", "The delimiter beween appendix number and title.", + "Enables a hover popup for cross references that shows the item being\nreferenced.", "Visual editor configuration", "Default editing mode for document", "Markdown writing options for visual editor", @@ -21105,7 +21174,8 @@ try { }, "Disambiguating year suffix in author-date styles (e.g. \u201Ca\u201D in \u201CDoe,\n1999a\u201D).", "internal-schema-hack", - "Enables a hover popup for cross references that shows the item being\nreferenced." + "Schema to use for numbering sections, e.g. 1.A.1", + "Keep the intermediate typst file used during render." ], "schema/external-schemas.yml": [ { @@ -21329,12 +21399,12 @@ try { mermaid: "%%" }, "handlers/mermaid/schema.yml": { - _internalId: 153686, + _internalId: 153849, type: "object", description: "be an object", properties: { "mermaid-format": { - _internalId: 153678, + _internalId: 153841, type: "enum", enum: [ "png", @@ -21350,7 +21420,7 @@ try { exhaustiveCompletions: true }, theme: { - _internalId: 153685, + _internalId: 153848, type: "anyOf", anyOf: [ { diff --git a/src/resources/editor/tools/yaml/yaml-intelligence-resources.json b/src/resources/editor/tools/yaml/yaml-intelligence-resources.json index 5f33b3db45a..09df207a729 100644 --- a/src/resources/editor/tools/yaml/yaml-intelligence-resources.json +++ b/src/resources/editor/tools/yaml/yaml-intelligence-resources.json @@ -5677,7 +5677,8 @@ "formats": [ "$html-doc", "context", - "$pdf-all" + "$pdf-all", + "typst" ] }, "description": { @@ -5707,7 +5708,8 @@ "formats": [ "$html-doc", "context", - "$pdf-all" + "$pdf-all", + "typst" ] }, "description": { @@ -6885,10 +6887,11 @@ "schema": "string", "tags": { "formats": [ - "$pdf-all" + "$pdf-all", + "typst" ] }, - "description": "The paper size for the document." + "description": "The paper size for the document.\n" }, { "name": "layout", @@ -7711,6 +7714,16 @@ "long": "Offset for section headings in output (offsets are 0 by default)\nThe first number is added to the section number for\ntop-level headings, the second for second-level headings, and so on.\nSo, for example, if you want the first top-level heading in your\ndocument to be numbered \"6\", specify `number-offset: 5`. If your\ndocument starts with a level-2 heading which you want to be numbered\n\"1.5\", specify `number-offset: [1,4]`. Implies `number-sections`\n" } }, + { + "name": "section-numbering", + "tags": { + "formats": [ + "typst" + ] + }, + "schema": "string", + "description": "Schema to use for numbering sections, e.g. `1.A.1`" + }, { "name": "shift-heading-level-by", "schema": "number", @@ -8605,6 +8618,17 @@ }, "description": "Filters to pre-process ipynb files before rendering to markdown" }, + { + "name": "keep-typ", + "tags": { + "formats": [ + "typst" + ] + }, + "schema": "boolean", + "default": false, + "description": "Keep the intermediate typst file used during render." + }, { "name": "keep-tex", "tags": { @@ -8815,12 +8839,54 @@ "name": "margin", "tags": { "formats": [ - "revealjs" + "revealjs", + "typst" + ] + }, + "schema": { + "anyOf": [ + "number", + { + "object": { + "closed": true, + "properties": { + "x": { + "string": { + "description": "Horizontal margin (e.g. 5cm)" + } + }, + "y": { + "string": { + "description": "Vertical margin (e.g. 5cm)" + } + }, + "top": { + "string": { + "description": "Top margin (e.g. 5cm)" + } + }, + "bottom": { + "string": { + "description": "Bottom margin (e.g. 5cm)" + } + }, + "left": { + "string": { + "description": "Left margin (e.g. 5cm)" + } + }, + "right": { + "string": { + "description": "Right margin (e.g. 5cm)" + } + } + } + } + } ] }, - "schema": "number", "default": 0.1, - "description": "Factor of the display size that should remain empty around the content" + "description": "For `revealjs`, the factor of the display size that should remain empty around the content (e.g. 0.1).\n\nFor `typst`, a dictionary with the fields defined in the Typst documentation:\n`x`, `y`, `top`, `bottom`, `left`, `right` (margins are specified in `cm` units,\ne.g. `5cm`).\n" }, { "name": "min-scale", @@ -9909,13 +9975,14 @@ "!$office-all", "!$odt-all", "!$html-all", - "!$docbook-all" + "!$docbook-all", + "typst" ] }, "schema": "number", "description": { - "short": "Specify length of lines in characters.", - "long": "Specify length of lines in characters. This affects text wrapping in generated source\ncode (see `wrap`). It also affects calculation of column widths for plain text\ntables.\n" + "short": "For text formats, specify length of lines in characters. For `typst`, number of columns for body text.", + "long": "Specify length of lines in characters. This affects text wrapping in generated source\ncode (see `wrap`). It also affects calculation of column widths for plain text\ntables. \n\nFor `typst`, number of columns for body text.\n" } }, { @@ -10348,6 +10415,7 @@ "tei", "texinfo", "textile", + "typst", "xwiki", "zimwiki", "md" @@ -12623,6 +12691,7 @@ "Whether cross references should be hyper-linked.", "The title used for appendix.", "The delimiter beween appendix number and title.", + "Enables a hover popup for cross references that shows the item being\nreferenced.", "Visual editor configuration", "Default editing mode for document", "Markdown writing options for visual editor", @@ -14080,7 +14149,8 @@ }, "Disambiguating year suffix in author-date styles (e.g. “a” in “Doe,\n1999a”).", "internal-schema-hack", - "Enables a hover popup for cross references that shows the item being\nreferenced." + "Schema to use for numbering sections, e.g. 1.A.1", + "Keep the intermediate typst file used during render." ], "schema/external-schemas.yml": [ { @@ -14304,12 +14374,12 @@ "mermaid": "%%" }, "handlers/mermaid/schema.yml": { - "_internalId": 153686, + "_internalId": 153849, "type": "object", "description": "be an object", "properties": { "mermaid-format": { - "_internalId": 153678, + "_internalId": 153841, "type": "enum", "enum": [ "png", @@ -14325,7 +14395,7 @@ "exhaustiveCompletions": true }, "theme": { - "_internalId": 153685, + "_internalId": 153848, "type": "anyOf", "anyOf": [ { diff --git a/src/resources/filters/crossref/equations.lua b/src/resources/filters/crossref/equations.lua index 616c14f09f5..312df6802ca 100644 --- a/src/resources/filters/crossref/equations.lua +++ b/src/resources/filters/crossref/equations.lua @@ -47,6 +47,11 @@ function processEquations(blockEl) targetInlines:insert(pandoc.RawInline("latex", "\\begin{equation}")) targetInlines:insert(pandoc.Span(pandoc.RawInline("latex", eq.text), pandoc.Attr(label))) targetInlines:insert(pandoc.RawInline("latex", "\\label{" .. label .. "}\\end{equation}")) + elseif _quarto.format.isTypstOutput() then + targetInlines:insert(pandoc.RawInline("typst", + "#set math.equation(numbering: \"(" .. inlinesToString(numberOption("eq", order)) .. ")\"); " .. + "$ " .. eq.text .. " $ <" .. label .. "> #set math.equation(numbering: none)" + )) else local eqNumber = eqQquad local mathMethod = param("html-math-method", nil) diff --git a/src/resources/filters/crossref/figures.lua b/src/resources/filters/crossref/figures.lua index 4288b143ecf..a6f58e03ee2 100644 --- a/src/resources/filters/crossref/figures.lua +++ b/src/resources/filters/crossref/figures.lua @@ -44,7 +44,7 @@ function processFigure(el, captionContent) tprepend(captionContent, { pandoc.RawInline('latex', '\\label{' .. label .. '}') }) - elseif _quarto.format.isAsciiDocOutput() then + elseif _quarto.format.isAsciiDocOutput() or _quarto.format.isTypstOutput() then el.attr.identifier = label else tprepend(captionContent, figureTitlePrefix(order)) diff --git a/src/resources/filters/crossref/refs.lua b/src/resources/filters/crossref/refs.lua index ee327c6f4f8..041d08e3e2f 100644 --- a/src/resources/filters/crossref/refs.lua +++ b/src/resources/filters/crossref/refs.lua @@ -69,6 +69,8 @@ function resolveRefs() ref:extend({pandoc.RawInline('latex', '\\ref{' .. label .. '}')}) elseif _quarto.format.isAsciiDocOutput() then ref = pandoc.List({pandoc.RawInline('asciidoc', '<<' .. label .. '>>')}) + elseif _quarto.format.isTypstOutput() then + ref = pandoc.List({pandoc.RawInline('typst', '@' .. label)}) else if not resolve then local refClasses = pandoc.List({"quarto-unresolved-ref"}) diff --git a/src/resources/filters/crossref/sections.lua b/src/resources/filters/crossref/sections.lua index 04821df726c..e0ef0e054b5 100644 --- a/src/resources/filters/crossref/sections.lua +++ b/src/resources/filters/crossref/sections.lua @@ -98,6 +98,7 @@ end function numberSections() return not _quarto.format.isLatexOutput() and + not _quarto.format.isTypstOutput and not _quarto.format.isMarkdownOutput() and numberSectionsOptionEnabled() end diff --git a/src/resources/filters/normalize/pandoc3.lua b/src/resources/filters/normalize/pandoc3.lua index 34c037c7628..23908d37b92 100644 --- a/src/resources/filters/normalize/pandoc3.lua +++ b/src/resources/filters/normalize/pandoc3.lua @@ -54,11 +54,11 @@ function parse_pandoc3_figures() end function render_pandoc3_figures() - -- only do this in jats because other formats emit
inadvertently otherwise + -- only do this in jats and typst because other formats emit
inadvertently otherwise -- with potentially bad captions. -- -- this will change with new crossref system anyway. - if not _quarto.format.isJatsOutput() then + if not _quarto.format.isJatsOutput() and not _quarto.format.isTypstOutput() then return {} end diff --git a/src/resources/formats/pdf/pandoc/latex.template b/src/resources/formats/pdf/pandoc/latex.template index f5c8b0dbd61..341bf258968 100644 --- a/src/resources/formats/pdf/pandoc/latex.template +++ b/src/resources/formats/pdf/pandoc/latex.template @@ -33,6 +33,9 @@ $if(background-image)$ \usebackgroundtemplate{% \includegraphics[width=\paperwidth]{$background-image$}% } +% In beamer background-image does not work well when other images are used, so this is the workaround +\pgfdeclareimage[width=\paperwidth,height=\paperheight]{background}{$background-image$} +\usebackgroundtemplate{\pgfuseimage{background}} $endif$ \usepackage{pgfpages} \setbeamertemplate{caption}[numbered] @@ -289,7 +292,12 @@ $if(svg)$ $endif$ $if(strikeout)$ $-- also used for underline -\usepackage{soul} +\ifLuaTeX + \usepackage{luacolor} + \usepackage[soul]{lua-ul} +\else + \usepackage{soul} +\fi $endif$ \setlength{\emergencystretch}{3em} % prevent overfull lines \providecommand{\tightlist}{% @@ -355,13 +363,16 @@ $if(babel-lang)$ $if(mainfont)$ \ifPDFTeX \else -\babelfont[$babel-lang$]{rm}{$mainfont$} +\babelfont[$babel-lang$]{rm}[$for(mainfontoptions)$$mainfontoptions$$sep$,$endfor$]{$mainfont$} \fi $endif$ $endif$ $for(babel-otherlangs)$ \babelprovide[import]{$babel-otherlangs$} $endfor$ +$for(babelfonts/pairs)$ +\babelfont[$babelfonts.key$]{rm}{$babelfonts.value$} +$endfor$ % get rid of language-specific shorthands (see #6817): \let\LanguageShortHands\languageshorthands \def\languageshorthands#1{} diff --git a/src/resources/formats/pdf/pandoc/template.tex b/src/resources/formats/pdf/pandoc/template.tex index e7424e648a5..9a25d73d05c 100644 --- a/src/resources/formats/pdf/pandoc/template.tex +++ b/src/resources/formats/pdf/pandoc/template.tex @@ -15,6 +15,9 @@ \usebackgroundtemplate{% \includegraphics[width=\paperwidth]{$background-image$}% } +% In beamer background-image does not work well when other images are used, so this is the workaround +\pgfdeclareimage[width=\paperwidth,height=\paperheight]{background}{$background-image$} +\usebackgroundtemplate{\pgfuseimage{background}} $endif$ \usepackage{pgfpages} \setbeamertemplate{caption}[numbered] @@ -231,7 +234,12 @@ $endif$ $if(strikeout)$ $-- also used for underline -\usepackage{soul} +\ifLuaTeX + \usepackage{luacolor} + \usepackage[soul]{lua-ul} +\else + \usepackage{soul} +\fi $endif$ \setlength{\emergencystretch}{3em} % prevent overfull lines $if(numbersections)$ @@ -265,10 +273,19 @@ \fi $if(babel-lang)$ \babelprovide[main,import]{$babel-lang$} +$if(mainfont)$ +\ifPDFTeX +\else +\babelfont[$babel-lang$]{rm}[$for(mainfontoptions)$$mainfontoptions$$sep$,$endfor$]{$mainfont$} +\fi +$endif$ $endif$ $for(babel-otherlangs)$ \babelprovide[import]{$babel-otherlangs$} $endfor$ +$for(babelfonts/pairs)$ +\babelfont[$babelfonts.key$]{rm}{$babelfonts.value$} +$endfor$ % get rid of language-specific shorthands (see #6817): \let\LanguageShortHands\languageshorthands \def\languageshorthands#1{} diff --git a/src/resources/pandoc/datadir/_format.lua b/src/resources/pandoc/datadir/_format.lua index b91a9f39f21..85b26f35357 100644 --- a/src/resources/pandoc/datadir/_format.lua +++ b/src/resources/pandoc/datadir/_format.lua @@ -232,6 +232,10 @@ local function isJatsOutput() return tcontains(formats, FORMAT) end +local function isTypstOutput() + return FORMAT == "typst" +end + return { isAsciiDocOutput = isAsciiDocOutput, @@ -258,6 +262,6 @@ return { isJsonOutput = isJsonOutput, isAstOutput = isAstOutput, isJatsOutput = isJatsOutput, - + isTypstOutput = isTypstOutput, parse_format = parse_format } \ No newline at end of file diff --git a/src/resources/schema/document-fonts.yml b/src/resources/schema/document-fonts.yml index 29b058576a7..616d43d142c 100644 --- a/src/resources/schema/document-fonts.yml +++ b/src/resources/schema/document-fonts.yml @@ -1,7 +1,7 @@ - name: mainfont schema: string tags: - formats: [$html-doc, context, $pdf-all] + formats: [$html-doc, context, $pdf-all, typst] description: short: Sets the main font for the document. long: | @@ -37,7 +37,7 @@ - name: fontsize schema: string tags: - formats: [$html-doc, context, $pdf-all] + formats: [$html-doc, context, $pdf-all, typst] description: short: Sets the main font size for the document. long: | diff --git a/src/resources/schema/document-layout.yml b/src/resources/schema/document-layout.yml index 5394f943d82..82ac9817169 100644 --- a/src/resources/schema/document-layout.yml +++ b/src/resources/schema/document-layout.yml @@ -44,8 +44,9 @@ - name: papersize schema: string tags: - formats: [$pdf-all] - description: The paper size for the document. + formats: [$pdf-all, typst] + description: | + The paper size for the document. - name: layout schema: diff --git a/src/resources/schema/document-numbering.yml b/src/resources/schema/document-numbering.yml index 93aa248b8fd..6bc28658c4e 100644 --- a/src/resources/schema/document-numbering.yml +++ b/src/resources/schema/document-numbering.yml @@ -48,6 +48,12 @@ document starts with a level-2 heading which you want to be numbered "1.5", specify `number-offset: [1,4]`. Implies `number-sections` +- name: section-numbering + tags: + formats: [typst] + schema: string + description: "Schema to use for numbering sections, e.g. `1.A.1`" + - name: shift-heading-level-by schema: number description: diff --git a/src/resources/schema/document-render.yml b/src/resources/schema/document-render.yml index 96de8cbddcb..9cec2efda5c 100644 --- a/src/resources/schema/document-render.yml +++ b/src/resources/schema/document-render.yml @@ -120,6 +120,13 @@ contexts: [document-execute] description: "Filters to pre-process ipynb files before rendering to markdown" +- name: keep-typ + tags: + formats: [typst] + schema: boolean + default: false + description: "Keep the intermediate typst file used during render." + - name: keep-tex tags: formats: [pdf, beamer] diff --git a/src/resources/schema/document-reveal-layout.yml b/src/resources/schema/document-reveal-layout.yml index bd068166cf4..c31ee9b8cc2 100644 --- a/src/resources/schema/document-reveal-layout.yml +++ b/src/resources/schema/document-reveal-layout.yml @@ -33,10 +33,38 @@ - name: margin tags: - formats: [revealjs] - schema: number + formats: [revealjs, typst] + schema: + anyOf: + - number + - object: + closed: true + properties: + x: + string: + description: "Horizontal margin (e.g. 5cm)" + y: + string: + description: "Vertical margin (e.g. 5cm)" + top: + string: + description: "Top margin (e.g. 5cm)" + bottom: + string: + description: "Bottom margin (e.g. 5cm)" + left: + string: + description: "Left margin (e.g. 5cm)" + right: + string: + description: "Right margin (e.g. 5cm)" default: 0.1 - description: "Factor of the display size that should remain empty around the content" + description: | + For `revealjs`, the factor of the display size that should remain empty around the content (e.g. 0.1). + + For `typst`, a dictionary with the fields defined in the Typst documentation: + `x`, `y`, `top`, `bottom`, `left`, `right` (margins are specified in `cm` units, + e.g. `5cm`). - name: min-scale tags: diff --git a/src/resources/schema/document-text.yml b/src/resources/schema/document-text.yml index 08cf4c60b96..9fd12052096 100644 --- a/src/resources/schema/document-text.yml +++ b/src/resources/schema/document-text.yml @@ -20,14 +20,23 @@ - name: columns tags: formats: - ["!$pdf-all", "!$office-all", "!$odt-all", "!$html-all", "!$docbook-all"] + [ + "!$pdf-all", + "!$office-all", + "!$odt-all", + "!$html-all", + "!$docbook-all", + "typst", + ] schema: number description: - short: Specify length of lines in characters. + short: For text formats, specify length of lines in characters. For `typst`, number of columns for body text. long: | Specify length of lines in characters. This affects text wrapping in generated source code (see `wrap`). It also affects calculation of column widths for plain text - tables. + tables. + + For `typst`, number of columns for body text. - name: tab-stop tags: diff --git a/src/resources/schema/format-aliases.yml b/src/resources/schema/format-aliases.yml index 43269bb3ab0..fb1eae861e9 100644 --- a/src/resources/schema/format-aliases.yml +++ b/src/resources/schema/format-aliases.yml @@ -77,6 +77,7 @@ aliases: tei, texinfo, textile, + typst, xwiki, zimwiki,