mirror of
https://github.com/zoriya/react-native-svg.git
synced 2025-12-21 06:15:15 +00:00
Port new GlyphContext, FontData, enums, props, Bezier and text rendering
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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
26
ios/PerformanceBezier/.gitignore
vendored
Normal 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/
|
||||||
319
ios/PerformanceBezier/LICENSE
Normal file
319
ios/PerformanceBezier/LICENSE
Normal 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
22
ios/PerformanceBezier/PerformanceBezier/Info.plist
Normal file
22
ios/PerformanceBezier/PerformanceBezier/Info.plist
Normal 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>
|
||||||
13
ios/PerformanceBezier/PerformanceBezier/JRSwizzle.h
Normal file
13
ios/PerformanceBezier/PerformanceBezier/JRSwizzle.h
Normal 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
|
||||||
134
ios/PerformanceBezier/PerformanceBezier/JRSwizzle.m
Normal file
134
ios/PerformanceBezier/PerformanceBezier/JRSwizzle.m
Normal 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
|
||||||
@@ -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>
|
||||||
20
ios/PerformanceBezier/PerformanceBezier/PerformanceBezier.h
Normal file
20
ios/PerformanceBezier/PerformanceBezier/PerformanceBezier.h
Normal 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"
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
54
ios/PerformanceBezier/PerformanceBezier/UIBezierPath+NSOSX.h
Normal file
54
ios/PerformanceBezier/PerformanceBezier/UIBezierPath+NSOSX.h
Normal 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
|
||||||
522
ios/PerformanceBezier/PerformanceBezier/UIBezierPath+NSOSX.m
Normal file
522
ios/PerformanceBezier/PerformanceBezier/UIBezierPath+NSOSX.m
Normal 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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
|
|
||||||
@@ -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
|
||||||
35
ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Trim.h
Normal file
35
ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Trim.h
Normal 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
|
||||||
209
ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Trim.m
Normal file
209
ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Trim.m
Normal 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
|
||||||
@@ -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
|
||||||
365
ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Trimming.m
Normal file
365
ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Trimming.m
Normal 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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
48
ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Util.h
Normal file
48
ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Util.h
Normal 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
|
||||||
|
|
||||||
316
ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Util.m
Normal file
316
ios/PerformanceBezier/PerformanceBezier/UIBezierPath+Util.m
Normal 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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
24
ios/PerformanceBezier/PerformanceBezierTests/Info.plist
Normal file
24
ios/PerformanceBezier/PerformanceBezierTests/Info.plist
Normal 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>
|
||||||
@@ -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
|
||||||
|
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
67
ios/PerformanceBezier/README.md
Normal file
67
ios/PerformanceBezier/README.md
Normal 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 <PerformanceBezier/PerformanceBezier.h>
|
||||||
|
|
||||||
|
## 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! :)
|
||||||
32
ios/PerformanceBezier/SubdivideLicense
Normal file
32
ios/PerformanceBezier/SubdivideLicense
Normal 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
17
ios/QuartzBookPack/Bezier/Bezier.h
Normal file
17
ios/QuartzBookPack/Bezier/Bezier.h
Normal 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"
|
||||||
34
ios/QuartzBookPack/Bezier/BezierElement.h
Normal file
34
ios/QuartzBookPack/Bezier/BezierElement.h
Normal 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;
|
||||||
163
ios/QuartzBookPack/Bezier/BezierElement.m
Normal file
163
ios/QuartzBookPack/Bezier/BezierElement.m
Normal 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
|
||||||
|
|
||||||
38
ios/QuartzBookPack/Bezier/BezierFunctions.h
Normal file
38
ios/QuartzBookPack/Bezier/BezierFunctions.h
Normal 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);
|
||||||
205
ios/QuartzBookPack/Bezier/BezierFunctions.m
Normal file
205
ios/QuartzBookPack/Bezier/BezierFunctions.m
Normal 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));
|
||||||
|
}
|
||||||
87
ios/QuartzBookPack/Bezier/BezierUtils.h
Normal file
87
ios/QuartzBookPack/Bezier/BezierUtils.h
Normal 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
|
||||||
709
ios/QuartzBookPack/Bezier/BezierUtils.m
Normal file
709
ios/QuartzBookPack/Bezier/BezierUtils.m
Normal 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
|
||||||
56
ios/QuartzBookPack/Bezier/UIBezierPath+Elements.h
Normal file
56
ios/QuartzBookPack/Bezier/UIBezierPath+Elements.h
Normal 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
|
||||||
519
ios/QuartzBookPack/Bezier/UIBezierPath+Elements.m
Normal file
519
ios/QuartzBookPack/Bezier/UIBezierPath+Elements.m
Normal 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
|
||||||
22
ios/QuartzBookPack/Drawing/Drawing-Block.h
Normal file
22
ios/QuartzBookPack/Drawing/Drawing-Block.h
Normal 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);
|
||||||
71
ios/QuartzBookPack/Drawing/Drawing-Block.m
Normal file
71
ios/QuartzBookPack/Drawing/Drawing-Block.m
Normal 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];
|
||||||
|
}
|
||||||
44
ios/QuartzBookPack/Drawing/Drawing-Gradient.h
Normal file
44
ios/QuartzBookPack/Drawing/Drawing-Gradient.h
Normal 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;
|
||||||
256
ios/QuartzBookPack/Drawing/Drawing-Gradient.m
Normal file
256
ios/QuartzBookPack/Drawing/Drawing-Gradient.m
Normal 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
|
||||||
25
ios/QuartzBookPack/Drawing/Drawing-Util.h
Normal file
25
ios/QuartzBookPack/Drawing/Drawing-Util.h
Normal 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);
|
||||||
241
ios/QuartzBookPack/Drawing/Drawing-Util.m
Normal file
241
ios/QuartzBookPack/Drawing/Drawing-Util.m
Normal 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];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
70
ios/QuartzBookPack/Geometry/BaseGeometry.h
Normal file
70
ios/QuartzBookPack/Geometry/BaseGeometry.h
Normal 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);
|
||||||
242
ios/QuartzBookPack/Geometry/BaseGeometry.m
Normal file
242
ios/QuartzBookPack/Geometry/BaseGeometry.m
Normal 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);
|
||||||
|
}
|
||||||
40
ios/QuartzBookPack/Image/ImageUtils.h
Normal file
40
ios/QuartzBookPack/Image/ImageUtils.h
Normal 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);
|
||||||
372
ios/QuartzBookPack/Image/ImageUtils.m
Normal file
372
ios/QuartzBookPack/Image/ImageUtils.m
Normal 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;
|
||||||
|
}
|
||||||
23
ios/QuartzBookPack/TextDrawing/Drawing-Text.h
Normal file
23
ios/QuartzBookPack/TextDrawing/Drawing-Text.h
Normal 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);
|
||||||
175
ios/QuartzBookPack/TextDrawing/Drawing-Text.m
Normal file
175
ios/QuartzBookPack/TextDrawing/Drawing-Text.m
Normal 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];
|
||||||
|
}
|
||||||
13
ios/QuartzBookPack/TextDrawing/UIBezierPath+Text.h
Normal file
13
ios/QuartzBookPack/TextDrawing/UIBezierPath+Text.h
Normal 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
|
||||||
56
ios/QuartzBookPack/TextDrawing/UIBezierPath+Text.m
Normal file
56
ios/QuartzBookPack/TextDrawing/UIBezierPath+Text.m
Normal 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
|
||||||
48
ios/QuartzBookPack/Utility/Utility.h
Normal file
48
ios/QuartzBookPack/Utility/Utility.h
Normal 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]
|
||||||
|
|
||||||
240
ios/QuartzBookPack/Utility/Utility.m
Normal file
240
ios/QuartzBookPack/Utility/Utility.m
Normal 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. It’s 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];
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
63
ios/Text/AlignmentBaseline.h
Normal file
63
ios/Text/AlignmentBaseline.h
Normal 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 */
|
||||||
18
ios/Text/AlignmentBaseline.m
Normal file
18
ios/Text/AlignmentBaseline.m
Normal 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
43
ios/Text/FontData.h
Normal 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
108
ios/Text/FontData.m
Normal 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
19
ios/Text/FontStyle.h
Normal 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
18
ios/Text/FontStyle.m
Normal 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;
|
||||||
|
}
|
||||||
18
ios/Text/FontVariantLigatures.h
Normal file
18
ios/Text/FontVariantLigatures.h
Normal 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
|
||||||
18
ios/Text/FontVariantLigatures.m
Normal file
18
ios/Text/FontVariantLigatures.m
Normal 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
30
ios/Text/FontWeight.h
Normal 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
18
ios/Text/FontWeight.m
Normal 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
51
ios/Text/GlyphContext.h
Normal 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
437
ios/Text/GlyphContext.m
Normal 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
15
ios/Text/PropHelper.h
Normal 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
49
ios/Text/PropHelper.m
Normal 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
|
||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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 box’s 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 parent’s content area.
|
||||||
|
// text-after-edge = text-bottom
|
||||||
|
// text-after-edge = descender depth
|
||||||
|
baselineShift = -descenderDepth;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AlignmentBaselineAlphabetic:
|
||||||
|
// Match the box’s alphabetic baseline to that of its parent.
|
||||||
|
// alphabetic = 0
|
||||||
|
baselineShift = 0;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AlignmentBaselineIdeographic:
|
||||||
|
// Match the box’s 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 box’s central baseline to the central baseline of its parent.
|
||||||
|
// central = (ascender height - descender depth) / 2
|
||||||
|
baselineShift = (ascenderHeight - descenderDepth) / 2;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AlignmentBaselineMathematical:
|
||||||
|
// Match the box’s 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 parent’s 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 parent’s box.
|
||||||
|
(The UA should use the parent’s font data to find this offset whenever possible.)
|
||||||
|
TODO super
|
||||||
|
Raise by the offset appropriate for superscripts of the parent’s box.
|
||||||
|
(The UA should use the parent’s 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 it’s 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
19
ios/Text/TextAnchor.h
Normal 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
18
ios/Text/TextAnchor.m
Normal 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
21
ios/Text/TextDecoration.h
Normal 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
18
ios/Text/TextDecoration.m
Normal 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;
|
||||||
|
}
|
||||||
18
ios/Text/TextLengthAdjust.h
Normal file
18
ios/Text/TextLengthAdjust.h
Normal 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
|
||||||
18
ios/Text/TextLengthAdjust.m
Normal file
18
ios/Text/TextLengthAdjust.m
Normal 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
18
ios/Text/TextPathMethod.h
Normal 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
18
ios/Text/TextPathMethod.m
Normal 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;
|
||||||
|
}
|
||||||
18
ios/Text/TextPathMidLine.h
Normal file
18
ios/Text/TextPathMidLine.h
Normal 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
|
||||||
18
ios/Text/TextPathMidLine.m
Normal file
18
ios/Text/TextPathMidLine.m
Normal 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
18
ios/Text/TextPathSide.h
Normal 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
Reference in New Issue
Block a user