@hyperframes/core 0.8.168 → 0.8.170

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.
@@ -4,7 +4,11 @@ export declare function scopeCssToComposition(css: string, compositionId: string
4
4
  }): string;
5
5
  /** Drops repeats of an identical `@font-face` across the given style texts, keeping the last copy:
6
6
  * the last matching rule is the one the browser uses, so a rule in between never gains precedence. */
7
- export declare function dedupeFontFaceRules(styleTexts: string[]): string[];
7
+ export declare const dedupeFontFaceRules: (styleTexts: string[]) => string[];
8
+ export declare function hoistFontFaceRules(styleTexts: string[]): {
9
+ styles: string[];
10
+ faces: string[];
11
+ };
8
12
  export declare function scopedModulePrelude(timelineCompositionId: string, compositionSrc?: string | null): string;
9
13
  export declare function wrapScopedCompositionScript(source: string, compositionId: string, errorLabel?: string, scopeSelectorOverride?: string, timelineCompositionId?: string, authoredRootId?: string | null, compositionSrc?: string | null): string;
10
14
  export declare function wrapInlineScriptWithErrorBoundary(source: string, errorLabel: string): string;
@@ -162,7 +162,12 @@ function fontFaceKey(atRule) {
162
162
  }
163
163
  /** Drops repeats of an identical `@font-face` across the given style texts, keeping the last copy:
164
164
  * the last matching rule is the one the browser uses, so a rule in between never gains precedence. */
