Skip to content

Commit be337d3

Browse files
claude: fix typst brand logo paths for subdirectory documents (#13917)
Transform brand logo paths to project-absolute (with / prefix) before merging with document logo metadata via resolveLogo. This ensures brand paths resolve correctly via Typst --root while document-sourced paths remain input-relative. Closes #13917. Removes the projectOffset() hack from typst-brand-yaml.lua that was indiscriminately prepending ../ to all logo paths. Co-Authored-By: Claude Opus 4.6 <[email protected]>
1 parent 7027d8b commit be337d3

28 files changed

Lines changed: 191 additions & 7 deletions

File tree

news/changelog-1.9.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ All changes included in 1.9:
6464
- ([#13745](https://github.com/quarto-dev/quarto-cli/issues/13745)): Fix relative `font-paths` from extensions or document metadata not resolving correctly for Typst compilation. Relative paths are now resolved against the document directory before being passed to the Typst CLI.
6565
- ([#13775](https://github.com/quarto-dev/quarto-cli/issues/13775)): Fix brand fonts not being applied when using `citeproc: true` with Typst format. Format detection now properly handles Pandoc format variants like `typst-citations`.
6666
- ([#13868](https://github.com/quarto-dev/quarto-cli/issues/13868)): Add image alt text support for PDF/UA accessibility. Alt text from markdown captions and explicit `alt` attributes is now passed to Typst's `image()` function. (Temporary workaround until [jgm/pandoc#11394](https://github.com/jgm/pandoc/pull/11394) is merged.)
67+
- ([#13917](https://github.com/quarto-dev/quarto-cli/issues/13917)): Fix brand logo paths not resolving correctly for Typst documents in project subdirectories. Brand logo paths are now converted to project-absolute paths before merging with document metadata, replacing the fragile `projectOffset()` workaround.
6768
- ([#13249](https://github.com/quarto-dev/quarto-cli/pull/13249)): Update to Pandoc's Typst template following Pandoc 3.8.3 and Typst 0.14.2 support:
6869
- Code syntax highlighting now uses Skylighting by default.
6970
- New template variables `mathfont`, `codefont`, and `linestretch` for font and line spacing customization.

src/core/brand/brand.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,45 @@ export function logoAddLeadingSlashes(
425425
};
426426
}
427427

428+
// Return a copy of the brand with logo paths converted from project-relative
429+
// to project-absolute (leading /). Typst resolves these via --root, which
430+
// points to the project directory. Call this before resolveLogo so that
431+
// brand-sourced paths get the / prefix while document-sourced paths are
432+
// left untouched.
433+
export function brandWithAbsoluteLogoPaths(
434+
brand: LightDarkBrand | undefined,
435+
): LightDarkBrand | undefined {
436+
if (!brand) {
437+
return brand;
438+
}
439+
const transformBrand = (b: Brand | undefined): Brand | undefined => {
440+
if (!b) return b;
441+
const oldLogo = b.processedData.logo;
442+
const logo: ProcessedBrandData["logo"] = { images: {} };
443+
for (const size of Zod.BrandNamedLogo.options) {
444+
if (oldLogo[size]) {
445+
logo[size] = {
446+
...oldLogo[size],
447+
path: ensureLeadingSlashIfNotExternal(oldLogo[size]!.path),
448+
};
449+
}
450+
}
451+
for (const [key, value] of Object.entries(oldLogo.images)) {
452+
logo.images[key] = {
453+
...value,
454+
path: ensureLeadingSlashIfNotExternal(value.path),
455+
};
456+
}
457+
const copy = Object.create(b) as Brand;
458+
copy.processedData = { ...b.processedData, logo };
459+
return copy;
460+
};
461+
return {
462+
light: transformBrand(brand.light),
463+
dark: transformBrand(brand.dark),
464+
};
465+
}
466+
428467
// this a typst workaround but might as well write it as a proper function
429468
export function fillLogoPaths(
430469
brand: LightDarkBrand | undefined,

src/format/typst/format-typst.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,11 @@ import {
3737
BrandNamedLogo,
3838
LogoLightDarkSpecifier,
3939
} from "../../resources/types/schema-types.ts";
40-
import { fillLogoPaths, resolveLogo } from "../../core/brand/brand.ts";
40+
import {
41+
brandWithAbsoluteLogoPaths,
42+
fillLogoPaths,
43+
resolveLogo,
44+
} from "../../core/brand/brand.ts";
4145
import { LogoLightDarkSpecifierPathOptional } from "../../resources/types/zod/schema-types.ts";
4246

4347
const typstBookExtension: BookExtension = {
@@ -98,6 +102,10 @@ export function typstFormat(): Format {
98102
}
99103

100104
const brand = format.render.brand;
105+
// For Typst, convert brand logo paths to project-absolute (with /)
106+
// before merging with document logo metadata. Typst resolves / paths
107+
// via --root which points to the project directory.
108+
const typstBrand = brandWithAbsoluteLogoPaths(brand);
101109
const logoSpec = format
102110
.metadata[kLogo] as LogoLightDarkSpecifierPathOptional;
103111
const sizeOrder: BrandNamedLogo[] = [
@@ -108,8 +116,8 @@ export function typstFormat(): Format {
108116
// temporary: if document logo has object or light/dark objects
109117
// without path, do our own findLogo to add the path
110118
// typst is the exception not needing path but we'll probably deprecate this
111-
const logo = fillLogoPaths(brand, logoSpec, sizeOrder);
112-
format.metadata[kLogo] = resolveLogo(brand, logo, sizeOrder);
119+
const logo = fillLogoPaths(typstBrand, logoSpec, sizeOrder);
120+
format.metadata[kLogo] = resolveLogo(typstBrand, logo, sizeOrder);
113121
// force columns to wrap and move any 'columns' setting to metadata
114122
const columns = format.pandoc[kColumns];
115123
if (columns) {

src/resources/filters/quarto-post/typst-brand-yaml.lua

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -328,10 +328,6 @@ function render_typst_brand_yaml()
328328
imageFilename = imageFilename and imageFilename:gsub('\\_', '_')
329329
else
330330
-- backslashes need to be doubled for Windows
331-
if imageFilename[1] ~= "/" and _quarto.projectOffset() ~= "." then
332-
local offset = _quarto.projectOffset()
333-
imageFilename = pandoc.path.join({offset, imageFilename})
334-
end
335331
imageFilename = string.gsub(imageFilename, '\\', '\\\\')
336332
end
337333
logoOptions.path = pandoc.RawInline('typst', imageFilename)
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/.quarto/
2+
*.pdf
3+
4+
**/*.quarto_ipynb
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
logo:
2+
images:
3+
main:
4+
path: logo.svg
5+
small: main
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
project:
2+
type: default
3+
format:
4+
typst: default
Lines changed: 1 addition & 0 deletions
Loading
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
title: "Logo from _brand.yml in subdirectory"
3+
format:
4+
typst:
5+
keep-typ: true
6+
_quarto:
7+
tests:
8+
typst:
9+
ensureTypstFileRegexMatches:
10+
-
11+
- 'image\("/logo\.svg"'
12+
-
13+
- 'image\("\.\./\.\./logo\.svg"'
14+
---
15+
16+
Hello world.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/.quarto/
2+
*.pdf
3+
4+
**/*.quarto_ipynb

0 commit comments

Comments
 (0)