freshcoat 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.
Files changed (62) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +17 -0
  3. package/README.md +110 -0
  4. package/package.json +53 -0
  5. package/src/adjust.d.ts +18 -0
  6. package/src/adjust.js +153 -0
  7. package/src/approx-layout.d.ts +2 -0
  8. package/src/approx-layout.js +91 -0
  9. package/src/bake-text.d.ts +12 -0
  10. package/src/bake-text.js +321 -0
  11. package/src/browser.d.ts +7 -0
  12. package/src/browser.js +79 -0
  13. package/src/canvaskit.d.ts +4 -0
  14. package/src/canvaskit.js +1674 -0
  15. package/src/compile-scene.d.ts +24 -0
  16. package/src/compile-scene.js +325 -0
  17. package/src/css.d.ts +5 -0
  18. package/src/css.js +153 -0
  19. package/src/decode.d.ts +22 -0
  20. package/src/decode.js +112 -0
  21. package/src/export-scale.d.ts +25 -0
  22. package/src/export-scale.js +74 -0
  23. package/src/font-bytes.d.ts +4 -0
  24. package/src/font-bytes.js +165 -0
  25. package/src/font-metrics.d.ts +5 -0
  26. package/src/font-metrics.js +62 -0
  27. package/src/headless.d.ts +24 -0
  28. package/src/headless.js +58 -0
  29. package/src/index.d.ts +25 -0
  30. package/src/index.js +29 -0
  31. package/src/jpeg.d.ts +5 -0
  32. package/src/jpeg.js +20 -0
  33. package/src/line-height.d.ts +7 -0
  34. package/src/line-height.js +80 -0
  35. package/src/node.d.ts +139 -0
  36. package/src/node.js +47 -0
  37. package/src/paint-cache.d.ts +49 -0
  38. package/src/paint-cache.js +109 -0
  39. package/src/paint-helpers.d.ts +21 -0
  40. package/src/paint-helpers.js +71 -0
  41. package/src/paragraph-layout.d.ts +6 -0
  42. package/src/paragraph-layout.js +283 -0
  43. package/src/path-data.d.ts +5 -0
  44. package/src/path-data.js +101 -0
  45. package/src/png.d.ts +23 -0
  46. package/src/png.js +189 -0
  47. package/src/resolve-layout.d.ts +9 -0
  48. package/src/resolve-layout.js +657 -0
  49. package/src/runtime.d.ts +6 -0
  50. package/src/runtime.js +44 -0
  51. package/src/squircle.d.ts +1 -0
  52. package/src/squircle.js +60 -0
  53. package/src/text-cache.d.ts +14 -0
  54. package/src/text-cache.js +46 -0
  55. package/src/text-engine.d.ts +26 -0
  56. package/src/text-engine.js +1 -0
  57. package/src/text-types.d.ts +30 -0
  58. package/src/text-types.js +1 -0
  59. package/src/types.d.ts +406 -0
  60. package/src/types.js +13 -0
  61. package/src/validate-commands.d.ts +8 -0
  62. package/src/validate-commands.js +206 -0
