Skip to content

Commit

Permalink
Merge pull request #7786 from quarto-dev/bugfix/issue-7784
Browse files Browse the repository at this point in the history
Typst: use quarto project root when available
  • Loading branch information
cscheid authored Dec 4, 2023
2 parents 7a0a15d + e4e82a4 commit cf526e2
Show file tree
Hide file tree
Showing 10 changed files with 310 additions and 6 deletions.
13 changes: 11 additions & 2 deletions src/command/render/output-typst.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@ import { OutputRecipe, RenderOptions } from "./types.ts";
import { normalizeOutputPath } from "./output-shared.ts";
import {
typstCompile,
TypstCompileOptions,
validateRequiredTypstVersion,
} from "../../core/typst.ts";
import { asArray } from "../../core/array.ts";
import { ProjectContext } from "../../project/types.ts";

export function useTypstPdfOutputRecipe(
format: Format,
Expand All @@ -38,6 +40,7 @@ export function typstPdfOutputRecipe(
finalOutput: string,
options: RenderOptions,
format: Format,
project?: ProjectContext,
): OutputRecipe {
// calculate output and args for pandoc (this is an intermediate file
// which we will then compile to a pdf and rename to .typ)
Expand All @@ -60,11 +63,17 @@ export function typstPdfOutputRecipe(
// run typst
await validateRequiredTypstVersion();
const pdfOutput = join(inputDir, inputStem + ".pdf");
const typstOptions: TypstCompileOptions = {
quiet: options.flags?.quiet,
fontPaths: asArray(format.metadata?.[kFontPaths]) as string[],
};
if (project?.dir) {
typstOptions.rootDir = project.dir;
}
const result = await typstCompile(
input,
pdfOutput,
options.flags?.quiet,
asArray(format.metadata?.[kFontPaths]) as string[],
typstOptions,
);
if (!result.success) {
throw new Error();
Expand Down
8 changes: 7 additions & 1 deletion src/command/render/output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,13 @@ export function outputRecipe(
} else if (useContextPdfOutputRecipe(format, options.flags)) {
return contextPdfOutputRecipe(input, output, options, format);
} else if (useTypstPdfOutputRecipe(format)) {
return typstPdfOutputRecipe(input, output, options, format);
return typstPdfOutputRecipe(
input,
output,
options,
format,
context.project,
);
} else {
// default recipe spec based on user input
const completeActions: VoidFunction[] = [];
Expand Down
18 changes: 15 additions & 3 deletions src/core/typst.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,22 +35,34 @@ function fontPathsArgs(fontPaths?: string[]) {
return fontPathsQuarto.concat(fontExtrasArgs);
}

export type TypstCompileOptions = {
quiet?: boolean;
fontPaths?: string[];
rootDir?: string;
};

export async function typstCompile(
input: string,
output: string,
quiet = false,
fontPaths?: string[],
options: TypstCompileOptions = {},
) {
const quiet = options.quiet ?? false;
const fontPaths = options.fontPaths;
if (!quiet) {
typstProgress(input, output);
}
const cmd = [
typstBinaryPath(),
"compile",
];
if (options.rootDir) {
cmd.push("--root", options.rootDir);
}
cmd.push(
input,
...fontPathsArgs(fontPaths),
output,
];
);
const result = await execProcess({ cmd });
if (!quiet && result.success) {
typstProgressDone();
Expand Down
20 changes: 20 additions & 0 deletions tests/docs/smoke-all/2023/12/04/7784/_quarto.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
project:
type: website

website:
title: "Quarto-typst"
navbar:
left:
- href: index.qmd
text: Home
- about.qmd

format:
html:
theme: cosmo
css: styles.css
toc: true



bibliography: refs.bib
5 changes: 5 additions & 0 deletions tests/docs/smoke-all/2023/12/04/7784/about.qmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
title: "About"
---

About this site
7 changes: 7 additions & 0 deletions tests/docs/smoke-all/2023/12/04/7784/index.qmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
title: "Quarto-typst"
---

This is a Quarto website.

To learn more about Quarto websites visit <https://quarto.org/docs/websites>.
Empty file.
1 change: 1 addition & 0 deletions tests/docs/smoke-all/2023/12/04/7784/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/* css styles */
4 changes: 4 additions & 0 deletions tests/docs/smoke-all/2023/12/04/7784/subdir/index.qmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
title: "subdir index"
format: typst
---
240 changes: 240 additions & 0 deletions tests/docs/smoke-all/2023/12/04/7784/subdir/index.typ
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
// Some definitions presupposed by pandoc's typst output.
#let blockquote(body) = [
#set text( size: 0.92em )
#block(inset: (left: 1.5em, top: 0.2em, bottom: 0.2em))[#body]
]

#let horizontalrule = [
#line(start: (25%,0%), end: (75%,0%))
]

#let endnote(num, contents) = [
#stack(dir: ltr, spacing: 3pt, super[#num], contents)
]

#show terms: it => {
it.children
.map(child => [
#strong[#child.term]
#block(inset: (left: 1.5em, top: -0.4em))[#child.description]
])
.join()
}

// Some quarto-specific definitions.

#show raw.where(block: true): block.with(
fill: luma(230),
width: 100%,
inset: 8pt,
radius: 2pt
)

#let block_with_new_content(old_block, new_content) = {
let d = (:)
let fields = old_block.fields()
fields.remove("body")
if fields.at("below", default: none) != none {
// TODO: this is a hack because below is a "synthesized element"
// according to the experts in the typst discord...
fields.below = fields.below.amount
}
return block.with(..fields)(new_content)
}

