@@ -8,7 +8,7 @@ import { basename, dirname, isAbsolute, join } from "../../deno_ral/path.ts";
88
99import { 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
1313import { stringify } from "yaml/mod.ts" ;
1414import { encodeBase64 } from "encoding/base64.ts" ;
@@ -139,7 +139,6 @@ import { kDefaultHighlightStyle } from "./constants.ts";
139139import {
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";
176175import { katexPostProcessor } from "../../format/html/format-html-math.ts" ;
177176import {
178177 readAndInjectDependencies ,
179- resolveTypstFontPaths ,
180178 writeDependencies ,
181179} from "./pandoc-dependencies-html.ts" ;
182180import {
@@ -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
12851283async 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 ( / ^ * s r c : ( .* ) ; * $ / ) ;
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+ / u r l \( ( [ ^ ) ] * ) \) * f o r m a t \( ' ( [ ^ ) ] * ) ' \) / ,
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 ( / ^ h t t p s ? : \/ \/ / , "" ) ;
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 ( / w o f f 2 ? $ / , "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
0 commit comments