forked from facebookarchive/AsyncDisplayKit
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathASTextLayout.h
547 lines (428 loc) · 20.6 KB
/
ASTextLayout.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
//
// ASTextLayout.h
// Texture
//
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//
#import <UIKit/UIKit.h>
#import <CoreText/CoreText.h>
#import "ASTextDebugOption.h"
#import "ASTextLine.h"
#import "ASTextInput.h"
@protocol ASTextLinePositionModifier;
NS_ASSUME_NONNULL_BEGIN
/**
The max text container size in layout.
*/
ASDK_EXTERN const CGSize ASTextContainerMaxSize;
/**
The ASTextContainer class defines a region in which text is laid out.
ASTextLayout class uses one or more ASTextContainer objects to generate layouts.
A ASTextContainer defines rectangular regions (`size` and `insets`) or
nonrectangular shapes (`path`), and you can define exclusion paths inside the
text container's bounding rectangle so that text flows around the exclusion
path as it is laid out.
All methods in this class is thread-safe.
Example:
┌─────────────────────────────┐ <------- container
│ │
│ asdfasdfasdfasdfasdfa <------------ container insets
│ asdfasdfa asdfasdfa │
│ asdfas asdasd │
│ asdfa <----------------------- container exclusion path
│ asdfas adfasd │
│ asdfasdfa asdfasdfa │
│ asdfasdfasdfasdfasdfa │
│ │
└─────────────────────────────┘
*/
@interface ASTextContainer : NSObject <NSCoding, NSCopying>
/// Creates a container with the specified size. @param size The size.
+ (instancetype)containerWithSize:(CGSize)size NS_RETURNS_RETAINED;
/// Creates a container with the specified size and insets. @param size The size. @param insets The text insets.
+ (instancetype)containerWithSize:(CGSize)size insets:(UIEdgeInsets)insets NS_RETURNS_RETAINED;
/// Creates a container with the specified path. @param path The path.
+ (instancetype)containerWithPath:(nullable UIBezierPath *)path NS_RETURNS_RETAINED;
/// Mark this immutable, so you get free copies going forward.
- (void)makeImmutable;
/// The constrained size. (if the size is larger than ASTextContainerMaxSize, it will be clipped)
@property CGSize size;
/// The insets for constrained size. The inset value should not be negative. Default is UIEdgeInsetsZero.
@property UIEdgeInsets insets;
/// Custom constrained path. Set this property to ignore `size` and `insets`. Default is nil.
@property (nullable, copy) UIBezierPath *path;
/// An array of `UIBezierPath` for path exclusion. Default is nil.
@property (nullable, copy) NSArray<UIBezierPath *> *exclusionPaths;
/// Path line width. Default is 0;
@property CGFloat pathLineWidth;
/// YES:(PathFillEvenOdd) Text is filled in the area that would be painted if the path were given to CGContextEOFillPath.
/// NO: (PathFillWindingNumber) Text is fill in the area that would be painted if the path were given to CGContextFillPath.
/// Default is YES;
@property (getter=isPathFillEvenOdd) BOOL pathFillEvenOdd;
/// Whether the text is vertical form (may used for CJK text layout). Default is NO.
@property (getter=isVerticalForm) BOOL verticalForm;
/// Maximum number of rows, 0 means no limit. Default is 0.
@property NSUInteger maximumNumberOfRows;
/// The line truncation type, default is none.
@property ASTextTruncationType truncationType;
/// The truncation token. If nil, the layout will use "…" instead. Default is nil.
@property (nullable, copy) NSAttributedString *truncationToken;
/// This modifier is applied to the lines before the layout is completed,
/// give you a chance to modify the line position. Default is nil.
@property (nullable, copy) id<ASTextLinePositionModifier> linePositionModifier;
@end
/**
The ASTextLinePositionModifier protocol declares the required method to modify
the line position in text layout progress. See `ASTextLinePositionSimpleModifier` for example.
*/
@protocol ASTextLinePositionModifier <NSObject, NSCopying>
@required
/**
This method will called before layout is completed. The method should be thread-safe.
@param lines An array of ASTextLine.
@param text The full text.
@param container The layout container.
*/
- (void)modifyLines:(NSArray<ASTextLine *> *)lines fromText:(NSAttributedString *)text inContainer:(ASTextContainer *)container;
@end
/**
A simple implementation of `ASTextLinePositionModifier`. It can fix each line's position
to a specified value, lets each line of height be the same.
*/
@interface ASTextLinePositionSimpleModifier : NSObject <ASTextLinePositionModifier>
@property CGFloat fixedLineHeight; ///< The fixed line height (distance between two baseline).
@end
/**
ASTextLayout class is a readonly class stores text layout result.
All the property in this class is readonly, and should not be changed.
The methods in this class is thread-safe (except some of the draw methods).
example: (layout with a circle exclusion path)
┌──────────────────────────┐ <------ container
│ [--------Line0--------] │ <- Row0
│ [--------Line1--------] │ <- Row1
│ [-Line2-] [-Line3-] │ <- Row2
│ [-Line4] [Line5-] │ <- Row3
│ [-Line6-] [-Line7-] │ <- Row4
│ [--------Line8--------] │ <- Row5
│ [--------Line9--------] │ <- Row6
└──────────────────────────┘
*/
@interface ASTextLayout : NSObject <NSCopying>
#pragma mark - Generate text layout
///=============================================================================
/// @name Generate text layout
///=============================================================================
/**
Generate a layout with the given container size and text.
@param size The text container's size
@param text The text (if nil, returns nil).
@return A new layout, or nil when an error occurs.
*/
+ (nullable ASTextLayout *)layoutWithContainerSize:(CGSize)size text:(NSAttributedString *)text;
/**
Generate a layout with the given container and text.
@param container The text container (if nil, returns nil).
@param text The text (if nil, returns nil).
@return A new layout, or nil when an error occurs.
*/
+ (nullable ASTextLayout *)layoutWithContainer:(ASTextContainer *)container text:(NSAttributedString *)text;
/**
Generate a layout with the given container and text.
@param container The text container (if nil, returns nil).
@param text The text (if nil, returns nil).
@param range The text range (if out of range, returns nil). If the
length of the range is 0, it means the length is no limit.
@return A new layout, or nil when an error occurs.
*/
+ (nullable ASTextLayout *)layoutWithContainer:(ASTextContainer *)container text:(NSAttributedString *)text range:(NSRange)range;
/**
Generate layouts with the given containers and text.
@param containers An array of ASTextContainer object (if nil, returns nil).
@param text The text (if nil, returns nil).
@return An array of ASTextLayout object (the count is same as containers),
or nil when an error occurs.
*/
+ (nullable NSArray<ASTextLayout *> *)layoutWithContainers:(NSArray<ASTextContainer *> *)containers
text:(NSAttributedString *)text;
/**
Generate layouts with the given containers and text.
@param containers An array of ASTextContainer object (if nil, returns nil).
@param text The text (if nil, returns nil).
@param range The text range (if out of range, returns nil). If the
length of the range is 0, it means the length is no limit.
@return An array of ASTextLayout object (the count is same as containers),
or nil when an error occurs.
*/
+ (nullable NSArray<ASTextLayout *> *)layoutWithContainers:(NSArray<ASTextContainer *> *)containers
text:(NSAttributedString *)text
range:(NSRange)range;
- (instancetype)init UNAVAILABLE_ATTRIBUTE;
+ (instancetype)new UNAVAILABLE_ATTRIBUTE;
#pragma mark - Text layout attributes
///=============================================================================
/// @name Text layout attributes
///=============================================================================
///< The text container
@property (nonatomic, readonly) ASTextContainer *container;
///< The full text
@property (nonatomic, readonly) NSAttributedString *text;
///< The text range in full text
@property (nonatomic, readonly) NSRange range;
///< CTFrame
@property (nonatomic, readonly) CTFrameRef frame;
///< Array of `ASTextLine`, no truncated
@property (nonatomic, readonly) NSArray<ASTextLine *> *lines;
///< ASTextLine with truncated token, or nil
@property (nullable, nonatomic, readonly) ASTextLine *truncatedLine;
///< Array of `ASTextAttachment`
@property (nullable, nonatomic, readonly) NSArray<ASTextAttachment *> *attachments;
///< Array of NSRange(wrapped by NSValue) in text
@property (nullable, nonatomic, readonly) NSArray<NSValue *> *attachmentRanges;
///< Array of CGRect(wrapped by NSValue) in container
@property (nullable, nonatomic, readonly) NSArray<NSValue *> *attachmentRects;
///< Set of Attachment (UIImage/UIView/CALayer)
@property (nullable, nonatomic, readonly) NSSet *attachmentContentsSet;
///< Number of rows
@property (nonatomic, readonly) NSUInteger rowCount;
///< Visible text range
@property (nonatomic, readonly) NSRange visibleRange;
///< Bounding rect (glyphs)
@property (nonatomic, readonly) CGRect textBoundingRect;
///< Bounding size (glyphs and insets, ceil to pixel)
@property (nonatomic, readonly) CGSize textBoundingSize;
///< Has highlight attribute
@property (nonatomic, readonly) BOOL containsHighlight;
///< Has block border attribute
@property (nonatomic, readonly) BOOL needDrawBlockBorder;
///< Has background border attribute
@property (nonatomic, readonly) BOOL needDrawBackgroundBorder;
///< Has shadow attribute
@property (nonatomic, readonly) BOOL needDrawShadow;
///< Has underline attribute
@property (nonatomic, readonly) BOOL needDrawUnderline;
///< Has visible text
@property (nonatomic, readonly) BOOL needDrawText;
///< Has attachment attribute
@property (nonatomic, readonly) BOOL needDrawAttachment;
///< Has inner shadow attribute
@property (nonatomic, readonly) BOOL needDrawInnerShadow;
///< Has strickthrough attribute
@property (nonatomic, readonly) BOOL needDrawStrikethrough;
///< Has border attribute
@property (nonatomic, readonly) BOOL needDrawBorder;
#pragma mark - Query information from text layout
///=============================================================================
/// @name Query information from text layout
///=============================================================================
/**
The first line index for row.
@param row A row index.
@return The line index, or NSNotFound if not found.
*/
- (NSUInteger)lineIndexForRow:(NSUInteger)row;
/**
The number of lines for row.
@param row A row index.
@return The number of lines, or NSNotFound when an error occurs.
*/
- (NSUInteger)lineCountForRow:(NSUInteger)row;
/**
The row index for line.
@param line A row index.
@return The row index, or NSNotFound if not found.
*/
- (NSUInteger)rowIndexForLine:(NSUInteger)line;
/**
The line index for a specified point.
@discussion It returns NSNotFound if there's no text at the point.
@param point A point in the container.
@return The line index, or NSNotFound if not found.
*/
- (NSUInteger)lineIndexForPoint:(CGPoint)point;
/**
The line index closest to a specified point.
@param point A point in the container.
@return The line index, or NSNotFound if no line exist in layout.
*/
- (NSUInteger)closestLineIndexForPoint:(CGPoint)point;
/**
The offset in container for a text position in a specified line.
@discussion The offset is the text position's baseline point.x.
If the container is vertical form, the offset is the baseline point.y;
@param position The text position in string.
@param lineIndex The line index.
@return The offset in container, or CGFLOAT_MAX if not found.
*/
- (CGFloat)offsetForTextPosition:(NSUInteger)position lineIndex:(NSUInteger)lineIndex;
/**
The text position for a point in a specified line.
@discussion This method just call CTLineGetStringIndexForPosition() and does
NOT consider the emoji, line break character, binding text...
@param point A point in the container.
@param lineIndex The line index.
@return The text position, or NSNotFound if not found.
*/
- (NSUInteger)textPositionForPoint:(CGPoint)point lineIndex:(NSUInteger)lineIndex;
/**
The closest text position to a specified point.
@discussion This method takes into account the restrict of emoji, line break
character, binding text and text affinity.
@param point A point in the container.
@return A text position, or nil if not found.
*/
- (nullable ASTextPosition *)closestPositionToPoint:(CGPoint)point;
/**
Returns the new position when moving selection grabber in text view.
@discussion There are two grabber in the text selection period, user can only
move one grabber at the same time.
@param point A point in the container.
@param oldPosition The old text position for the moving grabber.
@param otherPosition The other position in text selection view.
@return A text position, or nil if not found.
*/
- (nullable ASTextPosition *)positionForPoint:(CGPoint)point
oldPosition:(ASTextPosition *)oldPosition
otherPosition:(ASTextPosition *)otherPosition;
/**
Returns the character or range of characters that is at a given point in the container.
If there is no text at the point, returns nil.
@discussion This method takes into account the restrict of emoji, line break
character, binding text and text affinity.
@param point A point in the container.
@return An object representing a range that encloses a character (or characters)
at point. Or nil if not found.
*/
- (nullable ASTextRange *)textRangeAtPoint:(CGPoint)point;
/**
Returns the closest character or range of characters that is at a given point in
the container.
@discussion This method takes into account the restrict of emoji, line break
character, binding text and text affinity.
@param point A point in the container.
@return An object representing a range that encloses a character (or characters)
at point. Or nil if not found.
*/
- (nullable ASTextRange *)closestTextRangeAtPoint:(CGPoint)point;
/**
If the position is inside an emoji, composed character sequences, line break '\\r\\n'
or custom binding range, then returns the range by extend the position. Otherwise,
returns a zero length range from the position.
@param position A text-position object that identifies a location in layout.
@return A text-range object that extend the position. Or nil if an error occurs
*/
- (nullable ASTextRange *)textRangeByExtendingPosition:(ASTextPosition *)position;
/**
Returns a text range at a given offset in a specified direction from another
text position to its farthest extent in a certain direction of layout.
@param position A text-position object that identifies a location in layout.
@param direction A constant that indicates a direction of layout (right, left, up, down).
@param offset A character offset from position.
@return A text-range object that represents the distance from position to the
farthest extent in direction. Or nil if an error occurs.
*/
- (nullable ASTextRange *)textRangeByExtendingPosition:(ASTextPosition *)position
inDirection:(UITextLayoutDirection)direction
offset:(NSInteger)offset;
/**
Returns the line index for a given text position.
@discussion This method takes into account the text affinity.
@param position A text-position object that identifies a location in layout.
@return The line index, or NSNotFound if not found.
*/
- (NSUInteger)lineIndexForPosition:(ASTextPosition *)position;
/**
Returns the baseline position for a given text position.
@param position An object that identifies a location in the layout.
@return The baseline position for text, or CGPointZero if not found.
*/
- (CGPoint)linePositionForPosition:(ASTextPosition *)position;
/**
Returns a rectangle used to draw the caret at a given insertion point.
@param position An object that identifies a location in the layout.
@return A rectangle that defines the area for drawing the caret. The width is
always zero in normal container, the height is always zero in vertical form container.
If not found, it returns CGRectNull.
*/
- (CGRect)caretRectForPosition:(ASTextPosition *)position;
/**
Returns the first rectangle that encloses a range of text in the layout.
@param range An object that represents a range of text in layout.
@return The first rectangle in a range of text. You might use this rectangle to
draw a correction rectangle. The "first" in the name refers the rectangle
enclosing the first line when the range encompasses multiple lines of text.
If not found, it returns CGRectNull.
*/
- (CGRect)firstRectForRange:(ASTextRange *)range;
/**
Returns the rectangle union that encloses a range of text in the layout.
@param range An object that represents a range of text in layout.
@return A rectangle that defines the area than encloses the range.
If not found, it returns CGRectNull.
*/
- (CGRect)rectForRange:(ASTextRange *)range;
/**
Returns an array of selection rects corresponding to the range of text.
The start and end rect can be used to show grabber.
@param range An object representing a range in text.
@return An array of `ASTextSelectionRect` objects that encompass the selection.
If not found, the array is empty.
*/
- (NSArray<ASTextSelectionRect *> *)selectionRectsForRange:(ASTextRange *)range;
/**
Returns an array of selection rects corresponding to the range of text.
@param range An object representing a range in text.
@return An array of `ASTextSelectionRect` objects that encompass the selection.
If not found, the array is empty.
*/
- (NSArray<ASTextSelectionRect *> *)selectionRectsWithoutStartAndEndForRange:(ASTextRange *)range;
/**
Returns the start and end selection rects corresponding to the range of text.
The start and end rect can be used to show grabber.
@param range An object representing a range in text.
@return An array of `ASTextSelectionRect` objects contains the start and end to
the selection. If not found, the array is empty.
*/
- (NSArray<ASTextSelectionRect *> *)selectionRectsWithOnlyStartAndEndForRange:(ASTextRange *)range;
#pragma mark - Draw text layout
///=============================================================================
/// @name Draw text layout
///=============================================================================
/**
Draw the layout and show the attachments.
@discussion If the `view` parameter is not nil, then the attachment views will
add to this `view`, and if the `layer` parameter is not nil, then the attachment
layers will add to this `layer`.
@warning This method should be called on main thread if `view` or `layer` parameter
is not nil and there's UIView or CALayer attachments in layout.
Otherwise, it can be called on any thread.
@param context The draw context. Pass nil to avoid text and image drawing.
@param size The context size.
@param point The point at which to draw the layout.
@param view The attachment views will add to this view.
@param layer The attachment layers will add to this layer.
@param debug The debug option. Pass nil to avoid debug drawing.
@param cancel The cancel checker block. It will be called in drawing progress.
If it returns YES, the further draw progress will be canceled.
Pass nil to ignore this feature.
*/
- (void)drawInContext:(nullable CGContextRef)context
size:(CGSize)size
point:(CGPoint)point
view:(nullable UIView *)view
layer:(nullable CALayer *)layer
debug:(nullable ASTextDebugOption *)debug
cancel:(nullable BOOL (^)(void))cancel;
/**
Draw the layout text and image (without view or layer attachments).
@discussion This method is thread safe and can be called on any thread.
@param context The draw context. Pass nil to avoid text and image drawing.
@param size The context size.
@param debug The debug option. Pass nil to avoid debug drawing.
*/
- (void)drawInContext:(nullable CGContextRef)context
size:(CGSize)size
debug:(nullable ASTextDebugOption *)debug;
@end
NS_ASSUME_NONNULL_END