@hyperframes/core 0.8.205 → 0.8.207

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.
@@ -13,6 +13,8 @@ export declare function hoistFontFaceRules(styleTexts: string[]): {
13
13
  styles: string[];
14
14
  faces: string[];
15
15
  };
16
+ export declare const AUTHORED_SOURCE_BEGIN = "/*hf-authored-begin*/";
17
+ export declare const AUTHORED_SOURCE_END = "/*hf-authored-end*/";
16
18
  export declare function scopedModulePrelude(timelineCompositionId: string, compositionSrc?: string | null): string;
17
19
  export declare function wrapScopedCompositionScript(source: string, compositionId: string, errorLabel?: string, scopeSelectorOverride?: string, timelineCompositionId?: string, authoredRootId?: string | null, compositionSrc?: string | null): string;
18
20
  export declare function wrapInlineScriptWithErrorBoundary(source: string, errorLabel: string): string;
@@ -295,6 +295,8 @@ const SCOPED_HYPERFRAMES_EXPRESSION = `!__hfBaseHyperframes
295
295
  return scoped ? Object.assign({}, scoped) : {};
296
296
  },
297
297
  })`;
298
+ export const AUTHORED_SOURCE_BEGIN = "/*hf-authored-begin*/";
299
+ export const AUTHORED_SOURCE_END = "/*hf-authored-end*/";
298
300
  export function scopedModulePrelude(timelineCompositionId, compositionSrc) {
299
301
  return `const __hyperframes = (function(__hfBaseHyperframes, __hfTimelineCompId, __hfCompositionSrc) {
300
302
  return ${SCOPED_HYPERFRAMES_EXPRESSION};
@@ -310,6 +312,7 @@ export function wrapScopedCompositionScript(source, compositionId, errorLabel =
310
312
  const authoredRootIdLiteral = jsonScriptLiteral(authoredRootId?.trim() || null);
311
313
  const scopeSelectorLiteral = jsonScriptLiteral(scopeSelectorOverride ?? null);
312
314
  const rootSelectorPatternLiteral = jsonScriptLiteral(String.raw `\[\s*data-composition-id\s*=\s*(?:"${escapedCompositionId}"|'${escapedCompositionId}')\s*\]`);
315
+ const timelineRootSelectorPatternLiteral = jsonScriptLiteral(String.raw `\[\s*data-composition-id\s*=\s*(?:"${escapeRegExp(timelineCompositionId)}"|'${escapeRegExp(timelineCompositionId)}')\s*\]`);
313
316
  const timingSelectorPatternLiteral = jsonScriptLiteral(String.raw `\s*\[\s*data-(?:start|duration)\s*=\s*(?:"[^"]*"|'[^']*')\s*\]`);
314
317
  const aliasInstallerOutsideAuthoredStrictness = `(function(){ ${getSvgSelectorAliasesScript()} })();`;
315
318
  return `(function(){
@@ -328,6 +331,7 @@ export function wrapScopedCompositionScript(source, compositionId, errorLabel =
328
331
  : "");
329
332
  var __hfRoot = null;
330
333
  var __hfRootSelectorPattern = ${rootSelectorPatternLiteral};
334
+ var __hfTimelineRootSelectorPattern = ${timelineRootSelectorPatternLiteral};
331
335
  var __hfTimingSelectorPattern = ${timingSelectorPatternLiteral};
332
336
  var __hfAuthoredRootSelector = __hfAuthoredRootId
333
337
  ? "[" + __hfAuthoredRootAttr + '="' + __hfEscapeAttr(__hfAuthoredRootId) + '"]'
@@ -339,7 +343,11 @@ export function wrapScopedCompositionScript(source, compositionId, errorLabel =
339
343
  if (!__hfCompId || typeof selector !== "string") return selector;
340
344
  var normalized = selector
341
345
  .replace(new RegExp(__hfRootSelectorPattern + '(?:' + __hfTimingSelectorPattern + ')+', 'g'), __hfRootSelector)
342
- .replace(new RegExp('(?:' + __hfTimingSelectorPattern + ')+' + __hfRootSelectorPattern, 'g'), __hfRootSelector);
346
+ .replace(new RegExp('(?:' + __hfTimingSelectorPattern + ')+' + __hfRootSelectorPattern, 'g'), __hfRootSelector)
347
+ .replace(new RegExp(__hfRootSelectorPattern, 'g'), __hfRootSelector);
348
+ if (__hfTimelineCompId !== __hfCompId) {
349
+ normalized = normalized.replace(new RegExp(__hfTimelineRootSelectorPattern, 'g'), __hfRootSelector);
350
+ }
343
351
  return window.__hfSvgSelectorAliases.rewrite(normalized, __hfAuthoredRootEntries);
344
352
  };
345
353
  var __hfFindRoot = function() {
@@ -581,7 +589,9 @@ export function wrapScopedCompositionScript(source, compositionId, errorLabel =
581
589
  if (typeof report === "function") report("start", __hfCompositionSrc || "index.html");
582
590
  try {
583
591
  (function(document, gsap, window, __hyperframes) {
592
+ ${AUTHORED_SOURCE_BEGIN}
584
593
  ${source.replace(/<\/(script)/gi, "<\\/$1")}
594
+ ${AUTHORED_SOURCE_END}
585
595
  }).call(window, __hfScopedDocument, __hfScopedGsap, __hfScopedWindow, __hfScopedHyperframes);
586
596
  } catch (_err) {
587
597
  console.error(__hfErrorLabel, __hfCompId, _err);
@@ -2,6 +2,7 @@ import { createHash } from "node:crypto";
2
2
  import { compositionStyle, cssStyleMergeKey, deferScriptsUntilFonts, isAlwaysAppliedStyle, headStyleRuns, INLINED_FILE_ATTR, inlineScriptRuns, styleElementsFor, } from "./scriptRuns.js";
3
3
  import { countPageRoot, executableScripts, isJavaScriptType, planCompositionAssembly, } from "./compositionAssembly.js";
4
4
  import { SCENE_FONTS_ATTR, SCENE_PART_ATTR } from "../sceneParts.js";
5
+ import { stripSdrPngColourTags } from "./pngColourTags.js";
5
6
  import { ensureExternalScriptTag, readExternalScriptAttributes, } from "./externalScripts.js";
6
7
  import { emitMountedModuleScripts, parseImportMap } from "./importMaps.js";
7
8
  import { markFlattenedInnerRoot } from "../runtime/flattenedRoot.js";
@@ -313,7 +314,8 @@ function createAssetInliner(resolveProjectPath, inlineAssets) {
313
314
  const byteLength = safeStatSize(filePath);
314
315
  if (byteLength !== null && byteLength > MAX_INLINE_ASSET_BYTES)
315
316
  return { tooLarge: byteLength };
316
- const content = safeReadFileBuffer(filePath);
317
+ const read = safeReadFileBuffer(filePath);
318
+ const content = read && mimeType === "image/png" ? (stripSdrPngColourTags(read) ?? read) : read;
317
319
  return {
318
320
  dataUrl: content === null ? null : `data:${mimeType};base64,${content.toString("base64")}`,
319
321
  };
@@ -13,7 +13,7 @@ import { compositionStyle, cssStyleMergeKey, hasSameLink, } from "./scriptRuns.j
13
13
  */
14
14
  import { rewriteAssetPath, rewriteAssetPaths, rewriteCssAssetUrls, rewriteInlineStyleAssetUrls, } from "./rewriteSubCompPaths.js";
15
15
  import { warnUnknownEnumValues } from "../runtime/getVariables.js";
16
- import { createCompositionCssScoper, wrapInlineScriptWithErrorBoundary, scopedModulePrelude, wrapScopedCompositionScript, } from "./compositionScoping.js";
16
+ import { createCompositionCssScoper, AUTHORED_SOURCE_BEGIN, AUTHORED_SOURCE_END, wrapInlineScriptWithErrorBoundary, scopedModulePrelude, wrapScopedCompositionScript, } from "./compositionScoping.js";
17
17
  import { namespaceCollidingSvgIds, rewriteSvgIdReferencesInCss, } from "./svgIdNamespacing.js";
18
18
  import { checkSubCompositionUsability, resolveSubCompositionContent, } from "@hyperframes/parsers/sub-composition-validity";
19
19
  import { enumerateNestedCompositionHosts, planCompositionAssembly, extractedCompositionAssets, } from "./compositionAssembly.js";
@@ -419,7 +419,7 @@ export function inlineSubCompositions(document, hosts, options) {
419
419
  const prelude = scriptCompositionId
420
420
  ? scopedModulePrelude(runtimeCompId || scopeCompId || scriptCompositionId, src)
421
421
  : "";
422
- moduleScripts.push(prelude + (scriptEl.textContent || ""));
422
+ moduleScripts.push(`${prelude}${AUTHORED_SOURCE_BEGIN}\n${scriptEl.textContent || ""}\n${AUTHORED_SOURCE_END}`);
423
423
  }
424
424
  else if (externalSrc) {
425
425
  if (!externalScriptSrcs.includes(externalSrc)) {
@@ -0,0 +1,2 @@
1
+ /** An SDR PNG's copy without the tags that make Chrome re-map its pixels; null if none apply or it is HDR. */
2
+ export declare function stripSdrPngColourTags(png: Uint8Array): Buffer | null;
@@ -0,0 +1,27 @@
1
+ const PNG_SIGNATURE = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
2
+ const PNG_SIGNATURE_BYTES = PNG_SIGNATURE.length;
3
+ const CHUNK_OVERHEAD_BYTES = 12;
4
+ const REMOVED_CHUNKS = new Set(["cICP", "gAMA", "cHRM"]);
5
+ const HDR_CICP_TRANSFERS = new Set([16, 18]);
6
+ /** An SDR PNG's copy without the tags that make Chrome re-map its pixels; null if none apply or it is HDR. */
7
+ export function stripSdrPngColourTags(png) {
8
+ const bytes = Buffer.from(png.buffer, png.byteOffset, png.byteLength);
9
+ if (!bytes.subarray(0, PNG_SIGNATURE_BYTES).equals(PNG_SIGNATURE))
10
+ return null;
11
+ const kept = [bytes.subarray(0, PNG_SIGNATURE_BYTES)];
12
+ let removed = false;
13
+ for (let at = PNG_SIGNATURE_BYTES; at + CHUNK_OVERHEAD_BYTES <= bytes.length;) {
14
+ const end = at + CHUNK_OVERHEAD_BYTES + bytes.readUInt32BE(at);
15
+ if (end > bytes.length)
16
+ return null;
17
+ const type = bytes.toString("latin1", at + 4, at + 8);
18
+ if (type === "cICP" && HDR_CICP_TRANSFERS.has(bytes[at + 9] ?? 0))
19
+ return null;
20
+ if (REMOVED_CHUNKS.has(type))
21
+ removed = true;
22
+ else
23
+ kept.push(bytes.subarray(at, end));
24
+ at = end;
25
+ }
26
+ return removed ? Buffer.concat(kept) : null;
27
+ }
@@ -62,7 +62,7 @@ export declare function parseAttributeSelector(predicate: string): {
62
62
  name: string;
63
63
  rawName: string;
64
64
  namespace: AttributeNamespace;
65
- operator: "=" | "~=" | "|=" | "^=" | "$=" | "*=";
65
+ operator: "=" | "*=" | "|=" | "^=" | "~=" | "$=";
66
66
  value: string;
67
67
  flag: string | undefined;
68
68
  } | null;
@@ -1,70 +1,6 @@
1
1
  export type ExecutionMode = "planning" | "design" | "execution" | null;
2
- /**
3
- * Frame rate as an exact rational. Carrying `{num, den}` end-to-end (rather
4
- * than collapsing to `29.97`) lets us pass NTSC / drop-frame rates straight
5
- * through to FFmpeg via `-r 30000/1001` without any decimal round-trip.
6
- *
7
- * Integer fps is represented with `den: 1` (e.g. `{ num: 30, den: 1 }`).
8
- *
9
- * Use {@link fpsToNumber} when arithmetic forces a decimal (e.g. `setTimeout`
10
- * intervals) and {@link fpsToFfmpegArg} when emitting FFmpeg `-r` /
11
- * `-framerate` strings.
12
- */
13
- export interface Fps {
14
- num: number;
15
- den: number;
16
- }
17
- export type FpsInput = number | Fps;
18
- export declare function toFps(input: FpsInput): Fps;
19
- /**
20
- * Decimal value of an {@link Fps} rational. Used at sites that need a
21
- * `number` for arithmetic (frame-index → time, frame intervals, telemetry
22
- * payloads) where the small precision loss of the decimal is acceptable.
23
- */
24
- export declare function fpsToNumber(fps: Fps): number;
25
- /**
26
- * FFmpeg-style fps argument. Returns `"30"` for integer fps and `"30000/1001"`
27
- * for rationals — both forms are accepted verbatim by FFmpeg's `-r` and
28
- * `-framerate` flags. We keep integer fps as a bare integer so existing
29
- * snapshot tests / log output don't churn for the common case.
30
- */
31
- export declare function fpsToFfmpegArg(fps: Fps): string;
32
- /**
33
- * Discriminated parse result for {@link parseFps}. Lets the CLI / route
34
- * validation own its own error UX without losing the structured failure
35
- * reason.
36
- */
37
- export type FpsParseResult = {
38
- ok: true;
39
- value: Fps;
40
- } | {
41
- ok: false;
42
- reason: "empty" | "not-a-number" | "non-positive" | "out-of-range" | "invalid-fraction" | "ambiguous-decimal";
43
- };
44
- /**
45
- * Parse a user-supplied fps spec into an {@link Fps} rational.
46
- *
47
- * Accepted forms:
48
- * - integer string `"30"` → `{ num: 30, den: 1 }`
49
- * - integer number `30` → `{ num: 30, den: 1 }`
50
- * - rational string `"30000/1001"` → `{ num: 30000, den: 1001 }` (exact NTSC)
51
- *
52
- * Rejected:
53
- * - empty / non-numeric input
54
- * - decimals like `"29.97"` — callers must spell rationals with `/` so the
55
- * exact denominator is unambiguous (FFmpeg treats `29.97` as a slightly
56
- * different framerate than `30000/1001`).
57
- * - division by zero, negative or zero numerator
58
- * - decimal value outside `[1, 240]` — defensive bounds for "human" fps
59
- * ranges (24, 25, 30, 50, 60, 120, 240, plus the NTSC trio).
60
- */
61
- export declare function parseFps(input: string | number): FpsParseResult;
62
- /**
63
- * Convenience wrapper around {@link parseFps} for callsites that want the
64
- * default-30-fps fallback when input is `undefined`. Does NOT swallow parse
65
- * errors — those still surface via the discriminated result.
66
- */
67
- export declare function parseFpsWithDefault(input: string | number | undefined): FpsParseResult;
2
+ export type { Fps, FpsInput, FpsParseResult } from "./fps.js";
3
+ export { toFps, fpsToNumber, fpsToFfmpegArg, parseFps, parseFpsWithDefault } from "./fps.js";
68
4
  /** Video orientation / aspect ratio. */
69
5
  export type Orientation = "16:9" | "9:16";
70
6
  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";
@@ -1,104 +1,3 @@
1
1
  // ── Shared cross-package types ──────────────────────────────────────────────
2
- export function toFps(input) {
3
- if (typeof input === "number") {
4
- return { num: input, den: 1 };
5
- }
6
- return input;
7
- }
8
- /**
9
- * Decimal value of an {@link Fps} rational. Used at sites that need a
10
- * `number` for arithmetic (frame-index → time, frame intervals, telemetry
11
- * payloads) where the small precision loss of the decimal is acceptable.
12
- */
13
- export function fpsToNumber(fps) {
14
- return fps.num / fps.den;
15
- }
16
- /**
17
- * FFmpeg-style fps argument. Returns `"30"` for integer fps and `"30000/1001"`
18
- * for rationals — both forms are accepted verbatim by FFmpeg's `-r` and
19
- * `-framerate` flags. We keep integer fps as a bare integer so existing
20
- * snapshot tests / log output don't churn for the common case.
21
- */
22
- export function fpsToFfmpegArg(fps) {
23
- return fps.den === 1 ? String(fps.num) : `${fps.num}/${fps.den}`;
24
- }
25
- /**
26
- * Parse a user-supplied fps spec into an {@link Fps} rational.
27
- *
28
- * Accepted forms:
29
- * - integer string `"30"` → `{ num: 30, den: 1 }`
30
- * - integer number `30` → `{ num: 30, den: 1 }`
31
- * - rational string `"30000/1001"` → `{ num: 30000, den: 1001 }` (exact NTSC)
32
- *
33
- * Rejected:
34
- * - empty / non-numeric input
35
- * - decimals like `"29.97"` — callers must spell rationals with `/` so the
36
- * exact denominator is unambiguous (FFmpeg treats `29.97` as a slightly
37
- * different framerate than `30000/1001`).
38
- * - division by zero, negative or zero numerator
39
- * - decimal value outside `[1, 240]` — defensive bounds for "human" fps
40
- * ranges (24, 25, 30, 50, 60, 120, 240, plus the NTSC trio).
41
- */
42
- export function parseFps(input) {
43
- if (typeof input === "number") {
44
- if (!Number.isFinite(input))
45
- return { ok: false, reason: "not-a-number" };
46
- if (!Number.isInteger(input))
47
- return { ok: false, reason: "ambiguous-decimal" };
48
- if (input <= 0)
49
- return { ok: false, reason: "non-positive" };
50
- if (input > 240)
51
- return { ok: false, reason: "out-of-range" };
52
- return { ok: true, value: { num: input, den: 1 } };
53
- }
54
- const raw = input.trim();
55
- if (raw === "")
56
- return { ok: false, reason: "empty" };
57
- if (raw.includes("/")) {
58
- const parts = raw.split("/");
59
- if (parts.length !== 2)
60
- return { ok: false, reason: "invalid-fraction" };
61
- const num = Number(parts[0]);
62
- const den = Number(parts[1]);
63
- if (!Number.isFinite(num) || !Number.isFinite(den)) {
64
- return { ok: false, reason: "not-a-number" };
65
- }
66
- if (!Number.isInteger(num) || !Number.isInteger(den)) {
67
- return { ok: false, reason: "invalid-fraction" };
68
- }
69
- if (den <= 0)
70
- return { ok: false, reason: "invalid-fraction" };
71
- if (num <= 0)
72
- return { ok: false, reason: "non-positive" };
73
- const decimal = num / den;
74
- if (decimal < 1 || decimal > 240)
75
- return { ok: false, reason: "out-of-range" };
76
- return { ok: true, value: { num, den } };
77
- }
78
- // Integer-only path — reject `"29.97"` so users are explicit about the
79
- // exact rational they want.
80
- if (!/^-?\d+$/.test(raw)) {
81
- // Allow caller to differentiate "29.97" from "abc" if they want; both
82
- // are user errors but the message can be friendlier for decimals.
83
- if (/^-?\d*\.\d+$/.test(raw))
84
- return { ok: false, reason: "ambiguous-decimal" };
85
- return { ok: false, reason: "not-a-number" };
86
- }
87
- const n = Number(raw);
88
- if (n <= 0)
89
- return { ok: false, reason: "non-positive" };
90
- if (n > 240)
91
- return { ok: false, reason: "out-of-range" };
92
- return { ok: true, value: { num: n, den: 1 } };
93
- }
94
- /**
95
- * Convenience wrapper around {@link parseFps} for callsites that want the
96
- * default-30-fps fallback when input is `undefined`. Does NOT swallow parse
97
- * errors — those still surface via the discriminated result.
98
- */
99
- export function parseFpsWithDefault(input) {
100
- if (input === undefined || input === "")
101
- return { ok: true, value: { num: 30, den: 1 } };
102
- return parseFps(input);
103
- }
2
+ export { toFps, fpsToNumber, fpsToFfmpegArg, parseFps, parseFpsWithDefault } from "./fps.js";
104
3
  export { CANVAS_DIMENSIONS, VALID_CANVAS_RESOLUTIONS, normalizeResolutionFlag, isAspectAgnosticResolutionAlias, resolveResolutionFlagPair, checkOutputResolutionCompatibility, outputResolutionForSize, suggestMatchingPreset, COMPOSITION_VARIABLE_TYPES, TIMELINE_COLORS, DEFAULT_DURATIONS, getDefaultStageZoom, isTextElement, isMediaElement, isCompositionElement, } from "@hyperframes/parsers";
package/dist/fps.d.ts ADDED
@@ -0,0 +1,17 @@
1
+ export interface Fps {
2
+ num: number;
3
+ den: number;
4
+ }
5
+ export type FpsInput = number | Fps;
6
+ export declare function toFps(input: FpsInput): Fps;
7
+ export declare function fpsToNumber(fps: Fps): number;
8
+ export declare function fpsToFfmpegArg(fps: Fps): string;
9
+ export type FpsParseResult = {
10
+ ok: true;
11
+ value: Fps;
12
+ } | {
13
+ ok: false;
14
+ reason: "empty" | "not-a-number" | "non-positive" | "out-of-range" | "invalid-fraction" | "ambiguous-decimal";
15
+ };
16
+ export declare function parseFps(input: string | number): FpsParseResult;
17
+ export declare function parseFpsWithDefault(input: string | number | undefined): FpsParseResult;
package/dist/fps.js ADDED
@@ -0,0 +1,58 @@
1
+ export function toFps(input) {
2
+ if (typeof input === "number") {
3
+ return { num: input, den: 1 };
4
+ }
5
+ return input;
6
+ }
7
+ export function fpsToNumber(fps) {
8
+ return fps.num / fps.den;
9
+ }
10
+ export function fpsToFfmpegArg(fps) {
11
+ return fps.den === 1 ? String(fps.num) : `${fps.num}/${fps.den}`;
12
+ }
13
+ const fail = (reason) => ({ ok: false, reason });
14
+ function rangedFps(num, den) {
15
+ if (num <= 0)
16
+ return fail("non-positive");
17
+ const decimal = num / den;
18
+ if (decimal < 1 || decimal > 240)
19
+ return fail("out-of-range");
20
+ return { ok: true, value: { num, den } };
21
+ }
22
+ function numericFps(input) {
23
+ if (!Number.isFinite(input))
24
+ return fail("not-a-number");
25
+ if (!Number.isInteger(input))
26
+ return fail("ambiguous-decimal");
27
+ return rangedFps(input, 1);
28
+ }
29
+ function fractionFps(raw) {
30
+ const parts = raw.split("/");
31
+ if (parts.length !== 2)
32
+ return fail("invalid-fraction");
33
+ const num = Number(parts[0]);
34
+ const den = Number(parts[1]);
35
+ if (!Number.isFinite(num) || !Number.isFinite(den))
36
+ return fail("not-a-number");
37
+ if (!Number.isInteger(num) || !Number.isInteger(den) || den <= 0)
38
+ return fail("invalid-fraction");
39
+ return rangedFps(num, den);
40
+ }
41
+ // Decimals like "29.97" are rejected: FFmpeg reads them as a different rate than 30000/1001.
42
+ export function parseFps(input) {
43
+ if (typeof input === "number")
44
+ return numericFps(input);
45
+ const raw = input.trim();
46
+ if (raw === "")
47
+ return fail("empty");
48
+ if (raw.includes("/"))
49
+ return fractionFps(raw);
50
+ if (/^-?\d+$/.test(raw))
51
+ return rangedFps(Number(raw), 1);
52
+ return fail(/^-?\d*\.\d+$/.test(raw) ? "ambiguous-decimal" : "not-a-number");
53
+ }
54
+ export function parseFpsWithDefault(input) {
55
+ if (input === undefined || input === "")
56
+ return { ok: true, value: { num: 30, den: 1 } };
57
+ return parseFps(input);
58
+ }