// // MPLayout.h // MathKit // // Created by Kim Wittenburg on 07.08.14. // Copyright (c) 2014 Kim Wittenburg. All rights reserved. // /*! @header This file contains the MPLayout class. The MPLayout class renders an expression to be displayed in an @link //apple_ref/occ/cl/MPExpressionView@/link. There are multiple subclasses of MPLayout the most important of which are @link //apple_ref/occ/cl/MPExpressionLayout@/link and @link //apple_ref/occ/cl/MPFunctionLayout@/link for rendering expressions and functions respectively. Both types of layouts can occur in two different sizes: normal and small. The root expression ueses the normal size. Depending on the concrete subclass of @link //apple_ref/occ/cl/MPFunctionLayout@/link that is used a function's children (and any subsequent children) may be rendered using the small size. Also there are two different fonts available for layouts: a normal and a special font. The special font should only be used to emphasize special content that uses the normal font. If an expression is empty an empty box is rendered instead. That box (called the empty box uses the font metrics of the empty expression it represents. That means it uses different sizes depending on whether the expression uses a small or normal sized font. Also it is drawn a little smaller than the actual box's height is because it looks nicer. Depending on your needs you can query the metrics of the empty box using the kMPEmptyBox... or kMPEmptyBoxDrawing... macros. These macros must not be used outside of the MPLayout class or any of it subclasses. */ /*! @define kMPEmptyBoxWidth @abstract The width of the empty box. */ #define kMPEmptyBoxWidth (self.usesSmallSize ? 2.0 : 3.0) /*! @define kMPEmptyBoxHeight @abstract The actual height of the empty box. */ #define kMPEmptyBoxHeight (CTFontGetDescent((CTFontRef)self.font) + CTFontGetAscent((CTFontRef)self.font) + CTFontGetLeading((CTFontRef)self.font)) /*! @define kMPEmptyBoxYOrigin @abstract The actual vertical origin of the empty box. @discussion The vertical origin is a negative value to be compliant with the metrics of strings. */ #define kMPEmptyBoxYOrigin (-(CTFontGetDescent((CTFontRef)self.font) + CTFontGetLeading((CTFontRef)self.font))) /*! @define kMPEmptyBoxDrawingWidth @abstract The with the empty box is rendered with. */ #define kMPEmptyBoxDrawingWidth kMPEmptyBoxWidth /*! @define kMPEmptyBoxDrawingHeight @abstract The height the empty box is drawn with. */ #define kMPEmptyBoxDrawingHeight (CTFontGetDescent((CTFontRef)self.font) + CTFontGetAscent((CTFontRef)self.font)) /*! @define kMPEmptyBoxDrawingYOrigin @abstract The vertical origin the empty box is drawn at. @discussion The vertical origin is a negative value to be compliant with the metrics of strings. */ #define kMPEmptyBoxDrawingYOrigin (-(CTFontGetDescent((CTFontRef)self.font) + CTFontGetLeading((CTFontRef)self.font)/2)) @class MPLayout, MPExpressionView, MPRangePath; /*! @class MPLayout @abstract This is an abstract class that defines the basic methods that must be implemented by other layout classes. It also defines some useful helper methods. @discussion The MPLayout class maintains a generic cache. It is used to store child layouts and actual cache information. Every layout instance represents either an expression or a function. The instance's children represent the children of the represented object. */ @interface MPLayout : NSObject #pragma mark Creation Methods /*! @methodgroup Creation Methods */ /*! @method init @abstract Initializes the receiver. @discussion This method should only be used to initialize the expression layout that renders the root layout. This method is the designated initializer of the MPLayout class. @return A newly initialized MPLayout instance. */ - (instancetype)init; /*! @method initWithParent: @abstract Initializes the receiver with the specified parent. @discussion This method should generally be used when initializing layout instances. In most cases it should be called at some point in the implementation of the @link //apple_ref/occ/instm/MPLayout(MPSubclassImplement)/childLayoutAtIndex:@/link method. @param parent The receiver's parent. @return A newly initialized MPLayout instance. */ - (instancetype)initWithParent:(MPLayout *)parent; #pragma mark Properties /*! @methodgroup Properties */ /*! @property expressionView @abstract The receiver's expression view. @discussion The expression view is the view in which the receivin MPLayout instance draws itself into. Normally you should not call the setter of this property. It is automatically assigned when the @link //apple_ref/occ/instp/MPExpressionView/expressionStorage@/link property of the respective @link //apple_ref/occ/cl/MPExpressionView@/link instance is set. */ @property (nonatomic, weak) MPExpressionView *expressionView; /*! @method font @abstract Returns the receiver's context inferred font. @discussion The context inferred font is the normal font using the context inferred font size. Normally this method is used for any textual content of a layout. @return The font the receiver should use for rendering textual content. */ - (NSFont *)font; /*! @method normalFontWithSize: @abstract Returns a NSFont object that represents the normal font in the specified size. @discussion Instead of this method you want to use @link //apple_ref/occ/instm/MPLayout/font@/link instead in most cases. @param size The size of the font. @return A NSFont object that can be used to render textual content of the receiver in the specified size. */ - (NSFont *)normalFontWithSize:(CGFloat)size; /*! @method specialFontWithSize: @abstract Returns a NSFont object that represents the special font in the specified size. @discussion Use fonts returned by this method only if you need to emphasize text that is surrounded by other text that uses the normal font. @param size The size of the font. @return A NSFont object that can be used to render special textual content of the receiver in the specified size. */ - (NSFont *)specialFontWithSize:(CGFloat)size; /*! @method contextInferredFontSize @abstract Returns the appropriate font size for the receiver based on the context it will be rendered in. */ - (CGFloat)contextInferredFontSize; /*! @method normalFontSize @abstract Returns the font size that is used in the root expression. */ - (CGFloat)normalFontSize; /*! @method smallFontSize @abstract Returns the font size that is used in expressions that are using the small size. */ - (CGFloat)smallFontSize; #pragma mark Cache Tree /*! @methodgroup Cache Tree */ /*! @property parent @abstract The receiver's parent layout. @discussion The parent layout is most likely the one that created the receiver and is responsible for setting its attributes appropriately based on the rendering context. */ @property (readonly, nonatomic, weak) MPLayout *parent; #pragma mark Cache Methods /*! @methodgroup Cache Methods */ /*! @method chacableObjectForIndex:generator: @abstract Returns the cached object for the specified index or creates one if it does not exist. @discussion This method only returns nil if the generator returns nil. @param index The index of the cached object to retrieve. @param generator This block is executed if there is no cached object at index. The result of this block is then cached at the index. @return The cached object for index or the result of the generator if there is no cached object. */ - (id)cachableObjectForIndex:(NSUInteger)index generator:(id(^)())generator; /*! @method clearCacheInRange:replacementLength: @abstract Removes the objects in the specified from the cache and moves all objects at subsequent indexes. @discussion The objects at subsequent indexes are moved replacementLength - range.length places. If replacementLength is greater than range.length they are moved to smaller indexes. @param range The range of objects to be removed. @param replacementLength The number of indexes replacing the ones specified by range. */ - (void)clearCacheInRange:(NSRange)range replacementLength:(NSUInteger)replacementLength; /*! @method invalidate @abstract Invalidates the receiver. @discussion Invalidating the receiver causes him to discard all cached information about itself and send a invalidate message to its parent. The cached information about the receiver's children is not discarded. Use @link //apple_ref/occ/instm/MPLayout/clearCacheInRange:replacementLength:@/link for that purpose. */ - (void)invalidate; /*! @method childLayoutAtIndexPath: @abstract Returns the child layout at the specified index path. @discussion If the indexPath is empty the receiver is returned. If the child layout the index path specifies is not yet created this method should do so and add it to the cache of the respective parent layout. @param indexPath The index path of the child. @return The child at the specified index path. */ - (MPLayout *)childLayoutAtIndexPath:(NSIndexPath *)indexPath; #pragma mark Rendering Methods /*! @methodgroup Rendering Methods */ /*! @method createLineForString: @abstract Renders the specified string. @discussion This method uses the normal font and the context inferred font size to render the string. In most cases this is the appropriate method for rendering textual content in a layout. @param aString The string to be rendered. @return A CTLineRef representing the rendered string. */ - (CTLineRef)createLineForString:(NSString *)aString; /*! @method createLineForString:emphasize: @abstract Renders the specified string. @discussion This method uses either the normal or the special font depending on the emphasize flag. It uses the context inferred font size. @param aString The string to be rendered. @param emphasize A flag indicating whether the special font should be used. @return A CTLineRef representing the rendered string. */ - (CTLineRef)createLineForString:(NSString *)aString emphasize:(BOOL)emphasize; /*! @method createLineForString:usingFont: @abstract Renders the specified string in the specified font. @discussion In most cases you do should prefer @link //apple_ref/occ/instm/MPLayout/createLineForString:@/link or @link //apple_ref/occ/instm/MPLayout/createLineForString:emphasize:@/link over this method. @param aString The string to be rendered. @param font The font to render aString in. @return A CTLineRef representing the rendered string. */ - (CTLineRef)createLineForString:(NSString *)aString usingFont:(NSFont *)font; /*! @property flipped @abstract This property indicates whether the receiver uses a flipped layout. @discussion The value of this property should be the same in the receiver, its parent and all of its children. */ @property (nonatomic, getter = isFlipped) BOOL flipped; /*! @property usesSmallSize @abstract This property indicates whether the receiver uses the small font size. @discussion This property is used to determine the @link //apple_ref/occ/instm/MPLayout/contextInferredFontSize@/link. If this value is YES all of the receiver's children should also use the small size. If not the children may or may not (depending on the actual layout implementation) use the small size. */ @property (nonatomic) BOOL usesSmallSize; /*! @method bounds @abstract Returns the receiver's bounds. @discussion If the receiver has cached bounds it will return the cached value. The cached value can be removed using the @link //apple_ref/occ/instm/MPLayout/invalidate@/link method. If there is no cached value this method will generate a new cached value by calling the @link //apple_ref/occ/instm/MPLayout(MPSubclassImplement)/generateBounds@/link method. @return The receiver's bounds. */ - (NSRect)bounds; /*! @method boundingRectForRangePath: @abstract Returns the rect relative to the origin of the receiver's bounds that contains the symbols identified by the specified range path. @discussion Use this method to determine where the user's selection should be drawn at. @param rangePath The range path whose bounds are needed. @return The rectangle containing the symbols identified by rangePath. */ - (NSRect)boundingRectForRangePath:(MPRangePath *)rangePath; /* if rangePath.length is 0 the returned rect will have a width of 0 */ /*! @method drawAtPoint: @abstract Draws the receiver at the specified point. @discussion If the receiver returns NO from its @link //apple_ref/occ/instm/MPLayout(MPSubclassImplement)/drawsChildrenManually@/link method this method also draws every single child of the receiver. The location the children are drawn at is determined by the @link //apple_ref/occ/instm/MPLayout(MPSubclassImplement)/offsetOfChildLayoutAtIndex:@/link method. @param point The point the receiver is to be drawn at. */ - (void)drawAtPoint:(NSPoint)point; @end /*! @category MPLayout (MPSubclassImplement) @abstract The methods in this category must be implemented by any concrete subclass of MPLayout. */ @interface MPLayout (MPSubclassImplement) /*! @method numberOfChildren @abstract Returns the number of sublayouts of the receiver. @discussion This method must be implemented by subclasses. */ - (NSUInteger)numberOfChildren; /*! @method drawsChildrenManually @abstract Returns whether the receiver wants to take over responsibility of drawing its children. @discussion If you return YES from this method MPLayout does not draw the receiver's children. The receiver's //apple_ref/occ/intfm/MPLayout/draw method has to implement that functionality. If you return NO from this method the receiver must not draw its children in the //apple_ref/occ/intfm/MPLayout/draw method. This method may be implemented by subclasses to opt in and change the default behaviour. @return YES if the receiver draws its children automatically, NO otherwise. The default implementation returns NO. */ - (BOOL)drawsChildrenManually; /*! @method childLayoutAtIndex: @abstract Returns the receiver's child at the specified index. @discussion As long as the specified index does not exceed the receiver's number of children, this method should always return a value based on the following criteria: 1. If the receiver represents a @link //apple_ref/occ/cl/MPFunction@/link instance it should always return a valid MPLayout instance. 2. If the receiver represents a @link //apple_ref/occ/cl/MPExpression@/link instance it returns only a valid MPLayout instance if the represented expression's element at index is a function. This method should cache any generated child layout and use the cache whenever possible in order to reduce resources needed for rendering. This method must be implemented by subclasses. @param index The index of the requested child layout. @return A MPLayout instance representing the respective child of the object that is represented by the receiver, or nil if the child represents a string element in an expression. */ - (MPLayout *)childLayoutAtIndex:(NSUInteger)index; /*! @method generateBounds @abstract Calculates the receiever's bounds. @discussion This method should not use any cached values. Caching of the calculated bounds is done automatically by the MPLayout class. This method is only called if the receiving MPLayout instance has been invalidated and the receiver's bounds have been requested. This method must be implemented by subclasses. @return The receiver's bounds. */ - (NSRect)generateBounds; /*! @method boundingRectForRange: @abstract Returns the rectangle that encloses the symbols in the specified range. @discussion This method is called to calculate the rectangle that encloses the user's selection. If the specified range has a length of 0 the returned rectangle should be the bounds of the caret (which has a width of 0). The height and y-origin of the returned rectangle should be equal to the respective values of the receiver. This method must be implemented by subclasses. @param range The range of the selection whose bounds should be calculated. @return A rectangle enclosing the user's selection. */ - (NSRect)boundingRectForRange:(NSRange)range; /*! @method offsetOfChildLayoutAtIndex: @abstract Returns the location of the child layout at the specified index. @discussion This method must be implemented by subclasses even if the subclass returns YES from the @link //apple_ref/occ/instm/MPLayout(MPSubclassImplement)/drawsChildrenManually@/link method. @param index The index of the child whose location is to be calculated. @return The location of the child layout at the specified index relative to the receiver. */ - (NSPoint)offsetOfChildLayoutAtIndex:(NSUInteger)index; /*! @method indexPathForMousePoint: @abstract Performs hit testing. @discussion This method tests the specified point against all of its children and returns the index path (relative to the index path of the receiver) that identifies the location inside the expression tree the user selected. The specified point must be inside of the receiver and must be specified relatively to the receiver. If the specified point is not inside any of the receiver's children but in the receiver itself the returned index path must contain one index specifiying the location in the receiver. This method must be implemented by subclasses. @param point The point inside the receiver that is to be tested. @return The index path that points to the point the user selected. All indexes in the path except for the last one are specified in the element reference frame. The last index is specified in the symbol reference frame. */ - (NSIndexPath *)indexPathForMousePoint:(NSPoint)point; /*! @method draw @abstract Draws the receiver. @discussion If the receiver returns YES from its @link //apple_ref/occ/instm/MPLayout(MPSubclassImplement)/drawsChildrenManually@/link method the implementation of this method must also draw the receiver's children. This method must be implemented by subclasses. If the receiver does not have anything to draw except for its children you should implement this method with an empty method body. */ - (void)draw; @end