react-native-nano-icons 0.1.3 → 0.1.5
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 +276 -131
- 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,385 @@
|
|
|
8
7
|
|
|
9
8
|
using namespace facebook::react;
|
|
10
9
|
|
|
10
|
+
// Forward-declare so the layer subclass can call the drawing method.
|
|
11
|
+
@interface NanoIconView ()
|
|
12
|
+
- (void)_drawIconInContext:(CGContextRef)context bounds:(CGRect)bounds;
|
|
13
|
+
@end
|
|
14
|
+
|
|
15
|
+
// Lightweight sublayer for inline-in-Text icons. Provides a shifted pixel
|
|
16
|
+
// buffer so the icon can overflow the Yoga frame without a full UIView.
|
|
17
|
+
@interface NanoIconDrawingLayer : CALayer
|
|
18
|
+
@property (nonatomic, weak) NanoIconView *owner;
|
|
19
|
+
@end
|
|
20
|
+
|
|
21
|
+
@implementation NanoIconDrawingLayer
|
|
22
|
+
- (void)drawInContext:(CGContextRef)ctx {
|
|
23
|
+
[self.owner _drawIconInContext:ctx bounds:self.bounds];
|
|
24
|
+
}
|
|
25
|
+
@end
|
|
26
|
+
|
|
27
|
+
// Process-wide CTFontRef cache keyed by (fontFamily, fontSize).
|
|
28
|
+
// Avoids 1000× CTFontCreateWithName for identical (family, size) combos.
|
|
29
|
+
static CTFontRef NanoIconGetCachedFont(NSString *family, CGFloat size) {
|
|
30
|
+
static NSMutableDictionary *cache;
|
|
31
|
+
static dispatch_once_t onceToken;
|
|
32
|
+
dispatch_once(&onceToken, ^{ cache = [NSMutableDictionary new]; });
|
|
33
|
+
|
|
34
|
+
NSString *key = [NSString stringWithFormat:@"%@:%.1f", family, size];
|
|
35
|
+
id existing = cache[key];
|
|
36
|
+
if (existing) return (__bridge CTFontRef)existing;
|
|
37
|
+
|
|
38
|
+
CTFontRef font = CTFontCreateWithName((__bridge CFStringRef)family, size, NULL);
|
|
39
|
+
if (font) cache[key] = (__bridge id)font;
|
|
40
|
+
return font;
|
|
41
|
+
}
|
|
42
|
+
|
|
11
43
|
@implementation NanoIconView {
|
|
12
|
-
CTFontRef _font;
|
|
44
|
+
CTFontRef _font; // borrowed from static cache — do NOT CFRelease
|
|
13
45
|
NSString *_fontFamily;
|
|
14
46
|
CGFloat _fontSize;
|
|
15
47
|
std::vector<CGGlyph> _glyphs;
|
|
16
48
|
std::vector<uint32_t> _colors;
|
|
17
|
-
// Cached CGColor refs — rebuilt only when colors prop changes
|
|
18
49
|
std::vector<CGColorRef> _cachedCGColors;
|
|
19
|
-
// Cached layout metrics — rebuilt only when font or bounds change
|
|
20
50
|
CGFloat _fitScale;
|
|
21
51
|
CGPoint _baselinePosition;
|
|
22
52
|
BOOL _metricsValid;
|
|
53
|
+
|
|
54
|
+
// Inline-in-Text detection — resolved once on first layout, cached until reparenting.
|
|
55
|
+
// Standalone icons (vast majority) skip the superview walk entirely after detection.
|
|
56
|
+
BOOL _inlineDetected;
|
|
57
|
+
BOOL _isInlineInText;
|
|
58
|
+
UIView * __weak _paragraphView;
|
|
59
|
+
CGFloat _cachedBaselineOffset;
|
|
60
|
+
BOOL _baselineOffsetValid;
|
|
61
|
+
|
|
62
|
+
// Drawing sublayer for inline icons — provides a shifted pixel buffer so the
|
|
63
|
+
// icon can overflow the Yoga frame. Lighter than a UIView (no responder chain,
|
|
64
|
+
// hit testing, or accessibility). Standalone icons draw directly via drawRect:.
|
|
65
|
+
CALayer *_drawingLayer;
|
|
23
66
|
}
|
|
24
67
|
|
|
25
|
-
- (instancetype)initWithFrame:(CGRect)frame
|
|
26
|
-
{
|
|
68
|
+
- (instancetype)initWithFrame:(CGRect)frame {
|
|
27
69
|
if (self = [super initWithFrame:frame]) {
|
|
28
70
|
static const auto defaultProps = std::make_shared<const NanoIconViewProps>();
|
|
29
71
|
_props = defaultProps;
|
|
30
72
|
self.opaque = NO;
|
|
31
73
|
self.backgroundColor = [UIColor clearColor];
|
|
74
|
+
self.clipsToBounds = NO;
|
|
75
|
+
self.contentMode = UIViewContentModeRedraw;
|
|
76
|
+
|
|
32
77
|
_fitScale = 1.0;
|
|
33
78
|
_baselinePosition = CGPointZero;
|
|
34
|
-
|
|
79
|
+
|
|
80
|
+
// _drawingLayer is created lazily only when inline in Text
|
|
35
81
|
}
|
|
36
82
|
return self;
|
|
37
83
|
}
|
|
38
84
|
|
|
39
|
-
+ (ComponentDescriptorProvider)componentDescriptorProvider
|
|
40
|
-
{
|
|
85
|
+
+ (ComponentDescriptorProvider)componentDescriptorProvider {
|
|
41
86
|
return concreteComponentDescriptorProvider<NanoIconViewComponentDescriptor>();
|
|
42
87
|
}
|
|
43
88
|
|
|
44
|
-
- (void)
|
|
45
|
-
{
|
|
46
|
-
for (CGColorRef c : _cachedCGColors) {
|
|
47
|
-
CGColorRelease(c);
|
|
48
|
-
}
|
|
49
|
-
_cachedCGColors.clear();
|
|
50
|
-
}
|
|
89
|
+
- (void)updateClippedSubviewsWithClipRect:(__unused CGRect)clipRect
|
|
90
|
+
relativeToView:(__unused UIView *)clipView {}
|
|
51
91
|
|
|
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
|
-
}
|
|
92
|
+
#pragma mark - Metrics
|
|
65
93
|
|
|
66
|
-
|
|
67
|
-
|
|
94
|
+
// Scale factor to fit the icon font's em square into the view height,
|
|
95
|
+
// and the CoreText baseline origin used for all glyph draws.
|
|
96
|
+
- (void)_updateMetrics {
|
|
68
97
|
if (!_font) {
|
|
69
98
|
_metricsValid = NO;
|
|
70
99
|
return;
|
|
71
100
|
}
|
|
72
|
-
|
|
73
101
|
CGFloat ascent = CTFontGetAscent(_font);
|
|
74
102
|
CGFloat descent = CTFontGetDescent(_font);
|
|
75
|
-
CGFloat
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
_fitScale = (fontTotalHeight > 0) ? (viewHeight / fontTotalHeight) : 1.0;
|
|
103
|
+
CGFloat totalHeight = ascent + descent;
|
|
104
|
+
_fitScale = (totalHeight > 0) ? (self.bounds.size.height / totalHeight) : 1.0;
|
|
79
105
|
_baselinePosition = CGPointMake(0, descent);
|
|
80
106
|
_metricsValid = YES;
|
|
81
107
|
}
|
|
82
108
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
109
|
+
// Detect whether this icon is inline inside a <Text> component and, if so,
|
|
110
|
+
// compute the baseline offset in a single pass. Deferred to first layout
|
|
111
|
+
// (not didMoveToSuperview) because Fabric may assemble the hierarchy bottom-up.
|
|
112
|
+
// For standalone icons this completes in 1-3 class name checks with no
|
|
113
|
+
// ObjC runtime method resolution or attributed string reads.
|
|
114
|
+
- (void)_detectAndCacheInlineState {
|
|
115
|
+
_inlineDetected = YES;
|
|
116
|
+
_isInlineInText = NO;
|
|
117
|
+
_paragraphView = nil;
|
|
118
|
+
_cachedBaselineOffset = 0;
|
|
119
|
+
_baselineOffsetValid = YES;
|
|
120
|
+
|
|
121
|
+
// RCTParagraphComponentView is the immediate or near-immediate parent
|
|
122
|
+
// when the icon is inside <Text>. Three levels covers all known layouts.
|
|
123
|
+
UIView *current = self.superview;
|
|
124
|
+
UIView *target = nil;
|
|
125
|
+
for (int i = 0; i < 3 && current; i++) {
|
|
126
|
+
if ([NSStringFromClass([current class]) isEqualToString:@"RCTParagraphComponentView"]) {
|
|
127
|
+
target = current;
|
|
128
|
+
break;
|
|
94
129
|
}
|
|
95
|
-
|
|
96
|
-
_fontFamily = family;
|
|
97
|
-
_fontSize = newViewProps.fontSize;
|
|
98
|
-
_font = CTFontCreateWithName((__bridge CFStringRef)family, _fontSize, NULL);
|
|
99
|
-
fontChanged = YES;
|
|
100
|
-
_metricsValid = NO;
|
|
130
|
+
current = current.superview;
|
|
101
131
|
}
|
|
102
132
|
|
|
103
|
-
|
|
104
|
-
BOOL codepointsChanged = fontChanged || (oldViewProps.codepoints != newViewProps.codepoints);
|
|
105
|
-
if (codepointsChanged && _font) {
|
|
106
|
-
const auto &codepoints = newViewProps.codepoints;
|
|
107
|
-
_glyphs.resize(codepoints.size());
|
|
133
|
+
if (!target) return;
|
|
108
134
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
}
|
|
135
|
+
NSAttributedString *attrStr = nil;
|
|
136
|
+
if ([target respondsToSelector:@selector(attributedText)]) {
|
|
137
|
+
attrStr = [target performSelector:@selector(attributedText)];
|
|
125
138
|
}
|
|
139
|
+
if (!attrStr || attrStr.length == 0) return;
|
|
126
140
|
|
|
127
|
-
|
|
128
|
-
if (
|
|
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
|
-
}
|
|
141
|
+
UIFont *f = [attrStr attribute:NSFontAttributeName atIndex:0 effectiveRange:nil];
|
|
142
|
+
if (!f) return;
|
|
136
143
|
|
|
137
|
-
|
|
138
|
-
|
|
144
|
+
_isInlineInText = YES;
|
|
145
|
+
_paragraphView = target;
|
|
146
|
+
|
|
147
|
+
// Derive the distance from this view's bottom edge to the text baseline.
|
|
148
|
+
// Respects custom RN lineHeight (mapped to NSParagraphStyle.maximumLineHeight)
|
|
149
|
+
// and any baseline offset applied by Fabric's text layout.
|
|
150
|
+
NSParagraphStyle *style = [attrStr attribute:NSParagraphStyleAttributeName
|
|
151
|
+
atIndex:0 effectiveRange:nil];
|
|
152
|
+
CGFloat lineHeight = (style && style.maximumLineHeight > 0)
|
|
153
|
+
? style.maximumLineHeight : f.lineHeight;
|
|
154
|
+
|
|
155
|
+
NSNumber *bOff = [attrStr attribute:NSBaselineOffsetAttributeName
|
|
156
|
+
atIndex:0 effectiveRange:nil];
|
|
157
|
+
CGFloat baselineFromLineTop = f.ascender - (bOff ? bOff.doubleValue : 0);
|
|
158
|
+
|
|
159
|
+
CGFloat frameBottom = self.frame.origin.y + self.frame.size.height;
|
|
160
|
+
CGFloat posInLine = fmod(frameBottom, lineHeight);
|
|
161
|
+
if (posInLine < 0.01) posInLine = lineHeight;
|
|
162
|
+
|
|
163
|
+
_cachedBaselineOffset = MAX(0, posInLine - baselineFromLineTop);
|
|
139
164
|
}
|
|
140
165
|
|
|
141
|
-
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
166
|
+
// Lazily create the drawing sublayer for inline-in-Text icons.
|
|
167
|
+
// The sublayer's frame is shifted upward so the icon overflows the Yoga box.
|
|
168
|
+
- (void)_ensureDrawingLayer {
|
|
169
|
+
if (_drawingLayer) return;
|
|
170
|
+
NanoIconDrawingLayer *layer = [NanoIconDrawingLayer layer];
|
|
171
|
+
layer.owner = self;
|
|
172
|
+
layer.opaque = NO;
|
|
173
|
+
layer.contentsScale = [UIScreen mainScreen].scale;
|
|
174
|
+
[self.layer addSublayer:layer];
|
|
175
|
+
_drawingLayer = layer;
|
|
145
176
|
}
|
|
146
177
|
|
|
147
|
-
-
|
|
148
|
-
{
|
|
149
|
-
|
|
150
|
-
|
|
178
|
+
// Re-detect inline state when the view moves to a new parent.
|
|
179
|
+
- (void)didMoveToSuperview {
|
|
180
|
+
[super didMoveToSuperview];
|
|
181
|
+
_inlineDetected = NO;
|
|
182
|
+
_baselineOffsetValid = NO;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Invalidate cached offset when size changes (text relayout).
|
|
186
|
+
- (void)setBounds:(CGRect)bounds {
|
|
187
|
+
if (!CGSizeEqualToSize(self.bounds.size, bounds.size)) {
|
|
188
|
+
_baselineOffsetValid = NO;
|
|
151
189
|
}
|
|
190
|
+
[super setBounds:bounds];
|
|
191
|
+
}
|
|
152
192
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
193
|
+
#pragma mark - Layout
|
|
194
|
+
|
|
195
|
+
// Standalone: no work beyond metrics validation (drawing via drawRect: on self).
|
|
196
|
+
// Inline: position the drawing sublayer with the cached baseline offset,
|
|
197
|
+
// recomputing only when bounds change or the view is reparented.
|
|
198
|
+
- (void)layoutSubviews {
|
|
199
|
+
[super layoutSubviews];
|
|
200
|
+
if (!_metricsValid) [self _updateMetrics];
|
|
201
|
+
|
|
202
|
+
if (!_inlineDetected) {
|
|
203
|
+
[self _detectAndCacheInlineState];
|
|
156
204
|
}
|
|
157
205
|
|
|
158
|
-
if (
|
|
159
|
-
|
|
206
|
+
if (_isInlineInText) {
|
|
207
|
+
if (!_baselineOffsetValid) {
|
|
208
|
+
[self _detectAndCacheInlineState];
|
|
209
|
+
}
|
|
210
|
+
BOOL created = !_drawingLayer;
|
|
211
|
+
[self _ensureDrawingLayer];
|
|
212
|
+
CGRect newFrame = CGRectMake(0, -_cachedBaselineOffset,
|
|
213
|
+
self.bounds.size.width, self.bounds.size.height);
|
|
214
|
+
if (!CGRectEqualToRect(_drawingLayer.frame, newFrame)) {
|
|
215
|
+
[CATransaction begin];
|
|
216
|
+
[CATransaction setDisableActions:YES];
|
|
217
|
+
_drawingLayer.frame = newFrame;
|
|
218
|
+
[CATransaction commit];
|
|
219
|
+
}
|
|
220
|
+
// First layout after inline detection: the layer missed the initial
|
|
221
|
+
// setNeedsDisplay from updateProps (which targeted self before detection).
|
|
222
|
+
if (created) [_drawingLayer setNeedsDisplay];
|
|
160
223
|
}
|
|
224
|
+
// standalone: draws directly via drawRect: on self
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
#pragma mark - Drawing
|
|
228
|
+
|
|
229
|
+
// Standalone icons draw directly in this view's drawRect:.
|
|
230
|
+
- (void)drawRect:(CGRect)rect {
|
|
231
|
+
if (_isInlineInText) return; // inline icons draw via _drawingLayer
|
|
232
|
+
CGContextRef ctx = UIGraphicsGetCurrentContext();
|
|
233
|
+
if (ctx) [self _drawIconInContext:ctx bounds:self.bounds];
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Render multi-color icons by drawing each color layer glyph at the same
|
|
237
|
+
// position. Layers stack via painter's order to compose the final icon.
|
|
238
|
+
- (void)_drawIconInContext:(CGContextRef)context bounds:(CGRect)bounds {
|
|
239
|
+
if (!_font || _glyphs.empty()) return;
|
|
240
|
+
if (!_metricsValid) [self _updateMetrics];
|
|
161
241
|
|
|
162
242
|
CGContextSaveGState(context);
|
|
163
|
-
// CoreText
|
|
164
|
-
CGContextTranslateCTM(context, 0,
|
|
243
|
+
// Flip to CoreText coordinates (Y-up) and apply fit scale.
|
|
244
|
+
CGContextTranslateCTM(context, 0, bounds.size.height);
|
|
165
245
|
CGContextScaleCTM(context, 1.0, -1.0);
|
|
166
246
|
CGContextScaleCTM(context, _fitScale, _fitScale);
|
|
167
247
|
|
|
168
|
-
// Batch consecutive same-color glyphs into a single CTFontDrawGlyphs call
|
|
169
248
|
size_t i = 0;
|
|
170
249
|
while (i < _glyphs.size()) {
|
|
171
|
-
if (_glyphs[i] == 0) {
|
|
172
|
-
i++;
|
|
173
|
-
continue;
|
|
174
|
-
}
|
|
250
|
+
if (_glyphs[i] == 0) { i++; continue; }
|
|
175
251
|
|
|
176
|
-
// Determine the color for this run
|
|
177
252
|
CGColorRef color = (i < _cachedCGColors.size()) ? _cachedCGColors[i] : NULL;
|
|
178
253
|
if (!color) {
|
|
179
|
-
// Fallback: opaque black
|
|
180
254
|
static CGColorRef sBlack = CGColorCreateSRGB(0, 0, 0, 1);
|
|
181
255
|
color = sBlack;
|
|
182
256
|
}
|
|
183
257
|
CGContextSetFillColorWithColor(context, color);
|
|
184
258
|
|
|
185
|
-
//
|
|
259
|
+
// Batch consecutive same-color glyphs.
|
|
186
260
|
size_t batchStart = i;
|
|
187
261
|
size_t batchCount = 0;
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
CGGlyph glyphsBuf[16];
|
|
191
|
-
CGPoint *positions = positionsBuf;
|
|
192
|
-
CGGlyph *batchGlyphs = glyphsBuf;
|
|
262
|
+
CGPoint posBuf[16];
|
|
263
|
+
CGGlyph glyphBuf[16];
|
|
193
264
|
|
|
194
265
|
while (i < _glyphs.size()) {
|
|
195
266
|
if (_glyphs[i] == 0) { i++; continue; }
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
// Break batch if color changes
|
|
199
|
-
if (i > batchStart && nextColor != color) break;
|
|
200
|
-
|
|
267
|
+
CGColorRef next = (i < _cachedCGColors.size()) ? _cachedCGColors[i] : NULL;
|
|
268
|
+
if (i > batchStart && next != color) break;
|
|
201
269
|
if (batchCount < 16) {
|
|
202
|
-
|
|
203
|
-
|
|
270
|
+
posBuf[batchCount] = _baselinePosition;
|
|
271
|
+
glyphBuf[batchCount] = _glyphs[i];
|
|
204
272
|
}
|
|
205
273
|
batchCount++;
|
|
206
274
|
i++;
|
|
207
275
|
}
|
|
208
276
|
|
|
209
|
-
|
|
277
|
+
CGPoint *positions = posBuf;
|
|
278
|
+
CGGlyph *glyphs = glyphBuf;
|
|
210
279
|
if (batchCount > 16) {
|
|
211
280
|
positions = (CGPoint *)malloc(batchCount * sizeof(CGPoint));
|
|
212
|
-
|
|
281
|
+
glyphs = (CGGlyph *)malloc(batchCount * sizeof(CGGlyph));
|
|
213
282
|
size_t idx = 0;
|
|
214
283
|
for (size_t j = batchStart; j < i; j++) {
|
|
215
284
|
if (_glyphs[j] == 0) continue;
|
|
216
285
|
positions[idx] = _baselinePosition;
|
|
217
|
-
|
|
286
|
+
glyphs[idx] = _glyphs[j];
|
|
218
287
|
idx++;
|
|
219
288
|
}
|
|
220
289
|
}
|
|
221
290
|
|
|
222
|
-
CTFontDrawGlyphs(_font,
|
|
291
|
+
CTFontDrawGlyphs(_font, glyphs, positions, batchCount, context);
|
|
223
292
|
|
|
224
293
|
if (batchCount > 16) {
|
|
225
294
|
free(positions);
|
|
226
|
-
free(
|
|
295
|
+
free(glyphs);
|
|
227
296
|
}
|
|
228
297
|
}
|
|
229
298
|
|
|
230
299
|
CGContextRestoreGState(context);
|
|
231
300
|
}
|
|
232
301
|
|
|
233
|
-
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
302
|
+
#pragma mark - Props
|
|
303
|
+
|
|
304
|
+
- (void)_releaseCachedColors {
|
|
305
|
+
for (CGColorRef c : _cachedCGColors) CGColorRelease(c);
|
|
306
|
+
_cachedCGColors.clear();
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Convert ARGB uint32 color values into cached CGColorRefs.
|
|
310
|
+
- (void)_rebuildCachedColors {
|
|
311
|
+
[self _releaseCachedColors];
|
|
312
|
+
_cachedCGColors.resize(_colors.size());
|
|
313
|
+
for (size_t i = 0; i < _colors.size(); i++) {
|
|
314
|
+
uint32_t ci = _colors[i];
|
|
315
|
+
_cachedCGColors[i] = CGColorCreateSRGB(
|
|
316
|
+
((ci >> 16) & 0xFF) / 255.0,
|
|
317
|
+
((ci >> 8) & 0xFF) / 255.0,
|
|
318
|
+
( ci & 0xFF) / 255.0,
|
|
319
|
+
((ci >> 24) & 0xFF) / 255.0);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
- (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &)oldProps {
|
|
324
|
+
const auto &oldViewProps = static_cast<const NanoIconViewProps &>(*_props);
|
|
325
|
+
const auto &newViewProps = static_cast<const NanoIconViewProps &>(*props);
|
|
326
|
+
|
|
327
|
+
BOOL fontChanged = NO;
|
|
328
|
+
BOOL needsRedraw = NO;
|
|
329
|
+
|
|
330
|
+
if (oldViewProps.fontFamily != newViewProps.fontFamily ||
|
|
331
|
+
oldViewProps.fontSize != newViewProps.fontSize) {
|
|
332
|
+
_fontFamily = [NSString stringWithUTF8String:newViewProps.fontFamily.c_str()];
|
|
333
|
+
_fontSize = newViewProps.fontSize;
|
|
334
|
+
_font = NanoIconGetCachedFont(_fontFamily, _fontSize);
|
|
335
|
+
_metricsValid = NO;
|
|
336
|
+
fontChanged = YES;
|
|
337
|
+
needsRedraw = YES;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Map Unicode codepoints to font glyph IDs, handling surrogate pairs for codepoints > 0xFFFF.
|
|
341
|
+
if (fontChanged || oldViewProps.codepoints != newViewProps.codepoints) {
|
|
342
|
+
const auto &codepoints = newViewProps.codepoints;
|
|
343
|
+
_glyphs.resize(codepoints.size());
|
|
344
|
+
for (size_t i = 0; i < codepoints.size(); i++) {
|
|
345
|
+
int32_t cp = codepoints[i];
|
|
346
|
+
if (cp <= 0xFFFF) {
|
|
347
|
+
UniChar ch = (UniChar)cp;
|
|
348
|
+
CTFontGetGlyphsForCharacters(_font, &ch, &_glyphs[i], 1);
|
|
349
|
+
} else {
|
|
350
|
+
UniChar surr[2] = {
|
|
351
|
+
(UniChar)(0xD800 + ((cp - 0x10000) >> 10)),
|
|
352
|
+
(UniChar)(0xDC00 + ((cp - 0x10000) & 0x3FF))
|
|
353
|
+
};
|
|
354
|
+
CGGlyph pair[2] = {0, 0};
|
|
355
|
+
CTFontGetGlyphsForCharacters(_font, surr, pair, 2);
|
|
356
|
+
_glyphs[i] = pair[0];
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
needsRedraw = YES;
|
|
237
360
|
}
|
|
361
|
+
|
|
362
|
+
if (oldViewProps.colors != newViewProps.colors) {
|
|
363
|
+
const auto &colors = newViewProps.colors;
|
|
364
|
+
_colors.resize(colors.size());
|
|
365
|
+
for (size_t i = 0; i < colors.size(); i++) {
|
|
366
|
+
_colors[i] = (uint32_t)colors[i];
|
|
367
|
+
}
|
|
368
|
+
[self _rebuildCachedColors];
|
|
369
|
+
needsRedraw = YES;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
[super updateProps:props oldProps:oldProps];
|
|
373
|
+
if (needsRedraw) {
|
|
374
|
+
if (_isInlineInText && _drawingLayer) {
|
|
375
|
+
[_drawingLayer setNeedsDisplay];
|
|
376
|
+
} else {
|
|
377
|
+
[self setNeedsDisplay];
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
- (void)dealloc {
|
|
383
|
+
// _font is borrowed from static cache — do not release
|
|
238
384
|
[self _releaseCachedColors];
|
|
239
385
|
}
|
|
240
386
|
|
|
241
387
|
@end
|
|
242
388
|
|
|
243
|
-
Class<RCTComponentViewProtocol> NanoIconViewCls(void)
|
|
244
|
-
{
|
|
389
|
+
Class<RCTComponentViewProtocol> NanoIconViewCls(void) {
|
|
245
390
|
return NanoIconView.class;
|
|
246
391
|
}
|
|
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.5",
|
|
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],
|