#let empty(v) = {
if type(v) == "string" {
// two dollar signs here because we're technically inside
// a Pandoc template :grimace:
v.matches(regex("^\\s*$")).at(0, default: none) != none
} else if type(v) == "content" {
if v.at("text", default: none) != none {
return empty(v.text)
}
for child in v.at("children", default: ()) {
if not empty(child) {
return false
}
}
return true
}

}

#show figure: it => {
let kind_match = it.kind.matches(regex("^quarto-callout-(.*)")).at(0, default: none)
if kind_match != none {
let kind = kind_match.captures.at(0, default: "other")
kind = upper(kind.first()) + kind.slice(1)
// now we pull apart the callout and reassemble it with the crossref name and counter

// when we cleanup pandoc's emitted code to avoid spaces this will have to change
let old_callout = it.body.children.at(1).body.children.at(1)
let old_title_block = old_callout.body.children.at(0)
let old_title = old_title_block.body.body.children.at(2)

// TODO use custom separator if available
let new_title = if empty(old_title) {
[#kind #it.counter.display()]
} else {
[#kind #it.counter.display(): #old_title]
}

let new_title_block = block_with_new_content(
old_title_block,
block_with_new_content(
old_title_block.body,
old_title_block.body.body.children.at(0) +
old_title_block.body.body.children.at(1) +
new_title))

block_with_new_content(old_callout,
new_title_block +
old_callout.body.children.at(1))
} else {
it
}
}

#show ref: it => locate(loc => {
let target = query(it.target, loc).first()
if it.at("supplement", default: none) == none {
it
return
}

let sup = it.supplement.text.matches(regex("^45127368-afa1-446a-820f-fc64c546b2c5%(.*)")).at(0, default: none)
if sup != none {
let parent_id = sup.captures.first()
let parent_figure = query(label(parent_id), loc).first()
let parent_location = parent_figure.location()

let counters = numbering(
parent_figure.at("numbering"),
..parent_figure.at("counter").at(parent_location))

let subcounter = numbering(
target.at("numbering"),
..target.at("counter").at(target.location()))

// NOTE there's a nonbreaking space in the block below
link(target.location(), [#parent_figure.at("supplement") #counters#subcounter])
} else {
it
}
})

// 2023-10-09: #fa-icon("fa-info") is not working, so we'll eval "#fa-info()" instead
#let callout(body: [], title: "Callout", background_color: rgb("#dddddd"), icon: none, icon_color: black) = {
block(
breakable: false,
fill: background_color,
stroke: (paint: icon_color, thickness: 0.5pt, cap: "round"),
width: 100%,
radius: 2pt,
block(
inset: 1pt,
width: 100%,
below: 0pt,
block(
fill: background_color,
width: 100%,
inset: 8pt)[#text(icon_color, weight: 900)[#icon] #title]) +
block(
inset: 1pt,
width: 100%,
block(fill: white, width: 100%, inset: 8pt, body)))
}



#let article(
title: none,
authors: none,
date: none,
abstract: none,
cols: 1,
margin: (x: 1.25in, y: 1.25in),
paper: "us-letter",
lang: "en",
region: "US",
font: (),
fontsize: 11pt,
sectionnumbering: none,
toc: false,
doc,
) = {
set page(
paper: paper,
margin: margin,
numbering: "1",
)
set par(justify: true)
set text(lang: lang,
region: region,
font: font,
size: fontsize)
set heading(numbering: sectionnumbering)

if title != none {
align(center)[#block(inset: 2em)[
#text(weight: "bold", size: 1.5em)[#title]
]]
}

if authors != none {
let count = authors.len()
let ncols = calc.min(count, 3)
grid(
columns: (1fr,) * ncols,
row-gutter: 1.5em,
..authors.map(author =>
align(center)[
#author.name \
#author.affiliation \
#author.email
]
)
)
}

if date != none {
align(center)[#block(inset: 1em)[
#date
]]
}

if abstract != none {
block(inset: 2em)[
#text(weight: "semibold")[Abstract] #h(1em) #abstract
]
}

if toc {
block(above: 0em, below: 2em)[
#outline(
title: auto,
depth: none
);
]
}

if cols == 1 {
doc
} else {
columns(cols, doc)
}
}
#show: doc => article(
title: [subdir index],
cols: 1,
doc,
)






#bibliography("../refs.bib")

0 comments on commit cf526e2

Please sign in to comment.