12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407 |
- //
- // YYTextLayout.m
- // YYKit <https://github.com/ibireme/YYKit>
- //
- // Created by ibireme on 15/3/3.
- // Copyright (c) 2015 ibireme.
- //
- // This source code is licensed under the MIT-style license found in the
- // LICENSE file in the root directory of this source tree.
- //
- #import "YYTextLayout.h"
- #import "YYKitMacro.h"
- #import "YYCGUtilities.h"
- #import "YYTextUtilities.h"
- #import "YYTextAttribute.h"
- #import "YYTextArchiver.h"
- #import "NSAttributedString+YYText.h"
- #import "UIFont+YYAdd.h"
- const CGSize YYTextContainerMaxSize = (CGSize){0x100000, 0x100000};
- typedef struct {
- CGFloat head;
- CGFloat foot;
- } YYRowEdge;
- static inline CGSize YYTextClipCGSize(CGSize size) {
- if (size.width > YYTextContainerMaxSize.width) size.width = YYTextContainerMaxSize.width;
- if (size.height > YYTextContainerMaxSize.height) size.height = YYTextContainerMaxSize.height;
- return size;
- }
- static inline UIEdgeInsets UIEdgeInsetRotateVertical(UIEdgeInsets insets) {
- UIEdgeInsets one;
- one.top = insets.left;
- one.left = insets.bottom;
- one.bottom = insets.right;
- one.right = insets.top;
- return one;
- }
- /**
- Sometimes CoreText may convert CGColor to UIColor for `kCTForegroundColorAttributeName`
- attribute in iOS7. This should be a bug of CoreText, and may cause crash. Here's a workaround.
- */
- static CGColorRef YYTextGetCGColor(CGColorRef color) {
- static UIColor *defaultColor;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- defaultColor = [UIColor blackColor];
- });
- if (!color) return defaultColor.CGColor;
- if ([((__bridge NSObject *)color) respondsToSelector:@selector(CGColor)]) {
- return ((__bridge UIColor *)color).CGColor;
- }
- return color;
- }
- @implementation YYTextLinePositionSimpleModifier
- - (void)modifyLines:(NSArray *)lines fromText:(NSAttributedString *)text inContainer:(YYTextContainer *)container {
- if (container.verticalForm) {
- for (NSUInteger i = 0, max = lines.count; i < max; i++) {
- YYTextLine *line = lines[i];
- CGPoint pos = line.position;
- pos.x = container.size.width - container.insets.right - line.row * _fixedLineHeight - _fixedLineHeight * 0.9;
- line.position = pos;
- }
- } else {
- for (NSUInteger i = 0, max = lines.count; i < max; i++) {
- YYTextLine *line = lines[i];
- CGPoint pos = line.position;
- pos.y = line.row * _fixedLineHeight + _fixedLineHeight * 0.9 + container.insets.top;
- line.position = pos;
- }
- }
- }
- - (id)copyWithZone:(NSZone *)zone {
- YYTextLinePositionSimpleModifier *one = [self.class new];
- one.fixedLineHeight = _fixedLineHeight;
- return one;
- }
- @end
- @implementation YYTextContainer {
- @package
- BOOL _readonly; ///< used only in YYTextLayout.implementation
- dispatch_semaphore_t _lock;
-
- CGSize _size;
- UIEdgeInsets _insets;
- UIBezierPath *_path;
- NSArray *_exclusionPaths;
- BOOL _pathFillEvenOdd;
- CGFloat _pathLineWidth;
- BOOL _verticalForm;
- NSUInteger _maximumNumberOfRows;
- YYTextTruncationType _truncationType;
- NSAttributedString *_truncationToken;
- id<YYTextLinePositionModifier> _linePositionModifier;
- }
- + (instancetype)containerWithSize:(CGSize)size {
- return [self containerWithSize:size insets:UIEdgeInsetsZero];
- }
- + (instancetype)containerWithSize:(CGSize)size insets:(UIEdgeInsets)insets {
- YYTextContainer *one = [self new];
- one.size = YYTextClipCGSize(size);
- one.insets = insets;
- return one;
- }
- + (instancetype)containerWithPath:(UIBezierPath *)path {
- YYTextContainer *one = [self new];
- one.path = path;
- return one;
- }
- - (instancetype)init {
- self = [super init];
- if (!self) return nil;
- _lock = dispatch_semaphore_create(1);
- _pathFillEvenOdd = YES;
- return self;
- }
- - (id)copyWithZone:(NSZone *)zone {
- YYTextContainer *one = [self.class new];
- dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER);
- one->_size = _size;
- one->_insets = _insets;
- one->_path = _path;
- one->_exclusionPaths = _exclusionPaths.copy;
- one->_pathFillEvenOdd = _pathFillEvenOdd;
- one->_pathLineWidth = _pathLineWidth;
- one->_verticalForm = _verticalForm;
- one->_maximumNumberOfRows = _maximumNumberOfRows;
- one->_truncationType = _truncationType;
- one->_truncationToken = _truncationToken.copy;
- one->_linePositionModifier = [(NSObject *)_linePositionModifier copy];
- dispatch_semaphore_signal(_lock);
- return one;
- }
- - (id)mutableCopyWithZone:(nullable NSZone *)zone {
- return [self copyWithZone:zone];
- }
- - (void)encodeWithCoder:(NSCoder *)aCoder {
- [aCoder encodeObject:[NSValue valueWithCGSize:_size] forKey:@"size"];
- [aCoder encodeObject:[NSValue valueWithUIEdgeInsets:_insets] forKey:@"insets"];
- [aCoder encodeObject:_path forKey:@"path"];
- [aCoder encodeObject:_exclusionPaths forKey:@"exclusionPaths"];
- [aCoder encodeBool:_pathFillEvenOdd forKey:@"pathFillEvenOdd"];
- [aCoder encodeDouble:_pathLineWidth forKey:@"pathLineWidth"];
- [aCoder encodeBool:_verticalForm forKey:@"verticalForm"];
- [aCoder encodeInteger:_maximumNumberOfRows forKey:@"maximumNumberOfRows"];
- [aCoder encodeInteger:_truncationType forKey:@"truncationType"];
- [aCoder encodeObject:_truncationToken forKey:@"truncationToken"];
- if ([_linePositionModifier respondsToSelector:@selector(encodeWithCoder:)] &&
- [_linePositionModifier respondsToSelector:@selector(initWithCoder:)]) {
- [aCoder encodeObject:_linePositionModifier forKey:@"linePositionModifier"];
- }
- }
- - (id)initWithCoder:(NSCoder *)aDecoder {
- self = [self init];
- _size = ((NSValue *)[aDecoder decodeObjectForKey:@"size"]).CGSizeValue;
- _insets = ((NSValue *)[aDecoder decodeObjectForKey:@"insets"]).UIEdgeInsetsValue;
- _path = [aDecoder decodeObjectForKey:@"path"];
- _exclusionPaths = [aDecoder decodeObjectForKey:@"exclusionPaths"];
- _pathFillEvenOdd = [aDecoder decodeBoolForKey:@"pathFillEvenOdd"];
- _pathLineWidth = [aDecoder decodeDoubleForKey:@"pathLineWidth"];
- _verticalForm = [aDecoder decodeBoolForKey:@"verticalForm"];
- _maximumNumberOfRows = [aDecoder decodeIntegerForKey:@"maximumNumberOfRows"];
- _truncationType = [aDecoder decodeIntegerForKey:@"truncationType"];
- _truncationToken = [aDecoder decodeObjectForKey:@"truncationToken"];
- _linePositionModifier = [aDecoder decodeObjectForKey:@"linePositionModifier"];
- return self;
- }
- #define Getter(...) \
- dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER); \
- __VA_ARGS__; \
- dispatch_semaphore_signal(_lock);
- #define Setter(...) \
- if (_readonly) { \
- @throw [NSException exceptionWithName:NSInternalInconsistencyException \
- reason:@"Cannot change the property of the 'container' in 'YYTextLayout'." userInfo:nil]; \
- return; \
- } \
- dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER); \
- __VA_ARGS__; \
- dispatch_semaphore_signal(_lock);
- - (CGSize)size {
- Getter(CGSize size = _size) return size;
- }
- - (void)setSize:(CGSize)size {
- Setter(if(!_path) _size = YYTextClipCGSize(size));
- }
- - (UIEdgeInsets)insets {
- Getter(UIEdgeInsets insets = _insets) return insets;
- }
- - (void)setInsets:(UIEdgeInsets)insets {
- Setter(if(!_path){
- if (insets.top < 0) insets.top = 0;
- if (insets.left < 0) insets.left = 0;
- if (insets.bottom < 0) insets.bottom = 0;
- if (insets.right < 0) insets.right = 0;
- _insets = insets;
- });
- }
- - (UIBezierPath *)path {
- Getter(UIBezierPath *path = _path) return path;
- }
- - (void)setPath:(UIBezierPath *)path {
- Setter(
- _path = path.copy;
- if (_path) {
- CGRect bounds = _path.bounds;
- CGSize size = bounds.size;
- UIEdgeInsets insets = UIEdgeInsetsZero;
- if (bounds.origin.x < 0) size.width += bounds.origin.x;
- if (bounds.origin.x > 0) insets.left = bounds.origin.x;
- if (bounds.origin.y < 0) size.height += bounds.origin.y;
- if (bounds.origin.y > 0) insets.top = bounds.origin.y;
- _size = size;
- _insets = insets;
- }
- );
- }
- - (NSArray *)exclusionPaths {
- Getter(NSArray *paths = _exclusionPaths) return paths;
- }
- - (void)setExclusionPaths:(NSArray *)exclusionPaths {
- Setter(_exclusionPaths = exclusionPaths.copy);
- }
- - (BOOL)isPathFillEvenOdd {
- Getter(BOOL is = _pathFillEvenOdd) return is;
- }
- - (void)setPathFillEvenOdd:(BOOL)pathFillEvenOdd {
- Setter(_pathFillEvenOdd = pathFillEvenOdd);
- }
- - (CGFloat)pathLineWidth {
- Getter(CGFloat width = _pathLineWidth) return width;
- }
- - (void)setPathLineWidth:(CGFloat)pathLineWidth {
- Setter(_pathLineWidth = pathLineWidth);
- }
- - (BOOL)isVerticalForm {
- Getter(BOOL v = _verticalForm) return v;
- }
- - (void)setVerticalForm:(BOOL)verticalForm {
- Setter(_verticalForm = verticalForm);
- }
- - (NSUInteger)maximumNumberOfRows {
- Getter(NSUInteger num = _maximumNumberOfRows) return num;
- }
- - (void)setMaximumNumberOfRows:(NSUInteger)maximumNumberOfRows {
- Setter(_maximumNumberOfRows = maximumNumberOfRows);
- }
- - (YYTextTruncationType)truncationType {
- Getter(YYTextTruncationType type = _truncationType) return type;
- }
- - (void)setTruncationType:(YYTextTruncationType)truncationType {
- Setter(_truncationType = truncationType);
- }
- - (NSAttributedString *)truncationToken {
- Getter(NSAttributedString *token = _truncationToken) return token;
- }
- - (void)setTruncationToken:(NSAttributedString *)truncationToken {
- Setter(_truncationToken = truncationToken.copy);
- }
- - (void)setLinePositionModifier:(id<YYTextLinePositionModifier>)linePositionModifier {
- Setter(_linePositionModifier = [(NSObject *)linePositionModifier copy]);
- }
- - (id<YYTextLinePositionModifier>)linePositionModifier {
- Getter(id<YYTextLinePositionModifier> m = _linePositionModifier) return m;
- }
- #undef Getter
- #undef Setter
- @end
- @interface YYTextLayout ()
- @property (nonatomic, readwrite) YYTextContainer *container;
- @property (nonatomic, readwrite) NSAttributedString *text;
- @property (nonatomic, readwrite) NSRange range;
- @property (nonatomic, readwrite) CTFramesetterRef frameSetter;
- @property (nonatomic, readwrite) CTFrameRef frame;
- @property (nonatomic, readwrite) NSArray *lines;
- @property (nonatomic, readwrite) YYTextLine *truncatedLine;
- @property (nonatomic, readwrite) NSArray *attachments;
- @property (nonatomic, readwrite) NSArray *attachmentRanges;
- @property (nonatomic, readwrite) NSArray *attachmentRects;
- @property (nonatomic, readwrite) NSSet *attachmentContentsSet;
- @property (nonatomic, readwrite) NSUInteger rowCount;
- @property (nonatomic, readwrite) NSRange visibleRange;
- @property (nonatomic, readwrite) CGRect textBoundingRect;
- @property (nonatomic, readwrite) CGSize textBoundingSize;
- @property (nonatomic, readwrite) BOOL containsHighlight;
- @property (nonatomic, readwrite) BOOL needDrawBlockBorder;
- @property (nonatomic, readwrite) BOOL needDrawBackgroundBorder;
- @property (nonatomic, readwrite) BOOL needDrawShadow;
- @property (nonatomic, readwrite) BOOL needDrawUnderline;
- @property (nonatomic, readwrite) BOOL needDrawText;
- @property (nonatomic, readwrite) BOOL needDrawAttachment;
- @property (nonatomic, readwrite) BOOL needDrawInnerShadow;
- @property (nonatomic, readwrite) BOOL needDrawStrikethrough;
- @property (nonatomic, readwrite) BOOL needDrawBorder;
- @property (nonatomic, assign) NSUInteger *lineRowsIndex;
- @property (nonatomic, assign) YYRowEdge *lineRowsEdge; ///< top-left origin
- @end
- @implementation YYTextLayout
- #pragma mark - Layout
- - (instancetype)_init {
- self = [super init];
- return self;
- }
- + (YYTextLayout *)layoutWithContainerSize:(CGSize)size text:(NSAttributedString *)text {
- YYTextContainer *container = [YYTextContainer containerWithSize:size];
- return [self layoutWithContainer:container text:text];
- }
- + (YYTextLayout *)layoutWithContainer:(YYTextContainer *)container text:(NSAttributedString *)text {
- return [self layoutWithContainer:container text:text range:NSMakeRange(0, text.length)];
- }
- + (YYTextLayout *)layoutWithContainer:(YYTextContainer *)container text:(NSAttributedString *)text range:(NSRange)range {
- YYTextLayout *layout = NULL;
- CGPathRef cgPath = nil;
- CGRect cgPathBox = {0};
- BOOL isVerticalForm = NO;
- BOOL rowMaySeparated = NO;
- NSMutableDictionary *frameAttrs = nil;
- CTFramesetterRef ctSetter = NULL;
- CTFrameRef ctFrame = NULL;
- CFArrayRef ctLines = nil;
- CGPoint *lineOrigins = NULL;
- NSUInteger lineCount = 0;
- NSMutableArray *lines = nil;
- NSMutableArray *attachments = nil;
- NSMutableArray *attachmentRanges = nil;
- NSMutableArray *attachmentRects = nil;
- NSMutableSet *attachmentContentsSet = nil;
- BOOL needTruncation = NO;
- NSAttributedString *truncationToken = nil;
- YYTextLine *truncatedLine = nil;
- YYRowEdge *lineRowsEdge = NULL;
- NSUInteger *lineRowsIndex = NULL;
- NSRange visibleRange;
- NSUInteger maximumNumberOfRows = 0;
- BOOL constraintSizeIsExtended = NO;
- CGRect constraintRectBeforeExtended = {0};
-
- text = text.mutableCopy;
- container = container.copy;
- if (!text || !container) return nil;
- if (range.location + range.length > text.length) return nil;
- container->_readonly = YES;
- maximumNumberOfRows = container.maximumNumberOfRows;
-
- // CoreText bug when draw joined emoji since iOS 8.3.
- // See -[NSMutableAttributedString setClearColorToJoinedEmoji] for more information.
- static BOOL needFixJoinedEmojiBug = NO;
- // It may use larger constraint size when create CTFrame with
- // CTFramesetterCreateFrame in iOS 10.
- static BOOL needFixLayoutSizeBug = NO;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- CGFloat systemVersionFloat = [UIDevice currentDevice].systemVersion.floatValue;
- if (8.3 <= systemVersionFloat && systemVersionFloat < 9) {
- needFixJoinedEmojiBug = YES;
- }
- if (systemVersionFloat >= 10) {
- needFixLayoutSizeBug = YES;
- }
- });
- if (needFixJoinedEmojiBug) {
- [((NSMutableAttributedString *)text) setClearColorToJoinedEmoji];
- }
-
- layout = [[YYTextLayout alloc] _init];
- layout.text = text;
- layout.container = container;
- layout.range = range;
- isVerticalForm = container.verticalForm;
-
- // set cgPath and cgPathBox
- if (container.path == nil && container.exclusionPaths.count == 0) {
- if (container.size.width <= 0 || container.size.height <= 0) goto fail;
- CGRect rect = (CGRect) {CGPointZero, container.size };
- if (needFixLayoutSizeBug) {
- constraintSizeIsExtended = YES;
- constraintRectBeforeExtended = UIEdgeInsetsInsetRect(rect, container.insets);
- constraintRectBeforeExtended = CGRectStandardize(constraintRectBeforeExtended);
- if (container.isVerticalForm) {
- rect.size.width = YYTextContainerMaxSize.width;
- } else {
- rect.size.height = YYTextContainerMaxSize.height;
- }
- }
- rect = UIEdgeInsetsInsetRect(rect, container.insets);
- rect = CGRectStandardize(rect);
- cgPathBox = rect;
- rect = CGRectApplyAffineTransform(rect, CGAffineTransformMakeScale(1, -1));
- cgPath = CGPathCreateWithRect(rect, NULL); // let CGPathIsRect() returns true
- } else if (container.path && CGPathIsRect(container.path.CGPath, &cgPathBox) && container.exclusionPaths.count == 0) {
- CGRect rect = CGRectApplyAffineTransform(cgPathBox, CGAffineTransformMakeScale(1, -1));
- cgPath = CGPathCreateWithRect(rect, NULL); // let CGPathIsRect() returns true
- } else {
- rowMaySeparated = YES;
- CGMutablePathRef path = NULL;
- if (container.path) {
- path = CGPathCreateMutableCopy(container.path.CGPath);
- } else {
- CGRect rect = (CGRect) {CGPointZero, container.size };
- rect = UIEdgeInsetsInsetRect(rect, container.insets);
- CGPathRef rectPath = CGPathCreateWithRect(rect, NULL);
- if (rectPath) {
- path = CGPathCreateMutableCopy(rectPath);
- CGPathRelease(rectPath);
- }
- }
- if (path) {
- [layout.container.exclusionPaths enumerateObjectsUsingBlock: ^(UIBezierPath *onePath, NSUInteger idx, BOOL *stop) {
- CGPathAddPath(path, NULL, onePath.CGPath);
- }];
-
- cgPathBox = CGPathGetPathBoundingBox(path);
- CGAffineTransform trans = CGAffineTransformMakeScale(1, -1);
- CGMutablePathRef transPath = CGPathCreateMutableCopyByTransformingPath(path, &trans);
- CGPathRelease(path);
- path = transPath;
- }
- cgPath = path;
- }
- if (!cgPath) goto fail;
-
- // frame setter config
- frameAttrs = [NSMutableDictionary dictionary];
- if (container.isPathFillEvenOdd == NO) {
- frameAttrs[(id)kCTFramePathFillRuleAttributeName] = @(kCTFramePathFillWindingNumber);
- }
- if (container.pathLineWidth > 0) {
- frameAttrs[(id)kCTFramePathWidthAttributeName] = @(container.pathLineWidth);
- }
- if (container.isVerticalForm == YES) {
- frameAttrs[(id)kCTFrameProgressionAttributeName] = @(kCTFrameProgressionRightToLeft);
- }
-
- // create CoreText objects
- ctSetter = CTFramesetterCreateWithAttributedString((CFTypeRef)text);
- if (!ctSetter) goto fail;
- ctFrame = CTFramesetterCreateFrame(ctSetter, YYCFRangeFromNSRange(range), cgPath, (CFTypeRef)frameAttrs);
- if (!ctFrame) goto fail;
- lines = [NSMutableArray new];
- ctLines = CTFrameGetLines(ctFrame);
- lineCount = CFArrayGetCount(ctLines);
- if (lineCount > 0) {
- lineOrigins = malloc(lineCount * sizeof(CGPoint));
- if (lineOrigins == NULL) goto fail;
- CTFrameGetLineOrigins(ctFrame, CFRangeMake(0, lineCount), lineOrigins);
- }
-
- CGRect textBoundingRect = CGRectZero;
- CGSize textBoundingSize = CGSizeZero;
- NSInteger rowIdx = -1;
- NSUInteger rowCount = 0;
- CGRect lastRect = CGRectMake(0, -FLT_MAX, 0, 0);
- CGPoint lastPosition = CGPointMake(0, -FLT_MAX);
- if (isVerticalForm) {
- lastRect = CGRectMake(FLT_MAX, 0, 0, 0);
- lastPosition = CGPointMake(FLT_MAX, 0);
- }
-
- // calculate line frame
- NSUInteger lineCurrentIdx = 0;
- for (NSUInteger i = 0; i < lineCount; i++) {
- CTLineRef ctLine = CFArrayGetValueAtIndex(ctLines, i);
- CFArrayRef ctRuns = CTLineGetGlyphRuns(ctLine);
- if (!ctRuns || CFArrayGetCount(ctRuns) == 0) continue;
-
- // CoreText coordinate system
- CGPoint ctLineOrigin = lineOrigins[i];
-
- // UIKit coordinate system
- CGPoint position;
- position.x = cgPathBox.origin.x + ctLineOrigin.x;
- position.y = cgPathBox.size.height + cgPathBox.origin.y - ctLineOrigin.y;
-
- YYTextLine *line = [YYTextLine lineWithCTLine:ctLine position:position vertical:isVerticalForm];
- CGRect rect = line.bounds;
-
- if (constraintSizeIsExtended) {
- if (isVerticalForm) {
- if (rect.origin.x + rect.size.width >
- constraintRectBeforeExtended.origin.x +
- constraintRectBeforeExtended.size.width) break;
- } else {
- if (rect.origin.y + rect.size.height >
- constraintRectBeforeExtended.origin.y +
- constraintRectBeforeExtended.size.height) break;
- }
- }
-
- BOOL newRow = YES;
- if (rowMaySeparated && position.x != lastPosition.x) {
- if (isVerticalForm) {
- if (rect.size.width > lastRect.size.width) {
- if (rect.origin.x > lastPosition.x && lastPosition.x > rect.origin.x - rect.size.width) newRow = NO;
- } else {
- if (lastRect.origin.x > position.x && position.x > lastRect.origin.x - lastRect.size.width) newRow = NO;
- }
- } else {
- if (rect.size.height > lastRect.size.height) {
- if (rect.origin.y < lastPosition.y && lastPosition.y < rect.origin.y + rect.size.height) newRow = NO;
- } else {
- if (lastRect.origin.y < position.y && position.y < lastRect.origin.y + lastRect.size.height) newRow = NO;
- }
- }
- }
-
- if (newRow) rowIdx++;
- lastRect = rect;
- lastPosition = position;
-
- line.index = lineCurrentIdx;
- line.row = rowIdx;
- [lines addObject:line];
- rowCount = rowIdx + 1;
- lineCurrentIdx ++;
-
- if (i == 0) textBoundingRect = rect;
- else {
- if (maximumNumberOfRows == 0 || rowIdx < maximumNumberOfRows) {
- textBoundingRect = CGRectUnion(textBoundingRect, rect);
- }
- }
- }
-
- if (rowCount > 0) {
- if (maximumNumberOfRows > 0) {
- if (rowCount > maximumNumberOfRows) {
- needTruncation = YES;
- rowCount = maximumNumberOfRows;
- do {
- YYTextLine *line = lines.lastObject;
- if (!line) break;
- if (line.row < rowCount) break;
- [lines removeLastObject];
- } while (1);
- }
- }
- YYTextLine *lastLine = lines.lastObject;
- if (!needTruncation && lastLine.range.location + lastLine.range.length < text.length) {
- needTruncation = YES;
- }
-
- // Give user a chance to modify the line's position.
- if (container.linePositionModifier) {
- [container.linePositionModifier modifyLines:lines fromText:text inContainer:container];
- textBoundingRect = CGRectZero;
- for (NSUInteger i = 0, max = lines.count; i < max; i++) {
- YYTextLine *line = lines[i];
- if (i == 0) textBoundingRect = line.bounds;
- else textBoundingRect = CGRectUnion(textBoundingRect, line.bounds);
- }
- }
-
- lineRowsEdge = calloc(rowCount, sizeof(YYRowEdge));
- if (lineRowsEdge == NULL) goto fail;
- lineRowsIndex = calloc(rowCount, sizeof(NSUInteger));
- if (lineRowsIndex == NULL) goto fail;
- NSInteger lastRowIdx = -1;
- CGFloat lastHead = 0;
- CGFloat lastFoot = 0;
- for (NSUInteger i = 0, max = lines.count; i < max; i++) {
- YYTextLine *line = lines[i];
- CGRect rect = line.bounds;
- if ((NSInteger)line.row != lastRowIdx) {
- if (lastRowIdx >= 0) {
- lineRowsEdge[lastRowIdx] = (YYRowEdge) {.head = lastHead, .foot = lastFoot };
- }
- lastRowIdx = line.row;
- lineRowsIndex[lastRowIdx] = i;
- if (isVerticalForm) {
- lastHead = rect.origin.x + rect.size.width;
- lastFoot = lastHead - rect.size.width;
- } else {
- lastHead = rect.origin.y;
- lastFoot = lastHead + rect.size.height;
- }
- } else {
- if (isVerticalForm) {
- lastHead = MAX(lastHead, rect.origin.x + rect.size.width);
- lastFoot = MIN(lastFoot, rect.origin.x);
- } else {
- lastHead = MIN(lastHead, rect.origin.y);
- lastFoot = MAX(lastFoot, rect.origin.y + rect.size.height);
- }
- }
- }
- lineRowsEdge[lastRowIdx] = (YYRowEdge) {.head = lastHead, .foot = lastFoot };
-
- for (NSUInteger i = 1; i < rowCount; i++) {
- YYRowEdge v0 = lineRowsEdge[i - 1];
- YYRowEdge v1 = lineRowsEdge[i];
- lineRowsEdge[i - 1].foot = lineRowsEdge[i].head = (v0.foot + v1.head) * 0.5;
- }
- }
-
- { // calculate bounding size
- CGRect rect = textBoundingRect;
- if (container.path) {
- if (container.pathLineWidth > 0) {
- CGFloat inset = container.pathLineWidth / 2;
- rect = CGRectInset(rect, -inset, -inset);
- }
- } else {
- rect = UIEdgeInsetsInsetRect(rect, UIEdgeInsetsInvert(container.insets));
- }
- rect = CGRectStandardize(rect);
- CGSize size = rect.size;
- if (container.verticalForm) {
- size.width += container.size.width - (rect.origin.x + rect.size.width);
- } else {
- size.width += rect.origin.x;
- }
- size.height += rect.origin.y;
- if (size.width < 0) size.width = 0;
- if (size.height < 0) size.height = 0;
- size.width = ceil(size.width);
- size.height = ceil(size.height);
- textBoundingSize = size;
- }
-
- visibleRange = YYNSRangeFromCFRange(CTFrameGetVisibleStringRange(ctFrame));
- if (needTruncation) {
- YYTextLine *lastLine = lines.lastObject;
- NSRange lastRange = lastLine.range;
- visibleRange.length = lastRange.location + lastRange.length - visibleRange.location;
-
- // create truncated line
- if (container.truncationType != YYTextTruncationTypeNone) {
- CTLineRef truncationTokenLine = NULL;
- if (container.truncationToken) {
- truncationToken = container.truncationToken;
- truncationTokenLine = CTLineCreateWithAttributedString((CFAttributedStringRef)truncationToken);
- } else {
- CFArrayRef runs = CTLineGetGlyphRuns(lastLine.CTLine);
- NSUInteger runCount = CFArrayGetCount(runs);
- NSMutableDictionary *attrs = nil;
- if (runCount > 0) {
- CTRunRef run = CFArrayGetValueAtIndex(runs, runCount - 1);
- attrs = (id)CTRunGetAttributes(run);
- attrs = attrs ? attrs.mutableCopy : [NSMutableArray new];
- [attrs removeObjectsForKeys:[NSMutableAttributedString allDiscontinuousAttributeKeys]];
- CTFontRef font = (__bridge CFTypeRef)attrs[(id)kCTFontAttributeName];
- CGFloat fontSize = font ? CTFontGetSize(font) : 12.0;
- UIFont *uiFont = [UIFont systemFontOfSize:fontSize * 0.9];
- font = [uiFont CTFontRef];
- if (font) {
- attrs[(id)kCTFontAttributeName] = (__bridge id)(font);
- uiFont = nil;
- CFRelease(font);
- }
- CGColorRef color = (__bridge CGColorRef)(attrs[(id)kCTForegroundColorAttributeName]);
- if (color && CFGetTypeID(color) == CGColorGetTypeID() && CGColorGetAlpha(color) == 0) {
- // ignore clear color
- [attrs removeObjectForKey:(id)kCTForegroundColorAttributeName];
- }
- if (!attrs) attrs = [NSMutableDictionary new];
- }
- truncationToken = [[NSAttributedString alloc] initWithString:YYTextTruncationToken attributes:attrs];
- truncationTokenLine = CTLineCreateWithAttributedString((CFAttributedStringRef)truncationToken);
- }
- if (truncationTokenLine) {
- CTLineTruncationType type = kCTLineTruncationEnd;
- if (container.truncationType == YYTextTruncationTypeStart) {
- type = kCTLineTruncationStart;
- } else if (container.truncationType == YYTextTruncationTypeMiddle) {
- type = kCTLineTruncationMiddle;
- }
- NSMutableAttributedString *lastLineText = [text attributedSubstringFromRange:lastLine.range].mutableCopy;
- [lastLineText appendAttributedString:truncationToken];
- CTLineRef ctLastLineExtend = CTLineCreateWithAttributedString((CFAttributedStringRef)lastLineText);
- if (ctLastLineExtend) {
- CGFloat truncatedWidth = lastLine.width;
- CGRect cgPathRect = CGRectZero;
- if (CGPathIsRect(cgPath, &cgPathRect)) {
- if (isVerticalForm) {
- truncatedWidth = cgPathRect.size.height;
- } else {
- truncatedWidth = cgPathRect.size.width;
- }
- }
- CTLineRef ctTruncatedLine = CTLineCreateTruncatedLine(ctLastLineExtend, truncatedWidth, type, truncationTokenLine);
- CFRelease(ctLastLineExtend);
- if (ctTruncatedLine) {
- truncatedLine = [YYTextLine lineWithCTLine:ctTruncatedLine position:lastLine.position vertical:isVerticalForm];
- truncatedLine.index = lastLine.index;
- truncatedLine.row = lastLine.row;
- CFRelease(ctTruncatedLine);
- }
- }
- CFRelease(truncationTokenLine);
- }
- }
- }
-
- if (isVerticalForm) {
- NSCharacterSet *rotateCharset = YYTextVerticalFormRotateCharacterSet();
- NSCharacterSet *rotateMoveCharset = YYTextVerticalFormRotateAndMoveCharacterSet();
-
- void (^lineBlock)(YYTextLine *) = ^(YYTextLine *line){
- CFArrayRef runs = CTLineGetGlyphRuns(line.CTLine);
- if (!runs) return;
- NSUInteger runCount = CFArrayGetCount(runs);
- if (runCount == 0) return;
- NSMutableArray *lineRunRanges = [NSMutableArray new];
- line.verticalRotateRange = lineRunRanges;
- for (NSUInteger r = 0; r < runCount; r++) {
- CTRunRef run = CFArrayGetValueAtIndex(runs, r);
- NSMutableArray *runRanges = [NSMutableArray new];
- [lineRunRanges addObject:runRanges];
- NSUInteger glyphCount = CTRunGetGlyphCount(run);
- if (glyphCount == 0) continue;
-
- CFIndex runStrIdx[glyphCount + 1];
- CTRunGetStringIndices(run, CFRangeMake(0, 0), runStrIdx);
- CFRange runStrRange = CTRunGetStringRange(run);
- runStrIdx[glyphCount] = runStrRange.location + runStrRange.length;
- CFDictionaryRef runAttrs = CTRunGetAttributes(run);
- CTFontRef font = CFDictionaryGetValue(runAttrs, kCTFontAttributeName);
- BOOL isColorGlyph = CTFontContainsColorBitmapGlyphs(font);
-
- NSUInteger prevIdx = 0;
- YYTextRunGlyphDrawMode prevMode = YYTextRunGlyphDrawModeHorizontal;
- NSString *layoutStr = layout.text.string;
- for (NSUInteger g = 0; g < glyphCount; g++) {
- BOOL glyphRotate = 0, glyphRotateMove = NO;
- CFIndex runStrLen = runStrIdx[g + 1] - runStrIdx[g];
- if (isColorGlyph) {
- glyphRotate = YES;
- } else if (runStrLen == 1) {
- unichar c = [layoutStr characterAtIndex:runStrIdx[g]];
- glyphRotate = [rotateCharset characterIsMember:c];
- if (glyphRotate) glyphRotateMove = [rotateMoveCharset characterIsMember:c];
- } else if (runStrLen > 1){
- NSString *glyphStr = [layoutStr substringWithRange:NSMakeRange(runStrIdx[g], runStrLen)];
- BOOL glyphRotate = [glyphStr rangeOfCharacterFromSet:rotateCharset].location != NSNotFound;
- if (glyphRotate) glyphRotateMove = [glyphStr rangeOfCharacterFromSet:rotateMoveCharset].location != NSNotFound;
- }
-
- YYTextRunGlyphDrawMode mode = glyphRotateMove ? YYTextRunGlyphDrawModeVerticalRotateMove : (glyphRotate ? YYTextRunGlyphDrawModeVerticalRotate : YYTextRunGlyphDrawModeHorizontal);
- if (g == 0) {
- prevMode = mode;
- } else if (mode != prevMode) {
- YYTextRunGlyphRange *aRange = [YYTextRunGlyphRange rangeWithRange:NSMakeRange(prevIdx, g - prevIdx) drawMode:prevMode];
- [runRanges addObject:aRange];
- prevIdx = g;
- prevMode = mode;
- }
- }
- if (prevIdx < glyphCount) {
- YYTextRunGlyphRange *aRange = [YYTextRunGlyphRange rangeWithRange:NSMakeRange(prevIdx, glyphCount - prevIdx) drawMode:prevMode];
- [runRanges addObject:aRange];
- }
-
- }
- };
- for (YYTextLine *line in lines) {
- lineBlock(line);
- }
- if (truncatedLine) lineBlock(truncatedLine);
- }
-
- if (visibleRange.length > 0) {
- layout.needDrawText = YES;
-
- void (^block)(NSDictionary *attrs, NSRange range, BOOL *stop) = ^(NSDictionary *attrs, NSRange range, BOOL *stop) {
- if (attrs[YYTextHighlightAttributeName]) layout.containsHighlight = YES;
- if (attrs[YYTextBlockBorderAttributeName]) layout.needDrawBlockBorder = YES;
- if (attrs[YYTextBackgroundBorderAttributeName]) layout.needDrawBackgroundBorder = YES;
- if (attrs[YYTextShadowAttributeName] || attrs[NSShadowAttributeName]) layout.needDrawShadow = YES;
- if (attrs[YYTextUnderlineAttributeName]) layout.needDrawUnderline = YES;
- if (attrs[YYTextAttachmentAttributeName]) layout.needDrawAttachment = YES;
- if (attrs[YYTextInnerShadowAttributeName]) layout.needDrawInnerShadow = YES;
- if (attrs[YYTextStrikethroughAttributeName]) layout.needDrawStrikethrough = YES;
- if (attrs[YYTextBorderAttributeName]) layout.needDrawBorder = YES;
- };
-
- [layout.text enumerateAttributesInRange:visibleRange options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired usingBlock:block];
- if (truncatedLine) {
- [truncationToken enumerateAttributesInRange:NSMakeRange(0, truncationToken.length) options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired usingBlock:block];
- }
- }
-
- attachments = [NSMutableArray new];
- attachmentRanges = [NSMutableArray new];
- attachmentRects = [NSMutableArray new];
- attachmentContentsSet = [NSMutableSet new];
- for (NSUInteger i = 0, max = lines.count; i < max; i++) {
- YYTextLine *line = lines[i];
- if (truncatedLine && line.index == truncatedLine.index) line = truncatedLine;
- if (line.attachments.count > 0) {
- [attachments addObjectsFromArray:line.attachments];
- [attachmentRanges addObjectsFromArray:line.attachmentRanges];
- [attachmentRects addObjectsFromArray:line.attachmentRects];
- for (YYTextAttachment *attachment in line.attachments) {
- if (attachment.content) {
- [attachmentContentsSet addObject:attachment.content];
- }
- }
- }
- }
- if (attachments.count == 0) {
- attachments = attachmentRanges = attachmentRects = nil;
- }
-
- layout.frameSetter = ctSetter;
- layout.frame = ctFrame;
- layout.lines = lines;
- layout.truncatedLine = truncatedLine;
- layout.attachments = attachments;
- layout.attachmentRanges = attachmentRanges;
- layout.attachmentRects = attachmentRects;
- layout.attachmentContentsSet = attachmentContentsSet;
- layout.rowCount = rowCount;
- layout.visibleRange = visibleRange;
- layout.textBoundingRect = textBoundingRect;
- layout.textBoundingSize = textBoundingSize;
- layout.lineRowsEdge = lineRowsEdge;
- layout.lineRowsIndex = lineRowsIndex;
- CFRelease(cgPath);
- CFRelease(ctSetter);
- CFRelease(ctFrame);
- if (lineOrigins) free(lineOrigins);
- return layout;
-
- fail:
- if (cgPath) CFRelease(cgPath);
- if (ctSetter) CFRelease(ctSetter);
- if (ctFrame) CFRelease(ctFrame);
- if (lineOrigins) free(lineOrigins);
- if (lineRowsEdge) free(lineRowsEdge);
- if (lineRowsIndex) free(lineRowsIndex);
- return nil;
- }
- + (NSArray *)layoutWithContainers:(NSArray *)containers text:(NSAttributedString *)text {
- return [self layoutWithContainers:containers text:text range:NSMakeRange(0, text.length)];
- }
- + (NSArray *)layoutWithContainers:(NSArray *)containers text:(NSAttributedString *)text range:(NSRange)range {
- if (!containers || !text) return nil;
- if (range.location + range.length > text.length) return nil;
- NSMutableArray *layouts = [NSMutableArray array];
- for (NSUInteger i = 0, max = containers.count; i < max; i++) {
- YYTextContainer *container = containers[i];
- YYTextLayout *layout = [self layoutWithContainer:container text:text range:range];
- if (!layout) return nil;
- NSInteger length = (NSInteger)range.length - (NSInteger)layout.visibleRange.length;
- if (length <= 0) {
- range.length = 0;
- range.location = text.length;
- } else {
- range.length = length;
- range.location += layout.visibleRange.length;
- }
- }
- return layouts;
- }
- - (void)setFrameSetter:(CTFramesetterRef)frameSetter {
- if (_frameSetter != frameSetter) {
- if (frameSetter) CFRetain(frameSetter);
- if (_frameSetter) CFRelease(_frameSetter);
- _frameSetter = frameSetter;
- }
- }
- - (void)setFrame:(CTFrameRef)frame {
- if (_frame != frame) {
- if (frame) CFRetain(frame);
- if (_frame) CFRelease(_frame);
- _frame = frame;
- }
- }
- - (void)dealloc {
- if (_frameSetter) CFRelease(_frameSetter);
- if (_frame) CFRelease(_frame);
- if (_lineRowsIndex) free(_lineRowsIndex);
- if (_lineRowsEdge) free(_lineRowsEdge);
- }
- #pragma mark - Coding
- - (void)encodeWithCoder:(NSCoder *)aCoder {
- NSData *textData = [YYTextArchiver archivedDataWithRootObject:_text];
- [aCoder encodeObject:textData forKey:@"text"];
- [aCoder encodeObject:_container forKey:@"container"];
- [aCoder encodeObject:[NSValue valueWithRange:_range] forKey:@"range"];
- }
- - (id)initWithCoder:(NSCoder *)aDecoder {
- NSData *textData = [aDecoder decodeObjectForKey:@"text"];
- NSAttributedString *text = [YYTextUnarchiver unarchiveObjectWithData:textData];
- YYTextContainer *container = [aDecoder decodeObjectForKey:@"container"];
- NSRange range = ((NSValue *)[aDecoder decodeObjectForKey:@"range"]).rangeValue;
- self = [self.class layoutWithContainer:container text:text range:range];
- return self;
- }
- #pragma mark - Copying
- - (id)copyWithZone:(NSZone *)zone {
- return self; // readonly object
- }
- #pragma mark - Query
- /**
- Get the row index with 'edge' distance.
-
- @param edge The distance from edge to the point.
- If vertical form, the edge is left edge, otherwise the edge is top edge.
-
- @return Returns NSNotFound if there's no row at the point.
- */
- - (NSUInteger)_rowIndexForEdge:(CGFloat)edge {
- if (_rowCount == 0) return NSNotFound;
- BOOL isVertical = _container.verticalForm;
- NSUInteger lo = 0, hi = _rowCount - 1, mid = 0;
- NSUInteger rowIdx = NSNotFound;
- while (lo <= hi) {
- mid = (lo + hi) / 2;
- YYRowEdge oneEdge = _lineRowsEdge[mid];
- if (isVertical ?
- (oneEdge.foot <= edge && edge <= oneEdge.head) :
- (oneEdge.head <= edge && edge <= oneEdge.foot)) {
- rowIdx = mid;
- break;
- }
- if ((isVertical ? (edge > oneEdge.head) : (edge < oneEdge.head))) {
- if (mid == 0) break;
- hi = mid - 1;
- } else {
- lo = mid + 1;
- }
- }
- return rowIdx;
- }
- /**
- Get the closest row index with 'edge' distance.
-
- @param edge The distance from edge to the point.
- If vertical form, the edge is left edge, otherwise the edge is top edge.
-
- @return Returns NSNotFound if there's no line.
- */
- - (NSUInteger)_closestRowIndexForEdge:(CGFloat)edge {
- if (_rowCount == 0) return NSNotFound;
- NSUInteger rowIdx = [self _rowIndexForEdge:edge];
- if (rowIdx == NSNotFound) {
- if (_container.verticalForm) {
- if (edge > _lineRowsEdge[0].head) {
- rowIdx = 0;
- } else if (edge < _lineRowsEdge[_rowCount - 1].foot) {
- rowIdx = _rowCount - 1;
- }
- } else {
- if (edge < _lineRowsEdge[0].head) {
- rowIdx = 0;
- } else if (edge > _lineRowsEdge[_rowCount - 1].foot) {
- rowIdx = _rowCount - 1;
- }
- }
- }
- return rowIdx;
- }
- /**
- Get a CTRun from a line position.
-
- @param line The text line.
- @param position The position in the whole text.
-
- @return Returns NULL if not found (no CTRun at the position).
- */
- - (CTRunRef)_runForLine:(YYTextLine *)line position:(YYTextPosition *)position {
- if (!line || !position) return NULL;
- CFArrayRef runs = CTLineGetGlyphRuns(line.CTLine);
- for (NSUInteger i = 0, max = CFArrayGetCount(runs); i < max; i++) {
- CTRunRef run = CFArrayGetValueAtIndex(runs, i);
- CFRange range = CTRunGetStringRange(run);
- if (position.affinity == YYTextAffinityBackward) {
- if (range.location < position.offset && position.offset <= range.location + range.length) {
- return run;
- }
- } else {
- if (range.location <= position.offset && position.offset < range.location + range.length) {
- return run;
- }
- }
- }
- return NULL;
- }
- /**
- Whether the position is inside a composed character sequence.
-
- @param line The text line.
- @param position Text text position in whole text.
- @param block The block to be executed before returns YES.
- left: left X offset
- right: right X offset
- prev: left position
- next: right position
- */
- - (BOOL)_insideComposedCharacterSequences:(YYTextLine *)line position:(NSUInteger)position block:(void (^)(CGFloat left, CGFloat right, NSUInteger prev, NSUInteger next))block {
- NSRange range = line.range;
- if (range.length == 0) return NO;
- __block BOOL inside = NO;
- __block NSUInteger _prev, _next;
- [_text.string enumerateSubstringsInRange:range options:NSStringEnumerationByComposedCharacterSequences usingBlock: ^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) {
- NSUInteger prev = substringRange.location;
- NSUInteger next = substringRange.location + substringRange.length;
- if (prev == position || next == position) {
- *stop = YES;
- }
- if (prev < position && position < next) {
- inside = YES;
- _prev = prev;
- _next = next;
- *stop = YES;
- }
- }];
- if (inside && block) {
- CGFloat left = [self offsetForTextPosition:_prev lineIndex:line.index];
- CGFloat right = [self offsetForTextPosition:_next lineIndex:line.index];
- block(left, right, _prev, _next);
- }
- return inside;
- }
- /**
- Whether the position is inside an emoji (such as National Flag Emoji).
-
- @param line The text line.
- @param position Text text position in whole text.
- @param block Yhe block to be executed before returns YES.
- left: emoji's left X offset
- right: emoji's right X offset
- prev: emoji's left position
- next: emoji's right position
- */
- - (BOOL)_insideEmoji:(YYTextLine *)line position:(NSUInteger)position block:(void (^)(CGFloat left, CGFloat right, NSUInteger prev, NSUInteger next))block {
- if (!line) return NO;
- CFArrayRef runs = CTLineGetGlyphRuns(line.CTLine);
- for (NSUInteger r = 0, rMax = CFArrayGetCount(runs); r < rMax; r++) {
- CTRunRef run = CFArrayGetValueAtIndex(runs, r);
- NSUInteger glyphCount = CTRunGetGlyphCount(run);
- if (glyphCount == 0) continue;
- CFRange range = CTRunGetStringRange(run);
- if (range.length <= 1) continue;
- if (position <= range.location || position >= range.location + range.length) continue;
- CFDictionaryRef attrs = CTRunGetAttributes(run);
- CTFontRef font = CFDictionaryGetValue(attrs, kCTFontAttributeName);
- if (!CTFontContainsColorBitmapGlyphs(font)) continue;
-
- // Here's Emoji runs (larger than 1 unichar), and position is inside the range.
- CFIndex indices[glyphCount];
- CTRunGetStringIndices(run, CFRangeMake(0, glyphCount), indices);
- for (NSUInteger g = 0; g < glyphCount; g++) {
- CFIndex prev = indices[g];
- CFIndex next = g + 1 < glyphCount ? indices[g + 1] : range.location + range.length;
- if (position == prev) break; // Emoji edge
- if (prev < position && position < next) { // inside an emoji (such as National Flag Emoji)
- CGPoint pos = CGPointZero;
- CGSize adv = CGSizeZero;
- CTRunGetPositions(run, CFRangeMake(g, 1), &pos);
- CTRunGetAdvances(run, CFRangeMake(g, 1), &adv);
- if (block) {
- block(line.position.x + pos.x,
- line.position.x + pos.x + adv.width,
- prev, next);
- }
- return YES;
- }
- }
- }
- return NO;
- }
- /**
- Whether the write direction is RTL at the specified point
-
- @param line The text line
- @param point The point in layout.
-
- @return YES if RTL.
- */
- - (BOOL)_isRightToLeftInLine:(YYTextLine *)line atPoint:(CGPoint)point {
- if (!line) return NO;
- // get write direction
- BOOL RTL = NO;
- CFArrayRef runs = CTLineGetGlyphRuns(line.CTLine);
- for (NSUInteger r = 0, max = CFArrayGetCount(runs); r < max; r++) {
- CTRunRef run = CFArrayGetValueAtIndex(runs, r);
- CGPoint glyphPosition;
- CTRunGetPositions(run, CFRangeMake(0, 1), &glyphPosition);
- if (_container.verticalForm) {
- CGFloat runX = glyphPosition.x;
- runX += line.position.y;
- CGFloat runWidth = CTRunGetTypographicBounds(run, CFRangeMake(0, 0), NULL, NULL, NULL);
- if (runX <= point.y && point.y <= runX + runWidth) {
- if (CTRunGetStatus(run) & kCTRunStatusRightToLeft) RTL = YES;
- break;
- }
- } else {
- CGFloat runX = glyphPosition.x;
- runX += line.position.x;
- CGFloat runWidth = CTRunGetTypographicBounds(run, CFRangeMake(0, 0), NULL, NULL, NULL);
- if (runX <= point.x && point.x <= runX + runWidth) {
- if (CTRunGetStatus(run) & kCTRunStatusRightToLeft) RTL = YES;
- break;
- }
- }
- }
- return RTL;
- }
- /**
- Correct the range's edge.
- */
- - (YYTextRange *)_correctedRangeWithEdge:(YYTextRange *)range {
- NSRange visibleRange = self.visibleRange;
- YYTextPosition *start = range.start;
- YYTextPosition *end = range.end;
-
- if (start.offset == visibleRange.location && start.affinity == YYTextAffinityBackward) {
- start = [YYTextPosition positionWithOffset:start.offset affinity:YYTextAffinityForward];
- }
-
- if (end.offset == visibleRange.location + visibleRange.length && start.affinity == YYTextAffinityForward) {
- end = [YYTextPosition positionWithOffset:end.offset affinity:YYTextAffinityBackward];
- }
-
- if (start != range.start || end != range.end) {
- range = [YYTextRange rangeWithStart:start end:end];
- }
- return range;
- }
- - (NSUInteger)lineIndexForRow:(NSUInteger)row {
- if (row >= _rowCount) return NSNotFound;
- return _lineRowsIndex[row];
- }
- - (NSUInteger)lineCountForRow:(NSUInteger)row {
- if (row >= _rowCount) return NSNotFound;
- if (row == _rowCount - 1) {
- return _lines.count - _lineRowsIndex[row];
- } else {
- return _lineRowsIndex[row + 1] - _lineRowsIndex[row];
- }
- }
- - (NSUInteger)rowIndexForLine:(NSUInteger)line {
- if (line >= _lines.count) return NSNotFound;
- return ((YYTextLine *)_lines[line]).row;
- }
- - (NSUInteger)lineIndexForPoint:(CGPoint)point {
- if (_lines.count == 0 || _rowCount == 0) return NSNotFound;
- NSUInteger rowIdx = [self _rowIndexForEdge:_container.verticalForm ? point.x : point.y];
- if (rowIdx == NSNotFound) return NSNotFound;
-
- NSUInteger lineIdx0 = _lineRowsIndex[rowIdx];
- NSUInteger lineIdx1 = rowIdx == _rowCount - 1 ? _lines.count - 1 : _lineRowsIndex[rowIdx + 1] - 1;
- for (NSUInteger i = lineIdx0; i <= lineIdx1; i++) {
- CGRect bounds = ((YYTextLine *)_lines[i]).bounds;
- if (CGRectContainsPoint(bounds, point)) return i;
- }
-
- return NSNotFound;
- }
- - (NSUInteger)closestLineIndexForPoint:(CGPoint)point {
- BOOL isVertical = _container.verticalForm;
- if (_lines.count == 0 || _rowCount == 0) return NSNotFound;
- NSUInteger rowIdx = [self _closestRowIndexForEdge:isVertical ? point.x : point.y];
- if (rowIdx == NSNotFound) return NSNotFound;
-
- NSUInteger lineIdx0 = _lineRowsIndex[rowIdx];
- NSUInteger lineIdx1 = rowIdx == _rowCount - 1 ? _lines.count - 1 : _lineRowsIndex[rowIdx + 1] - 1;
- if (lineIdx0 == lineIdx1) return lineIdx0;
-
- CGFloat minDistance = CGFLOAT_MAX;
- NSUInteger minIndex = lineIdx0;
- for (NSUInteger i = lineIdx0; i <= lineIdx1; i++) {
- CGRect bounds = ((YYTextLine *)_lines[i]).bounds;
- if (isVertical) {
- if (bounds.origin.y <= point.y && point.y <= bounds.origin.y + bounds.size.height) return i;
- CGFloat distance;
- if (point.y < bounds.origin.y) {
- distance = bounds.origin.y - point.y;
- } else {
- distance = point.y - (bounds.origin.y + bounds.size.height);
- }
- if (distance < minDistance) {
- minDistance = distance;
- minIndex = i;
- }
- } else {
- if (bounds.origin.x <= point.x && point.x <= bounds.origin.x + bounds.size.width) return i;
- CGFloat distance;
- if (point.x < bounds.origin.x) {
- distance = bounds.origin.x - point.x;
- } else {
- distance = point.x - (bounds.origin.x + bounds.size.width);
- }
- if (distance < minDistance) {
- minDistance = distance;
- minIndex = i;
- }
- }
- }
- return minIndex;
- }
- - (CGFloat)offsetForTextPosition:(NSUInteger)position lineIndex:(NSUInteger)lineIndex {
- if (lineIndex >= _lines.count) return CGFLOAT_MAX;
- YYTextLine *line = _lines[lineIndex];
- CFRange range = CTLineGetStringRange(line.CTLine);
- if (position < range.location || position > range.location + range.length) return CGFLOAT_MAX;
-
- CGFloat offset = CTLineGetOffsetForStringIndex(line.CTLine, position, NULL);
- return _container.verticalForm ? (offset + line.position.y) : (offset + line.position.x);
- }
- - (NSUInteger)textPositionForPoint:(CGPoint)point lineIndex:(NSUInteger)lineIndex {
- if (lineIndex >= _lines.count) return NSNotFound;
- YYTextLine *line = _lines[lineIndex];
- if (_container.verticalForm) {
- point.x = point.y - line.position.y;
- point.y = 0;
- } else {
- point.x -= line.position.x;
- point.y = 0;
- }
- CFIndex idx = CTLineGetStringIndexForPosition(line.CTLine, point);
- if (idx == kCFNotFound) return NSNotFound;
-
- /*
- If the emoji contains one or more variant form (such as ☔️ "\u2614\uFE0F")
- and the font size is smaller than 379/15, then each variant form ("\uFE0F")
- will rendered as a single blank glyph behind the emoji glyph. Maybe it's a
- bug in CoreText? Seems iOS8.3 fixes this problem.
-
- If the point hit the blank glyph, the CTLineGetStringIndexForPosition()
- returns the position before the emoji glyph, but it should returns the
- position after the emoji and variant form.
-
- Here's a workaround.
- */
- CFArrayRef runs = CTLineGetGlyphRuns(line.CTLine);
- for (NSUInteger r = 0, max = CFArrayGetCount(runs); r < max; r++) {
- CTRunRef run = CFArrayGetValueAtIndex(runs, r);
- CFRange range = CTRunGetStringRange(run);
- if (range.location <= idx && idx < range.location + range.length) {
- NSUInteger glyphCount = CTRunGetGlyphCount(run);
- if (glyphCount == 0) break;
- CFDictionaryRef attrs = CTRunGetAttributes(run);
- CTFontRef font = CFDictionaryGetValue(attrs, kCTFontAttributeName);
- if (!CTFontContainsColorBitmapGlyphs(font)) break;
-
- CFIndex indices[glyphCount];
- CGPoint positions[glyphCount];
- CTRunGetStringIndices(run, CFRangeMake(0, glyphCount), indices);
- CTRunGetPositions(run, CFRangeMake(0, glyphCount), positions);
- for (NSUInteger g = 0; g < glyphCount; g++) {
- NSUInteger gIdx = indices[g];
- if (gIdx == idx && g + 1 < glyphCount) {
- CGFloat right = positions[g + 1].x;
- if (point.x < right) break;
- NSUInteger next = indices[g + 1];
- do {
- if (next == range.location + range.length) break;
- unichar c = [_text.string characterAtIndex:next];
- if ((c == 0xFE0E || c == 0xFE0F)) { // unicode variant form for emoji style
- next++;
- } else break;
- }
- while (1);
- if (next != indices[g + 1]) idx = next;
- break;
- }
- }
- break;
- }
- }
- return idx;
- }
- - (YYTextPosition *)closestPositionToPoint:(CGPoint)point {
- BOOL isVertical = _container.verticalForm;
- // When call CTLineGetStringIndexForPosition() on ligature such as 'fi',
- // and the point `hit` the glyph's left edge, it may get the ligature inside offset.
- // I don't know why, maybe it's a bug of CoreText. Try to avoid it.
- if (isVertical) point.y += 0.00001234;
- else point.x += 0.00001234;
-
- NSUInteger lineIndex = [self closestLineIndexForPoint:point];
- if (lineIndex == NSNotFound) return nil;
- YYTextLine *line = _lines[lineIndex];
- __block NSUInteger position = [self textPositionForPoint:point lineIndex:lineIndex];
- if (position == NSNotFound) position = line.range.location;
- if (position <= _visibleRange.location) {
- return [YYTextPosition positionWithOffset:_visibleRange.location affinity:YYTextAffinityForward];
- } else if (position >= _visibleRange.location + _visibleRange.length) {
- return [YYTextPosition positionWithOffset:_visibleRange.location + _visibleRange.length affinity:YYTextAffinityBackward];
- }
-
- YYTextAffinity finalAffinity = YYTextAffinityForward;
- BOOL finalAffinityDetected = NO;
-
- // binding range
- NSRange bindingRange;
- YYTextBinding *binding = [_text attribute:YYTextBindingAttributeName atIndex:position longestEffectiveRange:&bindingRange inRange:NSMakeRange(0, _text.length)];
- if (binding && bindingRange.length > 0) {
- NSUInteger headLineIdx = [self lineIndexForPosition:[YYTextPosition positionWithOffset:bindingRange.location]];
- NSUInteger tailLineIdx = [self lineIndexForPosition:[YYTextPosition positionWithOffset:bindingRange.location + bindingRange.length affinity:YYTextAffinityBackward]];
- if (headLineIdx == lineIndex && lineIndex == tailLineIdx) { // all in same line
- CGFloat left = [self offsetForTextPosition:bindingRange.location lineIndex:lineIndex];
- CGFloat right = [self offsetForTextPosition:bindingRange.location + bindingRange.length lineIndex:lineIndex];
- if (left != CGFLOAT_MAX && right != CGFLOAT_MAX) {
- if (_container.isVerticalForm) {
- if (fabs(point.y - left) < fabs(point.y - right)) {
- position = bindingRange.location;
- finalAffinity = YYTextAffinityForward;
- } else {
- position = bindingRange.location + bindingRange.length;
- finalAffinity = YYTextAffinityBackward;
- }
- } else {
- if (fabs(point.x - left) < fabs(point.x - right)) {
- position = bindingRange.location;
- finalAffinity = YYTextAffinityForward;
- } else {
- position = bindingRange.location + bindingRange.length;
- finalAffinity = YYTextAffinityBackward;
- }
- }
- } else if (left != CGFLOAT_MAX) {
- position = left;
- finalAffinity = YYTextAffinityForward;
- } else if (right != CGFLOAT_MAX) {
- position = right;
- finalAffinity = YYTextAffinityBackward;
- }
- finalAffinityDetected = YES;
- } else if (headLineIdx == lineIndex) {
- CGFloat left = [self offsetForTextPosition:bindingRange.location lineIndex:lineIndex];
- if (left != CGFLOAT_MAX) {
- position = bindingRange.location;
- finalAffinity = YYTextAffinityForward;
- finalAffinityDetected = YES;
- }
- } else if (tailLineIdx == lineIndex) {
- CGFloat right = [self offsetForTextPosition:bindingRange.location + bindingRange.length lineIndex:lineIndex];
- if (right != CGFLOAT_MAX) {
- position = bindingRange.location + bindingRange.length;
- finalAffinity = YYTextAffinityBackward;
- finalAffinityDetected = YES;
- }
- } else {
- BOOL onLeft = NO, onRight = NO;
- if (headLineIdx != NSNotFound && tailLineIdx != NSNotFound) {
- if (abs((int)headLineIdx - (int)lineIndex) < abs((int)tailLineIdx - (int)lineIndex)) onLeft = YES;
- else onRight = YES;
- } else if (headLineIdx != NSNotFound) {
- onLeft = YES;
- } else if (tailLineIdx != NSNotFound) {
- onRight = YES;
- }
-
- if (onLeft) {
- CGFloat left = [self offsetForTextPosition:bindingRange.location lineIndex:headLineIdx];
- if (left != CGFLOAT_MAX) {
- lineIndex = headLineIdx;
- line = _lines[headLineIdx];
- position = bindingRange.location;
- finalAffinity = YYTextAffinityForward;
- finalAffinityDetected = YES;
- }
- } else if (onRight) {
- CGFloat right = [self offsetForTextPosition:bindingRange.location + bindingRange.length lineIndex:tailLineIdx];
- if (right != CGFLOAT_MAX) {
- lineIndex = tailLineIdx;
- line = _lines[tailLineIdx];
- position = bindingRange.location + bindingRange.length;
- finalAffinity = YYTextAffinityBackward;
- finalAffinityDetected = YES;
- }
- }
- }
- }
-
- // empty line
- if (line.range.length == 0) {
- BOOL behind = (_lines.count > 1 && lineIndex == _lines.count - 1); //end line
- return [YYTextPosition positionWithOffset:line.range.location affinity:behind ? YYTextAffinityBackward:YYTextAffinityForward];
- }
-
- // detect whether the line is a linebreak token
- if (line.range.length <= 2) {
- NSString *str = [_text.string substringWithRange:line.range];
- if (YYTextIsLinebreakString(str)) { // an empty line ("\r", "\n", "\r\n")
- return [YYTextPosition positionWithOffset:line.range.location];
- }
- }
-
- // above whole text frame
- if (lineIndex == 0 && (isVertical ? (point.x > line.right) : (point.y < line.top))) {
- position = 0;
- finalAffinity = YYTextAffinityForward;
- finalAffinityDetected = YES;
- }
- // below whole text frame
- if (lineIndex == _lines.count - 1 && (isVertical ? (point.x < line.left) : (point.y > line.bottom))) {
- position = line.range.location + line.range.length;
- finalAffinity = YYTextAffinityBackward;
- finalAffinityDetected = YES;
- }
-
- // There must be at least one non-linebreak char,
- // ignore the linebreak characters at line end if exists.
- if (position >= line.range.location + line.range.length - 1) {
- if (position > line.range.location) {
- unichar c1 = [_text.string characterAtIndex:position - 1];
- if (YYTextIsLinebreakChar(c1)) {
- position--;
- if (position > line.range.location) {
- unichar c0 = [_text.string characterAtIndex:position - 1];
- if (YYTextIsLinebreakChar(c0)) {
- position--;
- }
- }
- }
- }
- }
- if (position == line.range.location) {
- return [YYTextPosition positionWithOffset:position];
- }
- if (position == line.range.location + line.range.length) {
- return [YYTextPosition positionWithOffset:position affinity:YYTextAffinityBackward];
- }
-
- [self _insideComposedCharacterSequences:line position:position block: ^(CGFloat left, CGFloat right, NSUInteger prev, NSUInteger next) {
- if (isVertical) {
- position = fabs(left - point.y) < fabs(right - point.y) < (right ? prev : next);
- } else {
- position = fabs(left - point.x) < fabs(right - point.x) < (right ? prev : next);
- }
- }];
-
- [self _insideEmoji:line position:position block: ^(CGFloat left, CGFloat right, NSUInteger prev, NSUInteger next) {
- if (isVertical) {
- position = fabs(left - point.y) < fabs(right - point.y) < (right ? prev : next);
- } else {
- position = fabs(left - point.x) < fabs(right - point.x) < (right ? prev : next);
- }
- }];
-
- if (position < _visibleRange.location) position = _visibleRange.location;
- else if (position > _visibleRange.location + _visibleRange.length) position = _visibleRange.location + _visibleRange.length;
-
- if (!finalAffinityDetected) {
- CGFloat ofs = [self offsetForTextPosition:position lineIndex:lineIndex];
- if (ofs != CGFLOAT_MAX) {
- BOOL RTL = [self _isRightToLeftInLine:line atPoint:point];
- if (position >= line.range.location + line.range.length) {
- finalAffinity = RTL ? YYTextAffinityForward : YYTextAffinityBackward;
- } else if (position <= line.range.location) {
- finalAffinity = RTL ? YYTextAffinityBackward : YYTextAffinityForward;
- } else {
- finalAffinity = (ofs < (isVertical ? point.y : point.x) && !RTL) ? YYTextAffinityForward : YYTextAffinityBackward;
- }
- }
- }
-
- return [YYTextPosition positionWithOffset:position affinity:finalAffinity];
- }
- - (YYTextPosition *)positionForPoint:(CGPoint)point
- oldPosition:(YYTextPosition *)oldPosition
- otherPosition:(YYTextPosition *)otherPosition {
- if (!oldPosition || !otherPosition) {
- return oldPosition;
- }
- YYTextPosition *newPos = [self closestPositionToPoint:point];
- if (!newPos) return oldPosition;
- if ([newPos compare:otherPosition] == [oldPosition compare:otherPosition] &&
- newPos.offset != otherPosition.offset) {
- return newPos;
- }
- NSUInteger lineIndex = [self lineIndexForPosition:otherPosition];
- if (lineIndex == NSNotFound) return oldPosition;
- YYTextLine *line = _lines[lineIndex];
- YYRowEdge vertical = _lineRowsEdge[line.row];
- if (_container.verticalForm) {
- point.x = (vertical.head + vertical.foot) * 0.5;
- } else {
- point.y = (vertical.head + vertical.foot) * 0.5;
- }
- newPos = [self closestPositionToPoint:point];
- if ([newPos compare:otherPosition] == [oldPosition compare:otherPosition] &&
- newPos.offset != otherPosition.offset) {
- return newPos;
- }
-
- if (_container.isVerticalForm) {
- if ([oldPosition compare:otherPosition] == NSOrderedAscending) { // search backward
- YYTextRange *range = [self textRangeByExtendingPosition:otherPosition inDirection:UITextLayoutDirectionUp offset:1];
- if (range) return range.start;
- } else { // search forward
- YYTextRange *range = [self textRangeByExtendingPosition:otherPosition inDirection:UITextLayoutDirectionDown offset:1];
- if (range) return range.end;
- }
- } else {
- if ([oldPosition compare:otherPosition] == NSOrderedAscending) { // search backward
- YYTextRange *range = [self textRangeByExtendingPosition:otherPosition inDirection:UITextLayoutDirectionLeft offset:1];
- if (range) return range.start;
- } else { // search forward
- YYTextRange *range = [self textRangeByExtendingPosition:otherPosition inDirection:UITextLayoutDirectionRight offset:1];
- if (range) return range.end;
- }
- }
-
- return oldPosition;
- }
- - (YYTextRange *)textRangeAtPoint:(CGPoint)point {
- NSUInteger lineIndex = [self lineIndexForPoint:point];
- if (lineIndex == NSNotFound) return nil;
- NSUInteger textPosition = [self textPositionForPoint:point lineIndex:[self lineIndexForPoint:point]];
- if (textPosition == NSNotFound) return nil;
- YYTextPosition *pos = [self closestPositionToPoint:point];
- if (!pos) return nil;
-
- // get write direction
- BOOL RTL = [self _isRightToLeftInLine:_lines[lineIndex] atPoint:point];
- CGRect rect = [self caretRectForPosition:pos];
- if (CGRectIsNull(rect)) return nil;
-
- if (_container.verticalForm) {
- YYTextRange *range = [self textRangeByExtendingPosition:pos inDirection:(rect.origin.y >= point.y && !RTL) ? UITextLayoutDirectionUp:UITextLayoutDirectionDown offset:1];
- return range;
- } else {
- YYTextRange *range = [self textRangeByExtendingPosition:pos inDirection:(rect.origin.x >= point.x && !RTL) ? UITextLayoutDirectionLeft:UITextLayoutDirectionRight offset:1];
- return range;
- }
- }
- - (YYTextRange *)closestTextRangeAtPoint:(CGPoint)point {
- YYTextPosition *pos = [self closestPositionToPoint:point];
- if (!pos) return nil;
- NSUInteger lineIndex = [self lineIndexForPosition:pos];
- if (lineIndex == NSNotFound) return nil;
- YYTextLine *line = _lines[lineIndex];
- BOOL RTL = [self _isRightToLeftInLine:line atPoint:point];
- CGRect rect = [self caretRectForPosition:pos];
- if (CGRectIsNull(rect)) return nil;
-
- UITextLayoutDirection direction = UITextLayoutDirectionRight;
- if (pos.offset >= line.range.location + line.range.length) {
- if (direction != RTL) {
- direction = _container.verticalForm ? UITextLayoutDirectionUp : UITextLayoutDirectionLeft;
- } else {
- direction = _container.verticalForm ? UITextLayoutDirectionDown : UITextLayoutDirectionRight;
- }
- } else if (pos.offset <= line.range.location) {
- if (direction != RTL) {
- direction = _container.verticalForm ? UITextLayoutDirectionDown : UITextLayoutDirectionRight;
- } else {
- direction = _container.verticalForm ? UITextLayoutDirectionUp : UITextLayoutDirectionLeft;
- }
- } else {
- if (_container.verticalForm) {
- direction = (rect.origin.y >= point.y && !RTL) ? UITextLayoutDirectionUp:UITextLayoutDirectionDown;
- } else {
- direction = (rect.origin.x >= point.x && !RTL) ? UITextLayoutDirectionLeft:UITextLayoutDirectionRight;
- }
- }
-
- YYTextRange *range = [self textRangeByExtendingPosition:pos inDirection:direction offset:1];
- return range;
- }
- - (YYTextRange *)textRangeByExtendingPosition:(YYTextPosition *)position {
- NSUInteger visibleStart = _visibleRange.location;
- NSUInteger visibleEnd = _visibleRange.location + _visibleRange.length;
-
- if (!position) return nil;
- if (position.offset < visibleStart || position.offset > visibleEnd) return nil;
-
- // head or tail, returns immediately
- if (position.offset == visibleStart) {
- return [YYTextRange rangeWithRange:NSMakeRange(position.offset, 0)];
- } else if (position.offset == visibleEnd) {
- return [YYTextRange rangeWithRange:NSMakeRange(position.offset, 0) affinity:YYTextAffinityBackward];
- }
-
- // binding range
- NSRange tRange;
- YYTextBinding *binding = [_text attribute:YYTextBindingAttributeName atIndex:position.offset longestEffectiveRange:&tRange inRange:_visibleRange];
- if (binding && tRange.length > 0 && tRange.location < position.offset) {
- return [YYTextRange rangeWithRange:tRange];
- }
-
- // inside emoji or composed character sequences
- NSUInteger lineIndex = [self lineIndexForPosition:position];
- if (lineIndex != NSNotFound) {
- __block NSUInteger _prev, _next;
- BOOL emoji = NO, seq = NO;
-
- YYTextLine *line = _lines[lineIndex];
- emoji = [self _insideEmoji:line position:position.offset block: ^(CGFloat left, CGFloat right, NSUInteger prev, NSUInteger next) {
- _prev = prev;
- _next = next;
- }];
- if (!emoji) {
- seq = [self _insideComposedCharacterSequences:line position:position.offset block: ^(CGFloat left, CGFloat right, NSUInteger prev, NSUInteger next) {
- _prev = prev;
- _next = next;
- }];
- }
- if (emoji || seq) {
- return [YYTextRange rangeWithRange:NSMakeRange(_prev, _next - _prev)];
- }
- }
-
- // inside linebreak '\r\n'
- if (position.offset > visibleStart && position.offset < visibleEnd) {
- unichar c0 = [_text.string characterAtIndex:position.offset - 1];
- if ((c0 == '\r') && position.offset < visibleEnd) {
- unichar c1 = [_text.string characterAtIndex:position.offset];
- if (c1 == '\n') {
- return [YYTextRange rangeWithStart:[YYTextPosition positionWithOffset:position.offset - 1] end:[YYTextPosition positionWithOffset:position.offset + 1]];
- }
- }
- if (YYTextIsLinebreakChar(c0) && position.affinity == YYTextAffinityBackward) {
- NSString *str = [_text.string substringToIndex:position.offset];
- NSUInteger len = YYTextLinebreakTailLength(str);
- return [YYTextRange rangeWithStart:[YYTextPosition positionWithOffset:position.offset - len] end:[YYTextPosition positionWithOffset:position.offset]];
- }
- }
-
- return [YYTextRange rangeWithRange:NSMakeRange(position.offset, 0) affinity:position.affinity];
- }
- - (YYTextRange *)textRangeByExtendingPosition:(YYTextPosition *)position
- inDirection:(UITextLayoutDirection)direction
- offset:(NSInteger)offset {
- NSInteger visibleStart = _visibleRange.location;
- NSInteger visibleEnd = _visibleRange.location + _visibleRange.length;
-
- if (!position) return nil;
- if (position.offset < visibleStart || position.offset > visibleEnd) return nil;
- if (offset == 0) return [self textRangeByExtendingPosition:position];
-
- BOOL isVerticalForm = _container.verticalForm;
- BOOL verticalMove, forwardMove;
-
- if (isVerticalForm) {
- verticalMove = direction == UITextLayoutDirectionLeft || direction == UITextLayoutDirectionRight;
- forwardMove = direction == UITextLayoutDirectionLeft || direction == UITextLayoutDirectionDown;
- } else {
- verticalMove = direction == UITextLayoutDirectionUp || direction == UITextLayoutDirectionDown;
- forwardMove = direction == UITextLayoutDirectionDown || direction == UITextLayoutDirectionRight;
- }
-
- if (offset < 0) {
- forwardMove = !forwardMove;
- offset = -offset;
- }
-
- // head or tail, returns immediately
- if (!forwardMove && position.offset == visibleStart) {
- return [YYTextRange rangeWithRange:NSMakeRange(_visibleRange.location, 0)];
- } else if (forwardMove && position.offset == visibleEnd) {
- return [YYTextRange rangeWithRange:NSMakeRange(position.offset, 0) affinity:YYTextAffinityBackward];
- }
-
- // extend from position
- YYTextRange *fromRange = [self textRangeByExtendingPosition:position];
- if (!fromRange) return nil;
- YYTextRange *allForward = [YYTextRange rangeWithStart:fromRange.start end:[YYTextPosition positionWithOffset:visibleEnd]];
- YYTextRange *allBackward = [YYTextRange rangeWithStart:[YYTextPosition positionWithOffset:visibleStart] end:fromRange.end];
-
- if (verticalMove) { // up/down in text layout
- NSInteger lineIndex = [self lineIndexForPosition:position];
- if (lineIndex == NSNotFound) return nil;
-
- YYTextLine *line = _lines[lineIndex];
- NSInteger moveToRowIndex = (NSInteger)line.row + (forwardMove ? offset : -offset);
- if (moveToRowIndex < 0) return allBackward;
- else if (moveToRowIndex >= (NSInteger)_rowCount) return allForward;
-
- CGFloat ofs = [self offsetForTextPosition:position.offset lineIndex:lineIndex];
- if (ofs == CGFLOAT_MAX) return nil;
-
- NSUInteger moveToLineFirstIndex = [self lineIndexForRow:moveToRowIndex];
- NSUInteger moveToLineCount = [self lineCountForRow:moveToRowIndex];
- if (moveToLineFirstIndex == NSNotFound || moveToLineCount == NSNotFound || moveToLineCount == 0) return nil;
- CGFloat mostLeft = CGFLOAT_MAX, mostRight = -CGFLOAT_MAX;
- YYTextLine *mostLeftLine = nil, *mostRightLine = nil;
- NSUInteger insideIndex = NSNotFound;
- for (NSUInteger i = 0; i < moveToLineCount; i++) {
- NSUInteger lineIndex = moveToLineFirstIndex + i;
- YYTextLine *line = _lines[lineIndex];
- if (isVerticalForm) {
- if (line.top <= ofs && ofs <= line.bottom) {
- insideIndex = line.index;
- break;
- }
- if (line.top < mostLeft) {
- mostLeft = line.top;
- mostLeftLine = line;
- }
- if (line.bottom > mostRight) {
- mostRight = line.bottom;
- mostRightLine = line;
- }
- } else {
- if (line.left <= ofs && ofs <= line.right) {
- insideIndex = line.index;
- break;
- }
- if (line.left < mostLeft) {
- mostLeft = line.left;
- mostLeftLine = line;
- }
- if (line.right > mostRight) {
- mostRight = line.right;
- mostRightLine = line;
- }
- }
- }
- BOOL afinityEdge = NO;
- if (insideIndex == NSNotFound) {
- if (ofs <= mostLeft) {
- insideIndex = mostLeftLine.index;
- } else {
- insideIndex = mostRightLine.index;
- }
- afinityEdge = YES;
- }
- YYTextLine *insideLine = _lines[insideIndex];
- NSUInteger pos;
- if (isVerticalForm) {
- pos = [self textPositionForPoint:CGPointMake(insideLine.position.x, ofs) lineIndex:insideIndex];
- } else {
- pos = [self textPositionForPoint:CGPointMake(ofs, insideLine.position.y) lineIndex:insideIndex];
- }
- if (pos == NSNotFound) return nil;
- YYTextPosition *extPos;
- if (afinityEdge) {
- if (pos == insideLine.range.location + insideLine.range.length) {
- NSString *subStr = [_text.string substringWithRange:insideLine.range];
- NSUInteger lineBreakLen = YYTextLinebreakTailLength(subStr);
- extPos = [YYTextPosition positionWithOffset:pos - lineBreakLen];
- } else {
- extPos = [YYTextPosition positionWithOffset:pos];
- }
- } else {
- extPos = [YYTextPosition positionWithOffset:pos];
- }
- YYTextRange *ext = [self textRangeByExtendingPosition:extPos];
- if (!ext) return nil;
- if (forwardMove) {
- return [YYTextRange rangeWithStart:fromRange.start end:ext.end];
- } else {
- return [YYTextRange rangeWithStart:ext.start end:fromRange.end];
- }
-
- } else { // left/right in text layout
- YYTextPosition *toPosition = [YYTextPosition positionWithOffset:position.offset + (forwardMove ? offset : -offset)];
- if (toPosition.offset <= visibleStart) return allBackward;
- else if (toPosition.offset >= visibleEnd) return allForward;
-
- YYTextRange *toRange = [self textRangeByExtendingPosition:toPosition];
- if (!toRange) return nil;
-
- NSInteger start = MIN(fromRange.start.offset, toRange.start.offset);
- NSInteger end = MAX(fromRange.end.offset, toRange.end.offset);
- return [YYTextRange rangeWithRange:NSMakeRange(start, end - start)];
- }
- }
- - (NSUInteger)lineIndexForPosition:(YYTextPosition *)position {
- if (!position) return NSNotFound;
- if (_lines.count == 0) return NSNotFound;
- NSUInteger location = position.offset;
- NSInteger lo = 0, hi = _lines.count - 1, mid = 0;
- if (position.affinity == YYTextAffinityBackward) {
- while (lo <= hi) {
- mid = (lo + hi) / 2;
- YYTextLine *line = _lines[mid];
- NSRange range = line.range;
- if (range.location < location && location <= range.location + range.length) {
- return mid;
- }
- if (location <= range.location) {
- hi = mid - 1;
- } else {
- lo = mid + 1;
- }
- }
- } else {
- while (lo <= hi) {
- mid = (lo + hi) / 2;
- YYTextLine *line = _lines[mid];
- NSRange range = line.range;
- if (range.location <= location && location < range.location + range.length) {
- return mid;
- }
- if (location < range.location) {
- hi = mid - 1;
- } else {
- lo = mid + 1;
- }
- }
- }
- return NSNotFound;
- }
- - (CGPoint)linePositionForPosition:(YYTextPosition *)position {
- NSUInteger lineIndex = [self lineIndexForPosition:position];
- if (lineIndex == NSNotFound) return CGPointZero;
- YYTextLine *line = _lines[lineIndex];
- CGFloat offset = [self offsetForTextPosition:position.offset lineIndex:lineIndex];
- if (offset == CGFLOAT_MAX) return CGPointZero;
- if (_container.verticalForm) {
- return CGPointMake(line.position.x, offset);
- } else {
- return CGPointMake(offset, line.position.y);
- }
- }
- - (CGRect)caretRectForPosition:(YYTextPosition *)position {
- NSUInteger lineIndex = [self lineIndexForPosition:position];
- if (lineIndex == NSNotFound) return CGRectNull;
- YYTextLine *line = _lines[lineIndex];
- CGFloat offset = [self offsetForTextPosition:position.offset lineIndex:lineIndex];
- if (offset == CGFLOAT_MAX) return CGRectNull;
- if (_container.verticalForm) {
- return CGRectMake(line.bounds.origin.x, offset, line.bounds.size.width, 0);
- } else {
- return CGRectMake(offset, line.bounds.origin.y, 0, line.bounds.size.height);
- }
- }
- - (CGRect)firstRectForRange:(YYTextRange *)range {
- range = [self _correctedRangeWithEdge:range];
-
- NSUInteger startLineIndex = [self lineIndexForPosition:range.start];
- NSUInteger endLineIndex = [self lineIndexForPosition:range.end];
- if (startLineIndex == NSNotFound || endLineIndex == NSNotFound) return CGRectNull;
- if (startLineIndex > endLineIndex) return CGRectNull;
- YYTextLine *startLine = _lines[startLineIndex];
- YYTextLine *endLine = _lines[endLineIndex];
- NSMutableArray *lines = [NSMutableArray new];
- for (NSUInteger i = startLineIndex; i <= startLineIndex; i++) {
- YYTextLine *line = _lines[i];
- if (line.row != startLine.row) break;
- [lines addObject:line];
- }
- if (_container.verticalForm) {
- if (lines.count == 1) {
- CGFloat top = [self offsetForTextPosition:range.start.offset lineIndex:startLineIndex];
- CGFloat bottom;
- if (startLine == endLine) {
- bottom = [self offsetForTextPosition:range.end.offset lineIndex:startLineIndex];
- } else {
- bottom = startLine.bottom;
- }
- if (top == CGFLOAT_MAX || bottom == CGFLOAT_MAX) return CGRectNull;
- if (top > bottom) YY_SWAP(top, bottom);
- return CGRectMake(startLine.left, top, startLine.width, bottom - top);
- } else {
- CGFloat top = [self offsetForTextPosition:range.start.offset lineIndex:startLineIndex];
- CGFloat bottom = startLine.bottom;
- if (top == CGFLOAT_MAX || bottom == CGFLOAT_MAX) return CGRectNull;
- if (top > bottom) YY_SWAP(top, bottom);
- CGRect rect = CGRectMake(startLine.left, top, startLine.width, bottom - top);
- for (NSUInteger i = 1; i < lines.count; i++) {
- YYTextLine *line = lines[i];
- rect = CGRectUnion(rect, line.bounds);
- }
- return rect;
- }
- } else {
- if (lines.count == 1) {
- CGFloat left = [self offsetForTextPosition:range.start.offset lineIndex:startLineIndex];
- CGFloat right;
- if (startLine == endLine) {
- right = [self offsetForTextPosition:range.end.offset lineIndex:startLineIndex];
- } else {
- right = startLine.right;
- }
- if (left == CGFLOAT_MAX || right == CGFLOAT_MAX) return CGRectNull;
- if (left > right) YY_SWAP(left, right);
- return CGRectMake(left, startLine.top, right - left, startLine.height);
- } else {
- CGFloat left = [self offsetForTextPosition:range.start.offset lineIndex:startLineIndex];
- CGFloat right = startLine.right;
- if (left == CGFLOAT_MAX || right == CGFLOAT_MAX) return CGRectNull;
- if (left > right) YY_SWAP(left, right);
- CGRect rect = CGRectMake(left, startLine.top, right - left, startLine.height);
- for (NSUInteger i = 1; i < lines.count; i++) {
- YYTextLine *line = lines[i];
- rect = CGRectUnion(rect, line.bounds);
- }
- return rect;
- }
- }
- }
- - (CGRect)rectForRange:(YYTextRange *)range {
- NSArray *rects = [self selectionRectsForRange:range];
- if (rects.count == 0) return CGRectNull;
- CGRect rectUnion = ((YYTextSelectionRect *)rects.firstObject).rect;
- for (NSUInteger i = 1; i < rects.count; i++) {
- YYTextSelectionRect *rect = rects[i];
- rectUnion = CGRectUnion(rectUnion, rect.rect);
- }
- return rectUnion;
- }
- - (NSArray *)selectionRectsForRange:(YYTextRange *)range {
- range = [self _correctedRangeWithEdge:range];
-
- BOOL isVertical = _container.verticalForm;
- NSMutableArray *rects = [NSMutableArray array];
- if (!range) return rects;
-
- NSUInteger startLineIndex = [self lineIndexForPosition:range.start];
- NSUInteger endLineIndex = [self lineIndexForPosition:range.end];
- if (startLineIndex == NSNotFound || endLineIndex == NSNotFound) return rects;
- if (startLineIndex > endLineIndex) YY_SWAP(startLineIndex, endLineIndex);
- YYTextLine *startLine = _lines[startLineIndex];
- YYTextLine *endLine = _lines[endLineIndex];
- CGFloat offsetStart = [self offsetForTextPosition:range.start.offset lineIndex:startLineIndex];
- CGFloat offsetEnd = [self offsetForTextPosition:range.end.offset lineIndex:endLineIndex];
-
- YYTextSelectionRect *start = [YYTextSelectionRect new];
- if (isVertical) {
- start.rect = CGRectMake(startLine.left, offsetStart, startLine.width, 0);
- } else {
- start.rect = CGRectMake(offsetStart, startLine.top, 0, startLine.height);
- }
- start.containsStart = YES;
- start.isVertical = isVertical;
- [rects addObject:start];
-
- YYTextSelectionRect *end = [YYTextSelectionRect new];
- if (isVertical) {
- end.rect = CGRectMake(endLine.left, offsetEnd, endLine.width, 0);
- } else {
- end.rect = CGRectMake(offsetEnd, endLine.top, 0, endLine.height);
- }
- end.containsEnd = YES;
- end.isVertical = isVertical;
- [rects addObject:end];
-
- if (startLine.row == endLine.row) { // same row
- if (offsetStart > offsetEnd) YY_SWAP(offsetStart, offsetEnd);
- YYTextSelectionRect *rect = [YYTextSelectionRect new];
- if (isVertical) {
- rect.rect = CGRectMake(startLine.bounds.origin.x, offsetStart, MAX(startLine.width, endLine.width), offsetEnd - offsetStart);
- } else {
- rect.rect = CGRectMake(offsetStart, startLine.bounds.origin.y, offsetEnd - offsetStart, MAX(startLine.height, endLine.height));
- }
- rect.isVertical = isVertical;
- [rects addObject:rect];
-
- } else { // more than one row
-
- // start line select rect
- YYTextSelectionRect *topRect = [YYTextSelectionRect new];
- topRect.isVertical = isVertical;
- CGFloat topOffset = [self offsetForTextPosition:range.start.offset lineIndex:startLineIndex];
- CTRunRef topRun = [self _runForLine:startLine position:range.start];
- if (topRun && (CTRunGetStatus(topRun) & kCTRunStatusRightToLeft)) {
- if (isVertical) {
- topRect.rect = CGRectMake(startLine.left, _container.path ? startLine.top : _container.insets.top, startLine.width, topOffset - startLine.top);
- } else {
- topRect.rect = CGRectMake(_container.path ? startLine.left : _container.insets.left, startLine.top, topOffset - startLine.left, startLine.height);
- }
- topRect.writingDirection = UITextWritingDirectionRightToLeft;
- } else {
- if (isVertical) {
- topRect.rect = CGRectMake(startLine.left, topOffset, startLine.width, (_container.path ? startLine.bottom : _container.size.height - _container.insets.bottom) - topOffset);
- } else {
- topRect.rect = CGRectMake(topOffset, startLine.top, (_container.path ? startLine.right : _container.size.width - _container.insets.right) - topOffset, startLine.height);
- }
- }
- [rects addObject:topRect];
-
- // end line select rect
- YYTextSelectionRect *bottomRect = [YYTextSelectionRect new];
- bottomRect.isVertical = isVertical;
- CGFloat bottomOffset = [self offsetForTextPosition:range.end.offset lineIndex:endLineIndex];
- CTRunRef bottomRun = [self _runForLine:endLine position:range.end];
- if (bottomRun && (CTRunGetStatus(bottomRun) & kCTRunStatusRightToLeft)) {
- if (isVertical) {
- bottomRect.rect = CGRectMake(endLine.left, bottomOffset, endLine.width, (_container.path ? endLine.bottom : _container.size.height - _container.insets.bottom) - bottomOffset);
- } else {
- bottomRect.rect = CGRectMake(bottomOffset, endLine.top, (_container.path ? endLine.right : _container.size.width - _container.insets.right) - bottomOffset, endLine.height);
- }
- bottomRect.writingDirection = UITextWritingDirectionRightToLeft;
- } else {
- if (isVertical) {
- CGFloat top = _container.path ? endLine.top : _container.insets.top;
- bottomRect.rect = CGRectMake(endLine.left, top, endLine.width, bottomOffset - top);
- } else {
- CGFloat left = _container.path ? endLine.left : _container.insets.left;
- bottomRect.rect = CGRectMake(left, endLine.top, bottomOffset - left, endLine.height);
- }
- }
- [rects addObject:bottomRect];
-
- if (endLineIndex - startLineIndex >= 2) {
- CGRect r = CGRectZero;
- BOOL startLineDetected = NO;
- for (NSUInteger l = startLineIndex + 1; l < endLineIndex; l++) {
- YYTextLine *line = _lines[l];
- if (line.row == startLine.row || line.row == endLine.row) continue;
- if (!startLineDetected) {
- r = line.bounds;
- startLineDetected = YES;
- } else {
- r = CGRectUnion(r, line.bounds);
- }
- }
- if (startLineDetected) {
- if (isVertical) {
- if (!_container.path) {
- r.origin.y = _container.insets.top;
- r.size.height = _container.size.height - _container.insets.bottom - _container.insets.top;
- }
- r.size.width = CGRectGetMinX(topRect.rect) - CGRectGetMaxX(bottomRect.rect);
- r.origin.x = CGRectGetMaxX(bottomRect.rect);
- } else {
- if (!_container.path) {
- r.origin.x = _container.insets.left;
- r.size.width = _container.size.width - _container.insets.right - _container.insets.left;
- }
- r.origin.y = CGRectGetMaxY(topRect.rect);
- r.size.height = bottomRect.rect.origin.y - r.origin.y;
- }
-
- YYTextSelectionRect *rect = [YYTextSelectionRect new];
- rect.rect = r;
- rect.isVertical = isVertical;
- [rects addObject:rect];
- }
- } else {
- if (isVertical) {
- CGRect r0 = bottomRect.rect;
- CGRect r1 = topRect.rect;
- CGFloat mid = (CGRectGetMaxX(r0) + CGRectGetMinX(r1)) * 0.5;
- r0.size.width = mid - r0.origin.x;
- CGFloat r1ofs = r1.origin.x - mid;
- r1.origin.x -= r1ofs;
- r1.size.width += r1ofs;
- topRect.rect = r1;
- bottomRect.rect = r0;
- } else {
- CGRect r0 = topRect.rect;
- CGRect r1 = bottomRect.rect;
- CGFloat mid = (CGRectGetMaxY(r0) + CGRectGetMinY(r1)) * 0.5;
- r0.size.height = mid - r0.origin.y;
- CGFloat r1ofs = r1.origin.y - mid;
- r1.origin.y -= r1ofs;
- r1.size.height += r1ofs;
- topRect.rect = r0;
- bottomRect.rect = r1;
- }
- }
- }
- return rects;
- }
- - (NSArray *)selectionRectsWithoutStartAndEndForRange:(YYTextRange *)range {
- NSMutableArray *rects = [self selectionRectsForRange:range].mutableCopy;
- for (NSInteger i = 0, max = rects.count; i < max; i++) {
- YYTextSelectionRect *rect = rects[i];
- if (rect.containsStart || rect.containsEnd) {
- [rects removeObjectAtIndex:i];
- i--;
- max--;
- }
- }
- return rects;
- }
- - (NSArray *)selectionRectsWithOnlyStartAndEndForRange:(YYTextRange *)range {
- NSMutableArray *rects = [self selectionRectsForRange:range].mutableCopy;
- for (NSInteger i = 0, max = rects.count; i < max; i++) {
- YYTextSelectionRect *rect = rects[i];
- if (!rect.containsStart && !rect.containsEnd) {
- [rects removeObjectAtIndex:i];
- i--;
- max--;
- }
- }
- return rects;
- }
- #pragma mark - Draw
- typedef NS_OPTIONS(NSUInteger, YYTextDecorationType) {
- YYTextDecorationTypeUnderline = 1 << 0,
- YYTextDecorationTypeStrikethrough = 1 << 1,
- };
- typedef NS_OPTIONS(NSUInteger, YYTextBorderType) {
- YYTextBorderTypeBackgound = 1 << 0,
- YYTextBorderTypeNormal = 1 << 1,
- };
- static CGRect YYTextMergeRectInSameLine(CGRect rect1, CGRect rect2, BOOL isVertical) {
- if (isVertical) {
- CGFloat top = MIN(rect1.origin.y, rect2.origin.y);
- CGFloat bottom = MAX(rect1.origin.y + rect1.size.height, rect2.origin.y + rect2.size.height);
- CGFloat width = MAX(rect1.size.width, rect2.size.width);
- return CGRectMake(rect1.origin.x, top, width, bottom - top);
- } else {
- CGFloat left = MIN(rect1.origin.x, rect2.origin.x);
- CGFloat right = MAX(rect1.origin.x + rect1.size.width, rect2.origin.x + rect2.size.width);
- CGFloat height = MAX(rect1.size.height, rect2.size.height);
- return CGRectMake(left, rect1.origin.y, right - left, height);
- }
- }
- static void YYTextGetRunsMaxMetric(CFArrayRef runs, CGFloat *xHeight, CGFloat *underlinePosition, CGFloat *lineThickness) {
- CGFloat maxXHeight = 0;
- CGFloat maxUnderlinePos = 0;
- CGFloat maxLineThickness = 0;
- for (NSUInteger i = 0, max = CFArrayGetCount(runs); i < max; i++) {
- CTRunRef run = CFArrayGetValueAtIndex(runs, i);
- CFDictionaryRef attrs = CTRunGetAttributes(run);
- if (attrs) {
- CTFontRef font = CFDictionaryGetValue(attrs, kCTFontAttributeName);
- if (font) {
- CGFloat xHeight = CTFontGetXHeight(font);
- if (xHeight > maxXHeight) maxXHeight = xHeight;
- CGFloat underlinePos = CTFontGetUnderlinePosition(font);
- if (underlinePos < maxUnderlinePos) maxUnderlinePos = underlinePos;
- CGFloat lineThickness = CTFontGetUnderlineThickness(font);
- if (lineThickness > maxLineThickness) maxLineThickness = lineThickness;
- }
- }
- }
- if (xHeight) *xHeight = maxXHeight;
- if (underlinePosition) *underlinePosition = maxUnderlinePos;
- if (lineThickness) *lineThickness = maxLineThickness;
- }
- static void YYTextDrawRun(YYTextLine *line, CTRunRef run, CGContextRef context, CGSize size, BOOL isVertical, NSArray *runRanges, CGFloat verticalOffset) {
- CGAffineTransform runTextMatrix = CTRunGetTextMatrix(run);
- BOOL runTextMatrixIsID = CGAffineTransformIsIdentity(runTextMatrix);
-
- CFDictionaryRef runAttrs = CTRunGetAttributes(run);
- NSValue *glyphTransformValue = CFDictionaryGetValue(runAttrs, (__bridge const void *)(YYTextGlyphTransformAttributeName));
- if (!isVertical && !glyphTransformValue) { // draw run
- if (!runTextMatrixIsID) {
- CGContextSaveGState(context);
- CGAffineTransform trans = CGContextGetTextMatrix(context);
- CGContextSetTextMatrix(context, CGAffineTransformConcat(trans, runTextMatrix));
- }
- CTRunDraw(run, context, CFRangeMake(0, 0));
- if (!runTextMatrixIsID) {
- CGContextRestoreGState(context);
- }
- } else { // draw glyph
- CTFontRef runFont = CFDictionaryGetValue(runAttrs, kCTFontAttributeName);
- if (!runFont) return;
- NSUInteger glyphCount = CTRunGetGlyphCount(run);
- if (glyphCount <= 0) return;
-
- CGGlyph glyphs[glyphCount];
- CGPoint glyphPositions[glyphCount];
- CTRunGetGlyphs(run, CFRangeMake(0, 0), glyphs);
- CTRunGetPositions(run, CFRangeMake(0, 0), glyphPositions);
-
- CGColorRef fillColor = (CGColorRef)CFDictionaryGetValue(runAttrs, kCTForegroundColorAttributeName);
- fillColor = YYTextGetCGColor(fillColor);
- NSNumber *strokeWidth = CFDictionaryGetValue(runAttrs, kCTStrokeWidthAttributeName);
-
- CGContextSaveGState(context); {
- CGContextSetFillColorWithColor(context, fillColor);
- if ((strokeWidth == nil) || strokeWidth.floatValue == 0) {
- CGContextSetTextDrawingMode(context, kCGTextFill);
- } else {
- CGColorRef strokeColor = (CGColorRef)CFDictionaryGetValue(runAttrs, kCTStrokeColorAttributeName);
- if (!strokeColor) strokeColor = fillColor;
- CGContextSetStrokeColorWithColor(context, strokeColor);
- CGContextSetLineWidth(context, CTFontGetSize(runFont) * fabs(strokeWidth.floatValue * 0.01));
- if (strokeWidth.floatValue > 0) {
- CGContextSetTextDrawingMode(context, kCGTextStroke);
- } else {
- CGContextSetTextDrawingMode(context, kCGTextFillStroke);
- }
- }
-
- if (isVertical) {
- CFIndex runStrIdx[glyphCount + 1];
- CTRunGetStringIndices(run, CFRangeMake(0, 0), runStrIdx);
- CFRange runStrRange = CTRunGetStringRange(run);
- runStrIdx[glyphCount] = runStrRange.location + runStrRange.length;
- CGSize glyphAdvances[glyphCount];
- CTRunGetAdvances(run, CFRangeMake(0, 0), glyphAdvances);
- CGFloat ascent = CTFontGetAscent(runFont);
- CGFloat descent = CTFontGetDescent(runFont);
- CGAffineTransform glyphTransform = glyphTransformValue.CGAffineTransformValue;
- CGPoint zeroPoint = CGPointZero;
-
- for (YYTextRunGlyphRange *oneRange in runRanges) {
- NSRange range = oneRange.glyphRangeInRun;
- NSUInteger rangeMax = range.location + range.length;
- YYTextRunGlyphDrawMode mode = oneRange.drawMode;
-
- for (NSUInteger g = range.location; g < rangeMax; g++) {
- CGContextSaveGState(context); {
- CGContextSetTextMatrix(context, CGAffineTransformIdentity);
- if (glyphTransformValue) {
- CGContextSetTextMatrix(context, glyphTransform);
- }
- if (mode) { // CJK glyph, need rotated
- CGFloat ofs = (ascent - descent) * 0.5;
- CGFloat w = glyphAdvances[g].width * 0.5;
- CGFloat x = x = line.position.x + verticalOffset + glyphPositions[g].y + (ofs - w);
- CGFloat y = -line.position.y + size.height - glyphPositions[g].x - (ofs + w);
- if (mode == YYTextRunGlyphDrawModeVerticalRotateMove) {
- x += w;
- y += w;
- }
- CGContextSetTextPosition(context, x, y);
- } else {
- CGContextRotateCTM(context, DegreesToRadians(-90));
- CGContextSetTextPosition(context,
- line.position.y - size.height + glyphPositions[g].x,
- line.position.x + verticalOffset + glyphPositions[g].y);
- }
-
- if (CTFontContainsColorBitmapGlyphs(runFont)) {
- CTFontDrawGlyphs(runFont, glyphs + g, &zeroPoint, 1, context);
- } else {
- CGFontRef cgFont = CTFontCopyGraphicsFont(runFont, NULL);
- CGContextSetFont(context, cgFont);
- CGContextSetFontSize(context, CTFontGetSize(runFont));
- CGContextShowGlyphsAtPositions(context, glyphs + g, &zeroPoint, 1);
- CGFontRelease(cgFont);
- }
- } CGContextRestoreGState(context);
- }
- }
- } else { // not vertical
- if (glyphTransformValue) {
- CFIndex runStrIdx[glyphCount + 1];
- CTRunGetStringIndices(run, CFRangeMake(0, 0), runStrIdx);
- CFRange runStrRange = CTRunGetStringRange(run);
- runStrIdx[glyphCount] = runStrRange.location + runStrRange.length;
- CGSize glyphAdvances[glyphCount];
- CTRunGetAdvances(run, CFRangeMake(0, 0), glyphAdvances);
- CGAffineTransform glyphTransform = glyphTransformValue.CGAffineTransformValue;
- CGPoint zeroPoint = CGPointZero;
-
- for (NSUInteger g = 0; g < glyphCount; g++) {
- CGContextSaveGState(context); {
- CGContextSetTextMatrix(context, CGAffineTransformIdentity);
- CGContextSetTextMatrix(context, glyphTransform);
- CGContextSetTextPosition(context,
- line.position.x + glyphPositions[g].x,
- size.height - (line.position.y + glyphPositions[g].y));
-
- if (CTFontContainsColorBitmapGlyphs(runFont)) {
- CTFontDrawGlyphs(runFont, glyphs + g, &zeroPoint, 1, context);
- } else {
- CGFontRef cgFont = CTFontCopyGraphicsFont(runFont, NULL);
- CGContextSetFont(context, cgFont);
- CGContextSetFontSize(context, CTFontGetSize(runFont));
- CGContextShowGlyphsAtPositions(context, glyphs + g, &zeroPoint, 1);
- CGFontRelease(cgFont);
- }
- } CGContextRestoreGState(context);
- }
- } else {
- if (CTFontContainsColorBitmapGlyphs(runFont)) {
- CTFontDrawGlyphs(runFont, glyphs, glyphPositions, glyphCount, context);
- } else {
- CGFontRef cgFont = CTFontCopyGraphicsFont(runFont, NULL);
- CGContextSetFont(context, cgFont);
- CGContextSetFontSize(context, CTFontGetSize(runFont));
- CGContextShowGlyphsAtPositions(context, glyphs, glyphPositions, glyphCount);
- CGFontRelease(cgFont);
- }
- }
- }
-
- } CGContextRestoreGState(context);
- }
- }
- static void YYTextSetLinePatternInContext(YYTextLineStyle style, CGFloat width, CGFloat phase, CGContextRef context){
- CGContextSetLineWidth(context, width);
- CGContextSetLineCap(context, kCGLineCapButt);
- CGContextSetLineJoin(context, kCGLineJoinMiter);
-
- CGFloat dash = 12, dot = 5, space = 3;
- NSUInteger pattern = style & 0xF00;
- if (pattern == YYTextLineStylePatternSolid) {
- CGContextSetLineDash(context, phase, NULL, 0);
- } else if (pattern == YYTextLineStylePatternDot) {
- CGFloat lengths[2] = {width * dot, width * space};
- CGContextSetLineDash(context, phase, lengths, 2);
- } else if (pattern == YYTextLineStylePatternDash) {
- CGFloat lengths[2] = {width * dash, width * space};
- CGContextSetLineDash(context, phase, lengths, 2);
- } else if (pattern == YYTextLineStylePatternDashDot) {
- CGFloat lengths[4] = {width * dash, width * space, width * dot, width * space};
- CGContextSetLineDash(context, phase, lengths, 4);
- } else if (pattern == YYTextLineStylePatternDashDotDot) {
- CGFloat lengths[6] = {width * dash, width * space,width * dot, width * space, width * dot, width * space};
- CGContextSetLineDash(context, phase, lengths, 6);
- } else if (pattern == YYTextLineStylePatternCircleDot) {
- CGFloat lengths[2] = {width * 0, width * 3};
- CGContextSetLineDash(context, phase, lengths, 2);
- CGContextSetLineCap(context, kCGLineCapRound);
- CGContextSetLineJoin(context, kCGLineJoinRound);
- }
- }
- static void YYTextDrawBorderRects(CGContextRef context, CGSize size, YYTextBorder *border, NSArray *rects, BOOL isVertical) {
- if (rects.count == 0) return;
-
- YYTextShadow *shadow = border.shadow;
- if (shadow.color) {
- CGContextSaveGState(context);
- CGContextSetShadowWithColor(context, shadow.offset, shadow.radius, shadow.color.CGColor);
- CGContextBeginTransparencyLayer(context, NULL);
- }
-
- NSMutableArray *paths = [NSMutableArray new];
- for (NSValue *value in rects) {
- CGRect rect = value.CGRectValue;
- if (isVertical) {
- rect = UIEdgeInsetsInsetRect(rect, UIEdgeInsetRotateVertical(border.insets));
- } else {
- rect = UIEdgeInsetsInsetRect(rect, border.insets);
- }
- rect = CGRectPixelRound(rect);
- UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:border.cornerRadius];
- [path closePath];
- [paths addObject:path];
- }
-
- if (border.fillColor) {
- CGContextSaveGState(context);
- CGContextSetFillColorWithColor(context, border.fillColor.CGColor);
- for (UIBezierPath *path in paths) {
- CGContextAddPath(context, path.CGPath);
- }
- CGContextFillPath(context);
- CGContextRestoreGState(context);
- }
-
- if (border.strokeColor && border.lineStyle > 0 && border.strokeWidth > 0) {
-
- //-------------------------- single line ------------------------------//
- CGContextSaveGState(context);
- for (UIBezierPath *path in paths) {
- CGRect bounds = CGRectUnion(path.bounds, (CGRect){CGPointZero, size});
- bounds = CGRectInset(bounds, -2 * border.strokeWidth, -2 * border.strokeWidth);
- CGContextAddRect(context, bounds);
- CGContextAddPath(context, path.CGPath);
- CGContextEOClip(context);
- }
- [border.strokeColor setStroke];
- YYTextSetLinePatternInContext(border.lineStyle, border.strokeWidth, 0, context);
- CGFloat inset = -border.strokeWidth * 0.5;
- if ((border.lineStyle & 0xFF) == YYTextLineStyleThick) {
- inset *= 2;
- CGContextSetLineWidth(context, border.strokeWidth * 2);
- }
- CGFloat radiusDelta = -inset;
- if (border.cornerRadius <= 0) {
- radiusDelta = 0;
- }
- CGContextSetLineJoin(context, border.lineJoin);
- for (NSValue *value in rects) {
- CGRect rect = value.CGRectValue;
- if (isVertical) {
- rect = UIEdgeInsetsInsetRect(rect, UIEdgeInsetRotateVertical(border.insets));
- } else {
- rect = UIEdgeInsetsInsetRect(rect, border.insets);
- }
- rect = CGRectInset(rect, inset, inset);
- UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:border.cornerRadius + radiusDelta];
- [path closePath];
- CGContextAddPath(context, path.CGPath);
- }
- CGContextStrokePath(context);
- CGContextRestoreGState(context);
-
- //------------------------- second line ------------------------------//
- if ((border.lineStyle & 0xFF) == YYTextLineStyleDouble) {
- CGContextSaveGState(context);
- CGFloat inset = -border.strokeWidth * 2;
- for (NSValue *value in rects) {
- CGRect rect = value.CGRectValue;
- rect = UIEdgeInsetsInsetRect(rect, border.insets);
- rect = CGRectInset(rect, inset, inset);
- UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:border.cornerRadius + 2 * border.strokeWidth];
- [path closePath];
-
- CGRect bounds = CGRectUnion(path.bounds, (CGRect){CGPointZero, size});
- bounds = CGRectInset(bounds, -2 * border.strokeWidth, -2 * border.strokeWidth);
- CGContextAddRect(context, bounds);
- CGContextAddPath(context, path.CGPath);
- CGContextEOClip(context);
- }
- CGContextSetStrokeColorWithColor(context, border.strokeColor.CGColor);
- YYTextSetLinePatternInContext(border.lineStyle, border.strokeWidth, 0, context);
- CGContextSetLineJoin(context, border.lineJoin);
- inset = -border.strokeWidth * 2.5;
- radiusDelta = border.strokeWidth * 2;
- if (border.cornerRadius <= 0) {
- radiusDelta = 0;
- }
- for (NSValue *value in rects) {
- CGRect rect = value.CGRectValue;
- rect = UIEdgeInsetsInsetRect(rect, border.insets);
- rect = CGRectInset(rect, inset, inset);
- UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:border.cornerRadius + radiusDelta];
- [path closePath];
- CGContextAddPath(context, path.CGPath);
- }
- CGContextStrokePath(context);
- CGContextRestoreGState(context);
- }
- }
-
- if (shadow.color) {
- CGContextEndTransparencyLayer(context);
- CGContextRestoreGState(context);
- }
- }
- static void YYTextDrawLineStyle(CGContextRef context, CGFloat length, CGFloat lineWidth, YYTextLineStyle style, CGPoint position, CGColorRef color, BOOL isVertical) {
- NSUInteger styleBase = style & 0xFF;
- if (styleBase == 0) return;
-
- CGContextSaveGState(context); {
- if (isVertical) {
- CGFloat x, y1, y2, w;
- y1 = CGFloatPixelRound(position.y);
- y2 = CGFloatPixelRound(position.y + length);
- w = (styleBase == YYTextLineStyleThick ? lineWidth * 2 : lineWidth);
-
- CGFloat linePixel = CGFloatToPixel(w);
- if (fabs(linePixel - floor(linePixel)) < 0.1) {
- int iPixel = linePixel;
- if (iPixel == 0 || (iPixel % 2)) { // odd line pixel
- x = CGFloatPixelHalf(position.x);
- } else {
- x = CGFloatPixelFloor(position.x);
- }
- } else {
- x = position.x;
- }
-
- CGContextSetStrokeColorWithColor(context, color);
- YYTextSetLinePatternInContext(style, lineWidth, position.y, context);
- CGContextSetLineWidth(context, w);
- if (styleBase == YYTextLineStyleSingle) {
- CGContextMoveToPoint(context, x, y1);
- CGContextAddLineToPoint(context, x, y2);
- CGContextStrokePath(context);
- } else if (styleBase == YYTextLineStyleThick) {
- CGContextMoveToPoint(context, x, y1);
- CGContextAddLineToPoint(context, x, y2);
- CGContextStrokePath(context);
- } else if (styleBase == YYTextLineStyleDouble) {
- CGContextMoveToPoint(context, x - w, y1);
- CGContextAddLineToPoint(context, x - w, y2);
- CGContextStrokePath(context);
- CGContextMoveToPoint(context, x + w, y1);
- CGContextAddLineToPoint(context, x + w, y2);
- CGContextStrokePath(context);
- }
- } else {
- CGFloat x1, x2, y, w;
- x1 = CGFloatPixelRound(position.x);
- x2 = CGFloatPixelRound(position.x + length);
- w = (styleBase == YYTextLineStyleThick ? lineWidth * 2 : lineWidth);
-
- CGFloat linePixel = CGFloatToPixel(w);
- if (fabs(linePixel - floor(linePixel)) < 0.1) {
- int iPixel = linePixel;
- if (iPixel == 0 || (iPixel % 2)) { // odd line pixel
- y = CGFloatPixelHalf(position.y);
- } else {
- y = CGFloatPixelFloor(position.y);
- }
- } else {
- y = position.y;
- }
-
- CGContextSetStrokeColorWithColor(context, color);
- YYTextSetLinePatternInContext(style, lineWidth, position.x, context);
- CGContextSetLineWidth(context, w);
- if (styleBase == YYTextLineStyleSingle) {
- CGContextMoveToPoint(context, x1, y);
- CGContextAddLineToPoint(context, x2, y);
- CGContextStrokePath(context);
- } else if (styleBase == YYTextLineStyleThick) {
- CGContextMoveToPoint(context, x1, y);
- CGContextAddLineToPoint(context, x2, y);
- CGContextStrokePath(context);
- } else if (styleBase == YYTextLineStyleDouble) {
- CGContextMoveToPoint(context, x1, y - w);
- CGContextAddLineToPoint(context, x2, y - w);
- CGContextStrokePath(context);
- CGContextMoveToPoint(context, x1, y + w);
- CGContextAddLineToPoint(context, x2, y + w);
- CGContextStrokePath(context);
- }
- }
- } CGContextRestoreGState(context);
- }
- static void YYTextDrawText(YYTextLayout *layout, CGContextRef context, CGSize size, CGPoint point, BOOL (^cancel)(void)) {
- CGContextSaveGState(context); {
-
- CGContextTranslateCTM(context, point.x, point.y);
- CGContextTranslateCTM(context, 0, size.height);
- CGContextScaleCTM(context, 1, -1);
-
- BOOL isVertical = layout.container.verticalForm;
- CGFloat verticalOffset = isVertical ? (size.width - layout.container.size.width) : 0;
-
- NSArray *lines = layout.lines;
- for (NSUInteger l = 0, lMax = lines.count; l < lMax; l++) {
- YYTextLine *line = lines[l];
- if (layout.truncatedLine && layout.truncatedLine.index == line.index) line = layout.truncatedLine;
- NSArray *lineRunRanges = line.verticalRotateRange;
- CGFloat posX = line.position.x + verticalOffset;
- CGFloat posY = size.height - line.position.y;
- CFArrayRef runs = CTLineGetGlyphRuns(line.CTLine);
- for (NSUInteger r = 0, rMax = CFArrayGetCount(runs); r < rMax; r++) {
- CTRunRef run = CFArrayGetValueAtIndex(runs, r);
- CGContextSetTextMatrix(context, CGAffineTransformIdentity);
- CGContextSetTextPosition(context, posX, posY);
- YYTextDrawRun(line, run, context, size, isVertical, lineRunRanges[r], verticalOffset);
- }
- if (cancel && cancel()) break;
- }
-
- // Use this to draw frame for test/debug.
- // CGContextTranslateCTM(context, verticalOffset, size.height);
- // CTFrameDraw(layout.frame, context);
-
- } CGContextRestoreGState(context);
- }
- static void YYTextDrawBlockBorder(YYTextLayout *layout, CGContextRef context, CGSize size, CGPoint point, BOOL (^cancel)(void)) {
- CGContextSaveGState(context);
- CGContextTranslateCTM(context, point.x, point.y);
-
- BOOL isVertical = layout.container.verticalForm;
- CGFloat verticalOffset = isVertical ? (size.width - layout.container.size.width) : 0;
-
- NSArray *lines = layout.lines;
- for (NSInteger l = 0, lMax = lines.count; l < lMax; l++) {
- if (cancel && cancel()) break;
-
- YYTextLine *line = lines[l];
- if (layout.truncatedLine && layout.truncatedLine.index == line.index) line = layout.truncatedLine;
- CFArrayRef runs = CTLineGetGlyphRuns(line.CTLine);
- for (NSInteger r = 0, rMax = CFArrayGetCount(runs); r < rMax; r++) {
- CTRunRef run = CFArrayGetValueAtIndex(runs, r);
- CFIndex glyphCount = CTRunGetGlyphCount(run);
- if (glyphCount == 0) continue;
- NSDictionary *attrs = (id)CTRunGetAttributes(run);
- YYTextBorder *border = attrs[YYTextBlockBorderAttributeName];
- if (!border) continue;
-
- NSUInteger lineStartIndex = line.index;
- while (lineStartIndex > 0) {
- if (((YYTextLine *)lines[lineStartIndex - 1]).row == line.row) lineStartIndex--;
- else break;
- }
-
- CGRect unionRect = CGRectZero;
- NSUInteger lineStartRow = ((YYTextLine *)lines[lineStartIndex]).row;
- NSUInteger lineContinueIndex = lineStartIndex;
- NSUInteger lineContinueRow = lineStartRow;
- do {
- YYTextLine *one = lines[lineContinueIndex];
- if (lineContinueIndex == lineStartIndex) {
- unionRect = one.bounds;
- } else {
- unionRect = CGRectUnion(unionRect, one.bounds);
- }
- if (lineContinueIndex + 1 == lMax) break;
- YYTextLine *next = lines[lineContinueIndex + 1];
- if (next.row != lineContinueRow) {
- YYTextBorder *nextBorder = [layout.text attribute:YYTextBlockBorderAttributeName atIndex:next.range.location];
- if ([nextBorder isEqual:border]) {
- lineContinueRow++;
- } else {
- break;
- }
- }
- lineContinueIndex++;
- } while (true);
-
- if (isVertical) {
- UIEdgeInsets insets = layout.container.insets;
- unionRect.origin.y = insets.top;
- unionRect.size.height = layout.container.size.height -insets.top - insets.bottom;
- } else {
- UIEdgeInsets insets = layout.container.insets;
- unionRect.origin.x = insets.left;
- unionRect.size.width = layout.container.size.width -insets.left - insets.right;
- }
- unionRect.origin.x += verticalOffset;
- YYTextDrawBorderRects(context, size, border, @[[NSValue valueWithCGRect:unionRect]], isVertical);
-
- l = lineContinueIndex;
- break;
- }
- }
-
-
- CGContextRestoreGState(context);
- }
- static void YYTextDrawBorder(YYTextLayout *layout, CGContextRef context, CGSize size, CGPoint point, YYTextBorderType type, BOOL (^cancel)(void)) {
- CGContextSaveGState(context);
- CGContextTranslateCTM(context, point.x, point.y);
-
- BOOL isVertical = layout.container.verticalForm;
- CGFloat verticalOffset = isVertical ? (size.width - layout.container.size.width) : 0;
-
- NSArray *lines = layout.lines;
- NSString *borderKey = (type == YYTextBorderTypeNormal ? YYTextBorderAttributeName : YYTextBackgroundBorderAttributeName);
-
- BOOL needJumpRun = NO;
- NSUInteger jumpRunIndex = 0;
-
- for (NSInteger l = 0, lMax = lines.count; l < lMax; l++) {
- if (cancel && cancel()) break;
-
- YYTextLine *line = lines[l];
- if (layout.truncatedLine && layout.truncatedLine.index == line.index) line = layout.truncatedLine;
- CFArrayRef runs = CTLineGetGlyphRuns(line.CTLine);
- for (NSInteger r = 0, rMax = CFArrayGetCount(runs); r < rMax; r++) {
- if (needJumpRun) {
- needJumpRun = NO;
- r = jumpRunIndex + 1;
- if (r >= rMax) break;
- }
-
- CTRunRef run = CFArrayGetValueAtIndex(runs, r);
- CFIndex glyphCount = CTRunGetGlyphCount(run);
- if (glyphCount == 0) continue;
-
- NSDictionary *attrs = (id)CTRunGetAttributes(run);
- YYTextBorder *border = attrs[borderKey];
- if (!border) continue;
-
- CFRange runRange = CTRunGetStringRange(run);
- if (runRange.location == kCFNotFound || runRange.length == 0) continue;
- if (runRange.location + runRange.length > layout.text.length) continue;
-
- NSMutableArray *runRects = [NSMutableArray new];
- NSInteger endLineIndex = l;
- NSInteger endRunIndex = r;
- BOOL endFound = NO;
- for (NSInteger ll = l; ll < lMax; ll++) {
- if (endFound) break;
- YYTextLine *iLine = lines[ll];
- CFArrayRef iRuns = CTLineGetGlyphRuns(iLine.CTLine);
-
- CGRect extLineRect = CGRectNull;
- for (NSInteger rr = (ll == l) ? r : 0, rrMax = CFArrayGetCount(iRuns); rr < rrMax; rr++) {
- CTRunRef iRun = CFArrayGetValueAtIndex(iRuns, rr);
- NSDictionary *iAttrs = (id)CTRunGetAttributes(iRun);
- YYTextBorder *iBorder = iAttrs[borderKey];
- if (![border isEqual:iBorder]) {
- endFound = YES;
- break;
- }
- endLineIndex = ll;
- endRunIndex = rr;
-
- CGPoint iRunPosition = CGPointZero;
- CTRunGetPositions(iRun, CFRangeMake(0, 1), &iRunPosition);
- CGFloat ascent, descent;
- CGFloat iRunWidth = CTRunGetTypographicBounds(iRun, CFRangeMake(0, 0), &ascent, &descent, NULL);
-
- if (isVertical) {
- YY_SWAP(iRunPosition.x, iRunPosition.y);
- iRunPosition.y += iLine.position.y;
- CGRect iRect = CGRectMake(verticalOffset + line.position.x - descent, iRunPosition.y, ascent + descent, iRunWidth);
- if (CGRectIsNull(extLineRect)) {
- extLineRect = iRect;
- } else {
- extLineRect = CGRectUnion(extLineRect, iRect);
- }
- } else {
- iRunPosition.x += iLine.position.x;
- CGRect iRect = CGRectMake(iRunPosition.x, iLine.position.y - ascent, iRunWidth, ascent + descent);
- if (CGRectIsNull(extLineRect)) {
- extLineRect = iRect;
- } else {
- extLineRect = CGRectUnion(extLineRect, iRect);
- }
- }
- }
-
- if (!CGRectIsNull(extLineRect)) {
- [runRects addObject:[NSValue valueWithCGRect:extLineRect]];
- }
- }
-
- NSMutableArray *drawRects = [NSMutableArray new];
- CGRect curRect= ((NSValue *)[runRects firstObject]).CGRectValue;
- for (NSInteger re = 0, reMax = runRects.count; re < reMax; re++) {
- CGRect rect = ((NSValue *)runRects[re]).CGRectValue;
- if (isVertical) {
- if (fabs(rect.origin.x - curRect.origin.x) < 1) {
- curRect = YYTextMergeRectInSameLine(rect, curRect, isVertical);
- } else {
- [drawRects addObject:[NSValue valueWithCGRect:curRect]];
- curRect = rect;
- }
- } else {
- if (fabs(rect.origin.y - curRect.origin.y) < 1) {
- curRect = YYTextMergeRectInSameLine(rect, curRect, isVertical);
- } else {
- [drawRects addObject:[NSValue valueWithCGRect:curRect]];
- curRect = rect;
- }
- }
- }
- if (!CGRectEqualToRect(curRect, CGRectZero)) {
- [drawRects addObject:[NSValue valueWithCGRect:curRect]];
- }
-
- YYTextDrawBorderRects(context, size, border, drawRects, isVertical);
-
- if (l == endLineIndex) {
- r = endRunIndex;
- } else {
- l = endLineIndex - 1;
- needJumpRun = YES;
- jumpRunIndex = endRunIndex;
- break;
- }
-
- }
- }
-
- CGContextRestoreGState(context);
- }
- static void YYTextDrawDecoration(YYTextLayout *layout, CGContextRef context, CGSize size, CGPoint point, YYTextDecorationType type, BOOL (^cancel)(void)) {
- NSArray *lines = layout.lines;
-
- CGContextSaveGState(context);
- CGContextTranslateCTM(context, point.x, point.y);
-
- BOOL isVertical = layout.container.verticalForm;
- CGFloat verticalOffset = isVertical ? (size.width - layout.container.size.width) : 0;
- CGContextTranslateCTM(context, verticalOffset, 0);
-
- for (NSUInteger l = 0, lMax = layout.lines.count; l < lMax; l++) {
- if (cancel && cancel()) break;
-
- YYTextLine *line = lines[l];
- if (layout.truncatedLine && layout.truncatedLine.index == line.index) line = layout.truncatedLine;
- CFArrayRef runs = CTLineGetGlyphRuns(line.CTLine);
- for (NSUInteger r = 0, rMax = CFArrayGetCount(runs); r < rMax; r++) {
- CTRunRef run = CFArrayGetValueAtIndex(runs, r);
- CFIndex glyphCount = CTRunGetGlyphCount(run);
- if (glyphCount == 0) continue;
-
- NSDictionary *attrs = (id)CTRunGetAttributes(run);
- YYTextDecoration *underline = attrs[YYTextUnderlineAttributeName];
- YYTextDecoration *strikethrough = attrs[YYTextStrikethroughAttributeName];
-
- BOOL needDrawUnderline = NO, needDrawStrikethrough = NO;
- if ((type & YYTextDecorationTypeUnderline) && underline.style > 0) {
- needDrawUnderline = YES;
- }
- if ((type & YYTextDecorationTypeStrikethrough) && strikethrough.style > 0) {
- needDrawStrikethrough = YES;
- }
- if (!needDrawUnderline && !needDrawStrikethrough) continue;
-
- CFRange runRange = CTRunGetStringRange(run);
- if (runRange.location == kCFNotFound || runRange.length == 0) continue;
- if (runRange.location + runRange.length > layout.text.length) continue;
- NSString *runStr = [layout.text attributedSubstringFromRange:NSMakeRange(runRange.location, runRange.length)].string;
- if (YYTextIsLinebreakString(runStr)) continue; // may need more checks...
-
- CGFloat xHeight, underlinePosition, lineThickness;
- YYTextGetRunsMaxMetric(runs, &xHeight, &underlinePosition, &lineThickness);
-
- CGPoint underlineStart, strikethroughStart;
- CGFloat length;
-
- if (isVertical) {
- underlineStart.x = line.position.x + underlinePosition;
- strikethroughStart.x = line.position.x + xHeight / 2;
-
- CGPoint runPosition = CGPointZero;
- CTRunGetPositions(run, CFRangeMake(0, 1), &runPosition);
- underlineStart.y = strikethroughStart.y = runPosition.x + line.position.y;
- length = CTRunGetTypographicBounds(run, CFRangeMake(0, 0), NULL, NULL, NULL);
-
- } else {
- underlineStart.y = line.position.y - underlinePosition;
- strikethroughStart.y = line.position.y - xHeight / 2;
-
- CGPoint runPosition = CGPointZero;
- CTRunGetPositions(run, CFRangeMake(0, 1), &runPosition);
- underlineStart.x = strikethroughStart.x = runPosition.x + line.position.x;
- length = CTRunGetTypographicBounds(run, CFRangeMake(0, 0), NULL, NULL, NULL);
- }
-
- if (needDrawUnderline) {
- CGColorRef color = underline.color.CGColor;
- if (!color) {
- color = (__bridge CGColorRef)(attrs[(id)kCTForegroundColorAttributeName]);
- color = YYTextGetCGColor(color);
- }
- CGFloat thickness = (underline.width != nil) ? underline.width.floatValue : lineThickness;
- YYTextShadow *shadow = underline.shadow;
- while (shadow) {
- if (!shadow.color) {
- shadow = shadow.subShadow;
- continue;
- }
- CGFloat offsetAlterX = size.width + 0xFFFF;
- CGContextSaveGState(context); {
- CGSize offset = shadow.offset;
- offset.width -= offsetAlterX;
- CGContextSaveGState(context); {
- CGContextSetShadowWithColor(context, offset, shadow.radius, shadow.color.CGColor);
- CGContextSetBlendMode(context, shadow.blendMode);
- CGContextTranslateCTM(context, offsetAlterX, 0);
- YYTextDrawLineStyle(context, length, thickness, underline.style, underlineStart, color, isVertical);
- } CGContextRestoreGState(context);
- } CGContextRestoreGState(context);
- shadow = shadow.subShadow;
- }
- YYTextDrawLineStyle(context, length, thickness, underline.style, underlineStart, color, isVertical);
- }
-
- if (needDrawStrikethrough) {
- CGColorRef color = strikethrough.color.CGColor;
- if (!color) {
- color = (__bridge CGColorRef)(attrs[(id)kCTForegroundColorAttributeName]);
- color = YYTextGetCGColor(color);
- }
- CGFloat thickness = (strikethrough.width != nil) ? strikethrough.width.floatValue : lineThickness;
- YYTextShadow *shadow = underline.shadow;
- while (shadow) {
- if (!shadow.color) {
- shadow = shadow.subShadow;
- continue;
- }
- CGFloat offsetAlterX = size.width + 0xFFFF;
- CGContextSaveGState(context); {
- CGSize offset = shadow.offset;
- offset.width -= offsetAlterX;
- CGContextSaveGState(context); {
- CGContextSetShadowWithColor(context, offset, shadow.radius, shadow.color.CGColor);
- CGContextSetBlendMode(context, shadow.blendMode);
- CGContextTranslateCTM(context, offsetAlterX, 0);
- YYTextDrawLineStyle(context, length, thickness, underline.style, underlineStart, color, isVertical);
- } CGContextRestoreGState(context);
- } CGContextRestoreGState(context);
- shadow = shadow.subShadow;
- }
- YYTextDrawLineStyle(context, length, thickness, strikethrough.style, strikethroughStart, color, isVertical);
- }
- }
- }
- CGContextRestoreGState(context);
- }
- static void YYTextDrawAttachment(YYTextLayout *layout, CGContextRef context, CGSize size, CGPoint point, UIView *targetView, CALayer *targetLayer, BOOL (^cancel)(void)) {
-
- BOOL isVertical = layout.container.verticalForm;
- CGFloat verticalOffset = isVertical ? (size.width - layout.container.size.width) : 0;
-
- for (NSUInteger i = 0, max = layout.attachments.count; i < max; i++) {
- YYTextAttachment *a = layout.attachments[i];
- if (!a.content) continue;
-
- UIImage *image = nil;
- UIView *view = nil;
- CALayer *layer = nil;
- if ([a.content isKindOfClass:[UIImage class]]) {
- image = a.content;
- } else if ([a.content isKindOfClass:[UIView class]]) {
- view = a.content;
- } else if ([a.content isKindOfClass:[CALayer class]]) {
- layer = a.content;
- }
- if (!image && !view && !layer) continue;
- if (image && !context) continue;
- if (view && !targetView) continue;
- if (layer && !targetLayer) continue;
- if (cancel && cancel()) break;
-
- CGSize asize = image ? image.size : view ? view.frame.size : layer.frame.size;
- CGRect rect = ((NSValue *)layout.attachmentRects[i]).CGRectValue;
- if (isVertical) {
- rect = UIEdgeInsetsInsetRect(rect, UIEdgeInsetRotateVertical(a.contentInsets));
- } else {
- rect = UIEdgeInsetsInsetRect(rect, a.contentInsets);
- }
- rect = YYCGRectFitWithContentMode(rect, asize, a.contentMode);
- rect = CGRectPixelRound(rect);
- rect = CGRectStandardize(rect);
- rect.origin.x += point.x + verticalOffset;
- rect.origin.y += point.y;
- if (image) {
- CGImageRef ref = image.CGImage;
- if (ref) {
- CGContextSaveGState(context);
- CGContextTranslateCTM(context, 0, CGRectGetMaxY(rect) + CGRectGetMinY(rect));
- CGContextScaleCTM(context, 1, -1);
- CGContextDrawImage(context, rect, ref);
- CGContextRestoreGState(context);
- }
- } else if (view) {
- view.frame = rect;
- [targetView addSubview:view];
- } else if (layer) {
- layer.frame = rect;
- [targetLayer addSublayer:layer];
- }
- }
- }
- static void YYTextDrawShadow(YYTextLayout *layout, CGContextRef context, CGSize size, CGPoint point, BOOL (^cancel)(void)) {
- //move out of context. (0xFFFF is just a random large number)
- CGFloat offsetAlterX = size.width + 0xFFFF;
-
- BOOL isVertical = layout.container.verticalForm;
- CGFloat verticalOffset = isVertical ? (size.width - layout.container.size.width) : 0;
-
- CGContextSaveGState(context); {
- CGContextTranslateCTM(context, point.x, point.y);
- CGContextTranslateCTM(context, 0, size.height);
- CGContextScaleCTM(context, 1, -1);
- NSArray *lines = layout.lines;
- for (NSUInteger l = 0, lMax = layout.lines.count; l < lMax; l++) {
- if (cancel && cancel()) break;
- YYTextLine *line = lines[l];
- if (layout.truncatedLine && layout.truncatedLine.index == line.index) line = layout.truncatedLine;
- NSArray *lineRunRanges = line.verticalRotateRange;
- CGFloat linePosX = line.position.x;
- CGFloat linePosY = size.height - line.position.y;
- CFArrayRef runs = CTLineGetGlyphRuns(line.CTLine);
- for (NSUInteger r = 0, rMax = CFArrayGetCount(runs); r < rMax; r++) {
- CTRunRef run = CFArrayGetValueAtIndex(runs, r);
- CGContextSetTextMatrix(context, CGAffineTransformIdentity);
- CGContextSetTextPosition(context, linePosX, linePosY);
- NSDictionary *attrs = (id)CTRunGetAttributes(run);
- YYTextShadow *shadow = attrs[YYTextShadowAttributeName];
- YYTextShadow *nsShadow = [YYTextShadow shadowWithNSShadow:attrs[NSShadowAttributeName]]; // NSShadow compatible
- if (nsShadow) {
- nsShadow.subShadow = shadow;
- shadow = nsShadow;
- }
- while (shadow) {
- if (!shadow.color) {
- shadow = shadow.subShadow;
- continue;
- }
- CGSize offset = shadow.offset;
- offset.width -= offsetAlterX;
- CGContextSaveGState(context); {
- CGContextSetShadowWithColor(context, offset, shadow.radius, shadow.color.CGColor);
- CGContextSetBlendMode(context, shadow.blendMode);
- CGContextTranslateCTM(context, offsetAlterX, 0);
- YYTextDrawRun(line, run, context, size, isVertical, lineRunRanges[r], verticalOffset);
- } CGContextRestoreGState(context);
- shadow = shadow.subShadow;
- }
- }
- }
- } CGContextRestoreGState(context);
- }
- static void YYTextDrawInnerShadow(YYTextLayout *layout, CGContextRef context, CGSize size, CGPoint point, BOOL (^cancel)(void)) {
- CGContextSaveGState(context);
- CGContextTranslateCTM(context, point.x, point.y);
- CGContextTranslateCTM(context, 0, size.height);
- CGContextScaleCTM(context, 1, -1);
- CGContextSetTextMatrix(context, CGAffineTransformIdentity);
-
- BOOL isVertical = layout.container.verticalForm;
- CGFloat verticalOffset = isVertical ? (size.width - layout.container.size.width) : 0;
-
- NSArray *lines = layout.lines;
- for (NSUInteger l = 0, lMax = lines.count; l < lMax; l++) {
- if (cancel && cancel()) break;
-
- YYTextLine *line = lines[l];
- if (layout.truncatedLine && layout.truncatedLine.index == line.index) line = layout.truncatedLine;
- NSArray *lineRunRanges = line.verticalRotateRange;
- CGFloat linePosX = line.position.x;
- CGFloat linePosY = size.height - line.position.y;
- CFArrayRef runs = CTLineGetGlyphRuns(line.CTLine);
- for (NSUInteger r = 0, rMax = CFArrayGetCount(runs); r < rMax; r++) {
- CTRunRef run = CFArrayGetValueAtIndex(runs, r);
- if (CTRunGetGlyphCount(run) == 0) continue;
- CGContextSetTextMatrix(context, CGAffineTransformIdentity);
- CGContextSetTextPosition(context, linePosX, linePosY);
- NSDictionary *attrs = (id)CTRunGetAttributes(run);
- YYTextShadow *shadow = attrs[YYTextInnerShadowAttributeName];
- while (shadow) {
- if (!shadow.color) {
- shadow = shadow.subShadow;
- continue;
- }
- CGPoint runPosition = CGPointZero;
- CTRunGetPositions(run, CFRangeMake(0, 1), &runPosition);
- CGRect runImageBounds = CTRunGetImageBounds(run, context, CFRangeMake(0, 0));
- runImageBounds.origin.x += runPosition.x;
- if (runImageBounds.size.width < 0.1 || runImageBounds.size.height < 0.1) continue;
-
- CFDictionaryRef runAttrs = CTRunGetAttributes(run);
- NSValue *glyphTransformValue = CFDictionaryGetValue(runAttrs, (__bridge const void *)(YYTextGlyphTransformAttributeName));
- if (glyphTransformValue) {
- runImageBounds = CGRectMake(0, 0, size.width, size.height);
- }
-
- // text inner shadow
- CGContextSaveGState(context); {
- CGContextSetBlendMode(context, shadow.blendMode);
- CGContextSetShadowWithColor(context, CGSizeZero, 0, NULL);
- CGContextSetAlpha(context, CGColorGetAlpha(shadow.color.CGColor));
- CGContextClipToRect(context, runImageBounds);
- CGContextBeginTransparencyLayer(context, NULL); {
- UIColor *opaqueShadowColor = [shadow.color colorWithAlphaComponent:1];
- CGContextSetShadowWithColor(context, shadow.offset, shadow.radius, opaqueShadowColor.CGColor);
- CGContextSetFillColorWithColor(context, opaqueShadowColor.CGColor);
- CGContextSetBlendMode(context, kCGBlendModeSourceOut);
- CGContextBeginTransparencyLayer(context, NULL); {
- CGContextFillRect(context, runImageBounds);
- CGContextSetBlendMode(context, kCGBlendModeDestinationIn);
- CGContextBeginTransparencyLayer(context, NULL); {
- YYTextDrawRun(line, run, context, size, isVertical, lineRunRanges[r], verticalOffset);
- } CGContextEndTransparencyLayer(context);
- } CGContextEndTransparencyLayer(context);
- } CGContextEndTransparencyLayer(context);
- } CGContextRestoreGState(context);
- shadow = shadow.subShadow;
- }
- }
- }
-
- CGContextRestoreGState(context);
- }
- static void YYTextDrawDebug(YYTextLayout *layout, CGContextRef context, CGSize size, CGPoint point, YYTextDebugOption *op) {
- UIGraphicsPushContext(context);
- CGContextSaveGState(context);
- CGContextTranslateCTM(context, point.x, point.y);
- CGContextSetLineWidth(context, 1.0 / YYScreenScale());
- CGContextSetLineDash(context, 0, NULL, 0);
- CGContextSetLineJoin(context, kCGLineJoinMiter);
- CGContextSetLineCap(context, kCGLineCapButt);
-
- BOOL isVertical = layout.container.verticalForm;
- CGFloat verticalOffset = isVertical ? (size.width - layout.container.size.width) : 0;
- CGContextTranslateCTM(context, verticalOffset, 0);
-
- if (op.CTFrameBorderColor || op.CTFrameFillColor) {
- UIBezierPath *path = layout.container.path;
- if (!path) {
- CGRect rect = (CGRect){CGPointZero, layout.container.size};
- rect = UIEdgeInsetsInsetRect(rect, layout.container.insets);
- if (op.CTFrameBorderColor) rect = CGRectPixelHalf(rect);
- else rect = CGRectPixelRound(rect);
- path = [UIBezierPath bezierPathWithRect:rect];
- }
- [path closePath];
-
- for (UIBezierPath *ex in layout.container.exclusionPaths) {
- [path appendPath:ex];
- }
- if (op.CTFrameFillColor) {
- [op.CTFrameFillColor setFill];
- if (layout.container.pathLineWidth > 0) {
- CGContextSaveGState(context); {
- CGContextBeginTransparencyLayer(context, NULL); {
- CGContextAddPath(context, path.CGPath);
- if (layout.container.pathFillEvenOdd) {
- CGContextEOFillPath(context);
- } else {
- CGContextFillPath(context);
- }
- CGContextSetBlendMode(context, kCGBlendModeDestinationOut);
- [[UIColor blackColor] setFill];
- CGPathRef cgPath = CGPathCreateCopyByStrokingPath(path.CGPath, NULL, layout.container.pathLineWidth, kCGLineCapButt, kCGLineJoinMiter, 0);
- if (cgPath) {
- CGContextAddPath(context, cgPath);
- CGContextFillPath(context);
- }
- CGPathRelease(cgPath);
- } CGContextEndTransparencyLayer(context);
- } CGContextRestoreGState(context);
- } else {
- CGContextAddPath(context, path.CGPath);
- if (layout.container.pathFillEvenOdd) {
- CGContextEOFillPath(context);
- } else {
- CGContextFillPath(context);
- }
- }
- }
- if (op.CTFrameBorderColor) {
- CGContextSaveGState(context); {
- if (layout.container.pathLineWidth > 0) {
- CGContextSetLineWidth(context, layout.container.pathLineWidth);
- }
- [op.CTFrameBorderColor setStroke];
- CGContextAddPath(context, path.CGPath);
- CGContextStrokePath(context);
- } CGContextRestoreGState(context);
- }
- }
-
- NSArray *lines = layout.lines;
- for (NSUInteger l = 0, lMax = lines.count; l < lMax; l++) {
- YYTextLine *line = lines[l];
- if (layout.truncatedLine && layout.truncatedLine.index == line.index) line = layout.truncatedLine;
- CGRect lineBounds = line.bounds;
- if (op.CTLineFillColor) {
- [op.CTLineFillColor setFill];
- CGContextAddRect(context, CGRectPixelRound(lineBounds));
- CGContextFillPath(context);
- }
- if (op.CTLineBorderColor) {
- [op.CTLineBorderColor setStroke];
- CGContextAddRect(context, CGRectPixelHalf(lineBounds));
- CGContextStrokePath(context);
- }
- if (op.baselineColor) {
- [op.baselineColor setStroke];
- if (isVertical) {
- CGFloat x = CGFloatPixelHalf(line.position.x);
- CGFloat y1 = CGFloatPixelHalf(line.top);
- CGFloat y2 = CGFloatPixelHalf(line.bottom);
- CGContextMoveToPoint(context, x, y1);
- CGContextAddLineToPoint(context, x, y2);
- CGContextStrokePath(context);
- } else {
- CGFloat x1 = CGFloatPixelHalf(lineBounds.origin.x);
- CGFloat x2 = CGFloatPixelHalf(lineBounds.origin.x + lineBounds.size.width);
- CGFloat y = CGFloatPixelHalf(line.position.y);
- CGContextMoveToPoint(context, x1, y);
- CGContextAddLineToPoint(context, x2, y);
- CGContextStrokePath(context);
- }
- }
- if (op.CTLineNumberColor) {
- [op.CTLineNumberColor set];
- NSMutableAttributedString *num = [[NSMutableAttributedString alloc] initWithString:@(l).description];
- num.color = op.CTLineNumberColor;
- num.font = [UIFont systemFontOfSize:6];
- [num drawAtPoint:CGPointMake(line.position.x, line.position.y - (isVertical ? 1 : 6))];
- }
- if (op.CTRunFillColor || op.CTRunBorderColor || op.CTRunNumberColor || op.CGGlyphFillColor || op.CGGlyphBorderColor) {
- CFArrayRef runs = CTLineGetGlyphRuns(line.CTLine);
- for (NSUInteger r = 0, rMax = CFArrayGetCount(runs); r < rMax; r++) {
- CTRunRef run = CFArrayGetValueAtIndex(runs, r);
- CFIndex glyphCount = CTRunGetGlyphCount(run);
- if (glyphCount == 0) continue;
-
- CGPoint glyphPositions[glyphCount];
- CTRunGetPositions(run, CFRangeMake(0, glyphCount), glyphPositions);
-
- CGSize glyphAdvances[glyphCount];
- CTRunGetAdvances(run, CFRangeMake(0, glyphCount), glyphAdvances);
-
- CGPoint runPosition = glyphPositions[0];
- if (isVertical) {
- YY_SWAP(runPosition.x, runPosition.y);
- runPosition.x = line.position.x;
- runPosition.y += line.position.y;
- } else {
- runPosition.x += line.position.x;
- runPosition.y = line.position.y - runPosition.y;
- }
-
- CGFloat ascent, descent, leading;
- CGFloat width = CTRunGetTypographicBounds(run, CFRangeMake(0, 0), &ascent, &descent, &leading);
- CGRect runTypoBounds;
- if (isVertical) {
- runTypoBounds = CGRectMake(runPosition.x - descent, runPosition.y, ascent + descent, width);
- } else {
- runTypoBounds = CGRectMake(runPosition.x, line.position.y - ascent, width, ascent + descent);
- }
-
- if (op.CTRunFillColor) {
- [op.CTRunFillColor setFill];
- CGContextAddRect(context, CGRectPixelRound(runTypoBounds));
- CGContextFillPath(context);
- }
- if (op.CTRunBorderColor) {
- [op.CTRunBorderColor setStroke];
- CGContextAddRect(context, CGRectPixelHalf(runTypoBounds));
- CGContextStrokePath(context);
- }
- if (op.CTRunNumberColor) {
- [op.CTRunNumberColor set];
- NSMutableAttributedString *num = [[NSMutableAttributedString alloc] initWithString:@(r).description];
- num.color = op.CTRunNumberColor;
- num.font = [UIFont systemFontOfSize:6];
- [num drawAtPoint:CGPointMake(runTypoBounds.origin.x, runTypoBounds.origin.y - 1)];
- }
- if (op.CGGlyphBorderColor || op.CGGlyphFillColor) {
- for (NSUInteger g = 0; g < glyphCount; g++) {
- CGPoint pos = glyphPositions[g];
- CGSize adv = glyphAdvances[g];
- CGRect rect;
- if (isVertical) {
- YY_SWAP(pos.x, pos.y);
- pos.x = runPosition.x;
- pos.y += line.position.y;
- rect = CGRectMake(pos.x - descent, pos.y, runTypoBounds.size.width, adv.width);
- } else {
- pos.x += line.position.x;
- pos.y = runPosition.y;
- rect = CGRectMake(pos.x, pos.y - ascent, adv.width, runTypoBounds.size.height);
- }
- if (op.CGGlyphFillColor) {
- [op.CGGlyphFillColor setFill];
- CGContextAddRect(context, CGRectPixelRound(rect));
- CGContextFillPath(context);
- }
- if (op.CGGlyphBorderColor) {
- [op.CGGlyphBorderColor setStroke];
- CGContextAddRect(context, CGRectPixelHalf(rect));
- CGContextStrokePath(context);
- }
- }
- }
- }
- }
- }
- CGContextRestoreGState(context);
- UIGraphicsPopContext();
- }
- - (void)drawInContext:(CGContextRef)context
- size:(CGSize)size
- point:(CGPoint)point
- view:(UIView *)view
- layer:(CALayer *)layer
- debug:(YYTextDebugOption *)debug
- cancel:(BOOL (^)(void))cancel{
- @autoreleasepool {
- if (self.needDrawBlockBorder && context) {
- if (cancel && cancel()) return;
- YYTextDrawBlockBorder(self, context, size, point, cancel);
- }
- if (self.needDrawBackgroundBorder && context) {
- if (cancel && cancel()) return;
- YYTextDrawBorder(self, context, size, point, YYTextBorderTypeBackgound, cancel);
- }
- if (self.needDrawShadow && context) {
- if (cancel && cancel()) return;
- YYTextDrawShadow(self, context, size, point, cancel);
- }
- if (self.needDrawUnderline && context) {
- if (cancel && cancel()) return;
- YYTextDrawDecoration(self, context, size, point, YYTextDecorationTypeUnderline, cancel);
- }
- if (self.needDrawText && context) {
- if (cancel && cancel()) return;
- YYTextDrawText(self, context, size, point, cancel);
- }
- if (self.needDrawAttachment && (context || view || layer)) {
- if (cancel && cancel()) return;
- YYTextDrawAttachment(self, context, size, point, view, layer, cancel);
- }
- if (self.needDrawInnerShadow && context) {
- if (cancel && cancel()) return;
- YYTextDrawInnerShadow(self, context, size, point, cancel);
- }
- if (self.needDrawStrikethrough && context) {
- if (cancel && cancel()) return;
- YYTextDrawDecoration(self, context, size, point, YYTextDecorationTypeStrikethrough, cancel);
- }
- if (self.needDrawBorder && context) {
- if (cancel && cancel()) return;
- YYTextDrawBorder(self, context, size, point, YYTextBorderTypeNormal, cancel);
- }
- if (debug.needDrawDebug && context) {
- if (cancel && cancel()) return;
- YYTextDrawDebug(self, context, size, point, debug);
- }
- }
- }
- - (void)drawInContext:(CGContextRef)context
- size:(CGSize)size
- debug:(YYTextDebugOption *)debug {
- [self drawInContext:context size:size point:CGPointZero view:nil layer:nil debug:debug cancel:nil];
- }
- - (void)addAttachmentToView:(UIView *)view layer:(CALayer *)layer {
- YYAssertMainThread();
- [self drawInContext:NULL size:CGSizeZero point:CGPointZero view:view layer:layer debug:nil cancel:nil];
- }
- - (void)removeAttachmentFromViewAndLayer {
- YYAssertMainThread();
- for (YYTextAttachment *a in self.attachments) {
- if ([a.content isKindOfClass:[UIView class]]) {
- UIView *v = a.content;
- [v removeFromSuperview];
- } else if ([a.content isKindOfClass:[CALayer class]]) {
- CALayer *l = a.content;
- [l removeFromSuperlayer];
- }
- }
- }
- @end
|