Port new GlyphContext, FontData, enums, props, Bezier and text rendering

This commit is contained in:
Mikael Sand
2017-08-23 05:41:35 +03:00
parent 3cafc34cb2
commit ccb8729917
105 changed files with 10437 additions and 105 deletions

View File

@@ -12,6 +12,7 @@
#import "RNSVGCGFCRule.h" #import "RNSVGCGFCRule.h"
#import "RNSVGSvgView.h" #import "RNSVGSvgView.h"
#import "RNSVGPath.h" #import "RNSVGPath.h"
#import "GlyphContext.h"
#import "RNSVGGlyphContext.h" #import "RNSVGGlyphContext.h"
@interface RNSVGGroup : RNSVGPath <RNSVGContainer> @interface RNSVGGroup : RNSVGPath <RNSVGContainer>
@@ -21,7 +22,8 @@
- (void)renderPathTo:(CGContextRef)context; - (void)renderPathTo:(CGContextRef)context;
- (void)renderGroupTo:(CGContextRef)context; - (void)renderGroupTo:(CGContextRef)context;
- (RNSVGGlyphContext *)getGlyphContext; - (RNSVGGlyphContext *)getRNSVGGlyphContext;
- (GlyphContext *)getGlyphContext;
- (void)pushGlyphContext; - (void)pushGlyphContext;
- (void)popGlyphContext; - (void)popGlyphContext;
@end @end

View File

@@ -10,7 +10,8 @@
@implementation RNSVGGroup @implementation RNSVGGroup
{ {
RNSVGGlyphContext *_glyphContext; GlyphContext *_glyphContext;
RNSVGGlyphContext *_RNSVGGlyphContext;
} }
- (void)renderLayerTo:(CGContextRef)context - (void)renderLayerTo:(CGContextRef)context
@@ -51,22 +52,31 @@
CGFloat width = CGRectGetWidth(clipBounds); CGFloat width = CGRectGetWidth(clipBounds);
CGFloat height = CGRectGetHeight(clipBounds); CGFloat height = CGRectGetHeight(clipBounds);
_glyphContext = [[RNSVGGlyphContext alloc] initWithDimensions:width _RNSVGGlyphContext = [[RNSVGGlyphContext alloc] initWithDimensions:width
height:height];
_glyphContext = [[GlyphContext alloc] initWithScale:1 width:width
height:height]; height:height];
} }
- (RNSVGGlyphContext *)getGlyphContext - (RNSVGGlyphContext *)getRNSVGGlyphContext
{
return _RNSVGGlyphContext;
}
- (GlyphContext *)getGlyphContext
{ {
return _glyphContext; return _glyphContext;
} }
- (void)pushGlyphContext - (void)pushGlyphContext
{ {
[[[self getTextRoot] getGlyphContext] pushContext:self.font]; [[[self getTextRoot] getRNSVGGlyphContext] pushContext:self.font];
[[[self getTextRoot] getGlyphContext] pushContextWithRNSVGGroup:self font:self.font];
} }
- (void)popGlyphContext - (void)popGlyphContext
{ {
[[[self getTextRoot] getRNSVGGlyphContext] popContext];
[[[self getTextRoot] getGlyphContext] popContext]; [[[self getTextRoot] getGlyphContext] popContext];
} }

26
ios/PerformanceBezier/.gitignore vendored Normal file
View File

@@ -0,0 +1,26 @@
# Xcode
#
build/
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata
*.xccheckout
*.moved-aside
DerivedData
*.hmap
*.ipa
*.xcuserstate
# CocoaPods
#
# We recommend against adding the Pods directory to your .gitignore. However
# you should judge for yourself, the pros and cons are mentioned at:
# http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control
#
# Pods/

View File

@@ -0,0 +1,319 @@
Creative Commons Legal Code
Attribution 3.0 Unported
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR
DAMAGES RESULTING FROM ITS USE.
License
THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE
COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY
COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS
AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED.
BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE
TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY
BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS
CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND
CONDITIONS.
1. Definitions
a. "Adaptation" means a work based upon the Work, or upon the Work and
other pre-existing works, such as a translation, adaptation,
derivative work, arrangement of music or other alterations of a
literary or artistic work, or phonogram or performance and includes
cinematographic adaptations or any other form in which the Work may be
recast, transformed, or adapted including in any form recognizably
derived from the original, except that a work that constitutes a
Collection will not be considered an Adaptation for the purpose of
this License. For the avoidance of doubt, where the Work is a musical
work, performance or phonogram, the synchronization of the Work in
timed-relation with a moving image ("synching") will be considered an
Adaptation for the purpose of this License.
b. "Collection" means a collection of literary or artistic works, such as
encyclopedias and anthologies, or performances, phonograms or
broadcasts, or other works or subject matter other than works listed
in Section 1(f) below, which, by reason of the selection and
arrangement of their contents, constitute intellectual creations, in
which the Work is included in its entirety in unmodified form along
with one or more other contributions, each constituting separate and
independent works in themselves, which together are assembled into a
collective whole. A work that constitutes a Collection will not be
considered an Adaptation (as defined above) for the purposes of this
License.
c. "Distribute" means to make available to the public the original and
copies of the Work or Adaptation, as appropriate, through sale or
other transfer of ownership.
d. "Licensor" means the individual, individuals, entity or entities that
offer(s) the Work under the terms of this License.
e. "Original Author" means, in the case of a literary or artistic work,
the individual, individuals, entity or entities who created the Work
or if no individual or entity can be identified, the publisher; and in
addition (i) in the case of a performance the actors, singers,
musicians, dancers, and other persons who act, sing, deliver, declaim,
play in, interpret or otherwise perform literary or artistic works or
expressions of folklore; (ii) in the case of a phonogram the producer
being the person or legal entity who first fixes the sounds of a
performance or other sounds; and, (iii) in the case of broadcasts, the
organization that transmits the broadcast.
f. "Work" means the literary and/or artistic work offered under the terms
of this License including without limitation any production in the
literary, scientific and artistic domain, whatever may be the mode or
form of its expression including digital form, such as a book,
pamphlet and other writing; a lecture, address, sermon or other work
of the same nature; a dramatic or dramatico-musical work; a
choreographic work or entertainment in dumb show; a musical
composition with or without words; a cinematographic work to which are
assimilated works expressed by a process analogous to cinematography;
a work of drawing, painting, architecture, sculpture, engraving or
lithography; a photographic work to which are assimilated works
expressed by a process analogous to photography; a work of applied
art; an illustration, map, plan, sketch or three-dimensional work
relative to geography, topography, architecture or science; a
performance; a broadcast; a phonogram; a compilation of data to the
extent it is protected as a copyrightable work; or a work performed by
a variety or circus performer to the extent it is not otherwise
considered a literary or artistic work.
g. "You" means an individual or entity exercising rights under this
License who has not previously violated the terms of this License with
respect to the Work, or who has received express permission from the
Licensor to exercise rights under this License despite a previous
violation.
h. "Publicly Perform" means to perform public recitations of the Work and
to communicate to the public those public recitations, by any means or
process, including by wire or wireless means or public digital
performances; to make available to the public Works in such a way that
members of the public may access these Works from a place and at a
place individually chosen by them; to perform the Work to the public
by any means or process and the communication to the public of the
performances of the Work, including by public digital performance; to
broadcast and rebroadcast the Work by any means including signs,
sounds or images.
i. "Reproduce" means to make copies of the Work by any means including
without limitation by sound or visual recordings and the right of
fixation and reproducing fixations of the Work, including storage of a
protected performance or phonogram in digital form or other electronic
medium.
2. Fair Dealing Rights. Nothing in this License is intended to reduce,
limit, or restrict any uses free from copyright or rights arising from
limitations or exceptions that are provided for in connection with the
copyright protection under copyright law or other applicable laws.
3. License Grant. Subject to the terms and conditions of this License,
Licensor hereby grants You a worldwide, royalty-free, non-exclusive,
perpetual (for the duration of the applicable copyright) license to
exercise the rights in the Work as stated below:
a. to Reproduce the Work, to incorporate the Work into one or more
Collections, and to Reproduce the Work as incorporated in the
Collections;
b. to create and Reproduce Adaptations provided that any such Adaptation,
including any translation in any medium, takes reasonable steps to
clearly label, demarcate or otherwise identify that changes were made
to the original Work. For example, a translation could be marked "The
original work was translated from English to Spanish," or a
modification could indicate "The original work has been modified.";
c. to Distribute and Publicly Perform the Work including as incorporated
in Collections; and,
d. to Distribute and Publicly Perform Adaptations.
e. For the avoidance of doubt:
i. Non-waivable Compulsory License Schemes. In those jurisdictions in
which the right to collect royalties through any statutory or
compulsory licensing scheme cannot be waived, the Licensor
reserves the exclusive right to collect such royalties for any
exercise by You of the rights granted under this License;
ii. Waivable Compulsory License Schemes. In those jurisdictions in
which the right to collect royalties through any statutory or
compulsory licensing scheme can be waived, the Licensor waives the
exclusive right to collect such royalties for any exercise by You
of the rights granted under this License; and,
iii. Voluntary License Schemes. The Licensor waives the right to
collect royalties, whether individually or, in the event that the
Licensor is a member of a collecting society that administers
voluntary licensing schemes, via that society, from any exercise
by You of the rights granted under this License.
The above rights may be exercised in all media and formats whether now
known or hereafter devised. The above rights include the right to make
such modifications as are technically necessary to exercise the rights in
other media and formats. Subject to Section 8(f), all rights not expressly
granted by Licensor are hereby reserved.
4. Restrictions. The license granted in Section 3 above is expressly made
subject to and limited by the following restrictions:
a. You may Distribute or Publicly Perform the Work only under the terms
of this License. You must include a copy of, or the Uniform Resource
Identifier (URI) for, this License with every copy of the Work You
Distribute or Publicly Perform. You may not offer or impose any terms
on the Work that restrict the terms of this License or the ability of
the recipient of the Work to exercise the rights granted to that
recipient under the terms of the License. You may not sublicense the
Work. You must keep intact all notices that refer to this License and
to the disclaimer of warranties with every copy of the Work You
Distribute or Publicly Perform. When You Distribute or Publicly
Perform the Work, You may not impose any effective technological
measures on the Work that restrict the ability of a recipient of the
Work from You to exercise the rights granted to that recipient under
the terms of the License. This Section 4(a) applies to the Work as
incorporated in a Collection, but this does not require the Collection
apart from the Work itself to be made subject to the terms of this
License. If You create a Collection, upon notice from any Licensor You
must, to the extent practicable, remove from the Collection any credit
as required by Section 4(b), as requested. If You create an
Adaptation, upon notice from any Licensor You must, to the extent
practicable, remove from the Adaptation any credit as required by
Section 4(b), as requested.
b. If You Distribute, or Publicly Perform the Work or any Adaptations or
Collections, You must, unless a request has been made pursuant to
Section 4(a), keep intact all copyright notices for the Work and
provide, reasonable to the medium or means You are utilizing: (i) the
name of the Original Author (or pseudonym, if applicable) if supplied,
and/or if the Original Author and/or Licensor designate another party
or parties (e.g., a sponsor institute, publishing entity, journal) for
attribution ("Attribution Parties") in Licensor's copyright notice,
terms of service or by other reasonable means, the name of such party
or parties; (ii) the title of the Work if supplied; (iii) to the
extent reasonably practicable, the URI, if any, that Licensor
specifies to be associated with the Work, unless such URI does not
refer to the copyright notice or licensing information for the Work;
and (iv) , consistent with Section 3(b), in the case of an Adaptation,
a credit identifying the use of the Work in the Adaptation (e.g.,
"French translation of the Work by Original Author," or "Screenplay
based on original Work by Original Author"). The credit required by
this Section 4 (b) may be implemented in any reasonable manner;
provided, however, that in the case of a Adaptation or Collection, at
a minimum such credit will appear, if a credit for all contributing
authors of the Adaptation or Collection appears, then as part of these
credits and in a manner at least as prominent as the credits for the
other contributing authors. For the avoidance of doubt, You may only
use the credit required by this Section for the purpose of attribution
in the manner set out above and, by exercising Your rights under this
License, You may not implicitly or explicitly assert or imply any
connection with, sponsorship or endorsement by the Original Author,
Licensor and/or Attribution Parties, as appropriate, of You or Your
use of the Work, without the separate, express prior written
permission of the Original Author, Licensor and/or Attribution
Parties.
c. Except as otherwise agreed in writing by the Licensor or as may be
otherwise permitted by applicable law, if You Reproduce, Distribute or
Publicly Perform the Work either by itself or as part of any
Adaptations or Collections, You must not distort, mutilate, modify or
take other derogatory action in relation to the Work which would be
prejudicial to the Original Author's honor or reputation. Licensor
agrees that in those jurisdictions (e.g. Japan), in which any exercise
of the right granted in Section 3(b) of this License (the right to
make Adaptations) would be deemed to be a distortion, mutilation,
modification or other derogatory action prejudicial to the Original
Author's honor and reputation, the Licensor will waive or not assert,
as appropriate, this Section, to the fullest extent permitted by the
applicable national law, to enable You to reasonably exercise Your
right under Section 3(b) of this License (right to make Adaptations)
but not otherwise.
5. Representations, Warranties and Disclaimer
UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR
OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY
KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE,
INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY,
FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF
LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS,
WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION
OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU.
6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE
LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR
ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES
ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS
BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
7. Termination
a. This License and the rights granted hereunder will terminate
automatically upon any breach by You of the terms of this License.
Individuals or entities who have received Adaptations or Collections
from You under this License, however, will not have their licenses
terminated provided such individuals or entities remain in full
compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will
survive any termination of this License.
b. Subject to the above terms and conditions, the license granted here is
perpetual (for the duration of the applicable copyright in the Work).
Notwithstanding the above, Licensor reserves the right to release the
Work under different license terms or to stop distributing the Work at
any time; provided, however that any such election will not serve to
withdraw this License (or any other license that has been, or is
required to be, granted under the terms of this License), and this
License will continue in full force and effect unless terminated as
stated above.
8. Miscellaneous
a. Each time You Distribute or Publicly Perform the Work or a Collection,
the Licensor offers to the recipient a license to the Work on the same
terms and conditions as the license granted to You under this License.
b. Each time You Distribute or Publicly Perform an Adaptation, Licensor
offers to the recipient a license to the original Work on the same
terms and conditions as the license granted to You under this License.
c. If any provision of this License is invalid or unenforceable under
applicable law, it shall not affect the validity or enforceability of
the remainder of the terms of this License, and without further action
by the parties to this agreement, such provision shall be reformed to
the minimum extent necessary to make such provision valid and
enforceable.
d. No term or provision of this License shall be deemed waived and no
breach consented to unless such waiver or consent shall be in writing
and signed by the party to be charged with such waiver or consent.
e. This License constitutes the entire agreement between the parties with
respect to the Work licensed here. There are no understandings,
agreements or representations with respect to the Work not specified
here. Licensor shall not be bound by any additional provisions that
may appear in any communication from You. This License may not be
modified without the mutual written agreement of the Licensor and You.
f. The rights granted under, and the subject matter referenced, in this
License were drafted utilizing the terminology of the Berne Convention
for the Protection of Literary and Artistic Works (as amended on
September 28, 1979), the Rome Convention of 1961, the WIPO Copyright
Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996
and the Universal Copyright Convention (as revised on July 24, 1971).
These rights and subject matter take effect in the relevant
jurisdiction in which the License terms are sought to be enforced
according to the corresponding provisions of the implementation of
those treaty provisions in the applicable national law. If the
standard suite of rights granted under applicable copyright law
includes additional rights not granted under this License, such
additional rights are deemed to be included in the License; this
License is not intended to restrict the license of any rights under
applicable law.
Creative Commons Notice
Creative Commons is not a party to this License, and makes no warranty
whatsoever in connection with the Work. Creative Commons will not be
liable to You or any party on any legal theory for any damages
whatsoever, including without limitation any general, special,
incidental or consequential damages arising in connection to this
license. Notwithstanding the foregoing two (2) sentences, if Creative
Commons has expressly identified itself as the Licensor hereunder, it
shall have all rights and obligations of Licensor.
Except for the limited purpose of indicating to the public that the
Work is licensed under the CCPL, Creative Commons does not authorize
the use by either party of the trademark "Creative Commons" or any
related trademark or logo of Creative Commons without the prior
written consent of Creative Commons. Any permitted use will be in
compliance with Creative Commons' then-current trademark usage
guidelines, as may be published on its website or otherwise made
available upon request from time to time. For the avoidance of doubt,
this trademark restriction does not form part of this License.
Creative Commons may be contacted at https://creativecommons.org/.

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
</dict>
</plist>

View File

@@ -0,0 +1,13 @@
// JRSwizzle.h semver:1.0
// Copyright (c) 2007-2011 Jonathan 'Wolf' Rentzsch: http://rentzsch.com
// Some rights reserved: http://opensource.org/licenses/MIT
// https://github.com/rentzsch/jrswizzle
#import <Foundation/Foundation.h>
@interface NSObject (JRSwizzle)
+ (BOOL)jr_swizzleMethod:(SEL)origSel_ withMethod:(SEL)altSel_ error:(NSError**)error_;
+ (BOOL)jr_swizzleClassMethod:(SEL)origSel_ withClassMethod:(SEL)altSel_ error:(NSError**)error_;
@end

View File

@@ -0,0 +1,134 @@
// JRSwizzle.m semver:1.0
// Copyright (c) 2007-2011 Jonathan 'Wolf' Rentzsch: http://rentzsch.com
// Some rights reserved: http://opensource.org/licenses/MIT
// https://github.com/rentzsch/jrswizzle
#import "JRSwizzle.h"
#if TARGET_OS_IPHONE
#import <objc/runtime.h>
#import <objc/message.h>
#else
#import <objc/objc-class.h>
#endif
#define SetNSErrorFor(FUNC, ERROR_VAR, FORMAT,...) \
if (ERROR_VAR) { \
NSString *errStr = [NSString stringWithFormat:@"%s: " FORMAT,FUNC,##__VA_ARGS__]; \
*ERROR_VAR = [NSError errorWithDomain:@"NSCocoaErrorDomain" \
code:-1 \
userInfo:[NSDictionary dictionaryWithObject:errStr forKey:NSLocalizedDescriptionKey]]; \
}
#define SetNSError(ERROR_VAR, FORMAT,...) SetNSErrorFor(__func__, ERROR_VAR, FORMAT, ##__VA_ARGS__)
#if OBJC_API_VERSION >= 2
#define GetClass(obj) object_getClass(obj)
#else
#define GetClass(obj) (obj ? obj->isa : Nil)
#endif
@implementation NSObject (JRSwizzle)
+ (BOOL)jr_swizzleMethod:(SEL)origSel_ withMethod:(SEL)altSel_ error:(NSError**)error_ {
#if OBJC_API_VERSION >= 2
Method origMethod = class_getInstanceMethod(self, origSel_);
if (!origMethod) {
#if TARGET_OS_IPHONE
SetNSError(error_, @"original method %@ not found for class %@", NSStringFromSelector(origSel_), [self class]);
#else
SetNSError(error_, @"original method %@ not found for class %@", NSStringFromSelector(origSel_), [self className]);
#endif
return NO;
}
Method altMethod = class_getInstanceMethod(self, altSel_);
if (!altMethod) {
#if TARGET_OS_IPHONE
SetNSError(error_, @"alternate method %@ not found for class %@", NSStringFromSelector(altSel_), [self class]);
#else
SetNSError(error_, @"alternate method %@ not found for class %@", NSStringFromSelector(altSel_), [self className]);
#endif
return NO;
}
class_addMethod(self,
origSel_,
class_getMethodImplementation(self, origSel_),
method_getTypeEncoding(origMethod));
class_addMethod(self,
altSel_,
class_getMethodImplementation(self, altSel_),
method_getTypeEncoding(altMethod));
method_exchangeImplementations(class_getInstanceMethod(self, origSel_), class_getInstanceMethod(self, altSel_));
return YES;
#else
// Scan for non-inherited methods.
Method directOriginalMethod = NULL, directAlternateMethod = NULL;
void *iterator = NULL;
struct objc_method_list *mlist = class_nextMethodList(self, &iterator);
while (mlist) {
int method_index = 0;
for (; method_index < mlist->method_count; method_index++) {
if (mlist->method_list[method_index].method_name == origSel_) {
assert(!directOriginalMethod);
directOriginalMethod = &mlist->method_list[method_index];
}
if (mlist->method_list[method_index].method_name == altSel_) {
assert(!directAlternateMethod);
directAlternateMethod = &mlist->method_list[method_index];
}
}
mlist = class_nextMethodList(self, &iterator);
}
// If either method is inherited, copy it up to the target class to make it non-inherited.
if (!directOriginalMethod || !directAlternateMethod) {
Method inheritedOriginalMethod = NULL, inheritedAlternateMethod = NULL;
if (!directOriginalMethod) {
inheritedOriginalMethod = class_getInstanceMethod(self, origSel_);
if (!inheritedOriginalMethod) {
SetNSError(error_, @"original method %@ not found for class %@", NSStringFromSelector(origSel_), [self className]);
return NO;
}
}
if (!directAlternateMethod) {
inheritedAlternateMethod = class_getInstanceMethod(self, altSel_);
if (!inheritedAlternateMethod) {
SetNSError(error_, @"alternate method %@ not found for class %@", NSStringFromSelector(altSel_), [self className]);
return NO;
}
}
int hoisted_method_count = !directOriginalMethod && !directAlternateMethod ? 2 : 1;
struct objc_method_list *hoisted_method_list = malloc(sizeof(struct objc_method_list) + (sizeof(struct objc_method)*(hoisted_method_count-1)));
hoisted_method_list->obsolete = NULL; // soothe valgrind - apparently ObjC runtime accesses this value and it shows as uninitialized in valgrind
hoisted_method_list->method_count = hoisted_method_count;
Method hoisted_method = hoisted_method_list->method_list;
if (!directOriginalMethod) {
bcopy(inheritedOriginalMethod, hoisted_method, sizeof(struct objc_method));
directOriginalMethod = hoisted_method++;
}
if (!directAlternateMethod) {
bcopy(inheritedAlternateMethod, hoisted_method, sizeof(struct objc_method));
directAlternateMethod = hoisted_method;
}
class_addMethods(self, hoisted_method_list);
}
// Swizzle.
IMP temp = directOriginalMethod->method_imp;
directOriginalMethod->method_imp = directAlternateMethod->method_imp;
directAlternateMethod->method_imp = temp;
return YES;
#endif
}
+ (BOOL)jr_swizzleClassMethod:(SEL)origSel_ withClassMethod:(SEL)altSel_ error:(NSError**)error_ {
return [GetClass((id)self) jr_swizzleMethod:origSel_ withMethod:altSel_ error:error_];
}
@end

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<string>CFBundleDevelopmentRegion</string>
</plist>

View File

@@ -0,0 +1,20 @@
//
// PerformanceBezier.h
// PerformanceBezier
//
// Created by Adam Wulf on 2/1/15.
// Copyright (c) 2015 Milestone Made, LLC. All rights reserved.
//
#define CGPointNotFound CGPointMake(CGFLOAT_MAX, CGFLOAT_MAX)
#import <Foundation/Foundation.h>
#import "UIBezierPathProperties.h"
#import "UIBezierPath+Clockwise.h"
#import "UIBezierPath+Performance.h"
#import "UIBezierPath+NSOSX.h"
#import "UIBezierPath+Equals.h"
#import "UIBezierPath+Center.h"
#import "UIBezierPath+Trim.h"
#import "UIBezierPath+Util.h"
#import "UIBezierPath+Trimming.h"

View File

@@ -0,0 +1,17 @@
//
// UIBezierPath+Center.h
// ios-hand-shadows
//
// Created by Adam Wulf on 2/2/15.
// Copyright (c) 2015 Milestone Made. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface UIBezierPath (Center)
// returns a point in the center of
// the path's bounds
-(CGPoint) center;
@end

View File

@@ -0,0 +1,18 @@
//
// UIBezierPath+Center.m
// ios-hand-shadows
//
// Created by Adam Wulf on 2/2/15.
// Copyright (c) 2015 Milestone Made. All rights reserved.
//
#import "UIBezierPath+Center.h"
@implementation UIBezierPath (Center)
-(CGPoint) center{
return CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
}
@end

View File

@@ -0,0 +1,18 @@
//
// UIBezierPath+Clockwise.h
// PerformanceBezier
//
// Created by Adam Wulf on 1/7/14.
// Copyright (c) 2014 Milestone Made, LLC. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface UIBezierPath (Clockwise)
//
// returns YES if the path elements curve
// around in clockwise direction
-(BOOL) isClockwise;
@end

View File

@@ -0,0 +1,50 @@
//
// UIBezierPath+Clockwise.m
// PerformanceBezier
//
// Created by Adam Wulf on 1/7/14.
// Copyright (c) 2014 Milestone Made, LLC. All rights reserved.
//
#import "UIBezierPath+Clockwise.h"
#import "PerformanceBezier.h"
@implementation UIBezierPath (Clockwise)
-(BOOL) isClockwise{
__block CGPoint lastMoveTo = CGPointZero;
__block CGPoint lastPoint = CGPointZero;
__block CGFloat sum = 0;
[self iteratePathWithBlock:^(CGPathElement element, NSUInteger idx){
if(element.type == kCGPathElementMoveToPoint){
lastMoveTo = element.points[0];
lastPoint = lastMoveTo;
}else if(element.type == kCGPathElementAddLineToPoint){
sum += [self calculateAreaFor:element.points[0] andPoint:lastPoint];
lastPoint = element.points[0];
}else if(element.type == kCGPathElementAddQuadCurveToPoint){
sum += [self calculateAreaFor:element.points[0] andPoint:lastPoint];
sum += [self calculateAreaFor:element.points[1] andPoint:element.points[0]];
lastPoint = element.points[1];
}else if(element.type == kCGPathElementAddCurveToPoint){
sum += [self calculateAreaFor:element.points[0] andPoint:lastPoint];
sum += [self calculateAreaFor:element.points[1] andPoint:element.points[0]];
sum += [self calculateAreaFor:element.points[2] andPoint:element.points[1]];
lastPoint = element.points[2];
}else if(element.type == kCGPathElementCloseSubpath){
sum += [self calculateAreaFor:lastMoveTo andPoint:lastPoint];
lastPoint = element.points[0];
lastPoint = lastMoveTo;
}
}];
return sum >= 0;
}
- (CGFloat) calculateAreaFor:(CGPoint)point1 andPoint:(CGPoint)point2{
return (point2.x - point1.x) * (point2.y + point1.y);
}
@end

View File

@@ -0,0 +1,13 @@
//
// UIBezierPath+Description.h
// LooseLeaf
//
// Created by Adam Wulf on 12/17/13.
// Copyright (c) 2013 Milestone Made, LLC. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface UIBezierPath (Description)
@end

View File

@@ -0,0 +1,57 @@
//
// UIBezierPath+Description.m
// LooseLeaf
//
// Created by Adam Wulf on 12/17/13.
// Copyright (c) 2013 Milestone Made, LLC. All rights reserved.
//
#import "UIBezierPath+Description.h"
#import "UIBezierPath+NSOSX.h"
#import "JRSwizzle.h"
@implementation UIBezierPath (Description)
//
// create a human readable objective-c string for
// the path. this lets a dev easily print out the bezier
// from the debugger, and copy the result directly back into
// code. Perfect for printing out runtime generated beziers
// for use later in tests.
-(NSString*) swizzle_description{
__block NSString* str = @"path = [UIBezierPath bezierPath];\n";
[self iteratePathWithBlock:^(CGPathElement ele, NSUInteger idx){
if(ele.type == kCGPathElementAddCurveToPoint){
CGPoint curveTo = ele.points[2];
CGPoint ctrl1 = ele.points[0];
CGPoint ctrl2 = ele.points[1];
str = [str stringByAppendingFormat:@"[path addCurveToPoint:CGPointMake(%f, %f) controlPoint1:CGPointMake(%f, %f) controlPoint2:CGPointMake(%f, %f)];\n", curveTo.x, curveTo.y, ctrl1.x, ctrl1.y, ctrl2.x, ctrl2.y];
}else if(ele.type == kCGPathElementAddLineToPoint){
CGPoint lineTo = ele.points[0];
str = [str stringByAppendingFormat:@"[path addLineToPoint:CGPointMake(%f, %f)];\n", lineTo.x, lineTo.y];
}else if(ele.type == kCGPathElementAddQuadCurveToPoint){
CGPoint curveTo = ele.points[2];
CGPoint ctrl = ele.points[0];
str = [str stringByAppendingFormat:@"[path addQuadCurveToPoint:CGPointMake(%f, %f) controlPoint:CGPointMake(%f, %f)];\n", curveTo.x, curveTo.y, ctrl.x, ctrl.y];
}else if(ele.type == kCGPathElementCloseSubpath){
[self closePath];
str = [str stringByAppendingString:@"[path closePath];\n"];
}else if(ele.type == kCGPathElementMoveToPoint){
CGPoint moveTo = ele.points[0];
str = [str stringByAppendingFormat:@"[path moveToPoint:CGPointMake(%f, %f)];\n", moveTo.x, moveTo.y];
}
}];
return str;
}
+(void)load{
@autoreleasepool {
NSError *error = nil;
[UIBezierPath jr_swizzleMethod:@selector(description)
withMethod:@selector(swizzle_description)
error:&error];
}
}
@end

View File

@@ -0,0 +1,18 @@
//
// UIBezierPath+Equals.h
// LooseLeaf
//
// Created by Adam Wulf on 6/3/14.
// Copyright (c) 2014 Milestone Made, LLC. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface UIBezierPath (Equals)
// returns YES if the input path is equal
// to the current path. convenience wrapper
// around CGPathEqualToPath
-(BOOL) isEqualToBezierPath:(UIBezierPath*)path;
@end

View File

@@ -0,0 +1,18 @@
//
// UIBezierPath+Debug.m
// LooseLeaf
//
// Created by Adam Wulf on 6/3/14.
// Copyright (c) 2014 Milestone Made, LLC. All rights reserved.
//
#import "UIBezierPath+Equals.h"
#import "PerformanceBezier.h"
@implementation UIBezierPath (Equals)
-(BOOL) isEqualToBezierPath:(UIBezierPath*)path{
return CGPathEqualToPath(self.CGPath, path.CGPath);
}
@end

View File

@@ -0,0 +1,21 @@
//
// UIBezierPath+FirstLast.h
// iOS-UIBezierPath-Performance
//
// Created by Adam Wulf on 2/1/15.
//
//
#import <UIKit/UIKit.h>
@interface UIBezierPath (FirstLast)
// calculates the first point of the path,
// useful if its not already cached
-(CGPoint) lastPointCalculated;
// calculates the last point of the path,
// useful if its not already cached
-(CGPoint) firstPointCalculated;
@end

View File

@@ -0,0 +1,61 @@
//
// UIBezierPath+FirstLast.m
// iOS-UIBezierPath-Performance
//
// Created by Adam Wulf on 2/1/15.
//
//
#import "UIBezierPath+FirstLast.h"
#import "UIBezierPath+NSOSX.h"
@implementation UIBezierPath (FirstLast)
-(CGPoint) lastPointCalculated{
__block CGPoint firstPoint = CGPointZero;
__block CGPoint lastPoint = CGPointZero;
[self iteratePathWithBlock:^(CGPathElement element, NSUInteger idx) {
CGPoint currPoint = CGPointZero;
if(element.type == kCGPathElementMoveToPoint){
currPoint = element.points[0];
firstPoint = currPoint;
}else if(element.type == kCGPathElementAddLineToPoint){
currPoint = element.points[0];
}else if(element.type == kCGPathElementCloseSubpath){
currPoint = firstPoint;
}else if(element.type == kCGPathElementAddCurveToPoint){
currPoint = element.points[2];
}else if(element.type == kCGPathElementAddQuadCurveToPoint){
currPoint = element.points[1];
}
if(idx == 0){
// path should've begun with a moveTo,
// but this is a sanity check for malformed
// paths
firstPoint = currPoint;
}
lastPoint = currPoint;
}];
return lastPoint;
}
-(CGPoint) firstPointCalculated{
__block CGPoint firstPoint = CGPointZero;
[self iteratePathWithBlock:^(CGPathElement element, NSUInteger idx) {
if(idx == 0){
if(element.type == kCGPathElementMoveToPoint ||
element.type == kCGPathElementAddLineToPoint){
firstPoint = element.points[0];
}else if(element.type == kCGPathElementCloseSubpath){
firstPoint = firstPoint;
}else if(element.type == kCGPathElementAddCurveToPoint){
firstPoint = element.points[2];
}else if(element.type == kCGPathElementAddQuadCurveToPoint){
firstPoint = element.points[1];
}
}
}];
return firstPoint;
}
@end

View File

@@ -0,0 +1,54 @@
//
// UIBezierPath+NSOSX.h
// PaintingSample
//
// Created by Adam Wulf on 10/5/12.
//
//
#import <UIKit/UIKit.h>
@interface UIBezierPath (NSOSX)
// A flattened version of the path object.
@property(nonatomic,readonly) UIBezierPath* bezierPathByFlatteningPath;
// returns the number of elements in this path
@property(nonatomic,readonly) NSInteger elementCount;
// YES if the path is made without curves, NO otherwise
@property(nonatomic,assign) BOOL isFlat;
-(UIBezierPath*) bezierPathByFlatteningPathAndImmutable:(BOOL)returnCopy;
// returns the element at the given index, and also
// fills the points[] array with the element's points.
// the points property of the CGPathElement is owned by
// the internal cache, so if you need the points, you should
// retrieve them through the points parameter.
- (CGPathElement)elementAtIndex:(NSInteger)index associatedPoints:(CGPoint[])points;
// returns the element at the given index. If you also need
// access to the element's points, then use the method above.
- (CGPathElement)elementAtIndex:(NSInteger)index;
// modifies the element at the index to have the input
// points associated with it. This allows modifying the
// path in place
- (void)setAssociatedPoints:(CGPoint[])points atIndex:(NSInteger)index;
// returns the bounds of the path including its control points
-(CGRect) controlPointBounds;
// iterate over each element in the path with the input block
-(void) iteratePathWithBlock:(void (^)(CGPathElement element,NSUInteger idx))block;
// helper method to return the number of points for any input element
// based on its type. ie, an element of type
// kCGPathElementAddCurveToPoint returns 3
+(NSInteger) numberOfPointsForElement:(CGPathElement)element;
// helper method to copy a path element to a new element.
// Note: you are responsible for calling free(yourElement.points)
// when you are done with its return value.
+(CGPathElement*) copyCGPathElement:(CGPathElement*)element;
@end

View File

@@ -0,0 +1,522 @@
//
// UIBezierPath+NSOSX.m
// PaintingSample
//
// Created by Adam Wulf on 10/5/12.
//
//
#import "UIBezierPath+NSOSX.h"
#import <objc/runtime.h>
#import "JRSwizzle.h"
#import "UIBezierPath+NSOSX_Private.h"
#import "UIBezierPath+Performance_Private.h"
#import "UIBezierPath+Performance.h"
#import "UIBezierPath+Uncached.h"
#import "UIBezierPath+Util.h"
static char ELEMENT_ARRAY;
static CGFloat idealFlatness = .01;
@implementation UIBezierPath (NSOSX)
#pragma mark - Properties
/**
* this is a property on the category, as described in:
* https://github.com/techpaa/iProperties
*
*
* this array is for private PerformanceBezier use only
*
* Since iOS doesn't allow for index lookup of CGPath elements (only option is CGPathApply)
* this array will cache the elements after they've been looked up once
*/
-(void) freeCurrentElementCacheArray{
NSMutableArray* currentArray = objc_getAssociatedObject(self, &ELEMENT_ARRAY);
if([currentArray count]){
while([currentArray count]){
NSValue* val = [currentArray lastObject];
CGPathElement* element = [val pointerValue];
free(element->points);
free(element);
[currentArray removeLastObject];
}
}
objc_setAssociatedObject(self, &ELEMENT_ARRAY, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(void)setElementCacheArray:(NSMutableArray *)_elementCacheArray{
[self freeCurrentElementCacheArray];
NSMutableArray* newArray = [NSMutableArray array];
for(NSValue* val in _elementCacheArray){
CGPathElement* element = [val pointerValue];
CGPathElement* copiedElement = [UIBezierPath copyCGPathElement:element];
[newArray addObject:[NSValue valueWithPointer:copiedElement]];
}
objc_setAssociatedObject(self, &ELEMENT_ARRAY, newArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(NSMutableArray*)elementCacheArray{
NSMutableArray* ret = objc_getAssociatedObject(self, &ELEMENT_ARRAY);
if(!ret){
ret = [NSMutableArray array];
self.elementCacheArray = ret;
}
return ret;
}
#pragma mark - UIBezierPath
/**
* returns the CGPathElement at the specified index, optionally
* also returning the elements points in the 2nd parameter
*
* this method is meant to mimic UIBezierPath's method of the same name
*/
- (CGPathElement)elementAtIndex:(NSInteger)askingForIndex associatedPoints:(CGPoint[])points{
__block BOOL didReturn = NO;
__block CGPathElement returnVal;
if(askingForIndex < [self.elementCacheArray count]){
returnVal = *(CGPathElement*)[[self.elementCacheArray objectAtIndex:askingForIndex] pointerValue];
#ifdef MMPreventBezierPerformance
[self simulateNoBezierCaching];
#endif
}else{
__block UIBezierPath* this = self;
[self iteratePathWithBlock:^(CGPathElement element, NSUInteger currentIndex){
int numberInCache = (int) [this.elementCacheArray count];
if(!didReturn || currentIndex == [this.elementCacheArray count]){
if(currentIndex == numberInCache){
[this.elementCacheArray addObject:[NSValue valueWithPointer:[UIBezierPath copyCGPathElement:&element]]];
}
if(currentIndex == askingForIndex){
returnVal = *(CGPathElement*)[[this.elementCacheArray objectAtIndex:askingForIndex] pointerValue];
didReturn = YES;
}
}
}];
}
if(points){
for(int i=0;i<[UIBezierPath numberOfPointsForElement:returnVal];i++){
points[i] = returnVal.points[i];
}
}
return returnVal;
}
/**
* returns the CGPathElement at the specified index
*
* this method is meant to mimic UIBezierPath's method of the same name
*/
- (CGPathElement)elementAtIndex:(NSInteger)index{
return [self elementAtIndex:index associatedPoints:NULL];
}
/**
* updates the point in the path with the new input points
*
* TODO: this method is entirely untested
*/
- (void)setAssociatedPoints:(CGPoint[])points atIndex:(NSInteger)index{
NSMutableDictionary* params = [NSMutableDictionary dictionary];
[params setObject:[NSNumber numberWithInteger:index] forKey:@"index"];
[params setObject:[NSValue valueWithPointer:points] forKey:@"points"];
CGPathApply(self.CGPath, params, updatePathElementAtIndex);
}
//
// helper function for the setAssociatedPoints: method
void updatePathElementAtIndex(void* info, const CGPathElement* element) {
NSMutableDictionary* params = (NSMutableDictionary*)info;
int currentIndex = 0;
if([params objectForKey:@"curr"]){
currentIndex = [[params objectForKey:@"curr"] intValue] + 1;
}
if(currentIndex == [[params objectForKey:@"index"] intValue]){
CGPoint* points = [[params objectForKey:@"points"] pointerValue];
for(int i=0;i<[UIBezierPath numberOfPointsForElement:*element];i++){
element->points[i] = points[i];
}
CGPathElement* returnVal = [UIBezierPath copyCGPathElement:(CGPathElement*)element];
[params setObject:[NSValue valueWithPointer:returnVal] forKey:@"element"];
}
[params setObject:[NSNumber numberWithInt:currentIndex] forKey:@"curr"];
}
/**
* Returns the bounding box containing all points in a graphics path.
* The bounding box is the smallest rectangle completely enclosing
* all points in the path, including control points for Bézier and
* quadratic curves.
*
* this method is meant to mimic UIBezierPath's method of the same name
*/
-(CGRect) controlPointBounds{
return CGPathGetBoundingBox(self.CGPath);
}
- (NSInteger)elementCount{
UIBezierPathProperties* props = [self pathProperties];
if(props.cachedElementCount){
#ifdef MMPreventBezierPerformance
[self simulateNoBezierCaching];
#endif
return props.cachedElementCount;
}
NSMutableDictionary* params = [NSMutableDictionary dictionary];
[params setObject:[NSNumber numberWithInteger:0] forKey:@"count"];
[params setObject:self forKey:@"self"];
[self retain];
CGPathApply(self.CGPath, params, countPathElement);
[self release];
NSInteger ret = [[params objectForKey:@"count"] integerValue];
props.cachedElementCount = ret;
return ret;
}
// helper function
void countPathElement(void* info, const CGPathElement* element) {
NSMutableDictionary* params = (NSMutableDictionary*) info;
UIBezierPath* this = [params objectForKey:@"self"];
NSInteger count = [[params objectForKey:@"count"] integerValue];
[params setObject:[NSNumber numberWithInteger:(count + 1)] forKey:@"count"];
if(count == [this.elementCacheArray count]){
[this.elementCacheArray addObject:[NSValue valueWithPointer:[UIBezierPath copyCGPathElement:(CGPathElement*)element]]];
}
}
-(void) iteratePathWithBlock:(void (^)(CGPathElement element,NSUInteger idx))block{
void (^copiedBlock)(CGPathElement element) = [block copy];
NSMutableDictionary* params = [NSMutableDictionary dictionary];
[params setObject:copiedBlock forKey:@"block"];
CGPathApply(self.CGPath, params, blockWithElement);
[copiedBlock release];
}
// helper function
static void blockWithElement(void* info, const CGPathElement* element) {
NSMutableDictionary* params = (NSMutableDictionary*) info;
void (^block)(CGPathElement element,NSUInteger idx) = [params objectForKey:@"block"];
NSUInteger index = [[params objectForKey:@"index"] unsignedIntegerValue];
block(*element, index);
[params setObject:@(index+1) forKey:@"index"];
}
#pragma mark - Flat
#pragma mark - Properties
/**
* this is a property on the category, as described in:
* https://github.com/techpaa/iProperties
*/
-(void)setIsFlat:(BOOL)isFlat{
[self pathProperties].isFlat = isFlat;
}
/**
* return YES if this bezier path is made up of only
* moveTo, closePath, and lineTo elements
*
* TODO
* this method helps caching flattened paths internally
* to this category, but is not yet fit for public use.
*
* detecting when this path is flat would mean we'd have
* to also swizzle the constructors to bezier paths
*/
-(BOOL) isFlat{
return [self pathProperties].isFlat;
}
#pragma mark - UIBezierPath
/**
* call this method on a UIBezierPath to generate
* a new flattened path
*
* This category is named after Athar Luqman Ahmad, who
* wrote a masters thesis about minimizing the number of
* lines required to flatten a bezier curve
*
* The thesis is available here:
* http://www.cis.usouthal.edu/~hain/general/Theses/Ahmad_thesis.pdf
*
* The algorithm that I use as of 10/09/2012 is a simple
* recursive algorithm that doesn't use any of ahmed's
* optimizations yet
*
* TODO: add in Ahmed's optimizations
*/
-(UIBezierPath*) bezierPathByFlatteningPath{
return [self bezierPathByFlatteningPathAndImmutable:NO];
}
/**
* @param shouldBeImmutable: YES if this function should return a distinct UIBezier, NO otherwise
*
* if the caller plans to modify the returned path, then shouldBeImmutable should
* be called with NO.
*
* if the caller only plans to iterate over and look at the returned value,
* then shouldBeImmutable should be YES - this is considerably faster to not
* return a copy if the value will be treated as immutable
*/
-(UIBezierPath*) bezierPathByFlatteningPathAndImmutable:(BOOL)willBeImmutable{
UIBezierPathProperties* props = [self pathProperties];
UIBezierPath* ret = props.bezierPathByFlatteningPath;
if(ret){
if(willBeImmutable) return ret;
return [[ret copy] autorelease];
}
if(self.isFlat){
if(willBeImmutable) return self;
return [[self copy] autorelease];
}
__block NSInteger flattenedElementCount = 0;
UIBezierPath *newPath = [UIBezierPath bezierPath];
NSInteger elements = [self elementCount];
NSInteger n;
CGPoint pointForClose = CGPointMake (0.0, 0.0);
CGPoint lastPoint = CGPointMake (0.0, 0.0);
for (n = 0; n < elements; ++n)
{
CGPoint points[3];
CGPathElement element = [self elementAtIndex:n associatedPoints:points];
switch (element.type)
{
case kCGPathElementMoveToPoint:
[newPath moveToPoint:points[0]];
pointForClose = lastPoint = points[0];
flattenedElementCount++;
continue;
case kCGPathElementAddLineToPoint:
[newPath addLineToPoint:points[0]];
lastPoint = points[0];
flattenedElementCount++;
break;
case kCGPathElementAddQuadCurveToPoint:
case kCGPathElementAddCurveToPoint:
{
//
// handle both curve types gracefully
CGPoint curveTo;
CGPoint ctrl1;
CGPoint ctrl2;
if(element.type == kCGPathElementAddQuadCurveToPoint){
curveTo = element.points[1];
ctrl1 = element.points[0];
ctrl2 = ctrl1;
}else if(element.type == kCGPathElementAddCurveToPoint){
curveTo = element.points[2];
ctrl1 = element.points[0];
ctrl2 = element.points[1];
}
//
// ok, this is the bezier for our current element
CGPoint bezier[4] = { lastPoint, ctrl1, ctrl2, curveTo };
//
// define our recursive function that will
// help us split the curve up as needed
void (^__block flattenCurve)(UIBezierPath* newPath, CGPoint startPoint, CGPoint bez[4]) = ^(UIBezierPath* newPath, CGPoint startPoint, CGPoint bez[4]){
//
// first, calculate the error rate for
// a line segement between the start/end points
// vs the curve
CGPoint onCurve = bezierPointAtT(bez, .5);
CGFloat error = distanceOfPointToLine(onCurve, startPoint, bez[2]);
//
// if that error is less than our accepted
// level of error, then just add a line,
//
// otherwise, split the curve in half and recur
if (error <= idealFlatness)
{
[newPath addLineToPoint:bez[3]];
flattenedElementCount++;
}
else
{
CGPoint bez1[4], bez2[4];
subdivideBezierAtT(bez, bez1, bez2, .5);
// now we've split the curve in half, and have
// two bezier curves bez1 and bez2. recur
// on these two halves
flattenCurve(newPath, startPoint, bez1);
flattenCurve(newPath, startPoint, bez2);
}
};
flattenCurve(newPath, lastPoint, bezier);
lastPoint = points[2];
break;
}
case kCGPathElementCloseSubpath:
[newPath closePath];
lastPoint = pointForClose;
flattenedElementCount++;
break;
default:
break;
}
}
// since we just built the flattened path
// we know how many elements there are, so cache that
UIBezierPathProperties* newPathProps = [newPath pathProperties];
newPathProps.cachedElementCount = flattenedElementCount;
props.bezierPathByFlatteningPath = newPath;
return [self bezierPathByFlatteningPathAndImmutable:willBeImmutable];
}
#pragma mark - Helper
/**
* returns the length of the points array for the input
* CGPathElement element
*/
+(NSInteger) numberOfPointsForElement:(CGPathElement)element{
NSInteger nPoints = 0;
switch (element.type)
{
case kCGPathElementMoveToPoint:
nPoints = 1;
break;
case kCGPathElementAddLineToPoint:
nPoints = 1;
break;
case kCGPathElementAddQuadCurveToPoint:
nPoints = 2;
break;
case kCGPathElementAddCurveToPoint:
nPoints = 3;
break;
case kCGPathElementCloseSubpath:
nPoints = 0;
break;
default:
nPoints = 0;
}
return nPoints;
}
/**
* copies the input CGPathElement
*
* TODO: I currently never free the memory assigned for the points array
* https://github.com/adamwulf/DrawKit-iOS/issues/4
*/
+(CGPathElement*) copyCGPathElement:(CGPathElement*)element{
CGPathElement* ret = malloc(sizeof(CGPathElement));
if(!ret){
@throw [NSException exceptionWithName:@"Memory Exception" reason:@"can't malloc" userInfo:nil];
}
NSInteger numberOfPoints = [UIBezierPath numberOfPointsForElement:*element];
if(numberOfPoints){
ret->points = malloc(sizeof(CGPoint) * numberOfPoints);
}else{
ret->points = NULL;
}
ret->type = element->type;
for(int i=0;i<numberOfPoints;i++){
ret->points[i] = element->points[i];
}
return ret;
}
#pragma mark - Swizzling
///////////////////////////////////////////////////////////////////////////
//
// All of these methods are to listen to UIBezierPath method calls
// so that we can add new functionality on top of them without
// changing any of the default behavior.
//
// These methods help maintain:
// 1. cachedElementCount
// 2. elementCacheArray
// 3. keeping cache's valid across copying
-(void) nsosx_swizzle_removeAllPoints{
[self setElementCacheArray:nil];
[self nsosx_swizzle_removeAllPoints];
}
-(UIBezierPath*) nsosx_swizzle_copy{
UIBezierPath* ret = [self nsosx_swizzle_copy];
// note, when setting the array here, it will actually be making
// a mutable copy of the input array, so the copied
// path will have its own version.
[ret setElementCacheArray:self.elementCacheArray];
return ret;
}
-(void) nsosx_swizzle_applyTransform:(CGAffineTransform)transform{
[self setElementCacheArray:nil];
[self pathProperties].hasLastPoint = NO;
[self pathProperties].hasFirstPoint = NO;
[self nsosx_swizzle_applyTransform:transform];
}
-(void) nsosx_swizzle_dealloc{
[self freeCurrentElementCacheArray];
[self nsosx_swizzle_dealloc];
}
+(void)load{
@autoreleasepool {
NSError *error = nil;
[UIBezierPath jr_swizzleMethod:@selector(removeAllPoints)
withMethod:@selector(nsosx_swizzle_removeAllPoints)
error:&error];
[UIBezierPath jr_swizzleMethod:@selector(applyTransform:)
withMethod:@selector(nsosx_swizzle_applyTransform:)
error:&error];
[UIBezierPath jr_swizzleMethod:@selector(copy)
withMethod:@selector(nsosx_swizzle_copy)
error:&error];
[UIBezierPath jr_swizzleMethod:@selector(dealloc)
withMethod:@selector(nsosx_swizzle_dealloc)
error:&error];
}
}
@end

View File

@@ -0,0 +1,29 @@
//
// UIBezierPath+NSOSX_Private.h
// PerformanceBezier
//
// Created by Adam Wulf on 10/9/12.
// Copyright (c) 2012 Milestone Made, LLC. All rights reserved.
//
#ifndef PerformanceBezier_UIBezierPath_NSOSX_Private_h
#define PerformanceBezier_UIBezierPath_NSOSX_Private_h
@interface UIBezierPath (NSOSX_Private)
// cache of path elements
@property(nonatomic,retain) NSMutableArray* elementCacheArray;
// cache of element count
@property(nonatomic,assign) NSInteger cachedElementCount;
// helper functions to prime the above caches
void countPathElement(void* info, const CGPathElement* element);
void updatePathElementAtIndex(void* info, const CGPathElement* element);
@end
#endif

View File

@@ -0,0 +1,49 @@
//
// UIBezierPath+Performance.h
// PerformanceBezier
//
// Created by Adam Wulf on 1/31/15.
// Copyright (c) 2015 Milestone Made, LLC. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "UIBezierPathProperties.h"
CGPoint bezierPointAtT(const CGPoint bez[4], CGFloat t);
@interface UIBezierPath (Performance)
-(UIBezierPathProperties*) pathProperties;
// returns the last point of the bezier path.
// if the path ends with a kCGPathElementClosed,
// then the first point of that subpath is returned
-(CGPoint) lastPoint;
// returns the first point of the bezier path
-(CGPoint) firstPoint;
// returns the tangent at the very end of the path
// in radians
-(CGFloat) tangentAtEnd;
// returns YES if the path is closed (or contains at least 1 closed subpath)
// returns NO otherwise
-(BOOL) isClosed;
// returns the tangent of the bezier path at the given t value
- (CGPoint) tangentOnPathAtElement:(NSInteger)elementIndex andTValue:(CGFloat)tVal;
// for the input bezier curve [start, ctrl1, ctrl2, end]
// return the point at the input T value
+(CGPoint) pointAtT:(CGFloat)t forBezier:(CGPoint*)bez;
// for the input bezier curve [start, ctrl1, ctrl2, end]
// return the tangent at the input T value
+(CGPoint) tangentAtT:(CGFloat)t forBezier:(CGPoint*)bez;
// fill the input point array with [start, ctrl1, ctrl2, end]
// for the element at the given index
-(void) fillBezier:(CGPoint[4])bezier forElement:(NSInteger)elementIndex;
@end

View File

@@ -0,0 +1,525 @@
//
// UIBezierPath+Performance.m
// PerformanceBezier
//
// Created by Adam Wulf on 1/31/15.
// Copyright (c) 2015 Milestone Made, LLC. All rights reserved.
//
#import "UIBezierPath+Performance.h"
#import "UIBezierPath+Performance_Private.h"
#import "UIBezierPath+FirstLast.h"
#import "UIBezierPath+NSOSX.h"
#import "UIBezierPath+Uncached.h"
#import <objc/runtime.h>
#import "JRSwizzle.h"
static char BEZIER_PROPERTIES;
@implementation UIBezierPath (Performance)
-(UIBezierPathProperties*) pathProperties{
UIBezierPathProperties* props = objc_getAssociatedObject(self, &BEZIER_PROPERTIES);
if(!props){
props = [[[UIBezierPathProperties alloc] init] autorelease];
objc_setAssociatedObject(self, &BEZIER_PROPERTIES, props, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return props;
}
-(void)setTangentAtEnd:(CGFloat)tangent{
[self pathProperties].tangentAtEnd = tangent;
}
-(CGPoint)lastPoint{
UIBezierPathProperties* props = [self pathProperties];
if(!props.hasLastPoint){
props.hasLastPoint = YES;
props.lastPoint = [self lastPointCalculated];
#ifdef MMPreventBezierPerformance
}else{
[self simulateNoBezierCaching];
#endif
}
return props.lastPoint;
}
-(CGPoint)firstPoint{
UIBezierPathProperties* props = [self pathProperties];
if(!props.hasFirstPoint){
props.hasFirstPoint = YES;
props.firstPoint = [self firstPointCalculated];
#ifdef MMPreventBezierPerformance
}else{
[self simulateNoBezierCaching];
#endif
}
return props.firstPoint;
}
-(BOOL) isClosed{
UIBezierPathProperties* props = [self pathProperties];
if(!props.knowsIfClosed){
// we dont know if the path is closed, so
// find a close element if we have one
[self iteratePathWithBlock:^(CGPathElement ele, NSUInteger idx){
if(ele.type == kCGPathElementCloseSubpath){
props.isClosed = YES;
}
}];
props.knowsIfClosed = YES;
#ifdef MMPreventBezierPerformance
}else{
[self simulateNoBezierCaching];
#endif
}
return props.isClosed;
}
-(CGFloat) tangentAtEnd{
#ifdef MMPreventBezierPerformance
[self simulateNoBezierCaching];
#endif
return [self pathProperties].tangentAtEnd;
}
/**
* this is a property on the category, as described in:
* https://github.com/techpaa/iProperties
*/
-(void)setBezierPathByFlatteningPath:(UIBezierPath *)bezierPathByFlatteningPath{
[self pathProperties].bezierPathByFlatteningPath = bezierPathByFlatteningPath;
}
/**
* this is a property on the category, as described in:
* https://github.com/techpaa/iProperties
*
*
* this is for internal PerformanceBezier use only
*
* Since iOS doesn't allow a quick lookup for element count,
* this property will act as a cache for the element count after
* it has been calculated once
*/
-(void)setCachedElementCount:(NSInteger)_cachedElementCount{
[self pathProperties].cachedElementCount = _cachedElementCount;
}
-(NSInteger)cachedElementCount{
return [self pathProperties].cachedElementCount;
}
-(void) fillBezier:(CGPoint[4])bezier forElement:(NSInteger)elementIndex{
if(elementIndex >= [self elementCount] || elementIndex < 0){
@throw [NSException exceptionWithName:@"BezierElementException" reason:@"Element index is out of range" userInfo:nil];
}
if(elementIndex == 0){
bezier[0] = self.firstPoint;
bezier[1] = self.firstPoint;
bezier[2] = self.firstPoint;
bezier[3] = self.firstPoint;
return;
}
CGPathElement previousElement = [self elementAtIndex:elementIndex-1];
CGPathElement thisElement = [self elementAtIndex:elementIndex];
if(previousElement.type == kCGPathElementMoveToPoint ||
previousElement.type == kCGPathElementAddLineToPoint){
bezier[0] = previousElement.points[0];
}else if(previousElement.type == kCGPathElementAddQuadCurveToPoint){
bezier[0] = previousElement.points[1];
}else if(previousElement.type == kCGPathElementAddCurveToPoint){
bezier[0] = previousElement.points[2];
}
if(thisElement.type == kCGPathElementCloseSubpath){
bezier[1] = bezier[0];
bezier[2] = self.firstPoint;
bezier[3] = self.firstPoint;
}else if (thisElement.type == kCGPathElementMoveToPoint ||
thisElement.type == kCGPathElementAddLineToPoint){
// bezier[1] = CGPointMake(bezier[0].x + (thisElement.points[0].x - bezier[0].x)/3,
// bezier[0].y + (thisElement.points[0].y - bezier[0].y)/3);
// bezier[2] = CGPointMake(bezier[0].x + (thisElement.points[0].x - bezier[0].x)*2/3,
// bezier[0].y + (thisElement.points[0].y - bezier[0].y)*2/3);
bezier[1] = bezier[0];
bezier[2] = thisElement.points[0];
bezier[3] = thisElement.points[0];
}else if (thisElement.type == kCGPathElementAddQuadCurveToPoint){
bezier[1] = thisElement.points[0];
bezier[2] = thisElement.points[0];
bezier[3] = thisElement.points[1];
}else if (thisElement.type == kCGPathElementAddCurveToPoint){
bezier[1] = thisElement.points[0];
bezier[2] = thisElement.points[1];
bezier[3] = thisElement.points[2];
}
}
- (CGPoint) tangentOnPathAtElement:(NSInteger)elementIndex andTValue:(CGFloat)tVal{
if(elementIndex >= [self elementCount] || elementIndex < 0){
@throw [NSException exceptionWithName:@"BezierElementException" reason:@"Element index is out of range" userInfo:nil];
}
if(elementIndex == 0){
return self.firstPoint;
}
CGPoint bezier[4];
[self fillBezier:bezier forElement:elementIndex];
return [UIBezierPath tangentAtT:tVal forBezier:bezier];
}
+(CGPoint) pointAtT:(CGFloat)t forBezier:(CGPoint*)bez{
return bezierPointAtT(bez, t);
}
+(CGPoint) tangentAtT:(CGFloat)t forBezier:(CGPoint*)bez{
return bezierTangentAtT(bez, t);
}
#pragma mark - Swizzle
///////////////////////////////////////////////////////////////////////////
//
// All of these methods are to listen to UIBezierPath method calls
// so that we can add new functionality on top of them without
// changing any of the default behavior.
//
// These methods help maintain:
// 1. the cached flat version of this path
// 2. the flag for if this path is already flat or not
-(void) ahmed_swizzle_dealloc{
objc_setAssociatedObject(self, &BEZIER_PROPERTIES, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
[self ahmed_swizzle_dealloc];
}
- (id)swizzle_initWithCoder:(NSCoder *)decoder{
self = [self swizzle_initWithCoder:decoder];
UIBezierPathProperties* props = [decoder decodeObjectForKey:@"pathProperties"];
objc_setAssociatedObject(self, &BEZIER_PROPERTIES, props, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
return self;
}
-(void) swizzle_encodeWithCoder:(NSCoder *)aCoder{
[self swizzle_encodeWithCoder:aCoder];
[aCoder encodeObject:self.pathProperties forKey:@"pathProperties"];
}
-(void) ahmed_swizzle_applyTransform:(CGAffineTransform)transform{
// reset our path properties
BOOL isClosed = [self pathProperties].isClosed;
UIBezierPathProperties* props = [[[UIBezierPathProperties alloc] init] autorelease];
props.isClosed = isClosed;
objc_setAssociatedObject(self, &BEZIER_PROPERTIES, props, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
[self ahmed_swizzle_applyTransform:transform];
}
-(void) swizzle_moveToPoint:(CGPoint)point{
UIBezierPathProperties* props = [self pathProperties];
props.bezierPathByFlatteningPath = nil;
BOOL isEmpty = [self isEmpty];
if(isEmpty || props.isFlat){
props.isFlat = YES;
}
if(isEmpty){
props.hasFirstPoint = YES;
props.firstPoint = point;
props.cachedElementCount = 1;
}else if(props.cachedElementCount){
if(!props.lastAddedElementWasMoveTo){
// when adding multiple moveTo elements to a path
// in a row, iOS actually just modifies the last moveTo
// instead of having tons of useless moveTos
props.cachedElementCount = props.cachedElementCount + 1;
}else if(props.cachedElementCount == 1){
// otherwise, the first and only point was
// a move to, so update our first point
props.firstPoint = point;
}
}
props.hasLastPoint = YES;
props.lastPoint = point;
props.tangentAtEnd = 0;
props.lastAddedElementWasMoveTo = YES;
[self swizzle_moveToPoint:point];
}
-(void) swizzle_addLineToPoint:(CGPoint)point{
UIBezierPathProperties* props = [self pathProperties];
props.lastAddedElementWasMoveTo = NO;
props.bezierPathByFlatteningPath = nil;
if([self isEmpty] || props.isFlat){
props.isFlat = YES;
}
if(props.cachedElementCount){
props.cachedElementCount = props.cachedElementCount + 1;
}
props.tangentAtEnd = [self calculateTangentBetween:point andPoint:props.lastPoint];
props.hasLastPoint = YES;
props.lastPoint = point;
[self swizzle_addLineToPoint:point];
}
-(void) swizzle_addCurveToPoint:(CGPoint)point controlPoint1:(CGPoint)ctrl1 controlPoint2:(CGPoint)ctrl2{
UIBezierPathProperties* props = [self pathProperties];
props.lastAddedElementWasMoveTo = NO;
props.bezierPathByFlatteningPath = nil;
if([self isEmpty] || props.isFlat){
props.isFlat = NO;
}
if(props.cachedElementCount){
props.cachedElementCount = props.cachedElementCount + 1;
}
props.tangentAtEnd = [self calculateTangentBetween:point andPoint:ctrl2];
props.hasLastPoint = YES;
props.lastPoint = point;
[self swizzle_addCurveToPoint:point controlPoint1:ctrl1 controlPoint2:ctrl2];
}
-(void) swizzle_quadCurveToPoint:(CGPoint)point controlPoint:(CGPoint)ctrl1{
UIBezierPathProperties* props = [self pathProperties];
props.lastAddedElementWasMoveTo = NO;
props.bezierPathByFlatteningPath = nil;
if([self isEmpty] || props.isFlat){
props.isFlat = NO;
}
if(props.cachedElementCount){
props.cachedElementCount = props.cachedElementCount + 1;
}
props.hasLastPoint = YES;
props.lastPoint = point;
props.tangentAtEnd = [self calculateTangentBetween:point andPoint:ctrl1];
[self swizzle_quadCurveToPoint:point controlPoint:ctrl1];
}
-(void) swizzle_closePath{
UIBezierPathProperties* props = [self pathProperties];
props.isClosed = YES;
props.knowsIfClosed = YES;
props.lastAddedElementWasMoveTo = NO;
props.bezierPathByFlatteningPath = nil;
if([self isEmpty] || props.isFlat){
props.isFlat = YES;
}
if(props.cachedElementCount){
props.cachedElementCount = props.cachedElementCount + 1;
}
if(props.hasLastPoint && props.hasFirstPoint){
props.lastPoint = props.firstPoint;
}else{
props.hasLastPoint = NO;
}
[self swizzle_closePath];
}
-(void)swizzle_arcWithCenter:(CGPoint)center radius:(CGFloat)radius startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle clockwise:(BOOL)clockwise{
UIBezierPathProperties* props = [self pathProperties];
props.lastAddedElementWasMoveTo = NO;
props.bezierPathByFlatteningPath = nil;
if([self isEmpty] || props.isFlat){
props.isFlat = NO;
}
if(props.cachedElementCount){
props.cachedElementCount = props.cachedElementCount + 1;
}
[self swizzle_arcWithCenter:center radius:radius startAngle:startAngle endAngle:endAngle clockwise:clockwise];
}
-(void) swizzle_removeAllPoints{
UIBezierPathProperties* props = [self pathProperties];
props.lastAddedElementWasMoveTo = NO;
props.bezierPathByFlatteningPath = nil;
[self swizzle_removeAllPoints];
props.cachedElementCount = 0;
props.tangentAtEnd = 0;
props.hasLastPoint = NO;
props.hasFirstPoint = NO;
props.isClosed = NO;
props.knowsIfClosed = YES;
if([self isEmpty] || props.isFlat){
props.isFlat = YES;
}
}
- (void)swizzle_appendPath:(UIBezierPath *)bezierPath{
UIBezierPathProperties* props = [self pathProperties];
props.lastAddedElementWasMoveTo = NO;
UIBezierPathProperties* bezierPathProps = [bezierPath pathProperties];
props.bezierPathByFlatteningPath = nil;
if(([self isEmpty] && bezierPathProps.isFlat) || (props.isFlat && bezierPathProps.isFlat)){
props.isFlat = YES;
}else{
props.isFlat = NO;
}
[self swizzle_appendPath:bezierPath];
props.hasLastPoint = bezierPathProps.hasLastPoint;
props.lastPoint = bezierPathProps.lastPoint;
props.tangentAtEnd = bezierPathProps.tangentAtEnd;
props.cachedElementCount = 0;
}
-(UIBezierPath*) swizzle_copy{
UIBezierPathProperties* props = [self pathProperties];
UIBezierPath* ret = [self swizzle_copy];
CGMutablePathRef pathRef = CGPathCreateMutableCopy(self.CGPath);
ret.CGPath = pathRef;
CGPathRelease(pathRef);
UIBezierPathProperties* retProps = [ret pathProperties];
retProps.lastAddedElementWasMoveTo = props.lastAddedElementWasMoveTo;
retProps.isFlat = props.isFlat;
retProps.hasLastPoint = props.hasLastPoint;
retProps.lastPoint = props.lastPoint;
retProps.hasFirstPoint = props.hasFirstPoint;
retProps.firstPoint = props.firstPoint;
retProps.tangentAtEnd = props.tangentAtEnd;
retProps.cachedElementCount = props.cachedElementCount;
retProps.isClosed = props.isClosed;
return ret;
}
+(UIBezierPath*) swizzle_bezierPathWithRect:(CGRect)rect{
UIBezierPath* path = [UIBezierPath swizzle_bezierPathWithRect:rect];
UIBezierPathProperties* props = [path pathProperties];
props.isFlat = YES;
props.knowsIfClosed = YES;
props.isClosed = YES;
return path;
}
+(UIBezierPath*) swizzle_bezierPathWithOvalInRect:(CGRect)rect{
UIBezierPath* path = [UIBezierPath swizzle_bezierPathWithOvalInRect:rect];
UIBezierPathProperties* props = [path pathProperties];
props.isFlat = YES;
props.knowsIfClosed = YES;
props.isClosed = YES;
return path;
}
+(UIBezierPath*) swizzle_bezierPathWithRoundedRect:(CGRect)rect byRoundingCorners:(UIRectCorner)corners cornerRadii:(CGSize)cornerRadii{
UIBezierPath* path = [UIBezierPath swizzle_bezierPathWithRoundedRect:rect byRoundingCorners:corners cornerRadii:cornerRadii];
UIBezierPathProperties* props = [path pathProperties];
props.isFlat = YES;
props.knowsIfClosed = YES;
props.isClosed = YES;
return path;
}
+(UIBezierPath*) swizzle_bezierPathWithRoundedRect:(CGRect)rect cornerRadius:(CGFloat)cornerRadii{
UIBezierPath* path = [UIBezierPath swizzle_bezierPathWithRoundedRect:rect cornerRadius:cornerRadii];
UIBezierPathProperties* props = [path pathProperties];
props.isFlat = YES;
props.knowsIfClosed = YES;
props.isClosed = YES;
return path;
}
+(void)load{
@autoreleasepool {
NSError *error = nil;
[UIBezierPath jr_swizzleClassMethod:@selector(bezierPathWithRect:)
withClassMethod:@selector(swizzle_bezierPathWithRect:)
error:&error];
[UIBezierPath jr_swizzleClassMethod:@selector(bezierPathWithOvalInRect:)
withClassMethod:@selector(swizzle_bezierPathWithOvalInRect:)
error:&error];
[UIBezierPath jr_swizzleClassMethod:@selector(bezierPathWithRoundedRect:byRoundingCorners:cornerRadii:)
withClassMethod:@selector(swizzle_bezierPathWithRoundedRect:byRoundingCorners:cornerRadii:)
error:&error];
[UIBezierPath jr_swizzleClassMethod:@selector(bezierPathWithRoundedRect:cornerRadius:)
withClassMethod:@selector(swizzle_bezierPathWithRoundedRect:cornerRadius:)
error:&error];
[UIBezierPath jr_swizzleMethod:@selector(moveToPoint:)
withMethod:@selector(swizzle_moveToPoint:)
error:&error];
[UIBezierPath jr_swizzleMethod:@selector(addLineToPoint:)
withMethod:@selector(swizzle_addLineToPoint:)
error:&error];
[UIBezierPath jr_swizzleMethod:@selector(addCurveToPoint:controlPoint1:controlPoint2:)
withMethod:@selector(swizzle_addCurveToPoint:controlPoint1:controlPoint2:)
error:&error];
[UIBezierPath jr_swizzleMethod:@selector(addQuadCurveToPoint:controlPoint:)
withMethod:@selector(swizzle_quadCurveToPoint:controlPoint:)
error:&error];
[UIBezierPath jr_swizzleMethod:@selector(closePath)
withMethod:@selector(swizzle_closePath)
error:&error];
[UIBezierPath jr_swizzleMethod:@selector(addArcWithCenter:radius:startAngle:endAngle:clockwise:)
withMethod:@selector(swizzle_arcWithCenter:radius:startAngle:endAngle:clockwise:)
error:&error];
[UIBezierPath jr_swizzleMethod:@selector(removeAllPoints)
withMethod:@selector(swizzle_removeAllPoints)
error:&error];
[UIBezierPath jr_swizzleMethod:@selector(appendPath:)
withMethod:@selector(swizzle_appendPath:)
error:&error];
[UIBezierPath jr_swizzleMethod:@selector(copy)
withMethod:@selector(swizzle_copy)
error:&error];
[UIBezierPath jr_swizzleMethod:@selector(initWithCoder:)
withMethod:@selector(swizzle_initWithCoder:)
error:&error];
[UIBezierPath jr_swizzleMethod:@selector(encodeWithCoder:)
withMethod:@selector(swizzle_encodeWithCoder:)
error:&error];
[UIBezierPath jr_swizzleMethod:@selector(applyTransform:)
withMethod:@selector(ahmed_swizzle_applyTransform:)
error:&error];
[UIBezierPath jr_swizzleMethod:@selector(dealloc)
withMethod:@selector(ahmed_swizzle_dealloc)
error:&error];
}
}
/**
* if a curve, the ctrlpoint should be point1, and end point is point2
* if line, prev point is point1, and end point is point2
*/
- (CGFloat) calculateTangentBetween:(CGPoint)point1 andPoint:(CGPoint)point2{
return atan2f( point1.y - point2.y, point1.x - point2.x );
}
/**
* calculate the point on a bezier at time t
* where 0 < t < 1
*/
CGPoint bezierPointAtT(const CGPoint bez[4], CGFloat t)
{
CGPoint q;
CGFloat mt = 1 - t;
CGPoint bez1[4];
CGPoint bez2[4];
q.x = mt * bez[1].x + t * bez[2].x;
q.y = mt * bez[1].y + t * bez[2].y;
bez1[1].x = mt * bez[0].x + t * bez[1].x;
bez1[1].y = mt * bez[0].y + t * bez[1].y;
bez2[2].x = mt * bez[2].x + t * bez[3].x;
bez2[2].y = mt * bez[2].y + t * bez[3].y;
bez1[2].x = mt * bez1[1].x + t * q.x;
bez1[2].y = mt * bez1[1].y + t * q.y;
bez2[1].x = mt * q.x + t * bez2[2].x;
bez2[1].y = mt * q.y + t * bez2[2].y;
bez1[3].x = bez2[0].x = mt * bez1[2].x + t * bez2[1].x;
bez1[3].y = bez2[0].y = mt * bez1[2].y + t * bez2[1].y;
return CGPointMake(bez1[3].x, bez1[3].y);
}
@end

View File

@@ -0,0 +1,26 @@
//
// UIBezierPath+Performance_Private.h
// PerformanceBezier
//
// Created by Adam Wulf on 10/16/12.
// Copyright (c) 2012 Milestone Made, LLC. All rights reserved.
//
#ifndef PerformanceBezier_UIBezierPath_Performance_Private_h
#define PerformanceBezier_UIBezierPath_Performance_Private_h
#import "UIBezierPathProperties.h"
@interface UIBezierPath (Performance_Private)
// helper functions for finding points and tangents
// on a bezier curve
CGPoint bezierPointAtT(const CGPoint bez[4], CGFloat t);
CGPoint bezierTangentAtT(const CGPoint bez[4], CGFloat t);
CGFloat bezierTangent(CGFloat t, CGFloat a, CGFloat b, CGFloat c, CGFloat d);
CGFloat dotProduct(const CGPoint p1, const CGPoint p2);
@end
#endif

View File

@@ -0,0 +1,35 @@
//
// UIBezierPath+Ahmed.h
// PerformanceBezier
//
// Created by Adam Wulf on 10/6/12.
// Copyright (c) 2012 Milestone Made, LLC. All rights reserved.
//
//
//
//
// This category is motivated by the masters thesis of Athar Ahmad
// available at http://www.cis.usouthal.edu/~hain/general/Theses/Ahmad_thesis.pdf
//
// More information available at
// http://www.cis.usouthal.edu/~hain/general/Thesis.htm
//
// subdivide code license in included SubdiviceLicense file
#import <UIKit/UIKit.h>
#include <math.h>
@interface UIBezierPath (Trim)
-(UIBezierPath*) bezierPathByTrimmingElement:(NSInteger)elementIndex fromTValue:(double)fromTValue toTValue:(double)toTValue;
-(UIBezierPath*) bezierPathByTrimmingToElement:(NSInteger)elementIndex andTValue:(double)tValue;
-(UIBezierPath*) bezierPathByTrimmingFromElement:(NSInteger)elementIndex andTValue:(double)tValue;
+(void) subdivideBezier:(const CGPoint[4])bez intoLeft:(CGPoint[4])bez1 andRight:(CGPoint[4])bez2 atT:(CGFloat)t;
+(void) subdivideBezier:(const CGPoint[4])bez intoLeft:(CGPoint[4])bez1 andRight:(CGPoint[4])bez2 atLength:(CGFloat)length withAcceptableError:(CGFloat)acceptableError withCache:(CGFloat*) subBezierlengthCache;
@end

View File

@@ -0,0 +1,209 @@
//
// UIBezierPath+Trim.m
// PerformanceBezier
//
// Created by Adam Wulf on 10/6/12.
// Copyright (c) 2012 Milestone Made, LLC. All rights reserved.
//
#import "UIBezierPath+Trim.h"
#import <objc/runtime.h>
#import <PerformanceBezier/PerformanceBezier.h>
@implementation UIBezierPath (Trim)
/**
* this will trim a specific element from a tvalue to a tvalue
*/
-(UIBezierPath*) bezierPathByTrimmingElement:(NSInteger)elementIndex fromTValue:(double)fromTValue toTValue:(double)toTValue{
__block CGPoint previousEndpoint;
__block UIBezierPath* outputPath = [UIBezierPath bezierPath];
[self iteratePathWithBlock:^(CGPathElement element, NSUInteger currentIndex){
if(currentIndex < elementIndex){
if(element.type == kCGPathElementMoveToPoint){
// moveto
previousEndpoint = element.points[0];
}else if(element.type == kCGPathElementAddCurveToPoint ){
// curve
previousEndpoint = element.points[2];
}else if(element.type == kCGPathElementAddLineToPoint){
// line
previousEndpoint = element.points[0];
}
}else if(currentIndex == elementIndex){
if(element.type == kCGPathElementMoveToPoint){
// moveto
previousEndpoint = element.points[0];
[outputPath moveToPoint:element.points[0]];
}else if(element.type == kCGPathElementAddCurveToPoint ){
// curve
CGPoint bez[4];
bez[0] = previousEndpoint;
bez[1] = element.points[0];
bez[2] = element.points[1];
bez[3] = element.points[2];
previousEndpoint = element.points[2];
CGPoint left[4], right[4];
subdivideBezierAtT(bez, left, right, toTValue);
bez[0] = left[0];
bez[1] = left[1];
bez[2] = left[2];
bez[3] = left[3];
subdivideBezierAtT(bez, left, right, fromTValue / toTValue);
[outputPath moveToPoint:right[0]];
[outputPath addCurveToPoint:right[3] controlPoint1:right[1] controlPoint2:right[2]];
}else if(element.type == kCGPathElementAddLineToPoint){
// line
CGPoint startPoint = CGPointMake(previousEndpoint.x + fromTValue * (element.points[0].x - previousEndpoint.x),
previousEndpoint.y + fromTValue * (element.points[0].y - previousEndpoint.y));
CGPoint endPoint = CGPointMake(previousEndpoint.x + toTValue * (element.points[0].x - previousEndpoint.x),
previousEndpoint.y + toTValue * (element.points[0].y - previousEndpoint.y));
previousEndpoint = element.points[0];
[outputPath moveToPoint:startPoint];
[outputPath addLineToPoint:endPoint];
}
}
}];
return outputPath;
}
/**
* this will trim a uibezier path from the input element index
* and that element's tvalue. it will return all elements after
* that input
*/
-(UIBezierPath*) bezierPathByTrimmingFromElement:(NSInteger)elementIndex andTValue:(double)tValue{
__block CGPoint previousEndpoint;
__block UIBezierPath* outputPath = [UIBezierPath bezierPath];
[self iteratePathWithBlock:^(CGPathElement element, NSUInteger currentIndex){
if(currentIndex < elementIndex){
if(element.type == kCGPathElementMoveToPoint){
// moveto
previousEndpoint = element.points[0];
}else if(element.type == kCGPathElementAddCurveToPoint ){
// curve
previousEndpoint = element.points[2];
}else if(element.type == kCGPathElementAddLineToPoint){
// line
previousEndpoint = element.points[0];
}
}else if(currentIndex == elementIndex){
if(element.type == kCGPathElementMoveToPoint){
// moveto
previousEndpoint = element.points[0];
[outputPath moveToPoint:element.points[0]];
}else if(element.type == kCGPathElementAddCurveToPoint ){
// curve
CGPoint bez[4];
bez[0] = previousEndpoint;
bez[1] = element.points[0];
bez[2] = element.points[1];
bez[3] = element.points[2];
previousEndpoint = element.points[2];
CGPoint left[4], right[4];
subdivideBezierAtT(bez, left, right, tValue);
[outputPath moveToPoint:right[0]];
[outputPath addCurveToPoint:right[3] controlPoint1:right[1] controlPoint2:right[2]];
}else if(element.type == kCGPathElementAddLineToPoint){
// line
CGPoint startPoint = CGPointMake(previousEndpoint.x + tValue * (element.points[0].x - previousEndpoint.x),
previousEndpoint.y + tValue * (element.points[0].y - previousEndpoint.y));
previousEndpoint = element.points[0];
[outputPath moveToPoint:startPoint];
[outputPath addLineToPoint:element.points[0]];
}
}else if(currentIndex > elementIndex){
if(element.type == kCGPathElementMoveToPoint){
// moveto
previousEndpoint = element.points[0];
[outputPath moveToPoint:element.points[0]];
}else if(element.type == kCGPathElementAddCurveToPoint ){
// curve
previousEndpoint = element.points[2];
[outputPath addCurveToPoint:element.points[2] controlPoint1:element.points[0] controlPoint2:element.points[1]];
}else if(element.type == kCGPathElementAddLineToPoint){
// line
previousEndpoint = element.points[0];
[outputPath addLineToPoint:element.points[0]];
}
}
}];
return outputPath;
}
/**
* this will trim a uibezier path to the input element index
* and that element's tvalue. it will return all elements before
* that input
*/
-(UIBezierPath*) bezierPathByTrimmingToElement:(NSInteger)elementIndex andTValue:(double)tValue{
__block CGPoint previousEndpoint;
__block UIBezierPath* outputPath = [UIBezierPath bezierPath];
[self iteratePathWithBlock:^(CGPathElement element, NSUInteger currentIndex){
if(currentIndex == elementIndex){
if(element.type == kCGPathElementMoveToPoint){
// moveto
previousEndpoint = element.points[0];
[outputPath moveToPoint:element.points[0]];
}else if(element.type == kCGPathElementAddCurveToPoint ){
// curve
CGPoint bez[4];
bez[0] = previousEndpoint;
bez[1] = element.points[0];
bez[2] = element.points[1];
bez[3] = element.points[2];
previousEndpoint = element.points[2];
CGPoint left[4], right[4];
subdivideBezierAtT(bez, left, right, tValue);
[outputPath addCurveToPoint:left[3] controlPoint1:left[1] controlPoint2:left[2]];
}else if(element.type == kCGPathElementAddLineToPoint){
// line
CGPoint endPoint = CGPointMake(previousEndpoint.x + tValue * (element.points[0].x - previousEndpoint.x),
previousEndpoint.y + tValue * (element.points[0].y - previousEndpoint.y));
previousEndpoint = element.points[0];
[outputPath addLineToPoint:endPoint];
}
}else if(currentIndex < elementIndex){
if(element.type == kCGPathElementMoveToPoint){
// moveto
previousEndpoint = element.points[0];
[outputPath moveToPoint:element.points[0]];
}else if(element.type == kCGPathElementAddCurveToPoint ){
// curve
previousEndpoint = element.points[2];
[outputPath addCurveToPoint:element.points[2] controlPoint1:element.points[0] controlPoint2:element.points[1]];
}else if(element.type == kCGPathElementAddLineToPoint){
// line
previousEndpoint = element.points[0];
[outputPath addLineToPoint:element.points[0]];
}
}
}];
return outputPath;
}
+(void) subdivideBezier:(const CGPoint[4])bez intoLeft:(CGPoint[4])bez1 andRight:(CGPoint[4])bez2 atT:(CGFloat)t{
subdivideBezierAtT(bez, bez1, bez2, t);
}
+(void) subdivideBezier:(const CGPoint[4])bez intoLeft:(CGPoint[4])bez1 andRight:(CGPoint[4])bez2{
subdivideBezierAtT(bez, bez1, bez2, .5);
}
+(void) subdivideBezier:(const CGPoint[4])bez intoLeft:(CGPoint[4])bez1 andRight:(CGPoint[4])bez2 atLength:(CGFloat)length withAcceptableError:(CGFloat)acceptableError withCache:(CGFloat*) subBezierlengthCache{
subdivideBezierAtLengthWithCache(bez, bez1, bez2, length, acceptableError,subBezierlengthCache);
}
@end

View File

@@ -0,0 +1,33 @@
//
// UIBezierPath+DKFix.h
// ClippingBezier
//
// Created by Adam Wulf on 5/9/15.
//
//
#import <UIKit/UIKit.h>
@interface UIBezierPath (Trimming)
-(void) appendPathRemovingInitialMoveToPoint:(UIBezierPath*)otherPath;
-(NSArray*) subPaths;
-(NSInteger) countSubPaths;
- (NSInteger) subpathIndexForElement:(NSInteger) element;
- (CGFloat) length;
- (CGFloat) tangentAtStart;
- (CGFloat) tangentAtStartOfSubpath:(NSInteger)index;
- (UIBezierPath*) bezierPathByTrimmingFromLength:(CGFloat)trimLength;
- (UIBezierPath*) bezierPathByTrimmingToLength:(CGFloat)trimLength;
- (UIBezierPath*) bezierPathByTrimmingToLength:(CGFloat)trimLength withMaximumError:(CGFloat)err;
@end

View File

@@ -0,0 +1,365 @@
//
// UIBezierPath+DKFix.m
// ClippingBezier
//
// Created by Adam Wulf on 5/9/15.
//
//
#import "UIBezierPath+Trimming.h"
#import <PerformanceBezier/PerformanceBezier.h>
#pragma mark - Subdivide helpers by Alastair J. Houghton
/*
* Bezier path utility category (trimming)
*
* (c) 2004 Alastair J. Houghton
* All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* 3. The name of the author of this software may not be used to endorse
* or promote products derived from the software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER "AS IS" AND ANY EXPRESS
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT OWNER BE LIABLE FOR ANY DIRECT, INDIRECT,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
@implementation UIBezierPath (Trimming)
-(NSInteger) countSubPaths{
return [[self subPaths] count];
}
/* Return an NSBezierPath corresponding to the first trimLength units
of this NSBezierPath. */
- (UIBezierPath *)bezierPathByTrimmingToLength:(CGFloat)trimLength
withMaximumError:(CGFloat)maxError
{
UIBezierPath *newPath = [UIBezierPath bezierPath];
NSInteger elements = [self elementCount];
int n;
double length = 0.0;
CGPoint pointForClose = CGPointMake(0.0, 0.0);
CGPoint lastPoint = CGPointMake (0.0, 0.0);
for (n = 0; n < elements; ++n) {
CGPoint points[3];
CGPathElement element = [self elementAtIndex:n
associatedPoints:points];
double elementLength;
double remainingLength = trimLength - length;
if(remainingLength == 0){
break;
}
switch (element.type) {
case kCGPathElementMoveToPoint:
[newPath moveToPoint:points[0]];
pointForClose = lastPoint = points[0];
continue;
case kCGPathElementAddLineToPoint:
elementLength = distance (lastPoint, points[0]);
if (length + elementLength <= trimLength)
[newPath addLineToPoint:points[0]];
else {
double f = remainingLength / elementLength;
[newPath addLineToPoint:CGPointMake (lastPoint.x
+ f * (points[0].x - lastPoint.x),
lastPoint.y
+ f * (points[0].y - lastPoint.y))];
return newPath;
}
length += elementLength;
lastPoint = points[0];
break;
case kCGPathElementAddCurveToPoint:
case kCGPathElementAddQuadCurveToPoint: {
CGPoint bezier[4];
if(element.type == kCGPathElementAddCurveToPoint){
bezier[0] = lastPoint;
bezier[1] = points[0];
bezier[2] = points[1];
bezier[3] = points[2];
}else{
bezier[0] = lastPoint;
bezier[1] = points[0];
bezier[2] = points[0];
bezier[3] = points[1];
}
elementLength = lengthOfBezier (bezier, maxError);
if (length + elementLength <= trimLength)
[newPath addCurveToPoint:points[2] controlPoint1:points[0] controlPoint2:points[1]];
else {
CGPoint bez1[4], bez2[4];
subdivideBezierAtLength (bezier, bez1, bez2,
remainingLength, maxError);
[newPath addCurveToPoint:bez1[3] controlPoint1:bez1[1] controlPoint2:bez1[2]];
return newPath;
}
length += elementLength;
lastPoint = points[2];
break;
}
case kCGPathElementCloseSubpath:
elementLength = distance (lastPoint, pointForClose);
if (length + elementLength <= trimLength)
[newPath closePath];
else {
double f = remainingLength / elementLength;
[newPath addLineToPoint:CGPointMake(lastPoint.x
+ f * (points[0].x - lastPoint.x),
lastPoint.y
+ f * (points[0].y - lastPoint.y))];
return newPath;
}
length += elementLength;
lastPoint = pointForClose;
break;
}
}
return newPath;
}
// Convenience method
- (UIBezierPath *)bezierPathByTrimmingToLength:(CGFloat)trimLength
{
return [self bezierPathByTrimmingToLength:trimLength withMaximumError:0.1];
}
/* Return an NSBezierPath corresponding to the part *after* the first
trimLength units of this NSBezierPath. */
- (UIBezierPath *)bezierPathByTrimmingFromLength:(CGFloat)trimLength
withMaximumError:(CGFloat)maxError
{
UIBezierPath *newPath = [UIBezierPath bezierPath];
NSInteger elements = [self elementCount];
int n;
double length = 0.0;
CGPoint pointForClose = CGPointMake (0.0, 0.0);
CGPoint lastPoint = CGPointMake (0.0, 0.0);
BOOL legitMoveTo = NO;
for (n = 0; n < elements; ++n) {
CGPoint points[3];
CGPathElement element = [self elementAtIndex:n
associatedPoints:points];
double elementLength;
double remainingLength = trimLength - length;
switch (element.type) {
case kCGPathElementMoveToPoint:
if(remainingLength < 0){
[newPath moveToPoint:points[0]];
legitMoveTo = YES;
}
pointForClose = lastPoint = points[0];
continue;
case kCGPathElementAddLineToPoint:
elementLength = distance (lastPoint, points[0]);
if (length > trimLength){
[newPath addLineToPoint:points[0]];
}else if (length + elementLength > trimLength) {
double f = remainingLength / elementLength;
[newPath moveToPoint:CGPointMake (lastPoint.x
+ f * (points[0].x - lastPoint.x),
lastPoint.y
+ f * (points[0].y - lastPoint.y))];
[newPath addLineToPoint:points[0]];
}
length += elementLength;
lastPoint = points[0];
break;
case kCGPathElementAddCurveToPoint:
case kCGPathElementAddQuadCurveToPoint: {
CGPoint bezier[4];
if(element.type == kCGPathElementAddCurveToPoint){
bezier[0] = lastPoint;
bezier[1] = points[0];
bezier[2] = points[1];
bezier[3] = points[2];
}else{
bezier[0] = lastPoint;
bezier[1] = points[0];
bezier[2] = points[0];
bezier[3] = points[1];
}
elementLength = lengthOfBezier (bezier, maxError);
if (length > trimLength){
[newPath addCurveToPoint:points[2]
controlPoint1:points[0]
controlPoint2:points[1]];
}else if (length + elementLength > trimLength) {
CGPoint bez1[4], bez2[4];
subdivideBezierAtLength (bezier, bez1, bez2,
remainingLength, maxError);
[newPath moveToPoint:bez2[0]];
[newPath addCurveToPoint:bez2[3]
controlPoint1:bez2[1]
controlPoint2:bez2[2]];
}
length += elementLength;
lastPoint = points[2];
break;
}
case kCGPathElementCloseSubpath:
elementLength = distance (lastPoint, pointForClose);
if (length > trimLength){
if(legitMoveTo){
[newPath closePath];
}else{
[newPath addLineToPoint:pointForClose];
}
} else if (length + elementLength > trimLength) {
double f = remainingLength / elementLength;
[newPath moveToPoint:CGPointMake (lastPoint.x
+ f * (points[0].x - lastPoint.x),
lastPoint.y
+ f * (points[0].y - lastPoint.y))];
[newPath addLineToPoint:points[0]];
}
length += elementLength;
lastPoint = pointForClose;
break;
}
}
return newPath;
}
// Convenience method
- (UIBezierPath *)bezierPathByTrimmingFromLength:(CGFloat)trimLength
{
return [self bezierPathByTrimmingFromLength:trimLength withMaximumError:0.1];
}
- (NSInteger) subpathIndexForElement:(NSInteger) element{
__block NSInteger subpathIndex = -1;
__block BOOL lastWasMoveTo = NO;
[self iteratePathWithBlock:^(CGPathElement element, NSUInteger idx) {
if(element.type == kCGPathElementMoveToPoint){
if(!lastWasMoveTo){
subpathIndex += 1;
}
lastWasMoveTo = YES;
}else{
lastWasMoveTo = NO;
}
}];
return subpathIndex;
}
- (CGFloat) length{
__block CGFloat length = 0;
__block CGPoint lastMoveToPoint = CGPointNotFound;
__block CGPoint lastElementEndPoint = CGPointNotFound;
[self iteratePathWithBlock:^(CGPathElement element, NSUInteger idx) {
if(element.type == kCGPathElementMoveToPoint){
lastElementEndPoint = element.points[0];
lastMoveToPoint = element.points[0];
}else if(element.type == kCGPathElementCloseSubpath){
length += distance(lastElementEndPoint, lastMoveToPoint);
lastElementEndPoint = lastMoveToPoint;
}else if(element.type == kCGPathElementAddLineToPoint){
length += distance(lastElementEndPoint, element.points[0]);
lastElementEndPoint = element.points[0];
}else if(element.type == kCGPathElementAddQuadCurveToPoint ||
element.type == kCGPathElementAddCurveToPoint){
CGPoint bez[4];
bez[0] = lastElementEndPoint;
if(element.type == kCGPathElementAddQuadCurveToPoint){
bez[1] = element.points[0];
bez[2] = element.points[0];
bez[3] = element.points[1];
lastElementEndPoint = element.points[1];
}else if(element.type == kCGPathElementAddCurveToPoint){
bez[1] = element.points[0];
bez[2] = element.points[1];
bez[3] = element.points[2];
lastElementEndPoint = element.points[2];
}
length += lengthOfBezier(bez, .5);;
}
}];
return length;
}
- (CGFloat) tangentAtStart{
if([self elementCount] < 2){
return 0.0;
}
CGPathElement ele1 = [self elementAtIndex:0];
CGPathElement ele2 = [self elementAtIndex:1];
if(ele1.type != kCGPathElementMoveToPoint){
return 0.0;
}
CGPoint point1 = ele1.points[0];
CGPoint point2 = CGPointZero;
switch (ele2.type) {
case kCGPathElementMoveToPoint:
return 0.0;
break;
case kCGPathElementAddCurveToPoint:
case kCGPathElementAddQuadCurveToPoint:
case kCGPathElementAddLineToPoint:
point2 = ele2.points[0];
break;
case kCGPathElementCloseSubpath:
return 0.0;
break;
}
return atan2f( point2.y - point1.y, point2.x - point1.x ) + M_PI;
}
- (CGFloat) tangentAtStartOfSubpath:(NSInteger)index{
return [[[self subPaths] objectAtIndex:index] tangentAtStart];
}
@end

View File

@@ -0,0 +1,17 @@
//
// UIBezierPath+Uncached.h
// PerformanceBezier
//
// Created by Adam Wulf on 2/6/15.
//
//
#import <UIKit/UIKit.h>
@interface UIBezierPath (Uncached)
#ifdef MMPreventBezierPerformance
-(void) simulateNoBezierCaching;
#endif
@end

View File

@@ -0,0 +1,23 @@
//
// UIBezierPath+Uncached.m
// PerformanceBezier
//
// Created by Adam Wulf on 2/6/15.
//
//
#import "UIBezierPath+Uncached.h"
#import "UIBezierPath+NSOSX.h"
@implementation UIBezierPath (Uncached)
#ifdef MMPreventBezierPerformance
-(void) simulateNoBezierCaching{
[self iteratePathWithBlock:^(CGPathElement ele, NSUInteger idx){
// noop
}];
}
#endif
@end

View File

@@ -0,0 +1,48 @@
//
// UIBezierPath+Util.h
// PerformanceBezier
//
// Created by Adam Wulf on 5/20/15.
//
//
#import <UIKit/UIKit.h>
@interface UIBezierPath (Util)
+(CGFloat) lengthOfBezier:(const CGPoint[4])bez withAccuracy:(CGFloat)accuracy;
@end
#if defined __cplusplus
extern "C" {
#endif
// simple helper function to return the distance of a point to a line
CGFloat distanceOfPointToLine(CGPoint point, CGPoint start, CGPoint end);
// returns the distance between two points
CGFloat distance(const CGPoint p1, const CGPoint p2);
void subdivideBezierAtT(const CGPoint bez[4], CGPoint bez1[4], CGPoint bez2[4], CGFloat t);
CGFloat subdivideBezierAtLength (const CGPoint bez[4], CGPoint bez1[4], CGPoint bez2[4], CGFloat length, CGFloat acceptableError);
CGFloat subdivideBezierAtLengthWithCache(const CGPoint bez[4], CGPoint bez1[4], CGPoint bez2[4], CGFloat length, CGFloat acceptableError,
CGFloat* subBezierLengthCache);
CGFloat lengthOfBezier(const CGPoint bez[4], CGFloat acceptableError);
CGPoint lineSegmentIntersection(CGPoint A, CGPoint B, CGPoint C, CGPoint D);
CGPoint bezierTangentAtT(const CGPoint bez[4], CGFloat t);
CGFloat bezierTangent(CGFloat t, CGFloat a, CGFloat b, CGFloat c, CGFloat d);
#if defined __cplusplus
};
#endif

View File

@@ -0,0 +1,316 @@
//
// UIBezierPath+Util.m
// PerformanceBezier
//
// Created by Adam Wulf on 5/20/15.
//
//
#import "PerformanceBezier.h"
#import "UIBezierPath+Util.h"
#import "UIBezierPath+Trim.h"
#import "UIBezierPath+Performance.h"
@implementation UIBezierPath (Util)
+(CGFloat) lengthOfBezier:(const CGPoint[4])bez withAccuracy:(CGFloat)accuracy{
return lengthOfBezier(bez, accuracy);
}
#pragma mark - Subdivide helpers by Alastair J. Houghton
/*
* Bezier path utility category (trimming)
*
* (c) 2004 Alastair J. Houghton
* All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* 3. The name of the author of this software may not be used to endorse
* or promote products derived from the software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER "AS IS" AND ANY EXPRESS
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT OWNER BE LIABLE FOR ANY DIRECT, INDIRECT,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
// Subdivide a Bézier (50% subdivision)
inline static void subdivideBezier(const CGPoint bez[4], CGPoint bez1[4], CGPoint bez2[4])
{
CGPoint q;
bez1[0].x = bez[0].x;
bez1[0].y = bez[0].y;
bez2[3].x = bez[3].x;
bez2[3].y = bez[3].y;
q.x = (bez[1].x + bez[2].x) / 2.0;
q.y = (bez[1].y + bez[2].y) / 2.0;
bez1[1].x = (bez[0].x + bez[1].x) / 2.0;
bez1[1].y = (bez[0].y + bez[1].y) / 2.0;
bez2[2].x = (bez[2].x + bez[3].x) / 2.0;
bez2[2].y = (bez[2].y + bez[3].y) / 2.0;
bez1[2].x = (bez1[1].x + q.x) / 2.0;
bez1[2].y = (bez1[1].y + q.y) / 2.0;
bez2[1].x = (q.x + bez2[2].x) / 2.0;
bez2[1].y = (q.y + bez2[2].y) / 2.0;
bez1[3].x = bez2[0].x = (bez1[2].x + bez2[1].x) / 2.0;
bez1[3].y = bez2[0].y = (bez1[2].y + bez2[1].y) / 2.0;
}
// Subdivide a Bézier (specific division)
void subdivideBezierAtT(const CGPoint bez[4], CGPoint bez1[4], CGPoint bez2[4], CGFloat t)
{
CGPoint q;
CGFloat mt = 1 - t;
bez1[0].x = bez[0].x;
bez1[0].y = bez[0].y;
bez2[3].x = bez[3].x;
bez2[3].y = bez[3].y;
q.x = mt * bez[1].x + t * bez[2].x;
q.y = mt * bez[1].y + t * bez[2].y;
bez1[1].x = mt * bez[0].x + t * bez[1].x;
bez1[1].y = mt * bez[0].y + t * bez[1].y;
bez2[2].x = mt * bez[2].x + t * bez[3].x;
bez2[2].y = mt * bez[2].y + t * bez[3].y;
bez1[2].x = mt * bez1[1].x + t * q.x;
bez1[2].y = mt * bez1[1].y + t * q.y;
bez2[1].x = mt * q.x + t * bez2[2].x;
bez2[1].y = mt * q.y + t * bez2[2].y;
bez1[3].x = bez2[0].x = mt * bez1[2].x + t * bez2[1].x;
bez1[3].y = bez2[0].y = mt * bez1[2].y + t * bez2[1].y;
}
// Length of a curve
CGFloat lengthOfBezier(const CGPoint bez[4], CGFloat acceptableError)
{
CGFloat polyLen = 0.0;
CGFloat chordLen = distance(bez[0], bez[3]);
CGFloat retLen, errLen;
NSUInteger n;
for (n = 0; n < 3; ++n)
polyLen += distance(bez[n], bez[n + 1]);
errLen = polyLen - chordLen;
if (errLen > acceptableError) {
CGPoint left[4], right[4];
subdivideBezier (bez, left, right);
retLen = (lengthOfBezier (left, acceptableError)
+ lengthOfBezier (right, acceptableError));
} else {
retLen = 0.5 * (polyLen + chordLen);
}
return retLen;
}
// Split a Bézier curve at a specific length
CGFloat subdivideBezierAtLength (const CGPoint bez[4],
CGPoint bez1[4],
CGPoint bez2[4],
CGFloat length,
CGFloat acceptableError)
{
return subdivideBezierAtLengthWithCache(bez, bez1, bez2, length, acceptableError, NULL);
}
/**
* will split the input bezier curve at the input length
* within a given margin of error
*
* the two curves will exactly match the original curve
*/
CGFloat subdivideBezierAtLengthWithCache(const CGPoint bez[4],
CGPoint bez1[4],
CGPoint bez2[4],
CGFloat length,
CGFloat acceptableError,
CGFloat* subBezierLengthCache){
CGFloat top = 1.0, bottom = 0.0;
CGFloat t, prevT;
BOOL needsDealloc = NO;
if(!subBezierLengthCache){
subBezierLengthCache = calloc(1000, sizeof(CGFloat));
needsDealloc = YES;
}
prevT = t = 0.5;
for (;;) {
CGFloat len1;
subdivideBezierAtT (bez, bez1, bez2, t);
int lengthCacheIndex = (int)floorf(t*1000);
len1 = subBezierLengthCache[lengthCacheIndex];
if(!len1){
len1 = [UIBezierPath lengthOfBezier:bez1 withAccuracy:0.5 * acceptableError];
subBezierLengthCache[lengthCacheIndex] = len1;
}
if (fabs (length - len1) < acceptableError){
return len1;
}
if (length > len1) {
bottom = t;
t = 0.5 * (t + top);
} else if (length < len1) {
top = t;
t = 0.5 * (bottom + t);
}
if (t == prevT){
subBezierLengthCache[lengthCacheIndex] = len1;
if(needsDealloc){
free(subBezierLengthCache);
}
return len1;
}
prevT = t;
}
}
// public domain function by Darel Rex Finley, 2006
// Determines the intersection point of the line segment defined by points A and B
// with the line segment defined by points C and D.
//
// Returns YES if the intersection point was found, and stores that point in X,Y.
// Returns NO if there is no determinable intersection point, in which case X,Y will
// be unmodified.
CGPoint lineSegmentIntersection(CGPoint A, CGPoint B, CGPoint C, CGPoint D) {
double distAB, theCos, theSin, newX, ABpos ;
// Fail if either line segment is zero-length.
if ((A.x==B.x && A.y==B.y) || (C.x==D.x && C.y==D.y)) return CGPointNotFound;
// Fail if the segments share an end-point.
if ((A.x==C.x && A.y==C.y) ||
(B.x==C.x && B.y==C.y) ||
(A.x==D.x && A.y==D.y) ||
(B.x==D.x && B.y==D.y)) {
return CGPointNotFound;
}
// (1) Translate the system so that point A is on the origin.
B.x-=A.x; B.y-=A.y;
C.x-=A.x; C.y-=A.y;
D.x-=A.x; D.y-=A.y;
// Discover the length of segment A-B.
distAB=sqrt(B.x*B.x+B.y*B.y);
// (2) Rotate the system so that point B is on the positive X axis.
theCos=B.x/distAB;
theSin=B.y/distAB;
newX=C.x*theCos+C.y*theSin;
C.y =C.y*theCos-C.x*theSin;
C.x=newX;
newX=D.x*theCos+D.y*theSin;
D.y =D.y*theCos-D.x*theSin;
D.x=newX;
// Fail if segment C-D doesn't cross line A-B.
if ((C.y<0. && D.y<0.) || (C.y>=0. && D.y>=0.)) return CGPointNotFound;
// (3) Discover the position of the intersection point along line A-B.
ABpos=D.x+(C.x-D.x)*D.y/(D.y-C.y);
// Fail if segment C-D crosses line A-B outside of segment A-B.
if (ABpos<0. || ABpos>distAB) return CGPointNotFound;
// (4) Apply the discovered position to line A-B in the original coordinate system.
// Success.
return CGPointMake(A.x+ABpos*theCos, A.y+ABpos*theSin);
}
#pragma mark - Helper
// primary algorithm from:
// http://stackoverflow.com/questions/4089443/find-the-tangent-of-a-point-on-a-cubic-bezier-curve-on-an-iphone
CGPoint bezierTangentAtT(const CGPoint bez[4], CGFloat t)
{
return CGPointMake(bezierTangent(t, bez[0].x, bez[1].x, bez[2].x, bez[3].x),
bezierTangent(t, bez[0].y, bez[1].y, bez[2].y, bez[3].y));
}
CGFloat bezierTangent(CGFloat t, CGFloat a, CGFloat b, CGFloat c, CGFloat d)
{
CGFloat C1 = ( d - (3.0 * c) + (3.0 * b) - a );
CGFloat C2 = ( (3.0 * c) - (6.0 * b) + (3.0 * a) );
CGFloat C3 = ( (3.0 * b) - (3.0 * a) );
return ( ( 3.0 * C1 * t* t ) + ( 2.0 * C2 * t ) + C3 );
}
/**
* returns the shortest distance from a point to a line
*/
CGFloat distanceOfPointToLine(CGPoint point, CGPoint start, CGPoint end){
CGPoint v = CGPointMake(end.x - start.x, end.y - start.y);
CGPoint w = CGPointMake(point.x - start.x, point.y - start.y);
CGFloat c1 = dotProduct(w, v);
CGFloat c2 = dotProduct(v, v);
CGFloat d;
if (c1 <= 0) {
d = distance(point, start);
}
else if (c2 <= c1) {
d = distance(point, end);
}
else {
CGFloat b = c1 / c2;
CGPoint Pb = CGPointMake(start.x + b * v.x, start.y + b * v.y);
d = distance(point, Pb);
}
return d;
}
/**
* returns the distance between two points
*/
CGFloat distance(const CGPoint p1, const CGPoint p2) {
return sqrt(pow(p2.x - p1.x, 2) + pow(p2.y - p1.y, 2));
}
/**
* returns the dot product of two coordinates
*/
CGFloat dotProduct(const CGPoint p1, const CGPoint p2) {
return p1.x * p2.x + p1.y * p2.y;
}
@end

View File

@@ -0,0 +1,25 @@
//
// UIBezierPathProperties.h
// PerformanceBezier
//
// Created by Adam Wulf on 2/1/15.
// Copyright (c) 2015 Milestone Made, LLC. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface UIBezierPathProperties : NSObject
@property (nonatomic) BOOL isClosed;
@property (nonatomic) BOOL knowsIfClosed;
@property (nonatomic) BOOL isFlat;
@property (nonatomic) BOOL hasLastPoint;
@property (nonatomic) CGPoint lastPoint;
@property (nonatomic) BOOL hasFirstPoint;
@property (nonatomic) CGPoint firstPoint;
@property (nonatomic) CGFloat tangentAtEnd;
@property (nonatomic) NSInteger cachedElementCount;
@property (nonatomic, retain) UIBezierPath* bezierPathByFlatteningPath;
@property (nonatomic) BOOL lastAddedElementWasMoveTo;
@end

View File

@@ -0,0 +1,81 @@
//
// UIBezierPathProperties.m
// PerformanceBezier
//
// Created by Adam Wulf on 2/1/15.
// Copyright (c) 2015 Milestone Made, LLC. All rights reserved.
//
#import "UIBezierPathProperties.h"
@implementation UIBezierPathProperties{
BOOL isFlat;
BOOL knowsIfClosed;
BOOL isClosed;
BOOL hasLastPoint;
CGPoint lastPoint;
BOOL hasFirstPoint;
CGPoint firstPoint;
CGFloat tangentAtEnd;
NSInteger cachedElementCount;
UIBezierPath* bezierPathByFlatteningPath;
}
@synthesize isFlat;
@synthesize knowsIfClosed;
@synthesize isClosed;
@synthesize hasLastPoint;
@synthesize lastPoint;
@synthesize tangentAtEnd;
@synthesize cachedElementCount;
@synthesize bezierPathByFlatteningPath;
@synthesize hasFirstPoint;
@synthesize firstPoint;
- (id)initWithCoder:(NSCoder *)decoder{
self = [super init];
if (!self) {
return nil;
}
isFlat = [decoder decodeBoolForKey:@"pathProperties_isFlat"];
knowsIfClosed = [decoder decodeBoolForKey:@"pathProperties_knowsIfClosed"];
isClosed = [decoder decodeBoolForKey:@"pathProperties_isClosed"];
hasLastPoint = [decoder decodeBoolForKey:@"pathProperties_hasLastPoint"];
lastPoint = [decoder decodeCGPointForKey:@"pathProperties_lastPoint"];
hasFirstPoint = [decoder decodeBoolForKey:@"pathProperties_hasFirstPoint"];
firstPoint = [decoder decodeCGPointForKey:@"pathProperties_firstPoint"];
tangentAtEnd = [decoder decodeFloatForKey:@"pathProperties_tangentAtEnd"];
cachedElementCount = [decoder decodeIntegerForKey:@"pathProperties_cachedElementCount"];
return self;
}
-(void) encodeWithCoder:(NSCoder *)aCoder{
[aCoder encodeBool:isFlat forKey:@"pathProperties_isFlat"];
[aCoder encodeBool:knowsIfClosed forKey:@"pathProperties_knowsIfClosed"];
[aCoder encodeBool:isClosed forKey:@"pathProperties_isClosed"];
[aCoder encodeBool:hasLastPoint forKey:@"pathProperties_hasLastPoint"];
[aCoder encodeCGPoint:lastPoint forKey:@"pathProperties_lastPoint"];
[aCoder encodeBool:hasFirstPoint forKey:@"pathProperties_hasFirstPoint"];
[aCoder encodeCGPoint:firstPoint forKey:@"pathProperties_firstPoint"];
[aCoder encodeFloat:tangentAtEnd forKey:@"pathProperties_tangentAtEnd"];
[aCoder encodeInteger:cachedElementCount forKey:@"pathProperties_cachedElementCount"];
}
// for some reason the iPad 1 on iOS 5 needs to have this
// method coded and not synthesized.
-(void) setBezierPathByFlatteningPath:(UIBezierPath *)_bezierPathByFlatteningPath{
if(bezierPathByFlatteningPath != _bezierPathByFlatteningPath){
[bezierPathByFlatteningPath release];
[_bezierPathByFlatteningPath retain];
}
bezierPathByFlatteningPath = _bezierPathByFlatteningPath;
}
-(void) dealloc{
[bezierPathByFlatteningPath release];
bezierPathByFlatteningPath = nil;
[super dealloc];
}
@end

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
</dict>
</plist>

View File

@@ -0,0 +1,23 @@
//
// PerformanceBezierAbstractTest.h
// PerformanceBezier
//
// Created by Adam Wulf on 11/20/13.
// Copyright (c) 2013 Milestone Made, LLC. All rights reserved.
//
#import "PerformanceBezier.h"
@interface PerformanceBezierAbstractTest : XCTestCase
@property (nonatomic, readonly) UIBezierPath* complexShape;
-(CGFloat) round:(CGFloat)val to:(int)digits;
-(BOOL) point:(CGPoint)p1 isNearTo:(CGPoint)p2;
-(BOOL) checkTanPoint:(CGFloat) f1 isLessThan:(CGFloat)f2;
-(BOOL) check:(CGFloat) f1 isLessThan:(CGFloat)f2 within:(CGFloat)marginOfError;
@end

View File

@@ -0,0 +1,161 @@
//
// PerformanceBezierAbstractTest.m
// PerformanceBezier
//
// Created by Adam Wulf on 11/20/13.
// Copyright (c) 2013 Milestone Made, LLC. All rights reserved.
//
#import <XCTest/XCTest.h>
#import "PerformanceBezierAbstractTest.h"
#define kIntersectionPointPrecision .1
@implementation PerformanceBezierAbstractTest{
UIBezierPath* complexShape;
}
@synthesize complexShape;
-(void) setUp{
complexShape = [UIBezierPath bezierPath];
[complexShape moveToPoint:CGPointMake(218.500000,376.000000)];
[complexShape addCurveToPoint:CGPointMake(227.000000,362.000000) controlPoint1:CGPointMake(218.100235,369.321564) controlPoint2:CGPointMake(222.816589,365.945923)];
// [complexShape addCurveToPoint:CGPointMake(213.000000,372.000000) controlPoint1:CGPointMake(237.447144,352.833008) controlPoint2:CGPointMake(255.082840,326.966705)];
[complexShape addCurveToPoint:CGPointMake(243.000000,316.000000) controlPoint1:CGPointMake(232.801880,363.107697) controlPoint2:CGPointMake(248.582321,338.033752)];
[complexShape addCurveToPoint:CGPointMake(172.000000,218.000000) controlPoint1:CGPointMake(215.326599,286.866608) controlPoint2:CGPointMake(182.880371,258.374298)];
[complexShape addCurveToPoint:CGPointMake(242.000000,111.000000) controlPoint1:CGPointMake(149.812820,168.222122) controlPoint2:CGPointMake(195.466583,119.790573)];
[complexShape addCurveToPoint:CGPointMake(265.000000,109.000000) controlPoint1:CGPointMake(249.474472,108.948883) controlPoint2:CGPointMake(257.298248,108.285248)];
[complexShape addCurveToPoint:CGPointMake(302.000000,116.000000) controlPoint1:CGPointMake(277.539948,110.401459) controlPoint2:CGPointMake(291.623962,104.844376)];
[complexShape addCurveToPoint:CGPointMake(310.000000,148.000000) controlPoint1:CGPointMake(310.890778,124.607719) controlPoint2:CGPointMake(310.189972,136.988098)];
[complexShape addCurveToPoint:CGPointMake(302.000000,184.000000) controlPoint1:CGPointMake(310.722900,160.600952) controlPoint2:CGPointMake(305.109131,172.145187)];
[complexShape addCurveToPoint:CGPointMake(295.000000,221.000000) controlPoint1:CGPointMake(299.082794,196.217377) controlPoint2:CGPointMake(297.024170,208.612000)];
[complexShape addCurveToPoint:CGPointMake(304.000000,294.000000) controlPoint1:CGPointMake(297.367493,244.586227) controlPoint2:CGPointMake(286.870880,273.642609)];
[complexShape addCurveToPoint:CGPointMake(343.000000,305.000000) controlPoint1:CGPointMake(315.167755,304.101868) controlPoint2:CGPointMake(329.526489,302.807587)];
[complexShape addCurveToPoint:CGPointMake(395.000000,297.000000) controlPoint1:CGPointMake(361.147430,306.169556) controlPoint2:CGPointMake(378.948700,305.707855)];
[complexShape addCurveToPoint:CGPointMake(413.000000,285.000000) controlPoint1:CGPointMake(401.931854,294.629944) controlPoint2:CGPointMake(408.147980,290.431854)];
[complexShape addCurveToPoint:CGPointMake(435.000000,255.000000) controlPoint1:CGPointMake(423.555023,278.199219) controlPoint2:CGPointMake(433.437531,268.110229)];
[complexShape addCurveToPoint:CGPointMake(424.000000,228.000000) controlPoint1:CGPointMake(436.063995,245.070923) controlPoint2:CGPointMake(432.549713,233.803619)];
[complexShape addCurveToPoint:CGPointMake(402.000000,209.000000) controlPoint1:CGPointMake(416.571869,221.776123) controlPoint2:CGPointMake(409.428101,215.223877)];
[complexShape addCurveToPoint:CGPointMake(385.000000,183.000000) controlPoint1:CGPointMake(392.239838,203.283615) controlPoint2:CGPointMake(390.379822,191.873459)];
[complexShape addCurveToPoint:CGPointMake(385.000000,128.000000) controlPoint1:CGPointMake(378.582947,166.163315) controlPoint2:CGPointMake(374.234924,143.960587)];
[complexShape addCurveToPoint:CGPointMake(472.000000,105.000000) controlPoint1:CGPointMake(405.864136,103.071175) controlPoint2:CGPointMake(442.110016,95.355499)];
[complexShape addLineToPoint:CGPointMake(472.000000,105.000000)];
[complexShape addLineToPoint:CGPointMake(488.000000,177.000000)];
[complexShape addCurveToPoint:CGPointMake(534.000000,177.000000) controlPoint1:CGPointMake(498.942047,187.308975) controlPoint2:CGPointMake(522.349426,190.655228)];
[complexShape addLineToPoint:CGPointMake(534.000000,177.000000)];
[complexShape addLineToPoint:CGPointMake(611.000000,77.000000)];
[complexShape addCurveToPoint:CGPointMake(700.000000,111.000000) controlPoint1:CGPointMake(643.743652,54.625603) controlPoint2:CGPointMake(684.106140,80.752876)];
[complexShape addCurveToPoint:CGPointMake(704.000000,126.000000) controlPoint1:CGPointMake(703.098755,115.324257) controlPoint2:CGPointMake(704.460693,120.742104)];
[complexShape addCurveToPoint:CGPointMake(700.000000,156.000000) controlPoint1:CGPointMake(703.210999,136.088333) controlPoint2:CGPointMake(706.451782,146.830612)];
[complexShape addCurveToPoint:CGPointMake(681.000000,196.000000) controlPoint1:CGPointMake(695.262329,170.166794) controlPoint2:CGPointMake(684.152832,181.086166)];
[complexShape addCurveToPoint:CGPointMake(655.000000,246.000000) controlPoint1:CGPointMake(672.227966,212.611191) controlPoint2:CGPointMake(663.084045,229.033600)];
[complexShape addCurveToPoint:CGPointMake(636.000000,300.000000) controlPoint1:CGPointMake(649.778381,264.429260) controlPoint2:CGPointMake(639.110535,280.894592)];
[complexShape addCurveToPoint:CGPointMake(637.000000,374.000000) controlPoint1:CGPointMake(633.264893,324.262054) controlPoint2:CGPointMake(629.540955,350.094421)];
[complexShape addCurveToPoint:CGPointMake(692.000000,512.000000) controlPoint1:CGPointMake(666.332947,413.789734) controlPoint2:CGPointMake(699.608032,459.043060)];
[complexShape addCurveToPoint:CGPointMake(683.000000,536.000000) controlPoint1:CGPointMake(688.852905,519.944031) controlPoint2:CGPointMake(685.852539,527.945801)];
[complexShape addCurveToPoint:CGPointMake(661.000000,571.000000) controlPoint1:CGPointMake(677.198853,548.592712) controlPoint2:CGPointMake(668.260010,559.289062)];
[complexShape addCurveToPoint:CGPointMake(610.000000,619.000000) controlPoint1:CGPointMake(648.250000,590.532715) controlPoint2:CGPointMake(631.222351,608.483521)];
[complexShape addCurveToPoint:CGPointMake(585.000000,625.000000) controlPoint1:CGPointMake(602.312439,623.111694) controlPoint2:CGPointMake(593.647827,625.135620)];
[complexShape addCurveToPoint:CGPointMake(542.000000,612.000000) controlPoint1:CGPointMake(569.838867,626.477356) controlPoint2:CGPointMake(551.172668,629.067810)];
[complexShape addCurveToPoint:CGPointMake(530.000000,557.000000) controlPoint1:CGPointMake(525.270691,596.196289) controlPoint2:CGPointMake(531.172729,575.895203)];
[complexShape addCurveToPoint:CGPointMake(539.000000,514.000000) controlPoint1:CGPointMake(528.119263,541.759460) controlPoint2:CGPointMake(537.465271,528.714905)];
[complexShape addCurveToPoint:CGPointMake(557.000000,458.000000) controlPoint1:CGPointMake(546.810364,495.904419) controlPoint2:CGPointMake(549.387146,476.172821)];
[complexShape addCurveToPoint:CGPointMake(577.000000,334.000000) controlPoint1:CGPointMake(570.620544,419.011993) controlPoint2:CGPointMake(584.251587,375.770599)];
[complexShape addCurveToPoint:CGPointMake(552.000000,307.000000) controlPoint1:CGPointMake(572.811646,321.661407) controlPoint2:CGPointMake(563.442261,312.060883)];
[complexShape addCurveToPoint:CGPointMake(505.000000,304.000000) controlPoint1:CGPointMake(537.517578,299.902161) controlPoint2:CGPointMake(520.555420,301.162994)];
[complexShape addCurveToPoint:CGPointMake(417.000000,395.000000) controlPoint1:CGPointMake(471.423096,326.244507) controlPoint2:CGPointMake(426.598511,351.118713)];
[complexShape addCurveToPoint:CGPointMake(412.000000,423.000000) controlPoint1:CGPointMake(413.398468,403.873810) controlPoint2:CGPointMake(411.718628,413.460388)];
[complexShape addCurveToPoint:CGPointMake(418.000000,460.000000) controlPoint1:CGPointMake(412.695190,435.349243) controlPoint2:CGPointMake(409.621643,449.299377)];
[complexShape addCurveToPoint:CGPointMake(445.000000,488.000000) controlPoint1:CGPointMake(425.380219,470.784973) controlPoint2:CGPointMake(435.315186,479.392761)];
[complexShape addCurveToPoint:CGPointMake(469.000000,514.000000) controlPoint1:CGPointMake(453.446838,496.144928) controlPoint2:CGPointMake(463.737061,503.078857)];
[complexShape addCurveToPoint:CGPointMake(476.000000,562.000000) controlPoint1:CGPointMake(477.951324,528.615112) controlPoint2:CGPointMake(475.631348,545.762817)];
[complexShape addLineToPoint:CGPointMake(476.000000,562.000000)];
[complexShape addLineToPoint:CGPointMake(384.000000,717.000000)];
[complexShape addCurveToPoint:CGPointMake(382.000000,740.000000) controlPoint1:CGPointMake(381.948761,724.474304) controlPoint2:CGPointMake(381.285309,732.298340)];
[complexShape addCurveToPoint:CGPointMake(394.000000,768.000000) controlPoint1:CGPointMake(373.999329,755.779053) controlPoint2:CGPointMake(424.795959,792.055847)];
[complexShape addCurveToPoint:CGPointMake(466.000000,790.000000) controlPoint1:CGPointMake(413.033173,794.757141) controlPoint2:CGPointMake(440.293671,789.339783)];
[complexShape addCurveToPoint:CGPointMake(509.000000,769.000000) controlPoint1:CGPointMake(479.958252,782.095642) controlPoint2:CGPointMake(496.425812,779.557007)];
[complexShape addCurveToPoint:CGPointMake(570.000000,717.000000) controlPoint1:CGPointMake(536.317139,759.409058) controlPoint2:CGPointMake(549.962769,734.921936)];
[complexShape addCurveToPoint:CGPointMake(632.000000,663.000000) controlPoint1:CGPointMake(586.772034,695.344360) controlPoint2:CGPointMake(605.504700,673.223633)];
[complexShape addCurveToPoint:CGPointMake(679.000000,654.000000) controlPoint1:CGPointMake(645.724731,654.867432) controlPoint2:CGPointMake(663.048096,650.318481)];
[complexShape addCurveToPoint:CGPointMake(696.000000,730.000000) controlPoint1:CGPointMake(702.263611,671.121704) controlPoint2:CGPointMake(698.817200,704.461426)];
[complexShape addCurveToPoint:CGPointMake(685.000000,758.000000) controlPoint1:CGPointMake(693.381836,739.709473) controlPoint2:CGPointMake(689.682678,749.111511)];
[complexShape addCurveToPoint:CGPointMake(639.000000,805.000000) controlPoint1:CGPointMake(674.516968,777.568542) controlPoint2:CGPointMake(664.349304,799.584106)];
[complexShape addCurveToPoint:CGPointMake(605.000000,809.000000) controlPoint1:CGPointMake(627.873596,807.853577) controlPoint2:CGPointMake(616.339233,807.757568)];
[complexShape addCurveToPoint:CGPointMake(559.000000,810.000000) controlPoint1:CGPointMake(589.680847,809.707275) controlPoint2:CGPointMake(574.264648,807.876892)];
[complexShape addCurveToPoint:CGPointMake(491.000000,832.000000) controlPoint1:CGPointMake(535.253113,811.682922) controlPoint2:CGPointMake(510.867584,818.326538)];
[complexShape addCurveToPoint:CGPointMake(483.000000,949.000000) controlPoint1:CGPointMake(452.488922,868.759766) controlPoint2:CGPointMake(506.715942,910.094971)];
[complexShape addCurveToPoint:CGPointMake(438.000000,971.000000) controlPoint1:CGPointMake(471.816193,962.527100) controlPoint2:CGPointMake(455.094940,970.199341)];
[complexShape addCurveToPoint:CGPointMake(381.000000,969.000000) controlPoint1:CGPointMake(419.703827,973.256775) controlPoint2:CGPointMake(398.264557,978.824951)];
[complexShape addCurveToPoint:CGPointMake(332.000000,894.000000) controlPoint1:CGPointMake(336.834229,961.446167) controlPoint2:CGPointMake(348.029205,921.138672)];
[complexShape addCurveToPoint:CGPointMake(201.000000,838.000000) controlPoint1:CGPointMake(307.704529,850.152588) controlPoint2:CGPointMake(249.883804,820.437744)];
[complexShape addCurveToPoint:CGPointMake(167.000000,881.000000) controlPoint1:CGPointMake(182.977020,844.574768) controlPoint2:CGPointMake(166.990494,861.006348)];
[complexShape addCurveToPoint:CGPointMake(175.000000,930.000000) controlPoint1:CGPointMake(165.412155,897.691711) controlPoint2:CGPointMake(168.718536,914.566101)];
[complexShape addCurveToPoint:CGPointMake(169.000000,974.000000) controlPoint1:CGPointMake(175.880753,944.083801) controlPoint2:CGPointMake(184.246262,962.543579)];
[complexShape addCurveToPoint:CGPointMake(112.000000,969.000000) controlPoint1:CGPointMake(153.879868,987.139404) controlPoint2:CGPointMake(121.948792,989.663391)];
[complexShape addCurveToPoint:CGPointMake(99.000000,945.000000) controlPoint1:CGPointMake(105.050240,962.664062) controlPoint2:CGPointMake(100.558395,954.098572)];
[complexShape addCurveToPoint:CGPointMake(97.000000,875.000000) controlPoint1:CGPointMake(95.831497,921.925537) controlPoint2:CGPointMake(94.644936,898.300354)];
[complexShape addCurveToPoint:CGPointMake(132.000000,821.000000) controlPoint1:CGPointMake(107.640190,856.536194) controlPoint2:CGPointMake(116.238022,835.936340)];
[complexShape addCurveToPoint:CGPointMake(184.000000,750.000000) controlPoint1:CGPointMake(155.798218,802.971130) controlPoint2:CGPointMake(173.768723,777.893677)];
[complexShape addCurveToPoint:CGPointMake(187.000000,687.000000) controlPoint1:CGPointMake(185.273651,729.554688) controlPoint2:CGPointMake(197.039795,707.556824)];
[complexShape addCurveToPoint:CGPointMake(154.000000,656.000000) controlPoint1:CGPointMake(180.328445,672.368408) controlPoint2:CGPointMake(164.994644,666.234436)];
[complexShape addCurveToPoint:CGPointMake(196.000000,600.000000) controlPoint1:CGPointMake(149.363876,627.436218) controlPoint2:CGPointMake(180.133606,615.870422)];
[complexShape addCurveToPoint:CGPointMake(268.000000,553.000000) controlPoint1:CGPointMake(219.965149,584.441223) controlPoint2:CGPointMake(243.722717,568.280640)];
[complexShape addCurveToPoint:CGPointMake(295.000000,494.000000) controlPoint1:CGPointMake(285.621643,544.794250) controlPoint2:CGPointMake(308.387665,515.697510)];
[complexShape addCurveToPoint:CGPointMake(223.000000,479.000000) controlPoint1:CGPointMake(274.950104,475.730682) controlPoint2:CGPointMake(247.686523,476.989319)];
[complexShape addCurveToPoint:CGPointMake(115.000000,488.000000) controlPoint1:CGPointMake(190.196381,488.207886) controlPoint2:CGPointMake(149.008881,507.599640)];
[complexShape addCurveToPoint:CGPointMake(92.000000,430.000000) controlPoint1:CGPointMake(91.723808,479.934326) controlPoint2:CGPointMake(81.161568,451.403015)];
[complexShape addCurveToPoint:CGPointMake(185.000000,434.000000) controlPoint1:CGPointMake(119.158417,409.750031) controlPoint2:CGPointMake(157.642273,416.770874)];
[complexShape addCurveToPoint:CGPointMake(298.000000,425.000000) controlPoint1:CGPointMake(219.467361,450.346191) controlPoint2:CGPointMake(268.697144,455.229706)];
[complexShape addCurveToPoint:CGPointMake(339.000000,443.000000) controlPoint1:CGPointMake(312.625793,410.994751) controlPoint2:CGPointMake(331.307281,429.430817)];
[complexShape addCurveToPoint:CGPointMake(349.000000,478.000000) controlPoint1:CGPointMake(344.453400,453.933380) controlPoint2:CGPointMake(347.832184,465.856812)];
[complexShape addCurveToPoint:CGPointMake(344.000000,540.000000) controlPoint1:CGPointMake(351.007965,498.725006) controlPoint2:CGPointMake(352.137878,520.179993)];
[complexShape addCurveToPoint:CGPointMake(316.000000,580.000000) controlPoint1:CGPointMake(339.491791,556.244446) controlPoint2:CGPointMake(328.167419,569.066284)];
[complexShape addCurveToPoint:CGPointMake(273.000000,620.000000) controlPoint1:CGPointMake(299.305786,590.606079) controlPoint2:CGPointMake(286.016296,605.359131)];
[complexShape addCurveToPoint:CGPointMake(219.000000,737.000000) controlPoint1:CGPointMake(253.199554,654.579529) controlPoint2:CGPointMake(210.386047,692.031433)];
[complexShape addCurveToPoint:CGPointMake(249.000000,759.000000) controlPoint1:CGPointMake(225.442154,748.286621) controlPoint2:CGPointMake(236.628326,756.096069)];
[complexShape addCurveToPoint:CGPointMake(334.000000,704.000000) controlPoint1:CGPointMake(285.551544,759.768677) controlPoint2:CGPointMake(315.180786,732.848572)];
[complexShape addCurveToPoint:CGPointMake(352.000000,664.000000) controlPoint1:CGPointMake(342.386566,691.840271) controlPoint2:CGPointMake(348.465118,678.245239)];
[complexShape addCurveToPoint:CGPointMake(363.000000,594.000000) controlPoint1:CGPointMake(356.415161,640.814575) controlPoint2:CGPointMake(362.213562,617.716248)];
[complexShape addCurveToPoint:CGPointMake(375.000000,545.000000) controlPoint1:CGPointMake(364.278687,577.084534) controlPoint2:CGPointMake(369.613586,560.946472)];
[complexShape addCurveToPoint:CGPointMake(370.000000,449.000000) controlPoint1:CGPointMake(385.057648,514.062866) controlPoint2:CGPointMake(387.877350,477.758911)];
[complexShape addLineToPoint:CGPointMake(370.000000,449.000000)];
[complexShape addLineToPoint:CGPointMake(349.000000,394.000000)];
[complexShape addCurveToPoint:CGPointMake(317.000000,376.000000) controlPoint1:CGPointMake(342.462463,381.669800) controlPoint2:CGPointMake(329.632996,375.483673)];
[complexShape addCurveToPoint:CGPointMake(288.000000,392.000000) controlPoint1:CGPointMake(302.854156,369.438843) controlPoint2:CGPointMake(296.932526,386.031342)];
[complexShape closePath];
[super setUp];
}
- (void)tearDown
{
// Put teardown code here; it will be run once, after the last test case.
[super tearDown];
}
-(CGFloat) round:(CGFloat)val to:(int)digits{
double factor = pow(10, digits);
return roundf(val * factor) / factor;
}
-(BOOL) point:(CGPoint)p1 isNearTo:(CGPoint)p2{
CGFloat xDiff = ABS(p2.x - p1.x);
CGFloat yDiff = ABS(p2.y - p1.y);
return xDiff < kIntersectionPointPrecision && yDiff < kIntersectionPointPrecision;
}
-(BOOL) check:(CGFloat) f1 isLessThan:(CGFloat)f2 within:(CGFloat)marginOfError{
if(f1 <= (f2 * (1.0f+marginOfError))){
return YES;
}
NSLog(@"float value %f is > %f", f1, f2);
return NO;
}
-(BOOL) checkTanPoint:(CGFloat) f1 isLessThan:(CGFloat)f2{
return [self check:f1 isLessThan:f2 within:.2];
}
@end

View File

@@ -0,0 +1,189 @@
//
// PerformanceBezierClockwiseTests.m
// PerformanceBezier
//
// Created by Adam Wulf on 1/7/14.
// Copyright (c) 2014 Milestone Made, LLC. All rights reserved.
//
#import <XCTest/XCTest.h>
#import "PerformanceBezierAbstractTest.h"
@interface PerformanceBezierClockwiseTests : PerformanceBezierAbstractTest
@end
@implementation PerformanceBezierClockwiseTests
- (void)setUp
{
[super setUp];
// Put setup code here; it will be run once, before the first test case.
}
- (void)tearDown
{
// Put teardown code here; it will be run once, after the last test case.
[super tearDown];
}
- (void)testLinearCounterClockwise
{
UIBezierPath* simplePath = [UIBezierPath bezierPath];
[simplePath moveToPoint:CGPointMake(100, 100)];
[simplePath addLineToPoint:CGPointMake(200, 100)];
[simplePath addLineToPoint:CGPointMake(200, 99)];
XCTAssertTrue(![simplePath isClockwise], @"clockwise is correct");
}
- (void)testLinearClockwise
{
UIBezierPath* simplePath = [UIBezierPath bezierPath];
[simplePath moveToPoint:CGPointMake(100, 100)];
[simplePath addLineToPoint:CGPointMake(200, 100)];
[simplePath addLineToPoint:CGPointMake(200, 101)];
XCTAssertTrue(![simplePath isClockwise], @"clockwise is correct");
}
- (void)testLinearEmptyShape
{
UIBezierPath* simplePath = [UIBezierPath bezierPath];
[simplePath moveToPoint:CGPointMake(100, 100)];
[simplePath addLineToPoint:CGPointMake(200, 100)];
[simplePath addLineToPoint:CGPointMake(200, 101)];
[simplePath closePath];
XCTAssertTrue([simplePath isClockwise], @"clockwise is correct");
}
- (void)testLinearEmptyShape2
{
UIBezierPath* simplePath = [UIBezierPath bezierPath];
[simplePath moveToPoint:CGPointMake(100, 100)];
[simplePath addLineToPoint:CGPointMake(200, 100)];
[simplePath addLineToPoint:CGPointMake(200, 99)];
[simplePath closePath];
XCTAssertTrue(![simplePath isClockwise], @"clockwise is correct");
}
- (void)testSimpleClockwiseCurve
{
UIBezierPath* simplePath = [UIBezierPath bezierPath];
[simplePath moveToPoint:CGPointMake(100, 100)];
[simplePath addCurveToPoint:CGPointMake(200, 100) controlPoint1:CGPointMake(100, 0) controlPoint2:CGPointMake(200, 0)];
XCTAssertTrue([simplePath isClockwise], @"clockwise is correct");
}
- (void)testSimpleCounterClockwiseCurve
{
UIBezierPath* simplePath = [UIBezierPath bezierPath];
[simplePath moveToPoint:CGPointMake(100, 100)];
[simplePath addCurveToPoint:CGPointMake(200, 100) controlPoint1:CGPointMake(100, 200) controlPoint2:CGPointMake(200, 200)];
XCTAssertTrue(![simplePath isClockwise], @"clockwise is correct");
}
- (void)testSimplePath
{
UIBezierPath* simplePath = [UIBezierPath bezierPathWithArcCenter:CGPointZero radius:10 startAngle:0 endAngle:M_PI clockwise:YES];
XCTAssertTrue([simplePath isClockwise], @"clockwise is correct");
}
- (void)testSimplePath2
{
UIBezierPath* simplePath = [UIBezierPath bezierPathWithArcCenter:CGPointZero radius:10 startAngle:0 endAngle:M_PI clockwise:NO];
XCTAssertTrue(![simplePath isClockwise], @"clockwise is correct");
}
- (void)testComplexPath
{
XCTAssertTrue([self.complexShape isClockwise], @"clockwise is correct");
}
- (void)testReversedComplexPath
{
XCTAssertTrue(![[self.complexShape bezierPathByReversingPath] isClockwise], @"clockwise is correct");
}
- (void)testFirstAndLastPointRect {
// This is an example of a functional test case.
UIBezierPath* path1 = [UIBezierPath bezierPathWithRect:CGRectMake(10, 10, 20, 20)];
XCTAssertEqual([path1 elementCount], 5, "element count is correct");
XCTAssertEqual([path1 firstPoint].x, (CGFloat) 10, "element count is correct");
XCTAssertEqual([path1 firstPoint].y, (CGFloat) 10, "element count is correct");
XCTAssertEqual([path1 lastPoint].x, (CGFloat) 10, "element count is correct");
XCTAssertEqual([path1 lastPoint].y, (CGFloat) 10, "element count is correct");
}
- (void)testFirstAndLastPointLine {
// This is an example of a functional test case.
UIBezierPath* path1 = [UIBezierPath bezierPath];
[path1 moveToPoint:CGPointMake(10, 10)];
[path1 addLineToPoint:CGPointMake(30, 30)];
XCTAssertEqual([path1 elementCount], 2, "element count is correct");
XCTAssertEqual([path1 firstPoint].x, (CGFloat) 10, "element count is correct");
XCTAssertEqual([path1 firstPoint].y, (CGFloat) 10, "element count is correct");
XCTAssertEqual([path1 lastPoint].x, (CGFloat) 30, "element count is correct");
XCTAssertEqual([path1 lastPoint].y, (CGFloat) 30, "element count is correct");
}
- (void)testFirstAndLastPointCurve {
// This is an example of a functional test case.
UIBezierPath* path1 = [UIBezierPath bezierPath];
[path1 moveToPoint:CGPointMake(10, 10)];
[path1 addCurveToPoint:CGPointMake(30, 30) controlPoint1:CGPointMake(15, 15) controlPoint2:CGPointMake(25, 25)];
XCTAssertEqual([path1 elementCount], 2, "element count is correct");
XCTAssertEqual([path1 firstPoint].x, (CGFloat) 10, "element count is correct");
XCTAssertEqual([path1 firstPoint].y, (CGFloat) 10, "element count is correct");
XCTAssertEqual([path1 lastPoint].x, (CGFloat) 30, "element count is correct");
XCTAssertEqual([path1 lastPoint].y, (CGFloat) 30, "element count is correct");
}
- (void)testFirstAndLastPointQuadCurve {
// This is an example of a functional test case.
UIBezierPath* path1 = [UIBezierPath bezierPath];
[path1 moveToPoint:CGPointMake(10, 10)];
[path1 addQuadCurveToPoint:CGPointMake(30, 30) controlPoint:CGPointMake(20, 20)];
XCTAssertEqual([path1 elementCount], 2, "element count is correct");
XCTAssertEqual([path1 firstPoint].x, (CGFloat) 10, "element count is correct");
XCTAssertEqual([path1 firstPoint].y, (CGFloat) 10, "element count is correct");
XCTAssertEqual([path1 lastPoint].x, (CGFloat) 30, "element count is correct");
XCTAssertEqual([path1 lastPoint].y, (CGFloat) 30, "element count is correct");
}
- (void)testFirstAndLastPointMoveTo {
// This is an example of a functional test case.
UIBezierPath* path1 = [UIBezierPath bezierPath];
[path1 moveToPoint:CGPointMake(10, 10)];
XCTAssertEqual([path1 elementCount], 1, "element count is correct");
XCTAssertEqual([path1 firstPoint].x, (CGFloat) 10, "element count is correct");
XCTAssertEqual([path1 firstPoint].y, (CGFloat) 10, "element count is correct");
XCTAssertEqual([path1 lastPoint].x, (CGFloat) 10, "element count is correct");
XCTAssertEqual([path1 lastPoint].y, (CGFloat) 10, "element count is correct");
}
- (void)testFirstAndLastPointMoveTo2 {
// This is an example of a functional test case.
UIBezierPath* path1 = [UIBezierPath bezierPathWithRect:CGRectMake(10, 10, 20, 20)];
[path1 moveToPoint:CGPointMake(50, 50)];
XCTAssertEqual([path1 elementCount], 6, "element count is correct");
XCTAssertEqual([path1 firstPoint].x, (CGFloat) 10, "element count is correct");
XCTAssertEqual([path1 firstPoint].y, (CGFloat) 10, "element count is correct");
XCTAssertEqual([path1 lastPoint].x, (CGFloat) 50, "element count is correct");
XCTAssertEqual([path1 lastPoint].y, (CGFloat) 50, "element count is correct");
}
@end

View File

@@ -0,0 +1,67 @@
iOS UIBezierPath Performance
=====
This code dramatically improves performance for common UIBezierPath operations, and it also
brings UIBezierPath API closer to its NSBezierPath counterpart. For full background of this
repo, checkout [the blogpost explaining what this framework does](http://blog.getlooseleaf.com/post/110511009139/improving-uibezierpath-performance-and-api).
This code was originally part of [Loose Leaf](https://getlooseleaf.com/). Additional components and
libraries from the app [have also been open sourced](https://getlooseleaf.com/opensource/).
## What is this?
This framework adds caching into every UIBezierPath so that common operations can
be performed in constant time. It also adds some missing NSBezierPath methods to the
UIBezierPath class.
After linking this framework into your project, all Bezier paths will automatically be upgraded
to use this new caching. No custom UIBezierPath allocation or initialization is required.
For example, by default there is no O(1) way to retrieve elements from a UIBezierPath. In order to
retrieve the first point of the curve, you must CGPathApply() and interate over the entire path
to retrieve that single point. This framework changes that. For many algorithms, this can
dramatically affect performance.
## Are you using PerformanceBezier?
Let me know! I'd love to know where PerformanceBezier is using and how it's affecting your apps. Ping me
at [@adamwulf](https://twitter.com/adamwulf)!
Also - If you like PerformanceBezier, then you'll _love_ [ClippingBezier](https://github.com/adamwulf/ClippingBezier) - an easy way to find intersecting points, lines, and shapes between two UIBezierPaths.
## Documentation
View the header files for full documentation.
## Building the framework
This library will generate a proper static framework bundle that can be used in any iOS7+ project.
## Including in your project
1. Link against the built framework.
2. Add "-ObjC++ -lstdc++" to the Other Linker Flags in the project's Settings
3. #import &lt;PerformanceBezier/PerformanceBezier.h&gt;
## JRSwizzle
This framework includes and uses the [JRSwizzle](https://github.com/rentzsch/jrswizzle) library, which is
licensed under the MIT license.
## License
<a rel="license" href="http://creativecommons.org/licenses/by/3.0/us/"><img alt="Creative Commons License" style="border-width:0" src="https://i.creativecommons.org/l/by/3.0/us/88x31.png" /></a><br />This work is licensed under a <a rel="license" href="http://creativecommons.org/licenses/by/3.0/us/">Creative Commons Attribution 3.0 United States License</a>.
For attribution, please include:
1. Mention original author "Adam Wulf for Loose Leaf app"
2. Link to https://getlooseleaf.com/opensource/
3. Link to https://github.com/adamwulf/PerformanceBezier
## Support this framework
This framework is created by Adam Wulf ([@adamwulf](https://twitter.com/adamwulf)) as a part of the [Loose Leaf app](https://getlooseleaf.com).
[Buy the app](https://itunes.apple.com/us/app/loose-leaf/id625659452?mt=8&uo=4&at=10lNUI&ct=github) to show your support! :)

View File

@@ -0,0 +1,32 @@
/*
* Bezier path utility category (trimming)
*
* (c) 2004 Alastair J. Houghton
* All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* 3. The name of the author of this software may not be used to endorse
* or promote products derived from the software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER "AS IS" AND ANY EXPRESS
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT OWNER BE LIABLE FOR ANY DIRECT, INDIRECT,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/

View File

@@ -0,0 +1,17 @@
/*
Erica Sadun, http://ericasadun.com
BEZIER PACK
*/
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#import <CoreText/CoreText.h>
#import "BaseGeometry.h"
#import "BezierUtils.h"
#import "BezierElement.h"
#import "BezierFunctions.h"
#import "UIBezierPath+Elements.h"

View File

@@ -0,0 +1,34 @@
/*
Erica Sadun, http://ericasadun.com
*/
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
// Block for transforming points
typedef CGPoint (^PathBlock)(CGPoint point);
@interface BezierElement : NSObject <NSCopying>
// Element storage
@property (nonatomic, assign) CGPathElementType elementType;
@property (nonatomic, assign) CGPoint point;
@property (nonatomic, assign) CGPoint controlPoint1;
@property (nonatomic, assign) CGPoint controlPoint2;
// Instance creation
+ (instancetype) elementWithPathElement: (CGPathElement) element;
// Applying transformations
- (BezierElement *) elementByApplyingBlock: (PathBlock) block;
// Adding to path
- (void) addToPath: (UIBezierPath *) path;
// Readable forms
@property (nonatomic, readonly) NSString *stringValue;
- (void) showTheCode;
@end;

View File

@@ -0,0 +1,163 @@
/*
Erica Sadun, http://ericasadun.com
*/
#import "Bezier.h"
#pragma mark - Bezier Element -
@implementation BezierElement
- (instancetype) init
{
self = [super init];
if (self)
{
_elementType = kCGPathElementMoveToPoint;
_point = NULLPOINT;
_controlPoint1 = NULLPOINT;
_controlPoint2 = NULLPOINT;
}
return self;
}
+ (instancetype) elementWithPathElement: (CGPathElement) element
{
BezierElement *newElement = [[self alloc] init];
newElement.elementType = element.type;
switch (newElement.elementType)
{
case kCGPathElementCloseSubpath:
break;
case kCGPathElementMoveToPoint:
case kCGPathElementAddLineToPoint:
{
newElement.point = element.points[0];
break;
}
case kCGPathElementAddQuadCurveToPoint:
{
newElement.point = element.points[1];
newElement.controlPoint1 = element.points[0];
break;
}
case kCGPathElementAddCurveToPoint:
{
newElement.point = element.points[2];
newElement.controlPoint1 = element.points[0];
newElement.controlPoint2 = element.points[1];
break;
}
default:
break;
}
return newElement;
}
- (instancetype) copyWithZone: (NSZone *) zone
{
BezierElement *theCopy = [[[self class] allocWithZone:zone] init];
if (theCopy)
{
theCopy.elementType = _elementType;
theCopy.point = _point;
theCopy.controlPoint1 = _controlPoint1;
theCopy.controlPoint2 = _controlPoint2;
}
return theCopy;
}
#pragma mark - Path
- (BezierElement *) elementByApplyingBlock: (PathBlock) block
{
BezierElement *output = [self copy];
if (!block)
return output;
if (!POINT_IS_NULL(output.point))
output.point = block(output.point);
if (!POINT_IS_NULL(output.controlPoint1))
output.controlPoint1 = block(output.controlPoint1);
if (!POINT_IS_NULL(output.controlPoint2))
output.controlPoint2 = block(output.controlPoint2);
return output;
}
- (void) addToPath: (UIBezierPath *) path
{
switch (self.elementType)
{
case kCGPathElementCloseSubpath:
[path closePath];
break;
case kCGPathElementMoveToPoint:
[path moveToPoint:self.point];
break;
case kCGPathElementAddLineToPoint:
[path addLineToPoint:self.point];
break;
case kCGPathElementAddQuadCurveToPoint:
[path addQuadCurveToPoint:self.point controlPoint:self.controlPoint1];
break;
case kCGPathElementAddCurveToPoint:
[path addCurveToPoint:self.point controlPoint1:self.controlPoint1 controlPoint2:self.controlPoint2];
break;
default:
break;
}
}
#pragma mark - Strings
- (NSString *) stringValue
{
switch (self.elementType)
{
case kCGPathElementCloseSubpath:
return @"Close Path";
case kCGPathElementMoveToPoint:
return [NSString stringWithFormat:@"Move to point %@", POINTSTRING(self.point)];
case kCGPathElementAddLineToPoint:
return [NSString stringWithFormat:@"Add line to point %@", POINTSTRING(self.point)];
case kCGPathElementAddQuadCurveToPoint:
return [NSString stringWithFormat:@"Add quad curve to point %@ with control point %@", POINTSTRING(self.point), POINTSTRING(self.controlPoint1)];
case kCGPathElementAddCurveToPoint:
return [NSString stringWithFormat:@"Add curve to point %@ with control points %@ and %@", POINTSTRING(self.point), POINTSTRING(self.controlPoint1), POINTSTRING(self.controlPoint2)];
}
return nil;
}
- (void) showTheCode
{
switch (self.elementType)
{
case kCGPathElementCloseSubpath:
printf(" [path closePath];\n\n");
break;
case kCGPathElementMoveToPoint:
printf(" [path moveToPoint:CGPointMake(%f, %f)];\n",
self.point.x, self.point.y);
break;
case kCGPathElementAddLineToPoint:
printf(" [path addLineToPoint:CGPointMake(%f, %f)];\n",
self.point.x, self.point.y);
break;
case kCGPathElementAddQuadCurveToPoint:
printf(" [path addQuadCurveToPoint:CGPointMake(%f, %f) controlPoint:CGPointMake(%f, %f)];\n",
self.point.x, self.point.y, self.controlPoint1.x, self.controlPoint1.y);
break;
case kCGPathElementAddCurveToPoint:
printf(" [path addCurveToPoint:CGPointMake(%f, %f) controlPoint1:CGPointMake(%f, %f) controlPoint2:CGPointMake(%f, %f)];\n",
self.point.x, self.point.y, self.controlPoint1.x, self.controlPoint1.y, self.controlPoint2.x, self.controlPoint2.y);
break;
default:
break;
}
}
@end

View File

@@ -0,0 +1,38 @@
/*
Erica Sadun, http://ericasadun.com
*/
#import <Foundation/Foundation.h>
#import "BezierElement.h"
#define NUMBER_OF_BEZIER_SAMPLES 6
typedef CGFloat (^InterpolationBlock)(CGFloat percent);
// Return Bezier Value
float CubicBezier(float t, float start, float c1, float c2, float end);
float QuadBezier(float t, float start, float c1, float end);
// Return Bezier Point
CGPoint CubicBezierPoint(CGFloat t, CGPoint start, CGPoint c1, CGPoint c2, CGPoint end);
CGPoint QuadBezierPoint(CGFloat t, CGPoint start, CGPoint c1, CGPoint end);
// Calculate Curve Length
float CubicBezierLength(CGPoint start, CGPoint c1, CGPoint c2, CGPoint end);
float QuadBezierLength(CGPoint start, CGPoint c1, CGPoint end);
// Element Distance
CGFloat ElementDistanceFromPoint(BezierElement *element, CGPoint point, CGPoint startPoint);
// Linear Interpolation
CGPoint InterpolateLineSegment(CGPoint p1, CGPoint p2, CGFloat percent, CGPoint *slope);
// Interpolate along element
CGPoint InterpolatePointFromElement(BezierElement *element, CGPoint point, CGPoint startPoint, CGFloat percent, CGPoint *slope);
// Ease
CGFloat EaseIn(CGFloat currentTime, int factor);
CGFloat EaseOut(CGFloat currentTime, int factor);
CGFloat EaseInOut(CGFloat currentTime, int factor);

View File

@@ -0,0 +1,205 @@
/*
Erica Sadun, http://ericasadun.com
*/
#import "Bezier.h"
#pragma mark - Bezier functions
float CubicBezier(float t, float start, float c1, float c2, float end)
{
CGFloat t_ = (1.0 - t);
CGFloat tt_ = t_ * t_;
CGFloat ttt_ = t_ * t_ * t_;
CGFloat tt = t * t;
CGFloat ttt = t * t * t;
return start * ttt_
+ 3.0 * c1 * tt_ * t
+ 3.0 * c2 * t_ * tt
+ end * ttt;
}
float QuadBezier(float t, float start, float c1, float end)
{
CGFloat t_ = (1.0 - t);
CGFloat tt_ = t_ * t_;
CGFloat tt = t * t;
return start * tt_
+ 2.0 * c1 * t_ * t
+ end * tt;
}
CGPoint CubicBezierPoint(CGFloat t, CGPoint start, CGPoint c1, CGPoint c2, CGPoint end)
{
CGPoint result;
result.x = CubicBezier(t, start.x, c1.x, c2.x, end.x);
result.y = CubicBezier(t, start.y, c1.y, c2.y, end.y);
return result;
}
CGPoint QuadBezierPoint(CGFloat t, CGPoint start, CGPoint c1, CGPoint end)
{
CGPoint result;
result.x = QuadBezier(t, start.x, c1.x, end.x);
result.y = QuadBezier(t, start.y, c1.y, end.y);
return result;
}
float CubicBezierLength(CGPoint start, CGPoint c1, CGPoint c2, CGPoint end)
{
int steps = NUMBER_OF_BEZIER_SAMPLES;
CGPoint current;
CGPoint previous;
float length = 0.0;
for (int i = 0; i <= steps; i++)
{
float t = (float) i / (float) steps;
current = CubicBezierPoint(t, start, c1, c2, end);
if (i > 0)
length += PointDistanceFromPoint(current, previous);
previous = current;
}
return length;
}
float QuadBezierLength(CGPoint start, CGPoint c1, CGPoint end)
{
int steps = NUMBER_OF_BEZIER_SAMPLES;
CGPoint current;
CGPoint previous;
float length = 0.0;
for (int i = 0; i <= steps; i++)
{
float t = (float) i / (float) steps;
current = QuadBezierPoint(t, start, c1, end);
if (i > 0)
length += PointDistanceFromPoint(current, previous);
previous = current;
}
return length;
}
#pragma mark - Point Percents
#define USE_CURVE_TWEAK 0
#if USE_CURVE_TWEAK
#define CURVETWEAK 0.8
#else
#define CURVETWEAK 1.0
#endif
CGFloat ElementDistanceFromPoint(BezierElement *element, CGPoint point, CGPoint startPoint)
{
CGFloat distance = 0.0f;
switch (element.elementType)
{
case kCGPathElementMoveToPoint:
return 0.0f;
case kCGPathElementCloseSubpath:
return PointDistanceFromPoint(point, startPoint);
case kCGPathElementAddLineToPoint:
return PointDistanceFromPoint(point, element.point);
case kCGPathElementAddCurveToPoint:
return CubicBezierLength(point, element.controlPoint1, element.controlPoint2, element.point) * CURVETWEAK;
case kCGPathElementAddQuadCurveToPoint:
return QuadBezierLength(point, element.controlPoint1, element.point) * CURVETWEAK;
}
return distance;
}
// Centralize for both close subpath and add line to point
CGPoint InterpolateLineSegment(CGPoint p1, CGPoint p2, CGFloat percent, CGPoint *slope)
{
CGFloat dx = p2.x - p1.x;
CGFloat dy = p2.y - p1.y;
if (slope)
*slope = CGPointMake(dx, dy);
CGFloat px = p1.x + dx * percent;
CGFloat py = p1.y + dy * percent;
return CGPointMake(px, py);
}
// Interpolate along element
CGPoint InterpolatePointFromElement(BezierElement *element, CGPoint point, CGPoint startPoint, CGFloat percent, CGPoint *slope)
{
switch (element.elementType)
{
case kCGPathElementMoveToPoint:
{
// No distance
if (slope)
*slope = CGPointMake(INFINITY, INFINITY);
return point;
}
case kCGPathElementCloseSubpath:
{
// from self.point to firstPoint
CGPoint p = InterpolateLineSegment(point, startPoint, percent, slope);
return p;
}
case kCGPathElementAddLineToPoint:
{
// from point to self.point
CGPoint p = InterpolateLineSegment(point, element.point, percent, slope);
return p;
}
case kCGPathElementAddQuadCurveToPoint:
{
// from point to self.point
CGPoint p = QuadBezierPoint(percent, point, element.controlPoint1, element.point);
CGFloat dx = p.x - QuadBezier(percent * 0.9, point.x, element.controlPoint1.x, element.point.x);
CGFloat dy = p.y - QuadBezier(percent * 0.9, point.y, element.controlPoint1.y, element.point.y);
if (slope)
*slope = CGPointMake(dx, dy);
return p;
}
case kCGPathElementAddCurveToPoint:
{
// from point to self.point
CGPoint p = CubicBezierPoint(percent, point, element.controlPoint1, element.controlPoint2, element.point);
CGFloat dx = p.x - CubicBezier(percent * 0.9, point.x, element.controlPoint1.x, element.controlPoint2.x, element.point.x);
CGFloat dy = p.y - CubicBezier(percent * 0.9, point.y, element.controlPoint1.y, element.controlPoint2.y, element.point.y);
if (slope)
*slope = CGPointMake(dx, dy);
return p;
}
}
return NULLPOINT;
}
CGFloat EaseIn(CGFloat currentTime, int factor)
{
return powf(currentTime, factor);
}
CGFloat EaseOut(CGFloat currentTime, int factor)
{
return 1 - powf((1 - currentTime), factor);
}
CGFloat EaseInOut(CGFloat currentTime, int factor)
{
currentTime = currentTime * 2.0;
if (currentTime < 1)
return (0.5 * pow(currentTime, factor));
currentTime -= 2.0;
if (factor % 2)
return 0.5 * (pow(currentTime, factor) + 2.0);
return 0.5 * (2.0 - pow(currentTime, factor));
}

View File

@@ -0,0 +1,87 @@
/*
Erica Sadun, http://ericasadun.com
*/
#import <Foundation/Foundation.h>
#import "BaseGeometry.h"
CGRect PathBoundingBox(UIBezierPath *path);
CGRect PathBoundingBoxWithLineWidth(UIBezierPath *path);
CGPoint PathBoundingCenter(UIBezierPath *path);
CGPoint PathCenter(UIBezierPath *path);
// Transformations
void ApplyCenteredPathTransform(UIBezierPath *path, CGAffineTransform transform);
UIBezierPath *PathByApplyingTransform(UIBezierPath *path, CGAffineTransform transform);
// Utility
void RotatePath(UIBezierPath *path, CGFloat theta);
void ScalePath(UIBezierPath *path, CGFloat sx, CGFloat sy);
void OffsetPath(UIBezierPath *path, CGSize offset);
void MovePathToPoint(UIBezierPath *path, CGPoint point);
void MovePathCenterToPoint(UIBezierPath *path, CGPoint point);
void MirrorPathHorizontally(UIBezierPath *path);
void MirrorPathVertically(UIBezierPath *path);
// Fitting
void FitPathToRect(UIBezierPath *path, CGRect rect);
void AdjustPathToRect(UIBezierPath *path, CGRect destRect);
// Path Attributes
void CopyBezierState(UIBezierPath *source, UIBezierPath *destination);
void CopyBezierDashes(UIBezierPath *source, UIBezierPath *destination);
void AddDashesToPath(UIBezierPath *path);
// String to Path
UIBezierPath *BezierPathFromString(NSString *string, UIFont *font);
UIBezierPath *BezierPathFromStringWithFontFace(NSString *string, NSString *fontFace);
// Draw Text in Path
void DrawAttributedStringInBezierPath(UIBezierPath *path, NSAttributedString *attributedString);
void DrawAttributedStringInBezierSubpaths(UIBezierPath *path, NSAttributedString *attributedString);
// N-Gons
UIBezierPath *BezierPolygon(NSUInteger numberOfSides);
UIBezierPath *BezierInflectedShape(NSUInteger numberOfInflections, CGFloat percentInflection);
UIBezierPath *BezierStarShape(NSUInteger numberOfInflections, CGFloat percentInflection);
// Misc
void ClipToRect(CGRect rect);
void FillRect(CGRect rect, UIColor *color);
void ShowPathProgression(UIBezierPath *path, CGFloat maxPercent);
// Effects
void SetShadow(UIColor *color, CGSize size, CGFloat blur);
void DrawShadow(UIBezierPath *path, UIColor *color, CGSize size, CGFloat blur);
void DrawInnerShadow(UIBezierPath *path, UIColor *color, CGSize size, CGFloat blur);
void EmbossPath(UIBezierPath *path, UIColor *color, CGFloat radius, CGFloat blur);
void BevelPath(UIBezierPath *p, UIColor *color, CGFloat r, CGFloat theta);
void InnerBevel(UIBezierPath *p, UIColor *color, CGFloat r, CGFloat theta);
void ExtrudePath(UIBezierPath *path, UIColor *color, CGFloat radius, CGFloat angle);
@interface UIBezierPath (HandyUtilities)
@property (nonatomic, readonly) CGPoint center;
@property (nonatomic, readonly) CGRect computedBounds;
@property (nonatomic, readonly) CGRect computedBoundsWithLineWidth;
// Stroke/Fill
- (void) stroke: (CGFloat) width;
- (void) stroke: (CGFloat) width color: (UIColor *) color;
- (void) strokeInside: (CGFloat) width;
- (void) strokeInside: (CGFloat) width color: (UIColor *) color;
- (void) fill: (UIColor *) fillColor;
- (void) fill: (UIColor *) fillColor withMode: (CGBlendMode) blendMode;
- (void) fillWithNoise: (UIColor *) fillColor;
- (void) addDashes;
- (void) addDashes: (NSArray *) pattern;
- (void) applyPathPropertiesToContext;
// Clipping
- (void) clipToPath; // I hate addClip
- (void) clipToStroke: (NSUInteger) width;
// Util
- (UIBezierPath *) safeCopy;
@end

View File

@@ -0,0 +1,709 @@
/*
Erica Sadun, http://ericasadun.com
*/
#import <CoreText/CoreText.h>
#import "BezierUtils.h"
#import "Utility.h"
#pragma mark - Bounds
CGRect PathBoundingBox(UIBezierPath *path)
{
return CGPathGetPathBoundingBox(path.CGPath);
}
CGRect PathBoundingBoxWithLineWidth(UIBezierPath *path)
{
CGRect bounds = PathBoundingBox(path);
return CGRectInset(bounds, -path.lineWidth / 2.0f, -path.lineWidth / 2.0f);
}
CGPoint PathBoundingCenter(UIBezierPath *path)
{
return RectGetCenter(PathBoundingBox(path));
}
CGPoint PathCenter(UIBezierPath *path)
{
return RectGetCenter(path.bounds);
}
#pragma mark - Misc
void ClipToRect(CGRect rect)
{
[[UIBezierPath bezierPathWithRect:rect] addClip];
}
void FillRect(CGRect rect, UIColor *color)
{
[[UIBezierPath bezierPathWithRect:rect] fill:color];
}
void ShowPathProgression(UIBezierPath *path, CGFloat maxPercent)
{
if (!path) COMPLAIN_AND_BAIL(@"Path cannot be nil", nil);
CGContextRef context = UIGraphicsGetCurrentContext();
if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil);
CGFloat maximumPercent = fmax(fmin(maxPercent, 1.0f), 0.0f);
PushDraw(^{
CGFloat distance = path.pathLength;
int samples = distance / 6;
float dLevel = 0.75 / (CGFloat) samples;
UIBezierPath *marker;
for (int i = 0; i <= samples * maximumPercent; i++)
{
CGFloat percent = (CGFloat) i / (CGFloat) samples;
CGPoint point = [path pointAtPercent:percent withSlope:NULL];
UIColor *color = [UIColor colorWithWhite:i * dLevel alpha:1];
CGRect r = RectAroundCenter(point, CGSizeMake(2, 2));
marker = [UIBezierPath bezierPathWithOvalInRect:r];
[marker fill:color];
}
});
}
#pragma mark - Transform
void ApplyCenteredPathTransform(UIBezierPath *path, CGAffineTransform transform)
{
CGPoint center = PathBoundingCenter(path);
CGAffineTransform t = CGAffineTransformIdentity;
t = CGAffineTransformTranslate(t, center.x, center.y);
t = CGAffineTransformConcat(transform, t);
t = CGAffineTransformTranslate(t, -center.x, -center.y);
[path applyTransform:t];
}
UIBezierPath *PathByApplyingTransform(UIBezierPath *path, CGAffineTransform transform)
{
UIBezierPath *copy = [path copy];
ApplyCenteredPathTransform(copy, transform);
return copy;
}
void RotatePath(UIBezierPath *path, CGFloat theta)
{
CGAffineTransform t = CGAffineTransformMakeRotation(theta);
ApplyCenteredPathTransform(path, t);
}
void ScalePath(UIBezierPath *path, CGFloat sx, CGFloat sy)
{
CGAffineTransform t = CGAffineTransformMakeScale(sx, sy);
ApplyCenteredPathTransform(path, t);
}
void OffsetPath(UIBezierPath *path, CGSize offset)
{
CGAffineTransform t = CGAffineTransformMakeTranslation(offset.width, offset.height);
ApplyCenteredPathTransform(path, t);
}
void MovePathToPoint(UIBezierPath *path, CGPoint destPoint)
{
CGRect bounds = PathBoundingBox(path);
CGPoint p1 = bounds.origin;
CGPoint p2 = destPoint;
CGSize vector = CGSizeMake(p2.x - p1.x, p2.y - p1.y);
OffsetPath(path, vector);
}
void MovePathCenterToPoint(UIBezierPath *path, CGPoint destPoint)
{
CGRect bounds = PathBoundingBox(path);
CGPoint p1 = bounds.origin;
CGPoint p2 = destPoint;
CGSize vector = CGSizeMake(p2.x - p1.x, p2.y - p1.y);
vector.width -= bounds.size.width / 2.0f;
vector.height -= bounds.size.height / 2.0f;
OffsetPath(path, vector);
}
void MirrorPathHorizontally(UIBezierPath *path)
{
CGAffineTransform t = CGAffineTransformMakeScale(-1, 1);
ApplyCenteredPathTransform(path, t);
}
void MirrorPathVertically(UIBezierPath *path)
{
CGAffineTransform t = CGAffineTransformMakeScale(1, -1);
ApplyCenteredPathTransform(path, t);
}
void FitPathToRect(UIBezierPath *path, CGRect destRect)
{
CGRect bounds = PathBoundingBox(path);
CGRect fitRect = RectByFittingRect(bounds, destRect);
CGFloat scale = AspectScaleFit(bounds.size, destRect);
CGPoint newCenter = RectGetCenter(fitRect);
MovePathCenterToPoint(path, newCenter);
ScalePath(path, scale, scale);
}
void AdjustPathToRect(UIBezierPath *path, CGRect destRect)
{
CGRect bounds = PathBoundingBox(path);
CGFloat scaleX = destRect.size.width / bounds.size.width;
CGFloat scaleY = destRect.size.height / bounds.size.height;
CGPoint newCenter = RectGetCenter(destRect);
MovePathCenterToPoint(path, newCenter);
ScalePath(path, scaleX, scaleY);
}
#pragma mark - Path Attributes
void AddDashesToPath(UIBezierPath *path)
{
CGFloat dashes[] = {6, 2};
[path setLineDash:dashes count:2 phase:0];
}
void CopyBezierDashes(UIBezierPath *source, UIBezierPath *destination)
{
NSInteger count;
[source getLineDash:NULL count:&count phase:NULL];
CGFloat phase;
CGFloat *pattern = malloc(count * sizeof(CGFloat));
[source getLineDash:pattern count:&count phase:&phase];
[destination setLineDash:pattern count:count phase:phase];
free(pattern);
}
void CopyBezierState(UIBezierPath *source, UIBezierPath *destination)
{
destination.lineWidth = source.lineWidth;
destination.lineCapStyle = source.lineCapStyle;
destination.lineJoinStyle = source.lineJoinStyle;
destination.miterLimit = source.miterLimit;
destination.flatness = source.flatness;
destination.usesEvenOddFillRule = source.usesEvenOddFillRule;
CopyBezierDashes(source, destination);
}
#pragma mark - Text
UIBezierPath *BezierPathFromString(NSString *string, UIFont *font)
{
// Initialize path
UIBezierPath *path = [UIBezierPath bezierPath];
if (!string.length) return path;
// Create font ref
CTFontRef fontRef = CTFontCreateWithName((__bridge CFStringRef)font.fontName, font.pointSize, NULL);
if (fontRef == NULL)
{
NSLog(@"Error retrieving CTFontRef from UIFont");
return nil;
}
// Create glyphs
CGGlyph *glyphs = malloc(sizeof(CGGlyph) * string.length);
const unichar *chars = (const unichar *)[string cStringUsingEncoding:NSUnicodeStringEncoding];
BOOL success = CTFontGetGlyphsForCharacters(fontRef, chars, glyphs, string.length);
if (!success)
{
NSLog(@"Error retrieving string glyphs");
CFRelease(fontRef);
free(glyphs);
return nil;
}
// Draw each char into path
for (int i = 0; i < string.length; i++)
{
CGGlyph glyph = glyphs[i];
CGPathRef pathRef = CTFontCreatePathForGlyph(fontRef, glyph, NULL);
[path appendPath:[UIBezierPath bezierPathWithCGPath:pathRef]];
CGPathRelease(pathRef);
CGSize size = [[string substringWithRange:NSMakeRange(i, 1)] sizeWithAttributes:@{NSFontAttributeName:font}];
OffsetPath(path, CGSizeMake(-size.width, 0));
}
// Clean up
free(glyphs);
CFRelease(fontRef);
// Math
MirrorPathVertically(path);
return path;
}
UIBezierPath *BezierPathFromStringWithFontFace(NSString *string, NSString *fontFace)
{
UIFont *font = [UIFont fontWithName:fontFace size:16];
if (!font)
font = [UIFont systemFontOfSize:16];
return BezierPathFromString(string, font);
}
// Listing 8-1
void MirrorPathVerticallyInContext(UIBezierPath *path)
{
CGContextRef context = UIGraphicsGetCurrentContext();
if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil);
CGSize size = GetUIKitContextSize();
CGRect contextRect = SizeMakeRect(size);
CGPoint center = RectGetCenter(contextRect);
CGAffineTransform t = CGAffineTransformIdentity;
t = CGAffineTransformTranslate(t, center.x, center.y);
t = CGAffineTransformScale(t, 1, -1);
t = CGAffineTransformTranslate(t, -center.x, -center.y);
[path applyTransform:t];
}
void DrawAttributedStringIntoSubpath(UIBezierPath *path, NSAttributedString *attributedString, NSAttributedString **remainder)
{
CGContextRef context = UIGraphicsGetCurrentContext();
if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil);
UIBezierPath *copy = [path safeCopy];
MirrorPathVerticallyInContext(copy);
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef) attributedString);
CTFrameRef theFrame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, attributedString.length), copy.CGPath, NULL);
if (remainder)
{
CFRange range = CTFrameGetVisibleStringRange(theFrame);
NSInteger startLocation = range.location + range.length;
NSInteger extent = attributedString.length - startLocation;
NSAttributedString *substring = [attributedString attributedSubstringFromRange:NSMakeRange(startLocation, extent)];
*remainder = substring;
}
PushDraw(^{
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
FlipContextVertically(GetUIKitContextSize());
CTFrameDraw(theFrame, UIGraphicsGetCurrentContext());
});
CFRelease(theFrame);
CFRelease(framesetter);
}
void DrawAttributedStringInBezierPath(UIBezierPath *path, NSAttributedString *attributedString)
{
DrawAttributedStringIntoSubpath(path, attributedString, nil);
}
void DrawAttributedStringInBezierSubpaths(UIBezierPath *path, NSAttributedString *attributedString)
{
NSAttributedString *string;
NSAttributedString *remainder = attributedString;
for (UIBezierPath *subpath in path.subpaths)
{
string = remainder;
DrawAttributedStringIntoSubpath(subpath, string, &remainder);
if (remainder.length == 0) return;
}
}
#pragma mark - Polygon Fun
UIBezierPath *BezierPolygon(NSUInteger numberOfSides)
{
if (numberOfSides < 3)
{
NSLog(@"Error: Please supply at least 3 sides");
return nil;
}
CGRect destinationRect = CGRectMake(0, 0, 1, 1);
UIBezierPath *path = [UIBezierPath bezierPath];
CGPoint center = RectGetCenter(destinationRect);
CGFloat r = 0.5f; // radius
BOOL firstPoint = YES;
for (int i = 0; i < (numberOfSides - 1); i++)
{
CGFloat theta = M_PI + i * TWO_PI / numberOfSides;
CGFloat dTheta = TWO_PI / numberOfSides;
CGPoint p;
if (firstPoint)
{
p.x = center.x + r * sin(theta);
p.y = center.y + r * cos(theta);
[path moveToPoint:p];
firstPoint = NO;
}
p.x = center.x + r * sin(theta + dTheta);
p.y = center.y + r * cos(theta + dTheta);
[path addLineToPoint:p];
}
[path closePath];
return path;
}
UIBezierPath *BezierInflectedShape(NSUInteger numberOfInflections, CGFloat percentInflection)
{
if (numberOfInflections < 3)
{
NSLog(@"Error: Please supply at least 3 inflections");
return nil;
}
UIBezierPath *path = [UIBezierPath bezierPath];
CGRect destinationRect = CGRectMake(0, 0, 1, 1);
CGPoint center = RectGetCenter(destinationRect);
CGFloat r = 0.5;
CGFloat rr = r * (1.0 + percentInflection);
BOOL firstPoint = YES;
for (int i = 0; i < numberOfInflections; i++)
{
CGFloat theta = i * TWO_PI / numberOfInflections;
CGFloat dTheta = TWO_PI / numberOfInflections;
if (firstPoint)
{
CGFloat xa = center.x + r * sin(theta);
CGFloat ya = center.y + r * cos(theta);
CGPoint pa = CGPointMake(xa, ya);
[path moveToPoint:pa];
firstPoint = NO;
}
CGFloat cp1x = center.x + rr * sin(theta + dTheta / 3);
CGFloat cp1y = center.y + rr * cos(theta + dTheta / 3);
CGPoint cp1 = CGPointMake(cp1x, cp1y);
CGFloat cp2x = center.x + rr * sin(theta + 2 * dTheta / 3);
CGFloat cp2y = center.y + rr * cos(theta + 2 * dTheta / 3);
CGPoint cp2 = CGPointMake(cp2x, cp2y);
CGFloat xb = center.x + r * sin(theta + dTheta);
CGFloat yb = center.y + r * cos(theta + dTheta);
CGPoint pb = CGPointMake(xb, yb);
[path addCurveToPoint:pb controlPoint1:cp1 controlPoint2:cp2];
}
[path closePath];
return path;
}
UIBezierPath *BezierStarShape(NSUInteger numberOfInflections, CGFloat percentInflection)
{
if (numberOfInflections < 3)
{
NSLog(@"Error: Please supply at least 3 inflections");
return nil;
}
UIBezierPath *path = [UIBezierPath bezierPath];
CGRect destinationRect = CGRectMake(0, 0, 1, 1);
CGPoint center = RectGetCenter(destinationRect);
CGFloat r = 0.5;
CGFloat rr = r * (1.0 + percentInflection);
BOOL firstPoint = YES;
for (int i = 0; i < numberOfInflections; i++)
{
CGFloat theta = i * TWO_PI / numberOfInflections;
CGFloat dTheta = TWO_PI / numberOfInflections;
if (firstPoint)
{
CGFloat xa = center.x + r * sin(theta);
CGFloat ya = center.y + r * cos(theta);
CGPoint pa = CGPointMake(xa, ya);
[path moveToPoint:pa];
firstPoint = NO;
}
CGFloat cp1x = center.x + rr * sin(theta + dTheta / 2);
CGFloat cp1y = center.y + rr * cos(theta + dTheta / 2);
CGPoint cp1 = CGPointMake(cp1x, cp1y);
CGFloat xb = center.x + r * sin(theta + dTheta);
CGFloat yb = center.y + r * cos(theta + dTheta);
CGPoint pb = CGPointMake(xb, yb);
[path addLineToPoint:cp1];
[path addLineToPoint:pb];
}
[path closePath];
return path;
}
#pragma mark - Shadows
// Establish context shadow state
void SetShadow(UIColor *color, CGSize size, CGFloat blur)
{
if (!color) COMPLAIN_AND_BAIL(@"Color cannot be nil", nil);
CGContextRef context = UIGraphicsGetCurrentContext();
if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil);
if (color)
CGContextSetShadowWithColor(context, size, blur, color.CGColor);
else
CGContextSetShadow(context, size, blur);
}
// Draw *only* the shadow
void DrawShadow(UIBezierPath *path, UIColor *color, CGSize size, CGFloat blur)
{
if (!path) COMPLAIN_AND_BAIL(@"Path cannot be nil", nil);
if (!color) COMPLAIN_AND_BAIL(@"Color cannot be nil", nil);
CGContextRef context = UIGraphicsGetCurrentContext();
if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil);
// Build shadow
PushDraw(^{
SetShadow(color, CGSizeMake(size.width, size.height), blur);
[path.inverse addClip];
[path fill:color];
});
}
// Draw shadow inside shape
void DrawInnerShadow(UIBezierPath *path, UIColor *color, CGSize size, CGFloat blur)
{
if (!path) COMPLAIN_AND_BAIL(@"Path cannot be nil", nil);
if (!color) COMPLAIN_AND_BAIL(@"Color cannot be nil", nil);
CGContextRef context = UIGraphicsGetCurrentContext();
if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil);
// Build shadow
PushDraw(^{
SetShadow(color, CGSizeMake(size.width, size.height), blur);
[path addClip];
[path.inverse fill:color];
});
}
#pragma mark - Photoshop Style Effects
UIColor *ContrastColor(UIColor *color)
{
if (CGColorSpaceGetNumberOfComponents(CGColorGetColorSpace(color.CGColor)) == 3)
{
CGFloat r, g, b, a;
[color getRed:&r green:&g blue:&b alpha:&a];
CGFloat luminance = r * 0.2126f + g * 0.7152f + b * 0.0722f;
return (luminance > 0.5f) ? [UIColor blackColor] : [UIColor whiteColor];
}
CGFloat w, a;
[color getWhite:&w alpha:&a];
return (w > 0.5f) ? [UIColor blackColor] : [UIColor whiteColor];
}
// Create 3d embossed effect
// Typically call with black color at 0.5
void EmbossPath(UIBezierPath *path, UIColor *color, CGFloat radius, CGFloat blur)
{
if (!path) COMPLAIN_AND_BAIL(@"Path cannot be nil", nil);
CGContextRef context = UIGraphicsGetCurrentContext();
if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil);
UIColor *contrast = ContrastColor(color);
DrawInnerShadow(path, contrast, CGSizeMake(-radius, radius), blur);
DrawInnerShadow(path, color, CGSizeMake(radius, -radius), blur);
}
// Half an emboss
void InnerBevel(UIBezierPath *path, UIColor *color, CGFloat radius, CGFloat theta)
{
if (!path) COMPLAIN_AND_BAIL(@"Path cannot be nil", nil);
if (!color) COMPLAIN_AND_BAIL(@"Color cannot be nil", nil);
CGContextRef context = UIGraphicsGetCurrentContext();
if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil);
CGFloat x = radius * sin(theta);
CGFloat y = radius * cos(theta);
UIColor *shadowColor = [color colorWithAlphaComponent:0.5f];
DrawInnerShadow(path, shadowColor, CGSizeMake(-x, y), 2);
}
// I don't love this
void ExtrudePath(UIBezierPath *path, UIColor *color, CGFloat radius, CGFloat theta)
{
if (!path) COMPLAIN_AND_BAIL(@"Path cannot be nil", nil);
if (!color) COMPLAIN_AND_BAIL(@"Color cannot be nil", nil);
CGContextRef context = UIGraphicsGetCurrentContext();
if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil);
CGFloat x = radius * sin(theta);
CGFloat y = radius * cos(theta);
DrawShadow(path, color, CGSizeMake(x, y), 0);
}
// Typically call with black color at 0.5
void BevelPath(UIBezierPath *path, UIColor *color, CGFloat radius, CGFloat theta)
{
if (!path) COMPLAIN_AND_BAIL(@"Path cannot be nil", nil);
if (!color) COMPLAIN_AND_BAIL(@"Color cannot be nil", nil);
CGContextRef context = UIGraphicsGetCurrentContext();
if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil);
CGFloat x = radius * sin(theta);
CGFloat y = radius * cos(theta);
DrawInnerShadow(path, color, CGSizeMake(-x, y), 2);
DrawShadow(path, color, CGSizeMake(x / 2 , -y / 2), 0);
}
@implementation UIBezierPath (HandyUtilities)
#pragma mark - Bounds
- (CGPoint) center
{
return PathBoundingCenter(self);
}
- (CGRect) computedBounds
{
return PathBoundingBox(self);
}
- (CGRect) computedBoundsWithLineWidth
{
return PathBoundingBoxWithLineWidth(self);
}
#pragma mark - Stroking and Filling
- (void) addDashes
{
AddDashesToPath(self);
}
- (void) addDashes: (NSArray *) pattern
{
if (!pattern.count) return;
CGFloat *dashes = malloc(pattern.count * sizeof(CGFloat));
for (int i = 0; i < pattern.count; i++)
dashes[i] = [pattern[i] floatValue];
[self setLineDash:dashes count:pattern.count phase:0];
free(dashes);
}
- (void) applyPathPropertiesToContext
{
CGContextRef context = UIGraphicsGetCurrentContext();
if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil);
CGContextSetLineWidth(context, self.lineWidth);
CGContextSetLineCap(context, self.lineCapStyle);
CGContextSetLineJoin(context, self.lineJoinStyle);
CGContextSetMiterLimit(context, self.miterLimit);
CGContextSetFlatness(context, self.flatness);
NSInteger count;
[self getLineDash:NULL count:&count phase:NULL];
CGFloat phase;
CGFloat *pattern = malloc(count * sizeof(CGFloat));
[self getLineDash:pattern count:&count phase:&phase];
CGContextSetLineDash(context, phase, pattern, count);
free(pattern);
}
- (void) stroke: (CGFloat) width color: (UIColor *) color
{
CGContextRef context = UIGraphicsGetCurrentContext();
if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil);
PushDraw(^{
if (color) [color setStroke];
CGFloat holdWidth = self.lineWidth;
if (width > 0)
self.lineWidth = width;
[self stroke];
self.lineWidth = holdWidth;
});
}
- (void) stroke: (CGFloat) width
{
[self stroke:width color:nil];
}
- (void) strokeInside: (CGFloat) width color: (UIColor *) color
{
CGContextRef context = UIGraphicsGetCurrentContext();
if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil);
PushDraw(^{
[self addClip];
[self stroke:width * 2 color:color];
});
}
- (void) strokeInside: (CGFloat) width
{
[self strokeInside:width color:nil];
}
- (void) fill: (UIColor *) fillColor
{
CGContextRef context = UIGraphicsGetCurrentContext();
if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil);
PushDraw(^{
if (fillColor)
[fillColor set];
[self fill];
});
}
- (void) fill: (UIColor *) fillColor withMode: (CGBlendMode) blendMode
{
CGContextRef context = UIGraphicsGetCurrentContext();
if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil);
PushDraw(^{
CGContextSetBlendMode(context, blendMode);
[self fill:fillColor];
});
}
- (void) fillWithNoise: (UIColor *) fillColor
{
CGContextRef context = UIGraphicsGetCurrentContext();
if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil);
[self fill:fillColor];
[self fill:[NoiseColor() colorWithAlphaComponent:0.05f] withMode:kCGBlendModeScreen];
}
#pragma mark - Clippage
- (void) clipToPath
{
[self addClip];
}
- (void) clipToStroke:(NSUInteger)width
{
CGPathRef pathRef = CGPathCreateCopyByStrokingPath(self.CGPath, NULL, width, kCGLineCapButt, kCGLineJoinMiter, 4);
UIBezierPath *clipPath = [UIBezierPath bezierPathWithCGPath:pathRef];
CGPathRelease(pathRef);
[clipPath addClip];
}
#pragma mark - Misc
- (UIBezierPath *) safeCopy
{
UIBezierPath *p = [UIBezierPath bezierPath];
[p appendPath:self];
CopyBezierState(self, p);
return p;
}
@end

View File

@@ -0,0 +1,56 @@
/*
Erica Sadun, http://ericasadun.com
*/
#import <Foundation/Foundation.h>
#import "Bezier.h"
// Construct path
UIBezierPath *BezierPathWithElements(NSArray *elements);
UIBezierPath *BezierPathWithPoints(NSArray *points);
UIBezierPath *InterpolatedPath(UIBezierPath *path);
// Partial paths
UIBezierPath *CroppedPath(UIBezierPath *path, CGFloat percent);
UIBezierPath *PathFromPercentToPercent(UIBezierPath *path, CGFloat startPercent, CGFloat endPercent);
/*
UIBezierPath - Elements Category
*/
@interface UIBezierPath (Elements)
@property (nonatomic, readonly) NSArray *elements;
@property (nonatomic, readonly) NSArray *subpaths;
@property (nonatomic, readonly) NSArray *destinationPoints;
@property (nonatomic, readonly) NSArray *interpolatedPathPoints;
@property (nonatomic, readonly) NSUInteger count;
- (id)objectAtIndexedSubscript:(NSUInteger)idx;
@property (nonatomic, readonly) CGPoint center;
@property (nonatomic, readonly) CGRect calculatedBounds;
@property (nonatomic, readonly) UIBezierPath *reversed;
@property (nonatomic, readonly) UIBezierPath *inverse;
@property (nonatomic, readonly) UIBezierPath *boundedInverse;
@property (nonatomic, readonly) BOOL subpathIsClosed;
- (BOOL) closeSafely;
// Measure length
@property (nonatomic, readonly) CGFloat pathLength;
- (CGPoint) pointAtPercent: (CGFloat) percent withSlope: (CGPoint *) slope;
// String Representations
- (void) showTheCode;
- (NSString *) stringValue;
// -- Invert path to arbitrary rectangle
- (UIBezierPath *) inverseInRect: (CGRect) rect;
@end

View File

@@ -0,0 +1,519 @@
/*
Erica Sadun, http://ericasadun.com
*/
#import "UIBezierPath+Elements.h"
#import "BaseGeometry.h"
#pragma mark - Construction
// Return Bezier path built with the supplied elements
UIBezierPath *BezierPathWithElements(NSArray *elements)
{
UIBezierPath *path = [UIBezierPath bezierPath];
for (BezierElement *element in elements)
[element addToPath:path];
return path;
}
UIBezierPath *InterpolatedPath(UIBezierPath *path)
{
return BezierPathWithPoints(path.interpolatedPathPoints);
}
#define POINT(_X_) ([(NSValue *)points[_X_] CGPointValue])
// Pass array with NSValue'd CGRect points
UIBezierPath *BezierPathWithPoints(NSArray *points)
{
UIBezierPath *path = [UIBezierPath bezierPath];
if (!points.count)
return path;
[path moveToPoint:POINT(0)];
for (int i = 1; i < points.count; i++)
[path addLineToPoint:POINT(i)];
[path closePath];
return path;
}
#pragma mark - Partial Path
UIBezierPath *CroppedPath(UIBezierPath *path, CGFloat percent)
{
NSArray *elements = path.elements;
if (elements.count == 0) return path;
int targetCount = elements.count * percent;
NSArray *targetElements = [elements subarrayWithRange:NSMakeRange(0, targetCount)];
UIBezierPath *outputPath = BezierPathWithElements(targetElements);
return outputPath;
}
UIBezierPath *PathFromPercentToPercent(UIBezierPath *path, CGFloat startPercent, CGFloat endPercent)
{
NSArray *elements = path.elements;
if (elements.count == 0) return path;
int targetCount = elements.count * endPercent;
NSArray *targetElements = [elements subarrayWithRange:NSMakeRange(0, targetCount)];
UIBezierPath *outputPath = BezierPathWithElements(targetElements);
return outputPath;
}
#pragma mark - Bezier Elements Category -
@implementation UIBezierPath (Elements)
// Convert one element to BezierElement and save to array
void GetBezierElements(void *info, const CGPathElement *element)
{
NSMutableArray *bezierElements = (__bridge NSMutableArray *)info;
if (element)
[bezierElements addObject:[BezierElement elementWithPathElement:*element]];
}
// Retrieve array of component elements
- (NSArray *) elements
{
NSMutableArray *elements = [NSMutableArray array];
CGPathApply(self.CGPath, (__bridge void *)elements, GetBezierElements);
return elements;
}
#pragma mark - Subpaths
// Subpaths must be well defined
- (NSMutableArray *) subpaths
{
NSMutableArray *results = [NSMutableArray array];
UIBezierPath *current = nil;
NSArray *elements = self.elements;
for (BezierElement *element in elements)
{
if (element.elementType == kCGPathElementCloseSubpath)
{
[current closePath];
if (current)
[results addObject:current];
current = nil;
continue;
}
if (element.elementType == kCGPathElementMoveToPoint)
{
if (current)
[results addObject:current];
current = [UIBezierPath bezierPath];
[current moveToPoint:element.point];
continue;
}
if (current)
[element addToPath:current];
else
{
NSLog(@"Error: cannot add element to nil path: %@", element.stringValue);
continue;
}
}
if (current)
[results addObject:current];
return results;
}
// Only collect those points that have destinations
- (NSArray *) destinationPoints
{
NSMutableArray *array = [NSMutableArray array];
NSArray *elements = self.elements;
for (BezierElement *element in elements)
if (!POINT_IS_NULL(element.point))
[array addObject:[NSValue valueWithCGPoint:element.point]];
return array;
}
// Points and interpolated points
- (NSArray *) interpolatedPathPoints
{
NSMutableArray *points = [NSMutableArray array];
BezierElement *current = nil;
int overkill = 3;
for (BezierElement *element in self.elements)
{
switch (element.elementType)
{
case kCGPathElementMoveToPoint:
case kCGPathElementAddLineToPoint:
[points addObject:[NSValue valueWithCGPoint:element.point]];
current = element;
break;
case kCGPathElementCloseSubpath:
current = nil;
break;
case kCGPathElementAddCurveToPoint:
{
for (int i = 1; i < NUMBER_OF_BEZIER_SAMPLES * overkill; i++)
{
CGFloat percent = (CGFloat) i / (CGFloat) (NUMBER_OF_BEZIER_SAMPLES * overkill);
CGPoint p = CubicBezierPoint(percent, current.point, element.controlPoint1, element.controlPoint2, element.point);
[points addObject:[NSValue valueWithCGPoint:p]];
}
[points addObject:[NSValue valueWithCGPoint:element.point]];
current = element;
break;
}
case kCGPathElementAddQuadCurveToPoint:
{
for (int i = 1; i < NUMBER_OF_BEZIER_SAMPLES * overkill; i++)
{
CGFloat percent = (CGFloat) i / (CGFloat) (NUMBER_OF_BEZIER_SAMPLES * overkill);
CGPoint p = QuadBezierPoint(percent, current.point, element.controlPoint1, element.point);
[points addObject:[NSValue valueWithCGPoint:p]];
}
[points addObject:[NSValue valueWithCGPoint:element.point]];
current = element;
break;
}
}
}
return points;
}
#pragma mark - Array Access
- (NSUInteger) count
{
return self.elements.count;
}
- (id)objectAtIndexedSubscript:(NSUInteger)idx
{
NSArray *elements = self.elements;
if (idx >= elements.count)
return nil;
return elements[idx];
}
#pragma mark - Geometry Workaround
- (CGRect) calculatedBounds
{
// Thank you Ryan Petrich
return CGPathGetPathBoundingBox(self.CGPath);
}
// Return center of bounds
- (CGPoint) center
{
return RectGetCenter(self.calculatedBounds);
}
#pragma mark - Reversal Workaround
- (UIBezierPath *) reverseSubpath: (UIBezierPath *) subpath
{
NSArray *elements = subpath.elements;
NSArray *reversedElements = [[elements reverseObjectEnumerator] allObjects];
UIBezierPath *newPath = [UIBezierPath bezierPath];
CopyBezierState(self, newPath);
BOOL closesSubpath = NO;
BezierElement *firstElement;
for (BezierElement *e in elements)
{
if (!POINT_IS_NULL(e.point))
{
firstElement = e;
break;
}
}
BezierElement *lastElement;
for (BezierElement *e in reversedElements)
{
if (!POINT_IS_NULL(e.point))
{
lastElement = e;
break;
}
}
BezierElement *element = [elements lastObject];
if (element.elementType == kCGPathElementCloseSubpath)
{
if (firstElement)
[newPath moveToPoint:firstElement.point];
if (lastElement)
[newPath addLineToPoint:lastElement.point];
closesSubpath = YES;
}
else
{
[newPath moveToPoint:lastElement.point];
}
CFIndex i = 0;
for (BezierElement *element in reversedElements)
{
i++;
BezierElement *nextElement = nil;
BezierElement *workElement = [element copy];
if (element.elementType == kCGPathElementCloseSubpath)
continue;
if (element == firstElement)
{
if (closesSubpath) [newPath closePath];
continue;
}
if (i < reversedElements.count)
{
nextElement = reversedElements[i];
if (!POINT_IS_NULL(workElement.controlPoint2))
{
CGPoint tmp = workElement.controlPoint1;
workElement.controlPoint1 = workElement.controlPoint2;
workElement.controlPoint2 = tmp;
}
workElement.point = nextElement.point;
}
if (element.elementType == kCGPathElementMoveToPoint)
workElement.elementType = kCGPathElementAddLineToPoint;
[workElement addToPath:newPath];
}
return newPath;
}
- (UIBezierPath *) reversed
{
// [self bezierPathByReversingPath] seriously does not work the
// way you expect. Radars are filed.
UIBezierPath *reversed = [UIBezierPath bezierPath];
NSArray *reversedSubpaths = [[self.subpaths reverseObjectEnumerator] allObjects];
for (UIBezierPath *subpath in reversedSubpaths)
{
UIBezierPath *p = [self reverseSubpath:subpath];
if (p)
[reversed appendPath:p];
}
return reversed;
}
#pragma mark - Closing
- (BOOL) subpathIsClosed
{
NSArray *elements = self.elements;
// A legal closed path must contain 3 elements
// move, add, close
if (elements.count < 3)
return NO;
BezierElement *element = [elements lastObject];
return element.elementType == kCGPathElementCloseSubpath;
}
- (BOOL) closeSafely
{
NSArray *elements = self.elements;
if (elements.count < 2)
return NO;
BezierElement *element = [elements lastObject];
if (element.elementType != kCGPathElementCloseSubpath)
{
[self closePath];
return YES;
}
return NO;
}
#pragma mark - Show the Code
- (void) showTheCode
{
printf("\n- (UIBezierPath *) buildBezierPath\n");
printf("{\n");
printf(" UIBezierPath *path = [UIBezierPath bezierPath];\n\n");
NSArray *elements = self.elements;
for (BezierElement *element in elements)
[element showTheCode];
printf(" return path;\n");
printf("}\n\n");
}
- (NSString *) stringValue
{
NSMutableString *string = [NSMutableString stringWithString:@"\n"];
NSArray *elements = self.elements;
for (BezierElement *element in elements)
[string appendFormat:@"%@\n", element.stringValue];
return string;
}
#pragma mark - Transformations
// Project point from native to dest
CGPoint adjustPoint(CGPoint p, CGRect native, CGRect dest)
{
CGFloat scaleX = dest.size.width / native.size.width;
CGFloat scaleY = dest.size.height / native.size.height;
CGPoint point = PointSubtractPoint(p, native.origin);
point.x *= scaleX;
point.y *= scaleY;
CGPoint destPoint = PointAddPoint(point, dest.origin);
return destPoint;
}
// Adjust points by applying block to each element
- (UIBezierPath *) adjustPathElementsWithBlock: (PathBlock) block
{
UIBezierPath *path = [UIBezierPath bezierPath];
if (!block)
{
[path appendPath:self];
return path;
}
for (BezierElement *element in self.elements)
[[element elementByApplyingBlock:block] addToPath:path];
return path;
}
// Apply transform
- (UIBezierPath *) pathApplyTransform: (CGAffineTransform) transform
{
UIBezierPath *copy = [UIBezierPath bezierPath];
[copy appendPath:self];
CGRect bounding = self.calculatedBounds;
CGPoint center = RectGetCenter(bounding);
CGAffineTransform t = CGAffineTransformIdentity;
t = CGAffineTransformTranslate(t, center.x, center.y);
t = CGAffineTransformConcat(transform, t);
t = CGAffineTransformTranslate(t, -center.x, -center.y);
[copy applyTransform:t];
return copy;
}
- (CGFloat) pathLength
{
NSArray *elements = self.elements;
CGPoint current = NULLPOINT;
CGPoint firstPoint = NULLPOINT;
float totalPointLength = 0.0f;
for (BezierElement *element in elements)
{
totalPointLength += ElementDistanceFromPoint(element, current, firstPoint);
if (element.elementType == kCGPathElementMoveToPoint)
firstPoint = element.point;
else if (element.elementType == kCGPathElementCloseSubpath)
firstPoint = NULLPOINT;
if (element.elementType != kCGPathElementCloseSubpath)
current = element.point;
}
return totalPointLength;
}
// Retrieve the point and slope at a given percent offset -- This is expensive
- (CGPoint) pointAtPercent: (CGFloat) percent withSlope: (CGPoint *) slope
{
NSArray *elements = self.elements;
if (percent == 0.0f)
{
BezierElement *first = [elements objectAtIndex:0];
return first.point;
}
float pathLength = self.pathLength;
float totalDistance = 0.0f;
CGPoint current = NULLPOINT;
CGPoint firstPoint = NULLPOINT;
for (BezierElement *element in elements)
{
float distance = ElementDistanceFromPoint(element, current, firstPoint);
CGFloat proposedTotalDistance = totalDistance + distance;
CGFloat proposedPercent = proposedTotalDistance / pathLength;
if (proposedPercent < percent)
{
// consume and continue
totalDistance = proposedTotalDistance;
if (element.elementType == kCGPathElementMoveToPoint)
firstPoint = element.point;
current = element.point;
continue;
}
// What percent between p1 and p2?
CGFloat currentPercent = totalDistance / pathLength;
CGFloat dPercent = percent - currentPercent;
CGFloat percentDistance = dPercent * pathLength;
CGFloat targetPercent = percentDistance / distance;
// Return result
CGPoint point = InterpolatePointFromElement(element, current, firstPoint, targetPercent, slope);
return point;
}
return NULLPOINT;
}
#pragma mark - Inverses
- (UIBezierPath *) inverseInRect: (CGRect) rect
{
UIBezierPath *path = [UIBezierPath bezierPath];
CopyBezierState(self, path);
[path appendPath:self];
[path appendPath:[UIBezierPath bezierPathWithRect:rect]];
path.usesEvenOddFillRule = YES;
return path;
}
- (UIBezierPath *) inverse
{
return [self inverseInRect:CGRectInfinite];
}
- (UIBezierPath *) boundedInverse
{
return [self inverseInRect:self.bounds];
}
@end

View File

@@ -0,0 +1,22 @@
/*
Erica Sadun, http://ericasadun.com
*/
#import <QuartzCore/QuartzCore.h>
#import <UIKit/UIKit.h>
typedef void (^DrawingBlock)(CGRect bounds);
typedef void (^DrawingStateBlock)();
void PushDraw(DrawingStateBlock block);
void PushLayerDraw(DrawingStateBlock block);
// Image
UIImage *ImageWithBlock(DrawingBlock block, CGSize size);
UIImage *DrawIntoImage(CGSize size, DrawingStateBlock block);
// Blurring
void DrawAndBlur(CGFloat radius, DrawingStateBlock block);

View File

@@ -0,0 +1,71 @@
/*
Erica Sadun, http://ericasadun.com
*/
#import "Drawing-Block.h"
#import "Utility.h"
#pragma mark - Drawing
UIImage *ImageWithBlock(DrawingBlock block, CGSize size)
{
UIGraphicsBeginImageContextWithOptions(size, NO, 0.0);
if (block) block((CGRect){.size = size});
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
void PushDraw(DrawingStateBlock block)
{
if (!block) return; // nothing to do
CGContextRef context = UIGraphicsGetCurrentContext();
if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil);
CGContextSaveGState(context);
block();
CGContextRestoreGState(context);
}
// Improve performance by pre-clipping context
// before beginning layer drawing
void PushLayerDraw(DrawingStateBlock block)
{
if (!block) return; // nothing to do
CGContextRef context = UIGraphicsGetCurrentContext();
if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil);
CGContextBeginTransparencyLayer(context, NULL);
block();
CGContextEndTransparencyLayer(context);
}
UIImage *DrawIntoImage(CGSize size, DrawingStateBlock block)
{
UIGraphicsBeginImageContextWithOptions(size, NO, 0.0);
if (block) block();
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
#define DEBUG_IMAGE(_IMAGE_, _NAME_) [UIImagePNGRepresentation(_IMAGE_) writeToFile:[NSString stringWithFormat:@"/Users/ericasadun/Desktop/%@.png", _NAME_] atomically:YES]
// Create a blurred drawing group
// Listing 7-4
void DrawAndBlur(CGFloat radius, DrawingStateBlock block)
{
if (!block) return; // nothing to do
CGContextRef context = UIGraphicsGetCurrentContext();
if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil);
// Draw and blur the image
UIImage *baseImage = DrawIntoImage(GetUIKitContextSize(), block);
UIImage *blurred = GaussianBlurImage(baseImage, radius);
[blurred drawAtPoint:CGPointZero];
}

View File

@@ -0,0 +1,44 @@
/*
Erica Sadun, http://ericasadun.com
*/
#import <Foundation/Foundation.h>
#import "BezierFunctions.h"
#define COLOR_LEVEL(_selector_, _alpha_) [([UIColor _selector_])colorWithAlphaComponent:_alpha_]
#define WHITE_LEVEL(_amt_, _alpha_) [UIColor colorWithWhite:(_amt_) alpha:(_alpha_)]
// Gradient drawing styles
#define LIMIT_GRADIENT_EXTENT 0
#define BEFORE_START kCGGradientDrawsBeforeStartLocation
#define AFTER_END kCGGradientDrawsAfterEndLocation
#define KEEP_DRAWING kCGGradientDrawsAfterEndLocation | kCGGradientDrawsBeforeStartLocation
typedef __attribute__((NSObject)) CGGradientRef GradientObject;
@interface Gradient : NSObject
@property (nonatomic, readonly) CGGradientRef gradient;
+ (instancetype) gradientWithColors: (NSArray *) colors locations: (NSArray *) locations;
+ (instancetype) gradientFrom: (UIColor *) color1 to: (UIColor *) color2;
+ (instancetype) rainbow;
+ (instancetype) linearGloss:(UIColor *) color;
+ (instancetype) gradientUsingInterpolationBlock: (InterpolationBlock) block between: (UIColor *) c1 and: (UIColor *) c2;
+ (instancetype) easeInGradientBetween: (UIColor *) c1 and:(UIColor *) c2;
+ (instancetype) easeInOutGradientBetween: (UIColor *) c1 and:(UIColor *) c2;
+ (instancetype) easeOutGradientBetween: (UIColor *) c1 and:(UIColor *) c2;
- (void) drawFrom:(CGPoint) p1 toPoint: (CGPoint) p2 style: (int) mask;
- (void) drawRadialFrom:(CGPoint) p1 toPoint: (CGPoint) p2 radii: (CGPoint) radii style: (int) mask;
- (void) drawTopToBottom: (CGRect) rect;
- (void) drawBottomToTop: (CGRect) rect;
- (void) drawLeftToRight: (CGRect) rect;
- (void) drawFrom:(CGPoint) p1 toPoint: (CGPoint) p2;
- (void) drawAlongAngle: (CGFloat) angle in:(CGRect) rect;
- (void) drawBasicRadial: (CGRect) rect;
- (void) drawRadialFrom: (CGPoint) p1 toPoint: (CGPoint) p2;
@end;

View File

@@ -0,0 +1,256 @@
/*
Erica Sadun, http://ericasadun.com
*/
#import "Drawing-Gradient.h"
#import "Utility.h"
@interface Gradient ()
@property (nonatomic, strong) GradientObject storedGradient;
@end
@implementation Gradient
#pragma mark - Internal
- (CGGradientRef) gradient
{
return _storedGradient;
}
#pragma mark - Convenience Creation
+ (instancetype) gradientWithColors: (NSArray *) colorsArray locations: (NSArray *) locationArray
{
if (!colorsArray) COMPLAIN_AND_BAIL_NIL(@"Missing colors array", nil);
if (!locationArray) COMPLAIN_AND_BAIL_NIL(@"Missing location array", nil);
CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
if (space == NULL)
{
NSLog(@"Error: Unable to create device RGB color space");
return nil;
}
// Convert locations to CGFloat *
CGFloat locations[locationArray.count];
for (int i = 0; i < locationArray.count; i++)
locations[i] = [locationArray[i] floatValue];
// Convert colors to (id) CGColorRef
NSMutableArray *colorRefArray = [NSMutableArray array];
for (UIColor *color in colorsArray)
[colorRefArray addObject:(id)color.CGColor];
CGGradientRef gradientRef = CGGradientCreateWithColors(space, (__bridge CFArrayRef) colorRefArray, locations);
CGColorSpaceRelease(space);
if (gradientRef == NULL)
{
NSLog(@"Error: Unable to construct CGGradientRef");
return nil;
}
Gradient *gradient = [[self alloc] init];
gradient.storedGradient = gradientRef;
CGGradientRelease(gradientRef);
return gradient;
}
+ (instancetype) gradientFrom: (UIColor *) color1 to: (UIColor *) color2
{
return [self gradientWithColors:@[color1, color2] locations:@[@(0.0f), @(1.0f)]];
}
#pragma mark - Linear
- (void) drawRadialFrom:(CGPoint) p1 toPoint: (CGPoint) p2 radii: (CGPoint) radii style: (int) mask
{
CGContextRef context = UIGraphicsGetCurrentContext();
if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil);
CGContextDrawRadialGradient(context, self.gradient, p1, radii.x, p2, radii.y, mask);
}
- (void) drawFrom:(CGPoint) p1 toPoint: (CGPoint) p2 style: (int) mask
{
CGContextRef context = UIGraphicsGetCurrentContext();
if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil);
CGContextDrawLinearGradient(context, self.gradient, p1, p2, mask);
}
- (void) drawLeftToRight: (CGRect) rect
{
CGPoint p1 = RectGetMidLeft(rect);
CGPoint p2 = RectGetMidRight(rect);
[self drawFrom:p1 toPoint:p2 style:KEEP_DRAWING];
}
- (void) drawTopToBottom: (CGRect) rect
{
CGPoint p1 = RectGetMidTop(rect);
CGPoint p2 = RectGetMidBottom(rect);
[self drawFrom:p1 toPoint:p2 style:KEEP_DRAWING];
}
- (void) drawBottomToTop:(CGRect)rect
{
CGPoint p1 = RectGetMidBottom(rect);
CGPoint p2 = RectGetMidTop(rect);
[self drawFrom:p1 toPoint:p2 style:KEEP_DRAWING];
}
- (void) drawFrom:(CGPoint) p1 toPoint: (CGPoint) p2
{
[self drawFrom:p1 toPoint:p2 style:KEEP_DRAWING];
}
- (void) drawAlongAngle: (CGFloat) theta in:(CGRect) rect
{
CGPoint center = RectGetCenter(rect);
CGFloat r = PointDistanceFromPoint(center, RectGetTopRight(rect));
CGFloat phi = theta + M_PI;
if (phi > TWO_PI)
phi -= TWO_PI;
CGFloat dx1 = r * sin(theta);
CGFloat dy1 = r * cos(theta);
CGFloat dx2 = r * sin(phi);
CGFloat dy2 = r * cos(phi);
CGPoint p1 = CGPointMake(center.x + dx1, center.y + dy1);
CGPoint p2 = CGPointMake(center.x + dx2, center.y + dy2);
[self drawFrom:p1 toPoint:p2];
}
#pragma mark - Radial
- (void) drawBasicRadial: (CGRect) rect
{
CGPoint p1 = RectGetCenter(rect);
CGFloat r = CGRectGetWidth(rect) / 2;
[self drawRadialFrom:p1 toPoint:p1 radii:CGPointMake(0, r) style:KEEP_DRAWING];
}
- (void) drawRadialFrom: (CGPoint) p1 toPoint: (CGPoint) p2;
{
[self drawRadialFrom:p1 toPoint:p1 radii:CGPointMake(0, PointDistanceFromPoint(p1, p2)) style:KEEP_DRAWING];
}
#pragma mark - Prebuilt
+ (instancetype) rainbow
{
NSMutableArray *colors = [NSMutableArray array];
NSMutableArray *locations = [NSMutableArray array];
int n = 24;
for (int i = 0; i <= n; i++)
{
CGFloat percent = (CGFloat) i / (CGFloat) n;
CGFloat colorDistance = percent * (CGFloat) (n - 1) / (CGFloat) n;
UIColor *color = [UIColor colorWithHue:colorDistance saturation:1 brightness:1 alpha:1];
[colors addObject:color];
[locations addObject:@(percent)];
}
return [Gradient gradientWithColors:colors locations:locations];
}
+ (instancetype) linearGloss:(UIColor *) color
{
CGFloat r, g, b, a;
[color getRed:&r green:&g blue:&b alpha:&a];
CGFloat l = (0.299f * r + 0.587f * g + 0.114f * b);
CGFloat gloss = pow(l, 0.2) * 0.5;
CGFloat h, s, v;
[color getHue:&h saturation:&s brightness:&v alpha:NULL];
s = fminf(s, 0.2f);
// Rotate by 0.6 PI
CGFloat rHue = ((h < 0.95) && (h > 0.7)) ? 0.67 : 0.17;
CGFloat phi = rHue * M_PI * 2;
CGFloat theta = h * M_PI;
// Interpolate distance
CGFloat dTheta = (theta - phi);
while (dTheta < 0) dTheta += M_PI * 2;
while (dTheta > 2 * M_PI) dTheta -= M_PI_2;
CGFloat factor = 0.7 + 0.3 * cosf(dTheta);
// Build highlight colors
UIColor *c1 = [UIColor colorWithHue:h * factor + (1 - factor) * rHue saturation:s brightness:v * factor + (1 - factor) alpha:gloss];
UIColor *c2 = [c1 colorWithAlphaComponent:0];
// Build gradient
NSArray *colors = @[WHITE_LEVEL(1, gloss), WHITE_LEVEL(1, 0.2), c2, c1];
NSArray *locations = @[@(0.0), @(0.5), @(0.5), @(1)];
return [Gradient gradientWithColors:colors locations:locations];
}
UIColor *InterpolateBetweenColors(UIColor *c1, UIColor *c2, CGFloat amt)
{
CGFloat r1, g1, b1, a1;
CGFloat r2, g2, b2, a2;
if (CGColorGetNumberOfComponents(c1.CGColor) == 4)
[c1 getRed:&r1 green:&g1 blue:&b1 alpha:&a1];
else
{
[c1 getWhite:&r1 alpha:&a1];
g1 = r1; b1 = r1;
}
if (CGColorGetNumberOfComponents(c2.CGColor) == 4)
[c2 getRed:&r2 green:&g2 blue:&b2 alpha:&a2];
else
{
[c2 getWhite:&r2 alpha:&a2];
g2 = r2; b2 = r2;
}
CGFloat r = (r2 * amt) + (r1 * (1.0 - amt));
CGFloat g = (g2 * amt) + (g1 * (1.0 - amt));
CGFloat b = (b2 * amt) + (b1 * (1.0 - amt));
CGFloat a = (a2 * amt) + (a1 * (1.0 - amt));
return [UIColor colorWithRed:r green:g blue:b alpha:a];
}
+ (instancetype) gradientUsingInterpolationBlock: (InterpolationBlock) block between: (UIColor *) c1 and: (UIColor *) c2;
{
if (!block)
COMPLAIN_AND_BAIL_NIL(@"Must pass interpolation block", nil);
NSMutableArray *colors = [NSMutableArray array];
NSMutableArray *locations = [NSMutableArray array];
int numberOfSamples = 24;
for (int i = 0; i <= numberOfSamples; i++)
{
CGFloat amt = (CGFloat) i / (CGFloat) numberOfSamples;
CGFloat percentage = Clamp(block(amt), 0.0, 1.0);
[colors addObject:InterpolateBetweenColors(c1, c2, percentage)];
[locations addObject:@(amt)];
}
return [Gradient gradientWithColors:colors locations:locations];
}
+ (instancetype) easeInGradientBetween: (UIColor *) c1 and:(UIColor *) c2
{
return [self gradientUsingInterpolationBlock:^CGFloat(CGFloat percent) {return EaseIn(percent, 3);} between:c1 and:c2];
}
+ (instancetype) easeInOutGradientBetween: (UIColor *) c1 and:(UIColor *) c2
{
return [self gradientUsingInterpolationBlock:^CGFloat(CGFloat percent) {return EaseInOut(percent, 3);} between:c1 and:c2];
}
+ (instancetype) easeOutGradientBetween: (UIColor *) c1 and:(UIColor *) c2
{
return [self gradientUsingInterpolationBlock:^CGFloat(CGFloat percent) {return EaseOut(percent, 3);} between:c1 and:c2];
}
@end

View File

@@ -0,0 +1,25 @@
/*
Erica Sadun, http://ericasadun.com
*/
#import <Foundation/Foundation.h>
#import "Drawing-Gradient.h"
UIColor *ScaleColorBrightness(UIColor *color, CGFloat amount);
void DrawStrokedShadowedShape(UIBezierPath *path, UIColor *baseColor, CGRect dest);
void DrawStrokedShadowedText(NSString *string, NSString *fontFace, UIColor *baseColor, CGRect dest);
void DrawIndentedPath(UIBezierPath *path, UIColor *primary, CGRect rect);
void DrawIndentedText(NSString *string, NSString *fontFace, UIColor *primary, CGRect rect);
void DrawGradientOverTexture(UIBezierPath *path, UIImage *texture, Gradient *gradient, CGFloat alpha);
void DrawBottomGlow(UIBezierPath *path, UIColor *color, CGFloat percent);
void DrawIconTopLight(UIBezierPath *path, CGFloat p);
CGSize GetUIKitContextSize();
UIImage *GradientMaskedReflectionImage(UIImage *sourceImage);
void DrawGradientMaskedReflection(UIImage *image, CGRect rect);;
void ApplyMaskToContext(UIImage *mask);

View File

@@ -0,0 +1,241 @@
/*
Erica Sadun, http://ericasadun.com
*/
#import "Drawing-Util.h"
#import "Utility.h"
UIColor *ScaleColorBrightness(UIColor *color, CGFloat amount)
{
CGFloat h, s, v, a;
[color getHue:&h saturation:&s brightness:&v alpha:&a];
CGFloat v1 = Clamp(v * amount, 0, 1);
return [UIColor colorWithHue:h saturation:s brightness:v1 alpha:a];
}
void DrawStrokedShadowedShape(UIBezierPath *path, UIColor *baseColor, CGRect dest)
{
CGContextRef context = UIGraphicsGetCurrentContext();
if (!context) COMPLAIN_AND_BAIL(@"No context to draw to", nil);
PushDraw(^{
CGContextSetShadow(context, CGSizeMake(4, 4), 4);
PushLayerDraw(^{
// Draw letter gradient (to half brightness)
PushDraw(^{
Gradient *innerGradient = [Gradient gradientFrom:baseColor to:ScaleColorBrightness(baseColor, 0.5)];
[path addClip];
[innerGradient drawTopToBottom:path.bounds];
});
// Add the inner shadow with darker color
PushDraw(^{
CGContextSetBlendMode(context, kCGBlendModeMultiply);
DrawInnerShadow(path, ScaleColorBrightness(baseColor, 0.3), CGSizeMake(0, -2), 2);
});
// Stroke with reversed gray gradient
PushDraw(^{
[path clipToStroke:6];
[path.inverse addClip];
Gradient *grayGradient = [Gradient gradientFrom:WHITE_LEVEL(0.0, 1) to:WHITE_LEVEL(0.5, 1)];
[grayGradient drawTopToBottom:dest];
});
});
});
}
void DrawStrokedShadowedText(NSString *string, NSString *fontFace, UIColor *baseColor, CGRect dest)
{
// Create text path
UIBezierPath *text = BezierPathFromStringWithFontFace(string, fontFace);
FitPathToRect(text, dest);
DrawStrokedShadowedShape(text, baseColor, dest);
}
void DrawIndentedPath(UIBezierPath *path, UIColor *primary, CGRect rect)
{
CGContextRef context = UIGraphicsGetCurrentContext();
if (!context) COMPLAIN_AND_BAIL(@"No context to draw to", nil);
PushDraw(^{
CGContextSetBlendMode(UIGraphicsGetCurrentContext(), kCGBlendModeMultiply);
DrawInnerShadow(path, WHITE_LEVEL(0, 0.4), CGSizeMake(0, 2), 1);
});
DrawShadow(path, WHITE_LEVEL(1, 0.5), CGSizeMake(0, 2), 1);
BevelPath(path, WHITE_LEVEL(0, 0.4), 2, 0);
PushDraw(^{
[path addClip];
CGContextSetAlpha(UIGraphicsGetCurrentContext(), 0.3);
UIColor *secondary = ScaleColorBrightness(primary, 0.3);
Gradient *gradient = [Gradient gradientFrom:primary to:secondary];
[gradient drawBottomToTop:path.bounds];
});
}
void DrawIndentedText(NSString *string, NSString *fontFace, UIColor *primary, CGRect rect)
{
UIBezierPath *letterPath = BezierPathFromStringWithFontFace(string, fontFace);
// RotatePath(letterPath, RadiansFromDegrees(-15));
FitPathToRect(letterPath, rect);
DrawIndentedPath(letterPath, primary, rect);
}
void DrawGradientOverTexture(UIBezierPath *path, UIImage *texture, Gradient *gradient, CGFloat alpha)
{
if (!path) COMPLAIN_AND_BAIL(@"Path cannot be nil", nil);
if (!texture) COMPLAIN_AND_BAIL(@"Texture cannot be nil", nil);
if (!gradient) COMPLAIN_AND_BAIL(@"Gradient cannot be nil", nil);
CGContextRef context = UIGraphicsGetCurrentContext();
if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil);
CGRect rect = path.bounds;
PushDraw(^{
CGContextSetAlpha(context, alpha);
[path addClip];
PushLayerDraw(^{
[texture drawInRect:rect];
CGContextSetBlendMode(context, kCGBlendModeColor);
[gradient drawTopToBottom:rect];
});
});
}
void DrawBottomGlow(UIBezierPath *path, UIColor *color, CGFloat percent)
{
if (!path) COMPLAIN_AND_BAIL(@"Path cannot be nil", nil);
if (!color) COMPLAIN_AND_BAIL(@"Color cannot be nil", nil);
CGContextRef context = UIGraphicsGetCurrentContext();
if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil);
CGRect rect = path.calculatedBounds;
CGPoint h1 = RectGetPointAtPercents(rect, 0.5f, 1.0f);
CGPoint h2 = RectGetPointAtPercents(rect, 0.5f, 1.0f - percent);
Gradient *gradient = [Gradient easeInOutGradientBetween:color and:[color colorWithAlphaComponent:0.0f]];
PushDraw(^{
[path addClip];
[gradient drawFrom:h1 toPoint:h2];
});
}
void DrawIconTopLight(UIBezierPath *path, CGFloat p)
{
if (!path) COMPLAIN_AND_BAIL(@"Path cannot be nil", nil);
CGContextRef context = UIGraphicsGetCurrentContext();
if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil);
CGFloat percent = 1.0f - p;
CGRect rect = path.bounds;
CGRect offset = rect;
offset.origin.y -= percent * offset.size.height;
offset = CGRectInset(offset, -offset.size.width * 0.3f, 0);
UIBezierPath *ovalPath = [UIBezierPath bezierPathWithOvalInRect:offset];
Gradient *gradient = [Gradient gradientFrom:WHITE_LEVEL(1, 0.0) to: WHITE_LEVEL(1, 0.5)];
PushDraw(^{
[path addClip];
[ovalPath addClip];
// Draw gradient
CGPoint p1 = RectGetPointAtPercents(rect, 0.5, 0.0);
CGPoint p2 = RectGetPointAtPercents(ovalPath.bounds, 0.5, 1);
[gradient drawFrom:p1 toPoint:p2];
});
}
CGSize GetQuartzContextSize()
{
CGContextRef context = UIGraphicsGetCurrentContext();
if (context == NULL) return CGSizeZero;
return CGSizeMake(CGBitmapContextGetWidth(context), CGBitmapContextGetHeight(context));
}
// Listing 7-4
CGSize GetUIKitContextSize()
{
CGContextRef context = UIGraphicsGetCurrentContext();
if (context == NULL) return CGSizeZero;
CGSize size = CGSizeMake(CGBitmapContextGetWidth(context), CGBitmapContextGetHeight(context));
CGFloat scale = [UIScreen mainScreen].scale;
return CGSizeMake(size.width / scale, size.height / scale);
}
void ApplyMaskToContext(UIImage *mask)
{
if (!mask) COMPLAIN_AND_BAIL(@"Mask cannot be nil", nil);
CGContextRef context = UIGraphicsGetCurrentContext();
if (context == NULL) COMPLAIN_AND_BAIL(@"No context to apply mask to", nil);
// Ensure that mask is grayscale
UIImage *gray = GrayscaleVersionOfImage(mask);
CGSize contextSize = GetUIKitContextSize();
// Clipping takes place in Quartz space, so flip before applying
FlipContextVertically(contextSize);
CGContextClipToMask(context, SizeMakeRect(contextSize), gray.CGImage);
FlipContextVertically(contextSize);
}
UIImage *ApplyMaskToImage(UIImage *image, UIImage *mask)
{
if (!image) COMPLAIN_AND_BAIL_NIL(@"Image cannot be nil", nil);
if (!mask) COMPLAIN_AND_BAIL_NIL(@"Mask cannot be nil", nil);
UIGraphicsBeginImageContextWithOptions(image.size, NO, 0.0);
ApplyMaskToContext(mask);
[image drawInRect:SizeMakeRect(image.size)];
UIImage *result = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return result;
}
UIImage *GradientImage(CGSize size, UIColor *c1, UIColor *c2)
{
UIGraphicsBeginImageContextWithOptions(size, NO, 0.0);
Gradient *gradient = [Gradient gradientFrom:c1 to:c2];
[gradient drawTopToBottom:SizeMakeRect(size)];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
UIImage *GradientMaskedReflectionImage(UIImage *sourceImage)
{
UIImage *mirror = ImageMirroredVertically(sourceImage);
UIImage *gradImage = GrayscaleVersionOfImage(GradientImage(sourceImage.size, WHITE_LEVEL(1, 0.5), WHITE_LEVEL(0, 0.5)));
UIImage *masked = ApplyMaskToImage(mirror, gradImage);
return masked;
}
// Listing 7-5
void DrawGradientMaskedReflection(UIImage *image, CGRect rect)
{
CGContextRef context = UIGraphicsGetCurrentContext();
if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil);
UIImage *gradient = GradientImage(rect.size, WHITE_LEVEL(1, 0.5), WHITE_LEVEL(1, 0.0));
PushDraw(^{
CGContextTranslateCTM(context, 0, rect.origin.y);
FlipContextVertically(rect.size);
CGContextTranslateCTM(context, 0, -rect.origin.y);
CGContextClipToMask(context, rect, gradient.CGImage);
[image drawInRect:rect];
});
}

View File

@@ -0,0 +1,70 @@
/*
Erica Sadun, http://ericasadun.com
*/
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
// Just because
#define TWO_PI (2 * M_PI)
// Undefined point
#define NULLPOINT CGRectNull.origin
#define POINT_IS_NULL(_POINT_) CGPointEqualToPoint(_POINT_, NULLPOINT)
// General
#define RECTSTRING(_aRect_) NSStringFromCGRect(_aRect_)
#define POINTSTRING(_aPoint_) NSStringFromCGPoint(_aPoint_)
#define SIZESTRING(_aSize_) NSStringFromCGSize(_aSize_)
#define RECT_WITH_SIZE(_SIZE_) (CGRect){.size = _SIZE_}
#define RECT_WITH_POINT(_POINT_) (CGRect){.origin = _POINT_}
// Conversion
CGFloat DegreesFromRadians(CGFloat radians);
CGFloat RadiansFromDegrees(CGFloat degrees);
// Clamping
CGFloat Clamp(CGFloat a, CGFloat min, CGFloat max);
CGPoint ClampToRect(CGPoint pt, CGRect rect);
// General Geometry
CGPoint RectGetCenter(CGRect rect);
CGFloat PointDistanceFromPoint(CGPoint p1, CGPoint p2);
// Construction
CGRect RectMakeRect(CGPoint origin, CGSize size);
CGRect SizeMakeRect(CGSize size);
CGRect PointsMakeRect(CGPoint p1, CGPoint p2);
CGRect OriginMakeRect(CGPoint origin);
CGRect RectAroundCenter(CGPoint center, CGSize size);
CGRect RectCenteredInRect(CGRect rect, CGRect mainRect);
// Point Locations
CGPoint RectGetPointAtPercents(CGRect rect, CGFloat xPercent, CGFloat yPercent);
CGPoint PointAddPoint(CGPoint p1, CGPoint p2);
CGPoint PointSubtractPoint(CGPoint p1, CGPoint p2);
// Cardinal Points
CGPoint RectGetTopLeft(CGRect rect);
CGPoint RectGetTopRight(CGRect rect);
CGPoint RectGetBottomLeft(CGRect rect);
CGPoint RectGetBottomRight(CGRect rect);
CGPoint RectGetMidTop(CGRect rect);
CGPoint RectGetMidBottom(CGRect rect);
CGPoint RectGetMidLeft(CGRect rect);
CGPoint RectGetMidRight(CGRect rect);
// Aspect and Fitting
CGSize SizeScaleByFactor(CGSize aSize, CGFloat factor);
CGSize RectGetScale(CGRect sourceRect, CGRect destRect);
CGFloat AspectScaleFill(CGSize sourceSize, CGRect destRect);
CGFloat AspectScaleFit(CGSize sourceSize, CGRect destRect);
CGRect RectByFittingRect(CGRect sourceRect, CGRect destinationRect);
CGRect RectByFillingRect(CGRect sourceRect, CGRect destinationRect);
CGRect RectInsetByPercent(CGRect rect, CGFloat percent);
// Transforms
CGFloat TransformGetXScale(CGAffineTransform t);
CGFloat TransformGetYScale(CGAffineTransform t);
CGFloat TransformGetRotation(CGAffineTransform t);

View File

@@ -0,0 +1,242 @@
/*
Erica Sadun, http://ericasadun.com
*/
#import "BaseGeometry.h"
#pragma mark - Conversion
// Degrees from radians
CGFloat DegreesFromRadians(CGFloat radians)
{
return radians * 180.0f / M_PI;
}
// Radians from degrees
CGFloat RadiansFromDegrees(CGFloat degrees)
{
return degrees * M_PI / 180.0f;
}
#pragma mark - Clamp
CGFloat Clamp(CGFloat a, CGFloat min, CGFloat max)
{
return fmin(fmax(min, a), max);
}
CGPoint ClampToRect(CGPoint pt, CGRect rect)
{
CGFloat x = Clamp(pt.x, CGRectGetMinX(rect), CGRectGetMaxX(rect));
CGFloat y = Clamp(pt.y, CGRectGetMinY(rect), CGRectGetMaxY(rect));
return CGPointMake(x, y);
}
#pragma mark - General Geometry
CGPoint RectGetCenter(CGRect rect)
{
return CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect));
}
CGFloat PointDistanceFromPoint(CGPoint p1, CGPoint p2)
{
CGFloat dx = p2.x - p1.x;
CGFloat dy = p2.y - p1.y;
return sqrt(dx*dx + dy*dy);
}
CGPoint RectGetPointAtPercents(CGRect rect, CGFloat xPercent, CGFloat yPercent)
{
CGFloat dx = xPercent * rect.size.width;
CGFloat dy = yPercent * rect.size.height;
return CGPointMake(rect.origin.x + dx, rect.origin.y + dy);
}
#pragma mark - Rectangle Construction
CGRect RectMakeRect(CGPoint origin, CGSize size)
{
return (CGRect){.origin = origin, .size = size};
}
CGRect SizeMakeRect(CGSize size)
{
return (CGRect){.size = size};
}
CGRect PointsMakeRect(CGPoint p1, CGPoint p2)
{
CGRect rect = CGRectMake(p1.x, p1.y, p2.x - p1.x, p2.y - p1.y);
return CGRectStandardize(rect);
}
CGRect OriginMakeRect(CGPoint origin)
{
return (CGRect){.origin = origin};
}
CGRect RectAroundCenter(CGPoint center, CGSize size)
{
CGFloat halfWidth = size.width / 2.0f;
CGFloat halfHeight = size.height / 2.0f;
return CGRectMake(center.x - halfWidth, center.y - halfHeight, size.width, size.height);
}
CGRect RectCenteredInRect(CGRect rect, CGRect mainRect)
{
CGFloat dx = CGRectGetMidX(mainRect)-CGRectGetMidX(rect);
CGFloat dy = CGRectGetMidY(mainRect)-CGRectGetMidY(rect);
return CGRectOffset(rect, dx, dy);
}
#pragma mark - Point Location
CGPoint PointAddPoint(CGPoint p1, CGPoint p2)
{
return CGPointMake(p1.x + p2.x, p1.y + p2.y);
}
CGPoint PointSubtractPoint(CGPoint p1, CGPoint p2)
{
return CGPointMake(p1.x - p2.x, p1.y - p2.y);
}
#pragma mark - Cardinal Points
CGPoint RectGetTopLeft(CGRect rect)
{
return CGPointMake(
CGRectGetMinX(rect),
CGRectGetMinY(rect)
);
}
CGPoint RectGetTopRight(CGRect rect)
{
return CGPointMake(
CGRectGetMaxX(rect),
CGRectGetMinY(rect)
);
}
CGPoint RectGetBottomLeft(CGRect rect)
{
return CGPointMake(
CGRectGetMinX(rect),
CGRectGetMaxY(rect)
);
}
CGPoint RectGetBottomRight(CGRect rect)
{
return CGPointMake(
CGRectGetMaxX(rect),
CGRectGetMaxY(rect)
);
}
CGPoint RectGetMidTop(CGRect rect)
{
return CGPointMake(
CGRectGetMidX(rect),
CGRectGetMinY(rect)
);
}
CGPoint RectGetMidBottom(CGRect rect)
{
return CGPointMake(
CGRectGetMidX(rect),
CGRectGetMaxY(rect)
);
}
CGPoint RectGetMidLeft(CGRect rect)
{
return CGPointMake(
CGRectGetMinX(rect),
CGRectGetMidY(rect)
);
}
CGPoint RectGetMidRight(CGRect rect)
{
return CGPointMake(
CGRectGetMaxX(rect),
CGRectGetMidY(rect)
);
}
#pragma mark - Aspect and Fitting
CGSize SizeScaleByFactor(CGSize aSize, CGFloat factor)
{
return CGSizeMake(aSize.width * factor, aSize.height * factor);
}
CGSize RectGetScale(CGRect sourceRect, CGRect destRect)
{
CGSize sourceSize = sourceRect.size;
CGSize destSize = destRect.size;
CGFloat scaleW = destSize.width / sourceSize.width;
CGFloat scaleH = destSize.height / sourceSize.height;
return CGSizeMake(scaleW, scaleH);
}
CGFloat AspectScaleFill(CGSize sourceSize, CGRect destRect)
{
CGSize destSize = destRect.size;
CGFloat scaleW = destSize.width / sourceSize.width;
CGFloat scaleH = destSize.height / sourceSize.height;
return fmax(scaleW, scaleH);
}
CGFloat AspectScaleFit(CGSize sourceSize, CGRect destRect)
{
CGSize destSize = destRect.size;
CGFloat scaleW = destSize.width / sourceSize.width;
CGFloat scaleH = destSize.height / sourceSize.height;
return fmin(scaleW, scaleH);
}
CGRect RectByFittingRect(CGRect sourceRect, CGRect destinationRect)
{
CGFloat aspect = AspectScaleFit(sourceRect.size, destinationRect);
CGSize targetSize = SizeScaleByFactor(sourceRect.size, aspect);
return RectAroundCenter(RectGetCenter(destinationRect), targetSize);
}
CGRect RectByFillingRect(CGRect sourceRect, CGRect destinationRect)
{
CGFloat aspect = AspectScaleFill(sourceRect.size, destinationRect);
CGSize targetSize = SizeScaleByFactor(sourceRect.size, aspect);
return RectAroundCenter(RectGetCenter(destinationRect), targetSize);
}
CGRect RectInsetByPercent(CGRect rect, CGFloat percent)
{
CGFloat wInset = rect.size.width * (percent / 2.0f);
CGFloat hInset = rect.size.height * (percent / 2.0f);
return CGRectInset(rect, wInset, hInset);
}
#pragma mark - Transforms
// Extract the x scale from transform
CGFloat TransformGetXScale(CGAffineTransform t)
{
return sqrt(t.a * t.a + t.c * t.c);
}
// Extract the y scale from transform
CGFloat TransformGetYScale(CGAffineTransform t)
{
return sqrt(t.b * t.b + t.d * t.d);
}
// Extract the rotation in radians
CGFloat TransformGetRotation(CGAffineTransform t)
{
return atan2f(t.b, t.a);
}

View File

@@ -0,0 +1,40 @@
/*
Erica Sadun, http://ericasadun.com
Gathered for book examples
*/
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
// 4 bytes per ARGB pixel, 8 bits per byte
#define ARGB_COUNT 4
#define BITS_PER_COMPONENT 8
UIEdgeInsets BuildInsets(CGRect alignmentRect, CGRect imageBounds);
UIImage *BuildSwatchWithColor(UIColor *color, CGFloat side);
UIImage *BuildThumbnail(UIImage *sourceImage, CGSize targetSize, BOOL useFitting);
UIImage *ExtractRectFromImage(UIImage *sourceImage, CGRect subRect);
UIImage *ExtractSubimageFromRect(UIImage *sourceImage, CGRect rect);
UIImage *GrayscaleVersionOfImage(UIImage *sourceImage);
UIImage *InvertImage(UIImage *sourceImage);
NSData *BytesFromRGBImage(UIImage *sourceImage);
UIImage *ImageFromRGBBytes(NSData *data, CGSize targetSize);
void FlipContextVertically(CGSize size);
void FlipContextHorizontally(CGSize size);
void FlipImageContextVertically();
void FlipImageContextHorizontally();
void RotateContext(CGSize size, CGFloat theta);
void MoveContextByVector(CGPoint vector);
UIImage *ImageMirroredVertically(UIImage *image);
void DrawPDFPageInRect(CGPDFPageRef pageRef, CGRect destinationRect);
UIImage *GaussianBlurImage(UIImage *image, CGFloat radius);

View File

@@ -0,0 +1,372 @@
/*
Erica Sadun, http://ericasadun.com
*/
#import "ImageUtils.h"
#import "Utility.h"
#import "BaseGeometry.h"
// Chapter 3-8
// Establish insets for image alignment
UIEdgeInsets BuildInsets(CGRect alignmentRect, CGRect imageBounds)
{
// Ensure alignment rect is fully within source
CGRect targetRect = CGRectIntersection(alignmentRect, imageBounds);
// Calculate insets
UIEdgeInsets insets;
insets.left = CGRectGetMinX(targetRect) - CGRectGetMinX(imageBounds);
insets.right = CGRectGetMaxX(imageBounds) - CGRectGetMaxX(targetRect);
insets.top = CGRectGetMinY(targetRect) - CGRectGetMinY(imageBounds);
insets.bottom = CGRectGetMaxY(imageBounds) - CGRectGetMaxY(targetRect);
return insets;
}
// Chapter 3 - 1
UIImage *BuildSwatchWithColor(UIColor *color, CGFloat side)
{
// Create image context
UIGraphicsBeginImageContextWithOptions(
CGSizeMake(side, side), YES,
0.0);
// Perform drawing
[color setFill];
UIRectFill(CGRectMake(0, 0, side, side));
// Retrieve image
UIImage *image =
UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
// Chapter 3 - 2
UIImage *BuildThumbnail(UIImage *sourceImage, CGSize targetSize, BOOL useFitting)
{
CGRect targetRect = SizeMakeRect(targetSize);
UIGraphicsBeginImageContextWithOptions(targetSize, NO, 0.0);
CGRect naturalRect = (CGRect){.size = sourceImage.size};
CGRect destinationRect = useFitting ? RectByFittingRect(naturalRect, targetRect) : RectByFillingRect(naturalRect, targetRect);
[sourceImage drawInRect:destinationRect];
UIImage *thumbnail = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return thumbnail;
}
// Chapter 3 - 3
UIImage *ExtractRectFromImage(UIImage *sourceImage, CGRect subRect)
{
// Extract image
CGImageRef imageRef = CGImageCreateWithImageInRect(sourceImage.CGImage, subRect);
if (imageRef != NULL)
{
UIImage *output = [UIImage imageWithCGImage:imageRef];
CGImageRelease(imageRef);
return output;
}
NSLog(@"Error: Unable to extract subimage");
return nil;
}
// This is a little less flaky when moving to and from Retina images
UIImage *ExtractSubimageFromRect(UIImage *sourceImage, CGRect rect)
{
UIGraphicsBeginImageContextWithOptions(rect.size, NO, 1);
CGRect destRect = CGRectMake(-rect.origin.x, -rect.origin.y,
sourceImage.size.width, sourceImage.size.height);
[sourceImage drawInRect:destRect];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
// Chapter 3 - 4
UIImage *GrayscaleVersionOfImage(UIImage *sourceImage)
{
// Establish grayscale color space
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray();
if (colorSpace == NULL)
{
NSLog(@"Error: Could not establish grayscale color space");
return nil;
}
// Extents are integers
int width = sourceImage.size.width;
int height = sourceImage.size.height;
// Build context: one byte per pixel, no alpha
CGContextRef context = CGBitmapContextCreate(NULL, width, height, BITS_PER_COMPONENT, width, colorSpace, (CGBitmapInfo)kCGImageAlphaNone);
CGColorSpaceRelease(colorSpace);
if (context == NULL)
{
NSLog(@"Error: Could not build grayscale bitmap context");
return nil;
}
// Replicate image using new color space
CGRect rect = SizeMakeRect(sourceImage.size);
CGContextDrawImage(context, rect, sourceImage.CGImage);
CGImageRef imageRef = CGBitmapContextCreateImage(context);
CGContextRelease(context);
// Return the grayscale image
UIImage *output = [UIImage imageWithCGImage:imageRef];
CFRelease(imageRef);
return output;
}
// Just for fun. Return image with colors flipped
UIImage *InvertImage(UIImage *sourceImage)
{
UIGraphicsBeginImageContextWithOptions(sourceImage.size, NO, 0.0);
CGContextRef context = UIGraphicsGetCurrentContext();
[sourceImage drawInRect:SizeMakeRect(sourceImage.size)];
CGContextSetBlendMode(context, kCGBlendModeDifference);
CGContextSetFillColorWithColor(context,[UIColor whiteColor].CGColor);
CGContextFillRect(context, SizeMakeRect(sourceImage.size));
UIImage *result = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return result;
}
// Chapter 3-6
// Extract bytes
NSData *BytesFromRGBImage(UIImage *sourceImage)
{
if (!sourceImage) return nil;
// Establish color space
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
if (colorSpace == NULL)
{
NSLog(@"Error creating RGB color space");
return nil;
}
// Establish context
int width = sourceImage.size.width;
int height = sourceImage.size.height;
CGContextRef context = CGBitmapContextCreate(NULL, width, height, BITS_PER_COMPONENT, width * ARGB_COUNT, colorSpace, (CGBitmapInfo)kCGImageAlphaPremultipliedFirst);
CGColorSpaceRelease(colorSpace );
if (context == NULL)
{
NSLog(@"Error creating context");
return nil;
}
// Draw source into context bytes
CGRect rect = (CGRect){.size = sourceImage.size};
CGContextDrawImage(context, rect, sourceImage.CGImage);
// Create NSData from bytes
NSData *data = [NSData dataWithBytes:CGBitmapContextGetData(context) length:(width * height * 4)];
CGContextRelease(context);
return data;
}
// Chapter 3-7
// Create image from bytes
UIImage *ImageFromRGBBytes(NSData *data, CGSize targetSize)
{
// Check data
int width = targetSize.width;
int height = targetSize.height;
if (data.length < (width * height * 4))
{
NSLog(@"Error: Not enough RGB data provided. Got %d bytes. Expected %d bytes", data.length, width * height * 4);
return nil;
}
// Create a color space
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
if (colorSpace == NULL)
{
NSLog(@"Error creating RGB colorspace");
return nil;
}
// Create the bitmap context
Byte *bytes = (Byte *) data.bytes;
CGContextRef context = CGBitmapContextCreate(bytes, width, height, BITS_PER_COMPONENT, width * ARGB_COUNT, colorSpace, (CGBitmapInfo)kCGImageAlphaPremultipliedFirst);
CGColorSpaceRelease(colorSpace );
if (context == NULL)
{
NSLog(@"Error. Could not create context");
return nil;
}
// Convert to image
CGImageRef imageRef = CGBitmapContextCreateImage(context);
UIImage *image = [UIImage imageWithCGImage:imageRef];
// Clean up
CGContextRelease(context);
CFRelease(imageRef);
return image;
}
#pragma mark - Context
// From Chapter 1
void FlipContextVertically(CGSize size)
{
CGContextRef context = UIGraphicsGetCurrentContext();
if (context == NULL)
{
NSLog(@"Error: No context to flip");
return;
}
CGAffineTransform transform = CGAffineTransformIdentity;
transform = CGAffineTransformScale(transform, 1.0f, -1.0f);
transform = CGAffineTransformTranslate(transform, 0.0f, -size.height);
CGContextConcatCTM(context, transform);
}
void FlipImageContextVertically()
{
CGContextRef context = UIGraphicsGetCurrentContext();
if (context == NULL)
{
NSLog(@"Error: No context to flip");
return;
}
// I don't like this approach
// CGFloat scale = [UIScreen mainScreen].scale;
// CGSize size = CGSizeMake(CGBitmapContextGetWidth(context) / scale, CGBitmapContextGetHeight(context) / scale);
// FlipContextVertically(size);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
FlipContextVertically(image.size);
}
void FlipContextHorizontally(CGSize size)
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGAffineTransform transform = CGAffineTransformIdentity;
transform = CGAffineTransformScale(transform, -1.0f, 1.0f);
transform = CGAffineTransformTranslate(transform, -size.width, 0.0);
CGContextConcatCTM(context, transform);
}
void FlipImageContextHorizontally()
{
CGContextRef context = UIGraphicsGetCurrentContext();
if (context == NULL)
{
NSLog(@"Error: No context to flip");
return;
}
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
FlipContextHorizontally(image.size);
}
void RotateContext(CGSize size, CGFloat theta)
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(context, size.width / 2.0f, size.height / 2.0f);
CGContextRotateCTM(context, theta);
CGContextTranslateCTM(context, -size.width / 2.0f, -size.height / 2.0f);
}
void MoveContextByVector(CGPoint vector)
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(context, vector.x, vector.y);
}
UIImage *ImageMirroredVertically(UIImage *source)
{
UIGraphicsBeginImageContextWithOptions(source.size, NO, 0.0);
FlipContextVertically(source.size);
[source drawInRect:SizeMakeRect(source.size)];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
#pragma mark - PDF Util
// Chapter 3-12
void DrawPDFPageInRect(CGPDFPageRef pageRef, CGRect destinationRect)
{
CGContextRef context = UIGraphicsGetCurrentContext();
if (context == NULL)
{
NSLog(@"Error: No context to draw to");
return;
}
CGContextSaveGState(context);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
// Flip the context to Quartz space
CGAffineTransform transform = CGAffineTransformIdentity;
transform = CGAffineTransformScale(transform, 1.0f, -1.0f);
transform = CGAffineTransformTranslate(transform, 0.0f, -image.size.height);
CGContextConcatCTM(context, transform);
// Flip the rect, which remains in UIKit space
CGRect d = CGRectApplyAffineTransform(destinationRect, transform);
// Calculate a rectangle to draw to
CGRect pageRect = CGPDFPageGetBoxRect(pageRef, kCGPDFCropBox);
CGFloat drawingAspect = AspectScaleFit(pageRect.size, d);
CGRect drawingRect = RectByFittingRect(pageRect, d);
// Draw the page outline (optional)
UIRectFrame(drawingRect);
// Adjust the context
CGContextTranslateCTM(context, drawingRect.origin.x, drawingRect.origin.y);
CGContextScaleCTM(context, drawingAspect, drawingAspect);
// Draw the page
CGContextDrawPDFPage(context, pageRef);
CGContextRestoreGState(context);
}
#pragma mark - Masking, Blurring
// Listing 7-3
UIImage *GaussianBlurImage(UIImage *image, CGFloat radius)
{
if (!image) COMPLAIN_AND_BAIL_NIL(@"Mask cannot be nil", nil);
CIFilter *blurFilter = [CIFilter filterWithName:@"CIGaussianBlur"];
[blurFilter setValue: [CIImage imageWithCGImage:image.CGImage]
forKey: @"inputImage"];
[blurFilter setValue:@(radius) forKey:@"inputRadius"];
CIFilter *crop = [CIFilter filterWithName: @"CICrop"];
[crop setDefaults];
[crop setValue:blurFilter.outputImage forKey:@"inputImage"];
CGFloat scale = [[UIScreen mainScreen] scale];
CGFloat w = image.size.width * scale;
CGFloat h = image.size.height * scale;
CIVector *v = [CIVector vectorWithX:0 Y:0 Z:w W:h];
[crop setValue:v forKey:@"inputRectangle"];
CGImageRef cgImageRef = [[CIContext contextWithOptions:nil] createCGImage:crop.outputImage fromRect:crop.outputImage.extent];
UIGraphicsBeginImageContextWithOptions(image.size, NO, 0.0);
FlipContextVertically(image.size);
CGContextDrawImage(UIGraphicsGetCurrentContext(), SizeMakeRect(image.size), cgImageRef);
UIImage *blurred = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return blurred;
}

View File

@@ -0,0 +1,23 @@
/*
Erica Sadun, http://ericasadun.com
*/
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
@import Foundation;
@import CoreText;
#import "Drawing-Text.h"
// Sizing
NSArray *WidthArrayForStringWithFont(NSString *string, UIFont *font);
// Drawing
void DrawStringInRect(NSString *string, CGRect rect, UIFont *font, NSTextAlignment alignment, UIColor *color);
void DrawWrappedStringInRect(NSString *string, CGRect rect, NSString *fontFace, NSTextAlignment alignment, UIColor *color);
void DrawUnwrappedStringInRect(NSString *string, CGRect rect, NSString *fontFace, NSTextAlignment alignment, UIColor *color);
void DrawStringCenteredInRect(NSString *string, UIFont *font, CGRect rect);
UIFont *FontForWrappedString(NSString *string, NSString *fontFace, CGRect rect, CGFloat tolerance);

View File

@@ -0,0 +1,175 @@
/*
Erica Sadun, http://ericasadun.com
*/
#import "Drawing-Text.h"
#import "Utility.h"
#pragma mark - Drawing
void DrawStringInRect(NSString *string, CGRect rect, UIFont *font, NSTextAlignment alignment, UIColor *color)
{
CGContextRef context = UIGraphicsGetCurrentContext();
if (context == NULL) return;
NSRange range = NSMakeRange(0, string.length);
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:string];
NSMutableDictionary *attributes = [NSMutableDictionary dictionary];
NSMutableParagraphStyle *style = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
style.alignment = alignment;
style.lineBreakMode = NSLineBreakByWordWrapping;
attributes[NSFontAttributeName] = font;
attributes[NSForegroundColorAttributeName] = color;
attributes[NSParagraphStyleAttributeName] = style;
[attributedString addAttributes:attributes range:range];
CGRect destRect = [string boundingRectWithSize:CGSizeMake(rect.size.width, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:attributes context:nil];
CGRect outputRect = RectCenteredInRect(destRect, rect);
[attributedString drawInRect:outputRect];
}
#pragma mark - Unwrapped
UIFont *FontForUnwrappedString(NSString *string, NSString *fontFace, CGRect rect)
{
CGFloat fontSize = 1;
UIFont *font = [UIFont fontWithName:fontFace size:fontSize];
CGSize destSize = [string sizeWithAttributes:@{NSFontAttributeName:font}];
while ((destSize.width < rect.size.width) && (destSize.height < rect.size.height))
{
fontSize++;
UIFont *proposedFont = [UIFont fontWithName:fontFace size:fontSize];
destSize = [string sizeWithAttributes:@{NSFontAttributeName:proposedFont}];
if ((destSize.height > rect.size.height) || (destSize.width > rect.size.width))
return font;
font = proposedFont;
}
return font;
}
void DrawUnwrappedStringInRect(NSString *string, CGRect rect, NSString *fontFace, NSTextAlignment alignment, UIColor *color)
{
UIFont *font = FontForUnwrappedString(string, fontFace, rect);
DrawStringInRect(string, rect, font, alignment, color);
}
void DrawStringCenteredInRect(NSString *string, UIFont *font, CGRect rect)
{
CGContextRef context = UIGraphicsGetCurrentContext();
if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil);
// Calculate string size
CGSize stringSize = [string sizeWithAttributes:@{NSFontAttributeName:font}];
// Find the target rectangle
CGRect target = RectAroundCenter(RectGetCenter(rect), stringSize);
// Draw the string
[string drawInRect:target withAttributes:@{NSFontAttributeName:font}];
}
#pragma mark - Wrapping
UIFont *FontForWrappedString(NSString *string, NSString *fontFace, CGRect rect, CGFloat tolerance)
{
if (rect.size.height < 1.0f) return nil;
CGFloat adjustedWidth = tolerance * rect.size.width;
CGSize measureSize = CGSizeMake(adjustedWidth, CGFLOAT_MAX);
// Initialize the proposed font
CGFloat fontSize = 1;
UIFont *proposedFont = [UIFont fontWithName:fontFace size:fontSize];
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
paragraphStyle.lineBreakMode = NSLineBreakByWordWrapping;
NSMutableDictionary *attributes = [NSMutableDictionary dictionary];
attributes[NSParagraphStyleAttributeName] = paragraphStyle;
attributes[NSFontAttributeName] = proposedFont;
// Measure the target
CGSize targetSize = [string boundingRectWithSize:measureSize options:NSStringDrawingUsesLineFragmentOrigin attributes:attributes context:nil].size;
// Double until the size is exceeded
while (targetSize.height <= rect.size.height)
{
// Establish a new proposed font
fontSize *= 2;
proposedFont = [UIFont fontWithName:fontFace size:fontSize];
// Measure the target
attributes[NSFontAttributeName] = proposedFont;
targetSize = [string boundingRectWithSize:measureSize options:NSStringDrawingUsesLineFragmentOrigin attributes:attributes context:nil].size;
// Break when the calculated height is too much
if (targetSize.height > rect.size.height)
break;
}
// Search between the previous and current font sizes
CGFloat minFontSize = fontSize / 2;
CGFloat maxFontSize = fontSize;
while (1)
{
// Get the midpoint between the two
CGFloat midPoint = (minFontSize + (maxFontSize - minFontSize) / 2);
proposedFont = [UIFont fontWithName:fontFace size:midPoint];
attributes[NSFontAttributeName] = proposedFont;
targetSize = [string boundingRectWithSize:measureSize options:NSStringDrawingUsesLineFragmentOrigin attributes:attributes context:nil].size;
// Look up one font size
UIFont *nextFont = [UIFont fontWithName:fontFace size:midPoint + 1];
attributes[NSFontAttributeName] = nextFont;
CGSize nextTargetSize = [string boundingRectWithSize:measureSize options:NSStringDrawingUsesLineFragmentOrigin attributes:attributes context:nil].size;;
// Test both fonts
CGFloat tooBig = targetSize.height > rect.size.height;
CGFloat nextIsTooBig = nextTargetSize.height > rect.size.height;
// If the current is sized right but the next is too big, win
if (!tooBig && nextIsTooBig)
return [UIFont fontWithName:fontFace size:midPoint];
// Adjust the search space
if (tooBig)
maxFontSize = midPoint;
else
minFontSize = midPoint;
}
// Should never get here
return [UIFont fontWithName:fontFace size:fontSize / 2];
}
CGSize AttributedStringSize(NSAttributedString *string, CGSize constrainedSize)
{
return [string boundingRectWithSize:constrainedSize options:NSStringDrawingUsesLineFragmentOrigin context:nil].size;
}
void DrawWrappedStringInRect(NSString *string, CGRect rect, NSString *fontFace, NSTextAlignment alignment, UIColor *color)
{
UIFont *font = FontForWrappedString(string, fontFace, rect, 0.95);
NSMutableAttributedString *s = [[NSMutableAttributedString alloc] initWithString:string];
NSRange r = NSMakeRange(0, string.length);
[s addAttribute:NSFontAttributeName value:font range:r];
[s addAttribute:NSForegroundColorAttributeName value:color range:r];
NSMutableParagraphStyle *p = [NSParagraphStyle defaultParagraphStyle].mutableCopy;
p.hyphenationFactor = 0.25f;
p.alignment = alignment;
[s addAttribute:NSParagraphStyleAttributeName value:p range:r];
CGRect stringBounds = SizeMakeRect(AttributedStringSize(s, rect.size));
stringBounds.size.width = fminf(rect.size.width, stringBounds.size.width);
CGRect dest = RectCenteredInRect(stringBounds, rect);
dest.size.height = CGFLOAT_MAX;
[s drawInRect:dest];
}

View File

@@ -0,0 +1,13 @@
/*
Erica Sadun, http://ericasadun.com
iPhone Developer's Cookbook, 6.x Edition
BSD License, Use at your own risk
*/
#import <Foundation/Foundation.h>
#import <CoreText/CoreText.h>
#import "UIBezierPath+Elements.h"
@interface UIBezierPath (TextUtilities)
- (void) drawAttributedString: (NSAttributedString *) string;
@end

View File

@@ -0,0 +1,56 @@
/*
Erica Sadun, http://ericasadun.com
iPhone Developer's Cookbook, 6.x Edition
BSD License, Use at your own risk
*/
#import "UIBezierPath+Text.h"
#import "Utility.h"
@implementation UIBezierPath (TextUtilities)
- (void) drawAttributedString: (NSAttributedString *) string
{
if (!string) return;
CGContextRef context = UIGraphicsGetCurrentContext();
if (context == NULL) COMPLAIN_AND_BAIL(@"No context to draw into", nil);
if (self.elements.count < 2) return;
// Keep a running tab of how far the glyphs have traveled to
// be able to calculate the percent along the point path
float glyphDistance = 0.0f;
float lineLength = self.pathLength;
for (int loc = 0; loc < string.length; loc++)
{
// Retrieve item
NSRange range = NSMakeRange(loc, 1);
NSAttributedString *item = [string attributedSubstringFromRange:range];
// Start halfway through each glyph
CGRect bounding = [item boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX) options:0 context:nil];
glyphDistance += bounding.size.width / 2;
// Find point
CGPoint slope;
CGFloat percentConsumed = glyphDistance / lineLength;
CGPoint targetPoint = [self pointAtPercent:percentConsumed withSlope:&slope];
// Accommodate the forward progress
glyphDistance += bounding.size.width / 2;
if (percentConsumed >= 1.0f) break;
// Calculate the rotation
float angle = atan(slope.y / slope.x); // + M_PI;
if (slope.x < 0) angle += M_PI; // going left, update the angle
// Draw the glyph
PushDraw(^{
CGContextTranslateCTM(context, targetPoint.x, targetPoint.y);
CGContextRotateCTM(context, angle);
CGContextTranslateCTM(context, -bounding.size.width / 2, -item.size.height / 2);
[item drawAtPoint:CGPointZero];
});
}
}
@end

View File

@@ -0,0 +1,48 @@
/*
Erica Sadun, http://ericasadun.com
*/
#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>
#import "BaseGeometry.h"
#import "Drawing-Block.h"
#import "Drawing-Util.h"
#import "Drawing-Gradient.h"
#import "Bezier.h"
#import "ImageUtils.h"
#import "UIBezierPath+Text.h"
#import "Drawing-Text.h"
#define BARBUTTON(TITLE, SELECTOR) [[UIBarButtonItem alloc] initWithTitle:TITLE style:UIBarButtonItemStylePlain target:self action:SELECTOR]
#define RGBCOLOR(_R_, _G_, _B_) [UIColor colorWithRed:(CGFloat)(_R_)/255.0f green: (CGFloat)(_G_)/255.0f blue: (CGFloat)(_B_)/255.0f alpha: 1.0f]
#define OLIVE RGBCOLOR(125, 162, 63)
#define LIGHTPURPLE RGBCOLOR(99, 62, 162)
#define DARKGREEN RGBCOLOR(40, 55, 32)
// Bail with complaint
#define COMPLAIN_AND_BAIL(_COMPLAINT_, _ARG_) {NSLog(_COMPLAINT_, _ARG_); return;}
#define COMPLAIN_AND_BAIL_NIL(_COMPLAINT_, _ARG_) {NSLog(_COMPLAINT_, _ARG_); return nil;}
#define SEED_RANDOM {static BOOL seeded = NO; if (!seeded) {seeded = YES; srandom((unsigned int) time(0));}}
#define RANDOM(_X_) (NSInteger)(random() % _X_)
#define RANDOM_01 ((double) random() / (double) LONG_MAX)
#define RANDOM_BOOL (BOOL)((NSInteger)random() % 2)
#define RANDOM_PT(_RECT_) CGPointMake(_RECT_.origin.x + RANDOM_01 * _RECT_.size.width, _RECT_.origin.y + RANDOM_01 * _RECT_.size.height)
UIBezierPath *BuildBunnyPath();
UIBezierPath *BuildMoofPath();
UIBezierPath *BuildStarPath();
UIColor *NoiseColor();
@interface NSString (Utility)
+ (NSString *) lorem: (NSUInteger) numberOfParagraphs;
+ (NSString *) loremWords: (NSUInteger) numberOfWords;
@end
#define DEBUG_IMAGE(_IMAGE_, _NAME_) [UIImagePNGRepresentation(_IMAGE_) writeToFile:[NSString stringWithFormat:@"/Users/ericasadun/Desktop/%@.png", _NAME_] atomically:YES]

View File

@@ -0,0 +1,240 @@
#import "Utility.h"
typedef BOOL (^TestingBlock)(id object);
@interface NSArray (Utility)
@end
@implementation NSArray (Utility)
- (NSArray *) collect: (TestingBlock) aBlock
{
NSMutableArray *resultArray = [NSMutableArray array];
for (id object in self)
{
BOOL result = aBlock(object);
if (result)
[resultArray addObject:object];
}
return resultArray;
}
@end
@implementation NSString (Utility)
+ (NSString *) ipsum:(NSUInteger) numberOfParagraphs
{
NSString *urlString = [NSString stringWithFormat:@"http://loripsum.net/api/%0d/short/prude/plaintext", numberOfParagraphs];
NSError *error;
NSString *string = [NSString stringWithContentsOfURL:[NSURL URLWithString:urlString] encoding:NSUTF8StringEncoding error:&error];
if (!string)
{
NSLog(@"Error: %@", error.localizedDescription);
return nil;
}
return string;
}
+ (NSString *) lorem:(NSUInteger) numberOfParagraphs
{
return [self ipsum:numberOfParagraphs];
}
- (NSArray *) words
{
NSArray *words = [self componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
TestingBlock block = ^BOOL(id word){return [(NSString *)word length] > 0;};
return [words collect:block];
}
- (NSString *) wordRange: (NSRange) range
{
NSArray *componentWords = self.words;
NSInteger start = range.location;
NSInteger end = range.location + range.length;
if ((start >= componentWords.count) || (end >= componentWords.count))
return nil;
NSArray *subArray = [componentWords subarrayWithRange:range];
return [subArray componentsJoinedByString:@" "];
}
+ (NSString *) loremWords: (NSUInteger) numberOfWords
{
// meant for up to 10 words
NSUInteger nWords = MIN(numberOfWords, 10);
NSRange r = NSMakeRange(0, nWords);
return [[NSString lorem:3] wordRange:r];
}
@end
UIBezierPath *BuildBunnyPath()
{
UIBezierPath *shapePath = [UIBezierPath bezierPath];
[shapePath moveToPoint: CGPointMake(392.05, 150.53)];
[shapePath addCurveToPoint: CGPointMake(343.11, 129.56) controlPoint1: CGPointMake(379.34, 133.37) controlPoint2: CGPointMake(359, 133.37)];
[shapePath addCurveToPoint: CGPointMake(305.61, 71.72) controlPoint1: CGPointMake(341.2, 119.39) controlPoint2: CGPointMake(316.41, 71.08)];
[shapePath addCurveToPoint: CGPointMake(304.34, 100.96) controlPoint1: CGPointMake(294.8, 72.35) controlPoint2: CGPointMake(302.43, 79.35)];
[shapePath addCurveToPoint: CGPointMake(283.36, 84.43) controlPoint1: CGPointMake(299.25, 95.24) controlPoint2: CGPointMake(287.81, 73.63)];
[shapePath addCurveToPoint: CGPointMake(301.79, 154.99) controlPoint1: CGPointMake(272.42, 111.01) controlPoint2: CGPointMake(302.43, 148.63)];
[shapePath addCurveToPoint: CGPointMake(287.17, 191.85) controlPoint1: CGPointMake(301.16, 161.34) controlPoint2: CGPointMake(299, 186.78)];
[shapePath addCurveToPoint: CGPointMake(194.38, 221.09) controlPoint1: CGPointMake(282.72, 193.76) controlPoint2: CGPointMake(240.78, 195.66)];
[shapePath addCurveToPoint: CGPointMake(128.27, 320.25) controlPoint1: CGPointMake(147.97, 246.51) controlPoint2: CGPointMake(138.44, 282.11)];
[shapePath addCurveToPoint: CGPointMake(124.75, 348.92) controlPoint1: CGPointMake(125.53, 330.51) controlPoint2: CGPointMake(124.54, 340.12)];
[shapePath addCurveToPoint: CGPointMake(118.34, 358.13) controlPoint1: CGPointMake(122.92, 350.31) controlPoint2: CGPointMake(120.74, 353.02)];
[shapePath addCurveToPoint: CGPointMake(136.06, 388.68) controlPoint1: CGPointMake(112.42, 370.73) controlPoint2: CGPointMake(123.06, 383.91)];
[shapePath addCurveToPoint: CGPointMake(144.8, 399.06) controlPoint1: CGPointMake(138.8, 392.96) controlPoint2: CGPointMake(141.79, 396.49)];
[shapePath addCurveToPoint: CGPointMake(210.9, 408.6) controlPoint1: CGPointMake(158.14, 410.5) controlPoint2: CGPointMake(205.18, 406.69)];
[shapePath addCurveToPoint: CGPointMake(240.78, 417.5) controlPoint1: CGPointMake(216.62, 410.5) controlPoint2: CGPointMake(234.41, 417.5)];
[shapePath addCurveToPoint: CGPointMake(267.47, 411.78) controlPoint1: CGPointMake(247.13, 417.5) controlPoint2: CGPointMake(267.47, 419.4)];
[shapePath addCurveToPoint: CGPointMake(250.3, 385.71) controlPoint1: CGPointMake(267.47, 394.61) controlPoint2: CGPointMake(250.3, 385.71)];
[shapePath addCurveToPoint: CGPointMake(274.46, 371.73) controlPoint1: CGPointMake(250.3, 385.71) controlPoint2: CGPointMake(260.48, 379.99)];
[shapePath addCurveToPoint: CGPointMake(302.43, 350.12) controlPoint1: CGPointMake(288.45, 363.47) controlPoint2: CGPointMake(297.34, 350.76)];
[shapePath addCurveToPoint: CGPointMake(318.32, 389.53) controlPoint1: CGPointMake(312.22, 348.89) controlPoint2: CGPointMake(311.33, 381.9)];
[shapePath addCurveToPoint: CGPointMake(341.84, 421.94) controlPoint1: CGPointMake(325.32, 397.15) controlPoint2: CGPointMake(332.15, 418.51)];
[shapePath addCurveToPoint: CGPointMake(375.53, 413.68) controlPoint1: CGPointMake(349.08, 424.51) controlPoint2: CGPointMake(373.62, 421.31)];
[shapePath addCurveToPoint: CGPointMake(367.26, 398.43) controlPoint1: CGPointMake(377.43, 406.06) controlPoint2: CGPointMake(372.35, 405.42)];
[shapePath addCurveToPoint: CGPointMake(361.54, 352.66) controlPoint1: CGPointMake(362.18, 391.43) controlPoint2: CGPointMake(356.46, 363.47)];
[shapePath addCurveToPoint: CGPointMake(378.07, 277.66) controlPoint1: CGPointMake(366.63, 341.86) controlPoint2: CGPointMake(376.8, 296.73)];
[shapePath addCurveToPoint: CGPointMake(388.87, 220.45) controlPoint1: CGPointMake(379.34, 258.59) controlPoint2: CGPointMake(378.7, 245.88)];
[shapePath addCurveToPoint: CGPointMake(411.12, 189.31) controlPoint1: CGPointMake(405.4, 214.1) controlPoint2: CGPointMake(410.48, 197.57)];
[shapePath addCurveToPoint: CGPointMake(392.05, 150.53) controlPoint1: CGPointMake(411.76, 181.04) controlPoint2: CGPointMake(404.76, 167.7)];
[shapePath closePath];
return shapePath;
}
// http://pastie.org/private/7i0lgpfqtlkihbjyn0ba
// http://imgur.com/Po3uoIy
UIBezierPath *BuildMoofPath()
{
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint: CGPointMake(107, 66)];
[path addCurveToPoint: CGPointMake(119, 69) controlPoint1: CGPointMake(113.17, 65.65) controlPoint2: CGPointMake(116.61, 65.38)];
[path addCurveToPoint: CGPointMake(118, 100) controlPoint1: CGPointMake(118.67, 79.33) controlPoint2: CGPointMake(118.33, 89.67)];
[path addCurveToPoint: CGPointMake(140, 114) controlPoint1: CGPointMake(125.33, 104.67) controlPoint2: CGPointMake(132.67, 109.33)];
[path addCurveToPoint: CGPointMake(240, 105) controlPoint1: CGPointMake(169.22, 124.94) controlPoint2: CGPointMake(219.91, 117.23)];
[path addCurveToPoint: CGPointMake(261, 76) controlPoint1: CGPointMake(252.29, 97.52) controlPoint2: CGPointMake(251.99, 86.39)];
[path addCurveToPoint: CGPointMake(267, 78) controlPoint1: CGPointMake(263, 76.67) controlPoint2: CGPointMake(265, 77.33)];
[path addCurveToPoint: CGPointMake(255, 145) controlPoint1: CGPointMake(276.45, 112.7) controlPoint2: CGPointMake(260.16, 119.11)];
[path addCurveToPoint: CGPointMake(262, 188) controlPoint1: CGPointMake(257.33, 159.33) controlPoint2: CGPointMake(259.67, 173.67)];
[path addCurveToPoint: CGPointMake(208, 232) controlPoint1: CGPointMake(268.07, 224.31) controlPoint2: CGPointMake(240.23, 239.3)];
[path addCurveToPoint: CGPointMake(206, 225) controlPoint1: CGPointMake(207.33, 229.67) controlPoint2: CGPointMake(206.67, 227.33)];
[path addCurveToPoint: CGPointMake(220, 192) controlPoint1: CGPointMake(217.21, 215.24) controlPoint2: CGPointMake(226.39, 211.63)];
[path addCurveToPoint: CGPointMake(127, 186) controlPoint1: CGPointMake(197.32, 181.14) controlPoint2: CGPointMake(155.61, 179.26)];
[path addCurveToPoint: CGPointMake(78, 230) controlPoint1: CGPointMake(126.93, 214.62) controlPoint2: CGPointMake(110.79, 250.16)];
[path addCurveToPoint: CGPointMake(86, 203) controlPoint1: CGPointMake(76.79, 220.05) controlPoint2: CGPointMake(83.57, 212.84)];
[path addCurveToPoint: CGPointMake(85, 132) controlPoint1: CGPointMake(90.93, 183.08) controlPoint2: CGPointMake(90.79, 150.81)];
[path addCurveToPoint: CGPointMake(28, 121) controlPoint1: CGPointMake(60.95, 132.68) controlPoint2: CGPointMake(39.36, 132.88)];
[path addCurveToPoint: CGPointMake(57, 91) controlPoint1: CGPointMake(27.34, 95.81) controlPoint2: CGPointMake(48.07, 104.89)];
[path addCurveToPoint: CGPointMake(60, 73) controlPoint1: CGPointMake(58, 85) controlPoint2: CGPointMake(59, 79)];
[path addCurveToPoint: CGPointMake(85, 76) controlPoint1: CGPointMake(66.31, 61.96) controlPoint2: CGPointMake(80.38, 68.36)];
[path addCurveToPoint: CGPointMake(107, 66) controlPoint1: CGPointMake(93.79, 75.13) controlPoint2: CGPointMake(101.6, 70.61)];
[path closePath];
[path moveToPoint: CGPointMake(116, 69)];
[path addCurveToPoint: CGPointMake(84, 78) controlPoint1: CGPointMake(104.12, 69.8) controlPoint2: CGPointMake(92.62, 83.34)];
[path addCurveToPoint: CGPointMake(77, 72) controlPoint1: CGPointMake(81.67, 76) controlPoint2: CGPointMake(79.33, 74)];
[path addCurveToPoint: CGPointMake(67, 72) controlPoint1: CGPointMake(73.67, 72) controlPoint2: CGPointMake(70.33, 72)];
[path addCurveToPoint: CGPointMake(60, 93) controlPoint1: CGPointMake(60.55, 76.74) controlPoint2: CGPointMake(64.31, 86.1)];
[path addCurveToPoint: CGPointMake(32, 111) controlPoint1: CGPointMake(54.17, 102.33) controlPoint2: CGPointMake(37.4, 101.45)];
[path addCurveToPoint: CGPointMake(32, 121) controlPoint1: CGPointMake(32, 114.33) controlPoint2: CGPointMake(32, 117.67)];
[path addCurveToPoint: CGPointMake(88, 129) controlPoint1: CGPointMake(44.53, 129.24) controlPoint2: CGPointMake(66.88, 129.21)];
[path addCurveToPoint: CGPointMake(109, 172) controlPoint1: CGPointMake(93.2, 149.04) controlPoint2: CGPointMake(120.58, 148.18)];
[path addCurveToPoint: CGPointMake(92, 176) controlPoint1: CGPointMake(103.33, 173.33) controlPoint2: CGPointMake(97.67, 174.67)];
[path addCurveToPoint: CGPointMake(83, 230) controlPoint1: CGPointMake(91.91, 195.18) controlPoint2: CGPointMake(78.84, 222.44)];
[path addCurveToPoint: CGPointMake(85, 230) controlPoint1: CGPointMake(83.67, 230) controlPoint2: CGPointMake(84.33, 230)];
[path addCurveToPoint: CGPointMake(127, 183) controlPoint1: CGPointMake(131.62, 242.71) controlPoint2: CGPointMake(110.92, 195.36)];
[path addCurveToPoint: CGPointMake(223, 190) controlPoint1: CGPointMake(149.67, 169.94) controlPoint2: CGPointMake(207.14, 180.38)];
[path addCurveToPoint: CGPointMake(209, 226) controlPoint1: CGPointMake(228.64, 206.57) controlPoint2: CGPointMake(224.64, 221.84)];
[path addCurveToPoint: CGPointMake(210, 229) controlPoint1: CGPointMake(209.33, 227) controlPoint2: CGPointMake(209.67, 228)];
[path addCurveToPoint: CGPointMake(258, 184) controlPoint1: CGPointMake(240.81, 233.96) controlPoint2: CGPointMake(265.07, 221.63)];
[path addCurveToPoint: CGPointMake(251, 147) controlPoint1: CGPointMake(255.67, 171.67) controlPoint2: CGPointMake(253.33, 159.33)];
[path addCurveToPoint: CGPointMake(262, 79) controlPoint1: CGPointMake(253.73, 128.51) controlPoint2: CGPointMake(281.22, 99.39)];
[path addCurveToPoint: CGPointMake(230, 113) controlPoint1: CGPointMake(260.12, 98.13) controlPoint2: CGPointMake(245.13, 107.06)];
[path addCurveToPoint: CGPointMake(174, 146) controlPoint1: CGPointMake(229.65, 133.05) controlPoint2: CGPointMake(198.37, 155.24)];
[path addCurveToPoint: CGPointMake(150, 119) controlPoint1: CGPointMake(166, 137) controlPoint2: CGPointMake(158, 128)];
[path addCurveToPoint: CGPointMake(101, 96) controlPoint1: CGPointMake(138.77, 111.56) controlPoint2: CGPointMake(109.21, 105.11)];
[path addCurveToPoint: CGPointMake(116, 69) controlPoint1: CGPointMake(103.48, 82.21) controlPoint2: CGPointMake(113.99, 82.77)];
[path closePath];
[path moveToPoint: CGPointMake(76, 93)];
[path addCurveToPoint: CGPointMake(82, 93) controlPoint1: CGPointMake(78, 93) controlPoint2: CGPointMake(80, 93)];
[path addCurveToPoint: CGPointMake(82, 98) controlPoint1: CGPointMake(82, 94.67) controlPoint2: CGPointMake(82, 96.33)];
[path addCurveToPoint: CGPointMake(76, 98) controlPoint1: CGPointMake(80, 98) controlPoint2: CGPointMake(78, 98)];
[path addCurveToPoint: CGPointMake(76, 93) controlPoint1: CGPointMake(76, 96.33) controlPoint2: CGPointMake(76, 94.67)];
[path closePath];
return path;
}
UIBezierPath *BuildStarPath()
{
// Create new path, courtesy of PaintCode (paintcodeapp.com)
UIBezierPath* bezierPath = [UIBezierPath bezierPath];
// Move to the start of the path
[bezierPath moveToPoint: CGPointMake(883.23, 430.54)];
// Add the cubic segments
[bezierPath addCurveToPoint: CGPointMake(749.25, 358.4)
controlPoint1: CGPointMake(873.68, 370.91)
controlPoint2: CGPointMake(809.43, 367.95)];
[bezierPath addCurveToPoint: CGPointMake(668.1, 353.25)
controlPoint1: CGPointMake(721.92, 354.07)
controlPoint2: CGPointMake(690.4, 362.15)];
[bezierPath addCurveToPoint: CGPointMake(492.9, 156.15)
controlPoint1: CGPointMake(575.39, 316.25)
controlPoint2: CGPointMake(629.21, 155.47)];
[bezierPath addCurveToPoint: CGPointMake(461.98, 169.03)
controlPoint1: CGPointMake(482.59, 160.45)
controlPoint2: CGPointMake(472.29, 164.74)];
[bezierPath addCurveToPoint: CGPointMake(365.36, 345.52)
controlPoint1: CGPointMake(409.88, 207.98)
controlPoint2: CGPointMake(415.22, 305.32)];
[bezierPath addCurveToPoint: CGPointMake(262.31, 358.4)
controlPoint1: CGPointMake(341.9, 364.44)
controlPoint2: CGPointMake(300.41, 352.37)];
[bezierPath addCurveToPoint: CGPointMake(133.48, 460.17)
controlPoint1: CGPointMake(200.89, 368.12)
controlPoint2: CGPointMake(118.62, 376.61)];
[bezierPath addCurveToPoint: CGPointMake(277.77, 622.49)
controlPoint1: CGPointMake(148.46, 544.36)
controlPoint2: CGPointMake(258.55, 560.05)];
[bezierPath addCurveToPoint: CGPointMake(277.77, 871.12)
controlPoint1: CGPointMake(301.89, 700.9)
controlPoint2: CGPointMake(193.24, 819.76)];
[bezierPath addCurveToPoint: CGPointMake(513.51, 798.97)
controlPoint1: CGPointMake(382.76, 934.9)
controlPoint2: CGPointMake(435.24, 786.06)];
[bezierPath addCurveToPoint: CGPointMake(723.49, 878.84)
controlPoint1: CGPointMake(582.42, 810.35)
controlPoint2: CGPointMake(628.93, 907.89)];
[bezierPath addCurveToPoint: CGPointMake(740.24, 628.93)
controlPoint1: CGPointMake(834.7, 844.69)
controlPoint2: CGPointMake(722.44, 699.2)];
[bezierPath addCurveToPoint: CGPointMake(883.23, 430.54)
controlPoint1: CGPointMake(756.58, 564.39)
controlPoint2: CGPointMake(899.19, 530.23)];
// Close the path. Its now ready to draw
[bezierPath closePath];
return bezierPath;
}
UIColor *NoiseColor()
{
static UIImage *noise = nil;
if (noise)
return [UIColor colorWithPatternImage:noise];
srandom(time(0));
CGSize size = CGSizeMake(128, 128);
UIGraphicsBeginImageContextWithOptions(size, NO, 0.0);
for (int j = 0; j < size.height; j++)
for (int i = 0; i < size.height; i++)
{
UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake(i, j, 1, 1)];
CGFloat level = ((double) random() / (double) LONG_MAX);
[path fill:[UIColor colorWithWhite:level alpha:1]];
}
noise = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return [UIColor colorWithPatternImage:noise];
}

View File

@@ -55,6 +55,55 @@
7FC260D11E34A12000A39833 /* RNSVGSymbol.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FC260D01E34A12000A39833 /* RNSVGSymbol.m */; }; 7FC260D11E34A12000A39833 /* RNSVGSymbol.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FC260D01E34A12000A39833 /* RNSVGSymbol.m */; };
7FC260D41E34A12A00A39833 /* RNSVGSymbolManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FC260D31E34A12A00A39833 /* RNSVGSymbolManager.m */; }; 7FC260D41E34A12A00A39833 /* RNSVGSymbolManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FC260D31E34A12A00A39833 /* RNSVGSymbolManager.m */; };
7FFC4EA41E24E5AD00AD5BE5 /* RNSVGGlyphContext.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FFC4EA31E24E5AD00AD5BE5 /* RNSVGGlyphContext.m */; }; 7FFC4EA41E24E5AD00AD5BE5 /* RNSVGGlyphContext.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FFC4EA31E24E5AD00AD5BE5 /* RNSVGGlyphContext.m */; };
945A8AE11F4CBFD1004BBF6B /* Drawing-Text.m in Sources */ = {isa = PBXBuildFile; fileRef = 945A8ADF1F4CBFD1004BBF6B /* Drawing-Text.m */; };
945A8AE21F4CBFD1004BBF6B /* UIBezierPath+Text.m in Sources */ = {isa = PBXBuildFile; fileRef = 945A8AE01F4CBFD1004BBF6B /* UIBezierPath+Text.m */; };
945A8AE41F4CC01D004BBF6B /* UIBezierPath+Elements.m in Sources */ = {isa = PBXBuildFile; fileRef = 945A8AE31F4CC01D004BBF6B /* UIBezierPath+Elements.m */; };
945A8AE61F4CC037004BBF6B /* BaseGeometry.m in Sources */ = {isa = PBXBuildFile; fileRef = 945A8AE51F4CC037004BBF6B /* BaseGeometry.m */; };
945A8AE81F4CC04C004BBF6B /* BezierUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 945A8AE71F4CC04C004BBF6B /* BezierUtils.m */; };
945A8AEA1F4CC050004BBF6B /* BezierFunctions.m in Sources */ = {isa = PBXBuildFile; fileRef = 945A8AE91F4CC050004BBF6B /* BezierFunctions.m */; };
945A8AEC1F4CC055004BBF6B /* BezierElement.m in Sources */ = {isa = PBXBuildFile; fileRef = 945A8AEB1F4CC055004BBF6B /* BezierElement.m */; };
945A8AEE1F4CC07A004BBF6B /* Utility.m in Sources */ = {isa = PBXBuildFile; fileRef = 945A8AED1F4CC07A004BBF6B /* Utility.m */; };
945A8AF01F4CC092004BBF6B /* Drawing-Block.m in Sources */ = {isa = PBXBuildFile; fileRef = 945A8AEF1F4CC092004BBF6B /* Drawing-Block.m */; };
945A8AF31F4CC0AF004BBF6B /* Drawing-Gradient.m in Sources */ = {isa = PBXBuildFile; fileRef = 945A8AF11F4CC0AF004BBF6B /* Drawing-Gradient.m */; };
945A8AF41F4CC0AF004BBF6B /* Drawing-Util.m in Sources */ = {isa = PBXBuildFile; fileRef = 945A8AF21F4CC0AF004BBF6B /* Drawing-Util.m */; };
945A8AF61F4CC0C1004BBF6B /* ImageUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 945A8AF51F4CC0C1004BBF6B /* ImageUtils.m */; };
945A8AF81F4CE3E8004BBF6B /* AlignmentBaseline.m in Sources */ = {isa = PBXBuildFile; fileRef = 945A8AF71F4CE3E8004BBF6B /* AlignmentBaseline.m */; };
945A8AF91F4CE3E8004BBF6B /* AlignmentBaseline.m in Sources */ = {isa = PBXBuildFile; fileRef = 945A8AF71F4CE3E8004BBF6B /* AlignmentBaseline.m */; };
9494C47A1F47116800D5BCFD /* PerformanceBezier.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9494C4771F4710FE00D5BCFD /* PerformanceBezier.framework */; };
9494C4BB1F472DDA00D5BCFD /* UIBezierPath+getTransformAtDistance.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C4BA1F472DDA00D5BCFD /* UIBezierPath+getTransformAtDistance.m */; };
9494C4BC1F472DDA00D5BCFD /* UIBezierPath+getTransformAtDistance.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C4BA1F472DDA00D5BCFD /* UIBezierPath+getTransformAtDistance.m */; };
9494C4D81F473BA700D5BCFD /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9494C4D71F473BA700D5BCFD /* QuartzCore.framework */; };
9494C4DA1F473BCB00D5BCFD /* CoreText.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9494C4D91F473BCB00D5BCFD /* CoreText.framework */; };
9494C4DC1F473BD900D5BCFD /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9494C4DB1F473BD900D5BCFD /* CoreGraphics.framework */; };
9494C4DE1F473BDD00D5BCFD /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9494C4DD1F473BDD00D5BCFD /* UIKit.framework */; };
9494C4E01F473BED00D5BCFD /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9494C4DF1F473BED00D5BCFD /* Accelerate.framework */; };
9494C4E21F473BF500D5BCFD /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9494C4E11F473BF500D5BCFD /* Foundation.framework */; };
9494C4FF1F4B5BE800D5BCFD /* FontData.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C4E81F4B5BE700D5BCFD /* FontData.m */; };
9494C5001F4B5BE800D5BCFD /* FontData.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C4E81F4B5BE700D5BCFD /* FontData.m */; };
9494C5051F4B5BE800D5BCFD /* FontWeight.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C4EE1F4B5BE700D5BCFD /* FontWeight.m */; };
9494C5061F4B5BE800D5BCFD /* FontWeight.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C4EE1F4B5BE700D5BCFD /* FontWeight.m */; };
9494C5071F4B5BE800D5BCFD /* PropHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C4F01F4B5BE700D5BCFD /* PropHelper.m */; };
9494C5081F4B5BE800D5BCFD /* PropHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C4F01F4B5BE700D5BCFD /* PropHelper.m */; };
9494C5251F4B605F00D5BCFD /* GlyphContext.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C51A1F4B605F00D5BCFD /* GlyphContext.m */; };
9494C5261F4B605F00D5BCFD /* GlyphContext.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C51A1F4B605F00D5BCFD /* GlyphContext.m */; };
9494C5381F4C44DD00D5BCFD /* FontStyle.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C52F1F4C44DD00D5BCFD /* FontStyle.m */; };
9494C5391F4C44DD00D5BCFD /* FontStyle.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C52F1F4C44DD00D5BCFD /* FontStyle.m */; };
9494C53A1F4C44DD00D5BCFD /* FontVariantLigatures.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C5301F4C44DD00D5BCFD /* FontVariantLigatures.m */; };
9494C53B1F4C44DD00D5BCFD /* FontVariantLigatures.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C5301F4C44DD00D5BCFD /* FontVariantLigatures.m */; };
9494C53C1F4C44DD00D5BCFD /* TextAnchor.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C5311F4C44DD00D5BCFD /* TextAnchor.m */; };
9494C53D1F4C44DD00D5BCFD /* TextAnchor.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C5311F4C44DD00D5BCFD /* TextAnchor.m */; };
9494C53E1F4C44DD00D5BCFD /* TextDecoration.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C5321F4C44DD00D5BCFD /* TextDecoration.m */; };
9494C53F1F4C44DD00D5BCFD /* TextDecoration.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C5321F4C44DD00D5BCFD /* TextDecoration.m */; };
9494C5401F4C44DD00D5BCFD /* TextLengthAdjust.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C5331F4C44DD00D5BCFD /* TextLengthAdjust.m */; };
9494C5411F4C44DD00D5BCFD /* TextLengthAdjust.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C5331F4C44DD00D5BCFD /* TextLengthAdjust.m */; };
9494C5421F4C44DD00D5BCFD /* TextPathMethod.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C5341F4C44DD00D5BCFD /* TextPathMethod.m */; };
9494C5431F4C44DD00D5BCFD /* TextPathMethod.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C5341F4C44DD00D5BCFD /* TextPathMethod.m */; };
9494C5441F4C44DD00D5BCFD /* TextPathMidLine.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C5351F4C44DD00D5BCFD /* TextPathMidLine.m */; };
9494C5451F4C44DD00D5BCFD /* TextPathMidLine.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C5351F4C44DD00D5BCFD /* TextPathMidLine.m */; };
9494C5461F4C44DD00D5BCFD /* TextPathSide.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C5361F4C44DD00D5BCFD /* TextPathSide.m */; };
9494C5471F4C44DD00D5BCFD /* TextPathSide.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C5361F4C44DD00D5BCFD /* TextPathSide.m */; };
9494C5481F4C44DD00D5BCFD /* TextPathSpacing.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C5371F4C44DD00D5BCFD /* TextPathSpacing.m */; };
9494C5491F4C44DD00D5BCFD /* TextPathSpacing.m in Sources */ = {isa = PBXBuildFile; fileRef = 9494C5371F4C44DD00D5BCFD /* TextPathSpacing.m */; };
A361E76E1EB0C33D00646005 /* RNSVGTextManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 10BA0D331CE74E3100887C2B /* RNSVGTextManager.m */; }; A361E76E1EB0C33D00646005 /* RNSVGTextManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 10BA0D331CE74E3100887C2B /* RNSVGTextManager.m */; };
A361E76F1EB0C33D00646005 /* RNSVGImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 1039D2841CE71EB7001E90A8 /* RNSVGImage.m */; }; A361E76F1EB0C33D00646005 /* RNSVGImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 1039D2841CE71EB7001E90A8 /* RNSVGImage.m */; };
A361E7701EB0C33D00646005 /* RNSVGRect.m in Sources */ = {isa = PBXBuildFile; fileRef = 10BA0D471CE74E3D00887C2B /* RNSVGRect.m */; }; A361E7701EB0C33D00646005 /* RNSVGRect.m in Sources */ = {isa = PBXBuildFile; fileRef = 10BA0D471CE74E3D00887C2B /* RNSVGRect.m */; };
@@ -105,6 +154,23 @@
A361E79D1EB0C33D00646005 /* RNSVGDefsManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 1023B48C1D3DDCCE0051496D /* RNSVGDefsManager.m */; }; A361E79D1EB0C33D00646005 /* RNSVGDefsManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 1023B48C1D3DDCCE0051496D /* RNSVGDefsManager.m */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
9494C4761F4710FE00D5BCFD /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 9494C4711F4710FE00D5BCFD /* PerformanceBezier.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 66F2EBE31A8DC05100D536E9;
remoteInfo = PerformanceBezier;
};
9494C4781F4710FE00D5BCFD /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 9494C4711F4710FE00D5BCFD /* PerformanceBezier.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 66B9D28C1A8D5FDE00CAC341;
remoteInfo = PerformanceBezierTests;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */ /* Begin PBXCopyFilesBuildPhase section */
0CF68ABF1AF0540F00FF9E5C /* CopyFiles */ = { 0CF68ABF1AF0540F00FF9E5C /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase; isa = PBXCopyFilesBuildPhase;
@@ -231,6 +297,56 @@
7FC260D31E34A12A00A39833 /* RNSVGSymbolManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNSVGSymbolManager.m; sourceTree = "<group>"; }; 7FC260D31E34A12A00A39833 /* RNSVGSymbolManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNSVGSymbolManager.m; sourceTree = "<group>"; };
7FFC4EA21E24E52500AD5BE5 /* RNSVGGlyphContext.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = RNSVGGlyphContext.h; path = Text/RNSVGGlyphContext.h; sourceTree = "<group>"; }; 7FFC4EA21E24E52500AD5BE5 /* RNSVGGlyphContext.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = RNSVGGlyphContext.h; path = Text/RNSVGGlyphContext.h; sourceTree = "<group>"; };
7FFC4EA31E24E5AD00AD5BE5 /* RNSVGGlyphContext.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNSVGGlyphContext.m; path = Text/RNSVGGlyphContext.m; sourceTree = "<group>"; }; 7FFC4EA31E24E5AD00AD5BE5 /* RNSVGGlyphContext.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNSVGGlyphContext.m; path = Text/RNSVGGlyphContext.m; sourceTree = "<group>"; };
945A8ADD1F4CB7F9004BBF6B /* QuartzBookPack */ = {isa = PBXFileReference; lastKnownFileType = folder; path = QuartzBookPack; sourceTree = "<group>"; };
945A8ADF1F4CBFD1004BBF6B /* Drawing-Text.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = "Drawing-Text.m"; path = "QuartzBookPack/TextDrawing/Drawing-Text.m"; sourceTree = "<group>"; };
945A8AE01F4CBFD1004BBF6B /* UIBezierPath+Text.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = "UIBezierPath+Text.m"; path = "QuartzBookPack/TextDrawing/UIBezierPath+Text.m"; sourceTree = "<group>"; };
945A8AE31F4CC01D004BBF6B /* UIBezierPath+Elements.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = "UIBezierPath+Elements.m"; path = "QuartzBookPack/Bezier/UIBezierPath+Elements.m"; sourceTree = "<group>"; };
945A8AE51F4CC037004BBF6B /* BaseGeometry.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = BaseGeometry.m; path = QuartzBookPack/Geometry/BaseGeometry.m; sourceTree = "<group>"; };
945A8AE71F4CC04C004BBF6B /* BezierUtils.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = BezierUtils.m; path = QuartzBookPack/Bezier/BezierUtils.m; sourceTree = "<group>"; };
945A8AE91F4CC050004BBF6B /* BezierFunctions.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = BezierFunctions.m; path = QuartzBookPack/Bezier/BezierFunctions.m; sourceTree = "<group>"; };
945A8AEB1F4CC055004BBF6B /* BezierElement.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = BezierElement.m; path = QuartzBookPack/Bezier/BezierElement.m; sourceTree = "<group>"; };
945A8AED1F4CC07A004BBF6B /* Utility.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = Utility.m; path = QuartzBookPack/Utility/Utility.m; sourceTree = "<group>"; };
945A8AEF1F4CC092004BBF6B /* Drawing-Block.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = "Drawing-Block.m"; path = "QuartzBookPack/Drawing/Drawing-Block.m"; sourceTree = "<group>"; };
945A8AF11F4CC0AF004BBF6B /* Drawing-Gradient.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = "Drawing-Gradient.m"; path = "QuartzBookPack/Drawing/Drawing-Gradient.m"; sourceTree = "<group>"; };
945A8AF21F4CC0AF004BBF6B /* Drawing-Util.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = "Drawing-Util.m"; path = "QuartzBookPack/Drawing/Drawing-Util.m"; sourceTree = "<group>"; };
945A8AF51F4CC0C1004BBF6B /* ImageUtils.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = ImageUtils.m; path = QuartzBookPack/Image/ImageUtils.m; sourceTree = "<group>"; };
945A8AF71F4CE3E8004BBF6B /* AlignmentBaseline.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AlignmentBaseline.m; path = Text/AlignmentBaseline.m; sourceTree = "<group>"; };
945A8AFA1F4CE41E004BBF6B /* AlignmentBaseline.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = AlignmentBaseline.h; path = Text/AlignmentBaseline.h; sourceTree = "<group>"; };
9494C4711F4710FE00D5BCFD /* PerformanceBezier.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = PerformanceBezier.xcodeproj; path = PerformanceBezier/PerformanceBezier.xcodeproj; sourceTree = "<group>"; };
9494C4B91F472DDA00D5BCFD /* UIBezierPath+getTransformAtDistance.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIBezierPath+getTransformAtDistance.h"; path = "Utils/UIBezierPath+getTransformAtDistance.h"; sourceTree = "<group>"; };
9494C4BA1F472DDA00D5BCFD /* UIBezierPath+getTransformAtDistance.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIBezierPath+getTransformAtDistance.m"; path = "Utils/UIBezierPath+getTransformAtDistance.m"; sourceTree = "<group>"; };
9494C4D71F473BA700D5BCFD /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };
9494C4D91F473BCB00D5BCFD /* CoreText.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreText.framework; path = System/Library/Frameworks/CoreText.framework; sourceTree = SDKROOT; };
9494C4DB1F473BD900D5BCFD /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
9494C4DD1F473BDD00D5BCFD /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
9494C4DF1F473BED00D5BCFD /* Accelerate.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accelerate.framework; path = System/Library/Frameworks/Accelerate.framework; sourceTree = SDKROOT; };
9494C4E11F473BF500D5BCFD /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
9494C4E71F4B5BE700D5BCFD /* FontData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FontData.h; path = Text/FontData.h; sourceTree = "<group>"; };
9494C4E81F4B5BE700D5BCFD /* FontData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FontData.m; path = Text/FontData.m; sourceTree = "<group>"; };
9494C4E91F4B5BE700D5BCFD /* FontStyle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FontStyle.h; path = Text/FontStyle.h; sourceTree = "<group>"; };
9494C4EB1F4B5BE700D5BCFD /* FontVariantLigatures.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FontVariantLigatures.h; path = Text/FontVariantLigatures.h; sourceTree = "<group>"; };
9494C4ED1F4B5BE700D5BCFD /* FontWeight.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FontWeight.h; path = Text/FontWeight.h; sourceTree = "<group>"; };
9494C4EE1F4B5BE700D5BCFD /* FontWeight.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FontWeight.m; path = Text/FontWeight.m; sourceTree = "<group>"; };
9494C4EF1F4B5BE700D5BCFD /* PropHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PropHelper.h; path = Text/PropHelper.h; sourceTree = "<group>"; };
9494C4F01F4B5BE700D5BCFD /* PropHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = PropHelper.m; path = Text/PropHelper.m; sourceTree = "<group>"; };
9494C4F11F4B5BE700D5BCFD /* TextAnchor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TextAnchor.h; path = Text/TextAnchor.h; sourceTree = "<group>"; };
9494C4F31F4B5BE700D5BCFD /* TextDecoration.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TextDecoration.h; path = Text/TextDecoration.h; sourceTree = "<group>"; };
9494C4F51F4B5BE700D5BCFD /* TextLengthAdjust.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TextLengthAdjust.h; path = Text/TextLengthAdjust.h; sourceTree = "<group>"; };
9494C4F71F4B5BE700D5BCFD /* TextPathMethod.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TextPathMethod.h; path = Text/TextPathMethod.h; sourceTree = "<group>"; };
9494C4F91F4B5BE700D5BCFD /* TextPathMidLine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TextPathMidLine.h; path = Text/TextPathMidLine.h; sourceTree = "<group>"; };
9494C4FB1F4B5BE700D5BCFD /* TextPathSide.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TextPathSide.h; path = Text/TextPathSide.h; sourceTree = "<group>"; };
9494C4FD1F4B5BE700D5BCFD /* TextPathSpacing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TextPathSpacing.h; path = Text/TextPathSpacing.h; sourceTree = "<group>"; };
9494C5191F4B605F00D5BCFD /* GlyphContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GlyphContext.h; path = Text/GlyphContext.h; sourceTree = "<group>"; };
9494C51A1F4B605F00D5BCFD /* GlyphContext.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GlyphContext.m; path = Text/GlyphContext.m; sourceTree = "<group>"; };
9494C52F1F4C44DD00D5BCFD /* FontStyle.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FontStyle.m; path = Text/FontStyle.m; sourceTree = "<group>"; };
9494C5301F4C44DD00D5BCFD /* FontVariantLigatures.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FontVariantLigatures.m; path = Text/FontVariantLigatures.m; sourceTree = "<group>"; };
9494C5311F4C44DD00D5BCFD /* TextAnchor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TextAnchor.m; path = Text/TextAnchor.m; sourceTree = "<group>"; };
9494C5321F4C44DD00D5BCFD /* TextDecoration.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TextDecoration.m; path = Text/TextDecoration.m; sourceTree = "<group>"; };
9494C5331F4C44DD00D5BCFD /* TextLengthAdjust.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TextLengthAdjust.m; path = Text/TextLengthAdjust.m; sourceTree = "<group>"; };
9494C5341F4C44DD00D5BCFD /* TextPathMethod.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TextPathMethod.m; path = Text/TextPathMethod.m; sourceTree = "<group>"; };
9494C5351F4C44DD00D5BCFD /* TextPathMidLine.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TextPathMidLine.m; path = Text/TextPathMidLine.m; sourceTree = "<group>"; };
9494C5361F4C44DD00D5BCFD /* TextPathSide.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TextPathSide.m; path = Text/TextPathSide.m; sourceTree = "<group>"; };
9494C5371F4C44DD00D5BCFD /* TextPathSpacing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TextPathSpacing.m; path = Text/TextPathSpacing.m; sourceTree = "<group>"; };
94DDAC5C1F3D024300EED511 /* libRNSVG-tvOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libRNSVG-tvOS.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 94DDAC5C1F3D024300EED511 /* libRNSVG-tvOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libRNSVG-tvOS.a"; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */ /* End PBXFileReference section */
@@ -239,6 +355,13 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
9494C4E01F473BED00D5BCFD /* Accelerate.framework in Frameworks */,
9494C4D81F473BA700D5BCFD /* QuartzCore.framework in Frameworks */,
9494C4DA1F473BCB00D5BCFD /* CoreText.framework in Frameworks */,
9494C4DC1F473BD900D5BCFD /* CoreGraphics.framework in Frameworks */,
9494C4DE1F473BDD00D5BCFD /* UIKit.framework in Frameworks */,
9494C4E21F473BF500D5BCFD /* Foundation.framework in Frameworks */,
9494C47A1F47116800D5BCFD /* PerformanceBezier.framework in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -255,6 +378,20 @@
0CF68AB81AF0540F00FF9E5C = { 0CF68AB81AF0540F00FF9E5C = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
945A8AF51F4CC0C1004BBF6B /* ImageUtils.m */,
945A8AF11F4CC0AF004BBF6B /* Drawing-Gradient.m */,
945A8AF21F4CC0AF004BBF6B /* Drawing-Util.m */,
945A8AEF1F4CC092004BBF6B /* Drawing-Block.m */,
945A8AED1F4CC07A004BBF6B /* Utility.m */,
945A8AEB1F4CC055004BBF6B /* BezierElement.m */,
945A8AE91F4CC050004BBF6B /* BezierFunctions.m */,
945A8AE71F4CC04C004BBF6B /* BezierUtils.m */,
945A8AE51F4CC037004BBF6B /* BaseGeometry.m */,
945A8AE31F4CC01D004BBF6B /* UIBezierPath+Elements.m */,
945A8ADF1F4CBFD1004BBF6B /* Drawing-Text.m */,
945A8AE01F4CBFD1004BBF6B /* UIBezierPath+Text.m */,
945A8ADD1F4CB7F9004BBF6B /* QuartzBookPack */,
9494C4711F4710FE00D5BCFD /* PerformanceBezier.xcodeproj */,
1039D29A1CE7212C001E90A8 /* Utils */, 1039D29A1CE7212C001E90A8 /* Utils */,
1039D2801CE71DCF001E90A8 /* Elements */, 1039D2801CE71DCF001E90A8 /* Elements */,
1039D27F1CE71D9B001E90A8 /* Text */, 1039D27F1CE71D9B001E90A8 /* Text */,
@@ -267,6 +404,7 @@
0CF68AE11AF0549300FF9E5C /* RNSVGRenderable.h */, 0CF68AE11AF0549300FF9E5C /* RNSVGRenderable.h */,
0CF68AE21AF0549300FF9E5C /* RNSVGRenderable.m */, 0CF68AE21AF0549300FF9E5C /* RNSVGRenderable.m */,
0CF68AC21AF0540F00FF9E5C /* Products */, 0CF68AC21AF0540F00FF9E5C /* Products */,
9494C2B31F46139600D5BCFD /* Frameworks */,
); );
sourceTree = "<group>"; sourceTree = "<group>";
}; };
@@ -358,16 +496,44 @@
1039D27F1CE71D9B001E90A8 /* Text */ = { 1039D27F1CE71D9B001E90A8 /* Text */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
945A8AFA1F4CE41E004BBF6B /* AlignmentBaseline.h */,
945A8AF71F4CE3E8004BBF6B /* AlignmentBaseline.m */,
9494C4E71F4B5BE700D5BCFD /* FontData.h */,
9494C4E81F4B5BE700D5BCFD /* FontData.m */,
9494C4E91F4B5BE700D5BCFD /* FontStyle.h */,
9494C52F1F4C44DD00D5BCFD /* FontStyle.m */,
9494C4EB1F4B5BE700D5BCFD /* FontVariantLigatures.h */,
9494C5301F4C44DD00D5BCFD /* FontVariantLigatures.m */,
9494C4ED1F4B5BE700D5BCFD /* FontWeight.h */,
9494C4EE1F4B5BE700D5BCFD /* FontWeight.m */,
9494C5191F4B605F00D5BCFD /* GlyphContext.h */,
9494C51A1F4B605F00D5BCFD /* GlyphContext.m */,
9494C4EF1F4B5BE700D5BCFD /* PropHelper.h */,
9494C4F01F4B5BE700D5BCFD /* PropHelper.m */,
103371331D41D3400028AF13 /* RNSVGBezierTransformer.h */,
103371311D41C5C90028AF13 /* RNSVGBezierTransformer.m */,
7FFC4EA21E24E52500AD5BE5 /* RNSVGGlyphContext.h */, 7FFC4EA21E24E52500AD5BE5 /* RNSVGGlyphContext.h */,
7FFC4EA31E24E5AD00AD5BE5 /* RNSVGGlyphContext.m */, 7FFC4EA31E24E5AD00AD5BE5 /* RNSVGGlyphContext.m */,
1039D28F1CE71EC2001E90A8 /* RNSVGText.h */,
1039D2901CE71EC2001E90A8 /* RNSVGText.m */,
7F08CE9C1E23479700650F83 /* RNSVGTextPath.h */, 7F08CE9C1E23479700650F83 /* RNSVGTextPath.h */,
7F08CE9D1E23479700650F83 /* RNSVGTextPath.m */, 7F08CE9D1E23479700650F83 /* RNSVGTextPath.m */,
7F08CE9E1E23479700650F83 /* RNSVGTSpan.h */, 7F08CE9E1E23479700650F83 /* RNSVGTSpan.h */,
7F08CE9F1E23479700650F83 /* RNSVGTSpan.m */, 7F08CE9F1E23479700650F83 /* RNSVGTSpan.m */,
103371331D41D3400028AF13 /* RNSVGBezierTransformer.h */, 9494C4F11F4B5BE700D5BCFD /* TextAnchor.h */,
103371311D41C5C90028AF13 /* RNSVGBezierTransformer.m */, 9494C5311F4C44DD00D5BCFD /* TextAnchor.m */,
1039D28F1CE71EC2001E90A8 /* RNSVGText.h */, 9494C4F31F4B5BE700D5BCFD /* TextDecoration.h */,
1039D2901CE71EC2001E90A8 /* RNSVGText.m */, 9494C5321F4C44DD00D5BCFD /* TextDecoration.m */,
9494C4F51F4B5BE700D5BCFD /* TextLengthAdjust.h */,
9494C5331F4C44DD00D5BCFD /* TextLengthAdjust.m */,
9494C4F71F4B5BE700D5BCFD /* TextPathMethod.h */,
9494C5341F4C44DD00D5BCFD /* TextPathMethod.m */,
9494C4F91F4B5BE700D5BCFD /* TextPathMidLine.h */,
9494C5351F4C44DD00D5BCFD /* TextPathMidLine.m */,
9494C4FB1F4B5BE700D5BCFD /* TextPathSide.h */,
9494C5361F4C44DD00D5BCFD /* TextPathSide.m */,
9494C4FD1F4B5BE700D5BCFD /* TextPathSpacing.h */,
9494C5371F4C44DD00D5BCFD /* TextPathSpacing.m */,
); );
name = Text; name = Text;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -415,10 +581,34 @@
7F9CDAF91E1F809C00E0C805 /* RNSVGPathParser.m */, 7F9CDAF91E1F809C00E0C805 /* RNSVGPathParser.m */,
1039D29B1CE72177001E90A8 /* RCTConvert+RNSVG.h */, 1039D29B1CE72177001E90A8 /* RCTConvert+RNSVG.h */,
1039D29C1CE72177001E90A8 /* RCTConvert+RNSVG.m */, 1039D29C1CE72177001E90A8 /* RCTConvert+RNSVG.m */,
9494C4B91F472DDA00D5BCFD /* UIBezierPath+getTransformAtDistance.h */,
9494C4BA1F472DDA00D5BCFD /* UIBezierPath+getTransformAtDistance.m */,
); );
name = Utils; name = Utils;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
9494C2B31F46139600D5BCFD /* Frameworks */ = {
isa = PBXGroup;
children = (
9494C4E11F473BF500D5BCFD /* Foundation.framework */,
9494C4DF1F473BED00D5BCFD /* Accelerate.framework */,
9494C4DD1F473BDD00D5BCFD /* UIKit.framework */,
9494C4DB1F473BD900D5BCFD /* CoreGraphics.framework */,
9494C4D91F473BCB00D5BCFD /* CoreText.framework */,
9494C4D71F473BA700D5BCFD /* QuartzCore.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
9494C4721F4710FE00D5BCFD /* Products */ = {
isa = PBXGroup;
children = (
9494C4771F4710FE00D5BCFD /* PerformanceBezier.framework */,
9494C4791F4710FE00D5BCFD /* PerformanceBezierTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
/* End PBXGroup section */ /* End PBXGroup section */
/* Begin PBXNativeTarget section */ /* Begin PBXNativeTarget section */
@@ -479,6 +669,12 @@
mainGroup = 0CF68AB81AF0540F00FF9E5C; mainGroup = 0CF68AB81AF0540F00FF9E5C;
productRefGroup = 0CF68AC21AF0540F00FF9E5C /* Products */; productRefGroup = 0CF68AC21AF0540F00FF9E5C /* Products */;
projectDirPath = ""; projectDirPath = "";
projectReferences = (
{
ProductGroup = 9494C4721F4710FE00D5BCFD /* Products */;
ProjectRef = 9494C4711F4710FE00D5BCFD /* PerformanceBezier.xcodeproj */;
},
);
projectRoot = ""; projectRoot = "";
targets = ( targets = (
0CF68AC01AF0540F00FF9E5C /* RNSVG */, 0CF68AC01AF0540F00FF9E5C /* RNSVG */,
@@ -487,21 +683,54 @@
}; };
/* End PBXProject section */ /* End PBXProject section */
/* Begin PBXReferenceProxy section */
9494C4771F4710FE00D5BCFD /* PerformanceBezier.framework */ = {
isa = PBXReferenceProxy;
fileType = wrapper.framework.static;
path = PerformanceBezier.framework;
remoteRef = 9494C4761F4710FE00D5BCFD /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
9494C4791F4710FE00D5BCFD /* PerformanceBezierTests.xctest */ = {
isa = PBXReferenceProxy;
fileType = wrapper.cfbundle;
path = PerformanceBezierTests.xctest;
remoteRef = 9494C4781F4710FE00D5BCFD /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
/* End PBXReferenceProxy section */
/* Begin PBXSourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */
0CF68ABD1AF0540F00FF9E5C /* Sources */ = { 0CF68ABD1AF0540F00FF9E5C /* Sources */ = {
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
945A8AF01F4CC092004BBF6B /* Drawing-Block.m in Sources */,
945A8AF31F4CC0AF004BBF6B /* Drawing-Gradient.m in Sources */,
945A8AF41F4CC0AF004BBF6B /* Drawing-Util.m in Sources */,
945A8AF61F4CC0C1004BBF6B /* ImageUtils.m in Sources */,
945A8AEE1F4CC07A004BBF6B /* Utility.m in Sources */,
945A8AE81F4CC04C004BBF6B /* BezierUtils.m in Sources */,
945A8AEC1F4CC055004BBF6B /* BezierElement.m in Sources */,
945A8AEA1F4CC050004BBF6B /* BezierFunctions.m in Sources */,
945A8AE61F4CC037004BBF6B /* BaseGeometry.m in Sources */,
945A8AE41F4CC01D004BBF6B /* UIBezierPath+Elements.m in Sources */,
945A8AE11F4CBFD1004BBF6B /* Drawing-Text.m in Sources */,
945A8AE21F4CBFD1004BBF6B /* UIBezierPath+Text.m in Sources */,
10BA0D3F1CE74E3100887C2B /* RNSVGTextManager.m in Sources */, 10BA0D3F1CE74E3100887C2B /* RNSVGTextManager.m in Sources */,
1039D28A1CE71EB7001E90A8 /* RNSVGImage.m in Sources */, 1039D28A1CE71EB7001E90A8 /* RNSVGImage.m in Sources */,
10BA0D4B1CE74E3D00887C2B /* RNSVGRect.m in Sources */, 10BA0D4B1CE74E3D00887C2B /* RNSVGRect.m in Sources */,
10BA0D341CE74E3100887C2B /* RNSVGCircleManager.m in Sources */, 10BA0D341CE74E3100887C2B /* RNSVGCircleManager.m in Sources */,
10BEC1BC1D3F66F500FDCB19 /* RNSVGLinearGradient.m in Sources */, 10BEC1BC1D3F66F500FDCB19 /* RNSVGLinearGradient.m in Sources */,
9494C5461F4C44DD00D5BCFD /* TextPathSide.m in Sources */,
1039D2B01CE72F27001E90A8 /* RNSVGPercentageConverter.m in Sources */, 1039D2B01CE72F27001E90A8 /* RNSVGPercentageConverter.m in Sources */,
7FFC4EA41E24E5AD00AD5BE5 /* RNSVGGlyphContext.m in Sources */, 7FFC4EA41E24E5AD00AD5BE5 /* RNSVGGlyphContext.m in Sources */,
9494C53C1F4C44DD00D5BCFD /* TextAnchor.m in Sources */,
10BA0D491CE74E3D00887C2B /* RNSVGEllipse.m in Sources */, 10BA0D491CE74E3D00887C2B /* RNSVGEllipse.m in Sources */,
9494C5051F4B5BE800D5BCFD /* FontWeight.m in Sources */,
1039D28B1CE71EB7001E90A8 /* RNSVGPath.m in Sources */, 1039D28B1CE71EB7001E90A8 /* RNSVGPath.m in Sources */,
7F08CEA01E23479700650F83 /* RNSVGTextPath.m in Sources */, 7F08CEA01E23479700650F83 /* RNSVGTextPath.m in Sources */,
9494C4BB1F472DDA00D5BCFD /* UIBezierPath+getTransformAtDistance.m in Sources */,
1023B4931D3DF5060051496D /* RNSVGUse.m in Sources */, 1023B4931D3DF5060051496D /* RNSVGUse.m in Sources */,
10BEC1C21D3F680F00FDCB19 /* RNSVGLinearGradientManager.m in Sources */, 10BEC1C21D3F680F00FDCB19 /* RNSVGLinearGradientManager.m in Sources */,
1039D2951CE71EC2001E90A8 /* RNSVGText.m in Sources */, 1039D2951CE71EC2001E90A8 /* RNSVGText.m in Sources */,
@@ -509,7 +738,10 @@
0CF68B071AF0549300FF9E5C /* RNSVGRenderable.m in Sources */, 0CF68B071AF0549300FF9E5C /* RNSVGRenderable.m in Sources */,
1039D2891CE71EB7001E90A8 /* RNSVGGroup.m in Sources */, 1039D2891CE71EB7001E90A8 /* RNSVGGroup.m in Sources */,
10ED4A9E1CF0656A0078BC02 /* RNSVGClipPathManager.m in Sources */, 10ED4A9E1CF0656A0078BC02 /* RNSVGClipPathManager.m in Sources */,
945A8AF81F4CE3E8004BBF6B /* AlignmentBaseline.m in Sources */,
10BEC1C61D3F7BD300FDCB19 /* RNSVGPainter.m in Sources */, 10BEC1C61D3F7BD300FDCB19 /* RNSVGPainter.m in Sources */,
9494C5381F4C44DD00D5BCFD /* FontStyle.m in Sources */,
9494C5071F4B5BE800D5BCFD /* PropHelper.m in Sources */,
10ED4AA21CF078830078BC02 /* RNSVGNode.m in Sources */, 10ED4AA21CF078830078BC02 /* RNSVGNode.m in Sources */,
10ED4A9B1CF065260078BC02 /* RNSVGClipPath.m in Sources */, 10ED4A9B1CF065260078BC02 /* RNSVGClipPath.m in Sources */,
10BA0D3E1CE74E3100887C2B /* RNSVGSvgViewManager.m in Sources */, 10BA0D3E1CE74E3100887C2B /* RNSVGSvgViewManager.m in Sources */,
@@ -519,14 +751,18 @@
10BA0D3C1CE74E3100887C2B /* RNSVGRenderableManager.m in Sources */, 10BA0D3C1CE74E3100887C2B /* RNSVGRenderableManager.m in Sources */,
10BEC1BD1D3F66F500FDCB19 /* RNSVGRadialGradient.m in Sources */, 10BEC1BD1D3F66F500FDCB19 /* RNSVGRadialGradient.m in Sources */,
10BEC1C31D3F680F00FDCB19 /* RNSVGRadialGradientManager.m in Sources */, 10BEC1C31D3F680F00FDCB19 /* RNSVGRadialGradientManager.m in Sources */,
9494C5441F4C44DD00D5BCFD /* TextPathMidLine.m in Sources */,
10BA0D371CE74E3100887C2B /* RNSVGImageManager.m in Sources */, 10BA0D371CE74E3100887C2B /* RNSVGImageManager.m in Sources */,
10BA0D391CE74E3100887C2B /* RNSVGNodeManager.m in Sources */, 10BA0D391CE74E3100887C2B /* RNSVGNodeManager.m in Sources */,
7FC260D11E34A12000A39833 /* RNSVGSymbol.m in Sources */, 7FC260D11E34A12000A39833 /* RNSVGSymbol.m in Sources */,
1023B4901D3DF4C40051496D /* RNSVGDefs.m in Sources */, 1023B4901D3DF4C40051496D /* RNSVGDefs.m in Sources */,
10BA0D381CE74E3100887C2B /* RNSVGLineManager.m in Sources */, 10BA0D381CE74E3100887C2B /* RNSVGLineManager.m in Sources */,
9494C5251F4B605F00D5BCFD /* GlyphContext.m in Sources */,
10BA0D481CE74E3D00887C2B /* RNSVGCircle.m in Sources */, 10BA0D481CE74E3D00887C2B /* RNSVGCircle.m in Sources */,
9494C5401F4C44DD00D5BCFD /* TextLengthAdjust.m in Sources */,
10BA0D351CE74E3100887C2B /* RNSVGEllipseManager.m in Sources */, 10BA0D351CE74E3100887C2B /* RNSVGEllipseManager.m in Sources */,
1039D2A01CE72177001E90A8 /* RCTConvert+RNSVG.m in Sources */, 1039D2A01CE72177001E90A8 /* RCTConvert+RNSVG.m in Sources */,
9494C4FF1F4B5BE800D5BCFD /* FontData.m in Sources */,
0CF68B0B1AF0549300FF9E5C /* RNSVGBrush.m in Sources */, 0CF68B0B1AF0549300FF9E5C /* RNSVGBrush.m in Sources */,
7FC260D41E34A12A00A39833 /* RNSVGSymbolManager.m in Sources */, 7FC260D41E34A12A00A39833 /* RNSVGSymbolManager.m in Sources */,
7F9CDAFA1E1F809C00E0C805 /* RNSVGPathParser.m in Sources */, 7F9CDAFA1E1F809C00E0C805 /* RNSVGPathParser.m in Sources */,
@@ -534,12 +770,16 @@
7F08CE9A1E23476900650F83 /* RNSVGTextPathManager.m in Sources */, 7F08CE9A1E23476900650F83 /* RNSVGTextPathManager.m in Sources */,
7F08CE9B1E23476900650F83 /* RNSVGTSpanManager.m in Sources */, 7F08CE9B1E23476900650F83 /* RNSVGTSpanManager.m in Sources */,
7FC260CE1E3499BC00A39833 /* RNSVGViewBox.m in Sources */, 7FC260CE1E3499BC00A39833 /* RNSVGViewBox.m in Sources */,
9494C53E1F4C44DD00D5BCFD /* TextDecoration.m in Sources */,
7F08CEA11E23479700650F83 /* RNSVGTSpan.m in Sources */, 7F08CEA11E23479700650F83 /* RNSVGTSpan.m in Sources */,
9494C5421F4C44DD00D5BCFD /* TextPathMethod.m in Sources */,
10BA0D4A1CE74E3D00887C2B /* RNSVGLine.m in Sources */, 10BA0D4A1CE74E3D00887C2B /* RNSVGLine.m in Sources */,
10FDEEB21D3FB60500A5C46C /* RNSVGPainterBrush.m in Sources */, 10FDEEB21D3FB60500A5C46C /* RNSVGPainterBrush.m in Sources */,
1039D28C1CE71EB7001E90A8 /* RNSVGSvgView.m in Sources */, 1039D28C1CE71EB7001E90A8 /* RNSVGSvgView.m in Sources */,
1023B4961D3DF57D0051496D /* RNSVGUseManager.m in Sources */, 1023B4961D3DF57D0051496D /* RNSVGUseManager.m in Sources */,
1023B48D1D3DDCCE0051496D /* RNSVGDefsManager.m in Sources */, 1023B48D1D3DDCCE0051496D /* RNSVGDefsManager.m in Sources */,
9494C53A1F4C44DD00D5BCFD /* FontVariantLigatures.m in Sources */,
9494C5481F4C44DD00D5BCFD /* TextPathSpacing.m in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -554,11 +794,14 @@
A361E7721EB0C33D00646005 /* RNSVGLinearGradient.m in Sources */, A361E7721EB0C33D00646005 /* RNSVGLinearGradient.m in Sources */,
A361E7731EB0C33D00646005 /* RNSVGPercentageConverter.m in Sources */, A361E7731EB0C33D00646005 /* RNSVGPercentageConverter.m in Sources */,
A361E7741EB0C33D00646005 /* RNSVGGlyphContext.m in Sources */, A361E7741EB0C33D00646005 /* RNSVGGlyphContext.m in Sources */,
9494C53F1F4C44DD00D5BCFD /* TextDecoration.m in Sources */,
A361E7751EB0C33D00646005 /* RNSVGEllipse.m in Sources */, A361E7751EB0C33D00646005 /* RNSVGEllipse.m in Sources */,
A361E7761EB0C33D00646005 /* RNSVGPath.m in Sources */, A361E7761EB0C33D00646005 /* RNSVGPath.m in Sources */,
A361E7771EB0C33D00646005 /* RNSVGTextPath.m in Sources */, A361E7771EB0C33D00646005 /* RNSVGTextPath.m in Sources */,
9494C4BC1F472DDA00D5BCFD /* UIBezierPath+getTransformAtDistance.m in Sources */,
A361E7781EB0C33D00646005 /* RNSVGUse.m in Sources */, A361E7781EB0C33D00646005 /* RNSVGUse.m in Sources */,
A361E7791EB0C33D00646005 /* RNSVGLinearGradientManager.m in Sources */, A361E7791EB0C33D00646005 /* RNSVGLinearGradientManager.m in Sources */,
9494C5061F4B5BE800D5BCFD /* FontWeight.m in Sources */,
A361E77A1EB0C33D00646005 /* RNSVGText.m in Sources */, A361E77A1EB0C33D00646005 /* RNSVGText.m in Sources */,
A361E77B1EB0C33D00646005 /* RNSVGRectManager.m in Sources */, A361E77B1EB0C33D00646005 /* RNSVGRectManager.m in Sources */,
A361E77C1EB0C33D00646005 /* RNSVGRenderable.m in Sources */, A361E77C1EB0C33D00646005 /* RNSVGRenderable.m in Sources */,
@@ -567,19 +810,31 @@
A361E77F1EB0C33D00646005 /* RNSVGPainter.m in Sources */, A361E77F1EB0C33D00646005 /* RNSVGPainter.m in Sources */,
A361E7801EB0C33D00646005 /* RNSVGNode.m in Sources */, A361E7801EB0C33D00646005 /* RNSVGNode.m in Sources */,
A361E7811EB0C33D00646005 /* RNSVGClipPath.m in Sources */, A361E7811EB0C33D00646005 /* RNSVGClipPath.m in Sources */,
9494C5081F4B5BE800D5BCFD /* PropHelper.m in Sources */,
A361E7821EB0C33D00646005 /* RNSVGSvgViewManager.m in Sources */, A361E7821EB0C33D00646005 /* RNSVGSvgViewManager.m in Sources */,
9494C5411F4C44DD00D5BCFD /* TextLengthAdjust.m in Sources */,
9494C5261F4B605F00D5BCFD /* GlyphContext.m in Sources */,
A361E7831EB0C33D00646005 /* RNSVGSolidColorBrush.m in Sources */, A361E7831EB0C33D00646005 /* RNSVGSolidColorBrush.m in Sources */,
9494C5391F4C44DD00D5BCFD /* FontStyle.m in Sources */,
A361E7841EB0C33D00646005 /* RNSVGPathManager.m in Sources */, A361E7841EB0C33D00646005 /* RNSVGPathManager.m in Sources */,
A361E7851EB0C33D00646005 /* RNSVGBezierTransformer.m in Sources */, A361E7851EB0C33D00646005 /* RNSVGBezierTransformer.m in Sources */,
A361E7861EB0C33D00646005 /* RNSVGRenderableManager.m in Sources */, A361E7861EB0C33D00646005 /* RNSVGRenderableManager.m in Sources */,
A361E7871EB0C33D00646005 /* RNSVGRadialGradient.m in Sources */, A361E7871EB0C33D00646005 /* RNSVGRadialGradient.m in Sources */,
945A8AF91F4CE3E8004BBF6B /* AlignmentBaseline.m in Sources */,
A361E7881EB0C33D00646005 /* RNSVGRadialGradientManager.m in Sources */, A361E7881EB0C33D00646005 /* RNSVGRadialGradientManager.m in Sources */,
A361E7891EB0C33D00646005 /* RNSVGImageManager.m in Sources */, A361E7891EB0C33D00646005 /* RNSVGImageManager.m in Sources */,
A361E78A1EB0C33D00646005 /* RNSVGNodeManager.m in Sources */, A361E78A1EB0C33D00646005 /* RNSVGNodeManager.m in Sources */,
A361E78B1EB0C33D00646005 /* RNSVGSymbol.m in Sources */, A361E78B1EB0C33D00646005 /* RNSVGSymbol.m in Sources */,
9494C5431F4C44DD00D5BCFD /* TextPathMethod.m in Sources */,
A361E78C1EB0C33D00646005 /* RNSVGDefs.m in Sources */, A361E78C1EB0C33D00646005 /* RNSVGDefs.m in Sources */,
9494C53B1F4C44DD00D5BCFD /* FontVariantLigatures.m in Sources */,
9494C5001F4B5BE800D5BCFD /* FontData.m in Sources */,
9494C5491F4C44DD00D5BCFD /* TextPathSpacing.m in Sources */,
A361E78D1EB0C33D00646005 /* RNSVGLineManager.m in Sources */, A361E78D1EB0C33D00646005 /* RNSVGLineManager.m in Sources */,
9494C53D1F4C44DD00D5BCFD /* TextAnchor.m in Sources */,
9494C5471F4C44DD00D5BCFD /* TextPathSide.m in Sources */,
A361E78E1EB0C33D00646005 /* RNSVGCircle.m in Sources */, A361E78E1EB0C33D00646005 /* RNSVGCircle.m in Sources */,
9494C5451F4C44DD00D5BCFD /* TextPathMidLine.m in Sources */,
A361E78F1EB0C33D00646005 /* RNSVGEllipseManager.m in Sources */, A361E78F1EB0C33D00646005 /* RNSVGEllipseManager.m in Sources */,
A361E7901EB0C33D00646005 /* RCTConvert+RNSVG.m in Sources */, A361E7901EB0C33D00646005 /* RCTConvert+RNSVG.m in Sources */,
A361E7911EB0C33D00646005 /* RNSVGBrush.m in Sources */, A361E7911EB0C33D00646005 /* RNSVGBrush.m in Sources */,
@@ -642,6 +897,10 @@
IPHONEOS_DEPLOYMENT_TARGET = 7.0; IPHONEOS_DEPLOYMENT_TARGET = 7.0;
MTL_ENABLE_DEBUG_INFO = YES; MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES; ONLY_ACTIVE_ARCH = YES;
OTHER_LDFLAGS = (
"-ObjC++",
"-lstdc++",
);
SDKROOT = iphoneos; SDKROOT = iphoneos;
}; };
name = Debug; name = Debug;
@@ -680,6 +939,10 @@
); );
IPHONEOS_DEPLOYMENT_TARGET = 7.0; IPHONEOS_DEPLOYMENT_TARGET = 7.0;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
OTHER_LDFLAGS = (
"-ObjC++",
"-lstdc++",
);
SDKROOT = iphoneos; SDKROOT = iphoneos;
VALIDATE_PRODUCT = YES; VALIDATE_PRODUCT = YES;
}; };
@@ -688,12 +951,23 @@
0CF68AD61AF0540F00FF9E5C /* Debug */ = { 0CF68AD61AF0540F00FF9E5C /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_ENABLE_MODULE_DEBUGGING = YES;
FRAMEWORK_SEARCH_PATHS = "";
HEADER_SEARCH_PATHS = ( HEADER_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(BUILT_PRODUCTS_DIR)/usr/local/include", "$(BUILT_PRODUCTS_DIR)/usr/local/include",
./PerformanceBezier,
"./QuartzBookPack/**",
); );
OTHER_LDFLAGS = "-ObjC"; LIBRARY_SEARCH_PATHS = "";
OTHER_LDFLAGS = (
"-ObjC++",
"-lstdc++",
"-all_load",
"-ObjC",
);
PRELINK_LIBS = "";
PRODUCT_NAME = RNSVG; PRODUCT_NAME = RNSVG;
PUBLIC_HEADERS_FOLDER_PATH = /usr/local/include/; PUBLIC_HEADERS_FOLDER_PATH = /usr/local/include/;
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
@@ -703,12 +977,23 @@
0CF68AD71AF0540F00FF9E5C /* Release */ = { 0CF68AD71AF0540F00FF9E5C /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_ENABLE_MODULE_DEBUGGING = YES;
FRAMEWORK_SEARCH_PATHS = "";
HEADER_SEARCH_PATHS = ( HEADER_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(BUILT_PRODUCTS_DIR)/usr/local/include", "$(BUILT_PRODUCTS_DIR)/usr/local/include",
./PerformanceBezier,
"./QuartzBookPack/**",
); );
OTHER_LDFLAGS = "-ObjC"; LIBRARY_SEARCH_PATHS = "";
OTHER_LDFLAGS = (
"-ObjC++",
"-lstdc++",
"-all_load",
"-ObjC",
);
PRELINK_LIBS = "";
PRODUCT_NAME = RNSVG; PRODUCT_NAME = RNSVG;
PUBLIC_HEADERS_FOLDER_PATH = /usr/local/include/; PUBLIC_HEADERS_FOLDER_PATH = /usr/local/include/;
SKIP_INSTALL = YES; SKIP_INSTALL = YES;

View File

@@ -11,11 +11,13 @@
#import "RNSVGClipPath.h" #import "RNSVGClipPath.h"
#import "RNSVGGroup.h" #import "RNSVGGroup.h"
#import "RNSVGGlyphContext.h" #import "RNSVGGlyphContext.h"
#import "GlyphContext.h"
@implementation RNSVGNode @implementation RNSVGNode
{ {
RNSVGGroup *_textRoot; RNSVGGroup *_textRoot;
RNSVGGlyphContext *glyphContext; GlyphContext *glyphContext;
RNSVGGlyphContext *RNSVGGlyphContext;
BOOL _transparent; BOOL _transparent;
CGPathRef _cachedClipPath; CGPathRef _cachedClipPath;
RNSVGSvgView *_svgView; RNSVGSvgView *_svgView;
@@ -240,12 +242,20 @@ CGFloat const DEFAULT_FONT_SIZE = 12;
- (CGFloat)relativeOnWidth:(NSString *)length - (CGFloat)relativeOnWidth:(NSString *)length
{ {
return [RNSVGPercentageConverter stringToFloat:length relative:[self getContextWidth] offset:0]; return [PropHelper fromRelativeWithNSString:length
relative:[self getContextWidth]
offset:0
scale:1
fontSize:[self getFontSizeFromContext]];
} }
- (CGFloat)relativeOnHeight:(NSString *)length - (CGFloat)relativeOnHeight:(NSString *)length
{ {
return [RNSVGPercentageConverter stringToFloat:length relative:[self getContextHeight] offset:0]; return [PropHelper fromRelativeWithNSString:length
relative:[self getContextHeight]
offset:0
scale:1
fontSize:[self getFontSizeFromContext]];
} }
- (CGFloat)relativeOnOther:(NSString *)length - (CGFloat)relativeOnOther:(NSString *)length
@@ -255,25 +265,32 @@ CGFloat const DEFAULT_FONT_SIZE = 12;
CGFloat powX = width * width; CGFloat powX = width * width;
CGFloat powY = height * height; CGFloat powY = height * height;
CGFloat r = sqrt(powX + powY) * M_SQRT1_2l; CGFloat r = sqrt(powX + powY) * M_SQRT1_2l;
return [RNSVGPercentageConverter stringToFloat:length relative:r offset:0];} return [PropHelper fromRelativeWithNSString:length
relative:r
offset:0
scale:1
fontSize:[self getFontSizeFromContext]];
}
- (CGFloat)getContextWidth - (CGFloat)getContextWidth
{ {
RNSVGGroup * root = [self getTextRoot]; RNSVGGroup * root = [self getTextRoot];
if (root == nil) { GlyphContext * gc = [root getGlyphContext];
if (root == nil || gc == nil) {
return CGRectGetWidth([[self getSvgView] getContextBounds]); return CGRectGetWidth([[self getSvgView] getContextBounds]);
} else { } else {
return [[root getGlyphContext] getWidth]; return [gc getWidth];
} }
} }
- (CGFloat)getContextHeight - (CGFloat)getContextHeight
{ {
RNSVGGroup * root = [self getTextRoot]; RNSVGGroup * root = [self getTextRoot];
if (root == nil) { GlyphContext * gc = [root getGlyphContext];
if (root == nil || gc == nil) {
return CGRectGetHeight([[self getSvgView] getContextBounds]); return CGRectGetHeight([[self getSvgView] getContextBounds]);
} else { } else {
return [[root getGlyphContext] getHeight]; return [gc getHeight];
} }
} }

View File

@@ -0,0 +1,63 @@
#ifndef AlignmentBaseline_h
#define AlignmentBaseline_h
#import <Foundation/Foundation.h>
NS_ENUM(NSInteger, AlignmentBaseline) {
AlignmentBaselineBaseline,
AlignmentBaselineTextBottom,
AlignmentBaselineAlphabetic,
AlignmentBaselineIdeographic,
AlignmentBaselineMiddle,
AlignmentBaselineCentral,
AlignmentBaselineMathematical,
AlignmentBaselineTextTop,
AlignmentBaselineBottom,
AlignmentBaselineCenter,
AlignmentBaselineTop,
/*
SVG implementations may support the following aliases in order to support legacy content:
text-before-edge = text-top
text-after-edge = text-bottom
*/
AlignmentBaselineTextBeforeEdge,
AlignmentBaselineTextAfterEdge,
// SVG 1.1
AlignmentBaselineBeforeEdge,
AlignmentBaselineAfterEdge,
AlignmentBaselineHanging,
AlignmentBaselineDEFAULT = AlignmentBaselineBaseline
};
static NSString* const AlignmentBaselineStrings[] = {
@"baseline",
@"text-bottom",
@"alphabetic",
@"ideographic",
@"middle",
@"central",
@"mathematical",
@"text-top",
@"bottom",
@"center",
@"top",
@"text-before-edge",
@"text-after-edge",
@"before-edge",
@"after-edge",
@"hanging",
@"central",
@"mathematical",
@"text-top",
@"bottom",
@"center",
@"top",
nil
};
NSString* AlignmentBaselineToString( enum AlignmentBaseline fw );
enum AlignmentBaseline AlignmentBaselineFromString( NSString* s );
#endif /* AlignmentBaseline_h */

View File

@@ -0,0 +1,18 @@
#import "AlignmentBaseline.h"
NSString* AlignmentBaselineToString( enum AlignmentBaseline fw )
{
return AlignmentBaselineStrings[fw];
}
enum AlignmentBaseline AlignmentBaselineFromString( NSString* s )
{
NSInteger i;
NSString* fw;
for (i = 0; fw = AlignmentBaselineStrings[i], fw != nil; i++) {
if ([fw isEqualToString:s]) {
return i;
}
}
return AlignmentBaselineDEFAULT;
}

43
ios/Text/FontData.h Normal file
View File

@@ -0,0 +1,43 @@
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "FontStyle.h"
#import "FontVariantLigatures.h"
#import "FontWeight.h"
#import "PropHelper.h"
#import "TextAnchor.h"
#import "TextDecoration.h"
@interface FontData : NSObject {
@public
double fontSize;
NSString * fontSize_;
NSString *fontFamily;
enum FontStyle fontStyle;
NSString * fontStyle_;
NSDictionary * fontData;
enum FontWeight fontWeight;
NSString * fontWeight_;
NSString *fontFeatureSettings;
enum FontVariantLigatures fontVariantLigatures;
enum TextAnchor textAnchor;
enum TextDecoration textDecoration;
double kerning;
double wordSpacing;
double letterSpacing;
bool manualKerning;
}
+ (instancetype)Defaults;
+ (double)toAbsoluteWithNSString:(NSString *)string
scale:(double)scale
fontSize:(double)fontSize;
+ (instancetype)initWithNSDictionary:(NSDictionary *)font
parent:(FontData *)parent
scale:(double)scale;
@end
#define FontData_DEFAULT_FONT_SIZE 12.0

108
ios/Text/FontData.m Normal file
View File

@@ -0,0 +1,108 @@
#import "FontData.h"
#import "FontStyle.h"
#import "FontVariantLigatures.h"
#import "FontWeight.h"
#import "PropHelper.h"
#import "TextAnchor.h"
#import "TextDecoration.h"
#import "RNSVGNode.h"
#define DEFAULT_KERNING 0.0
#define DEFAULT_WORD_SPACING 0.0
#define DEFAULT_LETTER_SPACING 0.0
static NSString *KERNING = @"kerning";
static NSString *FONT_SIZE = @"fontSize";
static NSString *FONT_DATA = @"fontData";
static NSString *FONT_STYLE = @"fontStyle";
static NSString *FONT_WEIGHT = @"fontWeight";
static NSString *FONT_FAMILY = @"fontFamily";
static NSString *TEXT_ANCHOR = @"textAnchor";
static NSString *WORD_SPACING = @"wordSpacing";
static NSString *LETTER_SPACING = @"letterSpacing";
static NSString *TEXT_DECORATION = @"textDecoration";
static NSString *FONT_FEATURE_SETTINGS = @"fontFeatureSettings";
static NSString *FONT_VARIANT_LIGATURES = @"fontVariantLigatures";
FontData *FontData_Defaults;
@implementation FontData
+ (instancetype)Defaults {
if (!FontData_Defaults) {
FontData *self = [FontData alloc];
self->fontData = nil;
self->fontFamily = @"";
self->fontStyle = FontStyleNormal;
self->fontWeight = FontWeightNormal;
self->fontFeatureSettings = @"";
self->fontVariantLigatures = FontVariantLigaturesNormal;
self->textAnchor = TextAnchorStart;
self->textDecoration = TextDecorationNone;
self->manualKerning = false;
self->kerning = DEFAULT_KERNING;
self->fontSize = DEFAULT_FONT_SIZE;
self->wordSpacing = DEFAULT_WORD_SPACING;
self->letterSpacing = DEFAULT_LETTER_SPACING;
FontData_Defaults = self;
}
return FontData_Defaults;
}
+ (double)toAbsoluteWithNSString:(NSString *)string
scale:(double)scale
fontSize:(double)fontSize {
return [PropHelper fromRelativeWithNSString:string
relative:0
offset:0
scale:scale
fontSize:fontSize];
}
+ (instancetype)initWithNSDictionary:(NSDictionary *)font
parent:(FontData *)parent
scale:(double)scale {
FontData *data = [FontData alloc];
double parentFontSize = parent->fontSize;
if ([font objectForKey:FONT_SIZE]) {
NSString *string = [font objectForKey:FONT_SIZE];
data->fontSize = [PropHelper fromRelativeWithNSString:string
relative:parentFontSize
offset:0
scale:scale
fontSize:parentFontSize];
}
else {
data->fontSize = parentFontSize;
}
data->fontData = [font objectForKey:FONT_DATA] ? [font objectForKey:FONT_DATA] : parent->fontData;
data->fontFamily = [font objectForKey:FONT_FAMILY] ? [font objectForKey:FONT_FAMILY] : parent->fontFamily;
NSString* style = [font objectForKey:FONT_STYLE];
data->fontStyle = style ? FontStyleFromString(style) : parent->fontStyle;
NSString* weight = [font objectForKey:FONT_WEIGHT];
data->fontWeight = weight ? FontWeightFromString(weight) : parent->fontWeight;
NSString* feature = [font objectForKey:FONT_FEATURE_SETTINGS];
data->fontFeatureSettings = feature ? [font objectForKey:FONT_FEATURE_SETTINGS] : parent->fontFeatureSettings;
NSString* variant = [font objectForKey:FONT_VARIANT_LIGATURES];
data->fontVariantLigatures = variant ? FontVariantLigaturesFromString(variant) : parent->fontVariantLigatures;
NSString* anchor = [font objectForKey:TEXT_ANCHOR];
data->textAnchor = anchor ? TextAnchorFromString(anchor) : parent->textAnchor;
NSString* decoration = [font objectForKey:TEXT_DECORATION];
data->textDecoration = decoration ? TextDecorationFromString(decoration) : parent->textDecoration;
NSString* kerning = [font objectForKey:KERNING];
data->manualKerning = (kerning || parent->manualKerning );
data->kerning = kerning ? [FontData toAbsoluteWithNSString:kerning
scale:scale
fontSize:data->fontSize ] : parent->kerning;
NSString* wordSpacing = [font objectForKey:WORD_SPACING];
data->wordSpacing = wordSpacing ? [FontData toAbsoluteWithNSString:wordSpacing
scale:scale
fontSize:data->fontSize ] : parent->wordSpacing;
NSString* letterSpacing = [font objectForKey:LETTER_SPACING];
data->letterSpacing = letterSpacing ? [FontData toAbsoluteWithNSString:letterSpacing
scale:scale
fontSize:data->fontSize ] : parent->letterSpacing;
return data;
}
@end

19
ios/Text/FontStyle.h Normal file
View File

@@ -0,0 +1,19 @@
#import <Foundation/Foundation.h>
#if !defined (FontStyle_)
#define FontStyle_
NS_ENUM(NSInteger, FontStyle) {
FontStyleNormal,
FontStyleItalic,
FontStyleOblique,
FontStyleDEFAULT = FontStyleNormal,
};
static NSString* const FontStyleStrings[] = {@"normal", @"italic", @"oblique", nil};
NSString* FontStyleToString( enum FontStyle fw );
enum FontStyle FontStyleFromString( NSString* s );
#endif

18
ios/Text/FontStyle.m Normal file
View File

@@ -0,0 +1,18 @@
#import "FontStyle.h"
NSString* FontStyleToString( enum FontStyle fw )
{
return FontStyleStrings[fw];
}
enum FontStyle FontStyleFromString( NSString* s )
{
NSInteger i;
NSString* fw;
for (i = 0; fw = FontStyleStrings[i], fw != nil; i++) {
if ([fw isEqualToString:s]) {
return i;
}
}
return FontStyleDEFAULT;
}

View File

@@ -0,0 +1,18 @@
#import <Foundation/Foundation.h>
#if !defined (FontVariantLigatures_)
#define FontVariantLigatures_
NS_ENUM(NSInteger, FontVariantLigatures) {
FontVariantLigaturesNormal,
FontVariantLigaturesNone,
FontVariantLigaturesDEFAULT = FontVariantLigaturesNormal,
};
static NSString* const FontVariantLigaturesStrings[] = {@"normal", @"none", nil};
NSString* FontVariantLigaturesToString( enum FontVariantLigatures fw );
enum FontVariantLigatures FontVariantLigaturesFromString( NSString* s );
#endif

View File

@@ -0,0 +1,18 @@
#import "FontVariantLigatures.h"
NSString* FontVariantLigaturesToString( enum FontVariantLigatures fw )
{
return FontVariantLigaturesStrings[fw];
}
enum FontVariantLigatures FontVariantLigaturesFromString( NSString* s )
{
NSInteger i;
NSString* fw;
for (i = 0; fw = FontVariantLigaturesStrings[i], fw != nil; i++) {
if ([fw isEqualToString:s]) {
return i;
}
}
return FontVariantLigaturesDEFAULT;
}

30
ios/Text/FontWeight.h Normal file
View File

@@ -0,0 +1,30 @@
#import <Foundation/Foundation.h>
#if !defined (FontWeight_)
#define FontWeight_
NS_ENUM(NSInteger, FontWeight) {
FontWeightNormal,
FontWeightBold,
FontWeightBolder,
FontWeightLighter,
FontWeight100,
FontWeight200,
FontWeight300,
FontWeight400,
FontWeight500,
FontWeight600,
FontWeight700,
FontWeight800,
FontWeight900,
FontWeightDEFAULT = FontWeightNormal,
};
static NSString* const FontWeightStrings[] = {@"Normal", @"Bold", @"Bolder", @"Lighter", @"100", @"200", @"300", @"400", @"500", @"600", @"700", @"800", @"900", nil};
NSString* FontWeightToString( enum FontWeight fw );
enum FontWeight FontWeightFromString( NSString* s );
#endif

18
ios/Text/FontWeight.m Normal file
View File

@@ -0,0 +1,18 @@
#import "FontWeight.h"
NSString* FontWeightToString( enum FontWeight fw )
{
return FontWeightStrings[fw];
}
enum FontWeight FontWeightFromString( NSString* s )
{
NSInteger i;
NSString* fw;
for (i = 0; fw = FontWeightStrings[i], fw != nil; i++) {
if ([fw isEqualToString:s]) {
return i;
}
}
return FontWeightDEFAULT;
}

51
ios/Text/GlyphContext.h Normal file
View File

@@ -0,0 +1,51 @@
#import <React/UIView+React.h>
#import <CoreText/CoreText.h>
#import "FontData.h"
@class RNSVGText;
@class RNSVGGroup;
@class GlyphContext;
@interface GlyphContext : NSObject
- (CTFontRef)getGlyphFont;
- (instancetype)initWithScale:(float)scale_
width:(float)width
height:(float)height;
- (FontData *)getFont;
- (double)getFontSize;
- (float)getHeight;
- (float)getWidth;
- (double)nextDeltaX;
- (double)nextDeltaY;
- (NSNumber*)nextRotation;
- (double)nextXWithDouble:(double)advance;
- (double)nextY;
- (void)popContext;
- (void)pushContextwithRNSVGText:(RNSVGText *)node
reset:(bool)reset
font:(NSDictionary *)font
x:(NSArray*)x
y:(NSArray*)y
deltaX:(NSArray*)deltaX
deltaY:(NSArray*)deltaY
rotate:(NSArray*)rotate;
- (void)pushContextWithRNSVGGroup:(RNSVGGroup*)node
font:(NSDictionary *)font;
@end

437
ios/Text/GlyphContext.m Normal file
View File

@@ -0,0 +1,437 @@
//
// Generated by the J2ObjC translator. DO NOT EDIT!
// source: GlyphContext.java
//
#import "GlyphContext.h"
#import <React/RCTFont.h>
#import "RNSVGNode.h"
#import "PropHelper.h"
#import "FontData.h"
#import "RNSVGText.h"
@interface GlyphContext () {
@public
NSMutableArray *mFontContext_;
NSMutableArray *mXsContext_;
NSMutableArray *mYsContext_;
NSMutableArray *mDXsContext_;
NSMutableArray *mDYsContext_;
NSMutableArray *mRsContext_;
NSMutableArray *mXIndices_;
NSMutableArray *mYIndices_;
NSMutableArray *mDXIndices_;
NSMutableArray *mDYIndices_;
NSMutableArray *mRIndices_;
NSMutableArray *mXsIndices_;
NSMutableArray *mYsIndices_;
NSMutableArray *mDXsIndices_;
NSMutableArray *mDYsIndices_;
NSMutableArray *mRsIndices_;
double mFontSize_;
FontData *topFont_;
double mX_;
double mY_;
double mDX_;
double mDY_;
NSArray *mXs_;
NSArray *mYs_;
NSArray *mDXs_;
NSArray *mDYs_;
NSArray *mRs_;
int mXsIndex_;
int mYsIndex_;
int mDXsIndex_;
int mDYsIndex_;
int mRsIndex_;
int mXIndex_;
int mYIndex_;
int mDXIndex_;
int mDYIndex_;
int mRIndex_;
int mTop_;
float mScale_;
float mWidth_;
float mHeight_;
}
- (void)pushIndices;
- (void)reset;
- (FontData *)getTopOrParentFontWithRNSVGGroup:(RNSVGGroup *)child;
- (void)pushNodeAndFontWithRNSVGGroup:(RNSVGGroup *)node
withNSDictionary:(NSDictionary *)font;
+ (void)incrementIndicesWithNSArray:(NSArray *)indices
withInt:(int)topIndex;
- (void)pushContextwithRNSVGText:(RNSVGText *)node
reset:(bool)reset
font:(NSDictionary *)font
x:(NSArray*)x
y:(NSArray*)y
deltaX:(NSArray*)deltaX
deltaY:(NSArray*)deltaY
rotate:(NSArray*)rotate;
- (void)pushContextWithRNSVGGroup:(RNSVGGroup*)node
font:(NSDictionary *)font;
@end
@implementation GlyphContext
- (CTFontRef)getGlyphFont
{
NSString *fontFamily = topFont_->fontFamily;
NSNumber * fontSize = [NSNumber numberWithDouble:topFont_->fontSize];
NSString * fontWeight = topFont_->fontWeight_;
NSString * fontStyle = topFont_->fontStyle_;
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];
}
- (void)pushIndices {
GlyphContext_pushIndices(self);
}
- (instancetype)initWithScale:(float)scale_
width:(float)width
height:(float)height {
GlyphContext_initWithFloat_withFloat_withFloat_(self, scale_, width, height);
return self;
}
- (void)reset {
GlyphContext_reset(self);
}
- (FontData *)getFont {
return topFont_;
}
- (FontData *)getTopOrParentFontWithRNSVGGroup:(RNSVGGroup*)child {
return GlyphContext_getTopOrParentFontWithRNSVGGroup_(self, child);
}
- (void)pushNodeAndFontWithRNSVGGroup:(RNSVGGroup*)node
withNSDictionary:(NSDictionary*)font {
GlyphContext_pushNodeAndFontWithRNSVGGroup_withNSDictionary_(self, node, font);
}
- (void)pushContextWithRNSVGGroup:(RNSVGGroup*)node
font:(NSDictionary*)font {
GlyphContext_pushNodeAndFontWithRNSVGGroup_withNSDictionary_(self, node, font);
GlyphContext_pushIndices(self);
}
- (void)pushContextwithRNSVGText:(RNSVGText*)node
reset:(bool)reset
font:(NSDictionary*)font
x:(NSArray*)x
y:(NSArray*)y
deltaX:(NSArray*)deltaX
deltaY:(NSArray*)deltaY
rotate:(NSArray*)rotate {
if (reset) {
GlyphContext_reset(self);
}
GlyphContext_pushNodeAndFontWithRNSVGGroup_withNSDictionary_(self, (RNSVGGroup*)node, font);
if (x != nil && [x count] != 0) {
mXsIndex_++;
mXIndex_ = -1;
[mXIndices_ addObject:[NSNumber numberWithInteger:mXIndex_]];
mXs_ = x;
[mXsContext_ addObject:mXs_];
}
if (y != nil && [y count] != 0) {
mYsIndex_++;
mYIndex_ = -1;
[mYIndices_ addObject:[NSNumber numberWithInteger:mYIndex_]];
mYs_ = y;
[mYsContext_ addObject:mYs_];
}
if (deltaX != nil && [deltaX count] != 0) {
mDXsIndex_++;
mDXIndex_ = -1;
[mDXIndices_ addObject:[NSNumber numberWithInteger:mDXIndex_]];
mDXs_ = deltaX;
[mDXsContext_ addObject:mDXs_];
}
if (deltaY != nil && [deltaY count] != 0) {
mDYsIndex_++;
mDYIndex_ = -1;
[mDYIndices_ addObject:[NSNumber numberWithInteger:mDYIndex_]];
mDYs_ = deltaY;
[mDYsContext_ addObject:mDYs_];
}
if (rotate != nil && [rotate count] != 0) {
mRsIndex_++;
mRIndex_ = -1;
[mRIndices_ addObject:[NSNumber numberWithInteger:mRIndex_]];
mRs_ = rotate;
[mRsContext_ addObject:mRs_];
}
GlyphContext_pushIndices(self);
}
- (void)popContext {
[mFontContext_ removeLastObject];
[mXsIndices_ removeLastObject];
[mYsIndices_ removeLastObject];
[mDXsIndices_ removeLastObject];
[mDYsIndices_ removeLastObject];
[mRsIndices_ removeLastObject];
mTop_--;
int x = mXsIndex_;
int y = mYsIndex_;
int dx = mDXsIndex_;
int dy = mDYsIndex_;
int r = mRsIndex_;
topFont_ = [mFontContext_ lastObject];
mXsIndex_ = [[mXsIndices_ lastObject] intValue];
mYsIndex_ = [[mYsIndices_ lastObject] intValue];
mDXsIndex_ = [[mDXsIndices_ lastObject] intValue];
mDYsIndex_ = [[mDYsIndices_ lastObject] intValue];
mRsIndex_ = [[mRsIndices_ lastObject] intValue];
if (x != mXsIndex_) {
[mXsContext_ removeObjectAtIndex:x];
mXs_ = [mXsContext_ objectAtIndex:mXsIndex_];
mXIndex_ = [[mXIndices_ objectAtIndex:mXsIndex_] intValue];
}
if (y != mYsIndex_) {
[mYsContext_ removeObjectAtIndex:y];
mYs_ = [mYsContext_ objectAtIndex:mYsIndex_];
mYIndex_ = [[mYIndices_ objectAtIndex:mYsIndex_] intValue];
}
if (dx != mDXsIndex_) {
[mDXsContext_ removeObjectAtIndex:dx];
mDXs_ = [mDXsContext_ objectAtIndex:mDXsIndex_];
mDXIndex_ = [[mDXIndices_ objectAtIndex:mDXsIndex_] intValue];
}
if (dy != mDYsIndex_) {
[mDYsContext_ removeObjectAtIndex:dy];
mDYs_ = [mDYsContext_ objectAtIndex:mDYsIndex_];
mDYIndex_ = [[mDYIndices_ objectAtIndex:mDYsIndex_] intValue];
}
if (r != mRsIndex_) {
[mRsContext_ removeObjectAtIndex:r];
mRs_ = [mRsContext_ objectAtIndex:mRsIndex_];
mRIndex_ = [[mRIndices_ objectAtIndex:mRsIndex_] intValue];
}
}
+ (void)incrementIndicesWithNSArray:(NSMutableArray *)indices
withInt:(int)topIndex {
GlyphContext_incrementIndicesWithNSArray_withInt_(indices, topIndex);
}
- (double)getFontSize {
return mFontSize_;
}
- (double)nextXWithDouble:(double)advance {
GlyphContext_incrementIndicesWithNSArray_withInt_(mXIndices_, mXsIndex_);
int nextIndex = mXIndex_ + 1;
if (nextIndex < [mXs_ count]) {
mDX_ = 0;
mXIndex_ = nextIndex;
NSString *string = [mXs_ objectAtIndex:nextIndex];
mX_ = [PropHelper fromRelativeWithNSString:string
relative:mWidth_
offset:0
scale:mScale_
fontSize:mFontSize_];
}
mX_ += advance;
return mX_;
}
- (double)nextY {
GlyphContext_incrementIndicesWithNSArray_withInt_(mYIndices_, mYsIndex_);
int nextIndex = mYIndex_ + 1;
if (nextIndex < [mYs_ count]) {
mDY_ = 0;
mYIndex_ = nextIndex;
NSString *string = [mYs_ objectAtIndex:nextIndex];
mY_ = [PropHelper fromRelativeWithNSString:string
relative:mHeight_
offset:0
scale:mScale_
fontSize:mFontSize_];
}
return mY_;
}
- (double)nextDeltaX {
GlyphContext_incrementIndicesWithNSArray_withInt_(mDXIndices_, mDXsIndex_);
int nextIndex = mDXIndex_ + 1;
if (nextIndex < [mDXs_ count]) {
mDXIndex_ = nextIndex;
NSString *string = [mDXs_ objectAtIndex:nextIndex];
double val = [PropHelper fromRelativeWithNSString:string
relative:mWidth_
offset:0
scale:mScale_
fontSize:mFontSize_];
mDX_ += val;
}
return mDX_;
}
- (double)nextDeltaY {
GlyphContext_incrementIndicesWithNSArray_withInt_(mDYIndices_, mDYsIndex_);
int nextIndex = mDYIndex_ + 1;
if (nextIndex < [mDYs_ count]) {
mDYIndex_ = nextIndex;
NSString *string = [mDYs_ objectAtIndex:nextIndex];
double val = [PropHelper fromRelativeWithNSString:string
relative:mHeight_
offset:0
scale:mScale_
fontSize:mFontSize_];
mDY_ += val;
}
return mDY_;
}
- (NSNumber*)nextRotation {
GlyphContext_incrementIndicesWithNSArray_withInt_(mRIndices_, mRsIndex_);
int nextIndex = mRIndex_ + 1;
if (nextIndex < [mRs_ count]) {
mRIndex_ = nextIndex;
}
return mRs_[mRIndex_];
}
- (float)getWidth {
return mWidth_;
}
- (float)getHeight {
return mHeight_;
}
void GlyphContext_pushIndices(GlyphContext *self) {
[self->mXsIndices_ addObject:[NSNumber numberWithInteger:self->mXsIndex_]];
[self->mYsIndices_ addObject:[NSNumber numberWithInteger:self->mYsIndex_]];
[self->mDXsIndices_ addObject:[NSNumber numberWithInteger:self->mDXsIndex_]];
[self->mDYsIndices_ addObject:[NSNumber numberWithInteger:self->mDYsIndex_]];
[self->mRsIndices_ addObject:[NSNumber numberWithInteger:self->mRsIndex_]];
}
void GlyphContext_initWithFloat_withFloat_withFloat_(GlyphContext *self, float scale_, float width, float height) {
self->mFontContext_ = [[NSMutableArray alloc]init];
self->mXsContext_ = [[NSMutableArray alloc]init];
self->mYsContext_ = [[NSMutableArray alloc]init];
self->mDXsContext_ = [[NSMutableArray alloc]init];
self->mDYsContext_ = [[NSMutableArray alloc]init];
self->mRsContext_ = [[NSMutableArray alloc]init];
self->mXIndices_ = [[NSMutableArray alloc]init];
self->mYIndices_ = [[NSMutableArray alloc]init];
self->mDXIndices_ = [[NSMutableArray alloc]init];
self->mDYIndices_ = [[NSMutableArray alloc]init];
self->mRIndices_ = [[NSMutableArray alloc]init];
self->mXsIndices_ = [[NSMutableArray alloc]init];
self->mYsIndices_ = [[NSMutableArray alloc]init];
self->mDXsIndices_ = [[NSMutableArray alloc]init];
self->mDYsIndices_ = [[NSMutableArray alloc]init];
self->mRsIndices_ = [[NSMutableArray alloc]init];
self->mFontSize_ = FontData_DEFAULT_FONT_SIZE;
self->topFont_ = [FontData Defaults];
self->mXs_ = [[NSArray alloc]init];
self->mYs_ = [[NSArray alloc]init];
self->mDXs_ = [[NSArray alloc]init];
self->mDYs_ = [[NSArray alloc]init];
self->mRs_ = [[NSArray alloc]initWithObjects:@0, nil];
self->mXIndex_ = -1;
self->mYIndex_ = -1;
self->mDXIndex_ = -1;
self->mDYIndex_ = -1;
self->mRIndex_ = -1;
self->mScale_ = scale_;
self->mWidth_ = width;
self->mHeight_ = height;
[self->mXsContext_ addObject:self->mXs_];
[self->mYsContext_ addObject:self->mYs_];
[self->mDXsContext_ addObject:self->mDXs_];
[self->mDYsContext_ addObject:self->mDYs_];
[self->mRsContext_ addObject:self->mRs_];
[self->mXIndices_ addObject:[NSNumber numberWithInteger:self->mXIndex_]];
[self->mYIndices_ addObject:[NSNumber numberWithInteger:self->mYIndex_]];
[self->mDXIndices_ addObject:[NSNumber numberWithInteger:self->mDXIndex_]];
[self->mDYIndices_ addObject:[NSNumber numberWithInteger:self->mDYIndex_]];
[self->mRIndices_ addObject:[NSNumber numberWithInteger:self->mRIndex_]];
[self->mFontContext_ addObject:self->topFont_];
GlyphContext_pushIndices(self);
}
void GlyphContext_reset(GlyphContext *self) {
self->mXsIndex_ = self->mYsIndex_ = self->mDXsIndex_ = self->mDYsIndex_ = self->mRsIndex_ = 0;
self->mXIndex_ = self->mYIndex_ = self->mDXIndex_ = self->mDYIndex_ = self->mRIndex_ = -1;
self->mX_ = self->mY_ = self->mDX_ = self->mDY_ = 0;
}
FontData *GlyphContext_getTopOrParentFontWithRNSVGGroup_(GlyphContext *self, RNSVGGroup* child) {
if (self->mTop_ > 0) {
return self->topFont_;
}
else {
RNSVGGroup* parentRoot = [child getParentTextRoot];
FontData* Defaults = [FontData Defaults];
while (parentRoot != nil) {
FontData *map = [[parentRoot getGlyphContext] getFont];
if (map != Defaults) {
return map;
}
parentRoot = [parentRoot getParentTextRoot];
}
return Defaults;
}
}
void GlyphContext_pushNodeAndFontWithRNSVGGroup_withNSDictionary_(GlyphContext *self, RNSVGGroup* node, NSDictionary* font) {
FontData *parent = GlyphContext_getTopOrParentFontWithRNSVGGroup_(self, node);
self->mTop_++;
if (font == nil) {
[self->mFontContext_ addObject:parent];
return;
}
FontData *data = [FontData initWithNSDictionary:font
parent:parent
scale:self->mScale_];
self->mFontSize_ = data->fontSize;
[self->mFontContext_ addObject:data];
self->topFont_ = data;
}
void GlyphContext_incrementIndicesWithNSArray_withInt_(NSMutableArray *indices, int topIndex) {
for (int index = topIndex; index >= 0; index--) {
int xIndex = [[indices objectAtIndex:index] intValue];
[indices setObject:[NSNumber numberWithInteger:xIndex + 1] atIndexedSubscript:index];
}
}
@end

15
ios/Text/PropHelper.h Normal file
View File

@@ -0,0 +1,15 @@
#import <Foundation/Foundation.h>
#import <QuartzCore/QuartzCore.h>
#if !defined (PropHelper_)
#define PropHelper_
@interface PropHelper : NSObject
+ (double) fromRelativeWithNSString:(NSString *)length
relative:(double)relative
offset:(double)offset
scale:(double)scale
fontSize:(double)fontSize;
@end
#endif

49
ios/Text/PropHelper.m Normal file
View File

@@ -0,0 +1,49 @@
#include "PropHelper.h"
@implementation PropHelper
+ (double)fromRelativeWithNSString:(NSString *)length
relative:(double)relative
offset:(double)offset
scale:(double)scale
fontSize:(double)fontSize {
length = [length stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
NSUInteger stringLength = [length length];
NSInteger percentIndex = stringLength - 1;
if (stringLength == 0) {
return offset;
}
else if ([length characterAtIndex:percentIndex] == '%') {
return [[length substringWithRange:NSMakeRange(0, percentIndex)] doubleValue] / 100 * relative + offset;
}
else {
NSInteger twoLetterUnitIndex = stringLength - 2;
if (twoLetterUnitIndex > 0) {
NSString *lastTwo = [length substringFromIndex:twoLetterUnitIndex];
NSUInteger end = twoLetterUnitIndex;
double unit = 1;
if ([lastTwo isEqualToString:@"px"]) {
} else if ([lastTwo isEqualToString:@"em"]) {
unit = fontSize;
} else if ([lastTwo isEqualToString:@"pt"]) {
unit = 1.25;
} else if ([lastTwo isEqualToString:@"pc"]) {
unit = 15;
} else if ([lastTwo isEqualToString:@"mm"]) {
unit = 3.543307;
} else if ([lastTwo isEqualToString:@"cm"]) {
unit = 35.43307;
} else if ([lastTwo isEqualToString:@"in"]) {
unit = 90;
} else {
end = stringLength;
}
return [[length substringWithRange:NSMakeRange(0, end)] doubleValue] * unit * scale + offset;
} else {
return [length doubleValue] * scale + offset;
}
}
}
@end

View File

@@ -5,10 +5,17 @@
* This source code is licensed under the MIT-style license found in the * This source code is licensed under the MIT-style license found in the
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
#import <UIKit/UIKit.h>
#import <PerformanceBezier/PerformanceBezier.h>
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#import <CoreText/CoreText.h> #import <CoreText/CoreText.h>
#import "RNSVGText.h" #import "RNSVGText.h"
#import "TextPathSide.h"
#import "TextPathMethod.h"
#import "TextPathMidLine.h"
#import "TextPathSpacing.h"
#import "TextLengthAdjust.h"
#import "AlignmentBaseline.h"
@interface RNSVGTSpan : RNSVGText @interface RNSVGTSpan : RNSVGText

View File

@@ -5,17 +5,23 @@
* This source code is licensed under the MIT-style license found in the * This source code is licensed under the MIT-style license found in the
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
#import <PerformanceBezier/PerformanceBezier.h>
#import "RNSVGTSpan.h" #import "RNSVGTSpan.h"
#import "RNSVGBezierTransformer.h" #import "RNSVGBezierTransformer.h"
#import "RNSVGText.h" #import "RNSVGText.h"
#import "RNSVGTextPath.h" #import "RNSVGTextPath.h"
#import "UIBezierPath+Text.h"
#import "FontData.h"
@implementation RNSVGTSpan @implementation RNSVGTSpan
{ {
RNSVGBezierTransformer *_bezierTransformer; RNSVGBezierTransformer *_bezierTransformer;
CGFloat startOffset;
UIBezierPath *_path;
CGPathRef _cache; CGPathRef _cache;
CGFloat pathLength;
RNSVGTextPath * textPath;
RNSVGPath * textPathPath;
} }
- (void)setContent:(NSString *)content - (void)setContent:(NSString *)content
@@ -63,9 +69,6 @@
CGMutablePathRef path = CGPathCreateMutable(); CGMutablePathRef path = CGPathCreateMutable();
// append spacing
text = [text stringByAppendingString:@" "];
[self pushGlyphContext]; [self pushGlyphContext];
CTFontRef font = [self getFontFromContext]; CTFontRef font = [self getFontFromContext];
@@ -73,9 +76,9 @@
CFDictionaryRef attributes; CFDictionaryRef attributes;
if (font != nil) { if (font != nil) {
attributes = (__bridge CFDictionaryRef)@{ attributes = (__bridge CFDictionaryRef)@{
(NSString *)kCTFontAttributeName: (__bridge id)font, (NSString *)kCTFontAttributeName: (__bridge id)font,
(NSString *)kCTForegroundColorFromContextAttributeName: @YES (NSString *)kCTForegroundColorFromContextAttributeName: @YES
}; };
} else { } else {
attributes = (__bridge CFDictionaryRef)@{ attributes = (__bridge CFDictionaryRef)@{
(NSString *)kCTForegroundColorFromContextAttributeName: @YES (NSString *)kCTForegroundColorFromContextAttributeName: @YES
@@ -86,8 +89,8 @@
CFAttributedStringRef attrString = CFAttributedStringCreate(kCFAllocatorDefault, string, attributes); CFAttributedStringRef attrString = CFAttributedStringCreate(kCFAllocatorDefault, string, attributes);
CTLineRef line = CTLineCreateWithAttributedString(attrString); CTLineRef line = CTLineCreateWithAttributedString(attrString);
CGMutablePathRef linePath = [self getLinePath:line]; CGMutablePathRef linePath = [self getLinePath:line text:text font:font];
CGAffineTransform offset = CGAffineTransformMakeTranslation(0, _bezierTransformer ? 0 : CTFontGetSize(font) * 1.1); CGAffineTransform offset = CGAffineTransformIdentity;
CGPathAddPath(path, &offset, linePath); CGPathAddPath(path, &offset, linePath);
CGPathRelease(linePath); CGPathRelease(linePath);
@@ -101,11 +104,585 @@
return (CGPathRef)CFAutorelease(path); return (CGPathRef)CFAutorelease(path);
} }
- (CGMutablePathRef)getLinePath:(CTLineRef)line - (CGMutablePathRef)getLinePath:(CTLineRef)line text:(NSString *)str font:(CTFontRef)fontRef
{ {
CGMutablePathRef path = CGPathCreateMutable(); CGMutablePathRef path = CGPathCreateMutable();
CFArrayRef runs = CTLineGetGlyphRuns(line); CFArrayRef runs = CTLineGetGlyphRuns(line);
for (CFIndex i = 0; i < CFArrayGetCount(runs); i++) { GlyphContext* gc = [[self getTextRoot] getGlyphContext];
FontData* font = [gc getFont];
NSUInteger length = str.length;
NSUInteger n = length;
unichar characters[n];
CFStringGetCharacters((__bridge CFStringRef) str, CFRangeMake(0, n), characters);
CGGlyph glyphs[n];
CGSize glyph_advances[n];
CTFontGetGlyphsForCharacters(fontRef, characters, glyphs, n);
CTFontGetAdvancesForGlyphs(fontRef, kCTFontDefaultOrientation, glyphs, glyph_advances, n);
/*
*
* Three properties affect the space between characters and words:
*
* kerning indicates whether the user agent should adjust inter-glyph spacing
* based on kerning tables that are included in the relevant font
* (i.e., enable auto-kerning) or instead disable auto-kerning
* and instead set inter-character spacing to a specific length (typically, zero).
*
* letter-spacing indicates an amount of space that is to be added between text
* characters supplemental to any spacing due to the kerning property.
*
* word-spacing indicates the spacing behavior between words.
*
* Letter-spacing is applied after bidi reordering and is in addition to any word-spacing.
* Depending on the justification rules in effect, user agents may further increase
* or decrease the space between typographic character units in order to justify text.
*
* */
// TODO double kerning = font->kerning;
double wordSpacing = font->wordSpacing;
double letterSpacing = font->letterSpacing;
bool autoKerning = !font->manualKerning;
/*
11.1.2. Fonts and glyphs
A font consists of a collection of glyphs together with other information (collectively,
the font tables) necessary to use those glyphs to present characters on some visual medium.
The combination of the collection of glyphs and the font tables is called the font data.
A font may supply substitution and positioning tables that can be used by a formatter
(text shaper) to re-order, combine and position a sequence of glyphs to form one or more
composite glyphs.
The combining may be as simple as a ligature, or as complex as an indic syllable which
combines, usually with some re-ordering, multiple consonants and vowel glyphs.
The tables may be language dependent, allowing the use of language appropriate letter forms.
When a glyph, simple or composite, represents an indivisible unit for typesetting purposes,
it is know as a typographic character.
Ligatures are an important feature of advance text layout.
Some ligatures are discretionary while others (e.g. in Arabic) are required.
The following explicit rules apply to ligature formation:
Ligature formation should not be enabled when characters are in different DOM text nodes;
thus, characters separated by markup should not use ligatures.
Ligature formation should not be enabled when characters are in different text chunks.
Discretionary ligatures should not be used when the spacing between two characters is not
the same as the default space (e.g. when letter-spacing has a non-default value,
or text-align has a value of justify and text-justify has a value of distribute).
(See CSS Text Module Level 3, ([css-text-3]).
SVG attributes such as dx, textLength, and spacing (in textPath) that may reposition
typographic characters do not break discretionary ligatures.
If discretionary ligatures are not desired
they can be turned off by using the font-variant-ligatures property.
When the effective letter-spacing between two characters is not zero
(due to either justification or non-zero computed letter-spacing),
user agents should not apply optional ligatures.
https://www.w3.org/TR/css-text-3/#letter-spacing-property
*/
// TODO bool allowOptionalLigatures = letterSpacing == 0 && font->fontVariantLigatures == FontVariantLigaturesNormal;
/*
For OpenType fonts, discretionary ligatures include those enabled by
the liga, clig, dlig, hlig, and cala features;
required ligatures are found in the rlig feature.
https://svgwg.org/svg2-draft/text.html#FontsGlyphs
http://dev.w3.org/csswg/css-fonts/#propdef-font-feature-settings
https://www.microsoft.com/typography/otspec/featurelist.htm
https://www.microsoft.com/typography/otspec/featuretags.htm
https://www.microsoft.com/typography/otspec/features_pt.htm
https://www.microsoft.com/typography/otfntdev/arabicot/features.aspx
http://unifraktur.sourceforge.net/testcases/enable_opentype_features/
https://en.wikipedia.org/wiki/List_of_typographic_features
http://ilovetypography.com/OpenType/opentype-features.html
https://www.typotheque.com/articles/opentype_features_in_css
https://practice.typekit.com/lesson/caring-about-opentype-features/
http://stateofwebtype.com/
6.12. Low-level font feature settings control: the font-feature-settings property
Name: font-feature-settings
Value: normal | <feature-tag-value> #
Initial: normal
Applies to: all elements
Inherited: yes
Percentages: N/A
Media: visual
Computed value: as specified
Animatable: no
https://drafts.csswg.org/css-fonts-3/#default-features
7.1. Default features
For OpenType fonts, user agents must enable the default features defined in the OpenType
documentation for a given script and writing mode.
Required ligatures, common ligatures and contextual forms must be enabled by default
(OpenType features: rlig, liga, clig, calt),
along with localized forms (OpenType feature: locl),
and features required for proper display of composed characters and marks
(OpenType features: ccmp, mark, mkmk).
These features must always be enabled, even when the value of the font-variant and
font-feature-settings properties is normal.
Individual features are only disabled when explicitly overridden by the author,
as when font-variant-ligatures is set to no-common-ligatures.
TODO For handling complex scripts such as Arabic, Mongolian or Devanagari additional features
are required.
TODO For upright text within vertical text runs,
vertical alternates (OpenType feature: vert) must be enabled.
*/
// OpenType.js font data
// TODO NSDictionary * fontData = font->fontData;
/*
float advances[] = new float[length];
paint.getTextWidths(line, advances);
*/
/*
This would give both advances and textMeasure in one call / looping over the text
double textMeasure = paint.getTextRunAdvances(line, 0, length, 0, length, true, advances, 0);
*/
/*
Determine the startpoint-on-the-path for the first glyph using attribute startOffset
and property text-anchor.
For text-anchor:start, startpoint-on-the-path is the point
on the path which represents the point on the path which is startOffset distance
along the path from the start of the path, calculated using the user agent's distance
along the path algorithm.
For text-anchor:middle, startpoint-on-the-path is the point
on the path which represents the point on the path which is [ startOffset minus half
of the total advance values for all of the glyphs in the textPath element ] distance
along the path from the start of the path, calculated using the user agent's distance
along the path algorithm.
For text-anchor:end, startpoint-on-the-path is the point on
the path which represents the point on the path which is [ startOffset minus the
total advance values for all of the glyphs in the textPath element ].
Before rendering the first glyph, the horizontal component of the startpoint-on-the-path
is adjusted to take into account various horizontal alignment text properties and
attributes, such as a dx attribute value on a tspan element.
*/
enum TextAnchor textAnchor = font->textAnchor;
CGRect textBounds = CTLineGetBoundsWithOptions(line, 0);
double textMeasure = CGRectGetWidth(textBounds);
double offset = getTextAnchorOffset(textAnchor, textMeasure);
bool hasTextPath = _path != nil;
bool isClosed = false;
int side = 1;
double startOfRendering = 0;
double endOfRendering = pathLength;
double fontSize = [gc getFontSize];
bool sharpMidLine = false;
if (hasTextPath) {
sharpMidLine = TextPathMidLineFromString([textPath midLine]) == TextPathMidLineSharp;
/*
Name
side
Value
left | right
initial value
left
Animatable
yes
Determines the side of the path the text is placed on
(relative to the path direction).
Specifying a value of right effectively reverses the path.
Added in SVG 2 to allow text either inside or outside closed subpaths
and basic shapes (e.g. rectangles, circles, and ellipses).
Adding 'side' was resolved at the Sydney (2015) meeting.
*/
side = TextPathSideFromString([textPath side]) == TextPathSideRight ? -1 : 1;
/*
Name
startOffset
Value
<length> | <percentage> | <number>
initial value
0
Animatable
yes
An offset from the start of the path for the initial current text position,
calculated using the user agent's distance along the path algorithm,
after converting the path to the textPath element's coordinate system.
If a <length> other than a percentage is given, then the startOffset
represents a distance along the path measured in the current user coordinate
system for the textPath element.
If a percentage is given, then the startOffset represents a percentage
distance along the entire path. Thus, startOffset="0%" indicates the start
point of the path and startOffset="100%" indicates the end point of the path.
Negative values and values larger than the path length (e.g. 150%) are allowed.
Any typographic characters with mid-points that are not on the path are not rendered
For paths consisting of a single closed subpath (including an equivalent path for a
basic shape), typographic characters are rendered along one complete circuit of the
path. The text is aligned as determined by the text-anchor property to a position
along the path set by the startOffset attribute.
For the start (end) value, the text is rendered from the start (end) of the line
until the initial position along the path is reached again.
For the middle, the text is rendered from the middle point in both directions until
a point on the path equal distance in both directions from the initial position on
the path is reached.
*/
double absoluteStartOffset = [PropHelper fromRelativeWithNSString:textPath.startOffset
relative:pathLength
offset:0
scale:1
fontSize:fontSize];
offset += absoluteStartOffset;
if (isClosed) {
double halfPathDistance = pathLength / 2;
startOfRendering = absoluteStartOffset + (textAnchor == TextAnchorMiddle ? -halfPathDistance : 0);
endOfRendering = startOfRendering + pathLength;
}
/*
TextPathSpacing spacing = textPath.getSpacing();
if (spacing == TextPathSpacing.auto) {
// Hmm, what to do here?
// https://svgwg.org/svg2-draft/text.html#TextPathElementSpacingAttribute
}
*/
}
/*
Name
method
Value
align | stretch
initial value
align
Animatable
yes
Indicates the method by which text should be rendered along the path.
A value of align indicates that the typographic character should be rendered using
simple 2×3 matrix transformations such that there is no stretching/warping of the
typographic characters. Typically, supplemental rotation, scaling and translation
transformations are done for each typographic characters to be rendered.
As a result, with align, in fonts where the typographic characters are designed to be
connected (e.g., cursive fonts), the connections may not align properly when text is
rendered along a path.
A value of stretch indicates that the typographic character outlines will be converted
into paths, and then all end points and control points will be adjusted to be along the
perpendicular vectors from the path, thereby stretching and possibly warping the glyphs.
With this approach, connected typographic characters, such as in cursive scripts,
will maintain their connections. (Non-vertical straight path segments should be
converted to Bézier curves in such a way that horizontal straight paths have an
(approximately) constant offset from the path along which the typographic characters
are rendered.)
TODO implement stretch
*/
/*
Name Value Initial value Animatable
textLength <length> | <percentage> | <number> See below yes
The author's computation of the total sum of all of the advance values that correspond
to character data within this element, including the advance value on the glyph
(horizontal or vertical), the effect of properties letter-spacing and word-spacing and
adjustments due to attributes dx and dy on this text or tspan element or any
descendants. This value is used to calibrate the user agent's own calculations with
that of the author.
The purpose of this attribute is to allow the author to achieve exact alignment,
in visual rendering order after any bidirectional reordering, for the first and
last rendered glyphs that correspond to this element; thus, for the last rendered
character (in visual rendering order after any bidirectional reordering),
any supplemental inter-character spacing beyond normal glyph advances are ignored
(in most cases) when the user agent determines the appropriate amount to expand/compress
the text string to fit within a length of textLength.
If attribute textLength is specified on a given element and also specified on an
ancestor, the adjustments on all character data within this element are controlled by
the value of textLength on this element exclusively, with the possible side-effect
that the adjustment ratio for the contents of this element might be different than the
adjustment ratio used for other content that shares the same ancestor. The user agent
must assume that the total advance values for the other content within that ancestor is
the difference between the advance value on that ancestor and the advance value for
this element.
This attribute is not intended for use to obtain effects such as shrinking or
expanding text.
A negative value is an error (see Error processing).
The textLength attribute is only applied when the wrapping area is not defined by the
TODO shape-inside or the inline-size properties. It is also not applied for any text or
TODO tspan element that has forced line breaks (due to a white-space value of pre or
pre-line).
If the attribute is not specified anywhere within a text element, the effect is as if
the author's computation exactly matched the value calculated by the user agent;
thus, no advance adjustments are made.
*/
double scaleSpacingAndGlyphs = 1;
NSString *mTextLength = [self textLength];
enum TextLengthAdjust mLengthAdjust = TextLengthAdjustFromString([self lengthAdjust]);
// TODO canvasWidth
double canvasWidth = 0;
if (mTextLength != nil) {
double author = [PropHelper fromRelativeWithNSString:mTextLength
relative:canvasWidth
offset:0
scale:1
fontSize:fontSize];
if (author < 0) {
NSException *e = [NSException
exceptionWithName:@"NegativeTextLength"
reason:@"Negative textLength value"
userInfo:nil];
@throw e;
}
switch (mLengthAdjust) {
default:
case TextLengthAdjustSpacing:
letterSpacing += (author - textMeasure) / (length - 1);
break;
case TextLengthAdjustSpacingAndGlyphs:
scaleSpacingAndGlyphs = author / textMeasure;
break;
}
}
double scaledDirection = scaleSpacingAndGlyphs * side;
/*
https://developer.mozilla.org/en/docs/Web/CSS/vertical-align
https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6bsln.html
https://www.microsoft.com/typography/otspec/base.htm
http://apike.ca/prog_svg_text_style.html
https://www.w3schools.com/tags/canvas_textbaseline.asp
http://vanseodesign.com/web-design/svg-text-baseline-alignment/
https://iamvdo.me/en/blog/css-font-metrics-line-height-and-vertical-align
https://tympanus.net/codrops/css_reference/vertical-align/
https://svgwg.org/svg2-draft/text.html#AlignmentBaselineProperty
11.10.2.6. The alignment-baseline property
This property is defined in the CSS Line Layout Module 3 specification. See 'alignment-baseline'. [css-inline-3]
https://drafts.csswg.org/css-inline/#propdef-alignment-baseline
The vertical-align property shorthand should be preferred in new content.
SVG 2 introduces some changes to the definition of this property.
In particular: the values 'auto', 'before-edge', and 'after-edge' have been removed.
For backwards compatibility, 'text-before-edge' should be mapped to 'text-top' and
'text-after-edge' should be mapped to 'text-bottom'.
Neither 'text-before-edge' nor 'text-after-edge' should be used with the vertical-align property.
*/
//Paint.FontMetrics fm = paint.getFontMetrics();
double top = 0;//-fm.top;
double bottom = 0;//fm.bottom;
double ascenderHeight = 0;//-fm.ascent;
double descenderDepth = 0;//fm.descent;
double totalHeight = top + bottom;
double baselineShift = 0;
NSString *baselineShiftString = [self baselineShift];
enum AlignmentBaseline baseline = AlignmentBaselineFromString([self alignmentBaseline]);
if (baseline != AlignmentBaselineBaseline) {
// TODO alignment-baseline, test / verify behavior
// TODO get per glyph baselines from font baseline table, for high-precision alignment
switch (baseline) {
// https://wiki.apache.org/xmlgraphics-fop/LineLayout/AlignmentHandling
default:
case AlignmentBaselineBaseline:
// Use the dominant baseline choice of the parent.
// Match the boxs corresponding baseline to that of its parent.
baselineShift = 0;
break;
case AlignmentBaselineTextBottom:
case AlignmentBaselineAfterEdge:
case AlignmentBaselineTextAfterEdge:
// Match the bottom of the box to the bottom of the parents content area.
// text-after-edge = text-bottom
// text-after-edge = descender depth
baselineShift = -descenderDepth;
break;
case AlignmentBaselineAlphabetic:
// Match the boxs alphabetic baseline to that of its parent.
// alphabetic = 0
baselineShift = 0;
break;
case AlignmentBaselineIdeographic:
// Match the boxs ideographic character face under-side baseline to that of its parent.
// ideographic = descender depth
baselineShift = -descenderDepth;
break;
case AlignmentBaselineMiddle:
// Align the vertical midpoint of the box with the baseline of the parent box plus half the x-height of the parent. TODO
// middle = x height / 2
//Rect bounds = new Rect();
// this will just retrieve the bounding rect for 'x'
//paint.getTextBounds("x", 0, 1, bounds);
//int xHeight = bounds.height();
//baselineShift = xHeight / 2;
break;
case AlignmentBaselineCentral:
// Match the boxs central baseline to the central baseline of its parent.
// central = (ascender height - descender depth) / 2
baselineShift = (ascenderHeight - descenderDepth) / 2;
break;
case AlignmentBaselineMathematical:
// Match the boxs mathematical baseline to that of its parent.
// Hanging and mathematical baselines
// There are no obvious formulas to calculate the position of these baselines.
// At the time of writing FOP puts the hanging baseline at 80% of the ascender
// height and the mathematical baseline at 50%.
baselineShift = 0.5 * ascenderHeight;
break;
case AlignmentBaselineHanging:
baselineShift = 0.8 * ascenderHeight;
break;
case AlignmentBaselineTextTop:
case AlignmentBaselineBeforeEdge:
case AlignmentBaselineTextBeforeEdge:
// Match the top of the box to the top of the parents content area.
// text-before-edge = text-top
// text-before-edge = ascender height
baselineShift = ascenderHeight;
break;
case AlignmentBaselineBottom:
// Align the top of the aligned subtree with the top of the line box.
baselineShift = bottom;
break;
case AlignmentBaselineCenter:
// Align the center of the aligned subtree with the center of the line box.
baselineShift = totalHeight / 2;
break;
case AlignmentBaselineTop:
// Align the bottom of the aligned subtree with the bottom of the line box.
baselineShift = top;
break;
}
/*
2.2.2. Alignment Shift: baseline-shift longhand
This property specifies by how much the box is shifted up from its alignment point.
It does not apply when alignment-baseline is top or bottom.
Authors should use the vertical-align shorthand instead of this property.
Values have the following meanings:
<length>
Raise (positive value) or lower (negative value) by the specified length.
<percentage>
Raise (positive value) or lower (negative value) by the specified percentage of the line-height.
TODO sub
Lower by the offset appropriate for subscripts of the parents box.
(The UA should use the parents font data to find this offset whenever possible.)
TODO super
Raise by the offset appropriate for superscripts of the parents box.
(The UA should use the parents font data to find this offset whenever possible.)
User agents may additionally support the keyword baseline as computing to 0
if is necessary for them to support legacy SVG content.
Issue: We would prefer to remove this,
and are looking for feedback from SVG user agents as to whether its necessary.
https://www.w3.org/TR/css-inline-3/#propdef-baseline-shift
*/
if (baselineShiftString != nil) {
switch (baseline) {
case AlignmentBaselineTop:
case AlignmentBaselineBottom:
break;
default:
if ([baselineShiftString isEqualToString:@"sub"]) {
// TODO
/*
if (fontData != nil && fontData.hasKey("tables") && fontData.hasKey("unitsPerEm")) {
int unitsPerEm = fontData.getInt("unitsPerEm");
ReadableMap tables = fontData.getMap("tables");
if (tables.hasKey("os2")) {
ReadableMap os2 = tables.getMap("os2");
if (os2.hasKey("ySubscriptYOffset")) {
double subOffset = os2.getDouble("ySubscriptYOffset");
baselineShift += fontSize * subOffset / unitsPerEm;
}
}
}
*/
} else if ([baselineShiftString isEqualToString:@"super"]) {
// TODO
/*
if (fontData != nil && fontData.hasKey("tables") && fontData.hasKey("unitsPerEm")) {
int unitsPerEm = fontData.getInt("unitsPerEm");
ReadableMap tables = fontData.getMap("tables");
if (tables.hasKey("os2")) {
ReadableMap os2 = tables.getMap("os2");
if (os2.hasKey("ySuperscriptYOffset")) {
double superOffset = os2.getDouble("ySuperscriptYOffset");
baselineShift -= fontSize * superOffset / unitsPerEm;
}
}
}
*/
} else if ([baselineShiftString isEqualToString:@"baseline"]) {
} else {
baselineShift -= [PropHelper fromRelativeWithNSString:baselineShiftString
relative:fontSize
offset:0
scale:1
fontSize:fontSize];
}
break;
}
}
}
CFIndex runEnd = CFArrayGetCount(runs);
for (CFIndex i = 0; i < runEnd; i++) {
CTRunRef run = CFArrayGetValueAtIndex(CTLineGetGlyphRuns(line), i); CTRunRef run = CFArrayGetValueAtIndex(CTLineGetGlyphRuns(line), i);
CFIndex runGlyphCount = CTRunGetGlyphCount(run); CFIndex runGlyphCount = CTRunGetGlyphCount(run);
@@ -117,31 +694,89 @@
CTRunGetGlyphs(run, CFRangeMake(0, 0), glyphs); CTRunGetGlyphs(run, CFRangeMake(0, 0), glyphs);
CTFontRef runFont = CFDictionaryGetValue(CTRunGetAttributes(run), kCTFontAttributeName); CTFontRef runFont = CFDictionaryGetValue(CTRunGetAttributes(run), kCTFontAttributeName);
CGPoint glyphPoint;
for(CFIndex i = 0; i < runGlyphCount; i++) { for(CFIndex i = 0; i < runGlyphCount; i++) {
CGPathRef letter = CTFontCreatePathForGlyph(runFont, glyphs[i], nil); CGPathRef letter = CTFontCreatePathForGlyph(runFont, glyphs[i], nil);
glyphPoint = [self getGlyphPointFromContext:positions[i] glyphWidth:CGRectGetWidth(CGPathGetBoundingBox(letter))]; /*
Determine the glyph's charwidth (i.e., the amount which the current text position
advances horizontally when the glyph is drawn using horizontal text layout).
*/
CGFloat charWidth = glyph_advances[i].width * scaleSpacingAndGlyphs;
//CGPoint glyphPoint = [self getGlyphPointFromContext:positions[i] glyphWidth:CGRectGetWidth(CGPathGetBoundingBox(letter))];
CGAffineTransform textPathTransform = CGAffineTransformIdentity; /*
CGAffineTransform transform; For each subsequent glyph, set a new startpoint-on-the-path as the previous
if (_bezierTransformer) { endpoint-on-the-path, but with appropriate adjustments taking into account
textPathTransform = [_bezierTransformer getTransformAtDistance:glyphPoint.x]; horizontal kerning tables in the font and current values of various attributes
if ([self textPathHasReachedEnd]) { and properties, including spacing properties (e.g. letter-spacing and word-spacing)
CGPathRelease(letter); and tspan elements with values provided for attributes dx and dy. All
break; adjustments are calculated as distance adjustments along the path, calculated
} else if (![self textPathHasReachedStart]) { using the user agent's distance along the path algorithm.
CGPathRelease(letter); */
if (autoKerning) {
// TODO double kerned = advances[index] * scaleSpacingAndGlyphs;
//kerning = kerned - charWidth;
}
bool isWordSeparator = false;
double wordSpace = isWordSeparator ? wordSpacing : 0;
double spacing = wordSpace + letterSpacing;
double advance = charWidth + spacing;
double x = [gc nextXWithDouble:charWidth];
double y = [gc nextY];
double dx = [gc nextDeltaX];
double dy = [gc nextDeltaY];
NSNumber* r = [gc nextRotation];
advance *= side;
charWidth *= side;
double cursor = offset + (x + dx) * side;
double startPoint = cursor - advance;
CGAffineTransform transform = CGAffineTransformIdentity;
if (hasTextPath) {
/*
Determine the point on the curve which is charwidth distance along the path from
the startpoint-on-the-path for this glyph, calculated using the user agent's
distance along the path algorithm. This point is the endpoint-on-the-path for
the glyph.
*/
// TODO double endPoint = startPoint + charWidth;
/*
Determine the midpoint-on-the-path, which is the point on the path which is
"halfway" (user agents can choose either a distance calculation or a parametric
calculation) between the startpoint-on-the-path and the endpoint-on-the-path.
*/
double halfWay = charWidth / 2;
double midPoint = startPoint + halfWay;
// Glyphs whose midpoint-on-the-path are off the path are not rendered.
if (midPoint > endOfRendering) {
continue;
} else if (midPoint < startOfRendering) {
continue; continue;
} }
textPathTransform = CGAffineTransformConcat(CGAffineTransformMakeTranslation(0, glyphPoint.y), textPathTransform); CGPoint slope;
transform = CGAffineTransformScale(textPathTransform, 1.0, -1.0); CGFloat percentConsumed = midPoint / pathLength;
CGPoint mid = [_path pointAtPercent:percentConsumed withSlope:&slope];
// Calculate the rotation
double angle = atan(slope.y / slope.x); // + M_PI;
if (slope.x < 0) angle += M_PI; // going left, update the angle
transform = CGAffineTransformConcat(CGAffineTransformMakeTranslation(mid.x, mid.y), transform);
transform = CGAffineTransformConcat(CGAffineTransformMakeRotation(angle + [r doubleValue]), transform);
transform = CGAffineTransformScale(transform, scaledDirection, side);
transform = CGAffineTransformConcat(CGAffineTransformMakeTranslation(-halfWay, dy + baselineShift), transform);
transform = CGAffineTransformConcat(CGAffineTransformMakeTranslation(0, y), transform);
} else { } else {
transform = CGAffineTransformTranslate(CGAffineTransformMakeScale(1.0, -1.0), glyphPoint.x, -glyphPoint.y); transform = CGAffineTransformConcat(CGAffineTransformMakeRotation([r doubleValue]), transform);
transform = CGAffineTransformMakeTranslation(startPoint, y + dy);
} }
transform = CGAffineTransformScale(transform, 1.0, -1.0);
CGPathAddPath(path, &transform, letter); CGPathAddPath(path, &transform, letter);
CGPathRelease(letter); CGPathRelease(letter);
} }
@@ -151,17 +786,37 @@
return path; return path;
} }
CGFloat getTextAnchorOffset(enum TextAnchor textAnchor, CGFloat width)
{
switch (textAnchor) {
case TextAnchorStart:
return 0;
case TextAnchorMiddle:
return -width / 2;
case TextAnchorEnd:
return -width;
}
return 0;
}
- (void)setupTextPath:(CGContextRef)context - (void)setupTextPath:(CGContextRef)context
{ {
_path = nil;
textPath = nil;
textPathPath = nil;
__block RNSVGBezierTransformer *bezierTransformer; __block RNSVGBezierTransformer *bezierTransformer;
[self traverseTextSuperviews:^(__kindof RNSVGText *node) { [self traverseTextSuperviews:^(__kindof RNSVGText *node) {
if ([node class] == [RNSVGTextPath class]) { if ([node class] == [RNSVGTextPath class]) {
bezierTransformer = [(RNSVGTextPath*)node getBezierTransformer]; textPath = (RNSVGTextPath*) node;
bezierTransformer = [textPath getBezierTransformer];
textPathPath = [textPath getPath];
_path = [UIBezierPath bezierPathWithCGPath:[textPathPath getPath:nil]];
pathLength = [_path length];
return NO; return NO;
} }
return YES; return YES;
}]; }];
_bezierTransformer = bezierTransformer; _bezierTransformer = bezierTransformer;
} }

View File

@@ -12,15 +12,18 @@
@interface RNSVGText : RNSVGGroup @interface RNSVGText : RNSVGGroup
@property (nonatomic, assign) RNSVGTextAnchor textAnchor; @property (nonatomic, strong) NSString *textLength;
@property (nonatomic, strong) NSString *baselineShift;
@property (nonatomic, strong) NSString *lengthAdjust;
@property (nonatomic, strong) NSString *alignmentBaseline;
@property (nonatomic, strong) NSArray<NSString *> *deltaX; @property (nonatomic, strong) NSArray<NSString *> *deltaX;
@property (nonatomic, strong) NSArray<NSString *> *deltaY; @property (nonatomic, strong) NSArray<NSString *> *deltaY;
@property (nonatomic, strong) NSArray<NSString *> *positionX; @property (nonatomic, strong) NSArray<NSString *> *positionX;
@property (nonatomic, strong) NSArray<NSString *> *positionY; @property (nonatomic, strong) NSArray<NSString *> *positionY;
@property (nonatomic, strong) NSArray<NSString *> *rotate;
- (void)releaseCachedPath; - (void)releaseCachedPath;
- (CGPathRef)getGroupPath:(CGContextRef)context; - (CGPathRef)getGroupPath:(CGContextRef)context;
- (CTFontRef)getFontFromContext; - (CTFontRef)getFontFromContext;
- (CGPoint)getGlyphPointFromContext:(CGPoint)offset glyphWidth:(CGFloat)glyphWidth; - (CGPoint)getGlyphPointFromContext:(CGPoint)offset glyphWidth:(CGFloat)glyphWidth;

View File

@@ -11,17 +11,13 @@
#import <React/RCTFont.h> #import <React/RCTFont.h>
#import <CoreText/CoreText.h> #import <CoreText/CoreText.h>
#import "RNSVGGlyphContext.h" #import "RNSVGGlyphContext.h"
#import "GlyphContext.h"
@implementation RNSVGText @implementation RNSVGText
{ {
RNSVGText *_textRoot; RNSVGText *_textRoot;
RNSVGGlyphContext *_glyphContext; GlyphContext *_glyphContext;
} RNSVGGlyphContext *_RNSVGGlyphContext;
- (void)setTextAnchor:(RNSVGTextAnchor)textAnchor
{
[self invalidate];
_textAnchor = textAnchor;
} }
- (void)renderLayerTo:(CGContextRef)context - (void)renderLayerTo:(CGContextRef)context
@@ -31,21 +27,20 @@
[self setupGlyphContext:context]; [self setupGlyphContext:context];
CGPathRef path = [self getGroupPath:context]; CGPathRef path = [self getGroupPath:context];
CGAffineTransform transform = [self getAlignTransform:path];
CGContextConcatCTM(context, transform);
[self renderGroupTo:context]; [self renderGroupTo:context];
[self releaseCachedPath]; [self releaseCachedPath];
CGContextRestoreGState(context); CGContextRestoreGState(context);
CGPathRef transformedPath = CGPathCreateCopyByTransformingPath(path, &CGAffineTransformIdentity);
CGPathRef transformedPath = CGPathCreateCopyByTransformingPath(path, &transform);
[self setHitArea:transformedPath]; [self setHitArea:transformedPath];
CGPathRelease(transformedPath); CGPathRelease(transformedPath);
} }
- (void)setupGlyphContext:(CGContextRef)context - (void)setupGlyphContext:(CGContextRef)context
{ {
_glyphContext = [[RNSVGGlyphContext alloc] initWithDimensions:[self getContextWidth] _glyphContext = [[GlyphContext alloc] initWithScale:1 width:[self getContextWidth]
height:[self getContextHeight]];
_RNSVGGlyphContext = [[RNSVGGlyphContext alloc] initWithDimensions:[self getContextWidth]
height:[self getContextHeight]]; height:[self getContextHeight]];
} }
@@ -72,10 +67,9 @@
{ {
[self setupGlyphContext:context]; [self setupGlyphContext:context];
CGPathRef groupPath = [self getGroupPath:context]; CGPathRef groupPath = [self getGroupPath:context];
CGAffineTransform transform = [self getAlignTransform:groupPath];
[self releaseCachedPath]; [self releaseCachedPath];
return (CGPathRef)CFAutorelease(CGPathCreateCopyByTransformingPath(groupPath, &transform)); return (CGPathRef)CFAutorelease(CGPathCreateCopyByTransformingPath(groupPath, &CGAffineTransformIdentity));
} }
- (void)renderGroupTo:(CGContextRef)context - (void)renderGroupTo:(CGContextRef)context
@@ -85,38 +79,6 @@
[self popGlyphContext]; [self popGlyphContext];
} }
- (CGAffineTransform)getAlignTransform:(CGPathRef)path
{
CGFloat width = CGRectGetWidth(CGPathGetBoundingBox(path));
CGFloat x = 0;
switch ([self getComputedTextAnchor]) {
case kRNSVGTextAnchorMiddle:
x = -width / 2;
break;
case kRNSVGTextAnchorEnd:
x = -width;
break;
default: ;
}
return CGAffineTransformMakeTranslation(x, 0);
}
- (RNSVGTextAnchor)getComputedTextAnchor
{
RNSVGTextAnchor anchor = self.textAnchor;
if (self.subviews.count > 0) {
RNSVGText *child = [self.subviews firstObject];
while (child.subviews.count && anchor == kRNSVGTextAnchorAuto) {
anchor = child.textAnchor;
child = [child.subviews firstObject];
}
}
return anchor;
}
- (RNSVGText *)getTextRoot - (RNSVGText *)getTextRoot
{ {
if (!_textRoot) { if (!_textRoot) {
@@ -133,18 +95,31 @@
return _textRoot; return _textRoot;
} }
- (RNSVGGlyphContext *)getGlyphContext - (RNSVGGlyphContext *)getRNSVGGlyphContext
{
return _RNSVGGlyphContext;
}
- (GlyphContext *)getGlyphContext
{ {
return _glyphContext; return _glyphContext;
} }
- (void)pushGlyphContext - (void)pushGlyphContext
{ {
[[[self getTextRoot] getGlyphContext] pushContext:self.font [[[self getTextRoot] getRNSVGGlyphContext] pushContext:self.font
deltaX:self.deltaX deltaX:self.deltaX
deltaY:self.deltaY deltaY:self.deltaY
positionX:self.positionX positionX:self.positionX
positionY:self.positionY]; positionY:self.positionY];
[[[self getTextRoot] getGlyphContext] pushContextwithRNSVGText:self
reset:false
font:self.font
x:self.positionX
y:self.positionY
deltaX:self.deltaX
deltaY:self.deltaY
rotate:self.rotate];
} }
- (void)popGlyphContext - (void)popGlyphContext
@@ -159,7 +134,7 @@
- (CGPoint)getGlyphPointFromContext:(CGPoint)offset glyphWidth:(CGFloat)glyphWidth - (CGPoint)getGlyphPointFromContext:(CGPoint)offset glyphWidth:(CGFloat)glyphWidth
{ {
return [[[self getTextRoot] getGlyphContext] getNextGlyphPoint:(CGPoint)offset glyphWidth:glyphWidth]; return [[[self getTextRoot] getRNSVGGlyphContext] getNextGlyphPoint:(CGPoint)offset glyphWidth:glyphWidth];
} }
@end @end

View File

@@ -14,8 +14,13 @@
@interface RNSVGTextPath : RNSVGText @interface RNSVGTextPath : RNSVGText
@property (nonatomic, strong) NSString *href; @property (nonatomic, strong) NSString *href;
@property (nonatomic, strong) NSString *side;
@property (nonatomic, strong) NSString *method;
@property (nonatomic, strong) NSString *midLine;
@property (nonatomic, strong) NSString *spacing;
@property (nonatomic, strong) NSString *startOffset; @property (nonatomic, strong) NSString *startOffset;
- (RNSVGPath *)getPath;
- (RNSVGBezierTransformer *)getBezierTransformer; - (RNSVGBezierTransformer *)getBezierTransformer;
@end @end

View File

@@ -17,7 +17,7 @@
[self renderGroupTo:context]; [self renderGroupTo:context];
} }
- (RNSVGBezierTransformer *)getBezierTransformer - (RNSVGPath *)getPath
{ {
RNSVGSvgView *svg = [self getSvgView]; RNSVGSvgView *svg = [self getSvgView];
RNSVGNode *template = [svg getDefinedTemplate:self.href]; RNSVGNode *template = [svg getDefinedTemplate:self.href];
@@ -28,6 +28,12 @@
} }
RNSVGPath *path = (RNSVGPath *)template; RNSVGPath *path = (RNSVGPath *)template;
return path;
}
- (RNSVGBezierTransformer *)getBezierTransformer
{
RNSVGPath *path = [self getPath];
CGFloat startOffset = [self relativeOnWidth:self.startOffset]; CGFloat startOffset = [self relativeOnWidth:self.startOffset];
return [[RNSVGBezierTransformer alloc] initWithBezierCurvesAndStartOffset:[path getBezierCurves] return [[RNSVGBezierTransformer alloc] initWithBezierCurvesAndStartOffset:[path getBezierCurves]
startOffset:startOffset]; startOffset:startOffset];

19
ios/Text/TextAnchor.h Normal file
View File

@@ -0,0 +1,19 @@
#import <Foundation/Foundation.h>
#if !defined (TextAnchor_)
#define TextAnchor_
NS_ENUM(NSInteger, TextAnchor) {
TextAnchorStart,
TextAnchorMiddle,
TextAnchorEnd,
TextAnchorDEFAULT = TextAnchorStart,
};
static NSString* const TextAnchorStrings[] = {@"start", @"middle", @"end", nil};
NSString* TextAnchorToString( enum TextAnchor fw );
enum TextAnchor TextAnchorFromString( NSString* s );
#endif

18
ios/Text/TextAnchor.m Normal file
View File

@@ -0,0 +1,18 @@
#import "TextAnchor.h"
NSString* TextAnchorToString( enum TextAnchor fw )
{
return TextAnchorStrings[fw];
}
enum TextAnchor TextAnchorFromString( NSString* s )
{
NSInteger i;
NSString* fw;
for (i = 0; fw = TextAnchorStrings[i], fw != nil; i++) {
if ([fw isEqualToString:s]) {
return i;
}
}
return TextAnchorDEFAULT;
}

21
ios/Text/TextDecoration.h Normal file
View File

@@ -0,0 +1,21 @@
#import <Foundation/Foundation.h>
#if !defined (TextDecoration_)
#define TextDecoration_
NS_ENUM(NSInteger, TextDecoration) {
TextDecorationNone,
TextDecorationUnderline,
TextDecorationOverline,
TextDecorationLineThrough,
TextDecorationBlink,
TextDecorationDEFAULT = TextDecorationNone,
};
static NSString* const TextDecorationStrings[] = {@"None", @"Underline", @"Overline", @"LineThrough", @"Blink", nil};
NSString* TextDecorationToString( enum TextDecoration fw );
enum TextDecoration TextDecorationFromString( NSString* s );
#endif

18
ios/Text/TextDecoration.m Normal file
View File

@@ -0,0 +1,18 @@
#import "TextDecoration.h"
NSString* TextDecorationToString( enum TextDecoration fw )
{
return TextDecorationStrings[fw];
}
enum TextDecoration TextDecorationFromString( NSString* s )
{
NSInteger i;
NSString* fw;
for (i = 0; fw = TextDecorationStrings[i], fw != nil; i++) {
if ([fw isEqualToString:s]) {
return i;
}
}
return TextDecorationDEFAULT;
}

View File

@@ -0,0 +1,18 @@
#import <Foundation/Foundation.h>
#if !defined (TextLengthAdjust_)
#define TextLengthAdjust_
NS_ENUM(NSInteger, TextLengthAdjust) {
TextLengthAdjustSpacing,
TextLengthAdjustSpacingAndGlyphs,
TextLengthAdjustDEFAULT = TextLengthAdjustSpacing,
};
static NSString* const TextLengthAdjustStrings[] = {@"spacing", @"spacingAndGlyphs", nil};
NSString* TextLengthAdjustToString( enum TextLengthAdjust fw );
enum TextLengthAdjust TextLengthAdjustFromString( NSString* s );
#endif

View File

@@ -0,0 +1,18 @@
#import "TextLengthAdjust.h"
NSString* TextLengthAdjustToString( enum TextLengthAdjust fw )
{
return TextLengthAdjustStrings[fw];
}
enum TextLengthAdjust TextLengthAdjustFromString( NSString* s )
{
NSInteger i;
NSString* fw;
for (i = 0; fw = TextLengthAdjustStrings[i], fw != nil; i++) {
if ([fw isEqualToString:s]) {
return i;
}
}
return TextLengthAdjustDEFAULT;
}

18
ios/Text/TextPathMethod.h Normal file
View File

@@ -0,0 +1,18 @@
#import <Foundation/Foundation.h>
#if !defined (TextPathMethod_)
#define TextPathMethod_
NS_ENUM(NSInteger, TextPathMethod) {
TextPathMethodAlign,
TextPathMethodStretch,
TextPathMethodDEFAULT = TextPathMethodAlign,
};
static NSString* const TextPathMethodStrings[] = {@"align", @"stretch", nil};
NSString* TextPathMethodToString( enum TextPathMethod fw );
enum TextPathMethod TextPathMethodFromString( NSString* s );
#endif

18
ios/Text/TextPathMethod.m Normal file
View File

@@ -0,0 +1,18 @@
#import "TextPathMethod.h"
NSString* TextPathMethodToString( enum TextPathMethod fw )
{
return TextPathMethodStrings[fw];
}
enum TextPathMethod TextPathMethodFromString( NSString* s )
{
NSInteger i;
NSString* fw;
for (i = 0; fw = TextPathMethodStrings[i], fw != nil; i++) {
if ([fw isEqualToString:s]) {
return i;
}
}
return TextPathMethodDEFAULT;
}

View File

@@ -0,0 +1,18 @@
#import <Foundation/Foundation.h>
#if !defined (TextPathMidLine_)
#define TextPathMidLine_
NS_ENUM(NSInteger, TextPathMidLine) {
TextPathMidLineSharp,
TextPathMidLineSmooth,
TextPathMidLineDEFAULT = TextPathMidLineSharp,
};
static NSString* const TextPathMidLineStrings[] = {@"sharp", @"smooth", nil};
NSString* TextPathMidLineToString( enum TextPathMidLine fw );
enum TextPathMidLine TextPathMidLineFromString( NSString* s );
#endif

View File

@@ -0,0 +1,18 @@
#import "TextPathMidLine.h"
NSString* TextPathMidLineToString( enum TextPathMidLine fw )
{
return TextPathMidLineStrings[fw];
}
enum TextPathMidLine TextPathMidLineFromString( NSString* s )
{
NSInteger i;
NSString* fw;
for (i = 0; fw = TextPathMidLineStrings[i], fw != nil; i++) {
if ([fw isEqualToString:s]) {
return i;
}
}
return TextPathMidLineDEFAULT;
}

18
ios/Text/TextPathSide.h Normal file
View File

@@ -0,0 +1,18 @@
#import <Foundation/Foundation.h>
#if !defined (TextPathSide_)
#define TextPathSide_
NS_ENUM(NSInteger, TextPathSide) {
TextPathSideLeft,
TextPathSideRight,
TextPathSideDEFAULT = TextPathSideLeft,
};
static NSString* const TextPathSideStrings[] = {@"left", @"right", nil};
NSString* TextPathSideToString( enum TextPathSide fw );
enum TextPathSide TextPathSideFromString( NSString* s );
#endif

Some files were not shown because too many files have changed in this diff Show More