react-native-nano-icons 0.0.0 → 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 (159) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +174 -0
  3. package/app.plugin.js +1 -0
  4. package/lib/commonjs/cli/build.d.ts +28 -0
  5. package/lib/commonjs/cli/build.js +83 -0
  6. package/lib/commonjs/cli/config.d.ts +9 -0
  7. package/lib/commonjs/cli/config.js +25 -0
  8. package/lib/commonjs/cli/index.d.ts +4 -0
  9. package/lib/commonjs/cli/index.js +13 -0
  10. package/lib/commonjs/cli/link.d.ts +11 -0
  11. package/lib/commonjs/cli/link.js +135 -0
  12. package/lib/commonjs/cli/logger.d.ts +27 -0
  13. package/lib/commonjs/cli/logger.js +83 -0
  14. package/lib/commonjs/index.node.js +8 -0
  15. package/lib/commonjs/plugin/src/buildFonts.d.ts +7 -0
  16. package/lib/commonjs/plugin/src/buildFonts.js +23 -0
  17. package/lib/commonjs/plugin/src/index.d.ts +5 -0
  18. package/lib/commonjs/plugin/src/index.js +43 -0
  19. package/lib/commonjs/plugin/src/types.d.ts +31 -0
  20. package/lib/commonjs/plugin/src/types.js +2 -0
  21. package/lib/commonjs/plugin/src/withNanoIconsFontLinking.d.ts +14 -0
  22. package/lib/commonjs/plugin/src/withNanoIconsFontLinking.js +92 -0
  23. package/lib/commonjs/scripts/cli.js +28 -0
  24. package/lib/commonjs/src/const/colors.d.ts +1 -0
  25. package/lib/commonjs/src/const/colors.js +153 -0
  26. package/lib/commonjs/src/core/font/compile.d.ts +9 -0
  27. package/lib/commonjs/src/core/font/compile.js +68 -0
  28. package/lib/commonjs/src/core/font/metrics.d.ts +1 -0
  29. package/lib/commonjs/src/core/font/metrics.js +33 -0
  30. package/lib/commonjs/src/core/pipeline/config.d.ts +14 -0
  31. package/lib/commonjs/src/core/pipeline/config.js +17 -0
  32. package/lib/commonjs/src/core/pipeline/index.d.ts +3 -0
  33. package/lib/commonjs/src/core/pipeline/index.js +7 -0
  34. package/lib/commonjs/src/core/pipeline/managers.d.ts +11 -0
  35. package/lib/commonjs/src/core/pipeline/managers.js +89 -0
  36. package/lib/commonjs/src/core/pipeline/run.d.ts +14 -0
  37. package/lib/commonjs/src/core/pipeline/run.js +99 -0
  38. package/lib/commonjs/src/core/svg/layers.d.ts +24 -0
  39. package/lib/commonjs/src/core/svg/layers.js +42 -0
  40. package/lib/commonjs/src/core/svg/svg_dom.d.ts +22 -0
  41. package/lib/commonjs/src/core/svg/svg_dom.js +84 -0
  42. package/lib/commonjs/src/core/svg/svg_pathops.d.ts +21 -0
  43. package/lib/commonjs/src/core/svg/svg_pathops.js +611 -0
  44. package/lib/commonjs/src/core/types.d.ts +120 -0
  45. package/lib/commonjs/src/core/types.js +2 -0
  46. package/lib/commonjs/src/utils/fingerPrint.d.ts +1 -0
  47. package/lib/commonjs/src/utils/fingerPrint.js +20 -0
  48. package/lib/commonjs/src/utils/parse.d.ts +2 -0
  49. package/lib/commonjs/src/utils/parse.js +64 -0
  50. package/lib/module/const/colors.js +153 -0
  51. package/lib/module/const/colors.js.map +1 -0
  52. package/lib/module/core/font/compile.js +70 -0
  53. package/lib/module/core/font/compile.js.map +1 -0
  54. package/lib/module/core/font/metrics.js +37 -0
  55. package/lib/module/core/font/metrics.js.map +1 -0
  56. package/lib/module/core/pipeline/config.js +20 -0
  57. package/lib/module/core/pipeline/config.js.map +1 -0
  58. package/lib/module/core/pipeline/index.js +5 -0
  59. package/lib/module/core/pipeline/index.js.map +1 -0
  60. package/lib/module/core/pipeline/managers.js +80 -0
  61. package/lib/module/core/pipeline/managers.js.map +1 -0
  62. package/lib/module/core/pipeline/run.js +114 -0
  63. package/lib/module/core/pipeline/run.js.map +1 -0
  64. package/lib/module/core/shims/pathops.py +181 -0
  65. package/lib/module/core/svg/layers.js +51 -0
  66. package/lib/module/core/svg/layers.js.map +1 -0
  67. package/lib/module/core/svg/svg_dom.js +83 -0
  68. package/lib/module/core/svg/svg_dom.js.map +1 -0
  69. package/lib/module/core/svg/svg_pathops.js +566 -0
  70. package/lib/module/core/svg/svg_pathops.js.map +1 -0
  71. package/lib/module/core/tsconfig.json +32 -0
  72. package/lib/module/core/types.js +2 -0
  73. package/lib/module/core/types.js.map +1 -0
  74. package/lib/module/createNanoIconsSet.js +84 -0
  75. package/lib/module/createNanoIconsSet.js.map +1 -0
  76. package/lib/module/index.js +5 -0
  77. package/lib/module/index.js.map +1 -0
  78. package/lib/module/index.node.js +13 -0
  79. package/lib/module/index.node.js.map +1 -0
  80. package/lib/module/package.json +1 -0
  81. package/lib/module/utils/fingerPrint.js +17 -0
  82. package/lib/module/utils/fingerPrint.js.map +1 -0
  83. package/lib/module/utils/parse.js +53 -0
  84. package/lib/module/utils/parse.js.map +1 -0
  85. package/lib/typescript/__tests__/build.unit.test.d.ts +3 -0
  86. package/lib/typescript/__tests__/build.unit.test.d.ts.map +1 -0
  87. package/lib/typescript/__tests__/clippath.e2e.test.d.ts +3 -0
  88. package/lib/typescript/__tests__/clippath.e2e.test.d.ts.map +1 -0
  89. package/lib/typescript/__tests__/fingerprint.unit.test.d.ts +3 -0
  90. package/lib/typescript/__tests__/fingerprint.unit.test.d.ts.map +1 -0
  91. package/lib/typescript/__tests__/pipeline.e2e.test.d.ts +3 -0
  92. package/lib/typescript/__tests__/pipeline.e2e.test.d.ts.map +1 -0
  93. package/lib/typescript/__tests__/placement.unit.test.d.ts +3 -0
  94. package/lib/typescript/__tests__/placement.unit.test.d.ts.map +1 -0
  95. package/lib/typescript/__tests__/svg_dom.unit.test.d.ts +3 -0
  96. package/lib/typescript/__tests__/svg_dom.unit.test.d.ts.map +1 -0
  97. package/lib/typescript/cli/build.d.ts +29 -0
  98. package/lib/typescript/cli/build.d.ts.map +1 -0
  99. package/lib/typescript/cli/logger.d.ts +28 -0
  100. package/lib/typescript/cli/logger.d.ts.map +1 -0
  101. package/lib/typescript/package.json +1 -0
  102. package/lib/typescript/src/const/colors.d.ts +2 -0
  103. package/lib/typescript/src/const/colors.d.ts.map +1 -0
  104. package/lib/typescript/src/core/font/compile.d.ts +10 -0
  105. package/lib/typescript/src/core/font/compile.d.ts.map +1 -0
  106. package/lib/typescript/src/core/font/metrics.d.ts +2 -0
  107. package/lib/typescript/src/core/font/metrics.d.ts.map +1 -0
  108. package/lib/typescript/src/core/pipeline/config.d.ts +15 -0
  109. package/lib/typescript/src/core/pipeline/config.d.ts.map +1 -0
  110. package/lib/typescript/src/core/pipeline/index.d.ts +4 -0
  111. package/lib/typescript/src/core/pipeline/index.d.ts.map +1 -0
  112. package/lib/typescript/src/core/pipeline/managers.d.ts +12 -0
  113. package/lib/typescript/src/core/pipeline/managers.d.ts.map +1 -0
  114. package/lib/typescript/src/core/pipeline/run.d.ts +15 -0
  115. package/lib/typescript/src/core/pipeline/run.d.ts.map +1 -0
  116. package/lib/typescript/src/core/svg/layers.d.ts +25 -0
  117. package/lib/typescript/src/core/svg/layers.d.ts.map +1 -0
  118. package/lib/typescript/src/core/svg/svg_dom.d.ts +23 -0
  119. package/lib/typescript/src/core/svg/svg_dom.d.ts.map +1 -0
  120. package/lib/typescript/src/core/svg/svg_pathops.d.ts +22 -0
  121. package/lib/typescript/src/core/svg/svg_pathops.d.ts.map +1 -0
  122. package/lib/typescript/src/core/types.d.ts +121 -0
  123. package/lib/typescript/src/core/types.d.ts.map +1 -0
  124. package/lib/typescript/src/createNanoIconsSet.d.ts +19 -0
  125. package/lib/typescript/src/createNanoIconsSet.d.ts.map +1 -0
  126. package/lib/typescript/src/index.d.ts +3 -0
  127. package/lib/typescript/src/index.d.ts.map +1 -0
  128. package/lib/typescript/src/index.node.d.ts +2 -0
  129. package/lib/typescript/src/index.node.d.ts.map +1 -0
  130. package/lib/typescript/src/utils/fingerPrint.d.ts +2 -0
  131. package/lib/typescript/src/utils/fingerPrint.d.ts.map +1 -0
  132. package/lib/typescript/src/utils/parse.d.ts +3 -0
  133. package/lib/typescript/src/utils/parse.d.ts.map +1 -0
  134. package/package.json +160 -1
  135. package/plugin/src/buildFonts.ts +29 -0
  136. package/plugin/src/index.ts +68 -0
  137. package/plugin/src/types.ts +33 -0
  138. package/plugin/src/withNanoIconsFontLinking.ts +119 -0
  139. package/plugin/tsconfig.json +9 -0
  140. package/scripts/cli.ts +34 -0
  141. package/scripts/tsconfig.json +25 -0
  142. package/src/const/colors.ts +150 -0
  143. package/src/core/font/compile.ts +96 -0
  144. package/src/core/font/metrics.ts +44 -0
  145. package/src/core/pipeline/config.ts +24 -0
  146. package/src/core/pipeline/index.ts +3 -0
  147. package/src/core/pipeline/managers.ts +114 -0
  148. package/src/core/pipeline/run.ts +151 -0
  149. package/src/core/shims/pathops.py +181 -0
  150. package/src/core/svg/layers.ts +66 -0
  151. package/src/core/svg/svg_dom.ts +99 -0
  152. package/src/core/svg/svg_pathops.ts +796 -0
  153. package/src/core/tsconfig.json +32 -0
  154. package/src/core/types.ts +138 -0
  155. package/src/createNanoIconsSet.tsx +131 -0
  156. package/src/index.node.ts +14 -0
  157. package/src/index.ts +7 -0
  158. package/src/utils/fingerPrint.ts +20 -0
  159. package/src/utils/parse.ts +67 -0
