hyperframes 0.8.84 → 0.8.86

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.
@@ -94979,96 +94979,234 @@ function partitionWeightRuns(faces, coveredWeights) {
94979
94979
  for (const face of unsortable) runs.push([face]);
94980
94980
  return runs;
94981
94981
  }
94982
- async function buildFontFaceCss(requestedFamilies, options, fontText) {
94982
+ async function bundledFamilyFaceRules(canonical, normalizedFamily, emitFamily, optional, options, fontText) {
94983
+ const rules = [];
94984
+ const coveredWeights = /* @__PURE__ */ new Set();
94985
+ const bundledRules = [];
94986
+ for (const face of canonical.faces) {
94987
+ const style = face.style || "normal";
94988
+ const src = fontDataUri(canonical.packageName, face.weight, style);
94989
+ bundledRules.push(
94990
+ buildFontFaceRule(emitFamily, src, face.weight, style, BUNDLED_SUBSET_UNICODE_RANGE)
94991
+ );
94992
+ coveredWeights.add(coverageKey(face.weight, style));
94993
+ }
94994
+ const canonicalFamily = resolveAliasDisplayName(normalizedFamily);
94995
+ const googleFaces = canonicalFamily ? await fetchFamilyFaces(canonicalFamily, optional, options, fontText) : [];
94996
+ const supplementary = googleFaces.filter(
94997
+ (face) => !coveredWeights.has(coverageKey(face.weight, face.style)) || !isBundledSubsetRange(face.unicodeRange)
94998
+ );
94999
+ const runs = groupFacesBySource(supplementary).flatMap(
95000
+ (group) => partitionWeightRuns(group, coveredWeights)
95001
+ );
95002
+ const firstAppearance = (run) => Math.min(...run.map((face) => supplementary.indexOf(face)));
95003
+ for (const run of [...runs].sort((a, b) => firstAppearance(a) - firstAppearance(b))) {
95004
+ const first = run[0];
95005
+ const last = run[run.length - 1];
95006
+ if (!first || !last) continue;
95007
+ const weight = run.length > 1 ? `${first.weight} ${last.weight}` : first.weight;
95008
+ rules.push(
95009
+ buildFontFaceRule(emitFamily, first.dataUri, weight, first.style, first.unicodeRange)
95010
+ );
95011
+ }
95012
+ rules.push(...bundledRules);
95013
+ return rules;
95014
+ }
95015
+ async function googleFamilyFaceRules(lookupFamily, emitFamily, optional, options, fontText) {
95016
+ const googleFaces = await fetchFamilyFaces(lookupFamily, optional, options, fontText);
95017
+ if (googleFaces.length === 0) return null;
95018
+ return googleFaces.map(
95019
+ (face) => buildFontFaceRule(emitFamily, face.dataUri, face.weight, face.style, face.unicodeRange)
95020
+ );
95021
+ }
95022
+ async function systemFamilyFaceRules(lookupFamily, emitFamily) {
95023
+ const variants = locateSystemFontVariants(lookupFamily);
95024
+ if (variants.length === 0) return null;
95025
+ const rules = [];
95026
+ let totalBytes = 0;
95027
+ for (const variant of variants) {
95028
+ const fontBuffer = readFileSync12(variant.path);
95029
+ totalBytes += fontBuffer.length;
95030
+ const dataUri = await fontToDataUri(fontBuffer, variant.format);
95031
+ rules.push(buildFontFaceRule(emitFamily, dataUri, variant.weight, variant.style));
95032
+ }
95033
+ if (totalBytes > SYSTEM_FONT_SIZE_LIMIT) {
95034
+ defaultLogger.warn(
95035
+ `[Compiler] System font "${lookupFamily}" is large (${(totalBytes / 1024 / 1024).toFixed(1)} MB total across ${variants.length} variant(s)) \u2014 embedding anyway. Consider font subsetting for production.`
95036
+ );
95037
+ }
95038
+ defaultLogger.info(
95039
+ `[Compiler] Embedded system font "${lookupFamily}" \u2014 ${variants.length} variant(s), ${(totalBytes / 1024).toFixed(0)} KB total`
95040
+ );
95041
+ return rules;
95042
+ }
95043
+ async function resolveFamilyFaceRules(normalizedFamily, lookupFamily, emitFamily, optional, options, fontText) {
95044
+ const canonicalKey = FONT_ALIASES[normalizedFamily];
95045
+ if (canonicalKey) {
95046
+ const canonical = CANONICAL_FONTS[canonicalKey];
95047
+ if (!canonical) return [];
95048
+ return bundledFamilyFaceRules(
95049
+ canonical,
95050
+ normalizedFamily,
95051
+ emitFamily,
95052
+ optional,
95053
+ options,
95054
+ fontText
95055
+ );
95056
+ }
95057
+ return await googleFamilyFaceRules(lookupFamily, emitFamily, optional, options, fontText) ?? (options.allowSystemFontCapture && !pageNamedThisFile(lookupFamily, options) ? await systemFamilyFaceRules(lookupFamily, emitFamily) : null);
95058
+ }
95059
+ function googleFontsUrlFamilies(rawUrl) {
95060
+ let url;
95061
+ try {
95062
+ url = new URL(rawUrl.trim(), "https://document.invalid/");
95063
+ } catch {
95064
+ return [];
95065
+ }
95066
+ if (url.hostname !== "fonts.googleapis.com") return [];
95067
+ if (url.pathname !== "/css" && url.pathname !== "/css2") return [];
95068
+ return url.searchParams.getAll("family").flatMap((value) => value.split("|")).map((entry) => (entry.split(":", 1)[0] ?? "").trim()).filter((family) => family.length > 0);
95069
+ }
95070
+ function importRuleUrl(params) {
95071
+ const trimmed = params.trim();
95072
+ if (trimmed.toLowerCase().startsWith("url(")) {
95073
+ const close = trimmed.indexOf(")");
95074
+ if (close < 0 || !isUnconditionalMedia(trimmed.slice(close + 1))) return null;
95075
+ return trimmed.slice(4, close).trim().replace(/^['"]/, "").replace(/['"]$/, "");
95076
+ }
95077
+ const quote = trimmed[0];
95078
+ if (quote !== '"' && quote !== "'") return null;
95079
+ const end = trimmed.indexOf(quote, 1);
95080
+ return end > 0 && isUnconditionalMedia(trimmed.slice(end + 1)) ? trimmed.slice(1, end) : null;
95081
+ }
95082
+ function fontFaceRuleFamily(rule) {
95083
+ let family;
95084
+ rule.walkDecls((decl) => {
95085
+ if (decl.prop.toLowerCase() === "font-family") family = parseFontFamilyValue(decl.value)[0];
95086
+ });
95087
+ return family;
95088
+ }
95089
+ function isUnconditionalMedia(media) {
95090
+ const normalized2 = (media ?? "").trim().toLowerCase();
95091
+ return normalized2 === "" || normalized2 === "all";
95092
+ }
95093
+ function isUnconditionalStylesheet(element) {
95094
+ const type = (element.getAttribute("type") ?? "").trim().toLowerCase();
95095
+ return isUnconditionalMedia(element.getAttribute("media")) && (type === "" || type === "text/css") && !(element.getAttribute("title") ?? "").trim() && !element.hasAttribute("disabled");
95096
+ }
95097
+ function declareStyleFontFamilies(styleEl, register) {
95098
+ if (!isUnconditionalStylesheet(styleEl)) return;
95099
+ const root = parseCssRoot(styleEl.textContent ?? "");
95100
+ if (!root) return;
95101
+ root.walkAtRules((atRule) => {
95102
+ if (atRule.parent?.type !== "root") return;
95103
+ const name = atRule.name.toLowerCase();
95104
+ if (name === "import") {
95105
+ const url = importRuleUrl(atRule.params);
95106
+ if (url) for (const family2 of googleFontsUrlFamilies(url)) register(family2);
95107
+ return;
95108
+ }
95109
+ if (name !== "font-face") return;
95110
+ const family = fontFaceRuleFamily(atRule);
95111
+ if (family) register(family, atRule);
95112
+ });
95113
+ }
95114
+ function declareLinkedFontFamilies(link, register) {
95115
+ const rel = (link.getAttribute("rel") ?? "").toLowerCase().split(/\s+/);
95116
+ if (!rel.includes("stylesheet") || rel.includes("alternate") || !isUnconditionalStylesheet(link))
95117
+ return;
95118
+ for (const family of googleFontsUrlFamilies(link.getAttribute("href") ?? "")) register(family);
95119
+ }
95120
+ function collectDeclaredFontFamilies(html) {
95121
+ const { document: document2 } = parseHTML(html);
95122
+ const declared = /* @__PURE__ */ new Map();
95123
+ const register = (family, fontFaceRule) => {
95124
+ const key2 = normalizeFamilyName(family);
95125
+ if (!key2 || GENERIC_FAMILIES2.has(key2)) return;
95126
+ let entry = declared.get(key2);
95127
+ if (!entry) {
95128
+ entry = { family, fontFaceRules: [] };
95129
+ declared.set(key2, entry);
95130
+ }
95131
+ if (fontFaceRule) entry.fontFaceRules.push(fontFaceRule);
95132
+ };
95133
+ for (const styleEl of Array.from(document2.querySelectorAll("style"))) {
95134
+ declareStyleFontFamilies(styleEl, register);
95135
+ }
95136
+ for (const link of Array.from(document2.querySelectorAll("link[href]"))) {
95137
+ declareLinkedFontFamilies(link, register);
95138
+ }
95139
+ return declared;
95140
+ }
95141
+ function familySpellingKey(family) {
95142
+ return family.toLowerCase().replace(/[\s_+-]+/g, "");
95143
+ }
95144
+ function findDeclaredFamilyAlias(authoredFamily, declaredFamilies) {
95145
+ if (declaredFamilies.has(normalizeFamilyName(authoredFamily))) return null;
95146
+ const key2 = familySpellingKey(authoredFamily);
95147
+ if (!key2) return null;
95148
+ let match = null;
95149
+ for (const entry of declaredFamilies.values()) {
95150
+ if (familySpellingKey(entry.family) !== key2) continue;
95151
+ if (match) return null;
95152
+ match = entry;
95153
+ }
95154
+ return match;
95155
+ }
95156
+ async function resolveDeclaredFamilyAlias(authoredFamily, declaredFamilies, optional, options, fontText) {
95157
+ const declared = findDeclaredFamilyAlias(authoredFamily, declaredFamilies);
95158
+ if (!declared) return null;
95159
+ let rules;
95160
+ if (declared.fontFaceRules.length > 0) {
95161
+ rules = declared.fontFaceRules.map((rule) => {
95162
+ const renamed = rule.clone();
95163
+ renamed.walkDecls((decl) => {
95164
+ if (decl.prop.toLowerCase() === "font-family") decl.value = `"${authoredFamily}"`;
95165
+ });
95166
+ return renamed.toString();
95167
+ });
95168
+ } else {
95169
+ rules = await resolveFamilyFaceRules(
95170
+ normalizeFamilyName(declared.family),
95171
+ declared.family,
95172
+ authoredFamily,
95173
+ optional,
95174
+ options,
95175
+ fontText
95176
+ );
95177
+ }
95178
+ if (!rules) return null;
95179
+ defaultLogger.warn(
95180
+ `[Compiler] font-family "${authoredFamily}" is not a known family; resolved it to "${declared.family}", which this document declares. Correct the authored font-family to "${declared.family}".`
95181
+ );
95182
+ return rules;
95183
+ }
95184
+ async function buildFontFaceCss(requestedFamilies, options, declaredFamilies, fontText) {
94983
95185
  const rules = [];
94984
95186
  const unresolved = [];
94985
95187
  const requiredFirst = [...requestedFamilies].sort(
94986
95188
  ([, a], [, b]) => Number(a.optional) - Number(b.optional)
94987
95189
  );
94988
95190
  for (const [normalizedFamily, { family: originalCaseFamily, optional }] of requiredFirst) {
94989
- const canonicalKey = FONT_ALIASES[normalizedFamily];
94990
- if (canonicalKey) {
94991
- const canonical = CANONICAL_FONTS[canonicalKey];
94992
- if (!canonical) continue;
94993
- const coveredWeights = /* @__PURE__ */ new Set();
94994
- const bundledRules = [];
94995
- for (const face of canonical.faces) {
94996
- const style = face.style || "normal";
94997
- const src = fontDataUri(canonical.packageName, face.weight, style);
94998
- bundledRules.push(
94999
- buildFontFaceRule(
95000
- originalCaseFamily,
95001
- src,
95002
- face.weight,
95003
- style,
95004
- BUNDLED_SUBSET_UNICODE_RANGE
95005
- )
95006
- );
95007
- coveredWeights.add(coverageKey(face.weight, style));
95008
- }
95009
- const canonicalFamily = resolveAliasDisplayName(normalizedFamily);
95010
- const googleFaces2 = canonicalFamily ? await fetchFamilyFaces(canonicalFamily, optional, options, fontText) : [];
95011
- const supplementary = googleFaces2.filter(
95012
- (face) => !coveredWeights.has(coverageKey(face.weight, face.style)) || !isBundledSubsetRange(face.unicodeRange)
95013
- );
95014
- const runs = groupFacesBySource(supplementary).flatMap(
95015
- (group) => partitionWeightRuns(group, coveredWeights)
95016
- );
95017
- const firstAppearance = (run) => Math.min(...run.map((face) => supplementary.indexOf(face)));
95018
- for (const run of [...runs].sort((a, b) => firstAppearance(a) - firstAppearance(b))) {
95019
- const first = run[0];
95020
- const last = run[run.length - 1];
95021
- if (!first || !last) continue;
95022
- const weight = run.length > 1 ? `${first.weight} ${last.weight}` : first.weight;
95023
- rules.push(
95024
- buildFontFaceRule(
95025
- originalCaseFamily,
95026
- first.dataUri,
95027
- weight,
95028
- first.style,
95029
- first.unicodeRange
95030
- )
95031
- );
95032
- }
95033
- rules.push(...bundledRules);
95034
- continue;
95035
- }
95036
- const googleFaces = await fetchFamilyFaces(originalCaseFamily, optional, options, fontText);
95037
- if (googleFaces.length > 0) {
95038
- for (const face of googleFaces) {
95039
- rules.push(
95040
- buildFontFaceRule(
95041
- originalCaseFamily,
95042
- face.dataUri,
95043
- face.weight,
95044
- face.style,
95045
- face.unicodeRange
95046
- )
95047
- );
95048
- }
95191
+ const familyRules = await resolveFamilyFaceRules(
95192
+ normalizedFamily,
95193
+ originalCaseFamily,
95194
+ originalCaseFamily,
95195
+ optional,
95196
+ options,
95197
+ fontText
95198
+ ) ?? // Last, so a family that already resolves keeps exactly the faces it had.
95199
+ await resolveDeclaredFamilyAlias(
95200
+ originalCaseFamily,
95201
+ declaredFamilies(),
95202
+ optional,
95203
+ options,
95204
+ fontText
95205
+ );
95206
+ if (familyRules) {
95207
+ rules.push(...familyRules);
95049
95208
  continue;
95050
95209
  }
95051
- if (options.allowSystemFontCapture && !pageNamedThisFile(originalCaseFamily, options)) {
95052
- const variants = locateSystemFontVariants(originalCaseFamily);
95053
- if (variants.length > 0) {
95054
- let totalBytes = 0;
95055
- for (const variant of variants) {
95056
- const fontBuffer = readFileSync12(variant.path);
95057
- totalBytes += fontBuffer.length;
95058
- const dataUri = await fontToDataUri(fontBuffer, variant.format);
95059
- rules.push(buildFontFaceRule(originalCaseFamily, dataUri, variant.weight, variant.style));
95060
- }
95061
- if (totalBytes > SYSTEM_FONT_SIZE_LIMIT) {
95062
- defaultLogger.warn(
95063
- `[Compiler] System font "${originalCaseFamily}" is large (${(totalBytes / 1024 / 1024).toFixed(1)} MB total across ${variants.length} variant(s)) \u2014 embedding anyway. Consider font subsetting for production.`
95064
- );
95065
- }
95066
- defaultLogger.info(
95067
- `[Compiler] Embedded system font "${originalCaseFamily}" \u2014 ${variants.length} variant(s), ${(totalBytes / 1024).toFixed(0)} KB total`
95068
- );
95069
- continue;
95070
- }
95071
- }
95072
95210
  unresolved.push(originalCaseFamily);
95073
95211
  }
95074
95212
  return {
@@ -95142,13 +95280,16 @@ var FontFetchError = class extends Error {
95142
95280
  familyName;
95143
95281
  url;
95144
95282
  cause;
95145
- constructor(familyName, url, message, cause, code = FONT_FETCH_FAILED) {
95283
+ /** Unlike `message`, holds no URLs: safe for callers that must not echo resolver output. */
95284
+ unresolvedFamilies;
95285
+ constructor(familyName, url, message, cause, code = FONT_FETCH_FAILED, unresolvedFamilies = []) {
95146
95286
  super(message);
95147
95287
  this.name = "FontFetchError";
95148
95288
  this.code = code;
95149
95289
  this.familyName = familyName;
95150
95290
  this.url = url;
95151
95291
  this.cause = cause;
95292
+ this.unresolvedFamilies = unresolvedFamilies;
95152
95293
  }
95153
95294
  };
95154
95295
  var FontFetchUnavailableError = class extends FontFetchError {
@@ -95571,9 +95712,11 @@ async function injectDeterministicFontFaces(html, options = {}) {
95571
95712
  if (pendingFamilies.size === 0) {
95572
95713
  return html;
95573
95714
  }
95715
+ let declaredFamilies;
95574
95716
  const { css, unresolved } = await buildFontFaceCss(
95575
95717
  pendingFamilies,
95576
95718
  fetchOptions,
95719
+ () => declaredFamilies ??= collectDeclaredFontFamilies(html),
95577
95720
  extractGoogleFontsText(html)
95578
95721
  );
95579
95722
  const required = unresolved.filter(
@@ -95583,7 +95726,10 @@ async function injectDeterministicFontFaces(html, options = {}) {
95583
95726
  throw new FontFetchError(
95584
95727
  required.join(", "),
95585
95728
  "",
95586
- `[Compiler] Unresolved fonts in fail-closed mode: ${required.join(", ")}. Distributed renders require all fonts to be resolvable.`
95729
+ `[Compiler] Unresolved fonts in fail-closed mode: ${required.join(", ")}. Distributed renders require all fonts to be resolvable.`,
95730
+ void 0,
95731
+ FONT_FETCH_FAILED,
95732
+ unresolved
95587
95733
  );
95588
95734
  }
95589
95735
  if (!css) {
@@ -105085,6 +105231,29 @@ function safeErrorName(error) {
105085
105231
  const name = error instanceof Error ? error.name : "UnknownError";
105086
105232
  return /^[A-Za-z][A-Za-z0-9]*$/.test(name) ? name : "Error";
105087
105233
  }
105234
+ var MAX_REPORTED_FAMILIES = 8;
105235
+ var MAX_FAMILY_NAME_LENGTH = 64;
105236
+ function unresolvedFamilyList(error) {
105237
+ if (!(error instanceof Error) || !("unresolvedFamilies" in error)) return [];
105238
+ const families = error.unresolvedFamilies;
105239
+ return Array.isArray(families) ? families : [];
105240
+ }
105241
+ function safeFamilyName(family) {
105242
+ if (typeof family !== "string" || /[:/?#&=%@\\]/.test(family)) return null;
105243
+ const safe = family.replace(/[^A-Za-z0-9 _-]+/g, " ").replace(/\s+/g, " ").trim().slice(0, MAX_FAMILY_NAME_LENGTH).trim();
105244
+ return safe || null;
105245
+ }
105246
+ function unresolvedFamiliesSuffix(error) {
105247
+ const families = unresolvedFamilyList(error);
105248
+ if (families.length === 0) return "";
105249
+ const printable = families.map(safeFamilyName).filter((name) => name !== null);
105250
+ const distinct = [...new Set(printable)];
105251
+ const shown = distinct.slice(0, MAX_REPORTED_FAMILIES);
105252
+ const hidden = families.length - printable.length + (distinct.length - shown.length);
105253
+ const listed = shown.map((name) => `"${name}"`).join(", ");
105254
+ const more = hidden > 0 ? `${listed ? " " : ""}(+${hidden} more)` : "";
105255
+ return `: unresolved font families: ${listed}${more}`;
105256
+ }
105088
105257
  async function runFontLocalize(io, localize) {
105089
105258
  const html = await io.readInput();
105090
105259
  if (!html.trim()) {
@@ -105100,15 +105269,17 @@ async function runFontLocalize(io, localize) {
105100
105269
  io.writeOutput(localized);
105101
105270
  return 0;
105102
105271
  } catch (error) {
105103
- io.writeError(`font localization failed (${safeErrorName(error)})
105104
- `);
105272
+ io.writeError(
105273
+ `font localization failed (${safeErrorName(error)})${unresolvedFamiliesSuffix(error)}
105274
+ `
105275
+ );
105105
105276
  return 1;
105106
105277
  }
105107
105278
  }
105108
105279
 
105109
105280
  // src/version.ts
105110
- var VERSION = true ? "0.8.84" : "0.0.0-dev";
105111
- var PRODUCER_VERSION = true ? "0.8.84" : "0.0.0-dev";
105281
+ var VERSION = true ? "0.8.86" : "0.0.0-dev";
105282
+ var PRODUCER_VERSION = true ? "0.8.86" : "0.0.0-dev";
105112
105283
 
105113
105284
  // src/fontLocalizeCli.ts
105114
105285
  async function readStdin() {