badgecraft 0.3.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.
- package/CHANGELOG.md +31 -0
- package/LICENSE +21 -0
- package/README.md +406 -0
- package/cli/index.mjs +143 -0
- package/cli/render.d.ts +20 -0
- package/cli/render.mjs +87 -0
- package/cli/studio.mjs +98 -0
- package/dist/badge-B-g_9WFj.d.ts +79 -0
- package/dist/badge-CCjUxNIG.d.cts +79 -0
- package/dist/chunk-3OMN635U.js +44 -0
- package/dist/chunk-3OMN635U.js.map +1 -0
- package/dist/chunk-BRT5S5PF.cjs +3418 -0
- package/dist/chunk-BRT5S5PF.cjs.map +1 -0
- package/dist/chunk-EBDSIGZR.cjs +48 -0
- package/dist/chunk-EBDSIGZR.cjs.map +1 -0
- package/dist/chunk-RRYBMB25.js +3410 -0
- package/dist/chunk-RRYBMB25.js.map +1 -0
- package/dist/cli-browser.js +526 -0
- package/dist/element.cjs +176 -0
- package/dist/element.cjs.map +1 -0
- package/dist/element.d.cts +31 -0
- package/dist/element.d.ts +31 -0
- package/dist/element.js +174 -0
- package/dist/element.js.map +1 -0
- package/dist/index.cjs +47 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +58 -0
- package/dist/index.d.ts +58 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/react.cjs +60 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +29 -0
- package/dist/react.d.ts +29 -0
- package/dist/react.js +55 -0
- package/dist/react.js.map +1 -0
- package/dist/studio/assets/badge-9Zq6e9Ht.js +524 -0
- package/dist/studio/assets/base-ItCYnbux.css +1 -0
- package/dist/studio/assets/base-LFa9cTPp.js +1 -0
- package/dist/studio/assets/element-CCTxyf61.js +1 -0
- package/dist/studio/assets/index-BVuSTaz-.css +1 -0
- package/dist/studio/assets/index-CTTcx2pV.js +28 -0
- package/dist/studio/assets/option-keys-CKi71y0n.js +1 -0
- package/dist/studio/assets/react-Cq5Erey2.js +9 -0
- package/dist/studio/assets/react-DyJFYpWJ.js +1 -0
- package/dist/studio/assets/studio-C6plMNOe.css +1 -0
- package/dist/studio/assets/studio-DBcSY6tV.js +1 -0
- package/dist/studio/assets/style-C2sJ2Xy3.css +1 -0
- package/dist/studio/assets/vue-BpGwr3iF.js +1 -0
- package/dist/studio/element.html +31 -0
- package/dist/studio/env/studio.hdr +5 -0
- package/dist/studio/index.html +24 -0
- package/dist/studio/logos/apple.svg +1 -0
- package/dist/studio/logos/atlassian.svg +1 -0
- package/dist/studio/logos/google.svg +1 -0
- package/dist/studio/logos/meta.svg +1 -0
- package/dist/studio/react.html +16 -0
- package/dist/studio/studio.html +27 -0
- package/dist/studio/vue.html +15 -0
- package/dist/types-CHM3-ZZV.d.cts +204 -0
- package/dist/types-CHM3-ZZV.d.ts +204 -0
- package/dist/vue.cjs +93 -0
- package/dist/vue.cjs.map +1 -0
- package/dist/vue.d.cts +115 -0
- package/dist/vue.d.ts +115 -0
- package/dist/vue.js +87 -0
- package/dist/vue.js.map +1 -0
- package/docs/cli.md +146 -0
- package/docs/gallery.jpg +0 -0
- package/docs/performance.md +151 -0
- package/docs/render-quality.md +220 -0
- package/docs/studio.jpg +0 -0
- package/package.json +163 -0
package/dist/vue.d.cts
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import * as vue from 'vue';
|
|
2
|
+
import { PropType, App } from 'vue';
|
|
3
|
+
import { a as BadgeMode, i as MaterialInput, f as FrameShape, F as FrameOptions, R as ReliefOptions, E as EnvironmentInput, T as ToneMapping, h as Interaction, B as BadgeOptions, I as IdleAnimation } from './types-CHM3-ZZV.cjs';
|
|
4
|
+
export { b as BadgecraftHandle, c as BrushDirection, d as EnvironmentLight, e as EnvironmentPreset, g as ImageEnvironment, M as Material, j as MaterialPreset, k as MaterialSlot, P as ProceduralEnvironment, l as ProfileShape, m as ReliefRole } from './types-CHM3-ZZV.cjs';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Vue 3 binding for badgecraft.
|
|
8
|
+
*
|
|
9
|
+
* ```vue
|
|
10
|
+
* <Badgecraft svg="/award.svg" metal="gold" :size="200" @ready="..." />
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
declare const Badgecraft: vue.DefineComponent<vue.ExtractPropTypes<{
|
|
14
|
+
svg: {
|
|
15
|
+
type: PropType<string | SVGElement>;
|
|
16
|
+
required: true;
|
|
17
|
+
};
|
|
18
|
+
mode: PropType<BadgeMode>;
|
|
19
|
+
metal: PropType<MaterialInput>;
|
|
20
|
+
enamel: PropType<MaterialInput>;
|
|
21
|
+
base: PropType<MaterialInput>;
|
|
22
|
+
accent: PropType<MaterialInput>;
|
|
23
|
+
color: StringConstructor;
|
|
24
|
+
frame: PropType<FrameShape | FrameOptions | null>;
|
|
25
|
+
rim: NumberConstructor;
|
|
26
|
+
outline: PropType<number | "auto">;
|
|
27
|
+
fillHoles: {
|
|
28
|
+
type: BooleanConstructor;
|
|
29
|
+
default: undefined;
|
|
30
|
+
};
|
|
31
|
+
relief: PropType<ReliefOptions>;
|
|
32
|
+
environment: PropType<EnvironmentInput>;
|
|
33
|
+
environmentRotation: NumberConstructor;
|
|
34
|
+
exposure: NumberConstructor;
|
|
35
|
+
toneMapping: PropType<ToneMapping>;
|
|
36
|
+
interaction: PropType<Interaction>;
|
|
37
|
+
tiltMax: NumberConstructor;
|
|
38
|
+
pose: PropType<BadgeOptions["pose"]>;
|
|
39
|
+
idle: PropType<IdleAnimation>;
|
|
40
|
+
locked: {
|
|
41
|
+
type: BooleanConstructor;
|
|
42
|
+
default: undefined;
|
|
43
|
+
};
|
|
44
|
+
lockedColor: StringConstructor;
|
|
45
|
+
shadow: NumberConstructor;
|
|
46
|
+
quality: PropType<number | "auto">;
|
|
47
|
+
padding: NumberConstructor;
|
|
48
|
+
/** Width and height (number = px). Default 160. */
|
|
49
|
+
size: {
|
|
50
|
+
type: (StringConstructor | NumberConstructor)[];
|
|
51
|
+
default: number;
|
|
52
|
+
};
|
|
53
|
+
/** Accessible label. */
|
|
54
|
+
alt: StringConstructor;
|
|
55
|
+
}>, () => vue.VNode<vue.RendererNode, vue.RendererElement, {
|
|
56
|
+
[key: string]: any;
|
|
57
|
+
}>, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {
|
|
58
|
+
ready: () => true;
|
|
59
|
+
error: (error: Error) => boolean;
|
|
60
|
+
}, string, vue.PublicProps, Readonly<vue.ExtractPropTypes<{
|
|
61
|
+
svg: {
|
|
62
|
+
type: PropType<string | SVGElement>;
|
|
63
|
+
required: true;
|
|
64
|
+
};
|
|
65
|
+
mode: PropType<BadgeMode>;
|
|
66
|
+
metal: PropType<MaterialInput>;
|
|
67
|
+
enamel: PropType<MaterialInput>;
|
|
68
|
+
base: PropType<MaterialInput>;
|
|
69
|
+
accent: PropType<MaterialInput>;
|
|
70
|
+
color: StringConstructor;
|
|
71
|
+
frame: PropType<FrameShape | FrameOptions | null>;
|
|
72
|
+
rim: NumberConstructor;
|
|
73
|
+
outline: PropType<number | "auto">;
|
|
74
|
+
fillHoles: {
|
|
75
|
+
type: BooleanConstructor;
|
|
76
|
+
default: undefined;
|
|
77
|
+
};
|
|
78
|
+
relief: PropType<ReliefOptions>;
|
|
79
|
+
environment: PropType<EnvironmentInput>;
|
|
80
|
+
environmentRotation: NumberConstructor;
|
|
81
|
+
exposure: NumberConstructor;
|
|
82
|
+
toneMapping: PropType<ToneMapping>;
|
|
83
|
+
interaction: PropType<Interaction>;
|
|
84
|
+
tiltMax: NumberConstructor;
|
|
85
|
+
pose: PropType<BadgeOptions["pose"]>;
|
|
86
|
+
idle: PropType<IdleAnimation>;
|
|
87
|
+
locked: {
|
|
88
|
+
type: BooleanConstructor;
|
|
89
|
+
default: undefined;
|
|
90
|
+
};
|
|
91
|
+
lockedColor: StringConstructor;
|
|
92
|
+
shadow: NumberConstructor;
|
|
93
|
+
quality: PropType<number | "auto">;
|
|
94
|
+
padding: NumberConstructor;
|
|
95
|
+
/** Width and height (number = px). Default 160. */
|
|
96
|
+
size: {
|
|
97
|
+
type: (StringConstructor | NumberConstructor)[];
|
|
98
|
+
default: number;
|
|
99
|
+
};
|
|
100
|
+
/** Accessible label. */
|
|
101
|
+
alt: StringConstructor;
|
|
102
|
+
}>> & Readonly<{
|
|
103
|
+
onReady?: (() => any) | undefined;
|
|
104
|
+
onError?: ((error: Error) => any) | undefined;
|
|
105
|
+
}>, {
|
|
106
|
+
fillHoles: boolean;
|
|
107
|
+
locked: boolean;
|
|
108
|
+
size: string | number;
|
|
109
|
+
}, {}, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>;
|
|
110
|
+
/** `app.use(BadgecraftPlugin)` registers `<Badgecraft>` globally. */
|
|
111
|
+
declare const BadgecraftPlugin: {
|
|
112
|
+
install(app: App): void;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
export { BadgeMode, BadgeOptions, Badgecraft, BadgecraftPlugin, EnvironmentInput, FrameOptions, FrameShape, IdleAnimation, Interaction, MaterialInput, ReliefOptions, ToneMapping, Badgecraft as default };
|
package/dist/vue.d.ts
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import * as vue from 'vue';
|
|
2
|
+
import { PropType, App } from 'vue';
|
|
3
|
+
import { a as BadgeMode, i as MaterialInput, f as FrameShape, F as FrameOptions, R as ReliefOptions, E as EnvironmentInput, T as ToneMapping, h as Interaction, B as BadgeOptions, I as IdleAnimation } from './types-CHM3-ZZV.js';
|
|
4
|
+
export { b as BadgecraftHandle, c as BrushDirection, d as EnvironmentLight, e as EnvironmentPreset, g as ImageEnvironment, M as Material, j as MaterialPreset, k as MaterialSlot, P as ProceduralEnvironment, l as ProfileShape, m as ReliefRole } from './types-CHM3-ZZV.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Vue 3 binding for badgecraft.
|
|
8
|
+
*
|
|
9
|
+
* ```vue
|
|
10
|
+
* <Badgecraft svg="/award.svg" metal="gold" :size="200" @ready="..." />
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
declare const Badgecraft: vue.DefineComponent<vue.ExtractPropTypes<{
|
|
14
|
+
svg: {
|
|
15
|
+
type: PropType<string | SVGElement>;
|
|
16
|
+
required: true;
|
|
17
|
+
};
|
|
18
|
+
mode: PropType<BadgeMode>;
|
|
19
|
+
metal: PropType<MaterialInput>;
|
|
20
|
+
enamel: PropType<MaterialInput>;
|
|
21
|
+
base: PropType<MaterialInput>;
|
|
22
|
+
accent: PropType<MaterialInput>;
|
|
23
|
+
color: StringConstructor;
|
|
24
|
+
frame: PropType<FrameShape | FrameOptions | null>;
|
|
25
|
+
rim: NumberConstructor;
|
|
26
|
+
outline: PropType<number | "auto">;
|
|
27
|
+
fillHoles: {
|
|
28
|
+
type: BooleanConstructor;
|
|
29
|
+
default: undefined;
|
|
30
|
+
};
|
|
31
|
+
relief: PropType<ReliefOptions>;
|
|
32
|
+
environment: PropType<EnvironmentInput>;
|
|
33
|
+
environmentRotation: NumberConstructor;
|
|
34
|
+
exposure: NumberConstructor;
|
|
35
|
+
toneMapping: PropType<ToneMapping>;
|
|
36
|
+
interaction: PropType<Interaction>;
|
|
37
|
+
tiltMax: NumberConstructor;
|
|
38
|
+
pose: PropType<BadgeOptions["pose"]>;
|
|
39
|
+
idle: PropType<IdleAnimation>;
|
|
40
|
+
locked: {
|
|
41
|
+
type: BooleanConstructor;
|
|
42
|
+
default: undefined;
|
|
43
|
+
};
|
|
44
|
+
lockedColor: StringConstructor;
|
|
45
|
+
shadow: NumberConstructor;
|
|
46
|
+
quality: PropType<number | "auto">;
|
|
47
|
+
padding: NumberConstructor;
|
|
48
|
+
/** Width and height (number = px). Default 160. */
|
|
49
|
+
size: {
|
|
50
|
+
type: (StringConstructor | NumberConstructor)[];
|
|
51
|
+
default: number;
|
|
52
|
+
};
|
|
53
|
+
/** Accessible label. */
|
|
54
|
+
alt: StringConstructor;
|
|
55
|
+
}>, () => vue.VNode<vue.RendererNode, vue.RendererElement, {
|
|
56
|
+
[key: string]: any;
|
|
57
|
+
}>, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {
|
|
58
|
+
ready: () => true;
|
|
59
|
+
error: (error: Error) => boolean;
|
|
60
|
+
}, string, vue.PublicProps, Readonly<vue.ExtractPropTypes<{
|
|
61
|
+
svg: {
|
|
62
|
+
type: PropType<string | SVGElement>;
|
|
63
|
+
required: true;
|
|
64
|
+
};
|
|
65
|
+
mode: PropType<BadgeMode>;
|
|
66
|
+
metal: PropType<MaterialInput>;
|
|
67
|
+
enamel: PropType<MaterialInput>;
|
|
68
|
+
base: PropType<MaterialInput>;
|
|
69
|
+
accent: PropType<MaterialInput>;
|
|
70
|
+
color: StringConstructor;
|
|
71
|
+
frame: PropType<FrameShape | FrameOptions | null>;
|
|
72
|
+
rim: NumberConstructor;
|
|
73
|
+
outline: PropType<number | "auto">;
|
|
74
|
+
fillHoles: {
|
|
75
|
+
type: BooleanConstructor;
|
|
76
|
+
default: undefined;
|
|
77
|
+
};
|
|
78
|
+
relief: PropType<ReliefOptions>;
|
|
79
|
+
environment: PropType<EnvironmentInput>;
|
|
80
|
+
environmentRotation: NumberConstructor;
|
|
81
|
+
exposure: NumberConstructor;
|
|
82
|
+
toneMapping: PropType<ToneMapping>;
|
|
83
|
+
interaction: PropType<Interaction>;
|
|
84
|
+
tiltMax: NumberConstructor;
|
|
85
|
+
pose: PropType<BadgeOptions["pose"]>;
|
|
86
|
+
idle: PropType<IdleAnimation>;
|
|
87
|
+
locked: {
|
|
88
|
+
type: BooleanConstructor;
|
|
89
|
+
default: undefined;
|
|
90
|
+
};
|
|
91
|
+
lockedColor: StringConstructor;
|
|
92
|
+
shadow: NumberConstructor;
|
|
93
|
+
quality: PropType<number | "auto">;
|
|
94
|
+
padding: NumberConstructor;
|
|
95
|
+
/** Width and height (number = px). Default 160. */
|
|
96
|
+
size: {
|
|
97
|
+
type: (StringConstructor | NumberConstructor)[];
|
|
98
|
+
default: number;
|
|
99
|
+
};
|
|
100
|
+
/** Accessible label. */
|
|
101
|
+
alt: StringConstructor;
|
|
102
|
+
}>> & Readonly<{
|
|
103
|
+
onReady?: (() => any) | undefined;
|
|
104
|
+
onError?: ((error: Error) => any) | undefined;
|
|
105
|
+
}>, {
|
|
106
|
+
fillHoles: boolean;
|
|
107
|
+
locked: boolean;
|
|
108
|
+
size: string | number;
|
|
109
|
+
}, {}, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>;
|
|
110
|
+
/** `app.use(BadgecraftPlugin)` registers `<Badgecraft>` globally. */
|
|
111
|
+
declare const BadgecraftPlugin: {
|
|
112
|
+
install(app: App): void;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
export { BadgeMode, BadgeOptions, Badgecraft, BadgecraftPlugin, EnvironmentInput, FrameOptions, FrameShape, IdleAnimation, Interaction, MaterialInput, ReliefOptions, ToneMapping, Badgecraft as default };
|
package/dist/vue.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { cssSize, pickOptions } from './chunk-3OMN635U.js';
|
|
2
|
+
import { Badgecraft } from './chunk-RRYBMB25.js';
|
|
3
|
+
import { defineComponent, ref, onMounted, watch, onBeforeUnmount, h } from 'vue';
|
|
4
|
+
|
|
5
|
+
var material = [String, Object];
|
|
6
|
+
var bool = { type: Boolean, default: void 0 };
|
|
7
|
+
var Badgecraft2 = defineComponent({
|
|
8
|
+
name: "Badgecraft",
|
|
9
|
+
props: {
|
|
10
|
+
svg: { type: [String, Object], required: true },
|
|
11
|
+
mode: String,
|
|
12
|
+
metal: material,
|
|
13
|
+
enamel: material,
|
|
14
|
+
base: material,
|
|
15
|
+
accent: material,
|
|
16
|
+
color: String,
|
|
17
|
+
frame: [String, Object],
|
|
18
|
+
rim: Number,
|
|
19
|
+
outline: [Number, String],
|
|
20
|
+
fillHoles: bool,
|
|
21
|
+
relief: Object,
|
|
22
|
+
environment: [String, Object],
|
|
23
|
+
environmentRotation: Number,
|
|
24
|
+
exposure: Number,
|
|
25
|
+
toneMapping: String,
|
|
26
|
+
interaction: String,
|
|
27
|
+
tiltMax: Number,
|
|
28
|
+
pose: Object,
|
|
29
|
+
idle: String,
|
|
30
|
+
locked: bool,
|
|
31
|
+
lockedColor: String,
|
|
32
|
+
shadow: Number,
|
|
33
|
+
quality: [Number, String],
|
|
34
|
+
padding: Number,
|
|
35
|
+
/** Width and height (number = px). Default 160. */
|
|
36
|
+
size: { type: [Number, String], default: 160 },
|
|
37
|
+
/** Accessible label. */
|
|
38
|
+
alt: String
|
|
39
|
+
},
|
|
40
|
+
emits: {
|
|
41
|
+
ready: () => true,
|
|
42
|
+
error: (error) => error instanceof Error
|
|
43
|
+
},
|
|
44
|
+
setup(props, { emit, expose }) {
|
|
45
|
+
const host = ref();
|
|
46
|
+
let badge = null;
|
|
47
|
+
const options = () => ({
|
|
48
|
+
...pickOptions(props),
|
|
49
|
+
onReady: () => emit("ready"),
|
|
50
|
+
onError: (error) => emit("error", error)
|
|
51
|
+
});
|
|
52
|
+
onMounted(() => {
|
|
53
|
+
badge = new Badgecraft(host.value, options());
|
|
54
|
+
});
|
|
55
|
+
watch(options, (next) => badge?.setOptions(next), { deep: true });
|
|
56
|
+
onBeforeUnmount(() => {
|
|
57
|
+
badge?.destroy();
|
|
58
|
+
badge = null;
|
|
59
|
+
});
|
|
60
|
+
expose({
|
|
61
|
+
spin: (turns) => badge?.spin(turns),
|
|
62
|
+
glint: () => badge?.glint(),
|
|
63
|
+
toDataURL: (type, quality) => badge?.toDataURL(type, quality) ?? "",
|
|
64
|
+
toBlob: (type, quality) => badge?.toBlob(type, quality) ?? Promise.resolve(null),
|
|
65
|
+
instance: () => badge
|
|
66
|
+
});
|
|
67
|
+
return () => {
|
|
68
|
+
const dim = cssSize(props.size);
|
|
69
|
+
return h("div", {
|
|
70
|
+
ref: host,
|
|
71
|
+
role: "img",
|
|
72
|
+
"aria-label": props.alt,
|
|
73
|
+
style: { display: "inline-block", position: "relative", width: dim, height: dim }
|
|
74
|
+
});
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
var BadgecraftPlugin = {
|
|
79
|
+
install(app) {
|
|
80
|
+
app.component("Badgecraft", Badgecraft2);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
var vue_default = Badgecraft2;
|
|
84
|
+
|
|
85
|
+
export { Badgecraft2 as Badgecraft, BadgecraftPlugin, vue_default as default };
|
|
86
|
+
//# sourceMappingURL=vue.js.map
|
|
87
|
+
//# sourceMappingURL=vue.js.map
|
package/dist/vue.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/vue/index.ts"],"names":["Badgecraft"],"mappings":"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","file":"vue.js","sourcesContent":["import {\n defineComponent,\n h,\n onBeforeUnmount,\n onMounted,\n ref,\n watch,\n type App,\n type PropType,\n} from 'vue'\nimport { Badgecraft as Badge } from '../badge'\nimport { cssSize, pickOptions } from '../option-keys'\nimport type {\n BadgeMode,\n BadgeOptions,\n EnvironmentInput,\n FrameOptions,\n FrameShape,\n IdleAnimation,\n Interaction,\n MaterialInput,\n ReliefOptions,\n ToneMapping,\n} from '../types'\n\nexport type * from '../types'\n\nconst material = [String, Object] as PropType<MaterialInput>\n// Booleans default to undefined so the library's own defaults apply.\nconst bool = { type: Boolean, default: undefined }\n\n/**\n * Vue 3 binding for badgecraft.\n *\n * ```vue\n * <Badgecraft svg=\"/award.svg\" metal=\"gold\" :size=\"200\" @ready=\"...\" />\n * ```\n */\nexport const Badgecraft = defineComponent({\n name: 'Badgecraft',\n props: {\n svg: { type: [String, Object] as PropType<string | SVGElement>, required: true },\n mode: String as PropType<BadgeMode>,\n metal: material,\n enamel: material,\n base: material,\n accent: material,\n color: String,\n frame: [String, Object] as PropType<FrameShape | FrameOptions | null>,\n rim: Number,\n outline: [Number, String] as PropType<number | 'auto'>,\n fillHoles: bool,\n relief: Object as PropType<ReliefOptions>,\n environment: [String, Object] as PropType<EnvironmentInput>,\n environmentRotation: Number,\n exposure: Number,\n toneMapping: String as PropType<ToneMapping>,\n interaction: String as PropType<Interaction>,\n tiltMax: Number,\n pose: Object as PropType<BadgeOptions['pose']>,\n idle: String as PropType<IdleAnimation>,\n locked: bool,\n lockedColor: String,\n shadow: Number,\n quality: [Number, String] as PropType<number | 'auto'>,\n padding: Number,\n /** Width and height (number = px). Default 160. */\n size: { type: [Number, String], default: 160 },\n /** Accessible label. */\n alt: String,\n },\n emits: {\n ready: () => true,\n error: (error: Error) => error instanceof Error,\n },\n setup(props, { emit, expose }) {\n const host = ref<HTMLDivElement>()\n let badge: Badge | null = null\n\n const options = (): BadgeOptions => ({\n ...pickOptions(props as unknown as Record<string, unknown>),\n onReady: () => emit('ready'),\n onError: (error: Error) => emit('error', error),\n })\n\n onMounted(() => {\n badge = new Badge(host.value!, options())\n })\n watch(options, (next) => badge?.setOptions(next), { deep: true })\n onBeforeUnmount(() => {\n badge?.destroy()\n badge = null\n })\n\n expose({\n spin: (turns?: number) => badge?.spin(turns),\n glint: () => badge?.glint(),\n toDataURL: (type?: string, quality?: number) => badge?.toDataURL(type, quality) ?? '',\n toBlob: (type?: string, quality?: number) => badge?.toBlob(type, quality) ?? Promise.resolve(null),\n instance: () => badge,\n })\n\n return () => {\n const dim = cssSize(props.size)\n return h('div', {\n ref: host,\n role: 'img',\n 'aria-label': props.alt,\n style: { display: 'inline-block', position: 'relative', width: dim, height: dim },\n })\n }\n },\n})\n\n/** `app.use(BadgecraftPlugin)` registers `<Badgecraft>` globally. */\nexport const BadgecraftPlugin = {\n install(app: App) {\n app.component('Badgecraft', Badgecraft)\n },\n}\n\nexport default Badgecraft\n"]}
|
package/docs/cli.md
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# CLI, local Studio, and Node rendering
|
|
2
|
+
|
|
3
|
+
## Interactive web Studio
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
npx badgecraft studio
|
|
7
|
+
npx badgecraft studio --open
|
|
8
|
+
npx badgecraft studio --port 8080
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Open the printed URL to upload, drop, or paste an SVG, adjust materials and
|
|
12
|
+
lighting, export PNG images, or copy code for a website. `--open` opens Google
|
|
13
|
+
Chrome; you can also open the URL yourself in a WebGL2-capable browser. Node.js
|
|
14
|
+
22+ is required. No source checkout or development dependencies are needed.
|
|
15
|
+
|
|
16
|
+
The app is bundled in the package and works offline after installation. SVGs
|
|
17
|
+
are read by the browser; the local server never receives them or serves your
|
|
18
|
+
working directory. Valid drafts are saved in that browser's local storage.
|
|
19
|
+
The hosted version is at https://parthjadhav.github.io/badgecraft/studio.html.
|
|
20
|
+
|
|
21
|
+
The server listens on `127.0.0.1`, port 5199. If the default is occupied, it picks
|
|
22
|
+
an available port and prints the URL. An explicitly requested occupied port
|
|
23
|
+
fails with an explanation. Use `--port 0` to always pick an available port.
|
|
24
|
+
Press Ctrl+C to stop. Use a consistent port to keep using the same saved draft
|
|
25
|
+
(browser storage is tied to the URL's origin).
|
|
26
|
+
|
|
27
|
+
Studio accepts SVGs up to 1 MiB and exports PNG at 512, 1024, or 2048 pixels.
|
|
28
|
+
For WebP/JPEG or automation, use image conversion below.
|
|
29
|
+
|
|
30
|
+
## Image conversion
|
|
31
|
+
|
|
32
|
+
The CLI turns a self-contained SVG into a static badge image with the same
|
|
33
|
+
WebGL2 renderer as the browser library. It runs locally; it does not upload
|
|
34
|
+
artwork or fetch remote assets.
|
|
35
|
+
|
|
36
|
+
## Install
|
|
37
|
+
|
|
38
|
+
Use Node.js 22 or newer and install [Google Chrome](https://www.google.com/chrome/).
|
|
39
|
+
Chrome is required only for image exports from Node or the CLI. Browser components
|
|
40
|
+
use the visitor's WebGL2 browser and do not need a local Chrome installation.
|
|
41
|
+
|
|
42
|
+
```sh
|
|
43
|
+
# Run without a global installation
|
|
44
|
+
npx badgecraft icon.svg --output badge.png
|
|
45
|
+
|
|
46
|
+
# Or install globally
|
|
47
|
+
npm install -g badgecraft
|
|
48
|
+
badgecraft icon.svg --output badge.png
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
`playwright-core` controls your installed Chrome; installing this package does
|
|
52
|
+
not download a browser or run an installation script. macOS, Windows, and Linux
|
|
53
|
+
with Chrome are supported. On a minimal Linux machine, install Chrome's system
|
|
54
|
+
libraries too. `npx playwright-core install chrome` is an alternative Chrome
|
|
55
|
+
installer and may need administrator access.
|
|
56
|
+
|
|
57
|
+
If Chrome is in a nonstandard location, pass `--chrome-path /path/to/chrome` or
|
|
58
|
+
set `BADGECRAFT_CHROME_PATH`. Chrome runs headlessly with software WebGL, so
|
|
59
|
+
no display server or dedicated GPU is needed. In containers where Chrome's
|
|
60
|
+
sandbox cannot run, configure the container for Chrome sandboxing or explicitly
|
|
61
|
+
pass `--no-sandbox`. Sandbox disabling is never the default.
|
|
62
|
+
|
|
63
|
+
## Examples
|
|
64
|
+
|
|
65
|
+
```sh
|
|
66
|
+
# Creates icon.badge.png next to the SVG
|
|
67
|
+
badgecraft icon.svg
|
|
68
|
+
|
|
69
|
+
# A silver badge on a hexagonal backplate
|
|
70
|
+
badgecraft icon.svg -o silver.png --metal silver --frame hexagon --size 1024
|
|
71
|
+
|
|
72
|
+
# Copper under warm lighting
|
|
73
|
+
badgecraft icon.svg -o copper.webp --metal copper --environment sunset
|
|
74
|
+
|
|
75
|
+
# Opaque JPEG with a custom background and camera pose
|
|
76
|
+
badgecraft icon.svg -o badge.jpg --background '#17191e' --yaw=-18 --pitch=-10
|
|
77
|
+
|
|
78
|
+
# Pipe SVG in, pipe PNG out (diagnostics go to stderr)
|
|
79
|
+
cat icon.svg | badgecraft - --output - > badge.png
|
|
80
|
+
|
|
81
|
+
# Discover all 23 materials or view all options
|
|
82
|
+
badgecraft --list-presets
|
|
83
|
+
badgecraft --help
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Use `--flag=-10` for negative numeric values. Values with spaces must be quoted.
|
|
87
|
+
The output extension selects PNG, WebP, or JPEG; `--format` selects the format
|
|
88
|
+
when stdout or a filename without an extension is used. `--format` and a filename
|
|
89
|
+
extension must agree. PNG and WebP have transparent backgrounds by default.
|
|
90
|
+
JPEG always has an opaque background (white unless customized).
|
|
91
|
+
|
|
92
|
+
Existing files are protected unless `--force` is supplied. Parent directories
|
|
93
|
+
are created automatically. A conversion error exits with status 1, writes an
|
|
94
|
+
explanation to stderr, and does not create a new output file.
|
|
95
|
+
|
|
96
|
+
Image size is square and limited to 16–2048 pixels. `--quality` controls the relief
|
|
97
|
+
map resolution (64–2048); `--image-quality` controls WebP/JPEG compression (0–1).
|
|
98
|
+
The render uses a fixed pose with animation and pointer interaction disabled.
|
|
99
|
+
Software WebGL keeps rendering portable; byte-identical output across different
|
|
100
|
+
Chrome versions and operating systems is not guaranteed.
|
|
101
|
+
|
|
102
|
+
## Input and output boundaries
|
|
103
|
+
|
|
104
|
+
Input must be an SVG file path or SVG markup on stdin, up to 10 MiB. Paths, fills,
|
|
105
|
+
strokes, gradients, masks, clip paths, inline styles, and embedded images are
|
|
106
|
+
supported by the underlying renderer. Embed images as data URLs and convert text
|
|
107
|
+
to paths for consistent results on machines with different installed fonts.
|
|
108
|
+
|
|
109
|
+
SVG scripts, event handlers, embedded HTML, and animations are removed. External
|
|
110
|
+
resources are blocked, including CSS imports, fonts, images, and image-based
|
|
111
|
+
lighting. CLI lighting accepts the five built-in environments. Conversions work
|
|
112
|
+
offline once the npm package and Chrome are installed.
|
|
113
|
+
|
|
114
|
+
The result is a raster image, not an editable vector SVG. Use the browser exports
|
|
115
|
+
for interactive badges. A missing WebGL2 context fails explicitly instead of
|
|
116
|
+
silently exporting the original flat SVG.
|
|
117
|
+
|
|
118
|
+
## Node API
|
|
119
|
+
|
|
120
|
+
```js
|
|
121
|
+
import { readFile, writeFile } from 'node:fs/promises'
|
|
122
|
+
import { renderBadge } from 'badgecraft/node'
|
|
123
|
+
|
|
124
|
+
const svg = await readFile('icon.svg', 'utf8')
|
|
125
|
+
const image = await renderBadge(svg, {
|
|
126
|
+
size: 1024,
|
|
127
|
+
format: 'png',
|
|
128
|
+
metal: { preset: 'gold', roughness: 0.25 },
|
|
129
|
+
frame: { shape: 'circle', padding: 0.2 },
|
|
130
|
+
relief: { depth: 0.06 },
|
|
131
|
+
environment: 'studio',
|
|
132
|
+
pose: { pitch: -5, yaw: -12 },
|
|
133
|
+
})
|
|
134
|
+
await writeFile('badge.png', image)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
`renderBadge(svg, options)` returns a `Promise<Buffer>`. Its `RenderOptions` type
|
|
138
|
+
includes the library's static badge options, plus `size`, `format`, `background`,
|
|
139
|
+
`imageQuality`, `timeout`, `executablePath`, and `sandbox`. The SVG argument must
|
|
140
|
+
be markup, not a URL. The Node entry is ESM; CommonJS callers can use
|
|
141
|
+
`await import('badgecraft/node')`. Import only the browser entry in frontend
|
|
142
|
+
bundles; Chrome and Node code are isolated in `badgecraft/node`.
|
|
143
|
+
|
|
144
|
+
Each call creates and closes an isolated Chrome process. For a batch, process
|
|
145
|
+
files sequentially or keep concurrency low to bound memory use. `timeout`
|
|
146
|
+
defaults to 60 seconds for startup and separately 60 seconds for rendering.
|
package/docs/gallery.jpg
ADDED
|
Binary file
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# Profiling and performance comparisons
|
|
2
|
+
|
|
3
|
+
Use `pnpm perf:profile artifacts/profile` for a current-source run. The harness
|
|
4
|
+
uses installed Google Chrome and saves raw samples, JSON summaries, and Chrome
|
|
5
|
+
CPU profiles. Open `.cpuprofile` files in Chrome DevTools' Performance panel.
|
|
6
|
+
It is development-only: published library code contains no profiling hooks.
|
|
7
|
+
|
|
8
|
+
## Measured change: mesh clipping fast paths
|
|
9
|
+
|
|
10
|
+
The first diagnostic profiles identified main-thread polygon clipping and
|
|
11
|
+
allocation/GC as a startup bottleneck: seven gallery mesh builds plus uploads
|
|
12
|
+
occupied 65.7 ms, with only 0.7 ms in texture upload calls. Cells wholly outside
|
|
13
|
+
the silhouette now skip clipping; strictly positive interior cells emit the
|
|
14
|
+
same two triangles directly. Contour cells, zero-distance vertices, and map
|
|
15
|
+
borders retain the existing clipping/wall construction path.
|
|
16
|
+
|
|
17
|
+
A subsequent diagnostic run measured 33.4 ms for those same seven mesh builds
|
|
18
|
+
plus uploads (49% less synchronous wall time). These are diagnostic observations,
|
|
19
|
+
not six-run component benchmarks. Uninstrumented paired timings confirm the
|
|
20
|
+
small-gallery startup benefit in **all six pairs**, ranging from 6.6% to 13.4%.
|
|
21
|
+
|
|
22
|
+
| Workload | Ready p50 before | Ready p50 after | Synchronized redraw p50 before | After |
|
|
23
|
+
| --- | ---: | ---: | ---: | ---: |
|
|
24
|
+
| One 320px badge | 122.7 ms | 117.3 ms | 3.28 ms | 3.53 ms |
|
|
25
|
+
| Nine 110px badges | 165.4 ms | 148.9 ms | 9.48 ms | 9.68 ms |
|
|
26
|
+
| 57 110px badges | 168.7 ms | 163.1 ms | 33.07 ms | 32.72 ms |
|
|
27
|
+
| Nine 640px badges | 703.9 ms | 708.4 ms | 13.13 ms | 12.87 ms |
|
|
28
|
+
|
|
29
|
+
These are nearest-rank p50 values across six runs, on Chrome 154.0.8037.57,
|
|
30
|
+
ANGLE Metal, Apple M5 Pro. Redraw figures summarize each run's p50. No redraw
|
|
31
|
+
speedup is claimed: only mesh construction changed. The large workload remains
|
|
32
|
+
worker-bake dominated and showed no reliable readiness benefit. All outliers
|
|
33
|
+
are retained, including candidate startup times of 210.4 ms for 57 badges and
|
|
34
|
+
1186.0 ms for the large workload in the first pair. Inspect the raw paired data
|
|
35
|
+
rather than assuming that a single summary quantile proves a universal gain.
|
|
36
|
+
|
|
37
|
+
Evidence is saved locally in `artifacts/profile-ab/report.json`, with CPU
|
|
38
|
+
profiles and event/query records in the same directory. Discovery captures are
|
|
39
|
+
in `artifacts/profile-discovery` and `artifacts/profile-instrumented`; the former
|
|
40
|
+
revealed the emulated-DPR gap and predates the worker execution timer. The full
|
|
41
|
+
source reference is `artifacts/profile-baseline/src`.
|
|
42
|
+
|
|
43
|
+
All **108 raw RGBA captures are byte-for-byte identical** between baseline and
|
|
44
|
+
candidate at the verified backing dimensions. Six reference mesh cases also
|
|
45
|
+
preserve every vertex/index byte and the face/wall split. All 23 unit tests,
|
|
46
|
+
TypeScript checks, package builds, and the standalone Chrome interaction suite
|
|
47
|
+
pass. Captures are in `artifacts/mesh-quality-before` and
|
|
48
|
+
`artifacts/mesh-quality-after`.
|
|
49
|
+
|
|
50
|
+
## Repeatable A/B measurement
|
|
51
|
+
|
|
52
|
+
Save a complete source snapshot **before** editing library code:
|
|
53
|
+
|
|
54
|
+
```sh
|
|
55
|
+
pnpm build:worker
|
|
56
|
+
mkdir -p artifacts/baseline
|
|
57
|
+
cp -R src artifacts/baseline/
|
|
58
|
+
# Make the optimization, then:
|
|
59
|
+
pnpm perf:profile artifacts/comparison --baseline artifacts/baseline
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
The snapshot includes the generated worker bundle. Each server resolves all
|
|
63
|
+
library source modules from its own revision; the same fixture drives both.
|
|
64
|
+
Do not edit either source tree while a run is active. `PROFILE_ROUNDS` overrides
|
|
65
|
+
the default six rounds; use one only for harness smoke tests, not speed claims.
|
|
66
|
+
Run browser workloads sequentially and avoid other GPU-intensive work.
|
|
67
|
+
|
|
68
|
+
The harness warms module transforms and shader disk caches for both variants,
|
|
69
|
+
then alternates baseline/candidate order every round. Every measured case gets
|
|
70
|
+
a fresh browser context and fresh library/worker caches. Workloads are one
|
|
71
|
+
320px badge, nine 110px badges, 57 110px badges, and nine 640px badges. The many-
|
|
72
|
+
badge case repeats the nine gallery styles (including locked badges), so it is
|
|
73
|
+
not identical to the older benchmark's 48 added badges. Actual canvas dimensions
|
|
74
|
+
and map resolutions are recorded and checked, along with GPU identity.
|
|
75
|
+
|
|
76
|
+
Readiness is timed in-page from host creation through all `ready` promises;
|
|
77
|
+
it excludes module download, Vite transformation, and automation polling.
|
|
78
|
+
It means the library has baked and submitted the first image, not that a physical
|
|
79
|
+
display has presented it. It is not a cold network-navigation measurement.
|
|
80
|
+
|
|
81
|
+
Each uninstrumented workload records 60 frames after 15 warm-up frames:
|
|
82
|
+
|
|
83
|
+
- `submitMs`: synchronous wall time for all badge draw calls, including canvas
|
|
84
|
+
copies and any implicit driver/browser waits. It is not GPU execution time.
|
|
85
|
+
- `rafIntervalMs`: actual animation callback cadence, including browser scheduling.
|
|
86
|
+
This is measured separately from draw submission and is not inferred from it.
|
|
87
|
+
- A separate synchronized pass records 45 frames after five warm-ups, with draw
|
|
88
|
+
time, a one-pixel GPU readback wait, and their total stored separately. This
|
|
89
|
+
stress measurement is not presented as interactive FPS and does not include
|
|
90
|
+
physical display presentation or guarantee completion of 2D compositing.
|
|
91
|
+
|
|
92
|
+
Reports retain every sample, nearest-rank per-run quantiles, across-run ranges, and paired
|
|
93
|
+
percentage changes. Negative changes mean improvement. Six rounds do not prove
|
|
94
|
+
universal speedups; inspect outliers and the individual paired changes. Metadata
|
|
95
|
+
includes Chrome version, OS/CPU, workload settings, and SHA-256 hashes of both
|
|
96
|
+
source trees. Different machines or browser builds require a new paired run.
|
|
97
|
+
|
|
98
|
+
## Diagnostic profiling
|
|
99
|
+
|
|
100
|
+
Diagnostic runs happen **after** timing runs. They use method wrappers, a
|
|
101
|
+
main-thread CDP sampling profiler, and asynchronous
|
|
102
|
+
[WebGL timer queries](https://registry.khronos.org/webgl/extensions/EXT_disjoint_timer_query_webgl2/).
|
|
103
|
+
Their numbers identify bottlenecks; the extra queries/wrappers can change GPU
|
|
104
|
+
batching and must not be used as the end-to-end speed comparison.
|
|
105
|
+
|
|
106
|
+
Recorded phases include renderer initialization, environment preparation,
|
|
107
|
+
mesh construction plus upload, texture upload, material uniforms, canvas copies,
|
|
108
|
+
and draw submission. Durations ending in `cpuWall` measure synchronous API wall
|
|
109
|
+
time, which can include waiting. Nested phases overlap: do not sum them as if
|
|
110
|
+
they were exclusive CPU categories. Environment-ready time is submission wall
|
|
111
|
+
time, not completion of environment filtering on the GPU.
|
|
112
|
+
|
|
113
|
+
A diagnostic-only worker shim records synchronous bake execution inside each
|
|
114
|
+
worker. A listener registered before the library's completion handler separately
|
|
115
|
+
records round-trip time, including queueing, transport and delivery. The shim
|
|
116
|
+
adds startup overhead, so production readiness comes from uninstrumented runs.
|
|
117
|
+
Worker jobs overlap; summing round-trip times does not give page startup time.
|
|
118
|
+
Main-thread CPU profiles do not represent worker CPU stacks.
|
|
119
|
+
|
|
120
|
+
GPU query results distinguish environment, relief, and shadow passes. Results
|
|
121
|
+
are read only after yielding; disjoint results are discarded and unsupported or
|
|
122
|
+
timed-out queries are reported explicitly. Query objects are deleted. These
|
|
123
|
+
queries cover WebGL passes, not Canvas2D copying/compositing. CDP metrics also
|
|
124
|
+
record heap and layout data; a single heap snapshot metric is not a leak test.
|
|
125
|
+
Long tasks are assigned to the phase in which they started.
|
|
126
|
+
|
|
127
|
+
## Quality protection
|
|
128
|
+
|
|
129
|
+
```sh
|
|
130
|
+
BADGE_BASELINE_DIR=artifacts/baseline pnpm test:quality artifacts/reference
|
|
131
|
+
pnpm test:quality artifacts/current artifacts/reference
|
|
132
|
+
pnpm test
|
|
133
|
+
pnpm typecheck
|
|
134
|
+
pnpm test:browser
|
|
135
|
+
pnpm build
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
The quality harness supports complete `src/` snapshots as well as the older
|
|
139
|
+
four-file rendering snapshot. It captures nine badge styles at actual backing
|
|
140
|
+
sizes of 110, 400, and 640 pixels from four poses (108 images). It checks backing
|
|
141
|
+
dimensions rather than trusting emulated DPR: Chrome's emulated 2× DPR did not
|
|
142
|
+
increase `devicePixelContentBoxSize` in the original capture harness. These
|
|
143
|
+
larger backing sizes exercise high-resolution rendering without claiming to
|
|
144
|
+
test native display scaling behavior.
|
|
145
|
+
|
|
146
|
+
Pixel thresholds, GPU/browser compatibility, blank-image checks and failures
|
|
147
|
+
are recorded by `scripts/quality-check.mjs`. PNGs remain available for visual
|
|
148
|
+
inspection. Mesh compatibility tests additionally hash complete vertex/index
|
|
149
|
+
buffers from the pre-optimization implementation, including contours on grid
|
|
150
|
+
vertices, holes, islands, map boundaries, and a dense mesh. Never regenerate
|
|
151
|
+
those expected values merely to silence a failed optimization test.
|