Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions news/changelog-1.9.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,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`

Expand Down
5 changes: 1 addition & 4 deletions src/core/brand/brand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { InternalError } from "../lib/error.ts";
import { dirname, join, relative, resolve } from "../../deno_ral/path.ts";
import { warnOnce } from "../log.ts";
import { isCssColorName } from "../css/color-names.ts";
import { isExternalPath } from "../url.ts";
import {
LogoLightDarkSpecifierPathOptional,
LogoOptionsPathOptional,
Expand Down Expand Up @@ -272,10 +273,6 @@ export class Brand {
}
}

function isExternalPath(path: string) {
return /^\w+:/.test(path);
}

export type LightDarkBrand = {
light?: Brand;
dark?: Brand;
Expand Down
6 changes: 5 additions & 1 deletion src/core/sass/brand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { Brand } from "../brand/brand.ts";
import { darkModeDefault } from "../../format/html/format-html-info.ts";
import { kBrandMode } from "../../config/constants.ts";
import { join, relative } from "../../deno_ral/path.ts";
import { isExternalPath } from "../url.ts";

const defaultColorNameMap: Record<string, string> = {
"link-color": "link",
Expand Down Expand Up @@ -162,9 +163,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`);
Expand Down
13 changes: 8 additions & 5 deletions src/core/url.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
/*
* url.ts
*
* Copyright (C) 2020-2022 Posit Software, PBC
*
*/
* url.ts
*
* Copyright (C) 2020-2022 Posit Software, PBC
*/

import { ensureTrailingSlash, pathWithForwardSlashes } from "./path.ts";

export function isHttpUrl(url: string) {
return /^https?:/i.test(url);
}

export function isExternalPath(path: string) {
return /^\w+:/.test(path);
}

export function joinUrl(baseUrl: string, path: string) {
const baseHasSlash = baseUrl.endsWith("/");

Expand Down
5 changes: 1 addition & 4 deletions src/project/types/website/website-navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { Document, Element } from "../../../core/deno-dom.ts";

import { pathWithForwardSlashes, safeExistsSync } from "../../../core/path.ts";
import { resourcePath } from "../../../core/resources.ts";
import { isExternalPath } from "../../../core/url.ts";
import { renderEjs } from "../../../core/ejs.ts";
import { warnOnce } from "../../../core/log.ts";
import { asHtmlId } from "../../../core/html.ts";
Expand Down Expand Up @@ -1540,10 +1541,6 @@ function navigationDependency(resource: string) {
};
}

function isExternalPath(path: string) {
return /^\w+:/.test(path);
}

function resolveNavReferences(
collection: unknown | Array<unknown> | Record<string, unknown>,
) {
Expand Down
5 changes: 1 addition & 4 deletions src/project/types/website/website-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Document, Element } from "../../../core/deno-dom.ts";
import { getDecodedAttribute } from "../../../core/html.ts";
import { resolveInputTarget } from "../../project-index.ts";
import { pathWithForwardSlashes, safeExistsSync } from "../../../core/path.ts";
import { isExternalPath } from "../../../core/url.ts";
import { projectOffset, projectOutputDir } from "../../project-shared.ts";
import { engineValidExtensions } from "../../../execute/engine.ts";
import { ProjectContext } from "../../types.ts";
Expand Down Expand Up @@ -119,7 +120,3 @@ export async function resolveProjectInputLinks(
}
}
}

function isExternalPath(path: string) {
return /^\w+:/.test(path);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/.quarto/
**/*.quarto_ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
title: My Brand
author: Quarto
version: 1.0.0
quarto-required: ">=99.9.0"
contributes:
metadata:
project:
brand: mybrand.yml
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
project:
type: default
format:
html:
theme: brand
Original file line number Diff line number Diff line change
@@ -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 >}}
2 changes: 2 additions & 0 deletions tests/smoke/smoke-all.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -171,6 +172,7 @@ function resolveTestSpecs(
const result = [];
// deno-lint-ignore no-explicit-any
const verifyMap: Record<string, any> = {
ensureCssRegexMatches,
ensureEpubFileRegexMatches,
ensureHtmlElements,
ensureHtmlElementContents,
Expand Down
59 changes: 58 additions & 1 deletion tests/verify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/

import { existsSync, walkSync } from "../src/deno_ral/fs.ts";
import { DOMParser, NodeList } from "../src/core/deno-dom.ts";
import { DOMParser, Element, NodeList } from "../src/core/deno-dom.ts";
import { assert } from "testing/asserts";
import { basename, dirname, join, relative, resolve } from "../src/deno_ral/path.ts";
import { parseXmlDocument } from "slimdom";
Expand Down Expand Up @@ -584,6 +584,63 @@ export const ensureFileRegexMatches = (
return(verifyFileRegexMatches(regexChecker)(file, matchesUntyped, noMatchesUntyped));
};

// Use this function to Regex match text in CSS files linked from the HTML document
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[]) => {
// Parse the HTML file to find linked CSS files
const htmlContent = await Deno.readTextFile(file);
const doc = new DOMParser().parseFromString(htmlContent, "text/html")!;
const [dir] = dirAndStem(file);

// Find all stylesheet links and read their content
let combinedContent = "";
const links = doc.querySelectorAll('link[rel="stylesheet"]');
for (const link of links) {
const href = (link as Element).getAttribute("href");
if (href && !href.startsWith("http://") && !href.startsWith("https://")) {
const cssPath = join(dir, href);
try {
combinedContent += await Deno.readTextFile(cssPath) + "\n";
} catch {
// Skip files that don't exist (e.g., external URLs we couldn't parse)
}
}
}

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 = (
Expand Down
Loading