//
// XTTextTableBlock.m
// XTads
//
// Created by Rune Berg on 03/09/2018.
// Copyright © 2018 Rune Berg. All rights reserved.
//
#import "XTTextTableBlock.h"
#import "XTTextTable.h"
#import "XTFontUtils.h"
#import "XTTableColumnWidthTracker.h"
#import "XTStringUtils.h"
#import "XTLogger.h"
#import "XTTimer.h"
#import "XTRequiredRectForTextCache.h"
#import "XTRect.h"
#import "XTOutputFormatter.h"
#import "XTTabStopUtils.h"
#import "XTTextView.h"
#import "XTAllocDeallocCounter.h"
@interface XTTextTableBlock ()
@property NSNumber *widthAsPercentage;
@property NSNumber *widthAsPoints;
@property XTRect *latestContentRect;
@property NSRange rangeForLatestContentRect;
@property XTHtmlColor *backgroundHtmlColor;
@end
@implementation XTTextTableBlock
static XTLogger* logger;
static NSUInteger countRectForLayoutAtPoint = 0;
static double totalTimeInRectForLayoutAtPoint1 = 0.0;
static double totalTimeInRectForLayoutAtPoint2 = 0.0;
static double totalTimeInRectForLayoutAtPoint3 = 0.0;
static double totalTimeInRectForLayoutAtPoint4 = 0.0;
static double totalTimeInRectForLayoutAtPoint5 = 0.0;
@synthesize widthAsPercentage = _widthAsPercentage;
@synthesize widthAsPoints = _widthAsPoints;
@synthesize backgroundHtmlColor = _backgroundHtmlColor;
+ (void)initialize
{
logger = [XTLogger loggerForClass:[XTTextTableBlock class]];
}
OVERRIDE_ALLOC_FOR_COUNTER
OVERRIDE_DEALLOC_FOR_COUNTER
- (NSNumber *)widthAsPercentage
{
return _widthAsPercentage;
}
- (void)setWidthAsPercentage:(NSNumber *)widthAsPercentage
{
_widthAsPercentage = widthAsPercentage;
}
- (NSNumber *)widthAsPoints
{
return _widthAsPoints;
}
- (void)setWidthAsPoints:(NSNumber *)widthAsPoints
{
_widthAsPoints = widthAsPoints;
}
- (XTHtmlColor *)backgroundHtmlColor
{
return _backgroundHtmlColor;
}
- (void)setBackgroundHtmlColor:(XTHtmlColor *)htmlColor
{
_backgroundHtmlColor = htmlColor;
[self updateBackgroundColor];
}
- (void)updateBackgroundColor
{
NSColor *color = nil;
if ([self allowGameToSetColors]) {
color = self.backgroundHtmlColor.color;
}
[self setBackgroundColor:color];
}
- (XTRect *)getlatestContentRect
{
return self.latestContentRect;
}
//TODO !!! too long - split up
- (NSRect)rectForLayoutAtPoint:(NSPoint)startingPoint
inRect:(NSRect)rect
textContainer:(NSTextContainer *)textContainer
characterRange:(NSRange)charRange
{
NSInteger startingRow = self.startingRow;
NSInteger startingColumn = self.startingColumn;
XT_DEF_SELNAME;
//XT_WARN_2(@"row=%ld col=%ld", startingRow, startingColumn); //TDOO !!! rm
XTTimer *timer = [XTTimer fromNow];
countRectForLayoutAtPoint += 1;
[self recalcCellMargins];
NSRect res = [super rectForLayoutAtPoint:startingPoint inRect:rect textContainer:textContainer characterRange:charRange];
// TODO !!! Only needed for origin.y - can we simplify/avoid?
//XT_WARN_3(@"charRange=(%lu, %lu) res.origin.y=%lf", charRange.location, charRange.length, res.origin.y);
totalTimeInRectForLayoutAtPoint1 += [timer timeElapsed];
NSTextStorage *textStorage = textContainer.layoutManager.textStorage;
XTTableColumnWidthTracker *tracker = [XTTableColumnWidthTracker tracker];
NSAttributedString *attrStringInCell = [textStorage attributedSubstringFromRange:charRange];
//XT_WARN_1(@"attrStringInCell=\"%@\"", attrStringInCell.string);
// Calc. max/ideal content rect width and remember it:
//----------------------------------------------------
NSSize viewSize;
if ((self.latestContentRect != nil) && NSEqualRanges(charRange, self.rangeForLatestContentRect)) {
viewSize = self.latestContentRect.rect.size;
} else {
viewSize = NSMakeSize(10000.0, 10000.0);
}
NSSize sizeOfRange = [XTFontUtils sizeOfTextInTextStorage:textStorage
range:charRange
viewSize:viewSize];
//if (sizeOfRange.width == 10000.0) {
// NSAttributedString *subString = [textStorage attributedSubstringFromRange:charRange];
//}
totalTimeInRectForLayoutAtPoint2 += [timer timeElapsed];
CGFloat maxContentRectWidth;
if (sizeOfRange.width == 0.0) {
maxContentRectWidth = 1.0;
} else {
maxContentRectWidth = sizeOfRange.width;
}
[tracker noteMaxContentRectWidthForTable:self.tableId
textTableBlock:self
width:maxContentRectWidth];
//XT_WARN_1(@"maxContentRectWidth=%lf", maxContentRectWidth);
// Calc. minimum content rect width and remember it:
//--------------------------------------------------
CGFloat minContentRectWidth;
if (sizeOfRange.width == 0.0) {
minContentRectWidth = 1.0;
} else {
minContentRectWidth = [XTFontUtils widthOfLongestIndivisibleWordInTextStorage:textStorage
range:charRange
numCharsInLongest:nil];
}
[tracker noteMinContentRectWidthForTable:self.tableId
textTableBlock:self
width:minContentRectWidth];
totalTimeInRectForLayoutAtPoint3 += [timer timeElapsed];
//XT_WARN_1(@"minContentRectWidth=%lf", minContentRectWidth);
// Calc. the actual content rect width
//----------------------------------------------------
//TODO !!! "when
has computed value" case
//TODO !!! CAPMIN for
CGFloat tableLevelBoundsSize = [self totalTableLevelBoundsWidth];
//TODO !!! adjust usableWidth for % / pts here?
CGFloat usableWidth = rect.size.width;
usableWidth -= tableLevelBoundsSize;
CGFloat contentRectWidth = [tracker contentRectWidthForTable:self.tableId textTableBlock:self usableWidth:usableWidth];
if (contentRectWidth < 0.0) {
XT_WARN_3(@"row=%ld col=%ld : contentRectWidth %lf < 0.0", self.startingRow, self.startingColumn, contentRectWidth);
}
totalTimeInRectForLayoutAtPoint4 += [timer timeElapsed];
//XT_WARN_1(@"contentRectWidth=%lf", contentRectWidth);
// Calc. the x position
//----------------------------------------------------
NSAttributedString *attrStringForRange = [textStorage attributedSubstringFromRange:charRange];
//XT_WARN_1(@"attrStringForRange=\"%@\"", attrStringForRange);
res.size.width = contentRectWidth;
CGFloat originXAdjustedForTableLevelBounds = rect.origin.x + floor(tableLevelBoundsSize / 2.0);
//CGFloat oldX = res.origin.x;
res.origin.x = [tracker contentRectXForTable:self.tableId
textTableBlock:self
originX:originXAdjustedForTableLevelBounds
usableWidth:usableWidth];
// Calc. cell height
//----------------------------------------------------
NSSize requiredContentRectSize;
if ([self isStringForEmptyCell:attrStringForRange.string]) {
requiredContentRectSize = NSMakeSize(1.0, 1.0);
} else {
NSSize contentRectSize = NSMakeSize(res.size.width, rect.size.height);
requiredContentRectSize = [XTFontUtils requiredRectForText:attrStringForRange
forViewOfSize:contentRectSize
suppressCenterAndRightTabs:YES];
}
CGFloat requiredContentRectHeight = requiredContentRectSize.height;
NSUInteger numberOfNewlinesTrailingNewlines = [XTStringUtils numberOfTrailingNewlinesIn:attrStringForRange.string];
if (numberOfNewlinesTrailingNewlines >= 2) {
requiredContentRectHeight += 1.0; // This is required to avoid looping the OS layout engine
}
res.size.height = requiredContentRectHeight;
totalTimeInRectForLayoutAtPoint5 += [timer timeElapsed];
// Log and finish up
//----------------------------------------------------
/*
XT_WARN_12(@"row=%ld col=%ld (start x=%ld y=%ld) (in rect x=%ld y=%ld w=%ld h=%ld) -> (x=%ld y=%ld w=%ld h=%ld)",
self.startingRow, self.startingColumn,
(NSInteger)startingPoint.x, (NSInteger)startingPoint.y,
(NSInteger)rect.origin.x, (NSInteger)rect.origin.y,
(NSInteger)rect.size.width, (NSInteger)rect.size.height,
(NSInteger)res.origin.x, (NSInteger)res.origin.y,
(NSInteger)res.size.width, (NSInteger)res.size.height);
*/
/*
XT_WARN_8(@"row=%ld col=%ld (start x=%ld) (in rect x=%ld w=%ld) -> res=(x=%ld w=%ld) instance %@",
self.startingRow, self.startingColumn,
(NSInteger)startingPoint.x,
(NSInteger)rect.origin.x,
(NSInteger)rect.size.width,
(NSInteger)res.origin.x,
(NSInteger)res.size.width,
self);
*/
/*
XT_WARN_6(@"row=%ld col=%ld (in rect x=%ld w=%ld) -> res=(x=%ld w=%ld)",
self.startingRow, self.startingColumn,
(NSInteger)rect.origin.x,
(NSInteger)rect.size.width,
(NSInteger)res.origin.x,
(NSInteger)res.size.width);
*/
if (res.size.width < 0.0) {
XT_WARN_3(@"row=%ld col=%ld : res.width %lf < 0.0", self.startingRow, self.startingColumn, res.size.width);
} else {
//XT_WARN_2(@"row=%ld col=%ld OK", self.startingRow, self.startingColumn);
}
CGFloat totalBoundsWidth = [self totalBoundsWidth];
CGFloat limit = rect.size.width + totalBoundsWidth;
// adjust by totalBoundsWidth because columnInfo.totalBoundsWidth might not have been set yet
if (res.origin.x + res.size.width > limit) {
XT_WARN_5(@"row=%ld col=%ld res.x %lf + res.width %lf > limit %lf", self.startingRow, self.startingColumn, res.origin.x, res.size.width, limit);
} else {
//XT_WARN_5(@"row=%ld col=%ld res.x %lf + res.width %lf <= limit %lf", self.startingRow, self.startingColumn, res.origin.x, res.size.width, limit);
//XT_WARN_6(@"row=%ld col=%ld res=(x=%lf width=%lf y=%lf height=%lf)",
// self.startingRow, self.startingColumn, res.origin.x, res.size.width, res.origin.y, res.size.height);
}
/*
XTRequiredRectForTextCache *reqRectForTextCache = [XTRequiredRectForTextCache singletonInstance];
XT_WARN_9(@"count=%ld totalTime1=%lf totalTime2=%lf totalTime3=%lf totalTime4=%lf totalTime5=%lf totalTime=%lf -- totalTimeInRequiredRectForText=%lf cache-stats=%@",
countRectForLayoutAtPoint,
totalTimeInRectForLayoutAtPoint1,
totalTimeInRectForLayoutAtPoint2 - totalTimeInRectForLayoutAtPoint1,
totalTimeInRectForLayoutAtPoint3 - totalTimeInRectForLayoutAtPoint2,
totalTimeInRectForLayoutAtPoint4 - totalTimeInRectForLayoutAtPoint3,
totalTimeInRectForLayoutAtPoint5 - totalTimeInRectForLayoutAtPoint4,
totalTimeInRectForLayoutAtPoint5,
//[XTFontUtils getTotalTimeInRequiredRectForTextStorage],
[XTFontUtils getTotalTimeInRequiredRectForText],
[reqRectForTextCache statsString]
);
*/
if ((self.latestContentRect == nil) || ! CGRectEqualToRect(self.latestContentRect.rect, res)) {
XTTextView *textView = (XTTextView *)textContainer.textView;
textView.tableCellChangedContentRect = YES;
}
self.latestContentRect = [XTRect with:res];
self.rangeForLatestContentRect = NSMakeRange(charRange.location, charRange.length);
//XT_WARN_2(@"self.latestContentRect set to (width=%lf) for instance %@", self, res.size.width);
//XT_WARN_4(@"row=%ld col=%ld -> res.width=%lf res.height=%lf", self.startingRow, self.startingColumn, res.size.width, res.size.height);
return res;
}
- (NSRect)boundsRectForContentRect:(NSRect)contentRect
inRect:(NSRect)rect
textContainer:(NSTextContainer *)textContainer
characterRange:(NSRange)charRange
{
//XTTimer *timer = [XTTimer fromNow];
//countBoundsRectForContentRect += 1;
XT_DEF_SELNAME;
/*
XT_WARN_8(@"contentRect=(x=%ld y=%ld w=%ld h=%ld) rect=(x=%ld y=%ld w=%ld, h=%ld)",
(NSInteger)contentRect.origin.x, (NSInteger)contentRect.origin.y,
(NSInteger)contentRect.size.width, (NSInteger)contentRect.size.height,
(NSInteger)rect.origin.x, (NSInteger)rect.origin.y,
(NSInteger)rect.size.width, (NSInteger)rect.size.height);
XT_WARN_4(@"contentRect=(x=%ld w=%ld) rect=(x=%ld w=%ld)",
(NSInteger)contentRect.origin.x,
(NSInteger)contentRect.size.width,
(NSInteger)rect.origin.x,
(NSInteger)rect.size.width);
XT_WARN_2(@"contentRect=(x=%ld w=%ld)",
(NSInteger)contentRect.origin.x,
(NSInteger)contentRect.size.width);
*/
//XT_WARN_2(@"row=%ld col=%ld", self.startingRow, self.startingColumn);
XTTableColumnWidthTracker *tracker = [XTTableColumnWidthTracker tracker];
NSRect res = [super boundsRectForContentRect:contentRect inRect:rect textContainer:textContainer characterRange:charRange];
//NSRect resOrig = res;
CGFloat totalBoundsWidth = [self totalBoundsWidth];
CGFloat origWidth = res.size.width;
res.size.width = contentRect.size.width + totalBoundsWidth;
//XT_WARN_6(@"row=%ld col=%ld res.width %lf -> contentRect.width %lf + totalBoundsWidth %lf = %lf",
// self.startingRow, self.startingColumn, origWidth, contentRect.size.width, totalBoundsWidth, res.size.width);
[tracker noteTotalBoundsWidthForTable:self.tableId
textTableBlock:self
width:totalBoundsWidth /*WithCollapsingBorders*/];
[tracker noteBoundsRectWidthForTable:self.tableId
textTableBlock:self
width:res.size.width];
/*
XT_WARN_6(@"row=%ld col=%ld res=(x=%lf width=%lf y=%lf height=%lf)",
self.startingRow, self.startingColumn,
res.origin.x, res.size.width, res.origin.y, res.size.height);
/*/
/*
XT_WARN_2(@"res=(x=%ld w=%ld)",
(NSInteger)res.origin.x,
(NSInteger)res.size.width);
*/
//totalTimeInBoundsRectForContentRect += [timer timeElapsed];
//XT_WARN_2(@"count=%ld totalTime=%lf", countBoundsRectForContentRect, totalTimeInBoundsRectForContentRect);
if (self.latestContentRect != nil &&
self.latestContentRect.rect.origin.x == contentRect.origin.x &&
self.latestContentRect.rect.size.width == contentRect.size.width) {
//
// contentRect param is same as last return value from rectForLayoutAtPoint,
// so we expect res to have "normal" values.
// (For some reason, boundsRectForContentRect: doesn't always get same contentRect as returned by rectForLayoutAtPoint:)
//
//TODO !!! test _18
if (res.origin.x < 0.0) {
XT_WARN_3(@"row=%ld col=%ld res.x %lf < 0.0", self.startingRow, self.startingColumn, res.origin.x);
int brkpt = 1;
} else {
//XT_WARN_3(@"row=%ld col=%ld res.x %lf >= 0.0", self.startingRow, self.startingColumn, res.origin.x);
}
CGFloat limit = rect.size.width + totalBoundsWidth;
// because rect.size.width might have been calc'd before columnInfo.totalBoundsWidth was updated
// (See similar affordance in rectForLayoutAtPoint)
if (res.size.width > limit) {
XT_WARN_5(@"row=%ld col=%ld res.width %lf > limit %lf, totalBoundsWidth=%lf", self.startingRow, self.startingColumn, res.size.width, limit, totalBoundsWidth);
int brkpt = 1;
} else {
//XT_WARN_5(@"row=%ld col=%ld res.width %lf <= limit %lf, totalBoundsWidth=%lf", self.startingRow, self.startingColumn, res.size.width,limit, totalBoundsWidth);
}
}
return res;
}
- (CGFloat)totalBoundsWidth
{
//XT_DEF_SELNAME;
//TODO !!! consider value type?
//TODO !!! consider collapsing borders
CGFloat borderLeft = [self widthForLayer:NSTextBlockBorder edge:NSRectEdgeMinX];
CGFloat borderRight = [self widthForLayer:NSTextBlockBorder edge:NSRectEdgeMaxX];
CGFloat paddingLeft = [self widthForLayer:NSTextBlockPadding edge:NSRectEdgeMinX];
CGFloat paddingRight = [self widthForLayer:NSTextBlockPadding edge:NSRectEdgeMaxX];
CGFloat marginLeft = [self widthForLayer:NSTextBlockMargin edge:NSRectEdgeMinX];
CGFloat marginRight = [self widthForLayer:NSTextBlockMargin edge:NSRectEdgeMaxX];
CGFloat res = borderLeft + borderRight + paddingLeft + paddingRight + marginLeft + marginRight;
//XT_WARN_3(@"row col %ld %ld --> %lf", self.startingRow, self.startingColumn, res);
/*XT_WARN_8(@"row=%ld col=%ld : borderLeft=%lf borderRight=%lf paddingLeft=%lf paddingRight=%lf marginLeft=%lf marginRight=%lf",
self.startingRow, self.startingColumn,
borderLeft, borderRight,
paddingLeft, paddingRight,
marginLeft, marginRight);*/
return res;
}
- (BOOL)isStringForEmptyCell:(NSString *)string
{
BOOL res = NO;
if ((string == nil) || (string.length == 0) || [string isEqualToString:@"\n"]) {
res = YES;
}
return res;
}
- (CGFloat)totalBoundsWidthOnLeftHandSide
{
//TODO !!! consider value type?
//TODO !!! consider collapsing borders
CGFloat borderLeft = [self widthForLayer:NSTextBlockBorder edge:NSRectEdgeMinX];
CGFloat paddingLeft = [self widthForLayer:NSTextBlockPadding edge:NSRectEdgeMinX];
CGFloat marginLeft = [self widthForLayer:NSTextBlockMargin edge:NSRectEdgeMinX];
CGFloat res = borderLeft + paddingLeft + marginLeft;
return res;
}
- (CGFloat)totalTableLevelBoundsWidth
{
//TODO !!! consider collapsing borders
XTTextTable *table = (XTTextTable *)self.table;
CGFloat res = table.borderSize * 2.0;
//TODO !!! margin etc.
return res;
}
- (CGFloat)getCellSpacing
{
//TODO !!! use from if def'd?
XTTextTable *table = (XTTextTable *)self.table;
CGFloat res = table.cellSpacing;
return res;
}
- (void)recalcCellMargins
{
//XT_DEF_SELNAME;
CGFloat cellSpacing = [self getCellSpacing];
if (cellSpacing >= 1.0) {
NSInteger tableColumnIndex = self.startingColumn;
NSInteger tableRowIndex = self.startingRow;
NSInteger numberOfColumns = self.table.numberOfColumns;
NSInteger numberOfRows = ((XTTextTable *)self.table).numberOfRows;
BOOL isFirstColumn = (tableColumnIndex == 0);
BOOL isLastColumn = (numberOfColumns - 1 == tableColumnIndex);
BOOL isFirstRow = (tableRowIndex == 0);
BOOL isLastRow = (numberOfRows - 1 == tableRowIndex);
//TODO !!! need ceil/floor for these? :
CGFloat marginLeft = (isFirstColumn ? cellSpacing : cellSpacing / 2.0);
CGFloat marginRight = (isLastColumn ? cellSpacing : cellSpacing / 2.0);
[self setWidth:marginLeft type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockMargin edge:NSRectEdgeMinX];
[self setWidth:marginRight type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockMargin edge:NSRectEdgeMaxX];
CGFloat marginTop = (isFirstRow ? cellSpacing : cellSpacing / 2.0);
CGFloat marginBottom = (isLastRow ? cellSpacing : cellSpacing / 2.0);
[self setWidth:marginTop type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockMargin edge:NSRectEdgeMinY];
[self setWidth:marginBottom type:NSTextBlockAbsoluteValueType forLayer:NSTextBlockMargin edge:NSRectEdgeMaxY];
//XT_WARN_0(@"");
}
}
- (BOOL)allowGameToSetColors
{
XTPrefs *prefs = [XTPrefs prefs];
NSNumber *valueObj = prefs.allowGamesToSetColors.value;
BOOL res = (valueObj == nil || valueObj.boolValue);
return res;
}
@end
|