New file |
| | |
| | | // |
| | | // YYTextLayout.h |
| | | // YYText <https://github.com/ibireme/YYText> |
| | | // |
| | | // 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 <UIKit/UIKit.h> |
| | | #import <CoreText/CoreText.h> |
| | | |
| | | #if __has_include(<YYText/YYText.h>) |
| | | #import <YYText/YYTextDebugOption.h> |
| | | #import <YYText/YYTextLine.h> |
| | | #import <YYText/YYTextInput.h> |
| | | #else |
| | | #import "YYTextDebugOption.h" |
| | | #import "YYTextLine.h" |
| | | #import "YYTextInput.h" |
| | | #endif |
| | | |
| | | @protocol YYTextLinePositionModifier; |
| | | |
| | | NS_ASSUME_NONNULL_BEGIN |
| | | |
| | | /** |
| | | The max text container size in layout. |
| | | */ |
| | | extern const CGSize YYTextContainerMaxSize; |
| | | |
| | | /** |
| | | The YYTextContainer class defines a region in which text is laid out. |
| | | YYTextLayout class uses one or more YYTextContainer objects to generate layouts. |
| | | |
| | | A YYTextContainer 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 YYTextContainer : NSObject <NSCoding, NSCopying> |
| | | |
| | | /// Creates a container with the specified size. @param size The size. |
| | | + (instancetype)containerWithSize:(CGSize)size; |
| | | |
| | | /// 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; |
| | | |
| | | /// Creates a container with the specified path. @param size The path. |
| | | + (instancetype)containerWithPath:(nullable UIBezierPath *)path; |
| | | |
| | | /// The constrained size. (if the size is larger than YYTextContainerMaxSize, 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 YYTextTruncationType 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<YYTextLinePositionModifier> linePositionModifier; |
| | | @end |
| | | |
| | | |
| | | /** |
| | | The YYTextLinePositionModifier protocol declares the required method to modify |
| | | the line position in text layout progress. See `YYTextLinePositionSimpleModifier` for example. |
| | | */ |
| | | @protocol YYTextLinePositionModifier <NSObject, NSCopying> |
| | | @required |
| | | /** |
| | | This method will called before layout is completed. The method should be thread-safe. |
| | | @param lines An array of YYTextLine. |
| | | @param text The full text. |
| | | @param container The layout container. |
| | | */ |
| | | - (void)modifyLines:(NSArray<YYTextLine *> *)lines fromText:(NSAttributedString *)text inContainer:(YYTextContainer *)container; |
| | | @end |
| | | |
| | | |
| | | /** |
| | | A simple implementation of `YYTextLinePositionModifier`. It can fix each line's position |
| | | to a specified value, lets each line of height be the same. |
| | | */ |
| | | @interface YYTextLinePositionSimpleModifier : NSObject <YYTextLinePositionModifier> |
| | | @property (assign) CGFloat fixedLineHeight; ///< The fixed line height (distance between two baseline). |
| | | @end |
| | | |
| | | |
| | | |
| | | /** |
| | | YYTextLayout 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 YYTextLayout : NSObject <NSCoding> |
| | | |
| | | |
| | | #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 YYTextLayout *)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 YYTextLayout *)layoutWithContainer:(YYTextContainer *)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 YYTextLayout *)layoutWithContainer:(YYTextContainer *)container text:(NSAttributedString *)text range:(NSRange)range; |
| | | |
| | | /** |
| | | Generate layouts with the given containers and text. |
| | | |
| | | @param containers An array of YYTextContainer object (if nil, returns nil). |
| | | @param text The text (if nil, returns nil). |
| | | @return An array of YYTextLayout object (the count is same as containers), |
| | | or nil when an error occurs. |
| | | */ |
| | | + (nullable NSArray<YYTextLayout *> *)layoutWithContainers:(NSArray<YYTextContainer *> *)containers |
| | | text:(NSAttributedString *)text; |
| | | |
| | | /** |
| | | Generate layouts with the given containers and text. |
| | | |
| | | @param containers An array of YYTextContainer 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 YYTextLayout object (the count is same as containers), |
| | | or nil when an error occurs. |
| | | */ |
| | | + (nullable NSArray<YYTextLayout *> *)layoutWithContainers:(NSArray<YYTextContainer *> *)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, strong, readonly) YYTextContainer *container; |
| | | ///< The full text |
| | | @property (nonatomic, strong, readonly) NSAttributedString *text; |
| | | ///< The text range in full text |
| | | @property (nonatomic, readonly) NSRange range; |
| | | ///< CTFrameSetter |
| | | @property (nonatomic, readonly) CTFramesetterRef frameSetter; |
| | | ///< CTFrame |
| | | @property (nonatomic, readonly) CTFrameRef frame; |
| | | ///< Array of `YYTextLine`, no truncated |
| | | @property (nonatomic, strong, readonly) NSArray<YYTextLine *> *lines; |
| | | ///< YYTextLine with truncated token, or nil |
| | | @property (nullable, nonatomic, strong, readonly) YYTextLine *truncatedLine; |
| | | ///< Array of `YYTextAttachment` |
| | | @property (nullable, nonatomic, strong, readonly) NSArray<YYTextAttachment *> *attachments; |
| | | ///< Array of NSRange(wrapped by NSValue) in text |
| | | @property (nullable, nonatomic, strong, readonly) NSArray<NSValue *> *attachmentRanges; |
| | | ///< Array of CGRect(wrapped by NSValue) in container |
| | | @property (nullable, nonatomic, strong, readonly) NSArray<NSValue *> *attachmentRects; |
| | | ///< Set of Attachment (UIImage/UIView/CALayer) |
| | | @property (nullable, nonatomic, strong, 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 YYTextPosition *)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 YYTextPosition *)positionForPoint:(CGPoint)point |
| | | oldPosition:(YYTextPosition *)oldPosition |
| | | otherPosition:(YYTextPosition *)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 YYTextRange *)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 YYTextRange *)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 YYTextRange *)textRangeByExtendingPosition:(YYTextPosition *)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 YYTextRange *)textRangeByExtendingPosition:(YYTextPosition *)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:(YYTextPosition *)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:(YYTextPosition *)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:(YYTextPosition *)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:(YYTextRange *)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:(YYTextRange *)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 `YYTextSelectionRect` objects that encompass the selection. |
| | | If not found, the array is empty. |
| | | */ |
| | | - (NSArray<YYTextSelectionRect *> *)selectionRectsForRange:(YYTextRange *)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 `YYTextSelectionRect` objects that encompass the selection. |
| | | If not found, the array is empty. |
| | | */ |
| | | - (NSArray<YYTextSelectionRect *> *)selectionRectsWithoutStartAndEndForRange:(YYTextRange *)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 `YYTextSelectionRect` objects contains the start and end to |
| | | the selection. If not found, the array is empty. |
| | | */ |
| | | - (NSArray<YYTextSelectionRect *> *)selectionRectsWithOnlyStartAndEndForRange:(YYTextRange *)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 YYTextDebugOption *)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 YYTextDebugOption *)debug; |
| | | |
| | | /** |
| | | Show view and layer attachments. |
| | | |
| | | @warning This method must be called on main thread. |
| | | |
| | | @param view The attachment views will add to this view. |
| | | @param layer The attachment layers will add to this layer. |
| | | */ |
| | | - (void)addAttachmentToView:(nullable UIView *)view layer:(nullable CALayer *)layer; |
| | | |
| | | /** |
| | | Remove attachment views and layers from their super container. |
| | | |
| | | @warning This method must be called on main thread. |
| | | */ |
| | | - (void)removeAttachmentFromViewAndLayer; |
| | | |
| | | @end |
| | | |
| | | NS_ASSUME_NONNULL_END |