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.
Files changed (73) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/LICENSE +21 -0
  3. package/README.md +406 -0
  4. package/cli/index.mjs +143 -0
  5. package/cli/render.d.ts +20 -0
  6. package/cli/render.mjs +87 -0
  7. package/cli/studio.mjs +98 -0
  8. package/dist/badge-B-g_9WFj.d.ts +79 -0
  9. package/dist/badge-CCjUxNIG.d.cts +79 -0
  10. package/dist/chunk-3OMN635U.js +44 -0
  11. package/dist/chunk-3OMN635U.js.map +1 -0
  12. package/dist/chunk-BRT5S5PF.cjs +3418 -0
  13. package/dist/chunk-BRT5S5PF.cjs.map +1 -0
  14. package/dist/chunk-EBDSIGZR.cjs +48 -0
  15. package/dist/chunk-EBDSIGZR.cjs.map +1 -0
  16. package/dist/chunk-RRYBMB25.js +3410 -0
  17. package/dist/chunk-RRYBMB25.js.map +1 -0
  18. package/dist/cli-browser.js +526 -0
  19. package/dist/element.cjs +176 -0
  20. package/dist/element.cjs.map +1 -0
  21. package/dist/element.d.cts +31 -0
  22. package/dist/element.d.ts +31 -0
  23. package/dist/element.js +174 -0
  24. package/dist/element.js.map +1 -0
  25. package/dist/index.cjs +47 -0
  26. package/dist/index.cjs.map +1 -0
  27. package/dist/index.d.cts +58 -0
  28. package/dist/index.d.ts +58 -0
  29. package/dist/index.js +17 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/react.cjs +60 -0
  32. package/dist/react.cjs.map +1 -0
  33. package/dist/react.d.cts +29 -0
  34. package/dist/react.d.ts +29 -0
  35. package/dist/react.js +55 -0
  36. package/dist/react.js.map +1 -0
  37. package/dist/studio/assets/badge-9Zq6e9Ht.js +524 -0
  38. package/dist/studio/assets/base-ItCYnbux.css +1 -0
  39. package/dist/studio/assets/base-LFa9cTPp.js +1 -0
  40. package/dist/studio/assets/element-CCTxyf61.js +1 -0
  41. package/dist/studio/assets/index-BVuSTaz-.css +1 -0
  42. package/dist/studio/assets/index-CTTcx2pV.js +28 -0
  43. package/dist/studio/assets/option-keys-CKi71y0n.js +1 -0
  44. package/dist/studio/assets/react-Cq5Erey2.js +9 -0
  45. package/dist/studio/assets/react-DyJFYpWJ.js +1 -0
  46. package/dist/studio/assets/studio-C6plMNOe.css +1 -0
  47. package/dist/studio/assets/studio-DBcSY6tV.js +1 -0
  48. package/dist/studio/assets/style-C2sJ2Xy3.css +1 -0
  49. package/dist/studio/assets/vue-BpGwr3iF.js +1 -0
  50. package/dist/studio/element.html +31 -0
  51. package/dist/studio/env/studio.hdr +5 -0
  52. package/dist/studio/index.html +24 -0
  53. package/dist/studio/logos/apple.svg +1 -0
  54. package/dist/studio/logos/atlassian.svg +1 -0
  55. package/dist/studio/logos/google.svg +1 -0
  56. package/dist/studio/logos/meta.svg +1 -0
  57. package/dist/studio/react.html +16 -0
  58. package/dist/studio/studio.html +27 -0
  59. package/dist/studio/vue.html +15 -0
  60. package/dist/types-CHM3-ZZV.d.cts +204 -0
  61. package/dist/types-CHM3-ZZV.d.ts +204 -0
  62. package/dist/vue.cjs +93 -0
  63. package/dist/vue.cjs.map +1 -0
  64. package/dist/vue.d.cts +115 -0
  65. package/dist/vue.d.ts +115 -0
  66. package/dist/vue.js +87 -0
  67. package/dist/vue.js.map +1 -0
  68. package/docs/cli.md +146 -0
  69. package/docs/gallery.jpg +0 -0
  70. package/docs/performance.md +151 -0
  71. package/docs/render-quality.md +220 -0
  72. package/docs/studio.jpg +0 -0
  73. 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
@@ -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.
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.