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.
- package/ios/NanoIconView.mm +174 -134
- package/lib/commonjs/scripts/cli.js +0 -0
- package/lib/module/createNanoIconsSet.native.js +47 -7
- package/lib/module/createNanoIconsSet.native.js.map +1 -1
- package/lib/typescript/src/createNanoIconsSet.native.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/createNanoIconsSet.native.tsx +53 -21
package/ios/NanoIconView.mm
CHANGED
|
@@ -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
|
-
|
|
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)
|
|
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
|
-
-
|
|
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
|
-
|
|
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
|
|
76
|
-
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
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
|
-
|
|
104
|
-
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
|
|
138
|
-
|
|
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
|
-
-
|
|
142
|
-
|
|
123
|
+
#pragma mark - Layout
|
|
124
|
+
|
|
125
|
+
- (void)layoutSubviews {
|
|
143
126
|
[super layoutSubviews];
|
|
144
|
-
_metricsValid
|
|
145
|
-
}
|
|
127
|
+
if (!_metricsValid) [self _updateMetrics];
|
|
146
128
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
|
|
154
|
-
if (!context) {
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
134
|
+
#pragma mark - Drawing
|
|
157
135
|
|
|
158
|
-
|
|
159
|
-
|
|
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
|
|
164
|
-
CGContextTranslateCTM(context, 0,
|
|
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
|
-
//
|
|
159
|
+
// Batch consecutive same-color glyphs.
|
|
186
160
|
size_t batchStart = i;
|
|
187
161
|
size_t batchCount = 0;
|
|
188
|
-
|
|
189
|
-
|
|
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
|
-
|
|
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
|
-
|
|
203
|
-
|
|
170
|
+
posBuf[batchCount] = _baselinePosition;
|
|
171
|
+
glyphBuf[batchCount] = _glyphs[i];
|
|
204
172
|
}
|
|
205
173
|
batchCount++;
|
|
206
174
|
i++;
|
|
207
175
|
}
|
|
208
176
|
|
|
209
|
-
|
|
177
|
+
CGPoint *positions = posBuf;
|
|
178
|
+
CGGlyph *glyphs = glyphBuf;
|
|
210
179
|
if (batchCount > 16) {
|
|
211
180
|
positions = (CGPoint *)malloc(batchCount * sizeof(CGPoint));
|
|
212
|
-
|
|
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
|
-
|
|
186
|
+
glyphs[idx] = _glyphs[j];
|
|
218
187
|
idx++;
|
|
219
188
|
}
|
|
220
189
|
}
|
|
221
190
|
|
|
222
|
-
CTFontDrawGlyphs(_font,
|
|
191
|
+
CTFontDrawGlyphs(_font, glyphs, positions, batchCount, context);
|
|
223
192
|
|
|
224
193
|
if (batchCount > 16) {
|
|
225
194
|
free(positions);
|
|
226
|
-
free(
|
|
195
|
+
free(glyphs);
|
|
227
196
|
}
|
|
228
197
|
}
|
|
229
198
|
|
|
230
199
|
CGContextRestoreGState(context);
|
|
231
200
|
}
|
|
232
201
|
|
|
233
|
-
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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
|
|
38
|
-
const
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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","
|
|
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;
|
|
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
|
+
"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
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
[
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
() =>
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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],
|