@@ -0,0 +1,32 @@
1
+ {
2
+ "compilerOptions": {
3
+ "rootDir": "../..",
4
+ "outDir": "../../lib/commonjs",
5
+
6
+ "target": "ES2022",
7
+ "module": "NodeNext",
8
+ "moduleResolution": "NodeNext",
9
+
10
+ "lib": ["ES2022"],
11
+ "types": ["node"],
12
+
13
+ "esModuleInterop": true,
14
+ "resolveJsonModule": true,
15
+ "skipLibCheck": true,
16
+
17
+ "noEmit": false,
18
+ "declaration": true,
19
+ "declarationMap": false,
20
+
21
+ "sourceMap": false,
22
+ "inlineSources": false,
23
+
24
+ "strict": true,
25
+ "noUncheckedIndexedAccess": true
26
+ },
27
+ "include": [
28
+ "./pipeline/**/*.ts",
29
+ "./font/**/*.ts",
30
+ "./svg/**/*.ts"
31
+ ]
32
+ }
@@ -0,0 +1,138 @@
1
+ export type NanoLogger = {
2
+ start: (msg: string) => void;
3
+ update: (msg: string) => void;
4
+ succeed: (msg: string) => void;
5
+ fail: (msg: string) => void;
6
+ /** Only printed when level is 'verbose'. */
7
+ info: (msg: string) => void;
8
+ warn: (msg: string) => void;
9
+ };
10
+
11
+ export type Point = readonly [number, number];
12
+
13
+ export type Cmd = readonly number[];
14
+
15
+ export type VerbMap = {
16
+ MOVE: number;
17
+ LINE: number;
18
+ QUAD: number;
19
+ CONIC: number;
20
+ CUBIC: number;
21
+ CLOSE: number;
22
+ };
23
+
24
+ export type WrappedPath = {
25
+ p: PathKitPath;
26
+ meta: { moves: Point[] };
27
+ };
28
+
29
+ export interface PathKitPath {
30
+ delete?: () => void;
31
+
32
+ moveTo: (x: number, y: number) => void;
33
+ lineTo: (x: number, y: number) => void;
34
+ quadTo: (x1: number, y1: number, x2: number, y2: number) => void;
35
+ cubicTo: (
36
+ x1: number,
37
+ y1: number,
38
+ x2: number,
39
+ y2: number,
40
+ x3: number,
41
+ y3: number
42
+ ) => void;
43
+ close: () => void;
44
+
45
+ simplify: () => void;
46
+ toSVGString: () => string;
47
+ toCmds: () => Cmd[];
48
+
49
+ getBounds: () => {
50
+ fLeft: number;
51
+ fTop: number;
52
+ fRight: number;
53
+ fBottom: number;
54
+ };
55
+
56
+ getFillType?: () => number;
57
+ setFillType: (t: number) => void;
58
+
59
+ dash?: (on: number, off: number, phase: number) => void;
60
+ stroke: (opts: {
61
+ width: number;
62
+ cap: number;
63
+ join: number;
64
+ miter_limit: number;
65
+ }) => PathKitPath | null;
66
+
67
+ transform: (
68
+ a: number,
69
+ c: number,
70
+ e: number,
71
+ b: number,
72
+ d: number,
73
+ f: number,
74
+ g: number,
75
+ h: number,
76
+ i: number
77
+ ) => void;
78
+ }
79
+
80
+ export interface PathKitModule {
81
+ MOVE_VERB?: number;
82
+ LINE_VERB?: number;
83
+ QUAD_VERB?: number;
84
+ CONIC_VERB?: number;
85
+ CUBIC_VERB?: number;
86
+ CLOSE_VERB?: number;
87
+
88
+ FillType?: {
89
+ EVENODD?: number;
90
+ EVEN_ODD?: number;
91
+ WINDING?: number;
92
+ NONZERO?: number;
93
+ };
94
+ StrokeCap?: { BUTT?: number; ROUND?: number; SQUARE?: number };
95
+ StrokeJoin?: { MITER?: number; ROUND?: number; BEVEL?: number };
96
+ PathOp?: { UNION?: number; INTERSECT?: number; DIFFERENCE?: number };
97
+
98
+ NewPath: (src?: PathKitPath) => PathKitPath;
99
+ FromSVGString: (svg: string) => PathKitPath | null;
100
+ MakeFromOp: (
101
+ a: PathKitPath,
102
+ b: PathKitPath,
103
+ op: number
104
+ ) => PathKitPath | null;
105
+ }
106
+
107
+ export type PyodideModule = {
108
+ mountNodeFS: (mountpoint: string, hostPath: string) => void;
109
+ registerJsModule: (name: string, mod: unknown) => void;
110
+ loadPackage: (
111
+ pkgs: string[],
112
+ options?: {
113
+ messageCallback?: (msg: string) => void;
114
+ errorCallback?: (msg: string) => void;
115
+ }
116
+ ) => Promise<void>;
117
+ runPythonAsync: (code: string) => Promise<unknown>;
118
+ runPython: (code: string) => string;
119
+ FS: { writeFile: (path: string, data: string) => void };
120
+ globals: {
121
+ set: (key: string, value: unknown) => void;
122
+ get: (key: string) => unknown;
123
+ };
124
+ };
125
+
126
+ export type GlyphLayer = { codepoint: number; color: string };
127
+ export type GlyphEntry = { adv: number; layers: GlyphLayer[] };
128
+ export type IconsMap = Record<string, GlyphEntry>;
129
+ export type NanoGlyphMap = {
130
+ meta: {
131
+ fontFamily: string;
132
+ upm: number;
133
+ safeZone: number;
134
+ startUnicode: number;
135
+ hash?: string;
136
+ };
137
+ icons: IconsMap;
138
+ };
@@ -0,0 +1,131 @@
1
+ import { forwardRef, type ComponentRef, type Ref } from 'react';
2
+ import {
3
+ Platform,
4
+ Text,
5
+ View,
6
+ type ViewProps,
7
+ type ColorValue,
8
+ type TextProps,
9
+ useWindowDimensions,
10
+ } from 'react-native';
11
+ import type { GlyphEntry, NanoGlyphMap } from './core/types';
12
+
13
+ const DEFAULT_ICON_SIZE = 12;
14
+
15
+ type ViewRef = ComponentRef<typeof View>;
16
+
17
+ export type IconProps<Name> = TextProps & {
18
+ name: Name;
19
+ size?: number;
20
+ colorPalette?: ColorValue[];
21
+ innerRef?: Ref<ViewRef>;
22
+ };
23
+
24
+ export type IconComponent<GM extends NanoGlyphMap> = React.FC<
25
+ TextProps & {
26
+ name: keyof GM['icons'];
27
+ size?: number;
28
+ colorPalette?: ColorValue[];
29
+ innerRef?: Ref<ViewRef>;
30
+ } & React.RefAttributes<ViewRef>
31
+ >;
32
+
33
+ export function createIconSet<GM extends NanoGlyphMap>(
34
+ glyphMap: GM
35
+ ): IconComponent<GM> {
36
+ const fontBasename = glyphMap.meta.fontFamily;
37
+
38
+ const fontReference = Platform.select({
39
+ windows: `/Assets/${fontBasename}`,
40
+ android: fontBasename,
41
+ default: fontBasename,
42
+ });
43
+
44
+ const styleOverrides: TextProps['style'] = {
45
+ fontFamily: fontReference,
46
+ fontWeight: 'normal',
47
+ fontStyle: 'normal',
48
+ position: 'absolute',
49
+ includeFontPadding: false,
50
+ bottom: 0,
51
+ };
52
+
53
+ const resolveEntry = (name: keyof GM['icons']): GlyphEntry => {
54
+ return (
55
+ glyphMap.icons[name as string] ?? {
56
+ adv: glyphMap.meta.upm,
57
+ layers: [{ codepoint: 63, color: 'black' }], // "?"
58
+ }
59
+ );
60
+ };
61
+
62
+ const Icon = ({
63
+ name,
64
+ size = DEFAULT_ICON_SIZE,
65
+ colorPalette,
66
+ style,
67
+ allowFontScaling = true,
68
+ innerRef,
69
+ ...props
70
+ }: IconProps<keyof GM['icons']>) => {
71
+ const { fontScale } = useWindowDimensions();
72
+
73
+ const entry = resolveEntry(name);
74
+ const layers = entry.layers ?? [];
75
+
76
+ const scaledSize = allowFontScaling ? size * fontScale : size;
77
+ const width = (entry.adv / glyphMap.meta.upm) * scaledSize;
78
+
79
+ const containerProps: ViewProps = {
80
+ style: {
81
+ height: scaledSize,
82
+ width,
83
+ bottom: 0,
84
+ },
85
+ };
86
+
87
+ const lastPaletteColor = colorPalette?.length
88
+ ? colorPalette[colorPalette.length - 1]
89
+ : undefined;
90
+
91
+ return (
92
+ <View
93
+ nativeID={`nano-icon-container-${String(name)}`}
94
+ ref={innerRef}
95
+ {...containerProps}
96
+ >
97
+ {layers.map(({ codepoint, color: srcColor }, i) => {
98
+ const layerColor =
99
+ colorPalette?.[i] ?? lastPaletteColor ?? srcColor ?? 'black';
100
+
101
+ return (
102
+ <Text
103
+ key={`${codepoint}-${i}`}
104
+ selectable={false}
105
+ {...props}
106
+ allowFontScaling={allowFontScaling}
107
+ style={[
108
+ style,
109
+ styleOverrides,
110
+ {
111
+ fontSize: size,
112
+ color: layerColor,
113
+ },
114
+ ]}
115
+ >
116
+ {String.fromCodePoint(codepoint)}
117
+ </Text>
118
+ );
119
+ })}
120
+ </View>
121
+ );
122
+ };
123
+
124
+ const WrappedIcon = forwardRef<ViewRef, IconProps<keyof GM['icons']>>(
125
+ (props, ref) => <Icon innerRef={ref} {...props} />
126
+ );
127
+
128
+ WrappedIcon.displayName = `NanoIcon(${fontBasename})`;
129
+
130
+ return WrappedIcon;
131
+ }
@@ -0,0 +1,14 @@
1
+ type CreateIconSetFn = (...args: unknown[]) => unknown;
2
+ type ConfigPluginFn = (...args: unknown[]) => unknown;
3
+
4
+ const createNanoIconSet = (...args: unknown[]) => {
5
+ const { createIconSet } = require('./createNanoIconsSet') as {
6
+ createIconSet: CreateIconSetFn;
7
+ };
8
+ return createIconSet(...args);
9
+ };
10
+
11
+ const plugin = (require('./plugin/src/index') as { default: ConfigPluginFn })
12
+ .default;
13
+
14
+ module.exports = Object.assign(plugin, { createNanoIconSet });
package/src/index.ts ADDED
@@ -0,0 +1,7 @@
1
+ // Extension-less so Metro can resolve when example uses path alias to ../src
2
+ export {
3
+ createIconSet as createNanoIconSet,
4
+ type IconComponent,
5
+ type IconProps,
6
+ } from './createNanoIconsSet';
7
+ export type { ColorValue } from 'react-native';
@@ -0,0 +1,20 @@
1
+ const crypto = require('crypto');
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+
5
+ export function getFingerprintSync(dir: string): string {
6
+ const files = fs
7
+ .readdirSync(dir)
8
+ .filter((f: string) => f.endsWith('.svg'))
9
+ .sort();
10
+ const hash = crypto.createHash('sha256');
11
+
12
+ for (const file of files) {
13
+ const filePath = path.join(dir, file);
14
+ // Hash both path and content
15
+ hash.update(file);
16
+ hash.update(fs.readFileSync(filePath));
17
+ }
18
+
19
+ return hash.digest('hex');
20
+ }
@@ -0,0 +1,67 @@
1
+ import { CSS_NAMED_COLORS } from '../const/colors';
2
+
3
+ // e.g. u0041.svg -> 65
4
+ export function parseCodepointFromFilename(filename: string): number {
5
+ const m = /^u([0-9a-fA-F]+)\.svg$/.exec(filename);
6
+ if (!m) throw new Error(`Unexpected glyph filename: ${filename}`);
7
+ return parseInt(m[1]!, 16);
8
+ }
9
+
10
+ // e.g. #ff0000 -> [255, 0, 0, 1], rgba(255, 0, 0, 0.5) -> [255, 0, 0, 0.5], rgb(255, 0, 0) -> [255, 0, 0, 1], blue -> [0, 0, 255, 1] etc.
11
+ export function parseColor(
12
+ color: string
13
+ ): [r: number, g: number, b: number, a: number] {
14
+ const c = color.trim();
15
+
16
+ // rgba(r,g,b,a)
17
+ if (c.indexOf('rgba(') === 0) {
18
+ const inner = c.slice(5, c.indexOf(')'));
19
+ const p = inner.split(',');
20
+ return [+(p[0] ?? '0'), +(p[1] ?? '0'), +(p[2] ?? '0'), +(p[3] ?? '1')];
21
+ }
22
+
23
+ // rgb(r,g,b)
24
+ if (c.indexOf('rgb(') === 0) {
25
+ const inner = c.slice(4, c.indexOf(')'));
26
+ const p = inner.split(',');
27
+ return [+(p[0] ?? '0'), +(p[1] ?? '0'), +(p[2] ?? '0'), 1];
28
+ }
29
+
30
+ // hex — first char is '#' (charCode 35)
31
+ if (c.charCodeAt(0) === 35) {
32
+ if (c.length === 9) {
33
+ // #rrggbbaa
34
+ return [
35
+ parseInt(c.slice(1, 3), 16),
36
+ parseInt(c.slice(3, 5), 16),
37
+ parseInt(c.slice(5, 7), 16),
38
+ parseInt(c.slice(7, 9), 16) / 255,
39
+ ];
40
+ }
41
+ if (c.length === 7) {
42
+ // #rrggbb
43
+ return [
44
+ parseInt(c.slice(1, 3), 16),
45
+ parseInt(c.slice(3, 5), 16),
46
+ parseInt(c.slice(5, 7), 16),
47
+ 1,
48
+ ];
49
+ }
50
+ if (c.length === 4) {
51
+ // #rgb — expand each nibble × 17
52
+ return [
53
+ parseInt(c.slice(1, 2), 16) * 17,
54
+ parseInt(c.slice(2, 3), 16) * 17,
55
+ parseInt(c.slice(3, 4), 16) * 17,
56
+ 1,
57
+ ];
58
+ }
59
+ }
60
+
61
+ // named color lookup (O(1))
62
+ const named = CSS_NAMED_COLORS[c.toLowerCase()];
63
+ if (named !== undefined) return [named[0], named[1], named[2], 1];
64
+
65
+ // unknown — SVG default is opaque black
66
+ return [0, 0, 0, 1];
67
+ }