package/src/node.d.ts ADDED
@@ -0,0 +1,139 @@
1
+ import type { Adjust, BitmapRole, BakedTextLayout, BlendMode, CornerRadius, FillRule, ResolvedFill, ResolvedFont, Shadows, ShapeMask, Size, Stroke, Vec2, ViewBox } from "./types.js";
2
+ export type Padding = number | [top: number, right: number, bottom: number, left: number];
3
+ export type Align = "start" | "center" | "end" | "stretch";
4
+ export type Justify = "start" | "center" | "end" | "space-between" | "space-around" | "space-evenly";
5
+ export type TrackSize = number | "auto" | `${number}fr`;
6
+ export type FlexLayout = {
7
+ type: "flex";
8
+ direction: "row" | "column";
9
+ gap?: number;
10
+ crossGap?: number;
11
+ padding?: Padding;
12
+ align?: Align;
13
+ justify?: Justify;
14
+ wrap?: boolean;
15
+ };
16
+ export type GridLayout = {
17
+ type: "grid";
18
+ columns: TrackSize[];
19
+ rows?: TrackSize[];
20
+ gap?: number | [number, number];
21
+ padding?: Padding;
22
+ };
23
+ export type Layout = FlexLayout | GridLayout;
24
+ export type ChildLayout = {
25
+ width?: "fill" | "hug" | number;
26
+ height?: "fill" | "hug" | number;
27
+ grow?: number;
28
+ alignSelf?: Align;
29
+ absolute?: boolean;
30
+ min?: {
31
+ width?: number;
32
+ height?: number;
33
+ };
34
+ max?: {
35
+ width?: number;
36
+ height?: number;
37
+ };
38
+ column?: number | [start: number, end: number];
39
+ row?: number | [start: number, end: number];
40
+ };
41
+ export type Transform = {
42
+ id?: string;
43
+ pos?: Vec2;
44
+ size?: Size;
45
+ rotation?: number;
46
+ opacity?: number;
47
+ blendMode?: BlendMode;
48
+ shadow?: Shadows;
49
+ blur?: number;
50
+ adjust?: Adjust;
51
+ layoutChild?: ChildLayout;
52
+ };
53
+ export type RectNode = Transform & {
54
+ kind: "rect";
55
+ fills?: ResolvedFill[];
56
+ stroke?: Stroke;
57
+ cornerRadius?: CornerRadius;
58
+ cornerSmoothing?: number;
59
+ };
60
+ export type EllipseNode = Transform & {
61
+ kind: "ellipse";
62
+ fills?: ResolvedFill[];
63
+ stroke?: Stroke;
64
+ };
65
+ export type PathNode = Transform & {
66
+ kind: "path";
67
+ d: string;
68
+ fills?: ResolvedFill[];
69
+ stroke?: Stroke;
70
+ viewBox?: ViewBox;
71
+ fillRule?: FillRule;
72
+ };
73
+ export type ImageNode = Transform & {
74
+ kind: "image";
75
+ src: string;
76
+ fit: "cover" | "contain" | "fill" | "tile";
77
+ stroke?: Stroke;
78
+ mask?: ShapeMask;
79
+ };
80
+ export type BitmapNode = Transform & {
81
+ kind: "bitmap";
82
+ pixels: Uint8Array;
83
+ pixelWidth: number;
84
+ pixelHeight: number;
85
+ role?: BitmapRole;
86
+ };
87
+ export type TextNode = Transform & {
88
+ kind: "text";
89
+ text?: string;
90
+ spans?: {
91
+ text: string;
92
+ font?: Partial<ResolvedFont>;
93
+ color?: string;
94
+ }[];
95
+ font: ResolvedFont;
96
+ color?: string;
97
+ fill?: ResolvedFill;
98
+ align?: "left" | "center" | "right";
99
+ verticalAlign?: "top" | "middle" | "bottom";
100
+ fit?: "shrink" | "clip";
101
+ maxLines?: number;
102
+ leadingTrim?: boolean;
103
+ layout?: BakedTextLayout;
104
+ };
105
+ export type GroupNode = Transform & {
106
+ kind: "group";
107
+ children: Node[];
108
+ layout?: Layout;
109
+ fills?: ResolvedFill[];
110
+ clip?: boolean;
111
+ cornerRadius?: CornerRadius;
112
+ cornerSmoothing?: number;
113
+ };
114
+ export type MaskNode = Transform & {
115
+ kind: "mask";
116
+ mask: Node;
117
+ children: Node[];
118
+ channel?: "alpha" | "luminance";
119
+ invert?: boolean;
120
+ };
121
+ export type Node = RectNode | EllipseNode | PathNode | ImageNode | BitmapNode | TextNode | GroupNode | MaskNode;
122
+ export declare const createRect: (p: Omit<RectNode, "kind">) => RectNode;
123
+ export declare const createEllipse: (p: Omit<EllipseNode, "kind">) => EllipseNode;
124
+ export declare const createCircle: (p: Omit<EllipseNode, "kind">) => EllipseNode;
125
+ export declare const createPath: (p: Omit<PathNode, "kind">) => PathNode;
126
+ export declare const createImage: (p: Omit<ImageNode, "kind">) => ImageNode;
127
+ export declare const createBitmap: (p: Omit<BitmapNode, "kind">) => BitmapNode;
128
+ export declare const createText: (p: Omit<TextNode, "kind">) => TextNode;
129
+ export declare const createGroup: (children: Node[], opts?: Omit<GroupNode, "kind" | "children">) => GroupNode;
130
+ export declare const createMask: (mask: Node, children: Node[], opts?: Omit<MaskNode, "kind" | "mask" | "children">) => MaskNode;
131
+ export declare function createFrame(opts: {
132
+ background?: Node;
133
+ children: Node[];
134
+ clip?: boolean;
135
+ cornerRadius?: CornerRadius;
136
+ layout?: Layout;
137
+ } & Omit<Transform, "layoutChild">): GroupNode;
138
+ export declare const autoLayout: (g: GroupNode, cfg: Omit<FlexLayout, "type">) => GroupNode;
139
+ export declare const gridLayout: (g: GroupNode, cfg: Omit<GridLayout, "type">) => GroupNode;
package/src/node.js ADDED
@@ -0,0 +1,47 @@
1
+ // ─────────────── constructors ───────────────
2
+ export const createRect = (p) => ({
3
+ kind: "rect",
4
+ ...p,
5
+ });
6
+ export const createEllipse = (p) => ({
7
+ kind: "ellipse",
8
+ ...p,
9
+ });
10
+ // Circle = square-box ellipse; the caller supplies an equal width/height.
11
+ export const createCircle = (p) => ({
12
+ kind: "ellipse",
13
+ ...p,
14
+ });
15
+ export const createPath = (p) => ({
16
+ kind: "path",
17
+ ...p,
18
+ });
19
+ export const createImage = (p) => ({
20
+ kind: "image",
21
+ ...p,
22
+ });
23
+ export const createBitmap = (p) => ({
24
+ kind: "bitmap",
25
+ ...p,
26
+ });
27
+ export const createText = (p) => ({
28
+ kind: "text",
29
+ ...p,
30
+ });
31
+ export const createGroup = (children, opts) => ({ kind: "group", children, ...opts });
32
+ export const createMask = (mask, children, opts) => ({ kind: "mask", mask, children, ...opts });
33
+ // Frame is a helper, not a kind: group + background child + self-clip.
34
+ export function createFrame(opts) {
35
+ const { background, children, clip, cornerRadius, layout, ...rest } = opts;
36
+ return {
37
+ kind: "group",
38
+ children: background ? [background, ...children] : children,
39
+ clip,
40
+ cornerRadius,
41
+ layout,
42
+ ...rest,
43
+ };
44
+ }
45
+ // ─────────────── layout annotators ───────────────
46
+ export const autoLayout = (g, cfg) => ({ ...g, layout: { type: "flex", ...cfg } });
47
+ export const gridLayout = (g, cfg) => ({ ...g, layout: { type: "grid", ...cfg } });
@@ -0,0 +1,49 @@
1
+ import type { CanvasLike } from "./types.js";
2
+ export type PaintCacheStats = {
3
+ paints: number;
4
+ surfaceCreates: number;
5
+ fontProviderBuilds: number;
6
+ imageDecodes: number;
7
+ };
8
+ export type PaintCache = {
9
+ clear(): void;
10
+ dispose(): void;
11
+ stats(): PaintCacheStats;
12
+ };
13
+ type CK = any;
14
+ type FontKey = {
15
+ family: string;
16
+ bytes: Uint8Array;
17
+ }[];
18
+ type CachedSurface = {
19
+ surface: CK;
20
+ canvas: CanvasLike;
21
+ loseContext: () => void;
22
+ width: number;
23
+ height: number;
24
+ hosted: boolean;
25
+ };
26
+ export type PaintCacheState = {
27
+ stats: PaintCacheStats;
28
+ fonts: {
29
+ key: FontKey;
30
+ provider: CK;
31
+ } | null;
32
+ images: Map<string, CK>;
33
+ surface: CachedSurface | null;
34
+ disposed: boolean;
35
+ };
36
+ export declare function createPaintCache(): PaintCache;
37
+ export declare function paintCacheState(cache: PaintCache): PaintCacheState;
38
+ export declare function cachedFontProvider(state: PaintCacheState, loaded: FontKey, build: () => CK): CK;
39
+ export declare function cachedSurface(state: PaintCacheState, want: {
40
+ width: number;
41
+ height: number;
42
+ hosted: boolean;
43
+ }, make: () => {
44
+ surface: CK;
45
+ canvas: CanvasLike;
46
+ loseContext: () => void;
47
+ }): CachedSurface;
48
+ export declare function evictUnusedImages(state: PaintCacheState, used: string[]): void;
49
+ export {};
@@ -0,0 +1,109 @@
1
+ // An opt-in cache a runtime carries across paints of the same scene, for an
2
+ // interactive caller that repaints many times a second. It keeps the three things
3
+ // paintScene would otherwise rebuild on every paint: the font provider, the
4
+ // decoded images and the output surface. A runtime without one paints exactly as
5
+ // it always has, building and freeing all three per paint.
6
+ // The state lives here rather than on the cache object so the public type stays
7
+ // three methods and nothing outside render-kit can reach into it.
8
+ const states = new WeakMap();
9
+ export function createPaintCache() {
10
+ const state = {
11
+ stats: {
12
+ paints: 0,
13
+ surfaceCreates: 0,
14
+ fontProviderBuilds: 0,
15
+ imageDecodes: 0,
16
+ },
17
+ fonts: null,
18
+ images: new Map(),
19
+ surface: null,
20
+ disposed: false,
21
+ };
22
+ const clear = () => {
23
+ const fonts = state.fonts;
24
+ state.fonts = null;
25
+ if (fonts)
26
+ tryFree(() => fonts.provider.delete());
27
+ for (const img of state.images.values())
28
+ tryFree(() => img.delete());
29
+ state.images.clear();
30
+ const surface = state.surface;
31
+ state.surface = null;
32
+ if (surface)
33
+ releaseSurface(surface);
34
+ };
35
+ const cache = {
36
+ clear,
37
+ dispose() {
38
+ clear();
39
+ state.disposed = true;
40
+ },
41
+ stats: () => ({ ...state.stats }),
42
+ };
43
+ states.set(cache, state);
44
+ return cache;
45
+ }
46
+ // paintScene's handle on a cache. Throws for a disposed one, so a paint through
47
+ // it fails before it allocates anything the cache would then leak.
48
+ export function paintCacheState(cache) {
49
+ const state = states.get(cache);
50
+ if (!state)
51
+ throw new Error("render-kit: not a cache from createPaintCache()");
52
+ if (state.disposed)
53
+ throw new Error("render-kit: paint cache is disposed");
54
+ return state;
55
+ }
56
+ // The provider is keyed on the loaded list itself, family and byte-array
57
+ // identity in order, so a caller that keeps its fonts map stable keeps its
58
+ // provider.
59
+ export function cachedFontProvider(state, loaded, build) {
60
+ const hit = state.fonts;
61
+ if (hit &&
62
+ hit.key.length === loaded.length &&
63
+ hit.key.every((f, i) => f.family === loaded[i]?.family && f.bytes === loaded[i]?.bytes))
64
+ return hit.provider;
65
+ state.fonts = null;
66
+ if (hit)
67
+ tryFree(() => hit.provider.delete());
68
+ const provider = build();
69
+ state.stats.fontProviderBuilds++;
70
+ state.fonts = { key: [...loaded], provider };
71
+ return provider;
72
+ }
73
+ // Reused only when it was made the same way (for a host canvas, or offscreen)
74
+ // at the same device size; anything else replaces it.
75
+ export function cachedSurface(state, want, make) {
76
+ const hit = state.surface;
77
+ if (hit &&
78
+ hit.width === want.width &&
79
+ hit.height === want.height &&
80
+ hit.hosted === want.hosted)
81
+ return hit;
82
+ state.surface = null;
83
+ if (hit)
84
+ releaseSurface(hit);
85
+ const made = make();
86
+ state.stats.surfaceCreates++;
87
+ state.surface = { ...made, ...want };
88
+ return state.surface;
89
+ }
90
+ // Deletes the cached images this paint's scene did not use.
91
+ export function evictUnusedImages(state, used) {
92
+ const keep = new Set(used);
93
+ for (const [src, img] of state.images) {
94
+ if (keep.has(src))
95
+ continue;
96
+ state.images.delete(src);
97
+ tryFree(() => img.delete());
98
+ }
99
+ }
100
+ function releaseSurface(s) {
101
+ tryFree(() => s.surface.dispose());
102
+ tryFree(s.loseContext);
103
+ }
104
+ function tryFree(free) {
105
+ try {
106
+ free();
107
+ }
108
+ catch { }
109
+ }
@@ -0,0 +1,21 @@
1
+ import type { CornerRadius, FontVariations, Stroke } from "./types.js";
2
+ export declare function fontVariationList(weight: number | undefined, variations: FontVariations | undefined): {
3
+ axis: string;
4
+ value: number;
5
+ }[];
6
+ export declare function fitRect(iw: number, ih: number, dx: number, dy: number, dw: number, dh: number, fit: "cover" | "contain" | "fill"): {
7
+ sx: number;
8
+ sy: number;
9
+ sw: number;
10
+ sh: number;
11
+ dx: number;
12
+ dy: number;
13
+ dw: number;
14
+ dh: number;
15
+ };
16
+ export declare function strokeInset(stroke: Stroke): number;
17
+ export declare function insetCorner(cr: CornerRadius | undefined, inset: number): CornerRadius;
18
+ export declare function decorationLine(size: number, decoration: string, baseline: number): {
19
+ top: number;
20
+ thickness: number;
21
+ };
@@ -0,0 +1,71 @@
1
+ // The variation axes a text style instantiates its face at. The weight goes
2
+ // first as `wght`: `fontStyle.weight` only PICKS among registered faces, and a
3
+ // variable font registers as its default instance, so without the axis a 700
4
+ // span would get that instance under synthetic bold. A static face has no axes
5
+ // and ignores all of them. Shared by the Paragraph engine and the painter, which
6
+ // must shape identically.
7
+ export function fontVariationList(weight, variations) {
8
+ const axes = { wght: weight || 400, ...variations };
9
+ return Object.entries(axes).map(([axis, value]) => ({ axis, value }));
10
+ }
11
+ // Source/dest rects for object-fit.
12
+ export function fitRect(iw, ih, dx, dy, dw, dh, fit) {
13
+ if (fit === "fill")
14
+ return { sx: 0, sy: 0, sw: iw, sh: ih, dx, dy, dw, dh };
15
+ const ir = iw / ih;
16
+ const dr = dw / dh;
17
+ if (fit === "cover") {
18
+ if (ir > dr) {
19
+ const sw = ih * dr;
20
+ return { sx: (iw - sw) / 2, sy: 0, sw, sh: ih, dx, dy, dw, dh };
21
+ }
22
+ const sh = iw / dr;
23
+ return { sx: 0, sy: (ih - sh) / 2, sw: iw, sh, dx, dy, dw, dh };
24
+ }
25
+ // contain
26
+ if (ir > dr) {
27
+ const h = dw / ir;
28
+ return {
29
+ sx: 0,
30
+ sy: 0,
31
+ sw: iw,
32
+ sh: ih,
33
+ dx,
34
+ dy: dy + (dh - h) / 2,
35
+ dw,
36
+ dh: h,
37
+ };
38
+ }
39
+ const w = dh * ir;
40
+ return { sx: 0, sy: 0, sw: iw, sh: ih, dx: dx + (dw - w) / 2, dy, dw: w, dh };
41
+ }
42
+ // Stroke-edge offset for alignment: +w/2 pulls it inside, -w/2 outside, 0 center.
43
+ export function strokeInset(stroke) {
44
+ if (stroke.align === "inside")
45
+ return stroke.width / 2;
46
+ if (stroke.align === "outside")
47
+ return -stroke.width / 2;
48
+ return 0;
49
+ }
50
+ // Shrink (or grow) corner radii to match an inset/outset stroke rect.
51
+ export function insetCorner(cr, inset) {
52
+ if (cr === undefined)
53
+ return 0;
54
+ if (typeof cr === "number")
55
+ return Math.max(0, cr - inset);
56
+ return [
57
+ Math.max(0, cr[0] - inset),
58
+ Math.max(0, cr[1] - inset),
59
+ Math.max(0, cr[2] - inset),
60
+ Math.max(0, cr[3] - inset),
61
+ ];
62
+ }
63
+ // Underline/strike geometry from the alphabetic baseline + font size. Kept in
64
+ // lockstep between both painters so they draw the same line.
65
+ export function decorationLine(size, decoration, baseline) {
66
+ const thickness = Math.max(1, size * 0.06);
67
+ const center = decoration === "line-through"
68
+ ? baseline - size * 0.28
69
+ : baseline + size * 0.1;
70
+ return { top: center - thickness / 2, thickness };
71
+ }
@@ -0,0 +1,6 @@
1
+ import type { TextEngine } from "./text-engine.js";
2
+ type CK = any;
3
+ export declare function createParagraphEngine(ck: CK, fonts: Map<string, Uint8Array[]>): TextEngine & {
4
+ dispose(): void;
5
+ };
6
+ export {};