//
// MPLayout.h
// MathPad
//
// 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, 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
*/
/*!
@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