@volter/twin-googlefonts 1.0.1 → 1.0.3
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/api/spec/sources.json +11 -1
- package/api/src/semantics/doors.ts +10 -5
- package/api/src/semantics/index.ts +1 -1
- package/assets/src/semantics/index.ts +1 -1
- package/css/spec/SOURCE.md +10 -2
- package/css/src/semantics/css.ts +5 -3
- package/css/src/semantics/css2.ts +9 -8
- package/css/src/semantics/index.ts +1 -1
- package/dist/api/spec/sources.json +11 -1
- package/dist/api/src/semantics/doors.js +10 -5
- package/dist/css/spec/SOURCE.md +10 -2
- package/dist/css/src/semantics/css.js +5 -3
- package/dist/css/src/semantics/css2.js +9 -8
- package/dist/generated/pack-facts.json +68 -0
- package/dist/journeys/customer-life.json +81 -0
- package/dist/journeys/decisions.json +15 -5
- package/dist/journeys/demand.json +56 -0
- package/dist/src/cli.js +11 -10
- package/dist/src/manifest.js +5 -3
- package/dist/src/semantics/seed.js +135 -0
- package/dist/src/semantics/shared.d.ts +1 -0
- package/dist/src/semantics/shared.js +50 -2
- package/generated/pack-facts.json +5 -2
- package/journeys/customer-life.json +81 -0
- package/journeys/decisions.json +15 -5
- package/journeys/demand.json +56 -0
- package/package.json +30 -14
- package/src/cli.ts +10 -10
- package/src/manifest.ts +5 -3
- package/src/semantics/seed.ts +135 -0
- package/src/semantics/shared.ts +55 -2
- package/{dist/vendor/OFL.txt → vendor/DMMono-OFL.txt} +93 -94
- package/vendor/SOURCE.md +28 -0
- package/vendor/browser-classes.json +529 -0
- package/vendor/inter-dm-mono-chromium.json +108 -0
- package/vendor/inter-variable.json +52 -0
- package/dist/api/src/fetch.ts +0 -16
- package/dist/api/src/index.ts +0 -3
- package/dist/api/src/manifest.ts +0 -31
- package/dist/api/src/semantics/doors.ts +0 -131
- package/dist/api/src/semantics/index.ts +0 -8
- package/dist/api/src/semantics/shared.ts +0 -1
- package/dist/api/src/semantics/states.ts +0 -6
- package/dist/api/src/semantics/webfonts.ts +0 -32
- package/dist/assets/src/fetch.ts +0 -14
- package/dist/assets/src/index.ts +0 -3
- package/dist/assets/src/manifest.ts +0 -19
- package/dist/assets/src/semantics/index.ts +0 -10
- package/dist/assets/src/semantics/l.ts +0 -4
- package/dist/assets/src/semantics/s.ts +0 -4
- package/dist/assets/src/semantics/shared.ts +0 -13
- package/dist/css/src/fetch.ts +0 -14
- package/dist/css/src/index.ts +0 -3
- package/dist/css/src/manifest.ts +0 -19
- package/dist/css/src/semantics/css.ts +0 -19
- package/dist/css/src/semantics/css2.ts +0 -20
- package/dist/css/src/semantics/index.ts +0 -10
- package/dist/css/src/semantics/shared.ts +0 -1
- package/dist/journeys/customer-life.outline.md +0 -24
- package/dist/journeys/customer-life.outline.verdict.json +0 -82
- package/dist/journeys/customer-life.verdict.json +0 -149
- package/dist/vendor/Inter-OFL.txt +0 -93
- package/dist/vendor/SOURCE.md +0 -33
- package/dist/vendor/antic.json +0 -93
- package/dist/vendor/inputs.json +0 -460
- package/dist/vendor/inter.json +0 -44
- package/dist/vendor/noto-sans-sc-text.json +0 -24
- package/journeys/customer-life.outline.md +0 -24
- package/journeys/customer-life.outline.verdict.json +0 -82
- package/journeys/customer-life.verdict.json +0 -149
package/api/spec/sources.json
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"https://developers.google.com/fonts/docs/developer_api": {
|
|
3
3
|
"status": 200,
|
|
4
|
-
"checked": "2026-
|
|
4
|
+
"checked": "2026-11-11",
|
|
5
|
+
"quotes": {
|
|
6
|
+
"by including an API key with each request": true
|
|
7
|
+
}
|
|
8
|
+
},
|
|
9
|
+
"https://docs.cloud.google.com/docs/authentication/api-keys": {
|
|
10
|
+
"status": 200,
|
|
11
|
+
"checked": "2026-11-11",
|
|
12
|
+
"quotes": {
|
|
13
|
+
"Standard API keys provide a way to associate a request with a project for billing and quota purposes.": true
|
|
14
|
+
}
|
|
5
15
|
}
|
|
6
16
|
}
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
// Google supplies catalog records and keys outside the read-only Fonts API.
|
|
2
2
|
import type { HandlerContext } from '@volter/world-core';
|
|
3
|
-
import { FONT, CATALOG, CSS, FILE, KEY, object, assetKey, cssKey, css2Key, css2Query, googleError } from './shared.ts';
|
|
3
|
+
import { FONT, CATALOG, CSS, FILE, KEY, object, assetKey, browserClass, cssKey, css2Key, css2Query, googleError } from './shared.ts';
|
|
4
4
|
|
|
5
5
|
// source: https://developers.google.com/fonts/docs/developer_api
|
|
6
6
|
export async function appCredentials(ctx: HandlerContext): Promise<Response> {
|
|
7
|
+
// An app's key is a standard API key, made on a Google Cloud project's Credentials page and associating each request
|
|
8
|
+
// with that project for billing and quota; the Fonts Developer API takes it as `key=` and nothing else.
|
|
9
|
+
// source: https://docs.cloud.google.com/docs/authentication/api-keys "Standard API keys provide a way to associate a request with a project for billing and quota purposes."
|
|
10
|
+
// source: https://developers.google.com/fonts/docs/developer_api "by including an API key with each request"
|
|
7
11
|
const project = await ctx.tenant('_project', async organization =>
|
|
8
12
|
String((await ctx.create('_project', { name: organization.label, owner: organization.members[0] }, 'project.create')).id));
|
|
9
13
|
const value = async (id: string): Promise<string> => 'AIza' + (await ctx.secret(`googlefonts:application-api-key:${id}`)).slice(0, 35);
|
|
@@ -79,8 +83,9 @@ export async function publishCatalog(ctx: HandlerContext): Promise<Response> {
|
|
|
79
83
|
if (!font || !(font.variants as string[]).includes(variant) ||
|
|
80
84
|
sheet.subset.split(',').some(s => !(font.subsets as string[]).includes(s)))
|
|
81
85
|
throw new Error('Stylesheet request must name a published font, variant and subset');
|
|
86
|
+
// a stylesheet is one browser class's representation (shared.ts browserClass): its user agent is that class's sample
|
|
82
87
|
const key = cssKey(ctx, sheet.family, sheet.subset, sheet.userAgent);
|
|
83
|
-
if (representations.has(key)) throw new Error('Duplicate stylesheet request');
|
|
88
|
+
if (representations.has(key)) throw new Error('Duplicate stylesheet request for browser class ' + browserClass(sheet.userAgent));
|
|
84
89
|
representations.add(key);
|
|
85
90
|
const urls = [...sheet.css.matchAll(/url\((https?:\/\/[^)]+)\)/g)].map(m => m[1]!);
|
|
86
91
|
if (urls.length === 0 || urls.some(url => !decoded.has(assetKey(url))))
|
|
@@ -91,7 +96,7 @@ export async function publishCatalog(ctx: HandlerContext): Promise<Response> {
|
|
|
91
96
|
if (!pairs || typeof sheet.userAgent !== 'string' || typeof sheet.css !== 'string' || !sheet.css.includes('@font-face'))
|
|
92
97
|
throw new Error('Invalid CSS2 stylesheet representation');
|
|
93
98
|
const key = css2Key(ctx, pairs, sheet.userAgent);
|
|
94
|
-
if (representations.has(key)) throw new Error('Duplicate stylesheet request');
|
|
99
|
+
if (representations.has(key)) throw new Error('Duplicate stylesheet request for browser class ' + browserClass(sheet.userAgent));
|
|
95
100
|
representations.add(key);
|
|
96
101
|
const urls = [...sheet.css.matchAll(/url\((https?:\/\/[^)]+)\)/g)].map(m => m[1]!);
|
|
97
102
|
if (urls.length === 0 || urls.some(url => !decoded.has(assetKey(url))))
|
|
@@ -118,13 +123,13 @@ export async function publishCatalog(ctx: HandlerContext): Promise<Response> {
|
|
|
118
123
|
const key = cssKey(ctx, sheet.family as string, sheet.subset as string, sheet.userAgent as string);
|
|
119
124
|
const blob = 'css-' + ctx.crypto.digest('sha256', sheet.css as string);
|
|
120
125
|
await ctx.blobs.put(blob, new TextEncoder().encode(sheet.css as string));
|
|
121
|
-
await ctx.record(CSS, { family: sheet.family, subset: sheet.subset, userAgent: sheet.userAgent, blob }, fingerprint + ':' + key);
|
|
126
|
+
await ctx.record(CSS, { family: sheet.family, subset: sheet.subset, userAgent: sheet.userAgent, browserClass: browserClass(sheet.userAgent as string), blob }, fingerprint + ':' + key);
|
|
122
127
|
}
|
|
123
128
|
for (const sheet of sheets2) {
|
|
124
129
|
const key = css2Key(ctx, css2Query(new URLSearchParams(sheet.query as string))!, sheet.userAgent as string);
|
|
125
130
|
const blob = 'css-' + ctx.crypto.digest('sha256', sheet.css as string);
|
|
126
131
|
await ctx.blobs.put(blob, new TextEncoder().encode(sheet.css as string));
|
|
127
|
-
await ctx.record(CSS, { query: sheet.query, userAgent: sheet.userAgent, blob }, fingerprint + ':' + key);
|
|
132
|
+
await ctx.record(CSS, { query: sheet.query, userAgent: sheet.userAgent, browserClass: browserClass(sheet.userAgent as string), blob }, fingerprint + ':' + key);
|
|
128
133
|
}
|
|
129
134
|
await ctx.record(CATALOG, { families: [...names], fingerprint }, 'published');
|
|
130
135
|
return Response.json({ families: [...names] }, { status: 201 });
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// GENERATED by
|
|
1
|
+
// GENERATED by twin-standard create --index: the handler map, by operationId, from the families' files (each
|
|
2
2
|
// handler named as its operation). Do not edit; add a handler to its family's file and generate again.
|
|
3
3
|
import type { Handler } from '@volter/world-core';
|
|
4
4
|
import * as webfonts from './webfonts.ts';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// GENERATED by
|
|
1
|
+
// GENERATED by twin-standard create --index: the handler map, by operationId, from the families' files (each
|
|
2
2
|
// handler named as its operation). Do not edit; add a handler to its family's file and generate again.
|
|
3
3
|
import type { Handler } from '@volter/world-core';
|
|
4
4
|
import * as l from './l.ts';
|
package/css/spec/SOURCE.md
CHANGED
|
@@ -7,9 +7,17 @@ GET /css?family=<family>:<variant>&subset=<subsets> with the exact configured
|
|
|
7
7
|
User-Agent and consumes each @font-face's font-family, style, weight and src URL.
|
|
8
8
|
Dub's Open Graph routes call GET /css2?family=Inter:wght@<weight> and Rallly's
|
|
9
9
|
calls GET /css2?family=Noto+Sans+SC:wght@<weight>&text=<text>, both from a server
|
|
10
|
-
without a browser User-Agent, and read the truetype src URL.
|
|
10
|
+
without a browser User-Agent, and read the truetype src URL. next/font/google's build (Rallly's and
|
|
11
|
+
Cal.diy's `next build`) calls GET /css2?family=Inter:wght@100..900&display=swap with a Chrome User-Agent and
|
|
12
|
+
reads each woff2 src URL of its unicode-range faces.
|
|
13
|
+
Twenty's front end (twentyhq/twenty 323e664, packages/twenty-front/index.html:33,37) links
|
|
14
|
+
GET /css2?family=Inter:wght@400;500;600&display=swap and GET /css2?family=DM+Mono:wght@400;500;600&display=swap
|
|
15
|
+
from the browser, which loads the woff2 faces its text needs.
|
|
16
|
+
A representation is published for a browser class (src/semantics/shared.ts browserClass): Google serves any
|
|
17
|
+
browser, choosing WOFF 2.0 and unicode-range subsets by the browser its User-Agent names, and the classes are
|
|
18
|
+
the agents Google answered the same bytes (vendor/browser-classes.json).
|
|
11
19
|
The supplied representations retain actual vendor CSS. Requests without a
|
|
12
|
-
published
|
|
20
|
+
representation published for their class answer a refusal, never an invented font format.
|
|
13
21
|
text/display/effect on /css are outside this client's demand.
|
|
14
22
|
|
|
15
23
|
SHA-256 `openapi.json`: bf7b9a3b70c3a75712fb784e22bfe07c0008dd55e1aa93b31cba8da26a64fb60
|
package/css/src/semantics/css.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { HandlerContext } from '@volter/world-core';
|
|
2
|
-
import { CATALOG, CSS, cssKey, htmlError } from './shared.ts';
|
|
2
|
+
import { browserClass, CATALOG, CSS, cssKey, htmlError } from './shared.ts';
|
|
3
3
|
|
|
4
4
|
// source: spec:getStylesheet "The representation is selected for the requesting browser."
|
|
5
5
|
export async function getStylesheet(ctx: HandlerContext): Promise<Response> {
|
|
@@ -8,11 +8,13 @@ export async function getStylesheet(ctx: HandlerContext): Promise<Response> {
|
|
|
8
8
|
if (!family || [...query.keys()].some(k => !['family','subset'].includes(k)))
|
|
9
9
|
return htmlError(400, 'Bad font request');
|
|
10
10
|
const generation = ctx.row(CATALOG, 'published')?.fingerprint;
|
|
11
|
-
|
|
11
|
+
// the representation published for the browser class the agent belongs to (shared.ts browserClass)
|
|
12
|
+
const userAgent = ctx.call.request.headers.get('user-agent') ?? '';
|
|
13
|
+
const key = cssKey(ctx, family, subset, userAgent);
|
|
12
14
|
const row = generation === undefined ? undefined : ctx.row(CSS, String(generation) + ':' + key);
|
|
13
15
|
// Where the documentation stops: no supplied representation is a named
|
|
14
16
|
// refusal, not an invented conversion or another browser's font format.
|
|
15
|
-
if (!row) return htmlError(400, 'Font family or browser representation is not published');
|
|
17
|
+
if (!row) return htmlError(400, 'Font family or browser representation is not published (' + browserClass(userAgent) + ')');
|
|
16
18
|
const body = await ctx.blobs.get(String(row.blob));
|
|
17
19
|
if (!body) return htmlError(404, 'Font stylesheet not found');
|
|
18
20
|
return new Response(Uint8Array.from(body).buffer, { headers: { 'content-type': 'text/css; charset=utf-8', 'content-length': String(body.length), 'vary': 'User-Agent' } });
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { HandlerContext } from '@volter/world-core';
|
|
2
|
-
import { CATALOG, CSS, css2Key, css2Query, htmlError } from './shared.ts';
|
|
2
|
+
import { browserClass, CATALOG, CSS, css2Key, css2Query, htmlError } from './shared.ts';
|
|
3
3
|
|
|
4
4
|
// source: spec:getStylesheet2 "To request multiple families, specify the family= param for each family."
|
|
5
5
|
export async function getStylesheet2(ctx: HandlerContext): Promise<Response> {
|
|
@@ -7,13 +7,14 @@ export async function getStylesheet2(ctx: HandlerContext): Promise<Response> {
|
|
|
7
7
|
if (!pairs) return htmlError(400, 'Bad font request');
|
|
8
8
|
const generation = ctx.row(CATALOG, 'published')?.fingerprint;
|
|
9
9
|
const userAgent = ctx.call.request.headers.get('user-agent') ?? '';
|
|
10
|
-
|
|
11
|
-
//
|
|
12
|
-
// (vendor/inter.json).
|
|
13
|
-
//
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
10
|
+
// The representation is the one published for the browser class the agent belongs to (shared.ts browserClass): a
|
|
11
|
+
// request with no browser's agent ("node", "undici", none) reads the truetype class, as Google answered the same CSS
|
|
12
|
+
// to all three (vendor/inter.json).
|
|
13
|
+
// source: spec:getStylesheet2 "The representation is selected for the requesting browser."
|
|
14
|
+
const row = generation === undefined ? undefined : ctx.row(CSS, String(generation) + ':' + css2Key(ctx, pairs, userAgent));
|
|
15
|
+
// Where the documentation stops: a class with no published representation is a named refusal, never another
|
|
16
|
+
// class's font format.
|
|
17
|
+
if (!row) return htmlError(400, 'Font family or browser representation is not published (' + browserClass(userAgent) + ')');
|
|
17
18
|
const body = await ctx.blobs.get(String(row.blob));
|
|
18
19
|
if (!body) return htmlError(404, 'Font stylesheet not found');
|
|
19
20
|
return new Response(Uint8Array.from(body).buffer, { headers: { 'content-type': 'text/css; charset=utf-8', 'content-length': String(body.length), 'access-control-allow-origin': '*' } });
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// GENERATED by
|
|
1
|
+
// GENERATED by twin-standard create --index: the handler map, by operationId, from the families' files (each
|
|
2
2
|
// handler named as its operation). Do not edit; add a handler to its family's file and generate again.
|
|
3
3
|
import type { Handler } from '@volter/world-core';
|
|
4
4
|
import * as css from './css.ts';
|
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"https://developers.google.com/fonts/docs/developer_api": {
|
|
3
3
|
"status": 200,
|
|
4
|
-
"checked": "2026-
|
|
4
|
+
"checked": "2026-11-11",
|
|
5
|
+
"quotes": {
|
|
6
|
+
"by including an API key with each request": true
|
|
7
|
+
}
|
|
8
|
+
},
|
|
9
|
+
"https://docs.cloud.google.com/docs/authentication/api-keys": {
|
|
10
|
+
"status": 200,
|
|
11
|
+
"checked": "2026-11-11",
|
|
12
|
+
"quotes": {
|
|
13
|
+
"Standard API keys provide a way to associate a request with a project for billing and quota purposes.": true
|
|
14
|
+
}
|
|
5
15
|
}
|
|
6
16
|
}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
import { FONT, CATALOG, CSS, FILE, KEY, object, assetKey, cssKey, css2Key, css2Query, googleError } from "./shared.js";
|
|
1
|
+
import { FONT, CATALOG, CSS, FILE, KEY, object, assetKey, browserClass, cssKey, css2Key, css2Query, googleError } from "./shared.js";
|
|
2
2
|
// source: https://developers.google.com/fonts/docs/developer_api
|
|
3
3
|
export async function appCredentials(ctx) {
|
|
4
|
+
// An app's key is a standard API key, made on a Google Cloud project's Credentials page and associating each request
|
|
5
|
+
// with that project for billing and quota; the Fonts Developer API takes it as `key=` and nothing else.
|
|
6
|
+
// source: https://docs.cloud.google.com/docs/authentication/api-keys "Standard API keys provide a way to associate a request with a project for billing and quota purposes."
|
|
7
|
+
// source: https://developers.google.com/fonts/docs/developer_api "by including an API key with each request"
|
|
4
8
|
const project = await ctx.tenant('_project', async (organization) => String((await ctx.create('_project', { name: organization.label, owner: organization.members[0] }, 'project.create')).id));
|
|
5
9
|
const value = async (id) => 'AIza' + (await ctx.secret(`googlefonts:application-api-key:${id}`)).slice(0, 35);
|
|
6
10
|
const key = await ctx.accountKey('api-key', KEY, {
|
|
@@ -78,9 +82,10 @@ export async function publishCatalog(ctx) {
|
|
|
78
82
|
if (!font || !font.variants.includes(variant) ||
|
|
79
83
|
sheet.subset.split(',').some(s => !font.subsets.includes(s)))
|
|
80
84
|
throw new Error('Stylesheet request must name a published font, variant and subset');
|
|
85
|
+
// a stylesheet is one browser class's representation (shared.ts browserClass): its user agent is that class's sample
|
|
81
86
|
const key = cssKey(ctx, sheet.family, sheet.subset, sheet.userAgent);
|
|
82
87
|
if (representations.has(key))
|
|
83
|
-
throw new Error('Duplicate stylesheet request');
|
|
88
|
+
throw new Error('Duplicate stylesheet request for browser class ' + browserClass(sheet.userAgent));
|
|
84
89
|
representations.add(key);
|
|
85
90
|
const urls = [...sheet.css.matchAll(/url\((https?:\/\/[^)]+)\)/g)].map(m => m[1]);
|
|
86
91
|
if (urls.length === 0 || urls.some(url => !decoded.has(assetKey(url))))
|
|
@@ -92,7 +97,7 @@ export async function publishCatalog(ctx) {
|
|
|
92
97
|
throw new Error('Invalid CSS2 stylesheet representation');
|
|
93
98
|
const key = css2Key(ctx, pairs, sheet.userAgent);
|
|
94
99
|
if (representations.has(key))
|
|
95
|
-
throw new Error('Duplicate stylesheet request');
|
|
100
|
+
throw new Error('Duplicate stylesheet request for browser class ' + browserClass(sheet.userAgent));
|
|
96
101
|
representations.add(key);
|
|
97
102
|
const urls = [...sheet.css.matchAll(/url\((https?:\/\/[^)]+)\)/g)].map(m => m[1]);
|
|
98
103
|
if (urls.length === 0 || urls.some(url => !decoded.has(assetKey(url))))
|
|
@@ -125,13 +130,13 @@ export async function publishCatalog(ctx) {
|
|
|
125
130
|
const key = cssKey(ctx, sheet.family, sheet.subset, sheet.userAgent);
|
|
126
131
|
const blob = 'css-' + ctx.crypto.digest('sha256', sheet.css);
|
|
127
132
|
await ctx.blobs.put(blob, new TextEncoder().encode(sheet.css));
|
|
128
|
-
await ctx.record(CSS, { family: sheet.family, subset: sheet.subset, userAgent: sheet.userAgent, blob }, fingerprint + ':' + key);
|
|
133
|
+
await ctx.record(CSS, { family: sheet.family, subset: sheet.subset, userAgent: sheet.userAgent, browserClass: browserClass(sheet.userAgent), blob }, fingerprint + ':' + key);
|
|
129
134
|
}
|
|
130
135
|
for (const sheet of sheets2) {
|
|
131
136
|
const key = css2Key(ctx, css2Query(new URLSearchParams(sheet.query)), sheet.userAgent);
|
|
132
137
|
const blob = 'css-' + ctx.crypto.digest('sha256', sheet.css);
|
|
133
138
|
await ctx.blobs.put(blob, new TextEncoder().encode(sheet.css));
|
|
134
|
-
await ctx.record(CSS, { query: sheet.query, userAgent: sheet.userAgent, blob }, fingerprint + ':' + key);
|
|
139
|
+
await ctx.record(CSS, { query: sheet.query, userAgent: sheet.userAgent, browserClass: browserClass(sheet.userAgent), blob }, fingerprint + ':' + key);
|
|
135
140
|
}
|
|
136
141
|
await ctx.record(CATALOG, { families: [...names], fingerprint }, 'published');
|
|
137
142
|
return Response.json({ families: [...names] }, { status: 201 });
|
package/dist/css/spec/SOURCE.md
CHANGED
|
@@ -7,9 +7,17 @@ GET /css?family=<family>:<variant>&subset=<subsets> with the exact configured
|
|
|
7
7
|
User-Agent and consumes each @font-face's font-family, style, weight and src URL.
|
|
8
8
|
Dub's Open Graph routes call GET /css2?family=Inter:wght@<weight> and Rallly's
|
|
9
9
|
calls GET /css2?family=Noto+Sans+SC:wght@<weight>&text=<text>, both from a server
|
|
10
|
-
without a browser User-Agent, and read the truetype src URL.
|
|
10
|
+
without a browser User-Agent, and read the truetype src URL. next/font/google's build (Rallly's and
|
|
11
|
+
Cal.diy's `next build`) calls GET /css2?family=Inter:wght@100..900&display=swap with a Chrome User-Agent and
|
|
12
|
+
reads each woff2 src URL of its unicode-range faces.
|
|
13
|
+
Twenty's front end (twentyhq/twenty 323e664, packages/twenty-front/index.html:33,37) links
|
|
14
|
+
GET /css2?family=Inter:wght@400;500;600&display=swap and GET /css2?family=DM+Mono:wght@400;500;600&display=swap
|
|
15
|
+
from the browser, which loads the woff2 faces its text needs.
|
|
16
|
+
A representation is published for a browser class (src/semantics/shared.ts browserClass): Google serves any
|
|
17
|
+
browser, choosing WOFF 2.0 and unicode-range subsets by the browser its User-Agent names, and the classes are
|
|
18
|
+
the agents Google answered the same bytes (vendor/browser-classes.json).
|
|
11
19
|
The supplied representations retain actual vendor CSS. Requests without a
|
|
12
|
-
published
|
|
20
|
+
representation published for their class answer a refusal, never an invented font format.
|
|
13
21
|
text/display/effect on /css are outside this client's demand.
|
|
14
22
|
|
|
15
23
|
SHA-256 `openapi.json`: bf7b9a3b70c3a75712fb784e22bfe07c0008dd55e1aa93b31cba8da26a64fb60
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CATALOG, CSS, cssKey, htmlError } from "./shared.js";
|
|
1
|
+
import { browserClass, CATALOG, CSS, cssKey, htmlError } from "./shared.js";
|
|
2
2
|
// source: spec:getStylesheet "The representation is selected for the requesting browser."
|
|
3
3
|
export async function getStylesheet(ctx) {
|
|
4
4
|
const query = new URL(ctx.call.request.url).searchParams;
|
|
@@ -6,12 +6,14 @@ export async function getStylesheet(ctx) {
|
|
|
6
6
|
if (!family || [...query.keys()].some(k => !['family', 'subset'].includes(k)))
|
|
7
7
|
return htmlError(400, 'Bad font request');
|
|
8
8
|
const generation = ctx.row(CATALOG, 'published')?.fingerprint;
|
|
9
|
-
|
|
9
|
+
// the representation published for the browser class the agent belongs to (shared.ts browserClass)
|
|
10
|
+
const userAgent = ctx.call.request.headers.get('user-agent') ?? '';
|
|
11
|
+
const key = cssKey(ctx, family, subset, userAgent);
|
|
10
12
|
const row = generation === undefined ? undefined : ctx.row(CSS, String(generation) + ':' + key);
|
|
11
13
|
// Where the documentation stops: no supplied representation is a named
|
|
12
14
|
// refusal, not an invented conversion or another browser's font format.
|
|
13
15
|
if (!row)
|
|
14
|
-
return htmlError(400, 'Font family or browser representation is not published');
|
|
16
|
+
return htmlError(400, 'Font family or browser representation is not published (' + browserClass(userAgent) + ')');
|
|
15
17
|
const body = await ctx.blobs.get(String(row.blob));
|
|
16
18
|
if (!body)
|
|
17
19
|
return htmlError(404, 'Font stylesheet not found');
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CATALOG, CSS, css2Key, css2Query, htmlError } from "./shared.js";
|
|
1
|
+
import { browserClass, CATALOG, CSS, css2Key, css2Query, htmlError } from "./shared.js";
|
|
2
2
|
// source: spec:getStylesheet2 "To request multiple families, specify the family= param for each family."
|
|
3
3
|
export async function getStylesheet2(ctx) {
|
|
4
4
|
const pairs = css2Query(new URL(ctx.call.request.url).searchParams);
|
|
@@ -6,14 +6,15 @@ export async function getStylesheet2(ctx) {
|
|
|
6
6
|
return htmlError(400, 'Bad font request');
|
|
7
7
|
const generation = ctx.row(CATALOG, 'published')?.fingerprint;
|
|
8
8
|
const userAgent = ctx.call.request.headers.get('user-agent') ?? '';
|
|
9
|
-
|
|
10
|
-
//
|
|
11
|
-
// (vendor/inter.json).
|
|
12
|
-
//
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
// The representation is the one published for the browser class the agent belongs to (shared.ts browserClass): a
|
|
10
|
+
// request with no browser's agent ("node", "undici", none) reads the truetype class, as Google answered the same CSS
|
|
11
|
+
// to all three (vendor/inter.json).
|
|
12
|
+
// source: spec:getStylesheet2 "The representation is selected for the requesting browser."
|
|
13
|
+
const row = generation === undefined ? undefined : ctx.row(CSS, String(generation) + ':' + css2Key(ctx, pairs, userAgent));
|
|
14
|
+
// Where the documentation stops: a class with no published representation is a named refusal, never another
|
|
15
|
+
// class's font format.
|
|
15
16
|
if (!row)
|
|
16
|
-
return htmlError(400, 'Font family or browser representation is not published');
|
|
17
|
+
return htmlError(400, 'Font family or browser representation is not published (' + browserClass(userAgent) + ')');
|
|
17
18
|
const body = await ctx.blobs.get(String(row.blob));
|
|
18
19
|
if (!body)
|
|
19
20
|
return htmlError(404, 'Font stylesheet not found');
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"//": "GENERATED by twin-standard facts from the pack descriptor; no prior-artifact entries are retained.",
|
|
3
|
+
"packs": {
|
|
4
|
+
"googlefonts": {
|
|
5
|
+
"adoption": {
|
|
6
|
+
"sdks": [],
|
|
7
|
+
"envStems": [
|
|
8
|
+
"GOOGLE_FONTS"
|
|
9
|
+
],
|
|
10
|
+
"modules": [
|
|
11
|
+
"next/font/google"
|
|
12
|
+
]
|
|
13
|
+
},
|
|
14
|
+
"hosts": [
|
|
15
|
+
{
|
|
16
|
+
"host": "www.googleapis.com",
|
|
17
|
+
"pathPattern": "^/webfonts/v1/webfonts$"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"host": "webfonts.googleapis.com",
|
|
21
|
+
"pathPattern": "^/v1/webfonts$"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"host": "fonts.googleapis.com",
|
|
25
|
+
"pathPattern": "^/css2?$"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"host": "fonts.gstatic.com",
|
|
29
|
+
"pathPattern": "^/(s/|l/font$)"
|
|
30
|
+
}
|
|
31
|
+
],
|
|
32
|
+
"archetype": "crud",
|
|
33
|
+
"protocol": {
|
|
34
|
+
"declared": "3",
|
|
35
|
+
"major": 3,
|
|
36
|
+
"standing": "current"
|
|
37
|
+
},
|
|
38
|
+
"transport": "rest",
|
|
39
|
+
"resources": [
|
|
40
|
+
"webfont",
|
|
41
|
+
"_api_key",
|
|
42
|
+
"_font_catalog",
|
|
43
|
+
"_stylesheet",
|
|
44
|
+
"_font_file"
|
|
45
|
+
],
|
|
46
|
+
"endpointEnvNone": "Pinned helper hardcodes the vendor API and CSS hosts, then downloads their returned gstatic URLs. Scoped host routing preserves these URLs.",
|
|
47
|
+
"credentialDoor": {
|
|
48
|
+
"path": "/_twin/app-credentials",
|
|
49
|
+
"body": {},
|
|
50
|
+
"account": {
|
|
51
|
+
"field": "account",
|
|
52
|
+
"tenancy": "Google Cloud project",
|
|
53
|
+
"source": "https://developers.google.com/fonts/docs/developer_api"
|
|
54
|
+
},
|
|
55
|
+
"deployment": {
|
|
56
|
+
"field": "deployment",
|
|
57
|
+
"object": "Project API key",
|
|
58
|
+
"source": "https://docs.cloud.google.com/docs/authentication/api-keys"
|
|
59
|
+
},
|
|
60
|
+
"fill": {
|
|
61
|
+
"GOOGLE_FONTS_API_KEY": "api_key"
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"serveExport": "createGooglefontsTwinServer"
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
"undeclared": []
|
|
68
|
+
}
|