@quartz-community/quartz-fonts 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 707a13b: Use `configuration.theme.typography` as the fallback when no font options are set, instead of always applying the hard-coded Quartz default fonts (jackyzha0/quartz#2507).
8
+ - 707a13b: Self-hosted fonts now work on localhost and under a base path: font URLs in `quartz-fonts.css` are relative to the stylesheet and the stylesheet link is relative to each page (jackyzha0/quartz#2547). `baseUrl` is no longer required for `fontOrigin: "selfHosted"`.
9
+
3
10
  ## 1.0.0
4
11
 
5
12
  ### Major Changes
package/dist/emitter.js CHANGED
@@ -70,13 +70,13 @@ var fontMimeMap = {
70
70
  opentype: "otf"
71
71
  };
72
72
  var fontUrlPattern = /url\((https:\/\/fonts.gstatic.com\/.+(?:\/|(?:kit=))(.+?)[.&].+?)\)\sformat\('(\w+?)'\);/g;
73
- function processGoogleFonts(stylesheet, baseUrl) {
73
+ function processGoogleFonts(stylesheet) {
74
74
  const fontFiles = [];
75
75
  const processedStylesheet = stylesheet.replace(
76
76
  fontUrlPattern,
77
77
  (match, url, filename, format) => {
78
78
  const extension = fontMimeMap[format] ?? format;
79
- const rewrittenUrl = `https://${baseUrl}/static/fonts/${filename}.${extension}`;
79
+ const rewrittenUrl = `${filename}.${extension}`;
80
80
  fontFiles.push({ url, filename, extension });
81
81
  return match.replace(url, rewrittenUrl);
82
82
  }
@@ -84,10 +84,35 @@ function processGoogleFonts(stylesheet, baseUrl) {
84
84
  return { processedStylesheet, fontFiles };
85
85
  }
86
86
 
87
- // src/emitter.ts
87
+ // src/util/typography.ts
88
88
  var QUARTZ_DEFAULT_HEADER = "Schibsted Grotesk";
89
89
  var QUARTZ_DEFAULT_BODY = "Source Sans Pro";
90
90
  var QUARTZ_DEFAULT_CODE = "IBM Plex Mono";
91
+ function isFontSpecification(value) {
92
+ if (typeof value === "string") return value.trim().length > 0;
93
+ return typeof value === "object" && value !== null && typeof value.name === "string";
94
+ }
95
+ function readConfiguredTypography(ctx) {
96
+ const theme = ctx?.cfg?.configuration?.theme;
97
+ const typography = theme?.typography;
98
+ if (typeof typography !== "object" || typography === null) return {};
99
+ const result = {};
100
+ for (const role of ["title", "header", "body", "code"]) {
101
+ const spec = typography[role];
102
+ if (isFontSpecification(spec)) result[role] = spec;
103
+ }
104
+ return result;
105
+ }
106
+ function resolveFontSpecs(options, typography) {
107
+ return {
108
+ title: options.title ?? typography.title,
109
+ header: options.header ?? typography.header ?? QUARTZ_DEFAULT_HEADER,
110
+ body: options.body ?? typography.body ?? QUARTZ_DEFAULT_BODY,
111
+ code: options.code ?? typography.code ?? QUARTZ_DEFAULT_CODE
112
+ };
113
+ }
114
+
115
+ // src/emitter.ts
91
116
  var defaultOptions = {
92
117
  useThemeFonts: true,
93
118
  fontOrigin: "googleFonts"
@@ -100,19 +125,7 @@ var FontsEmitter = (userOptions) => {
100
125
  if (options.fontOrigin !== "selfHosted") {
101
126
  return;
102
127
  }
103
- const baseUrl = ctx.cfg.configuration.baseUrl;
104
- if (!baseUrl) {
105
- throw new Error("[FontsEmitter] baseUrl is required for selfHosted fonts.");
106
- }
107
- const headerSpec = options.header ?? QUARTZ_DEFAULT_HEADER;
108
- const bodySpec = options.body ?? QUARTZ_DEFAULT_BODY;
109
- const codeSpec = options.code ?? QUARTZ_DEFAULT_CODE;
110
- const href = googleFontHref({
111
- title: options.title,
112
- header: headerSpec,
113
- body: bodySpec,
114
- code: codeSpec
115
- });
128
+ const href = googleFontHref(resolveFontSpecs(options, readConfiguredTypography(ctx)));
116
129
  const cssResponse = await fetch(href);
117
130
  if (!cssResponse.ok) {
118
131
  throw new Error(
@@ -120,7 +133,7 @@ var FontsEmitter = (userOptions) => {
120
133
  );
121
134
  }
122
135
  const cssText = await cssResponse.text();
123
- const { processedStylesheet, fontFiles } = processGoogleFonts(cssText, baseUrl);
136
+ const { processedStylesheet, fontFiles } = processGoogleFonts(cssText);
124
137
  const fontsDir = path.join(ctx.argv.output, "static", "fonts");
125
138
  await fs.promises.mkdir(fontsDir, { recursive: true });
126
139
  for (const fontFile of fontFiles) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/defaults.ts","../src/util/google-fonts.ts","../src/util/process-fonts.ts","../src/emitter.ts"],"names":[],"mappings":"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","file":"emitter.js","sourcesContent":["export const OBSIDIAN_SANS_STACK =\n 'ui-sans-serif, -apple-system, BlinkMacSystemFont, system-ui, \"Segoe UI\", Roboto, \"Inter\", \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", sans-serif';\n\nexport const OBSIDIAN_MONO_STACK =\n 'ui-monospace, SFMono-Regular, \"Cascadia Mono\", \"Roboto Mono\", \"DejaVu Sans Mono\", \"Liberation Mono\", Menlo, Monaco, \"Consolas\", \"Source Code Pro\", monospace';\n\nexport type FontRole = \"title\" | \"header\" | \"body\" | \"code\";\n\nexport const DEFAULT_WEIGHTS: Record<FontRole, number[]> = {\n title: [400, 700],\n header: [400, 700],\n body: [400, 600],\n code: [400, 600],\n};\n\nexport const DEFAULT_ITALIC: Record<FontRole, boolean> = {\n title: false,\n header: false,\n body: true,\n code: false,\n};\n","import type { FontSpecification } from \"../types\";\nimport type { FontRole } from \"../defaults\";\nimport { DEFAULT_WEIGHTS, DEFAULT_ITALIC } from \"../defaults\";\n\nfunction normalizeFontSpec(spec: FontSpecification): {\n name: string;\n weights?: number[];\n includeItalic?: boolean;\n} {\n return typeof spec === \"string\" ? { name: spec } : spec;\n}\n\nexport function getFontName(spec: FontSpecification): string {\n return typeof spec === \"string\" ? spec : spec.name;\n}\n\nexport function formatFontSpecification(role: FontRole, spec: FontSpecification): string {\n const { name, weights, includeItalic } = normalizeFontSpec(spec);\n\n const resolvedWeights = weights ?? DEFAULT_WEIGHTS[role];\n const italic = includeItalic ?? DEFAULT_ITALIC[role];\n\n const features: string[] = [];\n if (italic) {\n features.push(\"ital\");\n }\n\n if (resolvedWeights.length > 1) {\n const weightSpec = italic\n ? resolvedWeights\n .flatMap((w) => [`0,${w}`, `1,${w}`])\n .sort()\n .join(\";\")\n : resolvedWeights.join(\";\");\n\n features.push(`wght@${weightSpec}`);\n }\n\n if (features.length > 0) {\n return `${name}:${features.join(\",\")}`;\n }\n\n return name;\n}\n\ninterface MergedFontEntry {\n name: string;\n weights: Set<number>;\n italic: boolean;\n}\n\nfunction mergeInto(\n map: Map<string, MergedFontEntry>,\n role: FontRole,\n spec: FontSpecification,\n): void {\n const { name, weights, includeItalic } = normalizeFontSpec(spec);\n const resolvedWeights = weights ?? DEFAULT_WEIGHTS[role];\n const italic = includeItalic ?? DEFAULT_ITALIC[role];\n\n const existing = map.get(name);\n if (existing) {\n for (const w of resolvedWeights) existing.weights.add(w);\n if (italic) existing.italic = true;\n } else {\n map.set(name, { name, weights: new Set(resolvedWeights), italic });\n }\n}\n\nfunction formatMergedEntry(entry: MergedFontEntry): string {\n const sortedWeights = [...entry.weights].sort((a, b) => a - b);\n const features: string[] = [];\n\n if (entry.italic) {\n features.push(\"ital\");\n }\n\n if (sortedWeights.length > 1) {\n const weightSpec = entry.italic\n ? sortedWeights\n .flatMap((w) => [`0,${w}`, `1,${w}`])\n .sort()\n .join(\";\")\n : sortedWeights.join(\";\");\n features.push(`wght@${weightSpec}`);\n }\n\n if (features.length > 0) {\n return `${entry.name}:${features.join(\",\")}`;\n }\n return entry.name;\n}\n\nexport function googleFontHref(fonts: {\n title?: FontSpecification;\n header: FontSpecification;\n body: FontSpecification;\n code: FontSpecification;\n}): string {\n const merged = new Map<string, MergedFontEntry>();\n\n mergeInto(merged, \"header\", fonts.header);\n mergeInto(merged, \"body\", fonts.body);\n mergeInto(merged, \"code\", fonts.code);\n\n if (fonts.title) {\n mergeInto(merged, \"title\", fonts.title);\n }\n\n const families = [...merged.values()].map(formatMergedEntry);\n const params = families.map((f) => `family=${encodeURIComponent(f)}`).join(\"&\");\n return `https://fonts.googleapis.com/css2?${params}&display=swap`;\n}\n","import type { ProcessedFontResult, GoogleFontFile } from \"../types\";\n\nconst fontMimeMap: Record<string, string> = {\n truetype: \"ttf\",\n woff: \"woff\",\n woff2: \"woff2\",\n opentype: \"otf\",\n};\n\nconst fontUrlPattern =\n /url\\((https:\\/\\/fonts.gstatic.com\\/.+(?:\\/|(?:kit=))(.+?)[.&].+?)\\)\\sformat\\('(\\w+?)'\\);/g;\n\nexport function processGoogleFonts(stylesheet: string, baseUrl: string): ProcessedFontResult {\n const fontFiles: GoogleFontFile[] = [];\n\n const processedStylesheet = stylesheet.replace(\n fontUrlPattern,\n (match, url: string, filename: string, format: string) => {\n const extension = fontMimeMap[format] ?? format;\n const rewrittenUrl = `https://${baseUrl}/static/fonts/${filename}.${extension}`;\n fontFiles.push({ url, filename, extension });\n return match.replace(url, rewrittenUrl);\n },\n );\n\n return { processedStylesheet, fontFiles };\n}\n","import fs from \"fs\";\nimport path from \"path\";\nimport type { QuartzEmitterPlugin, BuildCtx, FilePath } from \"@quartz-community/types\";\nimport type { FontsOptions } from \"./types\";\nimport { googleFontHref } from \"./util/google-fonts\";\nimport { processGoogleFonts } from \"./util/process-fonts\";\n\nconst QUARTZ_DEFAULT_HEADER = \"Schibsted Grotesk\";\nconst QUARTZ_DEFAULT_BODY = \"Source Sans Pro\";\nconst QUARTZ_DEFAULT_CODE = \"IBM Plex Mono\";\n\nconst defaultOptions: FontsOptions = {\n useThemeFonts: true,\n fontOrigin: \"googleFonts\",\n};\n\nexport const FontsEmitter: QuartzEmitterPlugin<Partial<FontsOptions>> = (\n userOptions?: Partial<FontsOptions>,\n) => {\n const options: FontsOptions = { ...defaultOptions, ...userOptions };\n\n return {\n name: \"FontsEmitter\",\n async *emit(ctx: BuildCtx, _content: unknown[], _resources: unknown): AsyncGenerator<FilePath> {\n if (options.fontOrigin !== \"selfHosted\") {\n return;\n }\n\n const baseUrl = ctx.cfg.configuration.baseUrl;\n if (!baseUrl) {\n throw new Error(\"[FontsEmitter] baseUrl is required for selfHosted fonts.\");\n }\n\n const headerSpec = options.header ?? QUARTZ_DEFAULT_HEADER;\n const bodySpec = options.body ?? QUARTZ_DEFAULT_BODY;\n const codeSpec = options.code ?? QUARTZ_DEFAULT_CODE;\n\n const href = googleFontHref({\n title: options.title,\n header: headerSpec,\n body: bodySpec,\n code: codeSpec,\n });\n\n const cssResponse = await fetch(href);\n if (!cssResponse.ok) {\n throw new Error(\n `[FontsEmitter] Failed to fetch Google Fonts CSS: ${cssResponse.status} ${cssResponse.statusText}`,\n );\n }\n const cssText = await cssResponse.text();\n\n const { processedStylesheet, fontFiles } = processGoogleFonts(cssText, baseUrl);\n\n const fontsDir = path.join(ctx.argv.output, \"static\", \"fonts\");\n await fs.promises.mkdir(fontsDir, { recursive: true });\n\n for (const fontFile of fontFiles) {\n const fontResponse = await fetch(fontFile.url);\n if (!fontResponse.ok) {\n throw new Error(\n `[FontsEmitter] Failed to fetch font file: ${fontFile.url} (${fontResponse.status} ${fontResponse.statusText})`,\n );\n }\n const buf = await fontResponse.arrayBuffer();\n const filePath = path.join(fontsDir, `${fontFile.filename}.${fontFile.extension}`);\n await fs.promises.writeFile(filePath, Buffer.from(buf));\n yield filePath as FilePath;\n }\n\n const cssPath = path.join(fontsDir, \"quartz-fonts.css\");\n await fs.promises.writeFile(cssPath, processedStylesheet);\n yield cssPath as FilePath;\n },\n };\n};\n"]}
1
+ {"version":3,"sources":["../src/defaults.ts","../src/util/google-fonts.ts","../src/util/process-fonts.ts","../src/util/typography.ts","../src/emitter.ts"],"names":[],"mappings":";;;;;;;AAQO,IAAM,eAAA,GAA8C;AAAA,EACzD,KAAA,EAAO,CAAC,GAAA,EAAK,GAAG,CAAA;AAAA,EAChB,MAAA,EAAQ,CAAC,GAAA,EAAK,GAAG,CAAA;AAAA,EACjB,IAAA,EAAM,CAAC,GAAA,EAAK,GAAG,CAAA;AAAA,EACf,IAAA,EAAM,CAAC,GAAA,EAAK,GAAG;AACjB,CAAA;AAEO,IAAM,cAAA,GAA4C;AAAA,EACvD,KAAA,EAAO,KAAA;AAAA,EACP,MAAA,EAAQ,KAAA;AAAA,EACR,IAAA,EAAM,IAAA;AAAA,EACN,IAAA,EAAM;AACR,CAAA;;;AChBA,SAAS,kBAAkB,IAAA,EAIzB;AACA,EAAA,OAAO,OAAO,IAAA,KAAS,QAAA,GAAW,EAAE,IAAA,EAAM,MAAK,GAAI,IAAA;AACrD;AAyCA,SAAS,SAAA,CACP,GAAA,EACA,IAAA,EACA,IAAA,EACM;AACN,EAAA,MAAM,EAAE,IAAA,EAAM,OAAA,EAAS,aAAA,EAAc,GAAI,kBAAkB,IAAI,CAAA;AAC/D,EAAA,MAAM,eAAA,GAAkB,OAAA,IAAW,eAAA,CAAgB,IAAI,CAAA;AACvD,EAAA,MAAM,MAAA,GAAS,aAAA,IAAiB,cAAA,CAAe,IAAI,CAAA;AAEnD,EAAA,MAAM,QAAA,GAAW,GAAA,CAAI,GAAA,CAAI,IAAI,CAAA;AAC7B,EAAA,IAAI,QAAA,EAAU;AACZ,IAAA,KAAA,MAAW,CAAA,IAAK,eAAA,EAAiB,QAAA,CAAS,OAAA,CAAQ,IAAI,CAAC,CAAA;AACvD,IAAA,IAAI,MAAA,WAAiB,MAAA,GAAS,IAAA;AAAA,EAChC,CAAA,MAAO;AACL,IAAA,GAAA,CAAI,GAAA,CAAI,IAAA,EAAM,EAAE,IAAA,EAAM,OAAA,EAAS,IAAI,GAAA,CAAI,eAAe,CAAA,EAAG,MAAA,EAAQ,CAAA;AAAA,EACnE;AACF;AAEA,SAAS,kBAAkB,KAAA,EAAgC;AACzD,EAAA,MAAM,aAAA,GAAgB,CAAC,GAAG,KAAA,CAAM,OAAO,CAAA,CAAE,IAAA,CAAK,CAAC,CAAA,EAAG,CAAA,KAAM,CAAA,GAAI,CAAC,CAAA;AAC7D,EAAA,MAAM,WAAqB,EAAC;AAE5B,EAAA,IAAI,MAAM,MAAA,EAAQ;AAChB,IAAA,QAAA,CAAS,KAAK,MAAM,CAAA;AAAA,EACtB;AAEA,EAAA,IAAI,aAAA,CAAc,SAAS,CAAA,EAAG;AAC5B,IAAA,MAAM,UAAA,GAAa,MAAM,MAAA,GACrB,aAAA,CACG,QAAQ,CAAC,CAAA,KAAM,CAAC,CAAA,EAAA,EAAK,CAAC,CAAA,CAAA,EAAI,KAAK,CAAC,CAAA,CAAE,CAAC,CAAA,CACnC,IAAA,EAAK,CACL,KAAK,GAAG,CAAA,GACX,aAAA,CAAc,IAAA,CAAK,GAAG,CAAA;AAC1B,IAAA,QAAA,CAAS,IAAA,CAAK,CAAA,KAAA,EAAQ,UAAU,CAAA,CAAE,CAAA;AAAA,EACpC;AAEA,EAAA,IAAI,QAAA,CAAS,SAAS,CAAA,EAAG;AACvB,IAAA,OAAO,GAAG,KAAA,CAAM,IAAI,IAAI,QAAA,CAAS,IAAA,CAAK,GAAG,CAAC,CAAA,CAAA;AAAA,EAC5C;AACA,EAAA,OAAO,KAAA,CAAM,IAAA;AACf;AAEO,SAAS,eAAe,KAAA,EAKpB;AACT,EAAA,MAAM,MAAA,uBAAa,GAAA,EAA6B;AAEhD,EAAA,SAAA,CAAU,MAAA,EAAQ,QAAA,EAAU,KAAA,CAAM,MAAM,CAAA;AACxC,EAAA,SAAA,CAAU,MAAA,EAAQ,MAAA,EAAQ,KAAA,CAAM,IAAI,CAAA;AACpC,EAAA,SAAA,CAAU,MAAA,EAAQ,MAAA,EAAQ,KAAA,CAAM,IAAI,CAAA;AAEpC,EAAA,IAAI,MAAM,KAAA,EAAO;AACf,IAAA,SAAA,CAAU,MAAA,EAAQ,OAAA,EAAS,KAAA,CAAM,KAAK,CAAA;AAAA,EACxC;AAEA,EAAA,MAAM,QAAA,GAAW,CAAC,GAAG,MAAA,CAAO,QAAQ,CAAA,CAAE,IAAI,iBAAiB,CAAA;AAC3D,EAAA,MAAM,MAAA,GAAS,QAAA,CAAS,GAAA,CAAI,CAAC,CAAA,KAAM,CAAA,OAAA,EAAU,kBAAA,CAAmB,CAAC,CAAC,CAAA,CAAE,CAAA,CAAE,IAAA,CAAK,GAAG,CAAA;AAC9E,EAAA,OAAO,qCAAqC,MAAM,CAAA,aAAA,CAAA;AACpD;;;AC9GA,IAAM,WAAA,GAAsC;AAAA,EAC1C,QAAA,EAAU,KAAA;AAAA,EACV,IAAA,EAAM,MAAA;AAAA,EACN,KAAA,EAAO,OAAA;AAAA,EACP,QAAA,EAAU;AACZ,CAAA;AAEA,IAAM,cAAA,GACJ,2FAAA;AAOK,SAAS,mBAAmB,UAAA,EAAyC;AAC1E,EAAA,MAAM,YAA8B,EAAC;AAErC,EAAA,MAAM,sBAAsB,UAAA,CAAW,OAAA;AAAA,IACrC,cAAA;AAAA,IACA,CAAC,KAAA,EAAO,GAAA,EAAa,QAAA,EAAkB,MAAA,KAAmB;AACxD,MAAA,MAAM,SAAA,GAAY,WAAA,CAAY,MAAM,CAAA,IAAK,MAAA;AACzC,MAAA,MAAM,YAAA,GAAe,CAAA,EAAG,QAAQ,CAAA,CAAA,EAAI,SAAS,CAAA,CAAA;AAC7C,MAAA,SAAA,CAAU,IAAA,CAAK,EAAE,GAAA,EAAK,QAAA,EAAU,WAAW,CAAA;AAC3C,MAAA,OAAO,KAAA,CAAM,OAAA,CAAQ,GAAA,EAAK,YAAY,CAAA;AAAA,IACxC;AAAA,GACF;AAEA,EAAA,OAAO,EAAE,qBAAqB,SAAA,EAAU;AAC1C;;;AC5BO,IAAM,qBAAA,GAAwB,mBAAA;AAC9B,IAAM,mBAAA,GAAsB,iBAAA;AAC5B,IAAM,mBAAA,GAAsB,eAAA;AASnC,SAAS,oBAAoB,KAAA,EAA4C;AACvE,EAAA,IAAI,OAAO,KAAA,KAAU,QAAA,SAAiB,KAAA,CAAM,IAAA,GAAO,MAAA,GAAS,CAAA;AAC5D,EAAA,OACE,OAAO,KAAA,KAAU,QAAA,IACjB,UAAU,IAAA,IACV,OAAQ,MAA6B,IAAA,KAAS,QAAA;AAElD;AAMO,SAAS,yBAAyB,GAAA,EAAiD;AACxF,EAAA,MAAM,KAAA,GAAQ,GAAA,EAAK,GAAA,EAAK,aAAA,EAAe,KAAA;AACvC,EAAA,MAAM,aAAa,KAAA,EAAO,UAAA;AAC1B,EAAA,IAAI,OAAO,UAAA,KAAe,QAAA,IAAY,UAAA,KAAe,IAAA,SAAa,EAAC;AAEnE,EAAA,MAAM,SAA+B,EAAC;AACtC,EAAA,KAAA,MAAW,QAAQ,CAAC,OAAA,EAAS,QAAA,EAAU,MAAA,EAAQ,MAAM,CAAA,EAAY;AAC/D,IAAA,MAAM,IAAA,GAAQ,WAAuC,IAAI,CAAA;AACzD,IAAA,IAAI,mBAAA,CAAoB,IAAI,CAAA,EAAG,MAAA,CAAO,IAAI,CAAA,GAAI,IAAA;AAAA,EAChD;AACA,EAAA,OAAO,MAAA;AACT;AAMO,SAAS,gBAAA,CACd,SACA,UAAA,EAMA;AACA,EAAA,OAAO;AAAA,IACL,KAAA,EAAO,OAAA,CAAQ,KAAA,IAAS,UAAA,CAAW,KAAA;AAAA,IACnC,MAAA,EAAQ,OAAA,CAAQ,MAAA,IAAU,UAAA,CAAW,MAAA,IAAU,qBAAA;AAAA,IAC/C,IAAA,EAAM,OAAA,CAAQ,IAAA,IAAQ,UAAA,CAAW,IAAA,IAAQ,mBAAA;AAAA,IACzC,IAAA,EAAM,OAAA,CAAQ,IAAA,IAAQ,UAAA,CAAW,IAAA,IAAQ;AAAA,GAC3C;AACF;;;ACnDA,IAAM,cAAA,GAA+B;AAAA,EACnC,aAAA,EAAe,IAAA;AAAA,EACf,UAAA,EAAY;AACd,CAAA;AAEO,IAAM,YAAA,GAA2D,CACtE,WAAA,KACG;AACH,EAAA,MAAM,OAAA,GAAwB,EAAE,GAAG,cAAA,EAAgB,GAAG,WAAA,EAAY;AAElE,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,cAAA;AAAA,IACN,OAAO,IAAA,CAAK,GAAA,EAAe,QAAA,EAAqB,UAAA,EAA+C;AAC7F,MAAA,IAAI,OAAA,CAAQ,eAAe,YAAA,EAAc;AACvC,QAAA;AAAA,MACF;AAEA,MAAA,MAAM,OAAO,cAAA,CAAe,gBAAA,CAAiB,SAAS,wBAAA,CAAyB,GAAG,CAAC,CAAC,CAAA;AAEpF,MAAA,MAAM,WAAA,GAAc,MAAM,KAAA,CAAM,IAAI,CAAA;AACpC,MAAA,IAAI,CAAC,YAAY,EAAA,EAAI;AACnB,QAAA,MAAM,IAAI,KAAA;AAAA,UACR,CAAA,iDAAA,EAAoD,WAAA,CAAY,MAAM,CAAA,CAAA,EAAI,YAAY,UAAU,CAAA;AAAA,SAClG;AAAA,MACF;AACA,MAAA,MAAM,OAAA,GAAU,MAAM,WAAA,CAAY,IAAA,EAAK;AAEvC,MAAA,MAAM,EAAE,mBAAA,EAAqB,SAAA,EAAU,GAAI,mBAAmB,OAAO,CAAA;AAErE,MAAA,MAAM,WAAW,IAAA,CAAK,IAAA,CAAK,IAAI,IAAA,CAAK,MAAA,EAAQ,UAAU,OAAO,CAAA;AAC7D,MAAA,MAAM,GAAG,QAAA,CAAS,KAAA,CAAM,UAAU,EAAE,SAAA,EAAW,MAAM,CAAA;AAErD,MAAA,KAAA,MAAW,YAAY,SAAA,EAAW;AAChC,QAAA,MAAM,YAAA,GAAe,MAAM,KAAA,CAAM,QAAA,CAAS,GAAG,CAAA;AAC7C,QAAA,IAAI,CAAC,aAAa,EAAA,EAAI;AACpB,UAAA,MAAM,IAAI,KAAA;AAAA,YACR,CAAA,0CAAA,EAA6C,SAAS,GAAG,CAAA,EAAA,EAAK,aAAa,MAAM,CAAA,CAAA,EAAI,aAAa,UAAU,CAAA,CAAA;AAAA,WAC9G;AAAA,QACF;AACA,QAAA,MAAM,GAAA,GAAM,MAAM,YAAA,CAAa,WAAA,EAAY;AAC3C,QAAA,MAAM,QAAA,GAAW,IAAA,CAAK,IAAA,CAAK,QAAA,EAAU,CAAA,EAAG,SAAS,QAAQ,CAAA,CAAA,EAAI,QAAA,CAAS,SAAS,CAAA,CAAE,CAAA;AACjF,QAAA,MAAM,GAAG,QAAA,CAAS,SAAA,CAAU,UAAU,MAAA,CAAO,IAAA,CAAK,GAAG,CAAC,CAAA;AACtD,QAAA,MAAM,QAAA;AAAA,MACR;AAEA,MAAA,MAAM,OAAA,GAAU,IAAA,CAAK,IAAA,CAAK,QAAA,EAAU,kBAAkB,CAAA;AACtD,MAAA,MAAM,EAAA,CAAG,QAAA,CAAS,SAAA,CAAU,OAAA,EAAS,mBAAmB,CAAA;AACxD,MAAA,MAAM,OAAA;AAAA,IACR;AAAA,GACF;AACF","file":"emitter.js","sourcesContent":["export const OBSIDIAN_SANS_STACK =\n 'ui-sans-serif, -apple-system, BlinkMacSystemFont, system-ui, \"Segoe UI\", Roboto, \"Inter\", \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", sans-serif';\n\nexport const OBSIDIAN_MONO_STACK =\n 'ui-monospace, SFMono-Regular, \"Cascadia Mono\", \"Roboto Mono\", \"DejaVu Sans Mono\", \"Liberation Mono\", Menlo, Monaco, \"Consolas\", \"Source Code Pro\", monospace';\n\nexport type FontRole = \"title\" | \"header\" | \"body\" | \"code\";\n\nexport const DEFAULT_WEIGHTS: Record<FontRole, number[]> = {\n title: [400, 700],\n header: [400, 700],\n body: [400, 600],\n code: [400, 600],\n};\n\nexport const DEFAULT_ITALIC: Record<FontRole, boolean> = {\n title: false,\n header: false,\n body: true,\n code: false,\n};\n","import type { FontSpecification } from \"../types\";\nimport type { FontRole } from \"../defaults\";\nimport { DEFAULT_WEIGHTS, DEFAULT_ITALIC } from \"../defaults\";\n\nfunction normalizeFontSpec(spec: FontSpecification): {\n name: string;\n weights?: number[];\n includeItalic?: boolean;\n} {\n return typeof spec === \"string\" ? { name: spec } : spec;\n}\n\nexport function getFontName(spec: FontSpecification): string {\n return typeof spec === \"string\" ? spec : spec.name;\n}\n\nexport function formatFontSpecification(role: FontRole, spec: FontSpecification): string {\n const { name, weights, includeItalic } = normalizeFontSpec(spec);\n\n const resolvedWeights = weights ?? DEFAULT_WEIGHTS[role];\n const italic = includeItalic ?? DEFAULT_ITALIC[role];\n\n const features: string[] = [];\n if (italic) {\n features.push(\"ital\");\n }\n\n if (resolvedWeights.length > 1) {\n const weightSpec = italic\n ? resolvedWeights\n .flatMap((w) => [`0,${w}`, `1,${w}`])\n .sort()\n .join(\";\")\n : resolvedWeights.join(\";\");\n\n features.push(`wght@${weightSpec}`);\n }\n\n if (features.length > 0) {\n return `${name}:${features.join(\",\")}`;\n }\n\n return name;\n}\n\ninterface MergedFontEntry {\n name: string;\n weights: Set<number>;\n italic: boolean;\n}\n\nfunction mergeInto(\n map: Map<string, MergedFontEntry>,\n role: FontRole,\n spec: FontSpecification,\n): void {\n const { name, weights, includeItalic } = normalizeFontSpec(spec);\n const resolvedWeights = weights ?? DEFAULT_WEIGHTS[role];\n const italic = includeItalic ?? DEFAULT_ITALIC[role];\n\n const existing = map.get(name);\n if (existing) {\n for (const w of resolvedWeights) existing.weights.add(w);\n if (italic) existing.italic = true;\n } else {\n map.set(name, { name, weights: new Set(resolvedWeights), italic });\n }\n}\n\nfunction formatMergedEntry(entry: MergedFontEntry): string {\n const sortedWeights = [...entry.weights].sort((a, b) => a - b);\n const features: string[] = [];\n\n if (entry.italic) {\n features.push(\"ital\");\n }\n\n if (sortedWeights.length > 1) {\n const weightSpec = entry.italic\n ? sortedWeights\n .flatMap((w) => [`0,${w}`, `1,${w}`])\n .sort()\n .join(\";\")\n : sortedWeights.join(\";\");\n features.push(`wght@${weightSpec}`);\n }\n\n if (features.length > 0) {\n return `${entry.name}:${features.join(\",\")}`;\n }\n return entry.name;\n}\n\nexport function googleFontHref(fonts: {\n title?: FontSpecification;\n header: FontSpecification;\n body: FontSpecification;\n code: FontSpecification;\n}): string {\n const merged = new Map<string, MergedFontEntry>();\n\n mergeInto(merged, \"header\", fonts.header);\n mergeInto(merged, \"body\", fonts.body);\n mergeInto(merged, \"code\", fonts.code);\n\n if (fonts.title) {\n mergeInto(merged, \"title\", fonts.title);\n }\n\n const families = [...merged.values()].map(formatMergedEntry);\n const params = families.map((f) => `family=${encodeURIComponent(f)}`).join(\"&\");\n return `https://fonts.googleapis.com/css2?${params}&display=swap`;\n}\n","import type { ProcessedFontResult, GoogleFontFile } from \"../types\";\n\nconst fontMimeMap: Record<string, string> = {\n truetype: \"ttf\",\n woff: \"woff\",\n woff2: \"woff2\",\n opentype: \"otf\",\n};\n\nconst fontUrlPattern =\n /url\\((https:\\/\\/fonts.gstatic.com\\/.+(?:\\/|(?:kit=))(.+?)[.&].+?)\\)\\sformat\\('(\\w+?)'\\);/g;\n\n/**\n * Rewrites Google Fonts URLs to local files. Font files are written next to the\n * stylesheet (`static/fonts/`), so the rewritten URLs are relative to it and work\n * regardless of host or base path (jackyzha0/quartz#2547).\n */\nexport function processGoogleFonts(stylesheet: string): ProcessedFontResult {\n const fontFiles: GoogleFontFile[] = [];\n\n const processedStylesheet = stylesheet.replace(\n fontUrlPattern,\n (match, url: string, filename: string, format: string) => {\n const extension = fontMimeMap[format] ?? format;\n const rewrittenUrl = `${filename}.${extension}`;\n fontFiles.push({ url, filename, extension });\n return match.replace(url, rewrittenUrl);\n },\n );\n\n return { processedStylesheet, fontFiles };\n}\n","import type { BuildCtx } from \"@quartz-community/types\";\nimport type { FontSpecification } from \"../types\";\n\nexport const QUARTZ_DEFAULT_HEADER = \"Schibsted Grotesk\";\nexport const QUARTZ_DEFAULT_BODY = \"Source Sans Pro\";\nexport const QUARTZ_DEFAULT_CODE = \"IBM Plex Mono\";\n\nexport interface ConfiguredTypography {\n title?: FontSpecification;\n header?: FontSpecification;\n body?: FontSpecification;\n code?: FontSpecification;\n}\n\nfunction isFontSpecification(value: unknown): value is FontSpecification {\n if (typeof value === \"string\") return value.trim().length > 0;\n return (\n typeof value === \"object\" &&\n value !== null &&\n typeof (value as { name?: unknown }).name === \"string\"\n );\n}\n\n/**\n * Reads `configuration.theme.typography` from the Quartz build context.\n * Only well-formed font specifications are returned; anything else is omitted.\n */\nexport function readConfiguredTypography(ctx: BuildCtx | undefined): ConfiguredTypography {\n const theme = ctx?.cfg?.configuration?.theme as { typography?: unknown } | undefined;\n const typography = theme?.typography;\n if (typeof typography !== \"object\" || typography === null) return {};\n\n const result: ConfiguredTypography = {};\n for (const role of [\"title\", \"header\", \"body\", \"code\"] as const) {\n const spec = (typography as Record<string, unknown>)[role];\n if (isFontSpecification(spec)) result[role] = spec;\n }\n return result;\n}\n\n/**\n * Font specifications used to load fonts (Google Fonts / self-hosted):\n * plugin options first, then `configuration.theme.typography`, then the Quartz defaults.\n */\nexport function resolveFontSpecs(\n options: ConfiguredTypography,\n typography: ConfiguredTypography,\n): {\n title?: FontSpecification;\n header: FontSpecification;\n body: FontSpecification;\n code: FontSpecification;\n} {\n return {\n title: options.title ?? typography.title,\n header: options.header ?? typography.header ?? QUARTZ_DEFAULT_HEADER,\n body: options.body ?? typography.body ?? QUARTZ_DEFAULT_BODY,\n code: options.code ?? typography.code ?? QUARTZ_DEFAULT_CODE,\n };\n}\n","import fs from \"fs\";\nimport path from \"path\";\nimport type { QuartzEmitterPlugin, BuildCtx, FilePath } from \"@quartz-community/types\";\nimport type { FontsOptions } from \"./types\";\nimport { googleFontHref } from \"./util/google-fonts\";\nimport { processGoogleFonts } from \"./util/process-fonts\";\nimport { readConfiguredTypography, resolveFontSpecs } from \"./util/typography\";\n\nconst defaultOptions: FontsOptions = {\n useThemeFonts: true,\n fontOrigin: \"googleFonts\",\n};\n\nexport const FontsEmitter: QuartzEmitterPlugin<Partial<FontsOptions>> = (\n userOptions?: Partial<FontsOptions>,\n) => {\n const options: FontsOptions = { ...defaultOptions, ...userOptions };\n\n return {\n name: \"FontsEmitter\",\n async *emit(ctx: BuildCtx, _content: unknown[], _resources: unknown): AsyncGenerator<FilePath> {\n if (options.fontOrigin !== \"selfHosted\") {\n return;\n }\n\n const href = googleFontHref(resolveFontSpecs(options, readConfiguredTypography(ctx)));\n\n const cssResponse = await fetch(href);\n if (!cssResponse.ok) {\n throw new Error(\n `[FontsEmitter] Failed to fetch Google Fonts CSS: ${cssResponse.status} ${cssResponse.statusText}`,\n );\n }\n const cssText = await cssResponse.text();\n\n const { processedStylesheet, fontFiles } = processGoogleFonts(cssText);\n\n const fontsDir = path.join(ctx.argv.output, \"static\", \"fonts\");\n await fs.promises.mkdir(fontsDir, { recursive: true });\n\n for (const fontFile of fontFiles) {\n const fontResponse = await fetch(fontFile.url);\n if (!fontResponse.ok) {\n throw new Error(\n `[FontsEmitter] Failed to fetch font file: ${fontFile.url} (${fontResponse.status} ${fontResponse.statusText})`,\n );\n }\n const buf = await fontResponse.arrayBuffer();\n const filePath = path.join(fontsDir, `${fontFile.filename}.${fontFile.extension}`);\n await fs.promises.writeFile(filePath, Buffer.from(buf));\n yield filePath as FilePath;\n }\n\n const cssPath = path.join(fontsDir, \"quartz-fonts.css\");\n await fs.promises.writeFile(cssPath, processedStylesheet);\n yield cssPath as FilePath;\n },\n };\n};\n"]}