From 04fc2cdce5905a028ec1f56dc87f58d200176e7a Mon Sep 17 00:00:00 2001 From: Mikael Sand Date: Sat, 9 Feb 2019 22:05:47 +0200 Subject: [PATCH] [iOS] Attempt to fix text-anchor subtree advance/extent calculation Related to https://github.com/react-native-community/react-native-svg/issues/600 https://github.com/react-native-community/react-native-svg/issues/570 --- ios/Text/RNSVGTSpan.m | 63 +++++++++++++++++++++++++++++++++++++++++-- ios/Text/RNSVGText.h | 2 ++ ios/Text/RNSVGText.m | 41 ++++++++++++++++++++++++++++ 3 files changed, 104 insertions(+), 2 deletions(-) diff --git a/ios/Text/RNSVGTSpan.m b/ios/Text/RNSVGTSpan.m index 70b11adc..d52f780a 100644 --- a/ios/Text/RNSVGTSpan.m +++ b/ios/Text/RNSVGTSpan.m @@ -100,6 +100,65 @@ static CGFloat RNSVGTSpan_radToDeg = 180 / (CGFloat)M_PI; return path; } +- (CGFloat)getSubtreeTextChunksTotalAdvance +{ + CGFloat advance = 0; + + NSString *str = self.content; + if (!str) { + for (UIView *node in self.subviews) { + if ([node isKindOfClass:[RNSVGText class]]) { + RNSVGText *text = (RNSVGText*)node; + advance += [text getSubtreeTextChunksTotalAdvance]; + } + } + return advance; + } + + // Create a dictionary for this font + CTFontRef fontRef = [self getFontFromContext]; + RNSVGGlyphContext* gc = [self.textRoot getGlyphContext]; + RNSVGFontData* font = [gc getFont]; + + CGFloat letterSpacing = font->letterSpacing; + bool autoKerning = !font->manualKerning; + + bool allowOptionalLigatures = letterSpacing == 0 && font->fontVariantLigatures == RNSVGFontVariantLigaturesNormal; + + NSMutableDictionary *attrs = [[NSMutableDictionary alloc] init]; + + NSNumber *lig = [NSNumber numberWithInt:allowOptionalLigatures ? 2 : 1]; + attrs[NSLigatureAttributeName] = lig; + CFDictionaryRef attributes; + if (fontRef != nil) { + attrs[NSFontAttributeName] = (__bridge id)fontRef; + } + if (!autoKerning) { + NSNumber *noAutoKern = [NSNumber numberWithFloat:0.0f]; + +#if DTCORETEXT_SUPPORT_NS_ATTRIBUTES + if (___useiOS6Attributes) + { + [attrs setObject:noAutoKern forKey:NSKernAttributeName]; + } + else +#endif + { + [attrs setObject:noAutoKern forKey:(id)kCTKernAttributeName]; + } + } + + attributes = (__bridge CFDictionaryRef)attrs; + + CFStringRef string = (__bridge CFStringRef)str; + CFAttributedStringRef attrString = CFAttributedStringCreate(kCFAllocatorDefault, string, attributes); + CTLineRef line = CTLineCreateWithAttributedString(attrString); + + CGRect textBounds = CTLineGetBoundsWithOptions(line, 0); + CGFloat textMeasure = CGRectGetWidth(textBounds); + return textMeasure; +} + - (CGPathRef)getLinePath:(NSString *)str context:(CGContextRef)context { // Create a dictionary for this font @@ -292,8 +351,8 @@ static CGFloat RNSVGTSpan_radToDeg = 180 / (CGFloat)M_PI; attributes, such as a ‘dx’ attribute value on a ‘tspan’ element. */ enum RNSVGTextAnchor textAnchor = font->textAnchor; - CGRect textBounds = CTLineGetBoundsWithOptions(line, 0); - CGFloat textMeasure = CGRectGetWidth(textBounds); + RNSVGText *anchorRoot = [self getTextAnchorRoot]; + CGFloat textMeasure = [anchorRoot getSubtreeTextChunksTotalAdvance]; CGFloat offset = [RNSVGTSpan getTextAnchorOffset:textAnchor width:textMeasure]; bool hasTextPath = textPath != nil; diff --git a/ios/Text/RNSVGText.h b/ios/Text/RNSVGText.h index 138f18f9..913ce61d 100644 --- a/ios/Text/RNSVGText.h +++ b/ios/Text/RNSVGText.h @@ -23,5 +23,7 @@ - (CGPathRef)getGroupPath:(CGContextRef)context; - (CTFontRef)getFontFromContext; +- (CGFloat)getSubtreeTextChunksTotalAdvance; +- (RNSVGText*)getTextAnchorRoot; @end diff --git a/ios/Text/RNSVGText.m b/ios/Text/RNSVGText.m index 7efc8cf0..94625158 100644 --- a/ios/Text/RNSVGText.m +++ b/ios/Text/RNSVGText.m @@ -18,6 +18,7 @@ RNSVGGlyphContext *_glyphContext; NSString *_alignmentBaseline; NSString *_baselineShift; + CGFloat cachedAdvance; } - (void)invalidate @@ -25,6 +26,7 @@ if (self.dirty || self.merging) { return; } + cachedAdvance = NAN; [super invalidate]; [self clearChildCache]; } @@ -248,4 +250,43 @@ return [[self.textRoot getGlyphContext] getGlyphFont]; } +- (RNSVGText*)getTextAnchorRoot +{ + RNSVGGlyphContext* gc = [self.textRoot getGlyphContext]; + RNSVGFontData* font = [gc getFont]; + enum RNSVGTextAnchor textAnchor = font->textAnchor; + if (textAnchor == RNSVGTextAnchorStart) { + return self; + } + UIView* parent = [self superview]; + if ([parent isKindOfClass:[RNSVGText class]]) { + RNSVGText *parentText = (RNSVGText*)parent; + RNSVGGlyphContext* gc = [parentText.textRoot getGlyphContext]; + RNSVGFontData* font = [gc getFont]; + enum RNSVGTextAnchor textAnchor = font->textAnchor; + if (textAnchor == RNSVGTextAnchorStart) { + return self; + } else { + return [parentText getTextAnchorRoot]; + } + } + return self; +} + +- (CGFloat)getSubtreeTextChunksTotalAdvance +{ + if (!isnan(cachedAdvance)) { + return cachedAdvance; + } + CGFloat advance = 0; + for (UIView *node in self.subviews) { + if ([node isKindOfClass:[RNSVGText class]]) { + RNSVGText *text = (RNSVGText*)node; + advance += [text getSubtreeTextChunksTotalAdvance]; + } + } + cachedAdvance = advance; + return advance; +} + @end