react-native-nano-icons 0.1.3 → 0.1.4

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.
@@ -1,5 +1,4 @@
1
1
  #import "NanoIconView.h"
2
-
3
2
  #import <CoreText/CoreText.h>
4
3
  #import <React/RCTConversions.h>
5
4
  #import <React/RCTFabricComponentsPlugins.h>
@@ -8,239 +7,280 @@
8
7
 
9
8
  using namespace facebook::react;
10
9
 
10
+ // Drawing canvas that can be shifted outside the Yoga frame.
11
+ @interface NanoIconDrawingView : UIView
12
+ @property (nonatomic, copy) void (^drawBlock)(CGContextRef, CGRect);
13
+ @end
14
+
15
+ @implementation NanoIconDrawingView
16
+ - (instancetype)initWithFrame:(CGRect)frame {
17
+ if (self = [super initWithFrame:frame]) {
18
+ self.opaque = NO;
19
+ self.backgroundColor = [UIColor clearColor];
20
+ self.userInteractionEnabled = NO;
21
+ }
22
+ return self;
23
+ }
24
+ - (void)drawRect:(CGRect)rect {
25
+ if (self.drawBlock) {
26
+ CGContextRef ctx = UIGraphicsGetCurrentContext();
27
+ if (ctx) self.drawBlock(ctx, self.bounds);
28
+ }
29
+ }
30
+ @end
31
+
11
32
  @implementation NanoIconView {
12
33
  CTFontRef _font;
13
34
  NSString *_fontFamily;
14
35
  CGFloat _fontSize;
15
36
  std::vector<CGGlyph> _glyphs;
16
37
  std::vector<uint32_t> _colors;
17
- // Cached CGColor refs — rebuilt only when colors prop changes
18
38
  std::vector<CGColorRef> _cachedCGColors;
19
- // Cached layout metrics — rebuilt only when font or bounds change
20
39
  CGFloat _fitScale;
21
40
  CGPoint _baselinePosition;
22
41
  BOOL _metricsValid;
42
+ NanoIconDrawingView *_drawingView;
23
43
  }
24
44
 
