Skip to content

Commit 44e0f48

Browse files
committed
Merge branch 'main' into chore/1.6-pandoc-update
2 parents dd6717f + 1399d2e commit 44e0f48

File tree

101 files changed

+14233
-903
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

101 files changed

+14233
-903
lines changed

src/command/render/output-typst.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,7 @@ export function typstPdfOutputRecipe(
6565
const pdfOutput = join(inputDir, inputStem + ".pdf");
6666
const typstOptions: TypstCompileOptions = {
6767
quiet: options.flags?.quiet,
68-
// use recipe that may have been modified, not format which has not
69-
fontPaths: asArray(recipe.format.metadata?.[kFontPaths]) as string[],
68+
fontPaths: asArray(format.metadata?.[kFontPaths]) as string[],
7069
};
7170
if (project?.dir) {
7271
typstOptions.rootDir = project.dir;

src/command/render/pandoc-dependencies-html.ts

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -138,25 +138,6 @@ export function readAndInjectDependencies(
138138
});
139139
}
140140

141-
// this should be resolveMetadata returning an object
142-
// like {'output-recipe': metadata}
143-
export function resolveTypstFontPaths(
144-
dependenciesFile: string,
145-
) {
146-
const dependencyJsonStream = Deno.readTextFileSync(dependenciesFile);
147-
const fontPaths: string[] = [];
148-
lines(dependencyJsonStream).forEach((json) => {
149-
if (json) {
150-
const dependency = JSON.parse(json);
151-
if (dependency.type === "typst-font-path") {
152-
const path = dependency?.content?.path;
153-
fontPaths.push(path);
154-
}
155-
}
156-
});
157-
return fontPaths;
158-
}
159-
160141
export function resolveDependencies(
161142
extras: FormatExtras,
162143
inputDir: string,

src/command/render/pandoc-html.ts

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export async function resolveSassBundles(
5353
extras: FormatExtras,
5454
format: Format,
5555
temp: TempContext,
56-
project?: ProjectContext,
56+
project: ProjectContext,
5757
) {
5858
extras = cloneDeep(extras);
5959

@@ -286,6 +286,29 @@ async function resolveQuartoSyntaxHighlighting(
286286
if (themeDescriptor) {
287287
// Other variables that need to be injected (if any)
288288
const extraVariables = extras.html?.[kQuartoCssVariables] || [];
289+
for (let i = 0; i < extraVariables.length; ++i) {
290+
// For the same reason as outlined in https://github.com/rstudio/bslib/issues/1104,
291+
// we need to patch the text to include a semicolon inside the declaration
292+
// if it doesn't have one.
293+
// This happens because scss-parser is brittle, and will fail to parse a declaration
294+
// if it doesn't end with a semicolon.
295+
//
296+
// In addition, we know that some our variables come from the output
297+
// of sassCompile which
298+
// - misses the last semicolon
299+
// - emits a :root declaration
300+
// - triggers the scss-parser bug
301+
// So we'll attempt to target the last declaration in the :root
302+
// block specifically and add a semicolon if it doesn't have one.
303+
let variable = extraVariables[i].trim();
304+
if (
305+
variable.endsWith("}") && variable.startsWith(":root") &&
306+
!variable.match(/.*;\s?}$/)
307+
) {
308+
variable = variable.slice(0, -1) + ";}";
309+
extraVariables[i] = variable;
310+
}
311+
}
289312

290313
// The text highlighting CSS variables
291314
const highlightCss = generateThemeCssVars(themeDescriptor.json);
@@ -308,7 +331,7 @@ async function resolveQuartoSyntaxHighlighting(
308331
// Add this string literal to the rule set, which prevents pandoc
309332
// from inlining this style sheet
310333
// See https://github.com/jgm/pandoc/commit/7c0a80c323f81e6262848bfcfc922301e3f406e0
311-
rules.push(".prevent-inlining { content: '</' }");
334+
rules.push(".prevent-inlining { content: '</'; }");
312335

313336
// Compile the scss
314337
const highlightCssPath = await compileSass(

src/command/render/pandoc.ts

Lines changed: 133 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { basename, dirname, isAbsolute, join } from "../../deno_ral/path.ts";
88

99
import { info } from "../../deno_ral/log.ts";
1010

11-
import { existsSync, expandGlobSync } from "fs/mod.ts";
11+
import { ensureDir, existsSync, expandGlobSync } from "fs/mod.ts";
1212

1313
import { stringify } from "yaml/mod.ts";
1414
import { encodeBase64 } from "encoding/base64.ts";
@@ -139,7 +139,6 @@ import { kDefaultHighlightStyle } from "./constants.ts";
139139
import {
140140
HtmlPostProcessor,
141141
HtmlPostProcessResult,
142-
OutputRecipe,
143142
PandocOptions,
144143
RunPandocResult,
145144
} from "./types.ts";
@@ -176,7 +175,6 @@ import { resolveAndFormatDate, resolveDate } from "../../core/date.ts";
176175
import { katexPostProcessor } from "../../format/html/format-html-math.ts";
177176
import {
178177
readAndInjectDependencies,
179-
resolveTypstFontPaths,
180178
writeDependencies,
181179
} from "./pandoc-dependencies-html.ts";
182180
import {
@@ -420,13 +418,13 @@ export async function runPandoc(
420418
);
421419

422420
const extras = await resolveExtras(
421+
options.source,
423422
inputExtras,
424423
options.format,
425424
cwd,
426425
options.libDir,
427426
options.services.temp,
428427
dependenciesFile,
429-
options.recipe,
430428
options.project,
431429
);
432430

@@ -1283,14 +1281,14 @@ function cleanupPandocMetadata(metadata: Metadata) {
12831281
}
12841282

12851283
async function resolveExtras(
1284+
input: string,
12861285
extras: FormatExtras, // input format extras (project, format, brand)
12871286
format: Format,
12881287
inputDir: string,
12891288
libDir: string,
12901289
temp: TempContext,
12911290
dependenciesFile: string,
1292-
recipe: OutputRecipe,
1293-
project?: ProjectContext,
1291+
project: ProjectContext,
12941292
) {
12951293
// resolve format resources
12961294
await writeFormatResources(
@@ -1346,15 +1344,135 @@ async function resolveExtras(
13461344

13471345
// perform typst-specific merging
13481346
if (isTypstOutput(format.pandoc)) {
1349-
extras.postprocessors = extras.postprocessors || [];
1350-
extras.postprocessors.push(async () => {
1351-
// gw: IMO this could be way more general as resolveMetadata
1352-
// returning all metadata found in the file
1353-
// then apply output-recipe and any others found using mergeConfigs
1354-
// would not be format-specific
1355-
const fontPaths = await resolveTypstFontPaths(dependenciesFile);
1356-
recipe.format.metadata[kFontPaths] = fontPaths;
1357-
});
1347+
const brand = await project.resolveBrand(input);
1348+
const fontdirs: Set<string> = new Set();
1349+
const base_urls = {
1350+
google: "https://fonts.googleapis.com/css",
1351+
bunny: "https://fonts.bunny.net/css",
1352+
};
1353+
const ttf_urls = [], woff_urls: Array<string> = [];
1354+
if (brand?.data.typography) {
1355+
const fonts = brand.data.typography.fonts || [];
1356+
for (const font of fonts) {
1357+
if (font.source === "file") {
1358+
for (const file of font.files || []) {
1359+
const path = typeof file === "object" ? file.path : file;
1360+
fontdirs.add(dirname(join(brand.brandDir, path)));
1361+
}
1362+
} else if (font.source === "bunny") {
1363+
console.log(
1364+
"Font bunny is not yet supported for Typst, skipping",
1365+
font.family,
1366+
);
1367+
} else if (font.source === "google" /* || font.source === "bunny" */) {
1368+
let { family, style, weight } = font;
1369+
const parts = [family!];
1370+
if (style) {
1371+
style = Array.isArray(style) ? style : [style];
1372+
parts.push(style.join(","));
1373+
}
1374+
if (weight) {
1375+
weight = Array.isArray(weight) ? weight : [weight];
1376+
parts.push(weight.join(","));
1377+
}
1378+
const response = await fetch(
1379+
`${base_urls[font.source]}?family=${parts.join(":")}`,
1380+
);
1381+
const lines = (await response.text()).split("\n");
1382+
for (const line of lines) {
1383+
const sourcelist = line.match(/^ *src: (.*); *$/);
1384+
if (sourcelist) {
1385+
const sources = sourcelist[1].split(",").map((s) => s.trim());
1386+
const failed_formats = [];
1387+
for (const source of sources) {
1388+
const match = source.match(
1389+
/url\(([^)]*)\) *format\('([^)]*)'\)/,
1390+
);
1391+
if (match) {
1392+
const [_, url, format] = match;
1393+
if (["truetype", "opentype"].includes(format)) {
1394+
ttf_urls.push(url);
1395+
break;
1396+
}
1397+
// else if (["woff", "woff2"].includes(format)) {
1398+
// woff_urls.push(url);
1399+
// break;
1400+
// }
1401+
failed_formats.push(format);
1402+
}
1403+
}
1404+
console.log(
1405+
"skipping",
1406+
family,
1407+
"\nnot currently able to use formats",
1408+
failed_formats.join(", "),
1409+
);
1410+
}
1411+
}
1412+
}
1413+
}
1414+
}
1415+
if (ttf_urls.length || woff_urls.length) {
1416+
const font_cache = join(brand!.projectDir, ".quarto", "typst-font-cache");
1417+
const url_to_path = (url: string) => url.replace(/^https?:\/\//, "");
1418+
const cached = async (url: string) => {
1419+
const path = url_to_path(url);
1420+
try {
1421+
await Deno.lstat(join(font_cache, path));
1422+
return true;
1423+
} catch (err) {
1424+
if (!(err instanceof Deno.errors.NotFound)) {
1425+
throw err;
1426+
}
1427+
return false;
1428+
}
1429+
};
1430+
const download = async (url: string) => {
1431+
const path = url_to_path(url);
1432+
await ensureDir(
1433+
join(font_cache, dirname(path)),
1434+
);
1435+
1436+
const response = await fetch(url);
1437+
const blob = await response.blob();
1438+
const buffer = await blob.arrayBuffer();
1439+
const bytes = new Uint8Array(buffer);
1440+
await Deno.writeFile(join(font_cache, path), bytes);
1441+
};
1442+
const woff2ttf = async (url: string) => {
1443+
const path = url_to_path(url);
1444+
await Deno.run({ cmd: ["ttx", join(font_cache, path)] });
1445+
await Deno.run({
1446+
cmd: ["ttx", join(font_cache, path.replace(/woff2?$/, "ttx"))],
1447+
});
1448+
};
1449+
const ttf_urls2: Array<string> = [], woff_urls2: Array<string> = [];
1450+
await Promise.all(ttf_urls.map(async (url) => {
1451+
if (!await cached(url)) {
1452+
ttf_urls2.push(url);
1453+
}
1454+
}));
1455+
1456+
await woff_urls.reduce((cur, next) => {
1457+
return cur.then(() => woff2ttf(next));
1458+
}, Promise.resolve());
1459+
// await Promise.all(woff_urls.map(async (url) => {
1460+
// if (!await cached(url)) {
1461+
// woff_urls2.push(url);
1462+
// }
1463+
// }));
1464+
await Promise.all(ttf_urls2.concat(woff_urls2).map(download));
1465+
if (woff_urls2.length) {
1466+
await Promise.all(woff_urls2.map(woff2ttf));
1467+
}
1468+
fontdirs.add(font_cache);
1469+
}
1470+
let fontPaths = format.metadata[kFontPaths] as Array<string> || [];
1471+
if (typeof fontPaths === "string") {
1472+
fontPaths = [fontPaths];
1473+
}
1474+
fontPaths.push(...fontdirs);
1475+
format.metadata[kFontPaths] = fontPaths;
13581476
}
13591477

13601478
// Process format resources

src/command/render/render.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,6 @@ export async function renderPandoc(
191191
metadata: executeResult.metadata,
192192
quiet,
193193
flags: context.options.flags,
194-
recipe,
195194
};
196195

197196
// add offset if we are in a project

src/command/render/types.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -226,9 +226,6 @@ export interface PandocOptions {
226226

227227
// optional offset from file to project dir
228228
offset?: string;
229-
230-
// output recipe (this makes many of above options redundant)
231-
recipe: OutputRecipe;
232229
}
233230

234231
// command line flags that we need to inspect

0 commit comments

Comments
 (0)