@quartz-community/quartz-fonts 0.1.0
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 +12 -0
- package/LICENSE +21 -0
- package/README.md +192 -0
- package/dist/emitter.d.ts +6 -0
- package/dist/emitter.js +147 -0
- package/dist/emitter.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +414 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +58 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/package.json +423 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Initial Quartz community plugin template.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Quartz Community
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# @quartz-community/fonts
|
|
2
|
+
|
|
3
|
+
Fine-grained font control for Quartz sites. Supports per-heading fonts, automatic theme font discovery via [QuartzTheme](https://github.com/saberzero1/quartz-themes), and Obsidian-compatible defaults.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx quartz plugin add github:quartz-community/fonts
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```yaml title="quartz.config.yaml"
|
|
14
|
+
plugins:
|
|
15
|
+
- source: github:quartz-community/fonts
|
|
16
|
+
enabled: true
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
With custom fonts:
|
|
20
|
+
|
|
21
|
+
```yaml title="quartz.config.yaml"
|
|
22
|
+
plugins:
|
|
23
|
+
- source: github:quartz-community/fonts
|
|
24
|
+
enabled: true
|
|
25
|
+
options:
|
|
26
|
+
body: '"Inter", sans-serif'
|
|
27
|
+
header: '"Playfair Display", serif'
|
|
28
|
+
code: '"JetBrains Mono", monospace'
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Configuration
|
|
32
|
+
|
|
33
|
+
Font options accept either a CSS font-family string or an object with Google Fonts loading control:
|
|
34
|
+
|
|
35
|
+
```yaml
|
|
36
|
+
# String form
|
|
37
|
+
body: '"Inter", sans-serif'
|
|
38
|
+
|
|
39
|
+
# Object form (for Google Fonts weight/italic control)
|
|
40
|
+
body:
|
|
41
|
+
name: Inter
|
|
42
|
+
weights: [400, 600, 700]
|
|
43
|
+
includeItalic: true
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
| Option | Type | Default | Description |
|
|
47
|
+
| --------------- | ------------------- | ---------------- | ------------------------------------------------------------------------- |
|
|
48
|
+
| `title` | `FontSpecification` | `header` value | Font family for the site title. |
|
|
49
|
+
| `body` | `FontSpecification` | Obsidian default | Font family for body text. |
|
|
50
|
+
| `header` | `FontSpecification` | Obsidian default | Default font family for all headings (h1-h6). |
|
|
51
|
+
| `code` | `FontSpecification` | Obsidian default | Font family for code and monospace elements. |
|
|
52
|
+
| `interface` | `FontSpecification` | Obsidian default | Font family for UI elements. |
|
|
53
|
+
| `h1` | `FontSpecification` | `header` value | Font family for h1 headings. |
|
|
54
|
+
| `h2` | `FontSpecification` | `header` value | Font family for h2 headings. |
|
|
55
|
+
| `h3` | `FontSpecification` | `header` value | Font family for h3 headings. |
|
|
56
|
+
| `h4` | `FontSpecification` | `header` value | Font family for h4 headings. |
|
|
57
|
+
| `h5` | `FontSpecification` | `header` value | Font family for h5 headings. |
|
|
58
|
+
| `h6` | `FontSpecification` | `header` value | Font family for h6 headings. |
|
|
59
|
+
| `useThemeFonts` | `boolean` | `true` | Use fonts from QuartzTheme as defaults when it is installed. |
|
|
60
|
+
| `fontOrigin` | `string` | `"local"` | `"googleFonts"` to auto-load from Google Fonts, `"local"` for no loading. |
|
|
61
|
+
|
|
62
|
+
### Default options
|
|
63
|
+
|
|
64
|
+
```yaml title="quartz.config.yaml"
|
|
65
|
+
- source: github:quartz-community/fonts
|
|
66
|
+
enabled: true
|
|
67
|
+
options:
|
|
68
|
+
useThemeFonts: true
|
|
69
|
+
fontOrigin: local
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## How it works
|
|
73
|
+
|
|
74
|
+
Fonts resolves fonts using a priority chain:
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
User config (plugin options)
|
|
78
|
+
-> Theme fonts (from QuartzTheme, if installed)
|
|
79
|
+
-> Obsidian defaults (system font stacks)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
For individual headings, the resolution is:
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
h1 option -> header option -> theme --h1-font -> theme font -> Obsidian default
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
For the site title:
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
title option -> header option -> theme font -> Obsidian default
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### With QuartzTheme
|
|
95
|
+
|
|
96
|
+
When [QuartzTheme](https://github.com/saberzero1/quartz-themes) is installed and runs before Fonts, theme fonts are automatically discovered and used as defaults. Any options you set in Fonts will override the theme fonts.
|
|
97
|
+
|
|
98
|
+
Fonts must run after QuartzTheme. This is handled automatically by `defaultOrder` (QuartzTheme = 50, Fonts = 60).
|
|
99
|
+
|
|
100
|
+
### Without QuartzTheme
|
|
101
|
+
|
|
102
|
+
Fonts works standalone. Without a theme, it falls back to Obsidian's default system font stacks.
|
|
103
|
+
|
|
104
|
+
## Examples
|
|
105
|
+
|
|
106
|
+
```yaml title="quartz.config.yaml"
|
|
107
|
+
# Use theme fonts automatically (default behavior)
|
|
108
|
+
- source: github:quartz-community/fonts
|
|
109
|
+
enabled: true
|
|
110
|
+
|
|
111
|
+
# Override just the heading font
|
|
112
|
+
- source: github:quartz-community/fonts
|
|
113
|
+
enabled: true
|
|
114
|
+
options:
|
|
115
|
+
header: '"Playfair Display", serif'
|
|
116
|
+
|
|
117
|
+
# Full control with per-heading fonts
|
|
118
|
+
- source: github:quartz-community/fonts
|
|
119
|
+
enabled: true
|
|
120
|
+
options:
|
|
121
|
+
body: '"Inter", sans-serif'
|
|
122
|
+
header: '"Playfair Display", serif'
|
|
123
|
+
code: '"JetBrains Mono", monospace'
|
|
124
|
+
h1: '"Playfair Display", serif'
|
|
125
|
+
h2: '"Lora", serif'
|
|
126
|
+
|
|
127
|
+
# Load from Google Fonts automatically
|
|
128
|
+
- source: github:quartz-community/fonts
|
|
129
|
+
enabled: true
|
|
130
|
+
options:
|
|
131
|
+
fontOrigin: googleFonts
|
|
132
|
+
body: Inter
|
|
133
|
+
header: Playfair Display
|
|
134
|
+
code: JetBrains Mono
|
|
135
|
+
|
|
136
|
+
# Google Fonts with weight/italic control
|
|
137
|
+
- source: github:quartz-community/fonts
|
|
138
|
+
enabled: true
|
|
139
|
+
options:
|
|
140
|
+
fontOrigin: googleFonts
|
|
141
|
+
body:
|
|
142
|
+
name: Inter
|
|
143
|
+
weights: [400, 600, 700]
|
|
144
|
+
includeItalic: true
|
|
145
|
+
header:
|
|
146
|
+
name: Playfair Display
|
|
147
|
+
weights: [400, 700]
|
|
148
|
+
code:
|
|
149
|
+
name: JetBrains Mono
|
|
150
|
+
weights: [400]
|
|
151
|
+
|
|
152
|
+
# Custom title font (separate from header)
|
|
153
|
+
- source: github:quartz-community/fonts
|
|
154
|
+
enabled: true
|
|
155
|
+
options:
|
|
156
|
+
fontOrigin: googleFonts
|
|
157
|
+
title: Abril Fatface
|
|
158
|
+
header: Playfair Display
|
|
159
|
+
body: Inter
|
|
160
|
+
code: JetBrains Mono
|
|
161
|
+
|
|
162
|
+
# Ignore theme fonts entirely
|
|
163
|
+
- source: github:quartz-community/fonts
|
|
164
|
+
enabled: true
|
|
165
|
+
options:
|
|
166
|
+
useThemeFonts: false
|
|
167
|
+
body: '"Inter", sans-serif'
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Google Fonts Validation
|
|
171
|
+
|
|
172
|
+
When `fontOrigin: googleFonts` is set and the optional [`google-font-metadata`](https://www.npmjs.com/package/google-font-metadata) package is installed, Fonts validates your font configuration at build time:
|
|
173
|
+
|
|
174
|
+
- Checks that font family names exist in Google Fonts.
|
|
175
|
+
- Warns if requested weights are not available for a font.
|
|
176
|
+
- Warns if italic is requested but the font doesn't support it.
|
|
177
|
+
|
|
178
|
+
Install it to enable validation:
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
npm install google-font-metadata
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Validation warnings are logged to the console but do not block the build.
|
|
185
|
+
|
|
186
|
+
## Documentation
|
|
187
|
+
|
|
188
|
+
See the [Quartz documentation](https://quartz.jzhao.xyz/plugins/Fonts) for more information.
|
|
189
|
+
|
|
190
|
+
## License
|
|
191
|
+
|
|
192
|
+
MIT
|
package/dist/emitter.js
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { createRequire } from 'module';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
|
|
5
|
+
createRequire(import.meta.url);
|
|
6
|
+
|
|
7
|
+
// src/defaults.ts
|
|
8
|
+
var DEFAULT_WEIGHTS = {
|
|
9
|
+
title: [400, 700],
|
|
10
|
+
header: [400, 700],
|
|
11
|
+
body: [400, 600],
|
|
12
|
+
code: [400, 600]
|
|
13
|
+
};
|
|
14
|
+
var DEFAULT_ITALIC = {
|
|
15
|
+
title: false,
|
|
16
|
+
header: false,
|
|
17
|
+
body: true,
|
|
18
|
+
code: false
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// src/util/google-fonts.ts
|
|
22
|
+
function normalizeFontSpec(spec) {
|
|
23
|
+
return typeof spec === "string" ? { name: spec } : spec;
|
|
24
|
+
}
|
|
25
|
+
function mergeInto(map, role, spec) {
|
|
26
|
+
const { name, weights, includeItalic } = normalizeFontSpec(spec);
|
|
27
|
+
const resolvedWeights = weights ?? DEFAULT_WEIGHTS[role];
|
|
28
|
+
const italic = includeItalic ?? DEFAULT_ITALIC[role];
|
|
29
|
+
const existing = map.get(name);
|
|
30
|
+
if (existing) {
|
|
31
|
+
for (const w of resolvedWeights) existing.weights.add(w);
|
|
32
|
+
if (italic) existing.italic = true;
|
|
33
|
+
} else {
|
|
34
|
+
map.set(name, { name, weights: new Set(resolvedWeights), italic });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function formatMergedEntry(entry) {
|
|
38
|
+
const sortedWeights = [...entry.weights].sort((a, b) => a - b);
|
|
39
|
+
const features = [];
|
|
40
|
+
if (entry.italic) {
|
|
41
|
+
features.push("ital");
|
|
42
|
+
}
|
|
43
|
+
if (sortedWeights.length > 1) {
|
|
44
|
+
const weightSpec = entry.italic ? sortedWeights.flatMap((w) => [`0,${w}`, `1,${w}`]).sort().join(";") : sortedWeights.join(";");
|
|
45
|
+
features.push(`wght@${weightSpec}`);
|
|
46
|
+
}
|
|
47
|
+
if (features.length > 0) {
|
|
48
|
+
return `${entry.name}:${features.join(",")}`;
|
|
49
|
+
}
|
|
50
|
+
return entry.name;
|
|
51
|
+
}
|
|
52
|
+
function googleFontHref(fonts) {
|
|
53
|
+
const merged = /* @__PURE__ */ new Map();
|
|
54
|
+
mergeInto(merged, "header", fonts.header);
|
|
55
|
+
mergeInto(merged, "body", fonts.body);
|
|
56
|
+
mergeInto(merged, "code", fonts.code);
|
|
57
|
+
if (fonts.title) {
|
|
58
|
+
mergeInto(merged, "title", fonts.title);
|
|
59
|
+
}
|
|
60
|
+
const families = [...merged.values()].map(formatMergedEntry);
|
|
61
|
+
const params = families.map((f) => `family=${encodeURIComponent(f)}`).join("&");
|
|
62
|
+
return `https://fonts.googleapis.com/css2?${params}&display=swap`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// src/util/process-fonts.ts
|
|
66
|
+
var fontMimeMap = {
|
|
67
|
+
truetype: "ttf",
|
|
68
|
+
woff: "woff",
|
|
69
|
+
woff2: "woff2",
|
|
70
|
+
opentype: "otf"
|
|
71
|
+
};
|
|
72
|
+
var fontUrlPattern = /url\((https:\/\/fonts.gstatic.com\/.+(?:\/|(?:kit=))(.+?)[.&].+?)\)\sformat\('(\w+?)'\);/g;
|
|
73
|
+
function processGoogleFonts(stylesheet, baseUrl) {
|
|
74
|
+
const fontFiles = [];
|
|
75
|
+
const processedStylesheet = stylesheet.replace(
|
|
76
|
+
fontUrlPattern,
|
|
77
|
+
(match, url, filename, format) => {
|
|
78
|
+
const extension = fontMimeMap[format] ?? format;
|
|
79
|
+
const rewrittenUrl = `https://${baseUrl}/static/fonts/${filename}.${extension}`;
|
|
80
|
+
fontFiles.push({ url, filename, extension });
|
|
81
|
+
return match.replace(url, rewrittenUrl);
|
|
82
|
+
}
|
|
83
|
+
);
|
|
84
|
+
return { processedStylesheet, fontFiles };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// src/emitter.ts
|
|
88
|
+
var QUARTZ_DEFAULT_HEADER = "Schibsted Grotesk";
|
|
89
|
+
var QUARTZ_DEFAULT_BODY = "Source Sans Pro";
|
|
90
|
+
var QUARTZ_DEFAULT_CODE = "IBM Plex Mono";
|
|
91
|
+
var defaultOptions = {
|
|
92
|
+
useThemeFonts: true,
|
|
93
|
+
fontOrigin: "googleFonts"
|
|
94
|
+
};
|
|
95
|
+
var FontsEmitter = (userOptions) => {
|
|
96
|
+
const options = { ...defaultOptions, ...userOptions };
|
|
97
|
+
return {
|
|
98
|
+
name: "FontsEmitter",
|
|
99
|
+
async *emit(ctx, _content, _resources) {
|
|
100
|
+
if (options.fontOrigin !== "selfHosted") {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
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
|
+
});
|
|
116
|
+
const cssResponse = await fetch(href);
|
|
117
|
+
if (!cssResponse.ok) {
|
|
118
|
+
throw new Error(
|
|
119
|
+
`[FontsEmitter] Failed to fetch Google Fonts CSS: ${cssResponse.status} ${cssResponse.statusText}`
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
const cssText = await cssResponse.text();
|
|
123
|
+
const { processedStylesheet, fontFiles } = processGoogleFonts(cssText, baseUrl);
|
|
124
|
+
const fontsDir = path.join(ctx.argv.output, "static", "fonts");
|
|
125
|
+
await fs.promises.mkdir(fontsDir, { recursive: true });
|
|
126
|
+
for (const fontFile of fontFiles) {
|
|
127
|
+
const fontResponse = await fetch(fontFile.url);
|
|
128
|
+
if (!fontResponse.ok) {
|
|
129
|
+
throw new Error(
|
|
130
|
+
`[FontsEmitter] Failed to fetch font file: ${fontFile.url} (${fontResponse.status} ${fontResponse.statusText})`
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
const buf = await fontResponse.arrayBuffer();
|
|
134
|
+
const filePath = path.join(fontsDir, `${fontFile.filename}.${fontFile.extension}`);
|
|
135
|
+
await fs.promises.writeFile(filePath, Buffer.from(buf));
|
|
136
|
+
yield filePath;
|
|
137
|
+
}
|
|
138
|
+
const cssPath = path.join(fontsDir, "quartz-fonts.css");
|
|
139
|
+
await fs.promises.writeFile(cssPath, processedStylesheet);
|
|
140
|
+
yield cssPath;
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
export { FontsEmitter };
|
|
146
|
+
//# sourceMappingURL=emitter.js.map
|
|
147
|
+
//# sourceMappingURL=emitter.js.map
|
|
@@ -0,0 +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"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { QuartzTransformerPlugin } from '@quartz-community/types';
|
|
2
|
+
export { QuartzEmitterPlugin, QuartzTransformerPlugin } from '@quartz-community/types';
|
|
3
|
+
import { FontsOptions } from './types.js';
|
|
4
|
+
export { FontFileEntry, FontSpecification, GoogleFontFile, ProcessedFontResult, QuartzFontRegistry } from './types.js';
|
|
5
|
+
export { FontsEmitter } from './emitter.js';
|
|
6
|
+
|
|
7
|
+
declare const Fonts: QuartzTransformerPlugin<Partial<FontsOptions>>;
|
|
8
|
+
|
|
9
|
+
export { Fonts, FontsOptions };
|