188 lines
5.5 KiB
Objective-C
188 lines
5.5 KiB
Objective-C
/**
|
|
* Copyright (c) 2015-present, Horcrux.
|
|
* All rights reserved.
|
|
*
|
|
* This source code is licensed under the MIT-style license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*/
|
|
|
|
|
|
#import "RNSVGTSpan.h"
|
|
#import "RNSVGBezierTransformer.h"
|
|
#import "RNSVGText.h"
|
|
#import "RNSVGTextPath.h"
|
|
|
|
@implementation RNSVGTSpan
|
|
{
|
|
RNSVGBezierTransformer *_bezierTransformer;
|
|
CGPathRef _cache;
|
|
}
|
|
|
|
- (void)setContent:(NSString *)content
|
|
{
|
|
if (content == _content) {
|
|
return;
|
|
}
|
|
[self invalidate];
|
|
_content = content;
|
|
}
|
|
|
|
- (void)renderLayerTo:(CGContextRef)context
|
|
{
|
|
if (self.content) {
|
|
[self renderPathTo:context];
|
|
} else {
|
|
[self clip:context];
|
|
[self renderGroupTo:context];
|
|
}
|
|
}
|
|
|
|
- (void)releaseCachedPath
|
|
{
|
|
CGPathRelease(_cache);
|
|
_cache = nil;
|
|
}
|
|
|
|
- (void)dealloc
|
|
{
|
|
CGPathRelease(_cache);
|
|
}
|
|
|
|
- (CGPathRef)getPath:(CGContextRef)context
|
|
{
|
|
if (_cache) {
|
|
return _cache;
|
|
}
|
|
|
|
NSString *text = self.content;
|
|
if (!text) {
|
|
return [self getGroupPath:context];
|
|
}
|
|
|
|
[self setupTextPath:context];
|
|
|
|
CGMutablePathRef path = CGPathCreateMutable();
|
|
|
|
// append spacing
|
|
text = [text stringByAppendingString:@" "];
|
|
|
|
[self pushGlyphContext];
|
|
CTFontRef font = [self getFontFromContext];
|
|
|
|
// Create a dictionary for this font
|
|
CFDictionaryRef attributes = (__bridge CFDictionaryRef)@{
|
|
(NSString *)kCTFontAttributeName: (__bridge id)font,
|
|
(NSString *)kCTForegroundColorFromContextAttributeName: @YES
|
|
};
|
|
|
|
CFStringRef string = (__bridge CFStringRef)text;
|
|
CFAttributedStringRef attrString = CFAttributedStringCreate(kCFAllocatorDefault, string, attributes);
|
|
CTLineRef line = CTLineCreateWithAttributedString(attrString);
|
|
|
|
CGMutablePathRef linePath = [self getLinePath:line];
|
|
CGAffineTransform offset = CGAffineTransformMakeTranslation(0, _bezierTransformer ? 0 : CTFontGetSize(font) * 1.1);
|
|
CGPathAddPath(path, &offset, linePath);
|
|
CGPathRelease(linePath);
|
|
|
|
_cache = CGPathRetain(CFAutorelease(CGPathCreateCopy(path)));
|
|
[self popGlyphContext];
|
|
|
|
// clean up
|
|
CFRelease(attrString);
|
|
CFRelease(line);
|
|
|
|
return (CGPathRef)CFAutorelease(path);
|
|
}
|
|
|
|
- (CGMutablePathRef)getLinePath:(CTLineRef)line
|
|
{
|
|
CGMutablePathRef path = CGPathCreateMutable();
|
|
CFArrayRef runs = CTLineGetGlyphRuns(line);
|
|
for (CFIndex i = 0; i < CFArrayGetCount(runs); i++) {
|
|
CTRunRef run = CFArrayGetValueAtIndex(CTLineGetGlyphRuns(line), i);
|
|
|
|
CFIndex runGlyphCount = CTRunGetGlyphCount(run);
|
|
CGPoint positions[runGlyphCount];
|
|
CGGlyph glyphs[runGlyphCount];
|
|
|
|
// Grab the glyphs, positions, and font
|
|
CTRunGetPositions(run, CFRangeMake(0, 0), positions);
|
|
CTRunGetGlyphs(run, CFRangeMake(0, 0), glyphs);
|
|
CTFontRef runFont = CFDictionaryGetValue(CTRunGetAttributes(run), kCTFontAttributeName);
|
|
|
|
CGPoint glyphPoint;
|
|
|
|
for(CFIndex i = 0; i < runGlyphCount; i++) {
|
|
CGPathRef letter = CTFontCreatePathForGlyph(runFont, glyphs[i], nil);
|
|
|
|
glyphPoint = [self getGlyphPointFromContext:positions[i] glyphWidth:CGRectGetWidth(CGPathGetBoundingBox(letter))];
|
|
|
|
CGAffineTransform textPathTransform = CGAffineTransformIdentity;
|
|
CGAffineTransform transform;
|
|
if (_bezierTransformer) {
|
|
textPathTransform = [_bezierTransformer getTransformAtDistance:glyphPoint.x];
|
|
if ([self textPathHasReachedEnd]) {
|
|
CGPathRelease(letter);
|
|
break;
|
|
} else if (![self textPathHasReachedStart]) {
|
|
CGPathRelease(letter);
|
|
continue;
|
|
}
|
|
|
|
textPathTransform = CGAffineTransformConcat(CGAffineTransformMakeTranslation(0, glyphPoint.y), textPathTransform);
|
|
transform = CGAffineTransformScale(textPathTransform, 1.0, -1.0);
|
|
} else {
|
|
transform = CGAffineTransformTranslate(CGAffineTransformMakeScale(1.0, -1.0), glyphPoint.x, -glyphPoint.y);
|
|
}
|
|
|
|
CGPathAddPath(path, &transform, letter);
|
|
CGPathRelease(letter);
|
|
}
|
|
}
|
|
|
|
|
|
return path;
|
|
}
|
|
|
|
- (void)setupTextPath:(CGContextRef)context
|
|
{
|
|
__block RNSVGBezierTransformer *bezierTransformer;
|
|
[self traverseTextSuperviews:^(__kindof RNSVGText *node) {
|
|
if ([node class] == [RNSVGTextPath class]) {
|
|
bezierTransformer = [(RNSVGTextPath*)node getBezierTransformer];
|
|
return NO;
|
|
}
|
|
return YES;
|
|
}];
|
|
|
|
_bezierTransformer = bezierTransformer;
|
|
}
|
|
|
|
- (void)traverseTextSuperviews:(BOOL (^)(__kindof RNSVGText *node))block
|
|
{
|
|
RNSVGText *targetView = self;
|
|
BOOL result = block(self);
|
|
|
|
while (targetView && [targetView class] != [RNSVGText class] && result) {
|
|
if (![targetView isKindOfClass:[RNSVGText class]]) {
|
|
//todo: throw exception here
|
|
break;
|
|
}
|
|
|
|
targetView = (RNSVGText*)[targetView superview];
|
|
result = block(targetView);
|
|
}
|
|
}
|
|
|
|
- (BOOL)textPathHasReachedEnd
|
|
{
|
|
return [_bezierTransformer hasReachedEnd];
|
|
}
|
|
|
|
- (BOOL)textPathHasReachedStart
|
|
{
|
|
return [_bezierTransformer hasReachedStart];
|
|
}
|
|
|
|
@end
|