165
- export function dedupeFontFaceRules(styleTexts) {
165
+ export const dedupeFontFaceRules = (styleTexts) => dropFontFaces(styleTexts);
166
+ export function hoistFontFaceRules(styleTexts) {
167
+ const faces = [];
168
+ return { styles: dropFontFaces(styleTexts, faces), faces };
169
+ }
170
+ function dropFontFaces(styleTexts, hoisted) {
166
171
  const seen = new Set();
167
172
  return [...styleTexts]
168
173
  .reverse()
@@ -181,13 +186,14 @@ export function dedupeFontFaceRules(styleTexts) {
181
186
  if (!isFontFaceAtRule(node))
182
187
  continue;
183
188
  const key = fontFaceKey(node);
184
- if (seen.has(key)) {
185
- node.remove();
186
- changed = true;
187
- }
188
- else {
189
+ if (!seen.has(key)) {
189
190
  seen.add(key);
191
+ if (!hoisted)
192
+ continue;
193
+ hoisted.unshift(node.toString());
190
194
  }
195
+ node.remove();
196
+ changed = true;
191
197
  }
192
198
  return changed ? root.toResult({ map: false }).css : css;
193
199
  })
@@ -1,7 +1,7 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { compositionStyle, cssStyleMergeKey, deferScriptsUntilFonts, UNCONDITIONAL_CSS_KEY, headStyleRuns, INLINED_FILE_ATTR, inlineScriptRuns, styleElementsFor, } from "./scriptRuns.js";
3
3
  import { countPageRoot, executableScripts, isJavaScriptType, planCompositionAssembly, } from "./compositionAssembly.js";
4
- import { SCENE_PART_ATTR } from "../sceneParts.js";
4
+ import { SCENE_FONTS_ATTR, SCENE_PART_ATTR } from "../sceneParts.js";
5
5
  import { ensureExternalScriptTag, readExternalScriptAttributes, } from "./externalScripts.js";
6
6
  import { emitMountedModuleScripts, parseImportMap } from "./importMaps.js";
7
7
  import { markFlattenedInnerRoot } from "../runtime/flattenedRoot.js";
@@ -20,7 +20,7 @@ import { transform } from "esbuild";
20
20
  import { compileHtml } from "./htmlCompiler.js";
21
21
  import { RUNTIME_BOOTSTRAP_ATTR, escapeInlineScriptSource, insertRuntimeTag, parseHTMLContent, stripEmbeddedRuntimeScripts, } from "./htmlDocument.js";
22
22
  // rewriteSubCompPaths functions are used by inlineSubCompositions (shared module)
23
- import { buildVariablesByCompScript, dedupeFontFaceRules, scopeCssToComposition, wrapInlineScriptWithErrorBoundary, scopedModulePrelude, wrapScopedCompositionScript, } from "./compositionScoping.js";
23
+ import { buildVariablesByCompScript, dedupeFontFaceRules, hoistFontFaceRules, scopeCssToComposition, wrapInlineScriptWithErrorBoundary, scopedModulePrelude, wrapScopedCompositionScript, } from "./compositionScoping.js";
24
24
  import { validateHyperframeHtmlContract } from "./staticGuard.js";
25
25
  import { getHyperframeRuntimeScript } from "../generated/runtime-inline.js";
26
26
  import { readDeclaredDefaults } from "../runtime/getVariables.js";
@@ -1171,13 +1171,20 @@ async function bundleProject(projectDir, options) {
1171
1171
  if (srcset)
1172
1172
  el.setAttribute("srcset", rewriteSrcsetWithInlinedAssets(srcset, projectDir, inlineAssets));
1173
1173
  }
1174
- // Before inlining, so postcss reads paths not font bytes; scene parts keep copies to swap alone.
1175
- if (!options?.sceneParts) {
1176
- const liveStyles = [...document.querySelectorAll("style")].filter(isAlwaysAppliedStyle);
1177
- const dedupedStyles = dedupeFontFaceRules(liveStyles.map((el) => el.textContent || ""));
1178
- liveStyles.forEach((el, i) => {
1179
- el.textContent = dedupedStyles[i] ?? "";
1180
- });
1174
+ // Before inlining, so postcss reads paths not font bytes.
1175
+ const liveStyles = [...document.querySelectorAll("style")].filter(isAlwaysAppliedStyle);
1176
+ const liveCss = liveStyles.map((el) => el.textContent || "");
1177
+ const { styles, faces } = options?.sceneParts
1178
+ ? hoistFontFaceRules(liveCss)
1179
+ : { styles: dedupeFontFaceRules(liveCss), faces: [] };
1180
+ liveStyles.forEach((el, i) => {
1181
+ el.textContent = styles[i] ?? "";
1182
+ });
1183
+ if (faces.length > 0) {
1184
+ const holder = document.createElement("style");
1185
+ holder.setAttribute(SCENE_FONTS_ATTR, "");
1186
+ holder.textContent = faces.join("\n");
1187
+ document.head.prepend(holder);
1181
1188
  }
1182
1189
  for (const styleEl of document.querySelectorAll("style")) {
1183
1190
  styleEl.textContent = rewriteCssUrlsWithInlinedAssets(styleEl.textContent || "", projectDir, inlineAssets);
@@ -1,5 +1,5 @@
1
1
  import { createHash } from "node:crypto";
2
- import { SCENE_PART_ATTR, SCENE_PARTS_META } from "../sceneParts.js";
2
+ import { SCENE_FONTS_ATTR, SCENE_PART_ATTR, SCENE_PARTS_META, } from "../sceneParts.js";
3
3
  import { injectTagsAtHeadStart, parseHTMLContent } from "./htmlDocument.js";
4
4
  const hash = (text) => createHash("sha1").update(text).digest("hex").slice(0, 16);
5
5
  /**
@@ -19,7 +19,9 @@ export function addScenePartsManifest(html, ignore = []) {
19
19
  // A marker keeps each part's place in `shared`, so moving or reordering a scene changes it.
20
20
  for (const el of parts)
21
21
  el.replaceWith(doc.createComment(`hf-scene:${el.getAttribute(SCENE_PART_ATTR)}`));
22
- for (const el of ignore.flatMap((sel) => [...doc.querySelectorAll(sel)]))
22
+ for (const el of [...ignore, `style[${SCENE_FONTS_ATTR}]`].flatMap((sel) => [
23
+ ...doc.querySelectorAll(sel),
24
+ ]))
23
25
  el.remove();
24
26
  const manifest = {
25
27
  shared: hash(doc.toString()),
@@ -65,10 +65,6 @@ export declare function parseFps(input: string | number): FpsParseResult;
65
65
  * errors — those still surface via the discriminated result.
66
66
  */
67
67
  export declare function parseFpsWithDefault(input: string | number | undefined): FpsParseResult;
68
- /** Integer rates the distributed renderer (Lambda, Cloud Run) accepts; fractional fps renders in-process only. */
69
- export declare const DISTRIBUTED_FPS: readonly [24, 25, 30, 60];
70
- export type DistributedFps = (typeof DISTRIBUTED_FPS)[number];
71
- export declare function isDistributedFps(value: unknown): value is DistributedFps;
72
68
  /** Video orientation / aspect ratio. */
73
69
  export type Orientation = "16:9" | "9:16";
74
70
  export type { Asset, TimelineElementType, MediaElementType, TimelineElementBase, TimelineMediaElement, WaveformData, TimelineTextElement, TimelineCompositionElement, CompositionVariableType, CompositionVariableBase, StringVariable, NumberVariable, ColorVariable, BooleanVariable, EnumVariable, CompositionVariable, CompositionSpec, TimelineElement, MediaFile, CanvasResolution, CompositionAPI, PlayerAPI, AddElementData, ValidationResult, CompositionAsset, Keyframe, KeyframeProperties, ElementKeyframes, StageZoom, StageZoomKeyframe, } from "@hyperframes/parsers";
@@ -101,9 +101,4 @@ export function parseFpsWithDefault(input) {
101
101
  return { ok: true, value: { num: 30, den: 1 } };
102
102
  return parseFps(input);
103
103
  }
104
- /** Integer rates the distributed renderer (Lambda, Cloud Run) accepts; fractional fps renders in-process only. */
105
- export const DISTRIBUTED_FPS = [24, 25, 30, 60];
106
- export function isDistributedFps(value) {
107
- return DISTRIBUTED_FPS.includes(value);
108
- }
109
104
  export { CANVAS_DIMENSIONS, VALID_CANVAS_RESOLUTIONS, normalizeResolutionFlag, isAspectAgnosticResolutionAlias, resolveResolutionFlagPair, checkOutputResolutionCompatibility, suggestMatchingPreset, COMPOSITION_VARIABLE_TYPES, TIMELINE_COLORS, DEFAULT_DURATIONS, getDefaultStageZoom, isTextElement, isMediaElement, isCompositionElement, } from "@hyperframes/parsers";
@@ -0,0 +1,4 @@
1
+ /** Integer rates the distributed renderer (Lambda, Cloud Run) accepts; fractional fps renders in-process only. */
2
+ export declare const DISTRIBUTED_FPS: readonly [24, 25, 30, 60];
3
+ export type DistributedFps = (typeof DISTRIBUTED_FPS)[number];
4
+ export declare function isDistributedFps(value: unknown): value is DistributedFps;
@@ -0,0 +1,5 @@
1
+ /** Integer rates the distributed renderer (Lambda, Cloud Run) accepts; fractional fps renders in-process only. */
2
+ export const DISTRIBUTED_FPS = [24, 25, 30, 60];
3
+ export function isDistributedFps(value) {
4
+ return DISTRIBUTED_FPS.includes(value);
5
+ }
@@ -20,6 +20,8 @@ function googleStylesheetFamilies(urlText) {
20
20
  .filter(Boolean);
21
21
  }
22
22
  function importUrls(css) {
23
+ if (!/@import/i.test(css))
24
+ return [];
23
25
  let root;
24
26
  try {
25
27
  root = postcss.parse(css);
@@ -202,6 +202,8 @@ export function normalizeSystemFontPrimaryFamilies(html, deterministicPrimary =
202
202
  return changed ? document.toString() : html;
203
203
  }
204
204
  function collectCssCustomProperties(css, customProperties) {
205
+ if (!css.includes("--"))
206
+ return;
205
207
  const root = parseCssRoot(css);
206
208
  if (!root)
207
209
  return;
@@ -228,6 +230,8 @@ function* iterateCssRootFontFamilyDeclarations(root) {
228
230
  yield* declarations;
229
231
  }
230
232
  function* iterateCssFontFamilyDeclarations(css) {
233
+ if (!/font-family/i.test(css))
234
+ return;
231
235
  const root = parseCssRoot(css);
232
236
  if (!root)
233
237
  return;
@@ -24,5 +24,4 @@ export interface LocatedFontVariant extends LocatedFont {
24
24
  style: "normal" | "italic";
25
25
  }
26
26
  export declare function locateSystemFontVariants(family: string): LocatedFontVariant[];
27
- export declare function getSystemProfilerFamilies(): string[];
28
27
  export declare function clearSystemFontCache(): void;
@@ -3,7 +3,6 @@ import { existsSync, lstatSync, readdirSync, realpathSync } from "node:fs";
3
3
  import { homedir, platform } from "node:os";
4
4
  import { join, resolve } from "node:path";
5
5
  export const SYSTEM_FONT_SIZE_LIMIT = 5 * 1024 * 1024;
6
- const PROFILER_TIMEOUT_MS = 5000;
7
6
  const FC_MATCH_TIMEOUT_MS = 3000;
8
7
  export const FONT_EXT_RE = /\.(otf|ttf|ttc|woff2?)$/i;
9
8
  const FORMAT_PRIORITY = {
@@ -193,64 +192,6 @@ function pickBestCandidate(candidates) {
193
192
  const best = candidates[0];
194
193
  return { path: best.path, format: best.format };
195
194
  }
196
- let profilerCache = null;
197
- // fallow-ignore-next-line complexity
198
- function getSystemProfilerIndex() {
199
- if (profilerCache)
200
- return profilerCache;
201
- profilerCache = new Map();
202
- if (platform() !== "darwin")
203
- return profilerCache;
204
- try {
205
- const raw = execFileSync("system_profiler", ["SPFontsDataType", "-json"], {
206
- encoding: "utf8",
207
- maxBuffer: 12 * 1024 * 1024,
208
- timeout: PROFILER_TIMEOUT_MS,
209
- });
210
- const parsed = JSON.parse(raw);
211
- if (!parsed?.SPFontsDataType || !Array.isArray(parsed.SPFontsDataType))
212
- return profilerCache;
213
- for (const fontEntry of parsed.SPFontsDataType) {
214
- if (!fontEntry?.typefaces || !Array.isArray(fontEntry.typefaces))
215
- continue;
216
- for (const typeface of fontEntry.typefaces) {
217
- if (!typeface)
218
- continue;
219
- const family = typeface.family ?? typeface.fullname ?? typeface._name;
220
- if (typeof family !== "string")
221
- continue;
222
- const filePath = typeface.path;
223
- if (typeof filePath !== "string" || !FONT_EXT_RE.test(filePath))
224
- continue;
225
- const normalized = normalizeName(family);
226
- const ext = filePath.match(FONT_EXT_RE)?.[1] ?? "ttf";
227
- const entry = {
228
- family: normalized,
229
- path: filePath,
230
- format: extensionToFormat(ext),
231
- isRegular: isRegularWeight(filePath),
232
- };
233
- const list = profilerCache.get(normalized) ?? [];
234
- list.push(entry);
235
- profilerCache.set(normalized, list);
236
- }
237
- }
238
- }
239
- catch {
240
- // system_profiler unavailable
241
- }
242
- return profilerCache;
243
- }
244
- function locateViaSystemProfiler(targetFamily) {
245
- const index = getSystemProfilerIndex();
246
- const entries = index.get(targetFamily);
247
- if (!entries || entries.length === 0)
248
- return null;
249
- const candidates = entries
250
- .filter((e) => isRegularFile(e.path) && isPathBounded(e.path))
251
- .map((e) => ({ path: e.path, format: e.format, isRegular: e.isRegular }));
252
- return pickBestCandidate(candidates);
253
- }
254
195
  // fallow-ignore-next-line complexity
255
196
  function locateViaFcMatch(targetFamily) {
256
197
  if (platform() !== "linux")
@@ -280,11 +221,7 @@ export function locateSystemFont(family) {
280
221
  const cached = cache.get(normalized);
281
222
  if (cached !== undefined)
282
223
  return cached;
283
- let result = null;
284
- result = locateViaSystemProfiler(normalized);
285
- if (!result) {
286
- result = locateViaFcMatch(normalized);
287
- }
224
+ let result = locateViaFcMatch(normalized);
288
225
  if (!result) {
289
226
  const allCandidates = [];
290
227
  for (const dir of fontDirectories()) {
@@ -330,18 +267,6 @@ export function locateSystemFontVariants(family) {
330
267
  if (!normalized)
331
268
  return [];
332
269
  const variants = [];
333
- const profilerIndex = getSystemProfilerIndex();
334
- const profilerEntries = profilerIndex.get(normalized);
335
- if (profilerEntries && profilerEntries.length > 0) {
336
- for (const e of profilerEntries) {
337
- if (!isRegularFile(e.path) || !isPathBounded(e.path))
338
- continue;
339
- const { weight, style } = inferWeightAndStyle(e.path);
340
- variants.push({ path: e.path, format: e.format, weight, style });
341
- }
342
- if (variants.length > 0)
343
- return dedupeVariants(variants);
344
- }
345
270
  const allCandidates = [];
346
271
  for (const dir of fontDirectories()) {
347
272
  allCandidates.push(...collectCandidatesFromDir(dir, normalized));
@@ -361,12 +286,7 @@ function dedupeVariants(variants) {
361
286
  }
362
287
  return Array.from(seen.values());
363
288
  }
364
- export function getSystemProfilerFamilies() {
365
- const index = getSystemProfilerIndex();
366
- return Array.from(index.keys());
367
- }
368
289
  export function clearSystemFontCache() {
369
290
  cache.clear();
370
- profilerCache = null;
371
291
  allowedDirsCache = null;
372
292
  }