// // MPExpressionLayout.m // MathPad // // Created by Kim Wittenburg on 22.04.14. // Copyright (c) 2014 Kim Wittenburg. All rights reserved. // #import "MPExpressionLayout.h" #import "MPExpressionStorage.h" #import "MPFunctionLayout.h" #import "MPModel.h" #import "MPExpressionView.h" @implementation MPExpressionLayout #pragma mark Creation Methods - (instancetype)initRootLayoutWithExpressionStorage:(MPExpressionStorage *)expressionStorage { self = [super init]; if (self) { _symbolCache = [[NSMutableArray alloc] init]; _expressionStorage = expressionStorage; _expressionPath = [[NSIndexPath alloc] init]; } return self; } - (instancetype)initWithExpressionPath:(NSIndexPath *)expressionPath parent:(MPFunctionLayout *)parent { self = [super init]; if (self) { _symbolCache = [[NSMutableArray alloc] init]; _expressionPath = expressionPath; _parent = parent; } return self; } #pragma mark Properties @synthesize expressionStorage = _expressionStorage; - (MPExpressionStorage *)expressionStorage { if (_expressionStorage) { return _expressionStorage; } return self.parent.expressionStorage; } - (MPExpression *)expression { return [self.expressionStorage symbolAtIndexPath:self.expressionPath]; } - (NSLayoutManager *)layoutManager { return self.expressionStorage.layoutManager; } - (NSTextContainer *)textContainer { return self.expressionStorage.textContainer; } - (NSTextStorage *)textStorage { return self.expressionStorage.textStorage; } #pragma mark Cache Methods // TODO: Return nil from caching with illegal index - (void)invalidate { _valid = NO; [self.parent invalidate]; [self.expressionView setNeedsDisplay:YES]; } - (void)editedExpressionInRange:(NSRange)range replacementLength:(NSUInteger)length { // TODO: New symbols may also be inserted in the middle or at the beginning NSInteger changeInLength = length - range.length; while (_symbolCache.count < (self.expression.numberOfSymbols + changeInLength)) { [_symbolCache addObject:[NSNull null]]; } NSMutableArray *newPlaceholders = [[NSMutableArray alloc] initWithCapacity:length]; while (newPlaceholders.count < length) { [newPlaceholders addObject:[NSNull null]]; } [_symbolCache replaceObjectsInRange:range withObjectsFromArray:newPlaceholders]; [self invalidate]; } - (BOOL)hasCacheForSymbolAtIndex:(NSUInteger)index { if (index >= _symbolCache.count) { return NO; } return _symbolCache[index] != [NSNull null]; } - (MPFunctionLayout *)functionLayoutForFunctionAtIndex:(NSUInteger)index; { if ([self hasCacheForSymbolAtIndex:index]) { id cacheObject = _symbolCache[index]; if ([cacheObject isKindOfClass:[NSValue class]]) { return nil; } return cacheObject; } MPFunctionLayout *layout = [MPFunctionLayout functionLayoutForFunctionAtIndexPath:[self.expressionPath indexPathByAddingIndex:index] parent:self]; while (index >= _symbolCache.count) { [_symbolCache addObject:[NSNull null]]; } _symbolCache[index] = layout; return layout; } - (NSSize)cachedSizeForSymbolAtIndex:(NSUInteger)index { id cachedSymbol = _symbolCache[index]; if ([cachedSymbol isKindOfClass:[NSValue class]]) { return [cachedSymbol sizeValue]; } return [(MPFunctionLayout *)cachedSymbol sizeOfFunction]; } - (void)cacheSize:(NSSize)size forSymbolAtIndex:(NSUInteger)index { while (index >= _symbolCache.count) { [_symbolCache addObject:[NSNull null]]; } _symbolCache[index] = [NSValue valueWithSize:size]; } #pragma mark Size Calculation Methods - (NSSize)sizeForAllSymbols { if (!_valid) { _cachedSize = [self sizeForSymbolsInRange:NSMakeRange(0, self.expression.numberOfSymbols)]; _valid = YES; } return _cachedSize; } - (NSSize)sizeForSymbolsInRange:(NSRange)range { NSSize size = NSMakeSize(0, 0); for (NSUInteger index = range.location; index < NSMaxRange(range); index++) { NSSize symbolSize = [self sizeForSymbolAtIndex:index]; size.width += symbolSize.width; size.height = MAX(size.height, symbolSize.height); } return size; } - (NSSize)sizeForSymbolAtIndex:(NSUInteger)index { if ([self hasCacheForSymbolAtIndex:index]) { return [self cachedSizeForSymbolAtIndex:index]; } id symbol = [self.expression symbolAtIndex:index]; if ([symbol isString]) { [self.textStorage setString:symbol]; NSRange glyphRange = [self.layoutManager glyphRangeForTextContainer:self.textContainer]; NSSize symbolSize = [self.layoutManager boundingRectForGlyphRange:glyphRange inTextContainer:self.textContainer].size; [self cacheSize:symbolSize forSymbolAtIndex:index]; return symbolSize; } else { MPFunctionLayout *layout = [self functionLayoutForFunctionAtIndex:index]; return [layout sizeOfFunction]; } } #pragma mark Drawing Methods - (void)drawSymbolAtIndex:(NSUInteger)index atPoint:(NSPoint)point { id symbol = [self.expression symbolAtIndex:index]; // point.x = point.y = 0; NSLog(@"draw Symbol: %@ at Point: (x: %f, y: %f)", symbol, point.x, point.y); if ([symbol isString]) { [self.textStorage setString:symbol]; NSRange glyphRange = [self.layoutManager glyphRangeForTextContainer:self.textContainer]; [self.layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:point]; } else { MPFunctionLayout *layout = [self functionLayoutForFunctionAtIndex:index]; NSLog(@"layout: %@, index: %ld", layout, index); [layout drawFunctionAtPoint:point]; } } - (void)drawSymbolsInRange:(NSRange)range atPoint:(NSPoint)point { NSSize overallSize = [self sizeForSymbolsInRange:range]; CGFloat x = point.x; for (NSUInteger index = range.location; index < NSMaxRange(range); index++) { NSSize symbolSize = [self sizeForSymbolAtIndex:index]; CGFloat dy = (overallSize.height - symbolSize.height) / 2; [self drawSymbolAtIndex:index atPoint:NSMakePoint(x, point.y + dy)]; x += symbolSize.width; } } - (void)drawAllSymbolsAtPoint:(NSPoint)point { [self drawSymbolsInRange:NSMakeRange(0, [self.expression numberOfSymbols]) atPoint:point]; } @end