@solidtv/renderer 1.2.6 → 1.2.8
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/dist/exports/canvas-shaders.d.ts +1 -0
- package/dist/exports/canvas-shaders.js +1 -0
- package/dist/exports/canvas-shaders.js.map +1 -1
- package/dist/exports/webgl-shaders.d.ts +1 -0
- package/dist/exports/webgl-shaders.js +1 -0
- package/dist/exports/webgl-shaders.js.map +1 -1
- package/dist/src/core/CoreTextureManager.d.ts +1 -0
- package/dist/src/core/CoreTextureManager.js +3 -0
- package/dist/src/core/CoreTextureManager.js.map +1 -1
- package/dist/src/core/lib/textureCompression.js +17 -5
- package/dist/src/core/lib/textureCompression.js.map +1 -1
- package/dist/src/core/lib/textureSvg.d.ts +14 -4
- package/dist/src/core/lib/textureSvg.js +61 -24
- package/dist/src/core/lib/textureSvg.js.map +1 -1
- package/dist/src/core/shaders/canvas/Blur.d.ts +11 -0
- package/dist/src/core/shaders/canvas/Blur.js +18 -0
- package/dist/src/core/shaders/canvas/Blur.js.map +1 -0
- package/dist/src/core/shaders/templates/BlurTemplate.d.ts +19 -0
- package/dist/src/core/shaders/templates/BlurTemplate.js +6 -0
- package/dist/src/core/shaders/templates/BlurTemplate.js.map +1 -0
- package/dist/src/core/shaders/webgl/Blur.d.ts +11 -0
- package/dist/src/core/shaders/webgl/Blur.js +46 -0
- package/dist/src/core/shaders/webgl/Blur.js.map +1 -0
- package/dist/src/core/text-rendering/SdfFontHandler.js +27 -6
- package/dist/src/core/text-rendering/SdfFontHandler.js.map +1 -1
- package/dist/src/core/textures/ImageTexture.js +2 -5
- package/dist/src/core/textures/ImageTexture.js.map +1 -1
- package/dist/src/main-api/Inspector.js +8 -1
- package/dist/src/main-api/Inspector.js.map +1 -1
- package/dist/tsconfig.dist.tsbuildinfo +1 -1
- package/exports/canvas-shaders.ts +1 -0
- package/exports/webgl-shaders.ts +1 -0
- package/package.json +1 -1
- package/src/core/CoreTextureManager.ts +4 -0
- package/src/core/lib/textureCompression.ts +24 -8
- package/src/core/lib/textureSvg.ts +67 -28
- package/src/core/shaders/canvas/Blur.ts +23 -0
- package/src/core/shaders/templates/BlurTemplate.test.ts +23 -0
- package/src/core/shaders/templates/BlurTemplate.ts +25 -0
- package/src/core/shaders/webgl/Blur.ts +47 -0
- package/src/core/text-rendering/SdfFontHandler.ts +29 -7
- package/src/core/textures/ImageTexture.ts +2 -13
- package/src/main-api/Inspector.ts +9 -2
- package/dist/tsconfig.tsbuildinfo +0 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { assertTruthy } from '../../utils.js';
|
|
2
2
|
import { type TextureData } from '../textures/Texture.js';
|
|
3
|
+
import { isBase64Image } from './utils.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Tests if the given location is a SVG
|
|
@@ -14,11 +15,21 @@ export function isSvgImage(url: string): boolean {
|
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
|
-
* Loads a SVG image
|
|
18
|
-
*
|
|
19
|
-
* @
|
|
18
|
+
* Loads a SVG image and rasterizes it for use as a texture.
|
|
19
|
+
*
|
|
20
|
+
* @remarks
|
|
21
|
+
* Rasterizes at `pixelRatio` to keep the texture sharp on HiDPI / 4K displays.
|
|
22
|
+
* `width`/`height` are interpreted as the logical (CSS-pixel) target size; the
|
|
23
|
+
* backing canvas is allocated at `width * pixelRatio` × `height * pixelRatio`.
|
|
24
|
+
*
|
|
25
|
+
* When `sw`/`sh` are provided they describe a source-region crop on the SVG
|
|
26
|
+
* (not a crop of the destination canvas) and are sampled via the 9-arg form of
|
|
27
|
+
* drawImage.
|
|
28
|
+
*
|
|
29
|
+
* Returns an `ImageBitmap` when available (zero CPU readback, transferable),
|
|
30
|
+
* falling back to `ImageData` on older browsers without `createImageBitmap`.
|
|
20
31
|
*/
|
|
21
|
-
export const loadSvg = (
|
|
32
|
+
export const loadSvg = async (
|
|
22
33
|
url: string,
|
|
23
34
|
width: number | null,
|
|
24
35
|
height: number | null,
|
|
@@ -26,34 +37,62 @@ export const loadSvg = (
|
|
|
26
37
|
sy: number | null,
|
|
27
38
|
sw: number | null,
|
|
28
39
|
sh: number | null,
|
|
40
|
+
pixelRatio: number,
|
|
29
41
|
): Promise<TextureData> => {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
ctx.imageSmoothingEnabled = true;
|
|
36
|
-
const img = new Image();
|
|
37
|
-
img.onload = () => {
|
|
38
|
-
const x = sx ?? 0;
|
|
39
|
-
const y = sy ?? 0;
|
|
40
|
-
const w = width || img.width;
|
|
41
|
-
const h = height || img.height;
|
|
42
|
-
|
|
43
|
-
canvas.width = w;
|
|
44
|
-
canvas.height = h;
|
|
45
|
-
ctx.drawImage(img, 0, 0, w, h);
|
|
46
|
-
|
|
47
|
-
resolve({
|
|
48
|
-
data: ctx.getImageData(x, y, sw ?? w, sh ?? h),
|
|
49
|
-
premultiplyAlpha: false,
|
|
50
|
-
});
|
|
51
|
-
};
|
|
42
|
+
const img = new Image();
|
|
43
|
+
if (isBase64Image(url) === false) {
|
|
44
|
+
img.crossOrigin = 'anonymous';
|
|
45
|
+
}
|
|
52
46
|
|
|
47
|
+
await new Promise<void>((resolve, reject) => {
|
|
48
|
+
img.onload = () => resolve();
|
|
53
49
|
img.onerror = (err) => {
|
|
54
|
-
reject(
|
|
50
|
+
reject(
|
|
51
|
+
err instanceof Error ? err : new Error(`SVG loading failed: ${url}`),
|
|
52
|
+
);
|
|
55
53
|
};
|
|
56
|
-
|
|
57
54
|
img.src = url;
|
|
58
55
|
});
|
|
56
|
+
|
|
57
|
+
// Target size precedence: explicit w/h on the texture, then the source
|
|
58
|
+
// crop dims (so a node that only sets srcWidth/srcHeight gets a texture
|
|
59
|
+
// sized to the crop, matching pre-fix behavior), then the SVG's intrinsic
|
|
60
|
+
// dimensions.
|
|
61
|
+
const targetW = width || sw || img.naturalWidth || img.width;
|
|
62
|
+
const targetH = height || sh || img.naturalHeight || img.height;
|
|
63
|
+
// Clamp the DPR multiplier so a sub-1 stage pixelRatio (e.g. an app
|
|
64
|
+
// rendering at 720p inside a 1080p design grid) doesn't downscale the
|
|
65
|
+
// raster below the requested size. HiDPI upscaling still applies.
|
|
66
|
+
const ratio = pixelRatio > 1 ? pixelRatio : 1;
|
|
67
|
+
const physW = Math.max(1, Math.ceil(targetW * ratio));
|
|
68
|
+
const physH = Math.max(1, Math.ceil(targetH * ratio));
|
|
69
|
+
|
|
70
|
+
const canvas = document.createElement('canvas');
|
|
71
|
+
canvas.width = physW;
|
|
72
|
+
canvas.height = physH;
|
|
73
|
+
const ctx = canvas.getContext('2d');
|
|
74
|
+
assertTruthy(ctx);
|
|
75
|
+
|
|
76
|
+
if (sw !== null && sh !== null) {
|
|
77
|
+
ctx.drawImage(img, sx ?? 0, sy ?? 0, sw, sh, 0, 0, physW, physH);
|
|
78
|
+
} else {
|
|
79
|
+
ctx.drawImage(img, 0, 0, physW, physH);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (typeof createImageBitmap === 'function') {
|
|
83
|
+
try {
|
|
84
|
+
const bitmap = await createImageBitmap(canvas);
|
|
85
|
+
return {
|
|
86
|
+
data: bitmap,
|
|
87
|
+
premultiplyAlpha: false,
|
|
88
|
+
};
|
|
89
|
+
} catch {
|
|
90
|
+
// fall through to ImageData
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
data: ctx.getImageData(0, 0, physW, physH),
|
|
96
|
+
premultiplyAlpha: false,
|
|
97
|
+
};
|
|
59
98
|
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { CanvasShaderType } from '../../renderers/canvas/CanvasShaderNode.js';
|
|
2
|
+
import { BlurTemplate, type BlurProps } from '../templates/BlurTemplate.js';
|
|
3
|
+
|
|
4
|
+
export interface ComputedBlurValues {
|
|
5
|
+
filter: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Canvas2D Blur backed by the native `ctx.filter = 'blur(Npx)'`. Browsers that
|
|
10
|
+
* lack filter support (Chrome 38-52) will silently no-op — accept the
|
|
11
|
+
* degradation rather than emulating a multi-pass blur in JS.
|
|
12
|
+
*/
|
|
13
|
+
export const Blur: CanvasShaderType<BlurProps, ComputedBlurValues> = {
|
|
14
|
+
props: BlurTemplate.props,
|
|
15
|
+
saveAndRestore: true,
|
|
16
|
+
update() {
|
|
17
|
+
this.computed.filter = `blur(${this.props!.amount}px)`;
|
|
18
|
+
},
|
|
19
|
+
render(ctx, _node, renderContext) {
|
|
20
|
+
ctx.filter = this.computed.filter!;
|
|
21
|
+
renderContext();
|
|
22
|
+
},
|
|
23
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { BlurTemplate, type BlurProps } from './BlurTemplate.js';
|
|
3
|
+
import { resolveShaderProps } from '../../renderers/CoreShaderNode.js';
|
|
4
|
+
|
|
5
|
+
function resolve(input: Partial<BlurProps>): BlurProps {
|
|
6
|
+
const props = { ...input } as Record<string, unknown>;
|
|
7
|
+
resolveShaderProps(props, BlurTemplate.props as never);
|
|
8
|
+
return props as unknown as BlurProps;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
describe('BlurTemplate', () => {
|
|
12
|
+
it('applies default amount when omitted', () => {
|
|
13
|
+
expect(resolve({}).amount).toBe(4);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('passes through user-provided amount', () => {
|
|
17
|
+
expect(resolve({ amount: 10 }).amount).toBe(10);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('passes through zero (no blur)', () => {
|
|
21
|
+
expect(resolve({ amount: 0 }).amount).toBe(0);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { CoreShaderType } from '../../renderers/CoreShaderNode.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Properties of the {@link Blur} shader.
|
|
5
|
+
*
|
|
6
|
+
* Intended for nodes with an Image Texture. Applying it to color-only or text
|
|
7
|
+
* nodes will just blur the solid fill.
|
|
8
|
+
*/
|
|
9
|
+
export interface BlurProps {
|
|
10
|
+
/**
|
|
11
|
+
* Blur amount in node-space pixels.
|
|
12
|
+
*
|
|
13
|
+
* Single-pass kernel — small values (1-8) look best. Larger values still
|
|
14
|
+
* work but lose smoothness because there are only 9 samples per pixel.
|
|
15
|
+
*
|
|
16
|
+
* @default 4
|
|
17
|
+
*/
|
|
18
|
+
amount: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const BlurTemplate: CoreShaderType<BlurProps> = {
|
|
22
|
+
props: {
|
|
23
|
+
amount: 4,
|
|
24
|
+
},
|
|
25
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { WebGlShaderType } from '../../renderers/webgl/WebGlShaderNode.js';
|
|
2
|
+
import { BlurTemplate, type BlurProps } from '../templates/BlurTemplate.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Single-pass 3x3 Gaussian-approximation blur (1,2,1 / 2,4,2 / 1,2,1) / 16.
|
|
6
|
+
*
|
|
7
|
+
* 9 texture fetches, no second pass, no render target — designed for Image
|
|
8
|
+
* Textures on constrained devices. Larger blurs trade smoothness for speed;
|
|
9
|
+
* if a higher-quality blur is needed, stack multiple nodes or do a separable
|
|
10
|
+
* pass via a RenderTexture.
|
|
11
|
+
*/
|
|
12
|
+
export const Blur: WebGlShaderType<BlurProps> = {
|
|
13
|
+
props: BlurTemplate.props,
|
|
14
|
+
update() {
|
|
15
|
+
this.uniform1f('u_amount', this.props!.amount);
|
|
16
|
+
},
|
|
17
|
+
fragment: `
|
|
18
|
+
# ifdef GL_FRAGMENT_PRECISION_HIGH
|
|
19
|
+
precision highp float;
|
|
20
|
+
# else
|
|
21
|
+
precision mediump float;
|
|
22
|
+
# endif
|
|
23
|
+
|
|
24
|
+
uniform vec2 u_dimensions;
|
|
25
|
+
uniform sampler2D u_texture;
|
|
26
|
+
uniform float u_amount;
|
|
27
|
+
|
|
28
|
+
varying vec4 v_color;
|
|
29
|
+
varying vec2 v_textureCoords;
|
|
30
|
+
|
|
31
|
+
void main() {
|
|
32
|
+
vec2 px = vec2(u_amount) / u_dimensions;
|
|
33
|
+
|
|
34
|
+
vec4 c = texture2D(u_texture, v_textureCoords) * 0.25;
|
|
35
|
+
c += texture2D(u_texture, v_textureCoords + vec2( px.x, 0.0)) * 0.125;
|
|
36
|
+
c += texture2D(u_texture, v_textureCoords + vec2(-px.x, 0.0)) * 0.125;
|
|
37
|
+
c += texture2D(u_texture, v_textureCoords + vec2(0.0, px.y)) * 0.125;
|
|
38
|
+
c += texture2D(u_texture, v_textureCoords + vec2(0.0, -px.y)) * 0.125;
|
|
39
|
+
c += texture2D(u_texture, v_textureCoords + vec2( px.x, px.y)) * 0.0625;
|
|
40
|
+
c += texture2D(u_texture, v_textureCoords + vec2(-px.x, -px.y)) * 0.0625;
|
|
41
|
+
c += texture2D(u_texture, v_textureCoords + vec2( px.x, -px.y)) * 0.0625;
|
|
42
|
+
c += texture2D(u_texture, v_textureCoords + vec2(-px.x, px.y)) * 0.0625;
|
|
43
|
+
|
|
44
|
+
gl_FragColor = c * v_color;
|
|
45
|
+
}
|
|
46
|
+
`,
|
|
47
|
+
};
|
|
@@ -322,13 +322,35 @@ export const loadFont = (
|
|
|
322
322
|
const nwff: CoreTextNode[] = (nodesWaitingForFont[fontFamily] = []);
|
|
323
323
|
// Create loading promise
|
|
324
324
|
const loadPromise = (async (): Promise<void> => {
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
325
|
+
const fontData = await new Promise<SdfFontData>((resolve, reject) => {
|
|
326
|
+
const xhr = new XMLHttpRequest();
|
|
327
|
+
xhr.open('GET', atlasDataUrl, true);
|
|
328
|
+
xhr.responseType = 'json';
|
|
329
|
+
xhr.onload = () => {
|
|
330
|
+
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 0) {
|
|
331
|
+
let data = xhr.response;
|
|
332
|
+
if (typeof data === 'string') {
|
|
333
|
+
try {
|
|
334
|
+
data = JSON.parse(data);
|
|
335
|
+
} catch (e) {
|
|
336
|
+
reject(new Error('Failed to parse font data JSON'));
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
resolve(data as SdfFontData);
|
|
341
|
+
} else {
|
|
342
|
+
reject(new Error(`Failed to load font data: ${xhr.statusText}`));
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
xhr.onerror = () => {
|
|
346
|
+
reject(
|
|
347
|
+
new Error(
|
|
348
|
+
'Network error occurred while trying to load the font data.',
|
|
349
|
+
),
|
|
350
|
+
);
|
|
351
|
+
};
|
|
352
|
+
xhr.send(null);
|
|
353
|
+
});
|
|
332
354
|
if (!fontData || !fontData.chars) {
|
|
333
355
|
throw new Error('Invalid SDF font data format');
|
|
334
356
|
}
|
|
@@ -314,19 +314,7 @@ export class ImageTexture extends Texture {
|
|
|
314
314
|
return this.loadImage(absoluteSrc);
|
|
315
315
|
}
|
|
316
316
|
|
|
317
|
-
if (type === 'svg') {
|
|
318
|
-
return loadSvg(
|
|
319
|
-
absoluteSrc,
|
|
320
|
-
this.props.w,
|
|
321
|
-
this.props.h,
|
|
322
|
-
this.props.sx,
|
|
323
|
-
this.props.sy,
|
|
324
|
-
this.props.sw,
|
|
325
|
-
this.props.sh,
|
|
326
|
-
);
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
if (isSvgImage(src) === true) {
|
|
317
|
+
if (type === 'svg' || isSvgImage(src) === true) {
|
|
330
318
|
return loadSvg(
|
|
331
319
|
absoluteSrc,
|
|
332
320
|
this.props.w,
|
|
@@ -335,6 +323,7 @@ export class ImageTexture extends Texture {
|
|
|
335
323
|
this.props.sy,
|
|
336
324
|
this.props.sw,
|
|
337
325
|
this.props.sh,
|
|
326
|
+
this.txManager.pixelRatio,
|
|
338
327
|
);
|
|
339
328
|
}
|
|
340
329
|
|
|
@@ -859,7 +859,6 @@ export class Inspector {
|
|
|
859
859
|
// but we need it from the inspector to set the initial properties on the div element
|
|
860
860
|
const mergedProps = {
|
|
861
861
|
...node.props,
|
|
862
|
-
|
|
863
862
|
...(node as unknown as { textProps: CoreTextNodeProps }).textProps,
|
|
864
863
|
} as CoreTextNodeProps;
|
|
865
864
|
const div = this.createDiv(node.id, mergedProps);
|
|
@@ -1230,6 +1229,11 @@ export class Inspector {
|
|
|
1230
1229
|
|
|
1231
1230
|
// CSS mappable attribute
|
|
1232
1231
|
if (stylePropertyMap[property]) {
|
|
1232
|
+
// Text nodes must always stay at opacity 0.001 — never let alpha updates override it.
|
|
1233
|
+
if (property === 'alpha' && div.style.pointerEvents === 'none') {
|
|
1234
|
+
return;
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1233
1237
|
const mappedStyleResponse = stylePropertyMap[property]?.(value);
|
|
1234
1238
|
|
|
1235
1239
|
if (mappedStyleResponse === null) {
|
|
@@ -1354,7 +1358,10 @@ export class Inspector {
|
|
|
1354
1358
|
div.style.left = `${x - w * mountX}px`;
|
|
1355
1359
|
div.style.width = `${w}px`;
|
|
1356
1360
|
div.style.height = `${h}px`;
|
|
1357
|
-
|
|
1361
|
+
// Text nodes must keep opacity at 0.001 — never override it from animation props.
|
|
1362
|
+
if (div.style.pointerEvents !== 'none') {
|
|
1363
|
+
div.style.opacity = `${alpha}`;
|
|
1364
|
+
}
|
|
1358
1365
|
div.style.rotate = `${rotation}rad`;
|
|
1359
1366
|
div.style.scale = `${scale}`;
|
|
1360
1367
|
div.style.color = convertColorToRgba(color);
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"fileNames":[],"fileInfos":[],"root":[],"options":{"allowJs":true,"allowSyntheticDefaultImports":true,"composite":true,"declaration":true,"experimentalDecorators":true,"module":100,"noImplicitAny":false,"noImplicitOverride":true,"noUncheckedIndexedAccess":true,"outDir":"./","rootDir":"..","sourceMap":true,"strict":true,"target":9,"verbatimModuleSyntax":true},"version":"5.8.3"}
|