25
- - (instancetype)initWithFrame:(CGRect)frame
26
- {
45
+ - (instancetype)initWithFrame:(CGRect)frame {
27
46
  if (self = [super initWithFrame:frame]) {
28
47
  static const auto defaultProps = std::make_shared<const NanoIconViewProps>();
29
48
  _props = defaultProps;
30
49
  self.opaque = NO;
31
50
  self.backgroundColor = [UIColor clearColor];
51
+ self.clipsToBounds = NO;
52
+
32
53
  _fitScale = 1.0;
33
54
  _baselinePosition = CGPointZero;
34
- _metricsValid = NO;
55
+
56
+ _drawingView = [[NanoIconDrawingView alloc] initWithFrame:self.bounds];
57
+ __weak __typeof(self) weakSelf = self;
58
+ _drawingView.drawBlock = ^(CGContextRef context, CGRect bounds) {
59
+ [weakSelf _drawIconInContext:context bounds:bounds];
60
+ };
61
+ [self addSubview:_drawingView];
35
62
  }
36
63
  return self;
37
64
  }
38
65
 
39
- + (ComponentDescriptorProvider)componentDescriptorProvider
40
- {
66
+ + (ComponentDescriptorProvider)componentDescriptorProvider {
41
67
  return concreteComponentDescriptorProvider<NanoIconViewComponentDescriptor>();
42
68
  }
43
69
 
44
- - (void)_releaseCachedColors
45
- {
46
- for (CGColorRef c : _cachedCGColors) {
47
- CGColorRelease(c);
48
- }
49
- _cachedCGColors.clear();
50
- }
70
+ - (void)updateClippedSubviewsWithClipRect:(__unused CGRect)clipRect
71
+ relativeToView:(__unused UIView *)clipView {}
51
72
 
52
- - (void)_rebuildCachedColors
53
- {
54
- [self _releaseCachedColors];
55
- _cachedCGColors.resize(_colors.size());
56
- for (size_t i = 0; i < _colors.size(); i++) {
57
- uint32_t colorInt = _colors[i];
58
- CGFloat a = ((colorInt >> 24) & 0xFF) / 255.0;
59
- CGFloat r = ((colorInt >> 16) & 0xFF) / 255.0;
60
- CGFloat g = ((colorInt >> 8) & 0xFF) / 255.0;
61
- CGFloat b = (colorInt & 0xFF) / 255.0;
62
- _cachedCGColors[i] = CGColorCreateSRGB(r, g, b, a);
63
- }
64
- }
73
+ #pragma mark - Metrics
65
74
 
66
- - (void)_updateMetrics
67
- {
75
+ // Scale factor to fit the icon font's em square into the view height,
76
+ // and the CoreText baseline origin used for all glyph draws.
77
+ - (void)_updateMetrics {
68
78
  if (!_font) {
69
79
  _metricsValid = NO;
70
80
  return;
71
81
  }
72
-
73
82
  CGFloat ascent = CTFontGetAscent(_font);
74
83
  CGFloat descent = CTFontGetDescent(_font);
75
- CGFloat fontTotalHeight = ascent + descent;
76
- CGFloat viewHeight = self.bounds.size.height;
77
-
78
- _fitScale = (fontTotalHeight > 0) ? (viewHeight / fontTotalHeight) : 1.0;
84
+ CGFloat totalHeight = ascent + descent;
85
+ _fitScale = (totalHeight > 0) ? (self.bounds.size.height / totalHeight) : 1.0;
79
86
  _baselinePosition = CGPointMake(0, descent);
80
87
  _metricsValid = YES;
81
88
  }
82
89
 
83
- - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &)oldProps
84
- {
85
- const auto &oldViewProps = static_cast<const NanoIconViewProps &>(*_props);
86
- const auto &newViewProps = static_cast<const NanoIconViewProps &>(*props);
87
-
88
- // Recreate font if fontFamily or fontSize changed
89
- BOOL fontChanged = NO;
90
- if (oldViewProps.fontFamily != newViewProps.fontFamily || oldViewProps.fontSize != newViewProps.fontSize) {
91
- if (_font) {
92
- CFRelease(_font);
93
- _font = NULL;
90
+ // Distance from this view's bottom edge to the parent text baseline.
91
+ // Returns 0 when standalone or when the icon is taller than the text line.
92
+ - (CGFloat)_inlineBaselineOffset {
93
+ UIView *current = self.superview;
94
+ NSAttributedString *attrStr = nil;
95
+ while (current) {
96
+ if ([current respondsToSelector:@selector(attributedText)]) {
97
+ attrStr = [current performSelector:@selector(attributedText)];
98
+ if (attrStr.length > 0) break;
94
99
  }
95
- NSString *family = [NSString stringWithUTF8String:newViewProps.fontFamily.c_str()];
96
- _fontFamily = family;
97
- _fontSize = newViewProps.fontSize;
98
- _font = CTFontCreateWithName((__bridge CFStringRef)family, _fontSize, NULL);
99
- fontChanged = YES;
100
- _metricsValid = NO;
100
+ current = current.superview;
101
101
  }
102
+ if (!attrStr) return 0;
102
103
 
103
- // Update glyphs if codepoints changed or font changed
104
- BOOL codepointsChanged = fontChanged || (oldViewProps.codepoints != newViewProps.codepoints);
105
- if (codepointsChanged && _font) {
106
- const auto &codepoints = newViewProps.codepoints;
107
- _glyphs.resize(codepoints.size());
104
+ UIFont *f = [attrStr attribute:NSFontAttributeName atIndex:0 effectiveRange:nil];
105
+ if (!f) return 0;
108
106
 
109
- for (size_t i = 0; i < codepoints.size(); i++) {
110
- int32_t cp = codepoints[i];
111
- // Handle BMP and supplementary plane characters
112
- if (cp <= 0xFFFF) {
113
- UniChar ch = (UniChar)cp;
114
- CTFontGetGlyphsForCharacters(_font, &ch, &_glyphs[i], 1);
115
- } else {
116
- // Supplementary plane (private use area): use surrogate pair
117
- UniChar surrogates[2];
118
- surrogates[0] = (UniChar)(0xD800 + ((cp - 0x10000) >> 10));
119
- surrogates[1] = (UniChar)(0xDC00 + ((cp - 0x10000) & 0x3FF));
120
- CGGlyph glyphPair[2] = {0, 0};
121
- CTFontGetGlyphsForCharacters(_font, surrogates, glyphPair, 2);
122
- _glyphs[i] = glyphPair[0];
123
- }
124
- }
125
- }
107
+ NSParagraphStyle *style = [attrStr attribute:NSParagraphStyleAttributeName
108
+ atIndex:0 effectiveRange:nil];
109
+ CGFloat lineHeight = (style && style.maximumLineHeight > 0)
110
+ ? style.maximumLineHeight : f.lineHeight;
126
111
 
127
- // Update colors
128
- if (oldViewProps.colors != newViewProps.colors) {
129
- const auto &colors = newViewProps.colors;
130
- _colors.resize(colors.size());
131
- for (size_t i = 0; i < colors.size(); i++) {
132
- _colors[i] = (uint32_t)colors[i];
133
- }
134
- [self _rebuildCachedColors];
135
- }
112
+ NSNumber *bOff = [attrStr attribute:NSBaselineOffsetAttributeName
113
+ atIndex:0 effectiveRange:nil];
114
+ CGFloat baselineFromLineTop = f.ascender - (bOff ? bOff.doubleValue : 0);
136
115
 
137
- [super updateProps:props oldProps:oldProps];
138
- [self setNeedsDisplay];
116
+ CGFloat frameBottom = self.frame.origin.y + self.frame.size.height;
117
+ CGFloat posInLine = fmod(frameBottom, lineHeight);
118
+ if (posInLine < 0.01) posInLine = lineHeight;
119
+
120
+ return MAX(0, posInLine - baselineFromLineTop);
139
121
  }
140
122
 
141
- - (void)layoutSubviews
142
- {
123
+ #pragma mark - Layout
124
+
125
+ - (void)layoutSubviews {
143
126
  [super layoutSubviews];
144
- _metricsValid = NO;
145
- }
127
+ if (!_metricsValid) [self _updateMetrics];
146
128
 
147
- - (void)drawRect:(CGRect)rect
148
- {
149
- if (!_font || _glyphs.empty()) {
150
- return;
151
- }
129
+ CGFloat offset = [self _inlineBaselineOffset];
130
+ _drawingView.frame = CGRectMake(0, -offset,
131
+ self.bounds.size.width, self.bounds.size.height);
132
+ }
152
133
 
153
- CGContextRef context = UIGraphicsGetCurrentContext();
154
- if (!context) {
155
- return;
156
- }
134
+ #pragma mark - Drawing
157
135
 
158
- if (!_metricsValid) {
159
- [self _updateMetrics];
160
- }
136
+ // Render multi-color icons by drawing each color layer glyph at the same
137
+ // position. Layers stack via painter's order to compose the final icon.
138
+ - (void)_drawIconInContext:(CGContextRef)context bounds:(CGRect)bounds {
139
+ if (!_font || _glyphs.empty()) return;
140
+ if (!_metricsValid) [self _updateMetrics];
161
141
 
162
142
  CGContextSaveGState(context);
163
- // CoreText draws with y-axis pointing up; UIKit has y-axis pointing down.
164
- CGContextTranslateCTM(context, 0, self.bounds.size.height);
143
+ // Flip to CoreText coordinates (Y-up) and apply fit scale.
144
+ CGContextTranslateCTM(context, 0, bounds.size.height);
165
145
  CGContextScaleCTM(context, 1.0, -1.0);
166
146
  CGContextScaleCTM(context, _fitScale, _fitScale);
167
147
 
168
- // Batch consecutive same-color glyphs into a single CTFontDrawGlyphs call
169
148
  size_t i = 0;
170
149
  while (i < _glyphs.size()) {
171
- if (_glyphs[i] == 0) {
172
- i++;
173
- continue;
174
- }
150
+ if (_glyphs[i] == 0) { i++; continue; }
175
151
 
176
- // Determine the color for this run
177
152
  CGColorRef color = (i < _cachedCGColors.size()) ? _cachedCGColors[i] : NULL;
178
153
  if (!color) {
179
- // Fallback: opaque black
180
154
  static CGColorRef sBlack = CGColorCreateSRGB(0, 0, 0, 1);
181
155
  color = sBlack;
182
156
  }
183
157
  CGContextSetFillColorWithColor(context, color);
184
158
 
185
- // Collect consecutive glyphs with the same color
159
+ // Batch consecutive same-color glyphs.
186
160
  size_t batchStart = i;
187
161
  size_t batchCount = 0;
188
- // Use a small stack buffer for positions; heap-allocate only for very large batches
189
- CGPoint positionsBuf[16];
190
- CGGlyph glyphsBuf[16];
191
- CGPoint *positions = positionsBuf;
192
- CGGlyph *batchGlyphs = glyphsBuf;
162
+ CGPoint posBuf[16];
163
+ CGGlyph glyphBuf[16];
193
164
 
194
165
  while (i < _glyphs.size()) {
195
166
  if (_glyphs[i] == 0) { i++; continue; }
196
-
197
- CGColorRef nextColor = (i < _cachedCGColors.size()) ? _cachedCGColors[i] : NULL;
198
- // Break batch if color changes
199
- if (i > batchStart && nextColor != color) break;
200
-
167
+ CGColorRef next = (i < _cachedCGColors.size()) ? _cachedCGColors[i] : NULL;
168
+ if (i > batchStart && next != color) break;
201
169
  if (batchCount < 16) {
202
- positions[batchCount] = _baselinePosition;
203
- batchGlyphs[batchCount] = _glyphs[i];
170
+ posBuf[batchCount] = _baselinePosition;
171
+ glyphBuf[batchCount] = _glyphs[i];
204
172
  }
205
173
  batchCount++;
206
174
  i++;
207
175
  }
208
176
 
209
- // If batch exceeded stack buffer, allocate and refill
177
+ CGPoint *positions = posBuf;
178
+ CGGlyph *glyphs = glyphBuf;
210
179
  if (batchCount > 16) {
211
180
  positions = (CGPoint *)malloc(batchCount * sizeof(CGPoint));
212
- batchGlyphs = (CGGlyph *)malloc(batchCount * sizeof(CGGlyph));
181
+ glyphs = (CGGlyph *)malloc(batchCount * sizeof(CGGlyph));
213
182
  size_t idx = 0;
214
183
  for (size_t j = batchStart; j < i; j++) {
215
184
  if (_glyphs[j] == 0) continue;
216
185
  positions[idx] = _baselinePosition;
217
- batchGlyphs[idx] = _glyphs[j];
186
+ glyphs[idx] = _glyphs[j];
218
187
  idx++;
219
188
  }
220
189
  }
221
190
 
222
- CTFontDrawGlyphs(_font, batchGlyphs, positions, batchCount, context);
191
+ CTFontDrawGlyphs(_font, glyphs, positions, batchCount, context);
223
192
 
224
193
  if (batchCount > 16) {
225
194
  free(positions);
226
- free(batchGlyphs);
195
+ free(glyphs);
227
196
  }
228
197
  }
229
198
 
230
199
  CGContextRestoreGState(context);
231
200
  }
232
201
 
233
- - (void)dealloc
234
- {
235
- if (_font) {
236
- CFRelease(_font);
202
+ #pragma mark - Props
203
+
204
+ - (void)_releaseCachedColors {
205
+ for (CGColorRef c : _cachedCGColors) CGColorRelease(c);
206
+ _cachedCGColors.clear();
207
+ }
208
+
209
+ // Convert ARGB uint32 color values into cached CGColorRefs.
210
+ - (void)_rebuildCachedColors {
211
+ [self _releaseCachedColors];
212
+ _cachedCGColors.resize(_colors.size());
213
+ for (size_t i = 0; i < _colors.size(); i++) {
214
+ uint32_t ci = _colors[i];
215
+ _cachedCGColors[i] = CGColorCreateSRGB(
216
+ ((ci >> 16) & 0xFF) / 255.0,
217
+ ((ci >> 8) & 0xFF) / 255.0,
218
+ ( ci & 0xFF) / 255.0,
219
+ ((ci >> 24) & 0xFF) / 255.0);
220
+ }
221
+ }
222
+
223
+ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &)oldProps {
224
+ const auto &oldViewProps = static_cast<const NanoIconViewProps &>(*_props);
225
+ const auto &newViewProps = static_cast<const NanoIconViewProps &>(*props);
226
+
227
+ BOOL fontChanged = NO;
228
+ BOOL needsRedraw = NO;
229
+
230
+ if (oldViewProps.fontFamily != newViewProps.fontFamily ||
231
+ oldViewProps.fontSize != newViewProps.fontSize) {
232
+ if (_font) { CFRelease(_font); _font = NULL; }
233
+ _fontFamily = [NSString stringWithUTF8String:newViewProps.fontFamily.c_str()];
234
+ _fontSize = newViewProps.fontSize;
235
+ _font = CTFontCreateWithName((__bridge CFStringRef)_fontFamily, _fontSize, NULL);
236
+ _metricsValid = NO;
237
+ fontChanged = YES;
238
+ needsRedraw = YES;
239
+ }
240
+
241
+ // Map Unicode codepoints to font glyph IDs, handling surrogate pairs for codepoints > 0xFFFF.
242
+ if (fontChanged || oldViewProps.codepoints != newViewProps.codepoints) {
243
+ const auto &codepoints = newViewProps.codepoints;
244
+ _glyphs.resize(codepoints.size());
245
+ for (size_t i = 0; i < codepoints.size(); i++) {
246
+ int32_t cp = codepoints[i];
247
+ if (cp <= 0xFFFF) {
248
+ UniChar ch = (UniChar)cp;
249
+ CTFontGetGlyphsForCharacters(_font, &ch, &_glyphs[i], 1);
250
+ } else {
251
+ UniChar surr[2] = {
252
+ (UniChar)(0xD800 + ((cp - 0x10000) >> 10)),
253
+ (UniChar)(0xDC00 + ((cp - 0x10000) & 0x3FF))
254
+ };
255
+ CGGlyph pair[2] = {0, 0};
256
+ CTFontGetGlyphsForCharacters(_font, surr, pair, 2);
257
+ _glyphs[i] = pair[0];
258
+ }
259
+ }
260
+ needsRedraw = YES;
261
+ }
262
+
263
+ if (oldViewProps.colors != newViewProps.colors) {
264
+ const auto &colors = newViewProps.colors;
265
+ _colors.resize(colors.size());
266
+ for (size_t i = 0; i < colors.size(); i++) {
267
+ _colors[i] = (uint32_t)colors[i];
268
+ }
269
+ [self _rebuildCachedColors];
270
+ needsRedraw = YES;
237
271
  }
272
+
273
+ [super updateProps:props oldProps:oldProps];
274
+ if (needsRedraw) [_drawingView setNeedsDisplay];
275
+ }
276
+
277
+ - (void)dealloc {
278
+ if (_font) CFRelease(_font);
238
279
  [self _releaseCachedColors];
239
280
  }
240
281
 
241
282
  @end
242
283
 
243
- Class<RCTComponentViewProtocol> NanoIconViewCls(void)
244
- {
284
+ Class<RCTComponentViewProtocol> NanoIconViewCls(void) {
245
285
  return NanoIconView.class;
246
286
  }
File without changes
@@ -9,6 +9,18 @@ import { jsx as _jsx } from "react/jsx-runtime";
9
9
  export { shallowEqualColor };
10
10
  const DEFAULT_ICON_SIZE = 12;
11
11
  const HAS_NATIVE_IMPL = UIManager.hasViewManagerConfig('NanoIconView');
12
+
13
+ // Shared processColor cache — avoids redundant color parsing for repeated
14
+ // color strings like "black", "rgba(0,0,0,0.3)" across thousands of icons
15
+ const processedColorCache = new Map();
16
+ function cachedProcessColor(color) {
17
+ let result = processedColorCache.get(color);
18
+ if (result === undefined) {
19
+ result = processColor(color) ?? 0xff000000;
20
+ processedColorCache.set(color, result);
21
+ }
22
+ return result;
23
+ }
12
24
  export function createIconSet(glyphMap) {
13
25
  if (!HAS_NATIVE_IMPL) {
14
26
  return createJSIconSet(glyphMap);
@@ -18,6 +30,27 @@ export function createIconSet(glyphMap) {
18
30
  const resolveEntry = name => {
19
31
  return glyphMap.i[name] ?? [unitsPerEm, [[63, 'black']]];
20
32
  };
33
+
34
+ // Pre-compute per-icon static data (codepoints, default colors) once at set creation
35
+ // Avoids layers.map() + processColor per icon mount
36
+ const codepointsCache = new Map();
37
+ const defaultColorsCache = new Map();
38
+ function getCodepoints(name, layers) {
39
+ let cp = codepointsCache.get(name);
40
+ if (!cp) {
41
+ cp = layers.map(([c]) => c);
42
+ codepointsCache.set(name, cp);
43
+ }
44
+ return cp;
45
+ }
46
+ function getDefaultColors(name, layers) {
47
+ let colors = defaultColorsCache.get(name);
48
+ if (!colors) {
49
+ colors = layers.map(([, srcColor]) => cachedProcessColor(srcColor ?? 'black'));
50
+ defaultColorsCache.set(name, colors);
51
+ }
52
+ return colors;
53
+ }
21
54
  const Icon = /*#__PURE__*/memo(({
22
55
  name,
23
56
  size = DEFAULT_ICON_SIZE,
@@ -34,13 +67,20 @@ export function createIconSet(glyphMap) {
34
67
  const [adv, layers] = resolveEntry(name);
35
68
  const scaledSize = size * fontScale;
36
69
  const width = adv / unitsPerEm * scaledSize;
37
- const colorArray = Array.isArray(color) ? color : [color];
38
- const lastPaletteColor = colorArray?.length ? colorArray[colorArray.length - 1] : undefined;
39
- const codepoints = useMemo(() => layers.map(([cp]) => cp), [name]);
40
- const processedColors = useMemo(() => layers.map(([, srcColor], i) => {
41
- const layerColor = colorArray?.[i] ?? lastPaletteColor ?? srcColor ?? 'black';
42
- return processColor(layerColor) ?? 0xff000000;
43
- }), [name, color]);
70
+ const nameStr = name;
71
+ const codepoints = getCodepoints(nameStr, layers);
72
+ const processedColors = useMemo(() => {
73
+ // Fast path: no custom color use pre-computed defaults
74
+ if (color === undefined || color === null) {
75
+ return getDefaultColors(nameStr, layers);
76
+ }
77
+ const colorArray = Array.isArray(color) ? color : [color];
78
+ const lastPaletteColor = colorArray.length ? colorArray[colorArray.length - 1] : undefined;
79
+ return layers.map(([, srcColor], i) => {
80
+ const layerColor = colorArray[i] ?? lastPaletteColor ?? srcColor ?? 'black';
81
+ return cachedProcessColor(layerColor);
82
+ });
83
+ }, [nameStr, color]);
44
84
  const nativeStyle = useMemo(() => [{
45
85
  width,
46
86
  height: scaledSize
@@ -1 +1 @@
1
- {"version":3,"names":["memo","useMemo","PixelRatio","UIManager","processColor","shallowEqualColor","NanoIconViewNative","createJSIconSet","jsx","_jsx","DEFAULT_ICON_SIZE","HAS_NATIVE_IMPL","hasViewManagerConfig","createIconSet","glyphMap","fontFamilyBasename","m","f","unitsPerEm","u","resolveEntry","name","i","Icon","size","color","style","allowFontScaling","accessible","accessibilityLabel","accessibilityRole","testID","ref","fontScale","getFontScale","adv","layers","scaledSize","width","colorArray","Array","isArray","lastPaletteColor","length","undefined","codepoints","map","cp","processedColors","srcColor","layerColor","nativeStyle","height","fontFamily","colors","fontSize","advanceWidth","iconWidth","iconHeight","prev","next","displayName"],"sourceRoot":"../../src","sources":["createNanoIconsSet.native.tsx"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":["memo","useMemo","PixelRatio","UIManager","processColor","shallowEqualColor","NanoIconViewNative","createJSIconSet","jsx","_jsx","DEFAULT_ICON_SIZE","HAS_NATIVE_IMPL","hasViewManagerConfig","processedColorCache","Map","cachedProcessColor","color","result","get","undefined","set","createIconSet","glyphMap","fontFamilyBasename","m","f","unitsPerEm","u","resolveEntry","name","i","codepointsCache","defaultColorsCache","getCodepoints","layers","cp","map","c","getDefaultColors","colors","srcColor","Icon","size","style","allowFontScaling","accessible","accessibilityLabel","accessibilityRole","testID","ref","fontScale","getFontScale","adv","scaledSize","width","nameStr","codepoints","processedColors","colorArray","Array","isArray","lastPaletteColor","length","layerColor","nativeStyle","height","fontFamily","fontSize","advanceWidth","iconWidth","iconHeight","prev","next","displayName"],"sourceRoot":"../../src","sources":["createNanoIconsSet.native.tsx"],"mappings":"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","ignoreList":[]}
@@ -1 +1 @@
1
- {"version":3,"file":"createNanoIconsSet.native.d.ts","sourceRoot":"","sources":["../../../src/createNanoIconsSet.native.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAc,MAAM,cAAc,CAAC;AAClE,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAI9D,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,CAAC;AACzC,OAAO,EAAE,iBAAiB,EAAE,CAAC;AAM7B,wBAAgB,aAAa,CAAC,EAAE,SAAS,iBAAiB,EACxD,QAAQ,EAAE,EAAE,GACX,aAAa,CAAC,EAAE,CAAC,CA0FnB"}
1
+ {"version":3,"file":"createNanoIconsSet.native.d.ts","sourceRoot":"","sources":["../../../src/createNanoIconsSet.native.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAc,MAAM,cAAc,CAAC;AAClE,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAI9D,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,CAAC;AACzC,OAAO,EAAE,iBAAiB,EAAE,CAAC;AAkB7B,wBAAgB,aAAa,CAAC,EAAE,SAAS,iBAAiB,EACxD,QAAQ,EAAE,EAAE,GACX,aAAa,CAAC,EAAE,CAAC,CA8GnB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-nano-icons",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "Use any svg as font. High-performance, build-time icon font generation and rendering for React Native & Expo.",
5
5
  "react-native": "src/index.ts",
6
6
  "source": "src/index.ts",
@@ -13,6 +13,18 @@ const DEFAULT_ICON_SIZE = 12;
13
13
 
14
14
  const HAS_NATIVE_IMPL = UIManager.hasViewManagerConfig('NanoIconView');
15
15
 
16
+ // Shared processColor cache — avoids redundant color parsing for repeated
17
+ // color strings like "black", "rgba(0,0,0,0.3)" across thousands of icons
18
+ const processedColorCache = new Map<string, number>();
19
+ function cachedProcessColor(color: string): number {
20
+ let result = processedColorCache.get(color);
21
+ if (result === undefined) {
22
+ result = (processColor(color) ?? 0xff000000) as number;
23
+ processedColorCache.set(color, result);
24
+ }
25
+ return result;
26
+ }
27
+
16
28
  export function createIconSet<GM extends NanoGlyphMapInput>(
17
29
  glyphMap: GM
18
30
  ): IconComponent<GM> {
@@ -30,6 +42,29 @@ export function createIconSet<GM extends NanoGlyphMapInput>(
30
42
  ]) as GlyphEntry;
31
43
  };
32
44
 
45
+ // Pre-compute per-icon static data (codepoints, default colors) once at set creation
46
+ // Avoids layers.map() + processColor per icon mount
47
+ const codepointsCache = new Map<string, readonly number[]>();
48
+ const defaultColorsCache = new Map<string, readonly number[]>();
49
+
50
+ function getCodepoints(name: string, layers: GlyphEntry[1]): readonly number[] {
51
+ let cp = codepointsCache.get(name);
52
+ if (!cp) {
53
+ cp = layers.map(([c]) => c);
54
+ codepointsCache.set(name, cp);
55
+ }
56
+ return cp;
57
+ }
58
+
59
+ function getDefaultColors(name: string, layers: GlyphEntry[1]): readonly number[] {
60
+ let colors = defaultColorsCache.get(name);
61
+ if (!colors) {
62
+ colors = layers.map(([, srcColor]) => cachedProcessColor(srcColor ?? 'black'));
63
+ defaultColorsCache.set(name, colors);
64
+ }
65
+ return colors;
66
+ }
67
+
33
68
  const Icon = memo(
34
69
  ({
35
70
  name,
@@ -48,27 +83,24 @@ export function createIconSet<GM extends NanoGlyphMapInput>(
48
83
  const scaledSize = size * fontScale;
49
84
  const width = (adv / unitsPerEm) * scaledSize;
50
85
 
51
- const colorArray = Array.isArray(color) ? color : [color];
52
- const lastPaletteColor = colorArray?.length
53
- ? colorArray[colorArray.length - 1]
54
- : undefined;
55
-
56
- const codepoints = useMemo(
57
- () => layers.map(([cp]) => cp),
58
-
59
- [name]
60
- );
61
-
62
- const processedColors = useMemo(
63
- () =>
64
- layers.map(([, srcColor], i) => {
65
- const layerColor =
66
- colorArray?.[i] ?? lastPaletteColor ?? srcColor ?? 'black';
67
- return (processColor(layerColor) ?? 0xff000000) as number;
68
- }),
69
-
70
- [name, color]
71
- );
86
+ const nameStr = name as string;
87
+ const codepoints = getCodepoints(nameStr, layers);
88
+
89
+ const processedColors = useMemo(() => {
90
+ // Fast path: no custom color — use pre-computed defaults
91
+ if (color === undefined || color === null) {
92
+ return getDefaultColors(nameStr, layers);
93
+ }
94
+ const colorArray = Array.isArray(color) ? color : [color];
95
+ const lastPaletteColor = colorArray.length
96
+ ? colorArray[colorArray.length - 1]
97
+ : undefined;
98
+ return layers.map(([, srcColor], i) => {
99
+ const layerColor =
100
+ colorArray[i] ?? lastPaletteColor ?? srcColor ?? 'black';
101
+ return cachedProcessColor(layerColor as string);
102
+ });
103
+ }, [nameStr, color]);
72
104
 
73
105
  const nativeStyle = useMemo(
74
106
  () => [{ width, height: scaledSize }, style],