Implement kerning, ligatures, etc., and cleanup; in ios.

Implement hasGlyph ligature helper.

Export view property setters for:
 text: textLength, baselineShift, lengthAdjust, alignmentBaseline,
 textPath: side, method, midLine, spacing.

Attempt to fix alignmentBaseline and baselineShift,
but both properties are nil at all times, I must be missing something.
This commit is contained in:
Mikael Sand
2017-08-29 02:34:56 +03:00
parent 0dbddcc2b4
commit 04887af278
15 changed files with 505 additions and 916 deletions
-19
View File
@@ -1,19 +0,0 @@
/**
* 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 <UIKit/UIKit.h>
@interface RNSVGBezierTransformer : NSObject
- (instancetype)initWithBezierCurvesAndStartOffset:(NSArray<NSArray *> *)bezierCurves startOffset:(CGFloat)startOffset;
- (CGAffineTransform)getTransformAtDistance:(CGFloat)distance;
- (BOOL)hasReachedEnd;
- (BOOL)hasReachedStart;
@end
-147
View File
@@ -1,147 +0,0 @@
/**
* 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.
*/
/**
* based on CurvyText by iosptl: https://github.com/iosptl/ios7ptl/blob/master/ch21-Text/CurvyText/CurvyText/CurvyTextView.m
*/
#import "RNSVGBezierTransformer.h"
@implementation RNSVGBezierTransformer
{
NSArray<NSArray *> *_bezierCurves;
int _currentBezierIndex;
CGFloat _startOffset;
CGFloat _lastOffset;
CGFloat _lastRecord;
CGFloat _lastDistance;
CGPoint _lastPoint;
CGPoint _P0;
CGPoint _P1;
CGPoint _P2;
CGPoint _P3;
BOOL _reachedEnd;
BOOL _reachedStart;
}
- (instancetype)initWithBezierCurvesAndStartOffset:(NSArray<NSArray *> *)bezierCurves startOffset:(CGFloat)startOffset
{
if (self = [super init]) {
_bezierCurves = bezierCurves;
_currentBezierIndex = _lastOffset = _lastRecord = _lastDistance = 0;
_startOffset = startOffset;
[self setControlPoints];
}
return self;
}
static CGFloat calculateBezier(CGFloat t, CGFloat P0, CGFloat P1, CGFloat P2, CGFloat P3) {
return (1-t)*(1-t)*(1-t)*P0+3*(1-t)*(1-t)*t*P1+3*(1-t)*t*t*P2+t*t*t*P3;
}
- (CGPoint)pointAtOffset:(CGFloat)t {
CGFloat x = calculateBezier(t, _P0.x, _P1.x, _P2.x, _P3.x);
CGFloat y = calculateBezier(t, _P0.y, _P1.y, _P2.y, _P3.y);
return CGPointMake(x, y);
}
static CGFloat calculateBezierPrime(CGFloat t, CGFloat P0, CGFloat P1, CGFloat P2, CGFloat P3) {
return -3*(1-t)*(1-t)*P0+(3*(1-t)*(1-t)*P1)-(6*t*(1-t)*P1)-(3*t*t*P2)+(6*t*(1-t)*P2)+3*t*t*P3;
}
- (CGFloat)angleAtOffset:(CGFloat)t {
CGFloat dx = calculateBezierPrime(t, _P0.x, _P1.x, _P2.x, _P3.x);
CGFloat dy = calculateBezierPrime(t, _P0.y, _P1.y, _P2.y, _P3.y);
return atan2(dy, dx);
}
static CGFloat calculateDistance(CGPoint a, CGPoint b) {
return hypot(a.x - b.x, a.y - b.y);
}
// Simplistic routine to find the offset along Bezier that is
// `distance` away from `point`. `offset` is the offset used to
// generate `point`, and saves us the trouble of recalculating it
// This routine just walks forward until it finds a point at least
// `distance` away. Good optimizations here would reduce the number
// of guesses, but this is tricky since if we go too far out, the
// curve might loop back on leading to incorrect results. Tuning
// kStep is good start.
- (CGFloat)offsetAtDistance:(CGFloat)distance
fromPoint:(CGPoint)point
offset:(CGFloat)offset {
const CGFloat kStep = 0.001; // 0.0001 - 0.001 work well
CGFloat newDistance = 0;
CGFloat newOffset = offset + kStep;
while (newDistance <= distance && newOffset < 1.0) {
newOffset += kStep;
newDistance = calculateDistance(point, [self pointAtOffset:newOffset]);
}
_lastDistance = newDistance;
return newOffset;
}
- (void)setControlPoints
{
NSArray *bezier = [_bezierCurves objectAtIndex:_currentBezierIndex++];
// set start point
if (bezier.count == 1) {
_lastPoint = _P0 = [[bezier objectAtIndex:0] CGPointValue];
[self setControlPoints];
} else if (bezier.count == 3) {
_P1 = [[bezier objectAtIndex:0] CGPointValue];
_P2 = [[bezier objectAtIndex:1] CGPointValue];
_P3 = [[bezier objectAtIndex:2] CGPointValue];
}
}
- (CGAffineTransform)getTransformAtDistance:(CGFloat)distance
{
distance += _startOffset;
_reachedStart = distance >= 0;
if (_reachedEnd || !_reachedStart) {
return CGAffineTransformIdentity;
}
CGFloat offset = [self offsetAtDistance:distance - _lastRecord
fromPoint:_lastPoint
offset:_lastOffset];
if (offset < 1) {
CGPoint glyphPoint = [self pointAtOffset:offset];
_lastOffset = offset;
_lastPoint = glyphPoint;
_lastRecord = distance;
return CGAffineTransformRotate(CGAffineTransformMakeTranslation(glyphPoint.x, glyphPoint.y), [self angleAtOffset:offset]);
} else if (_bezierCurves.count == _currentBezierIndex) {
_reachedEnd = YES;
return CGAffineTransformIdentity;
} else {
_lastOffset = 0;
_lastPoint = _P0 = _P3;
_lastRecord += _lastDistance;
[self setControlPoints];
return [self getTransformAtDistance:distance - _startOffset];
}
}
- (BOOL)hasReachedEnd
{
return _reachedEnd;
}
- (BOOL)hasReachedStart
{
return _reachedStart;
}
@end
-26
View File
@@ -1,26 +0,0 @@
/**
* 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 <React/UIView+React.h>
#import <CoreText/CoreText.h>
#import "RNSVGPercentageConverter.h"
@interface RNSVGGlyphContext : NSObject
- (instancetype)initWithDimensions:(CGFloat)width height:(CGFloat)height;
- (CGFloat)getWidth;
- (CGFloat)getHeight;
- (CGFloat)getFontSize;
- (void)pushContext:(NSDictionary *)font;
- (void)pushContext:(NSDictionary *)font deltaX:(NSArray<NSString *> *)deltaX deltaY:(NSArray<NSString *> *)deltaY positionX:(NSArray<NSString *> *)positionX positionY:(NSArray<NSString *> *)positionY;
- (void)popContext;
- (CTFontRef)getGlyphFont;
- (CGPoint)getNextGlyphPoint:(CGPoint)offset glyphWidth:(CGFloat)glyphWidth;
@end
-205
View File
@@ -1,205 +0,0 @@
/**
* 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 "RNSVGGlyphContext.h"
#import "RNSVGPercentageConverter.h"
#import <React/RCTFont.h>
#import "RNSVGNode.h"
@implementation RNSVGGlyphContext
{
NSMutableArray<NSDictionary* > *_fontContext;
NSMutableArray<NSValue* > *_locationContext;
NSMutableArray<NSArray *> *_deltaXContext;
NSMutableArray<NSArray *> *_deltaYContext;
NSMutableArray<NSNumber *> *_xContext;
CGPoint _currentLocation;
CGFloat _width;
CGFloat _height;
CGFloat _fontSize;
}
- (instancetype)initWithDimensions:(CGFloat)width height:(CGFloat)height
{
if (self = [super init]) {
_width = width;
_height = height;
_fontContext = [[NSMutableArray alloc] init];
_locationContext = [[NSMutableArray alloc] init];
_deltaXContext = [[NSMutableArray alloc] init];
_deltaYContext = [[NSMutableArray alloc] init];
_xContext = [[NSMutableArray alloc] init];
_currentLocation = CGPointZero;
_fontSize = DEFAULT_FONT_SIZE;
}
return self;
}
- (CGFloat) getWidth
{
return _width;
}
- (CGFloat) getHeight {
return _height;
}
- (CGFloat) getFontSize
{
return _fontSize;
}
- (void)pushContext:(NSDictionary *)font
{
CGPoint location = _currentLocation;
[_locationContext addObject:[NSValue valueWithCGPoint:location]];
[_fontContext addObject:font ? font : @{}];
[_xContext addObject:[NSNumber numberWithFloat:location.x]];
_currentLocation = location;
}
- (void)pushContext:(NSDictionary *)font deltaX:(NSArray<NSString *> *)deltaX deltaY:(NSArray<NSString *> *)deltaY positionX:(NSArray<NSString *> *)positionX positionY:(NSArray<NSString *> *)positionY
{
CGPoint location = _currentLocation;
if (positionX) {
location.x = [RNSVGPercentageConverter stringToFloat:[positionX firstObject] relative:_width offset:0];
}
if (positionY) {
location.y = [RNSVGPercentageConverter stringToFloat:[positionY firstObject] relative:_height offset:0];
}
[_locationContext addObject:[NSValue valueWithCGPoint:location]];
[_fontContext addObject:font ? font : @{}];
[_deltaXContext addObject:deltaX ? deltaX : @[]];
[_deltaYContext addObject:deltaY ? deltaY : @[]];
[_xContext addObject:[NSNumber numberWithFloat:location.x]];
_currentLocation = location;
}
- (void)popContext
{
NSNumber *x = [_xContext lastObject];
[_fontContext removeLastObject];
[_locationContext removeLastObject];
[_deltaXContext removeLastObject];
[_deltaYContext removeLastObject];
[_xContext removeLastObject];
if (_xContext.count) {
[_xContext replaceObjectAtIndex:_xContext.count - 1 withObject:x];
}
if (_locationContext.count) {
_currentLocation = [[_locationContext lastObject] CGPointValue];
_currentLocation.x = [x floatValue];
[_locationContext replaceObjectAtIndex:_locationContext.count - 1
withObject:[NSValue valueWithCGPoint:_currentLocation]];
}
}
- (CGPoint)getNextGlyphPoint:(CGPoint)offset glyphWidth:(CGFloat)glyphWidth
{
CGPoint currentLocation = _currentLocation;
NSNumber *dx = [self getNextDelta:_deltaXContext];
currentLocation.x += [dx floatValue];
NSNumber *dy = [self getNextDelta:_deltaYContext];
currentLocation.y += [dy floatValue];
for (NSUInteger i = 0; i < _locationContext.count; i++) {
CGPoint point = [[_locationContext objectAtIndex:i] CGPointValue];
point.x += [dx floatValue];
point.y += [dy floatValue];
[_locationContext replaceObjectAtIndex:i withObject:[NSValue valueWithCGPoint:point]];
}
_currentLocation = currentLocation;
NSNumber *x = [NSNumber numberWithFloat:currentLocation.x + offset.x + glyphWidth];
[_xContext replaceObjectAtIndex:_xContext.count - 1 withObject:x];
return CGPointMake(currentLocation.x + offset.x, currentLocation.y + offset.y);
}
- (NSNumber *)getNextDelta:(NSMutableArray *)deltaContext
{
NSNumber *value;
NSUInteger index = deltaContext.count;
for (NSArray *delta in [deltaContext reverseObjectEnumerator]) {
index--;
if (value == nil) {
value = [delta firstObject];
}
if (delta.count) {
NSMutableArray *mutableDelta = [delta mutableCopy];
[mutableDelta removeObjectAtIndex:0];
[deltaContext replaceObjectAtIndex:index withObject:[mutableDelta copy]];
}
}
return value;
}
- (CTFontRef)getGlyphFont
{
NSString *fontFamily;
NSNumber *fontSize;
NSString *fontWeight;
NSString *fontStyle;
NSNumberFormatter *f = [[NSNumberFormatter alloc] init];
f.numberStyle = NSNumberFormatterDecimalStyle;
for (NSDictionary *font in [_fontContext reverseObjectEnumerator]) {
if (!fontFamily) {
fontFamily = font[@"fontFamily"];
}
if (fontSize == nil) {
fontSize = [f numberFromString:font[@"fontSize"]];
}
if (!fontWeight) {
fontWeight = font[@"fontWeight"];
}
if (!fontStyle) {
fontStyle = font[@"fontStyle"];
}
if (fontFamily && fontSize && fontWeight && fontStyle) {
break;
}
}
BOOL fontFamilyFound = NO;
NSArray *supportedFontFamilyNames = [UIFont familyNames];
if ([supportedFontFamilyNames containsObject:fontFamily]) {
fontFamilyFound = YES;
} else {
for (NSString *fontFamilyName in supportedFontFamilyNames) {
if ([[UIFont fontNamesForFamilyName: fontFamilyName] containsObject:fontFamily]) {
fontFamilyFound = YES;
break;
}
}
}
fontFamily = fontFamilyFound ? fontFamily : nil;
return (__bridge CTFontRef)[RCTFont updateFont:nil
withFamily:fontFamily
size:fontSize
weight:fontWeight
style:fontStyle
variant:nil
scaleMultiplier:1.0];
}
@end
+497 -456
View File
File diff suppressed because it is too large Load Diff
-1
View File
@@ -25,6 +25,5 @@
- (void)releaseCachedPath;
- (CGPathRef)getGroupPath:(CGContextRef)context;
- (CTFontRef)getFontFromContext;
- (CGPoint)getGlyphPointFromContext:(CGPoint)offset glyphWidth:(CGFloat)glyphWidth;
@end
-21
View File
@@ -10,14 +10,12 @@
#import "RNSVGTextPath.h"
#import <React/RCTFont.h>
#import <CoreText/CoreText.h>
#import "RNSVGGlyphContext.h"
#import "GlyphContext.h"
@implementation RNSVGText
{
RNSVGText *_textRoot;
GlyphContext *_glyphContext;
RNSVGGlyphContext *_RNSVGGlyphContext;
}
- (void)renderLayerTo:(CGContextRef)context
@@ -40,8 +38,6 @@
{
_glyphContext = [[GlyphContext alloc] initWithScale:1 width:[self getContextWidth]
height:[self getContextHeight]];
_RNSVGGlyphContext = [[RNSVGGlyphContext alloc] initWithDimensions:[self getContextWidth]
height:[self getContextHeight]];
}
// release the cached CGPathRef for RNSVGTSpan
@@ -95,11 +91,6 @@
return _textRoot;
}
- (RNSVGGlyphContext *)getRNSVGGlyphContext
{
return _RNSVGGlyphContext;
}
- (GlyphContext *)getGlyphContext
{
return _glyphContext;
@@ -107,12 +98,6 @@
- (void)pushGlyphContext
{
/*
[[[self getTextRoot] getRNSVGGlyphContext] pushContext:self.font
deltaX:self.deltaX
deltaY:self.deltaY
positionX:self.positionX
positionY:self.positionY];*/
[[[self getTextRoot] getGlyphContext] pushContextwithRNSVGText:self
reset:false
font:self.font
@@ -125,7 +110,6 @@
- (void)popGlyphContext
{
//[[[self getTextRoot] getRNSVGGlyphContext] popContext];
[[[self getTextRoot] getGlyphContext] popContext];
}
@@ -134,9 +118,4 @@
return [[[self getTextRoot] getGlyphContext] getGlyphFont];
}
- (CGPoint)getGlyphPointFromContext:(CGPoint)offset glyphWidth:(CGFloat)glyphWidth
{
return [[[self getTextRoot] getRNSVGGlyphContext] getNextGlyphPoint:(CGPoint)offset glyphWidth:glyphWidth];
}
@end
-2
View File
@@ -9,7 +9,6 @@
#import <Foundation/Foundation.h>
#import <CoreText/CoreText.h>
#import "RNSVGText.h"
#import "RNSVGBezierTransformer.h"
@interface RNSVGTextPath : RNSVGText
@@ -21,6 +20,5 @@
@property (nonatomic, strong) NSString *startOffset;
- (RNSVGPath *)getPath;
- (RNSVGBezierTransformer *)getBezierTransformer;
@end
-9
View File
@@ -8,7 +8,6 @@
#import "RNSVGTextPath.h"
#import "RNSVGBezierTransformer.h"
@implementation RNSVGTextPath
@@ -31,14 +30,6 @@
return path;
}
- (RNSVGBezierTransformer *)getBezierTransformer
{
RNSVGPath *path = [self getPath];
CGFloat startOffset = [self relativeOnWidth:self.startOffset];
return [[RNSVGBezierTransformer alloc] initWithBezierCurvesAndStartOffset:[path getBezierCurves]
startOffset:startOffset];
}
- (CGPathRef)getPath:(CGContextRef)context
{
return [self getGroupPath:context];