diff --git a/news/changelog-1.9.md b/news/changelog-1.9.md index 14f341aa6d9..1f55af4301e 100644 --- a/news/changelog-1.9.md +++ b/news/changelog-1.9.md @@ -32,6 +32,7 @@ All changes included in 1.9: - ([#11929](https://github.com/quarto-dev/quarto-cli/issues/11929)): Import all `brand.typography.fonts` in CSS, whether or not fonts are referenced by typography elements. - ([#13413](https://github.com/quarto-dev/quarto-cli/issues/13413)): Fix uncentered play button in `video` shortcodes from cross-reference divs. (author: @bruvellu) - ([#13508](https://github.com/quarto-dev/quarto-cli/issues/13508)): Add `aria-label` support to `video` shortcode for improved accessibility. +- ([#13685](https://github.com/quarto-dev/quarto-cli/issues/13685)): Fix remote font URLs in brand extensions being incorrectly joined with the extension path, resulting in broken font imports. ### `typst` diff --git a/src/core/sass/brand.ts b/src/core/sass/brand.ts index 993fb5eac0a..51036ccbddc 100644 --- a/src/core/sass/brand.ts +++ b/src/core/sass/brand.ts @@ -150,6 +150,8 @@ const googleFontImportString = (description: BrandFontGoogle) => { }:${styleString}wght@${weights}&display=${display}');`; }; +const isExternalPath = (path: string) => /^\w+:/.test(path); + const fileFontImportString = (brand: Brand, description: BrandFontFile) => { const pathPrefix = relative(brand.projectDir, brand.brandDir); const parts = []; @@ -162,9 +164,12 @@ const fileFontImportString = (brand: Brand, description: BrandFontFile) => { weight = file.weight; style = file.style; } + const fontUrl = isExternalPath(path) + ? path + : join(pathPrefix, path).replace(/\\/g, "/"); parts.push(`@font-face { font-family: '${description.family}'; - src: url('${join(pathPrefix, path).replace(/\\/g, "/")}'); + src: url('${fontUrl}'); font-weight: ${weight || "normal"}; font-style: ${style || "normal"}; }\n`); diff --git a/tests/docs/smoke-all/brand/typography/remote-font-extension/.gitignore b/tests/docs/smoke-all/brand/typography/remote-font-extension/.gitignore new file mode 100644 index 00000000000..ad293093b07 --- /dev/null +++ b/tests/docs/smoke-all/brand/typography/remote-font-extension/.gitignore @@ -0,0 +1,2 @@ +/.quarto/ +**/*.quarto_ipynb diff --git a/tests/docs/smoke-all/brand/typography/remote-font-extension/_extensions/my-brand/_extension.yml b/tests/docs/smoke-all/brand/typography/remote-font-extension/_extensions/my-brand/_extension.yml new file mode 100644 index 00000000000..859925eab79 --- /dev/null +++ b/tests/docs/smoke-all/brand/typography/remote-font-extension/_extensions/my-brand/_extension.yml @@ -0,0 +1,8 @@ +title: My Brand +author: Quarto +version: 1.0.0 +quarto-required: ">=99.9.0" +contributes: + metadata: + project: + brand: mybrand.yml diff --git a/tests/docs/smoke-all/brand/typography/remote-font-extension/_extensions/my-brand/mybrand.yml b/tests/docs/smoke-all/brand/typography/remote-font-extension/_extensions/my-brand/mybrand.yml new file mode 100644 index 00000000000..1f8fcf43c9d --- /dev/null +++ b/tests/docs/smoke-all/brand/typography/remote-font-extension/_extensions/my-brand/mybrand.yml @@ -0,0 +1,8 @@ +typography: + fonts: + - family: Noto Sans + source: file + files: + - path: https://notofonts.github.io/latin-greek-cyrillic/fonts/NotoSans/unhinted/ttf/NotoSans-Regular.ttf + base: + family: Noto Sans diff --git a/tests/docs/smoke-all/brand/typography/remote-font-extension/_quarto.yml b/tests/docs/smoke-all/brand/typography/remote-font-extension/_quarto.yml new file mode 100644 index 00000000000..f98ff850162 --- /dev/null +++ b/tests/docs/smoke-all/brand/typography/remote-font-extension/_quarto.yml @@ -0,0 +1,5 @@ +project: + type: default +format: + html: + theme: brand diff --git a/tests/docs/smoke-all/brand/typography/remote-font-extension/remote-font-extension.qmd b/tests/docs/smoke-all/brand/typography/remote-font-extension/remote-font-extension.qmd new file mode 100644 index 00000000000..25812585524 --- /dev/null +++ b/tests/docs/smoke-all/brand/typography/remote-font-extension/remote-font-extension.qmd @@ -0,0 +1,15 @@ +--- +title: Remote Font Extension Test +_quarto: + tests: + html: + ensureCssRegexMatches: + - ['src:url\("https://notofonts\.github\.io/'] + - ['_extensions/my-brand/https:'] +--- + +# Remote Font Test + +This document tests that remote font URLs in brand extensions are handled correctly (issue #13685). + +{{< lipsum 1 >}} diff --git a/tests/smoke/smoke-all.test.ts b/tests/smoke/smoke-all.test.ts index b5aaafa6897..b92c9479aff 100644 --- a/tests/smoke/smoke-all.test.ts +++ b/tests/smoke/smoke-all.test.ts @@ -18,6 +18,7 @@ import { breakQuartoMd } from "../../src/core/lib/break-quarto-md.ts"; import { parse } from "../../src/core/yaml.ts"; import { cleanoutput } from "./render/render.ts"; import { + ensureCssRegexMatches, ensureEpubFileRegexMatches, ensureDocxRegexMatches, ensureDocxXpath, @@ -171,6 +172,7 @@ function resolveTestSpecs( const result = []; // deno-lint-ignore no-explicit-any const verifyMap: Record = { + ensureCssRegexMatches, ensureEpubFileRegexMatches, ensureHtmlElements, ensureHtmlElementContents, diff --git a/tests/verify.ts b/tests/verify.ts index a38dbbd01d5..524ad8f5eed 100644 --- a/tests/verify.ts +++ b/tests/verify.ts @@ -584,6 +584,53 @@ export const ensureFileRegexMatches = ( return(verifyFileRegexMatches(regexChecker)(file, matchesUntyped, noMatchesUntyped)); }; +// Use this function to Regex match text in CSS files in the supporting files directory +export const ensureCssRegexMatches = ( + file: string, + matchesUntyped: (string | RegExp)[], + noMatchesUntyped?: (string | RegExp)[], +): Verify => { + const asRegexp = (m: string | RegExp) => { + if (typeof m === "string") { + return new RegExp(m, "m"); + } + return m; + }; + const matches = matchesUntyped.map(asRegexp); + const noMatches = noMatchesUntyped?.map(asRegexp); + + return { + name: `Inspecting CSS files for Regex matches`, + verify: async (_output: ExecuteOutput[]) => { + // Find support directory from file path + const [dir, stem] = dirAndStem(file); + const supportDir = join(dir, stem + "_files"); + + // Find all CSS files recursively and combine their content + let combinedContent = ""; + for (const entry of walkSync(supportDir, { exts: [".css"] })) { + combinedContent += await Deno.readTextFile(entry.path) + "\n"; + } + + matches.forEach((regex) => { + assert( + regex.test(combinedContent), + `Required CSS match ${String(regex)} is missing.`, + ); + }); + + if (noMatches) { + noMatches.forEach((regex) => { + assert( + !regex.test(combinedContent), + `Illegal CSS match ${String(regex)} was found.`, + ); + }); + } + }, + }; +}; + // Use this function to Regex match text in the intermediate kept file // FIXME: do this properly without resorting on file having keep-* export const verifyKeepFileRegexMatches = (