//
//  XTOutputFormatter.m
//  XTads
//
//  Created by Rune Berg on 09/07/14.
//  Copyright (c) 2014 Rune Berg. All rights reserved.
//

#import "XTOutputFormatter.h"
#import "XTTextView.h"
#import "XTHtmlLinebreakHandler2.h"
#import "XTFormattedOutputElement.h"
#import "XTHtmlTagContainer.h"
#import "XTHtmlTagContainer_private.h"
#import "XTHtmlTagA.h"
#import "XTHtmlTagHr.h"
#import "XTHtmlTagQ.h"
#import "XTHtmlTagAboutBox.h"
#import "XTHtmlTagBanner.h"
#import "XTHtmlTagCenter.h"
#import "XTHtmlTagH1.h"
#import "XTHtmlTagH2.h"
#import "XTHtmlTagH3.h"
#import "XTHtmlTagH4.h"
#import "XTHtmlTagOl.h"
#import "XTHtmlTagUl.h"
#import "XTHtmlTagLi.h"
#import "XTHtmlTagNoop.h"
#import "XTHtmlTagQuestionMarkT2.h"
#import "XTHtmlTagQuestionMarkT3.h"
#import "XTHtmlTagTab.h"
#import "XTHtmlTagBr.h"
#import "XTHtmlTagP.h"
#import "XTHtmlTagTitle.h"
#import "XTHtmlTagFont.h"
#import "XTHtmlTagPre.h"
#import "XTHtmlTagImg.h"
#import "XTHtmlTagBody.h"
#import "XTHtmlTagTable.h"
#import "XTHtmlTagTr.h"
#import "XTHtmlTagTh.h"
#import "XTHtmlTagTd.h"
#import "XTHtmlTagT2TradStatusLine.h"
#import "XTHtmlTagWhitespace.h"
#import "XTHtmlTagQuotedSpace.h"
#import "XTHtmlTagSpecialSpace.h"
#import "XTHtmlTagNonbreakingSpace.h"
#import "XTHtmlTagText.h"
#import "XTHtmlTagCredit.h"
#import "XTLogger.h"
#import "XTFontManager.h"
#import "XTPendingWhitespaceQueue.h"
#import "XTPrefs.h"
#import "XTStringUtils.h"
#import "XTFontUtils.h"
#import "XTTabStopModel.h"
#import "XTTabStopModelEntry.h"
#import "XTAllocDeallocCounter.h"
#import "XTTimer.h"
#import "XTNotifications.h"
#import "XTTextAlignMode.h"
#import "XTCharConstants.h"
#import "XTTextTableBlock.h"
//#import "XTTableDimensionsTracker.h"
#import "XTMutableAttributedStringHelper.h"
#import "XTFormattingSpecificationForHtmlTag.h"
#import "XTHtmlUtils.h"
#import "XTRect.h"
#import "XTTabStopUtils.h"
#import "XTRequiredRectForTextCache.h"
#import "XTTextTab.h"
#import "XTDefaultColours.h"
#import "XTOutputTextColorResult.h"
#import "XTColorUtils.h"
#import "XTColorationHelper.h"
#import "XTTabStopHelper.h"


@interface XTOutputFormatter ()

@property XTHtmlLinebreakHandler2 *linebreakHandler2;
@property BOOL afterBlockLevelSpacing;

@property XTPendingWhitespaceQueue *pendingWhitespaceQueue;

@property BOOL shouldWriteWhitespace;
	//TODO rename?

@property CGFloat pendingParagraphSpacingBefore;
@property XTTextAlignMode pTagTextAlignMode;

@property NSInteger tagBannerDepth;
@property NSInteger tableDepth;

@property NSUInteger listItemIndexForContinue;

@property NSArray *emptyArray;

@property XTFontManager *fontManager;
@property XTPrefs *prefs;
@property XTTabStopHelper *tabStopHelper;

@property XTHtmlTagSpecialSpace *htmlSpecialSpaceForOneSpace;

@end


@implementation XTOutputFormatter

static XTLogger* logger;

@synthesize textView = _textView;
@synthesize colorationHelper = _colorationHelper;
@synthesize htmlMode = _htmlMode;
@synthesize formattingSpecForHtmlTag = _formattingSpecForHtmlTag;
@synthesize isForTagBanner = _isForTagBanner;
@synthesize needsRecalcAllTabStops = _needsRecalcAllTabStops;

+ (void)initialize
{
	logger = [XTLogger loggerForClass:[XTOutputFormatter class]];
}

OVERRIDE_ALLOC_FOR_COUNTER
OVERRIDE_DEALLOC_FOR_COUNTER

- (id)init
{
    self = [super init];
    if (self) {
		_linebreakHandler2 = [XTHtmlLinebreakHandler2 new];
		_fontManager = [XTFontManager fontManager];
		_pendingWhitespaceQueue = [XTPendingWhitespaceQueue new];
		_pTagTextAlignMode = XT_TEXT_ALIGN_LEFT;
		
		_prefs = [XTPrefs prefs];
		_tabStopHelper = [XTTabStopHelper new];
		
		_emptyArray = [NSArray array];
		_isForBanner = NO;
		_isForTagBanner = NO;
		_isForT3 = NO;
		_htmlSpecialSpaceForOneSpace = [XTHtmlTagSpecialSpace tagWithChar:' '];
		
		_formattingSpecForHtmlTag = [XTFormattingSpecificationForHtmlTag new];
		
		_tagBannerDepth = 0;
		_tableDepth = 0;
		_pendingParagraphSpacingBefore = 0.0;
		
		_listItemIndexForContinue = 1;
		
		_needsRecalcAllTabStops = NO;
		
		[self resetFlags];
 	}
    return self;
}

- (void)setTextView:(XTTextView *)textView
{
	_textView = textView;
	self.tabStopHelper.textView = textView;
	[self.textView setColorationHelper:self.colorationHelper];
}

- (XTTextView *)textView
{
	return _textView;
}

- (void)setColorationHelper:(XTColorationHelper *)colorationHelper
{
	_colorationHelper = colorationHelper;
	[self.textView setColorationHelper:colorationHelper];
}

- (XTColorationHelper *)colorationHelper
{
	return _colorationHelper;
}

- (NSTextStorage *)textStorage
{
	NSTextStorage *textStorage = [self.textView textStorage];
	return textStorage;
}

- (BOOL)isForTagBanner
{
	return _isForTagBanner;
}

- (void)setIsForTagBanner:(BOOL)isForTagBanner
{
	_isForTagBanner = isForTagBanner;
	if (isForTagBanner) {
		self.tagBannerDepth = 1;
	} else {
		self.tagBannerDepth = 0;
	}
}

- (void)setHtmlMode:(BOOL)htmlMode
{
	XT_DEF_SELNAME;
	XT_TRACE_1(@"%d", htmlMode);
	
	_htmlMode = htmlMode;
}

- (BOOL)htmlMode {
	return _htmlMode;
}

- (void)teardown
{
	_textView = nil;
	_linebreakHandler2 = nil;
	[_pendingWhitespaceQueue reset];
	_pendingWhitespaceQueue = nil;
	[self.tabStopHelper resetTabStopModel];
	_emptyArray = nil;
	_fontManager = nil;
	_prefs = nil;
	_htmlSpecialSpaceForOneSpace = nil;
}

- (void)resetFlags
{
	//XT_DEF_SELNAME;
	//XT_WARN_0(@"");

	[self resetNonHtmlModesFlags];
	[self resetHtmlModesFlags];
	[self.linebreakHandler2 reset];
	[self.tabStopHelper resetTabStopModel];

	self.formattingSpecForHtmlTag = [XTFormattingSpecificationForHtmlTag new];
}

- (void)resetForNextCommand
{
	//XT_DEF_SELNAME;
	//XT_WARN_0(@"");
	
	[self resetNonHtmlModesFlags];
	[self.linebreakHandler2 resetForNextCommand];
	[self.formattingSpecForHtmlTag.formattingSpec resetForNextCommand];
}

- (void)resetNonHtmlModesFlags
{
	_receivingGameTitle = NO;
	_afterBlockLevelSpacing = NO;
	[_pendingWhitespaceQueue reset];
	_shouldWriteWhitespace = NO;
}

- (void)resetHtmlModesFlags
{
	//TODO !!! curr font spec?
	_tagBannerDepth = 0;
	_tableDepth = 0;
	_pendingParagraphSpacingBefore = 0.0;
	_listItemIndexForContinue = 1;
}

- (BOOL)suppressContentInTagBanner
{
	NSUInteger minTagBannerDepthForSuppression = (self.isForT3 ? 1 : 2);
	BOOL res = (self.tagBannerDepth >= minTagBannerDepthForSuppression);
	return res;
}

//TODO why isn't this called for banners?
- (NSArray *)flushPendingWhitespace
{
	XT_DEF_SELNAME;

	NSMutableArray *res = [self makeArrayWithPendingWhitespace];
	[self clearPendingWhitespace];
	
	XT_TRACE_1(@"res.count=%lu", res.count);
	return res;
}

- (NSArray<NSAttributedString *>*)formatOutputText:(NSString *)string;
{
	return [self makeAttributedStringForOutput:string];
}

- (NSAttributedString *)formatInputText:(NSString *)string
{
	return [self makeAttributedStringForInput:string];
}

- (NSAttributedString *)formatStringForGridBanner:(NSString *)string
{
	return [self makeAttributedStringForGridBanner:string];
}

//-------------------------------------------------------------------------------------------

- (BOOL)willProcessTag:(XTHtmlTag *)tag
{
	BOOL res =
		[tag isKindOfClass:[XTHtmlTagBanner class]] ||
		(((! self.isForT3) || (! self.isForTagBanner)));
	return res;
}

- (NSArray *)handleBlockLevelTagEntry:(XTHtmlTag *)tag
{
	NSMutableArray *resTemp = [NSMutableArray array];
	
	if (! [self shouldPrintOutputText]) {
		return resTemp;
	}
	
	//TODO must clear self.afterBlockLevelSpacing
	// - for non block level tag

	/*TODO adapt?... not here, in caller? is InlineTag event useful anymore?
	if (! tag.isBlockLevel) {
		if (! [tag isKindOfClass:[XTHtmlTagTab class]]) {
			if (! [tag isKindOfClass:[XTHtmlTagBr class]]) {
				[self.pendingWhitespaceQueue appendInlineTag];
			}
		}
	}*/
	
	//TODO try and combine with lbh2
	if ([tag needsBlockLevelSpacingBefore])	{
		if (! tag.closing) {
			if (! self.afterBlockLevelSpacing) {
				NSArray<NSMutableAttributedString *> *mutAttrStringArray = [self makeAttributedStringForOutput:@"\n"];
				for (NSMutableAttributedString *mutAttrString in mutAttrStringArray) {
					[resTemp addObject:[XTFormattedOutputElement regularOutputElement:mutAttrString]];
				}
				self.afterBlockLevelSpacing = YES;
			}
		} else {
			//XT_DEF_SELNAME;
			//XT_WARN_0(@"tag.closing == YES");
			//int brkpt = 1;
		}
	}
	NSString *resPrefix = [self.linebreakHandler2 handleBlockLevelNewline:tag];
	if (resPrefix != nil) {
		NSArray<NSMutableAttributedString *> *mutAttrStringArray = [self makeAttributedStringForOutput:resPrefix];
		for (NSMutableAttributedString *mutAttrString in mutAttrStringArray) {
			[resTemp addObject:[XTFormattedOutputElement regularOutputElement:mutAttrString]];
		}
	}

	[self handleWhitespaceBeforeOrAfterBlockLevelTag:resTemp];
	
	return resTemp;
}

//TODO !!! mv
- (void)handleWhitespaceBeforeOrAfterBlockLevelTag:(NSMutableArray *)res
{
	//XT_DEF_SELNAME;
	
	self.shouldWriteWhitespace = (self.linebreakHandler2.state != XT_LINEBREAKHANDLER2_AT_START_OF_LINE);
	//XT_WARN_1(@"shouldWriteWhitespace=%d", self.shouldWriteWhitespace);
	if (! self.shouldWriteWhitespace) {
		//XT_WARN_ENTRY;
		[self clearPendingWhitespace]; // must be done sync'ly!
		[res addObject:[XTFormattedOutputElement clearWhitespaceBeforeOrAfterBlockLevelTagOutputElement]];
	}
}

- (void)clearAfterBlockLevelSpacing
{
	self.afterBlockLevelSpacing = NO;
}

- (void)resetTagBannerDepth
{
	self.tagBannerDepth = 0;
}

- (void)clearShouldWriteWhiteSpace
{
	self.shouldWriteWhitespace = NO;
}

- (NSArray *)handleBlockLevelTagExit:(XTHtmlTag *)tag
{
	NSMutableArray *res = [NSMutableArray array];

	if (! [self shouldPrintOutputText]) {
		return res;
	}

	NSString *resSuffix = [self.linebreakHandler2 handleBlockLevelNewline:tag];
	if (resSuffix != nil) {
		NSArray<NSMutableAttributedString *> *mutAttrStringArray = [self makeAttributedStringForOutput:resSuffix];
		for (NSMutableAttributedString *mutAttrString in mutAttrStringArray) {
			[res addObject:[XTFormattedOutputElement regularOutputElement:mutAttrString]];
		}
	}

	[self handleWhitespaceBeforeOrAfterBlockLevelTag:res];
		//TODO try and combine with lbh2
	if ([tag needsBlockLevelSpacingAfter])	{
		if (! self.afterBlockLevelSpacing) {
			NSArray<NSMutableAttributedString *> *mutAttrStringArray = [self makeAttributedStringForOutput:@"\n"];
			for (NSMutableAttributedString *mutAttrString in mutAttrStringArray) {
				[res addObject:[XTFormattedOutputElement regularOutputElement:mutAttrString]];
			}
			self.afterBlockLevelSpacing = YES;
		}
	}

	return res;
}

- (NSArray *)handleRegularText:(NSString *)string verbatim:(BOOL)verbatim
{
	//XT_DEF_SELNAME;
	//XT_WARN_2(@"\"%@\" htmlMode=%d", string, self.htmlMode);

	self.afterBlockLevelSpacing = NO;
	
	NSMutableArray *res = [NSMutableArray array];
	
	if (! self.htmlMode) {
		// Handle pending ws for plain-text mode is due to tab handling
		res = [self makeArrayWithPendingWhitespace];
		[self clearPendingWhitespace];
		
		NSArray<XTFormattedOutputElement *>* array = [self makeRegularOutputElements:string];
		for (XTFormattedOutputElement *fmtOutputElt in array) {
			[res addObject:fmtOutputElt];
			NSAttributedString *attrString = fmtOutputElt.attributedString;
			if (attrString != nil && attrString.length >= 1) {
				[self.linebreakHandler2 handleText:attrString.string];
			}
		}
		
	} else {
		if (self.receivingGameTitle) {
			NSArray *tempRes = [self makeArrayWithGameTitleElement:string];
			[res addObjectsFromArray:tempRes];
		} else if (! [self shouldPrintOutputText]) {
			// nothing
			int brkpt = 1;
		} else {
			res = [self makeArrayWithPendingWhitespace];
			[self clearPendingWhitespace];
			if (! verbatim) {
				NSArray *stringsSepdByNewline = [string componentsSeparatedByString:@"\n"];
				//TODO mv to parser
				for (NSString *s in stringsSepdByNewline) {
					if (s.length >= 1) {
						[self.linebreakHandler2 handleText:s];
						[res addObjectsFromArray:[self makeRegularOutputElements:s]];
						self.shouldWriteWhitespace = YES;
						//XT_WARN_1(@"shouldWriteWhitespace=%d", self.shouldWriteWhitespace);
					}
				}
			} else {
				if (string.length >= 1) {
					[self.linebreakHandler2 handleText:string];
					[res addObjectsFromArray:[self makeRegularOutputElements:string]];
					self.shouldWriteWhitespace = YES;
					//XT_WARN_1(@"shouldWriteWhitespace=%d", self.shouldWriteWhitespace);
				}
			}
		}
	}
	
	return res;
}

//------- TODO ---------

- (void)addPendingWhitespace:(XTHtmlTagWhitespace *)whitespace
{
	//TODO tabs?
	NSString *wsText = whitespace.text;
	for (NSUInteger i = 0; i < wsText.length; i++) {
		unichar ch = [wsText characterAtIndex:i];
		if (ch == '\n') {
			// Handle newline as regular space.
			// (More convenient to convert this here than in pending-ws-queue.)
			ch = ' ';
		}
		NSString *wsOneCh = [NSString stringWithFormat:@"%C", ch];
			//TODO these can be precomputed
		NSArray<NSMutableAttributedString *> *mutAttrStringArray = [self makeAttributedStringForOutput:wsOneCh];
		for (NSMutableAttributedString *mutAttrString in mutAttrStringArray) {
			[self.pendingWhitespaceQueue append:mutAttrString];
		}
	}
}

- (void)addPendingSpecialSpace:(XTHtmlTagSpecialSpace *)specialSpace
{
	NSString *string = [NSString stringWithFormat:@"%C", specialSpace.ch];
	NSArray<NSMutableAttributedString *> *mutAttrStringArray = [self makeAttributedStringForOutput:string];
	for (NSMutableAttributedString *mutAttrString in mutAttrStringArray) {
		[self.pendingWhitespaceQueue append:mutAttrString];
	}
}

- (NSArray<NSAttributedString *> *)createTabStringForIndent:(NSNumber *)indent
{
	NSArray<NSAttributedString *> *res = nil;
	
	if (indent != nil) {
		NSInteger indentInt = indent.integerValue;
		if (indentInt >= 1) {
			//TODO? doesn't behave exactly like HTML TADS, but very nearly
			NSMutableString *mutString = [NSMutableString stringWithCapacity:indentInt];
			for (NSInteger idx = 0; idx < indentInt; idx++) {
				[mutString appendString:EFFECTIVE_EN_SPACE_FOR_TABS_PROP_FONT];
				//[mutString appendString:UNICHAR_EN_SPACE]; // too narrow. 2 regular spaces looks like on qtads.
			}
			NSArray<NSMutableAttributedString *> *mutAttrStringArray = [self makeAttributedStringForOutput:mutString];
			res = mutAttrStringArray;
		}
	}
	
	return res;
}

- (NSArray<NSAttributedString *>*)createTabStringFor:(XTHtmlTagTab *)tag
{
	NSArray<NSMutableAttributedString *> *res = [self makeAttributedStringForOutput:@"\t"];
	[XTMutableAttributedStringHelper addTabTag:tag toString:[res lastObject]];
	return res;
}

- (NSArray<NSAttributedString *>*)createTabStringForId:(XTHtmlTagTab *)tag
{
	NSArray<NSMutableAttributedString *> *res = [self makeAttributedStringForOutput:ZERO_WIDTH_SPACE];
	[XTMutableAttributedStringHelper addTabTag:tag toString:[res lastObject]];
	return res;
}

- (NSArray<NSAttributedString *>*)createAttributedStringForChar:(unichar)ch
{
	NSString *string = [NSString stringWithFormat:@"%C", ch];
	NSArray<NSMutableAttributedString *> *res = [self makeAttributedStringForOutput:string];
	return res;
}

- (void)clearPendingWhitespace
{
	[self.pendingWhitespaceQueue reset];
}

//-------------

- (NSMutableArray *)makeArrayWithPendingWhitespace
{
	NSMutableArray *res = [self makeArrayWithPendingWhitespace:NO];
	return res;
}

- (NSMutableArray *)makeArrayWithPendingWhitespace:(BOOL)force
{
	XT_DEF_SELNAME;
	
	NSMutableArray *res = [NSMutableArray arrayWithCapacity:30];
	
	if (force || self.shouldWriteWhitespace /*TODO rm || [self.pendingWhitespaceQueue containsTabs]*/) {
		NSMutableArray *tempRes = [self.pendingWhitespaceQueue combinedWhitespace];
		//TODO ? return one attr str?
		for (NSMutableAttributedString *attrStr in tempRes) {
			[res addObject:[XTFormattedOutputElement regularOutputElement:attrStr]];
		}
	}
	
	XT_TRACE_1(@"-> with res.count %d", res.count);
	return res;
}
	
- (NSArray *)makeArrayWithGameTitleElement:(NSString *)string
{
	XTFormattedOutputElement *outputElement;
	NSMutableAttributedString *attrString = [[NSMutableAttributedString new] initWithString:string];
		//TODO why mutable?
	outputElement = [XTFormattedOutputElement gameTitleElement:attrString];
	NSArray *res = [NSArray arrayWithObject:outputElement];
	return res;
}

- (NSArray *)makeArrayWithRegularOutputElement:(NSString *)string
{
	NSArray *res = [NSArray arrayWithArray:[self makeRegularOutputElements:string]];
	return res;
}

//------

- (NSArray<XTFormattedOutputElement *>*)makeRegularOutputElements:(NSString *)string
{
	NSArray<NSMutableAttributedString *> *mutAttrStringArray;
	if (self.isForGridBanner) {
		NSMutableAttributedString *mutAttrString = [self makeAttributedStringForGridBanner:string];
		mutAttrStringArray = [NSArray arrayWithObject:mutAttrString];
	} else {
		mutAttrStringArray = [self makeAttributedStringForOutput:string];
	}
	
	NSMutableArray<XTFormattedOutputElement *> *res = [NSMutableArray arrayWithCapacity:mutAttrStringArray.count];
	for (NSMutableAttributedString *mutAttrString in mutAttrStringArray) {
		XTFormattedOutputElement *outputElement = [XTFormattedOutputElement regularOutputElement:mutAttrString];
		[res addObject:outputElement];
	}
	return res;
}

- (NSArray<NSMutableAttributedString *>*)makeAttributedStringForOutput:(NSString *)string
{
	if (! [self shouldPrintOutputText]) {
		return self.emptyArray;
	}
	
	NSDictionary *dict = [self getTextAttributesDictionaryForOutput];

	if ([string isEqualToString:@"\n"]) {
		XTFormattingSpecification *formattingSpec = self.formattingSpecForHtmlTag.formattingSpec;
		if (formattingSpec.inListItem) {
			formattingSpec.inListItemAfterFirstParagraph = YES;
		}
	}
	
	NSMutableArray<NSAttributedString *> *mutRes = [NSMutableArray arrayWithCapacity:2];
	
	if ([self.linebreakHandler2 isAtStartOfLine]) {
		if (! [string isEqualToString:ZERO_WIDTH_SPACE]) {
			//TODO !!! skip if "\n"?
			if (! [string isEqualToString:@"\n"]) {
				NSMutableAttributedString *zwspAttrString = [[NSMutableAttributedString alloc] initWithString:ZERO_WIDTH_SPACE
																							   attributes:dict];
				[mutRes addObject:zwspAttrString];
			}
		} else {
			int brkpt = 1;
		}
	}
	
   	NSMutableAttributedString *mutAttrString = [[NSMutableAttributedString alloc] initWithString:string
																				   attributes:dict];
	if (dict[XT_OUTPUT_FORMATTER_ATTR_RECOLORABLE_TEXT_BACKGROUND] != nil) {
		[self removeTextBackgroundColorForNewlines:mutAttrString];
	}
	[mutRes addObject:mutAttrString];

	return [NSArray arrayWithArray:mutRes];
}

- (NSMutableAttributedString *)makeAttributedStringForInput:(NSString *)string
{
	NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] initWithString:string
																				   attributes:[self getTextAttributesDictionaryForInput]];
	return attrString;
}

- (NSMutableAttributedString *)makeAttributedStringForGridBanner:(NSString *)string
{
	NSDictionary *attrs = [self getTextAttributesDictionaryForGridBanner];
	NSMutableAttributedString *mutAttrString = [[NSMutableAttributedString alloc] initWithString:string
																				   attributes:attrs];
	
	if (attrs[XT_OUTPUT_FORMATTER_ATTR_RECOLORABLE_TEXT_BACKGROUND] != nil) {
		[self removeTextBackgroundColorForNewlines:mutAttrString];
	}
	
	return mutAttrString;
}

- (void)removeTextBackgroundColorForNewlines:(NSMutableAttributedString *)mutAttrString
{
	[XTStringUtils removeAttributeForNewlines:mutAttrString attrName:XT_OUTPUT_FORMATTER_ATTR_RECOLORABLE_TEXT_BACKGROUND];
	[XTStringUtils removeAttributeForNewlines:mutAttrString attrName:NSBackgroundColorAttributeName];
}

//------

- (NSDictionary *)getTextAttributesDictionaryForOutput
{
	XT_DEF_SELNAME;
	
	NSMutableDictionary *dict = [self getTextAttributesDictionaryCommonForOutput];
	NSMutableParagraphStyle *pgStyle = dict[NSParagraphStyleAttributeName];

	NSArray *tabStops = [self.tabStopHelper getDefaultTabStops];
	[pgStyle setTabStops:tabStops];
	
	XTFormattingSpecification *formattingSpec = self.formattingSpecForHtmlTag.formattingSpec;

	NSTextAlignment alignment = NSTextAlignmentLeft;
	if (formattingSpec.credit) {
		alignment = NSTextAlignmentRight;
	} else if (formattingSpec.textAlignMode == XT_TEXT_ALIGN_LEFT) {
		alignment = NSTextAlignmentLeft;
	} else if (formattingSpec.textAlignMode == XT_TEXT_ALIGN_CENTER) {
		alignment = NSTextAlignmentCenter;
	} else if (formattingSpec.textAlignMode == XT_TEXT_ALIGN_RIGHT) {
		alignment = NSTextAlignmentRight;
	} else if (formattingSpec.textAlignMode == XT_TEXT_ALIGN_JUSTIFY) {
		alignment = NSTextAlignmentJustified;
	} else if (formattingSpec.textAlignMode == XT_TEXT_ALIGN_UNSPECIFIED) {
		alignment = NSTextAlignmentLeft;
	} else {
		XT_ERROR_1(@"unknown textAlignMode %d", formattingSpec.textAlignMode);
	}
	[pgStyle setAlignment:alignment];

	CGFloat firstLineHeadIndent = 0.0;
	CGFloat headIndent = 0.0;
	CGFloat tailIndent = 0.0;

	if (formattingSpec.blockquoteLevel >= 1) {

		CGFloat tabWidth = [self getPlainTextModeTabStopColumnWidthInPoints];
		CGFloat indent = tabWidth * formattingSpec.blockquoteLevel * 1.35;
		firstLineHeadIndent += indent;
		headIndent += indent;
		tailIndent -= indent;
		
	}
	
	CGFloat listItemTab1Width = 0;
	CGFloat listItemTab2Width = 0;

	if (formattingSpec.listLevel >= 1) {
		if (formattingSpec.inListItem || formattingSpec.inListHeader) {

			listItemTab1Width = [self calcListItemTab1Width];
			listItemTab2Width = [self calcListItemTab2Width];

			NSUInteger listLevelUsed = formattingSpec.listLevel;
			if (formattingSpec.inListHeader) {
				listLevelUsed -= 1;
			}
			
			CGFloat flhiBaseFactor = [self getPlainTextModeTabStopColumnWidthInPoints];
			flhiBaseFactor = ceil(flhiBaseFactor * 1.51);
			
			if (formattingSpec.inListItemAfterFirstParagraph) {
				// 2nd+ para in a list item should have both headIndent and firstLineHeadIndent == headIndent of 1st para
				firstLineHeadIndent += listLevelUsed * flhiBaseFactor + listItemTab2Width;
				if (formattingSpec.inListHeader && listLevelUsed >= 1) {
					if (! formattingSpec.inListItem) {
						firstLineHeadIndent += listItemTab1Width;
					}
					firstLineHeadIndent += listItemTab2Width;
				}
				headIndent = firstLineHeadIndent;
			} else {
				firstLineHeadIndent += (listLevelUsed * flhiBaseFactor - listItemTab1Width);
				if (firstLineHeadIndent < 0.0) {
					//firstLineHeadIndent = 0.0;
				}
				if (formattingSpec.inListHeader && listLevelUsed >= 1) {
					if (! formattingSpec.inListItem) {
						firstLineHeadIndent += listItemTab1Width;
					}
					firstLineHeadIndent += listItemTab2Width;
				}
				headIndent = firstLineHeadIndent + listItemTab1Width + listItemTab2Width; //[self getListItemPrefixColumnWidthInPoints];
			}
			//XT_WARN_6(@"listLevel=%lu AfterFirstParagraph=%ld Tab1h=%lf Tab2h=%lf -> flHI=%lf hi=%lf",
			//		  formattingSpec.listLevel, formattingSpec.inListItemAfterFirstParagraph, listItemTab1Width, listItemTab2Width, firstLineHeadIndent, headIndent);
		}
	}
	
	if (formattingSpec.dlLevel >= 1) {
		CGFloat tabWidth = [self getPlainTextModeTabStopColumnWidthInPoints];
		CGFloat indent = tabWidth * ((CGFloat)(formattingSpec.dlLevel - 1)) * 1.8;
		firstLineHeadIndent += indent;
		headIndent += indent;
		//XT_WARN_3(@"dlLevel %lu >= 1 - firstLineHeadIndent=%lf headIndent=%lf", formattingSpec.dlLevel, firstLineHeadIndent, headIndent);
	}
	if (formattingSpec.dd) {
		CGFloat tabWidth = [self getPlainTextModeTabStopColumnWidthInPoints];
		CGFloat indent = tabWidth * 1.8;
		firstLineHeadIndent += indent;
		headIndent += indent;
		//XT_WARN_2(@"dd - firstLineHeadIndent=%lf headIndent=%lf", firstLineHeadIndent, headIndent);
	}

	firstLineHeadIndent = round(firstLineHeadIndent);
	if (firstLineHeadIndent != 0.0) {
		[pgStyle setFirstLineHeadIndent:firstLineHeadIndent];
	}
	headIndent = round(headIndent);
	if (headIndent != 0.0) {
		[pgStyle setHeadIndent:headIndent];
	}
	[formattingSpec setTotalHeadIndent:headIndent];
	tailIndent = round(tailIndent);
	if (tailIndent != 0.0) {
		[pgStyle setTailIndent:tailIndent];
	}
	[formattingSpec setTotalTailIndent:tailIndent];

	if (formattingSpec.listLevel >= 1) {
		if (formattingSpec.inListItem) {

			NSMutableArray *tabStops = [NSMutableArray arrayWithArray:pgStyle.tabStops];
			NSArray *newTabStops = [self.tabStopHelper createTabStopsForListItemFromFirstLineHeadIndent:firstLineHeadIndent
																							  tab1Width:listItemTab1Width
																							  tab2Width:listItemTab2Width];
			for (NSTextTab *tagStop in newTabStops) {
				[tabStops addObject:tagStop];
			}
			pgStyle.tabStops = tabStops;
		}
	}

	if (formattingSpec.underline) {
		dict[NSUnderlineStyleAttributeName] = [NSNumber numberWithInt:1];
	}
	
	if (formattingSpec.strikethrough) {
		dict[NSStrikethroughStyleAttributeName] = [NSNumber numberWithInt:NSUnderlineStyleSingle];
	}
	
	if (formattingSpec.superscript) {
		dict[NSSuperscriptAttributeName] = [NSNumber numberWithInt:1];
	}
	
	if (formattingSpec.subscript) {
		dict[NSSuperscriptAttributeName] = [NSNumber numberWithInt:-1];
	}
	
	NSTextTableBlock *textTableCell = formattingSpec.activeTextTableCell;
	if (textTableCell != nil) {
		[pgStyle setTextBlocks:[NSArray arrayWithObjects:textTableCell, nil]];
			//TODO !!! must support nested tables
	}
	
	if (self.pendingParagraphSpacingBefore >= 1.0) {
		pgStyle.paragraphSpacingBefore = self.pendingParagraphSpacingBefore;
	}
	self.pendingParagraphSpacingBefore = 0.0;
	
	XTHtmlTagA *activeTagA = formattingSpec.activeTagA;
	[self updateTextAttributesDictionaryForOutput:dict
									fromTHtmlTagA:activeTagA];
	
	NSDictionary *temporaryAttrsDict = [self getTextTemporaryAttributesDictionaryForOutput];
	dict[XT_OUTPUT_FORMATTER_ATTR_TEMPATTRSDICT] = temporaryAttrsDict;
	
	return dict;
}

- (void)updateTextAttributesDictionaryForOutput:(NSMutableDictionary *)mutDict
								fromTHtmlTagA:(XTHtmlTagA *)activeTagA
{
	if (activeTagA != nil) {
		NSString *href = [activeTagA attributeAsString:@"href"];
		if (href == nil) {
			href = @"";
		}
		mutDict[NSLinkAttributeName] = href;
		if ([activeTagA hasAttribute:@"title"]) {
			mutDict[NSToolTipAttributeName] = [activeTagA attributeAsString:@"title"];
		}
		if ([activeTagA hasAttribute:@"append"]) {
			mutDict[XT_OUTPUT_FORMATTER_ATTR_CMDLINK_APPEND] = @"true";
		}
		if ([activeTagA hasAttribute:@"noenter"]) {
			mutDict[XT_OUTPUT_FORMATTER_ATTR_CMDLINK_NOENTER] = @"true";
		}
		if ([activeTagA hasAttribute:@"hover"]) {
			mutDict[XT_OUTPUT_FORMATTER_ATTR_CMDLINK_HOVER_COLORS] = [activeTagA attributeAsString:@"hover"];
		}
	}
}

// For the right aligned tab for the list item's bullet or number
- (CGFloat)calcListItemTab1Width
{
	BOOL hasProperContainer = YES;
	NSString *stringToMeasure;
	switch (self.formattingSpecForHtmlTag.formattingSpec.listBulletType) {
		case XT_LIST_BULLET_TYPE_UNSPECIFIED:
			stringToMeasure = [self liBullet:hasProperContainer];
			break;
		case XT_LIST_BULLET_TYPE_DISC:
			stringToMeasure = [self liBullet:hasProperContainer];
			break;
		case XT_LIST_BULLET_TYPE_SQUARE:
			stringToMeasure = [self liBullet:hasProperContainer];
			break;
		case XT_LIST_BULLET_TYPE_CIRCLE:
			stringToMeasure = [self liBullet:hasProperContainer];
			break;
		case XT_LIST_BULLET_TYPE_UL_PLAIN:
			stringToMeasure = [self liBullet:hasProperContainer];
			break;
		case XT_LIST_BULLET_TYPE_OL_DECIMAL: // 1, 2, 3, ...
			stringToMeasure = @"MMMM";
			break;
		case XT_LIST_BULLET_TYPE_OL_LOWER_ALPHA: // a, b, c, ...
			stringToMeasure = @"MMMM";
			//stringToMeasure = @"www";
			break;
		case XT_LIST_BULLET_TYPE_OL_UPPER_ALPHA: // A, B, C, ...
			stringToMeasure = @"MMMM";
			//stringToMeasure = @"WWW";
			break;
		case XT_LIST_BULLET_TYPE_OL_LOWER_ROMAN: // i, ii, iii, ...
			stringToMeasure = @"MMMM";
			//stringToMeasure = @"mmmmmm";
			break;
		case XT_LIST_BULLET_TYPE_OL_UPPER_ROMAN: // I. II, III, ...
			//stringToMeasure = @"MMMMMM";
			stringToMeasure = @"MMMM";
			break;
		case XT_LIST_BULLET_TYPE_OL_PLAIN:
			//stringToMeasure = LIST_BULLET_TYPE_DISC;
			stringToMeasure = @"MMMM";
			break;
		case XT_LIST_BULLET_TYPE_OL_PLAIN_FROM_LI:
			stringToMeasure = @"MMMM";
			break;
		default: {
			XT_DEF_SELNAME;
			XT_WARN_1(@"unknown listBulletType %ld", self.formattingSpecForHtmlTag.formattingSpec.listBulletType);
			stringToMeasure = [self liBullet:hasProperContainer];
			break;
		}
	}
	
	CGFloat res = [self widthOfStringForOutput:stringToMeasure];
	return res;
}

// For the left aligned tab for the list item's text
- (CGFloat)calcListItemTab2Width
{
	NSString *stringForBullets =  @"  ";
	NSString *stringForNumbers =  @"  ";
	NSString *stringToMeasure;
	XTFormattingSpecification *formattingSpec = self.formattingSpecForHtmlTag.formattingSpec;
	XTHtmlListBulletType listBulletType = formattingSpec.listBulletType;
	switch (listBulletType) {
		case XT_LIST_BULLET_TYPE_UNSPECIFIED:
		case XT_LIST_BULLET_TYPE_DISC:
		case XT_LIST_BULLET_TYPE_SQUARE:
		case XT_LIST_BULLET_TYPE_CIRCLE:
		case XT_LIST_BULLET_TYPE_UL_PLAIN:
			stringToMeasure = stringForBullets;
			break;
		case XT_LIST_BULLET_TYPE_OL_DECIMAL: // 1, 2, 3, ...
		case XT_LIST_BULLET_TYPE_OL_LOWER_ALPHA: // a, b, c, ...
		case XT_LIST_BULLET_TYPE_OL_UPPER_ALPHA: // A, B, C, ...
		case XT_LIST_BULLET_TYPE_OL_LOWER_ROMAN: // i, ii, iii, ...
		case XT_LIST_BULLET_TYPE_OL_UPPER_ROMAN: // I. II, III, ...
		case XT_LIST_BULLET_TYPE_OL_PLAIN_FROM_LI:
			stringToMeasure = stringForNumbers;
			break;
		case XT_LIST_BULLET_TYPE_OL_PLAIN:
			//TODO !!! adapt: is this right?
			stringToMeasure = stringForNumbers;
			break;
		default:
			stringToMeasure = stringForBullets;
			break;
	}
	CGFloat res = [self widthOfStringForOutput:stringToMeasure];
	return res;
}

- (CGFloat)widthOfStringForOutput:(NSString *)string
{
	NSFont *font = [self getCurrentFontForOutput];
	CGFloat res = [XTFontUtils defaultWidthOfString:string inFont:font];
	return res;
}

- (void)applyParagraphStyleToLastParagraph:(NSMutableParagraphStyle *)pgStyle
{
	[XTStringUtils applyParagraphStyle:pgStyle toLastParagraphOf:self.textView.textStorage];
}

- (NSMutableParagraphStyle *)getParagraphStyleAtStartOfOngoingParagraph
{
	NSMutableParagraphStyle *res = [XTStringUtils getParagraphStyleAtStartOfOngoingParagraphOf:self.textView.textStorage];
	return res;
}

- (NSDictionary *)getTextTemporaryAttributesDictionaryForOutput
{
	//XT_DEF_SELNAME;
	
	NSMutableDictionary *dict = nil;
	
	XTFormattingSpecification *formattingSpec = self.formattingSpecForHtmlTag.formattingSpec;
	if (formattingSpec.activeTagA == nil) {
		return dict;
	}
		
	dict = [NSMutableDictionary dictionary];

	BOOL plainLink = [formattingSpec.activeTagA hasAttribute:@"plain"];
	BOOL hiddenLink = [formattingSpec.activeTagA hasAttribute:@"hidden"];
	BOOL regularLink = ! (plainLink || hiddenLink);
	
	if (regularLink) {
		NSColor *color = nil;
		XTOutputTextColorResult *colorResult = [self.colorationHelper getLinkColorResult];
		if ([self allowGameToSetColors]) {
			color = colorResult.htmlColor.color;
		}
		if (color == nil) {
			color = self.prefs.linksTextColor.value;
		}
		dict[NSForegroundColorAttributeName] = color;
		dict[XT_OUTPUT_FORMATTER_ATTR_RECOLORABLE_TEXT] = colorResult;
		
		if (! formattingSpec.underline) {
			if (! self.prefs.linksUnderline.value.boolValue) {
				dict[NSUnderlineStyleAttributeName] = [NSNumber numberWithInt:NSUnderlineStyleNone];
			}
			dict[XT_OUTPUT_FORMATTER_ATTR_UNDERLINE_FROM_PREFS] = [NSNumber numberWithBool:YES];
		}
		
	} else {
		// if "plain" / "hidden" link, set temp attrs so the link looks like regular text:

		if (plainLink) {
			dict[XT_OUTPUT_FORMATTER_ATTR_PLAIN_LINK] = [NSNumber numberWithBool:YES];
		}
		if (hiddenLink) {
			dict[XT_OUTPUT_FORMATTER_ATTR_HIDDEN_LINK] = [NSNumber numberWithBool:YES];
		}
		
		XTOutputTextColorResult *colorResult = [self.colorationHelper getOutputTextColor:self.formattingSpecForHtmlTag];
		NSColor *color = nil;
		if ([self allowGameToSetColors]) {
			color = colorResult.htmlColor.color;
		} else {
			color = [self.colorationHelper getPrefsOutputTextColor];
		}
		dict[NSForegroundColorAttributeName] = color;
		dict[XT_OUTPUT_FORMATTER_ATTR_RECOLORABLE_TEXT] = colorResult;

		if (! formattingSpec.underline) {
			dict[NSUnderlineStyleAttributeName] = [NSNumber numberWithInt:NSUnderlineStyleNone];
		}
	}
	
	return dict;
}

- (NSMutableDictionary *)getTextAttributesDictionaryCommonForOutput
{
    NSMutableDictionary *dict = [NSMutableDictionary dictionary];
	
	dict[NSFontAttributeName] = [self getCurrentFontForOutput];
		//TODO (wasteful) not here - overridden for input and grid banners
	
	XTOutputTextColorResult *outputTextColorResult = [self.colorationHelper getOutputTextColor:self.formattingSpecForHtmlTag];
	if ([self allowGameToSetColors]) {
		dict[NSForegroundColorAttributeName] = outputTextColorResult.htmlColor.color;
	} else {
		dict[NSForegroundColorAttributeName] = [self.colorationHelper getPrefsOutputTextColor];
	}
	dict[XT_OUTPUT_FORMATTER_ATTR_RECOLORABLE_TEXT] = outputTextColorResult;
	
	XTOutputTextColorResult *outputBackgroundColorResult = [self.colorationHelper getOutputBackgroundColor:self.formattingSpecForHtmlTag];
	if (outputBackgroundColorResult != nil) {
		if ([self allowGameToSetColors]) {
			dict[NSBackgroundColorAttributeName] = outputBackgroundColorResult.htmlColor.color;
		}
		dict[XT_OUTPUT_FORMATTER_ATTR_RECOLORABLE_TEXT_BACKGROUND] = outputBackgroundColorResult;
	}
	
	NSMutableParagraphStyle *pgStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
	dict[NSParagraphStyleAttributeName] = pgStyle;
	
	return dict;
}

- (NSDictionary *)getTextAttributesDictionaryForInput
{
    NSMutableDictionary *dict = [NSMutableDictionary dictionary];
	
	BOOL allowGameToSetColors = [self allowGameToSetColors];
	
	dict[NSFontAttributeName] = [self getCurrentFontForInput];
	
	XTHtmlColor *htmlColor = nil;
	XTColorSource colorSource = XT_COLOR_SOURCE_UNSPECIFIED;
	if (self.colorationHelper.bodyInputColor.color != nil) {
		htmlColor = self.colorationHelper.bodyInputColor;
		colorSource = XT_COLOR_SOURCE_INPUT_FROM_BODY;
	} else {
		htmlColor = [XTHtmlColor forNSColor:self.prefs.inputTextColor.value];
		colorSource = XT_COLOR_SOURCE_INPUT_FROM_PREFS;
	}
	
	XTOutputTextColorResult *colorResult = [XTOutputTextColorResult forHtmlColor:htmlColor colorSource:colorSource];
	dict[XT_OUTPUT_FORMATTER_ATTR_RECOLORABLE_TEXT] = colorResult;

	NSColor *color;
	if (allowGameToSetColors) {
		color = htmlColor.color;
	} else {
		color = self.prefs.inputTextColor.value;
	}
	dict[NSForegroundColorAttributeName] = color;

	return dict;
}

- (NSDictionary *)getTextAttributesDictionaryForGridBanner
{
	//XT_DEF_SELNAME;
	
	NSMutableDictionary *dict = [self getTextAttributesDictionaryCommonForOutput];
	dict[NSFontAttributeName] = [self getCurrentFontForGridBanner];
	
	return dict;
}

- (NSFont *)getCurrentFontForGridBanner
{
	NSString *parameterizedFontName = [self.fontManager xtadsFixedWidthParameterizedFontName];
	XTParameterizedFont *parameterizedFont = [self.fontManager getParameterizedFontWithName:parameterizedFontName];
	CGFloat defaultPointSize = parameterizedFont.size;
	NSNumber *pointSize = [NSNumber numberWithFloat:defaultPointSize];
	NSNumber *htmlFontSize = [NSNumber numberWithInteger:3]; // default - don't scale up/down
	NSArray *fontNames = [NSArray arrayWithObject:parameterizedFontName];

	NSFont *res = [self.fontManager getFontWithName:fontNames
										  pointSize:pointSize //TODO !!! needed? if not, rm from method signature
										   htmlSize:htmlFontSize
											   bold:NO
											italics:NO];
	return res;
}

- (NSFont *)getCurrentFontForOutput
{
	XTFormattingSpecification *formattingSpec = self.formattingSpecForHtmlTag.formattingSpec;
	
	BOOL bold = (formattingSpec.bold || formattingSpec.t2Hilite || formattingSpec.strong);
	BOOL italics = (formattingSpec.italic || formattingSpec.t2Italics || formattingSpec.cite || formattingSpec.em ||
					formattingSpec.credit || formattingSpec.address || formattingSpec.dfn || formattingSpec.var);
	
	NSString *parameterizedFontName;
	if (formattingSpec.tt || formattingSpec.code || formattingSpec.verbatim) {
		parameterizedFontName = [self.fontManager xtadsFixedWidthParameterizedFontName];
	} else {
		parameterizedFontName = [self.fontManager xtadsDefaultParameterizedFontName];
	}
	
	BOOL allowGameToSetFonts = [self allowGameToSetFonts];
	NSArray *fontNames;
	if (allowGameToSetFonts && formattingSpec.htmlFontFaceList != nil && formattingSpec.htmlFontFaceList.count >= 1) {
		fontNames = formattingSpec.htmlFontFaceList;
	} else {
		fontNames = [NSArray arrayWithObject:parameterizedFontName];
	}

	NSFont *res = [self.fontManager getFontWithName:fontNames
										  pointSize:nil
										   htmlSize:[NSNumber numberWithUnsignedInteger:formattingSpec.htmlSize]
											   bold:bold
											italics:italics];
	
	return res;
}

- (NSFont *)getCurrentFontForInput
{
	XTFormattingSpecification *formattingSpec = self.formattingSpecForHtmlTag.formattingSpec;
	
	BOOL bold = (formattingSpec.bold || formattingSpec.t2Hilite);
	BOOL italics = formattingSpec.italic;

	XTPrefs *prefs = [XTPrefs prefs];
	NSString *parameterizedFontName;
	
	if (prefs.inputFontIsSameAsDefaultFont.value.boolValue) {
		parameterizedFontName = [self.fontManager xtadsDefaultParameterizedFontName];
	} else if (prefs.inputFontUsedEvenIfNotRequestedByGame.value.boolValue) {
		parameterizedFontName = [self.fontManager tadsInputParameterizedFontName];
	} else {
		//TODO how to handle?
		parameterizedFontName = [self.fontManager xtadsDefaultParameterizedFontName];
	}
	
	BOOL allowGameToSetFonts = [self allowGameToSetFonts];
	NSArray *fontNames;
	if (allowGameToSetFonts && formattingSpec.htmlFontFaceList != nil && formattingSpec.htmlFontFaceList.count >= 1) {
		fontNames = formattingSpec.htmlFontFaceList;
	} else {
		fontNames = [NSArray arrayWithObject:parameterizedFontName];
	}
	
	NSFont *res = [self.fontManager getFontWithName:fontNames
										  pointSize:nil
										   htmlSize:[NSNumber numberWithUnsignedInteger:formattingSpec.htmlSize]
											   bold:bold
											italics:italics];
	return res;
}

- (BOOL)allowGameToSetColors
{
	BOOL res;
	
	if ([self isForT2PlainTextBanner]) {
		res = NO;
	} else {
		NSNumber *valueObj = self.prefs.allowGamesToSetColors.value;
		res = (valueObj == nil || valueObj.boolValue);
	}
	
	return res;
}

- (BOOL)allowGameToSetFonts
{
	BOOL res;
	
	if ([self isForT2PlainTextBanner]) {
		res = NO;
	} else {
		NSNumber *valueObj = self.prefs.allowGamesToSetFonts.value;
		res = (valueObj == nil || valueObj.boolValue);
	}
	
	return res;
}

- (BOOL)isForT2PlainTextBanner
{
	BOOL res = self.isForBanner && (! self.isForT3) && (! self.htmlMode);
	return res;
}

- (XTTextTab *)createTabStopAtRhsOfViewForOptTextTableBlock:(XTTextTableBlock *)textTableBlock
{
	XTTextTab *tab = [self.tabStopHelper createTabStopAtRhsOfViewForOptTextTableBlock:textTableBlock];
	_needsRecalcAllTabStops = YES;
	return tab;
}

- (XTTextTab *)createTabStopForHorizRulerForTagTab:(XTHtmlTagTab *)tagTab optTextTableBlock:(XTTextTableBlock *)textTableBlock
{
	XTTextTab *tab = [self.tabStopHelper createTabStopForHorizRulerForTagTab:tagTab optTextTableBlock:textTableBlock];
	_needsRecalcAllTabStops = YES;
	return tab;
}

- (XTTextTab *)createTabStopAtHalfwayToRhsOfViewFromLoc:(CGFloat)fromLoc textTableBlock:(XTTextTableBlock *)textTableBlock
{
	//XT_DEF_SELNAME;
	
	XTTextTab *tab = [self.tabStopHelper createTabStopAtHalfwayToRhsOfViewFromLoc:fromLoc textTableBlock:textTableBlock];
	_needsRecalcAllTabStops = YES;
	//XT_WARN_1(@"--> tabLoc=%lf", tabLoc);
	return tab;
}

- (CGFloat)getPlainTextModeTabStopColumnWidthInPoints
{
	NSFont *font = [self getCurrentFontForOutput];
	CGFloat res = [self.tabStopHelper getPlainTextModeTabStopColumnWidthInPoints:font];
	return res;
}

- (CGFloat)getListItemPrefixColumnWidthInPoints
{
	CGFloat res = [self getPlainTextModeTabStopColumnWidthInPoints] * 0.7;
	return res;
}

//TODO mv down?
- (BOOL)isInTabOppressingTag
{
	BOOL res = self.formattingSpecForHtmlTag.formattingSpec.isInHeader;
	return res;
}

//-------------------------------------------------------------------------------------------

#pragma mark XTOutputFormatterProtocol

- (NSArray *)handleHtmlTagQ:(XTHtmlTagQ *)tag
{
	NSString *quote = @"\"";
	//TODO? diff char for open / close?
	
	NSMutableArray *res = [self makeArrayWithPendingWhitespace];
	[self clearPendingWhitespace];

	[self.linebreakHandler2 handleText:quote];
	
	[res addObjectsFromArray:[self makeRegularOutputElements:quote]];
	
	return res;
}

- (NSArray *)handleHtmlTagTab:(XTHtmlTagTab *)tag
{
	//XT_DEF_SELNAME;
	
	NSArray *res;
	
	if ([tag hasAttribute:@"id"]) {

		res = [self handleHtmlTagTabWhenNotOppressed:tag];

	} else if ([self isInTabOppressingTag]) {
	
		res = self.emptyArray;
		
	} else if ([tag hasAttribute:@"to"]) {
		
		res = [self handleHtmlTagTabWhenNotOppressed:tag];

	} else if ([tag hasAttribute:@"indent"]) {
		
		res = [self handleHtmlTagTabWhenNotOppressed:tag];

	} else if ([tag hasAttribute:@"multiple"]) {

		res = [self handleHtmlTagTabWhenNotOppressed:tag];

	} else if ([tag hasAttribute:@"align"]) {
		
		res = [self handleHtmlTagTabWhenNotOppressed:tag];
	}

	return res;
}

//TODO !!! adapt: this doc is outdated/wrong
/* <tab> is handled in several steps:
 	1. Render any pending whitespace, so that the layout machinery can have coordinates.
 	   Such whitespace is returned by this method.
	2. Create the tab stop string itself.
 	   This is done in createTabStringWithoutTabAttr:ongoingParagraphStyleHasRightAlignedTab:, triggered by
 	   the extra XTFormattedOutputElement we append to the return array from this method.
 	3. Calculate the tab position.
       Done in applyTagTab: called from BaseTH expandTabsAndAppendToTextStorage:
*/
- (NSArray *)handleHtmlTagTabWhenNotOppressed:(XTHtmlTagTab *)tag
{
	// process pending whitespace so that we have the correct insertion position...
	NSMutableArray *tempRes = [self makeArrayWithPendingWhitespace];
	[self clearPendingWhitespace];
	
	// ...when this element gets handled by createTabStringWithoutTabAttr:ongoingParagraphStyleHasRightAlignedTab:
	NSArray<NSAttributedString *> *attrStringArrayForTab = [self createTabStringWithoutTabAttr:tag];
	NSMutableAttributedString *mutAttrStringForTab = [XTStringUtils concatenateAttributedStringArray:attrStringArrayForTab];
	XTFormattedOutputElement *fmtElt = [XTFormattedOutputElement tabElement:tag attributedString:mutAttrStringForTab];
	[tempRes addObject:fmtElt];
	
	NSArray *res = tempRes;
	return res;
}

//TODO? too long - break into several funcs
- (NSArray<NSAttributedString *>*)createTabStringWithoutTabAttr:(XTHtmlTagTab *)tag;
{
	XT_DEF_SELNAME;
	
	NSMutableArray<NSAttributedString *> *res = [NSMutableArray arrayWithCapacity:3];
	
	if ([tag hasAttribute:@"id"]) {
		NSString *tabId = [tag attributeAsString:@"id"];
		[self.tabStopHelper preregisterModelTabWithId:tabId];
		[res addObjectsFromArray:[self createTabStringForId:tag]];
		
	} else if ([tag hasAttribute:@"to"]) {
		
		NSString *to = [tag attributeAsString:@"to"];
		XTTabStopModelEntry *modelTabStop = [self.tabStopHelper findModelTabWithId:to];
		if (modelTabStop == nil) {
			// toId is unknown - at least add a space
			[res addObjectsFromArray:[self createAttributedStringForChar:self.htmlSpecialSpaceForOneSpace.ch]];
			self.shouldWriteWhitespace = YES;
			//XT_WARN_1(@"shouldWriteWhitespace=%d", self.shouldWriteWhitespace);
		} else {
			[res addObjectsFromArray:[self createTabStringFor:tag]];
		}
		
		[self.linebreakHandler2 handleTagTab];

	} else if ([tag hasAttribute:@"multiple"]) {
		
		XTFormattingSpecification *formattingSpec = self.formattingSpecForHtmlTag.formattingSpec;
		if (! formattingSpec.hasRightAlignedTab) {
			[res addObjectsFromArray:[self createTabStringFor:tag]];
		} else {
			NSNumber *multiple = [tag attributeAsNumber:@"multiple"];
			NSArray<NSAttributedString *> *array = [self createTabStringForIndent:multiple];
			if (array != nil) {
				[res addObjectsFromArray:array];
			}
		}
		
		[self.linebreakHandler2 handleTagTab];

	} else if ([tag hasAttribute:@"indent"]) {
		
		NSNumber *indent = [tag attributeAsNumber:@"indent"];
		if (indent != nil) {
			if (indent.integerValue >= 1) {
				[res addObjectsFromArray:[self createTabStringFor:tag]];
				
				[self.linebreakHandler2 handleTagTab];
				self.shouldWriteWhitespace = NO; //TODO exp for tab test game indent_2
				//XT_WARN_1(@"shouldWriteWhitespace=%d", self.shouldWriteWhitespace);
			}
		}

	} else if ([tag hasAttribute:@"align"]) {
		
		NSString *align = [tag attributeAsString:@"align"];
		XTTabStopAlignment alignEnum = [self.tabStopHelper alignmentFromStringLeftCenterRightOnly:align];
		
		if (alignEnum == XTTABSTOP_ALIGN_RIGHT) {
			XTFormattingSpecification *formattingSpec = self.formattingSpecForHtmlTag.formattingSpec;
			if (! formattingSpec.hasRightAlignedTab) {
				[res addObjectsFromArray:[self createTabStringFor:tag]];
				[self.linebreakHandler2 handleTagTab];
			}
			formattingSpec.hasRightAlignedTab = YES;
		} else if (alignEnum == XTTABSTOP_ALIGN_CENTER) {
			[res addObjectsFromArray:[self createTabStringFor:tag]];
			[self.linebreakHandler2 handleTagTab];
		} else if (alignEnum == XTTABSTOP_ALIGN_LEFT) {
			[res addObjectsFromArray:[self createTabStringFor:tag]];
			[self.linebreakHandler2 handleTagTab];
		} else {
			XT_ERROR_1(@"Unexpected tab alignment: %d", alignEnum);
		}
		
	} else {
		XT_ERROR_0(@"not handling attribute id, to, multiple or align");
	}
	
	return res;
}

//TODO!!! too long - break into several funcs
- (NSMutableAttributedString *)applyTagTab:(XTHtmlTagTab *)tagTab
				forMutableAttributedString:(NSMutableAttributedString *)mutAttrString
{
	XT_DEF_SELNAME;
	
	NSMutableAttributedString *res = mutAttrString;
	
	[self.textView ensureLayoutForTextContainer];
	
	NSString *align = [tagTab attributeAsString:@"align"];
	NSString *decimal = [tagTab attributeAsString:@"dp"];
	
	if ([tagTab hasAttribute:@"id"]) {

		NSString *id = [tagTab attributeAsString:@"id"];
		
		CGFloat position = [self findXCoordOfInsertionPointForTabs2];
		
		[self.tabStopHelper addModelTabStopWithId:id position:position alignment:align decimalChar:decimal];
		
		//XT_WARN_2(@"defined tab id=%@ position=%lf", id, position);

	} else if ([tagTab hasAttribute:@"to"]) {
		
		NSString *to = [tagTab attributeAsString:@"to"];
		XTTabStopModelEntry *modelTabStop = [self.tabStopHelper findModelTabWithId:to];
		if (modelTabStop == nil) {
			XT_ERROR_1(@"modelTabStop == nil for to=%@", to);
		
		} else {
			CGFloat position = [self findXCoordOfInsertionPointForTabs2];
			
			NSMutableParagraphStyle *pgStyle = [self getParagraphStyleAtStartOfOngoingParagraph];
			BOOL hasOngoingParagraph = (pgStyle != nil);
			if (! hasOngoingParagraph) {
				pgStyle = [XTStringUtils mutableParagraphStyleFor:mutAttrString];
			}

			XTTextTableBlock *textTableBlock = [XTStringUtils tableCellForAttrString:mutAttrString atIndex:0];
			NSMutableArray *tabStops = [NSMutableArray arrayWithArray:pgStyle.tabStops];
			if ([tagTab hasAttribute:@"align"] || [tagTab hasAttribute:@"dp"]) {
				XTTabStopModelEntry *overriddenModelTabStop = [self.tabStopHelper createOverridenModelTabStop:modelTabStop
																										align:align
																								  decimalChar:decimal];
				modelTabStop = overriddenModelTabStop;
			}
			XTTabStopModelEntry *usedModelTabStop = [modelTabStop copy];
			if (usedModelTabStop.decimalPointAligned) {
				NSFont *font = [self getCurrentFontForOutput];
				NSString *decPtChar = usedModelTabStop.effectiveDecimalChar;
				usedModelTabStop.positionAdjustmentForDecimalPoint = [XTFontUtils defaultWidthOfString:decPtChar inFont:font];
					// needed for tab recalc
			}
			XTTextTab *newTabStop = [self.tabStopHelper createTabFromModelEntry:usedModelTabStop
																	minLocation:position
																	   resizing:NO
															  optTextTableBlock:textTableBlock];
			//XT_WARN_2(@"created tab to=%@ position=%lf", to, newTabStop.location);
			[tabStops addObject:newTabStop];
			pgStyle.tabStops = tabStops;
			
			if (! hasOngoingParagraph) {
				[XTStringUtils applyParagraphStyle:pgStyle toAttrString:mutAttrString];
				//XT_WARN_0(@"! hasOngoingParagraph");
			} else {
				[self applyParagraphStyleToLastParagraph:pgStyle];
				//XT_WARN_0(@"hasOngoingParagraph");
			}

			//XT_WARN_1(@"applied tab to=%@", to);
		}

	} else if ([tagTab hasAttribute:@"multiple"]) {

		NSNumber *multiple = [tagTab attributeAsNumber:@"multiple"];

		NSMutableParagraphStyle *pgStyle = [self getParagraphStyleAtStartOfOngoingParagraph];
		BOOL hasOngoingParagraph = (pgStyle != nil);
		if (! hasOngoingParagraph) {
			pgStyle = [XTStringUtils mutableParagraphStyleFor:mutAttrString];
		}

		if (! [self.tabStopHelper paragraphStyleHasTabStopAtRhsOfWindow:pgStyle]) {
			
			CGFloat position = [self findXCoordOfInsertionPointForTabs2];
			XTTextTab *newTabStop = [self.tabStopHelper createTabStopFor:mutAttrString
														atNextMultipleOf:multiple
															fromLocation:position];
			NSMutableArray *tabStops = [NSMutableArray arrayWithArray:pgStyle.tabStops];
			[tabStops addObject:newTabStop];
			pgStyle.tabStops = tabStops;
			
			if (! hasOngoingParagraph) {
				[XTStringUtils applyParagraphStyle:pgStyle toAttrString:mutAttrString];
			} else {
				[self applyParagraphStyleToLastParagraph:pgStyle];
			}
		}

	} else if ([tagTab hasAttribute:@"indent"]) {
		
		NSNumber *indentObj = [tagTab attributeAsNumber:@"indent"];
		NSUInteger indent = [indentObj unsignedIntegerValue];
		BOOL foldLeadingSpace = NO;
		NSString *replacement = @"";

		if (indent >= 1) {
			foldLeadingSpace = [self.tabStopHelper textStorageEndsWithARegularSpace];
				//TODO !!! can mv into stringForTabIndent:
			replacement = [self.tabStopHelper stringForTabIndent:indent foldLeadingSpace:foldLeadingSpace];
		}
		//TODO !!! adapt: check if mutAttrString != "\t"
		res = [[NSMutableAttributedString alloc] initWithAttributedString:mutAttrString];
		NSRange range = NSMakeRange(0, mutAttrString.length);
		[res replaceCharactersInRange:range withString:replacement];
		
	} else if ([tagTab hasAttribute:@"align"]) {
		
		XTTabStopAlignment alignEnum = [self.tabStopHelper alignmentFromString:align];
		
		if (alignEnum == XTTABSTOP_ALIGN_RIGHT) {

			NSMutableParagraphStyle *pgStyle = [self getParagraphStyleAtStartOfOngoingParagraph];
			BOOL hasOngoingParagraph = (pgStyle != nil);
			if (! hasOngoingParagraph) {
				pgStyle = [XTStringUtils mutableParagraphStyleFor:mutAttrString];
			}

			if (! [self.tabStopHelper paragraphStyleHasTabStopAtRhsOfWindow:pgStyle]) {
				
				NSMutableArray *tabStops = [NSMutableArray arrayWithArray:pgStyle.tabStops];
				XTTextTableBlock *textTableBlock = [XTStringUtils tableCellForAttrString:mutAttrString atIndex:0];

				XTTextTab *newTabStop = [self createTabStopAtRhsOfViewForOptTextTableBlock:textTableBlock];
				[tabStops addObject:newTabStop];
				pgStyle.tabStops = tabStops;
				
				if (! hasOngoingParagraph) {
					[XTStringUtils applyParagraphStyle:pgStyle toAttrString:mutAttrString];
				} else {
					[self applyParagraphStyleToLastParagraph:pgStyle];
				}
			}
			
		} else if (alignEnum == XTTABSTOP_ALIGN_CENTER) {
			
			NSMutableParagraphStyle *pgStyle = [self getParagraphStyleAtStartOfOngoingParagraph];
			BOOL hasOngoingParagraph = (pgStyle != nil);
			if (! hasOngoingParagraph) {
				pgStyle = [XTStringUtils mutableParagraphStyleFor:mutAttrString];
			}
			
			NSMutableArray *tabStops = [NSMutableArray arrayWithArray:pgStyle.tabStops];
			CGFloat fromLoc = [self findXCoordOfInsertionPointForTabs2];
			XTTextTableBlock *textTableBlock = [XTStringUtils tableCellForAttrString:mutAttrString atIndex:0];
			XTTextTab *newTabStop = [self createTabStopAtHalfwayToRhsOfViewFromLoc:fromLoc textTableBlock:textTableBlock];
			[tabStops addObject:newTabStop];
			pgStyle.tabStops = tabStops;
			
			if (! hasOngoingParagraph) {
				[XTStringUtils applyParagraphStyle:pgStyle toAttrString:mutAttrString];
			} else {
				[self applyParagraphStyleToLastParagraph:pgStyle];
			}
			
		} else if (alignEnum == XTTABSTOP_ALIGN_LEFT) {

			NSString *replacement = [self.tabStopHelper stringForTabStopLeftAligned];
			res = [[NSMutableAttributedString alloc] initWithAttributedString:mutAttrString];
			NSRange range = NSMakeRange(0, mutAttrString.length);
			[res replaceCharactersInRange:range withString:replacement];
			
		} else {
			XT_ERROR_1(@"Unexpected tab alignment: %d", alignEnum);
		}
	
	} else if ([tagTab isForHorizontalRuler]) {
		
		NSMutableParagraphStyle *pgStyle = [self getParagraphStyleAtStartOfOngoingParagraph];
		BOOL hasOngoingParagraph = (pgStyle != nil);
		if (! hasOngoingParagraph) {
			pgStyle = [XTStringUtils mutableParagraphStyleFor:mutAttrString];
		}

		NSMutableArray *tabStops = [NSMutableArray arrayWithArray:pgStyle.tabStops];
		XTTextTableBlock *textTableBlock = [XTStringUtils tableCellForAttrString:mutAttrString atIndex:0];

		XTTextTab *newTabStop = [self createTabStopForHorizRulerForTagTab:tagTab optTextTableBlock:textTableBlock];
		[tabStops addObject:newTabStop];
		pgStyle.tabStops = tabStops;
		
		if (! hasOngoingParagraph) {
			[XTStringUtils applyParagraphStyle:pgStyle toAttrString:mutAttrString];
		} else {
			[self applyParagraphStyleToLastParagraph:pgStyle];
		}

	} else {
		XT_ERROR_0(@"not handling attribute id, to, multiple, align or for-horiz-ruler");
	}
	
	return res;
}

- (void)prepareForRecalcAllOfTabStops
{
	[self.tabStopHelper prepareForRecalcAllOfTabStops];
}

- (void)prepareForRecalcAllOfTabStopsInRange:(NSRange)range
{
	[self.tabStopHelper prepareForRecalcAllOfTabStopsInRange:range];
}

- (void)recalcAllTabStops
{
	//XT_DEF_SELNAME;
	//XT_WARN_0(@"entry")

	[self.tabStopHelper recalcAllTabStops];
	_needsRecalcAllTabStops = NO;
}

- (XTTextTableBlock *)tableCellForTextInRange:(NSRange)range
{
	XTTextTableBlock *res = [XTStringUtils tableCellForAttrString:self.textView.textStorage range:range];
	return res;
}

- (CGFloat)findXCoordOfInsertionPointForTabs2
{
	NSTextStorage *textStorage = self.textView.textStorage;
	NSRange rangeOfLastParagraph = [XTStringUtils findRangeOfOngoingParagraph:textStorage.string];
	CGFloat res = 0.0;
	
	if (rangeOfLastParagraph.length >= 1) {
		NSAttributedString *lastParagraph = [textStorage attributedSubstringFromRange:rangeOfLastParagraph];
		NSMutableAttributedString *mutLastParagraph = [[NSMutableAttributedString alloc] initWithAttributedString:lastParagraph];

		NSMutableParagraphStyle *mutParagraphStyle = [XTStringUtils mutableParagraphStyleFor:mutLastParagraph];
		mutParagraphStyle.textBlocks = self.emptyArray;

		[XTStringUtils applyParagraphStyle:mutParagraphStyle toAttrString:mutLastParagraph];
		
		NSAttributedString *measuredLastParagraph = [XTStringUtils withoutTrailingNewline:mutLastParagraph];
		NSSize size = [XTFontUtils sizeOfAttrString:measuredLastParagraph];
		res = size.width; //TODO !!! +1 ? round off?
	}
	
	return res;
}

//TODO  mv
- (CGFloat)findXCoordAfterLoc:(NSUInteger)loc
{
	//XT_DEF_SELNAME;
	
	NSRange range = NSMakeRange(loc, 1);
	//NSString *tsString = self.textView.textStorage.string;
	//NSString *string = [tsString substringWithRange:range];
	
	CGFloat resTemp = [self.textView findEndXCoordOfTextInRange:range];
	CGFloat res = [self adjustTextXCoordforInsetAndPadding:resTemp];
	
	//XT_WARN_5(@"range=(%lu,%lu) \"%@\"  --> %lf (%lf)", range.location, range.length, string, res, resTemp);
	return res;
}

//TODO  mv
- (CGFloat)findXCoordBeforeLoc:(NSUInteger)loc
{
	//XT_DEF_SELNAME;
	
	NSRange range = NSMakeRange(loc, 1);
	//NSString *tsString = self.textView.textStorage.string;
	//NSString *string = [tsString substringWithRange:range];
	
	CGFloat resTemp = [self.textView findStartXCoordOfTextInRange:range];
	CGFloat res = [self adjustTextXCoordforInsetAndPadding:resTemp];
	
	//XT_WARN_5(@"range=(%lu,%lu) \"%@\"  --> %lf (%lf)", range.location, range.length, string, res, resTemp);
	return res;
}

//TODO  mv
- (CGFloat)adjustTextXCoordforInsetAndPadding:(CGFloat)textXCoord
{
	CGFloat res = textXCoord;
	res -= self.textView.leftRightInset;
	res -= self.textView.textContainer.lineFragmentPadding;
	if (res < 0.0) {
		res = 0.0;
	}
	return res;
}

//TODO make block level?!
- (NSArray *)handleHtmlTagBr:(XTHtmlTagBr *)tag
{
	NSMutableArray *res = [NSMutableArray arrayWithCapacity:2];
	
	if (! [self shouldPrintOutputText]) {
		return res;
	}

	NSInteger height = -1;
	if ([tag hasAttribute:@"height"]) {
	 	height = [tag attributeAsUInt:@"height"];
	}

	NSString *s = [self.linebreakHandler2 handleTagBr:height];
	[self handleWhitespaceBeforeOrAfterBlockLevelTag:res];
	
	if (s != nil && s.length >= 1) {
		NSArray<NSMutableAttributedString *> *mutAttrStringArray = [self makeAttributedStringForOutput:s];
		for (NSMutableAttributedString *mutAttrString in mutAttrStringArray) {
			[res addObject:[XTFormattedOutputElement regularOutputElement:mutAttrString]];
		}
	}
	
	// We're starting a new paragraph, so:
	self.formattingSpecForHtmlTag.formattingSpec.hasRightAlignedTab = NO;

	return res;
}

- (NSArray *)handleHtmlTagPOpen:(XTHtmlTagP *)tag
{
	[self.linebreakHandler2 handleTagPOpen];

	XTFormattingSpecification *formattingSpec = self.formattingSpecForHtmlTag.formattingSpec;
	
	self.pTagTextAlignMode = formattingSpec.textAlignMode;
	formattingSpec.textAlignMode = [tag getTextAlignModeFrom:formattingSpec.textAlignMode];

	// We're starting a new paragraph, so:
	self.formattingSpecForHtmlTag.formattingSpec.hasRightAlignedTab = NO;

	return self.emptyArray;
}

- (NSArray *)handleHtmlTagPClose:(XTHtmlTagP *)tag
{
	XTFormattingSpecification *formattingSpec = self.formattingSpecForHtmlTag.formattingSpec;
	
	formattingSpec.textAlignMode = self.pTagTextAlignMode;
	self.pTagTextAlignMode = XT_TEXT_ALIGN_LEFT;
	
	// We're starting a new paragraph, so:
	self.formattingSpecForHtmlTag.formattingSpec.hasRightAlignedTab = NO;

	return _emptyArray;
}

- (NSArray *)handleHtmlTagTitleOpen:(XTHtmlTagTitle *)tag
{
	self.receivingGameTitle = YES;
	NSArray *res = [self makeArrayWithGameTitleElement:@"{{clear}}"];
		//TODO hack
	return res;
}

- (NSArray *)handleHtmlTagTitleClose
{
	self.receivingGameTitle = NO;
	return self.emptyArray;
}

- (NSArray *)handleHtmlTagHr:(XTHtmlTagHr *)tagHr
{
	NSString *horRulerStringPrefix = ZERO_WIDTH_SPACE ZERO_WIDTH_JOINER;
	NSString *horRulerStringTab = @"\t";
	NSString *horRulerStringSuffix = ZERO_WIDTH_JOINER;

	[self.linebreakHandler2 handleText:horRulerStringTab]; // so we're "in text"
	
	NSArray<XTFormattedOutputElement *>*fmtEltsArrayPrefix = [self makeRegularOutputElements:horRulerStringPrefix];
	XTFormattedOutputElement *fmtEltPrefix = fmtEltsArrayPrefix[0];
	NSMutableAttributedString *fmtEltPrefixMutAttrString = fmtEltPrefix.attributedString;

	XTTextAlignMode alignment = [tagHr getTextAlignModeWithDefault:XT_TEXT_ALIGN_CENTER];
	
	XTPair *widthAsPercentageAndPoints = [tagHr getWidthAsPercentageAndPoints];
	NSNumber *widthAsPercentage = (NSNumber *)widthAsPercentageAndPoints.firstObject;
	NSNumber *widthAsPoints = (NSNumber *)widthAsPercentageAndPoints.secondObject;
	
	NSAttributedString *tabAttrStringLhs = nil;
	XTHtmlTagTab *tagTabLhs = [XTHtmlTagTab forHorizontalRulerLhsWithAlignment:alignment
															 widthAsPercentage:widthAsPercentage
																 widthAsPoints:widthAsPoints];
	if (tagTabLhs != nil) {
		NSArray<NSAttributedString *> *tabTempArrayLhs = [self createTabStringFor:tagTabLhs];
		tabAttrStringLhs = tabTempArrayLhs[0];
	}

	XTHtmlTagTab *tagTabRhs = [XTHtmlTagTab forHorizontalRulerRhsWithAlignment:alignment
															 widthAsPercentage:widthAsPercentage
																 widthAsPoints:widthAsPoints];
	NSArray<NSAttributedString *> *tabTempArray = [self createTabStringFor:tagTabRhs];
	NSAttributedString *tabAttrStringRhs = tabTempArray[0];

	NSArray<XTFormattedOutputElement *>*fmtEltsArraySuffix = [self makeRegularOutputElements:horRulerStringSuffix];
	XTFormattedOutputElement *fmtEltSuffix = fmtEltsArraySuffix[0];
	NSMutableAttributedString *fmtEltSuffixMutAttrString = fmtEltSuffix.attributedString;

	NSMutableAttributedString *horRulerAttrString = [NSMutableAttributedString new];
	NSUInteger strikethroughStartIndex = 0;
	if (tabAttrStringLhs != nil) {
		[horRulerAttrString appendAttributedString:tabAttrStringLhs];
		strikethroughStartIndex = 2;
	}
	[horRulerAttrString appendAttributedString:fmtEltPrefixMutAttrString];
	[horRulerAttrString appendAttributedString:tabAttrStringRhs];
	[horRulerAttrString appendAttributedString:fmtEltSuffixMutAttrString];
	
	NSRange strikethroughRange = NSMakeRange(strikethroughStartIndex, horRulerAttrString.length - strikethroughStartIndex);
	[horRulerAttrString addAttribute:NSStrikethroughStyleAttributeName value:[NSNumber numberWithInt:NSUnderlineStyleSingle] range:strikethroughRange];
	[horRulerAttrString addAttribute:NSStrikethroughColorAttributeName value:[XTDefaultColours horizRulerColor] range:strikethroughRange];

	NSMutableArray *tabTagStops = [NSMutableArray arrayWithCapacity:3];
	if (tabAttrStringLhs != nil) {
		NSArray *tabTagStopsLhs = [XTMutableAttributedStringHelper tagTabStopsForAttrAtring:tabAttrStringLhs];
		[tabTagStops addObjectsFromArray:tabTagStopsLhs];
	}
	NSArray *tabTagStopsRhs = [XTMutableAttributedStringHelper tagTabStopsForAttrAtring:tabAttrStringRhs];
	[tabTagStops addObjectsFromArray:tabTagStopsRhs];
	[XTMutableAttributedStringHelper setTagTabStops:tabTagStops forMutAttrAtring:horRulerAttrString];

	XTFormattedOutputElement *resFmtElt = [XTFormattedOutputElement regularOutputElement:horRulerAttrString];
	NSArray *res = [NSArray arrayWithObject:resFmtElt];
	
	return res;
}

- (NSArray *)handleHtmlTagBannerOpen:(XTHtmlTagBanner *)tag
{
	XT_DEF_SELNAME;
	//XT_WARN_0(@"");

	if (! [tag isForRemovingBanners]) {
		self.tagBannerDepth += 1;
	}

	if (self.isForT3) {
		XT_WARN_0(@"<banner> tag and contents ignored for T3 game");
		return self.emptyArray;
	}

	if (self.isForTagBanner) {
		XT_WARN_0(@"nested <banner> tag and contents ignored");
		return self.emptyArray;
	}

	if (self.tagBannerDepth >= 2) {
		// This should never happen
		XT_ERROR_0(@"nested <banner> tag and contents ignored");
		return self.emptyArray;
	}

	XTFormattedOutputElement *e = [XTFormattedOutputElement bannerStartElement:tag];
	NSArray *res = [NSArray arrayWithObject:e];
	return res;
}

- (NSArray *)handleHtmlTagBannerClose
{
	XT_DEF_SELNAME;
	//XT_WARN_0(@"");

	NSInteger oldTagBannerDepth = self.tagBannerDepth;
	self.tagBannerDepth -= 1;
	if (self.tagBannerDepth < 0) {
		self.tagBannerDepth = 0;
	}

	if (self.isForT3) {
		XT_WARN_0(@"<banner> tag and contents ignored for T3 game");
		return self.emptyArray;
	}

	if (! self.isForTagBanner) {
		XT_WARN_0("extraneous </banner> ignored")
		return self.emptyArray;
	}

	if (oldTagBannerDepth >= 2) {
		XT_WARN_0("nested </banner> ignored")
		return self.emptyArray;
	}
	
	//TODO !!! exp for xtads_banners_t2/banner_t2_103 bug:
	//self.shouldWriteWhitespace = NO;

	XTFormattedOutputElement *e = [XTFormattedOutputElement bannerEndElement];
	NSArray *res = [NSArray arrayWithObject:e];
	return res;
}

- (NSArray *)handleHtmlTagOlOpen:(XTHtmlTagOl *)tag
{
	[tag setItemIndexForContinue:self.listItemIndexForContinue];
	
	return self.emptyArray;
}

- (NSArray *)handleHtmlTagLiOpen:(XTHtmlTagLi *)tag
{
	NSArray *res = self.emptyArray;
	XTHtmlTagListContainer *listContainer = [XTHtmlUtils listContainerFor:tag];
	
	BOOL inOl = [listContainer isKindOfClass:[XTHtmlTagOl class]];
	BOOL inUl = [listContainer isKindOfClass:[XTHtmlTagUl class]];
	XTHtmlListBulletType listBulletType = self.formattingSpecForHtmlTag.formattingSpec.listBulletType;
	BOOL olPlain = (listBulletType == XT_LIST_BULLET_TYPE_OL_PLAIN) || (listBulletType == XT_LIST_BULLET_TYPE_OL_PLAIN_FROM_LI);

	if (inOl && (! olPlain)) {
		
		XTHtmlTagOl *tagOl = (XTHtmlTagOl *)listContainer;
		NSUInteger itemIndex = [tagOl getItemIndex];

		XTFormattingSpecification *formattingSpec = self.formattingSpecForHtmlTag.formattingSpec;
		NSString *numberPrefix = [self liNumberPrefixForItemNumber:(NSInteger)itemIndex type:formattingSpec.listBulletType];
		
		NSString *format;
		if ([XTStringUtils stringContainsOnlyWhitespace:numberPrefix]) {
			format = @"\t%@\t";
		} else {
			format = @"\t%@.\t";
		}
		NSString *s = [NSString stringWithFormat:format, numberPrefix];
		[self.linebreakHandler2 handleText:s];
		res = [self makeArrayWithRegularOutputElement:s];

		self.listItemIndexForContinue = [tagOl incItemIndex];

	} else if (inUl || (inOl && olPlain)) {
		
		NSString *bulletStr = [self liBullet:YES];
		NSString *tabAndBulletStr = [NSString stringWithFormat:@"\t%@\t", bulletStr];
		[self.linebreakHandler2 handleText:tabAndBulletStr];
		res = [self makeArrayWithRegularOutputElement:tabAndBulletStr];
		
		if (inOl && olPlain) {
			XTHtmlTagOl *tagOl = (XTHtmlTagOl *)listContainer;
			self.listItemIndexForContinue = [tagOl incItemIndex];
		}

	} else {

		NSString *s = [self liBullet:NO];
		[self.linebreakHandler2 handleText:s];
		res = [self makeArrayWithRegularOutputElement:s];
		
		// No tab stop
	}
	
	return res;
}

- (NSArray *)handleHtmlTagLiClose:(XTHtmlTagLi *)tag
{
	return self.emptyArray;
}

- (NSString *)liNumberPrefixForItemNumber:(NSUInteger)itemNumber type:(XTHtmlListBulletType)listBulletType
{
	// for <li plain> in <ol> :
	if (listBulletType == XT_LIST_BULLET_TYPE_OL_PLAIN_FROM_LI) {
		return @"    ";
	}
	
	NSTextListMarkerFormat listMarkerFormat;
	switch (listBulletType) {
		case XT_LIST_BULLET_TYPE_OL_DECIMAL:
			listMarkerFormat = NSTextListMarkerDecimal;
			break;
		case XT_LIST_BULLET_TYPE_OL_LOWER_ALPHA:
			listMarkerFormat = NSTextListMarkerLowercaseAlpha;
			break;
		case XT_LIST_BULLET_TYPE_OL_UPPER_ALPHA:
			listMarkerFormat = NSTextListMarkerUppercaseAlpha;
			break;
		case XT_LIST_BULLET_TYPE_OL_LOWER_ROMAN:
			listMarkerFormat = NSTextListMarkerLowercaseRoman;
			break;
		case XT_LIST_BULLET_TYPE_OL_UPPER_ROMAN:
			listMarkerFormat = NSTextListMarkerUppercaseRoman;
			break;
		default:
			listMarkerFormat = NSTextListMarkerDecimal;
			break;
	}
	
	NSTextList *textList = [[NSTextList alloc]initWithMarkerFormat:listMarkerFormat options:0];
	
	NSString *res = [textList markerForItemNumber:(NSInteger)itemNumber];
	return res;
}

- (NSString *)liBullet:(BOOL)hasProperContainer
{
	NSString *res;
	XTFormattingSpecification *formattingSpec = self.formattingSpecForHtmlTag.formattingSpec;
	if (hasProperContainer) {
		if (formattingSpec.listBulletType == XT_LIST_BULLET_TYPE_UL_PLAIN) {
			res = @"    ";
		} else if (formattingSpec.listBulletType == XT_LIST_BULLET_TYPE_OL_PLAIN) {
			res = LIST_BULLET_TYPE_DISC;
		} else if (formattingSpec.listBulletType ==  XT_LIST_BULLET_TYPE_OL_PLAIN_FROM_LI) {
			res = @"    ";
		} else if (formattingSpec.listBulletType == XT_LIST_BULLET_TYPE_DISC) {
			res = LIST_BULLET_TYPE_DISC;
		} else if (formattingSpec.listBulletType == XT_LIST_BULLET_TYPE_SQUARE) {
			res = LIST_BULLET_TYPE_SQUARE;
		} else if (formattingSpec.listBulletType == XT_LIST_BULLET_TYPE_CIRCLE) {
			res = LIST_BULLET_TYPE_CIRCLE;
		} else {
			switch (formattingSpec.listLevel) {
				case 1:
					res = LIST_BULLET_TYPE_DISC;
					break;
				case 2:
					res = LIST_BULLET_TYPE_CIRCLE;
					break;
				default:
					res = LIST_BULLET_TYPE_SQUARE;
					break;
			}
		}
	} else {
		if (formattingSpec.listBulletType == XT_LIST_BULLET_TYPE_UL_PLAIN) {
			res = @"       ";
		} else {
			// No support for nesting here
			res = LIST_BULLET_TYPE_DISC @"   ";
		}
	}
	return res;
}

- (NSArray *)handleHtmlTagNoop:(XTHtmlTagNoop *)tag
{
	return self.emptyArray;
}

- (NSArray *)handleHtmlTagQuestionMarkT2:(XTHtmlTagQuestionMarkT2 *)tag
{
	/* TODO handle:
	 *   Write out the special <?T2> HTML sequence, in case we're on an HTML
	 *   system.  This tells the HTML parser to use the parsing rules for
	 *   TADS 2 callers.
	 */
	 //outformat("\\H+<?T2>\\H-");
	
	 return self.emptyArray;
}

- (NSArray *)handleHtmlTagQuestionMarkT3:(XTHtmlTagQuestionMarkT3 *)tag
{
	/* TODO handle, but for T3:
	 *   Write out the special <?T2> HTML sequence, in case we're on an HTML
	 *   system.  This tells the HTML parser to use the parsing rules for
	 *   TADS 2 callers.
	 */
	//outformat("\\H+<?T2>\\H-");
	
	return self.emptyArray;
}

- (NSArray *)handleHtmlTagTableOpen:(XTHtmlTagTable *)tag
{
	//XT_DEF_SELNAME;
	//XT_WARN_0(@"");
	
	self.tableDepth += 1;
	if (self.tableDepth >= 2) {
		return self.emptyArray;
	}
	self.tableDepth = 1;

	XTFormattedOutputElement *fmtElt = [XTFormattedOutputElement tableStartElement];
	NSArray *res = [NSArray arrayWithObject:fmtElt];
	return res;
}

- (NSArray *)handleHtmlTagTableClose:(XTHtmlTagTable *)tag
{
	//XT_DEF_SELNAME;
	//XT_WARN_0(@"");
	
	self.tableDepth -= 1;
	if (self.tableDepth >= 1) {
		return self.emptyArray;
	}
	self.tableDepth = 0;

	[self clearPendingWhitespace];
	self.shouldWriteWhitespace = NO;
	//XT_WARN_1(@"shouldWriteWhitespace=%d", self.shouldWriteWhitespace);

	[self.linebreakHandler2 handleTableClose];

	if (tag.tableBorderSize >= 1) {
		self.pendingParagraphSpacingBefore = (CGFloat)tag.tableBorderSize;
	}

	XTFormattedOutputElement *fmtElt = [XTFormattedOutputElement tableEndElement];
	NSArray *res = [NSArray arrayWithObject:fmtElt];
	return res;
}

- (NSArray *)handleHtmlTagTrClose:(XTHtmlTagTr *)tag
{
	return self.emptyArray;
}

- (NSArray *)handleHtmlTagTdOpen
{
	//XT_DEF_SELNAME;
	
	[self clearPendingWhitespace];
	self.shouldWriteWhitespace = NO;
	//XT_WARN_1(@"shouldWriteWhitespace=%d", self.shouldWriteWhitespace);

	[self.linebreakHandler2 handleTdOpen];
	return self.emptyArray;
}

- (NSArray *)handleHtmlTagTdClose:(XTHtmlTagTd *)tag
{
	[self clearPendingWhitespace];

	// Each table cell must be a paragraph:
	NSString *s = [self.linebreakHandler2 handleTdClose];
	NSArray *res;
	if (s == nil) {
		res = self.emptyArray;
	} else {
		res = [self makeArrayWithRegularOutputElement:s];
	}
	return res;
}

- (NSArray *)handleHtmlTagImg:(XTHtmlTagImg *)tag
{
	NSMutableArray *res = [NSMutableArray array];
	
	XTHtmlTagWhitespace *whitespace = [XTHtmlTagWhitespace tagWithText:@" "];
	NSArray *formattedWhitespaceArray = [self handleHtmlTagWhitespace:whitespace]; //TODO !!! probably needs redoing
	[res addObjectsFromArray:formattedWhitespaceArray];
	
	if ([tag hasAttribute:@"alt"]) {
		NSString *altText = [tag attributeAsString:@"alt"];
		if (altText != nil && altText.length >= 1) {
			//NSString *s = [NSString stringWithFormat:@"[Image \"%@\" not shown]\n", altText];
			NSArray *formattedAltTextArray = [self handleRegularText:altText verbatim:NO];
			[res addObjectsFromArray:formattedAltTextArray];
			formattedWhitespaceArray = [self handleHtmlTagWhitespace:whitespace];  //TODO !!! probably needs redoing
			[res addObjectsFromArray:formattedWhitespaceArray];
		}
	}

	return res;
}

- (NSArray *)handleHtmlTagBody:(XTHtmlTagBody *)tag
{
	if (! [self shouldPrintOutputText]) {
		return self.emptyArray;
	}

	XTFormattedOutputElement *fmtElement = [XTFormattedOutputElement bodyElement:tag];
	NSArray *res = [NSArray arrayWithObject:fmtElement];
	return res;
}

- (void)executeTagBody:(XTHtmlTagBody *)tag
{
	NSString *bgColorAttr = [tag attributeAsString:@"bgcolor"];
	if (bgColorAttr != nil) {
		XTHtmlColor *backgroundHtmlColor = [XTHtmlColor forAttributeValue:bgColorAttr];
		if (backgroundHtmlColor.color != nil) {
			self.colorationHelper.bodyBackgroundColor = backgroundHtmlColor;
			if ([self allowGameToSetColors]) {
				self.textView.backgroundColor = self.colorationHelper.bodyBackgroundColor.color;
			} else {
				self.textView.backgroundColor = [self.colorationHelper getPrefsOutputBackgroundColorForTextView];
			}
		} else {
			self.colorationHelper.bodyBackgroundColor = nil;
			self.textView.backgroundColor = [self.colorationHelper getPrefsOutputBackgroundColorForTextBannerTextView];
		}
	} else {
		self.colorationHelper.bodyBackgroundColor = nil;
		self.textView.backgroundColor = [self.colorationHelper getPrefsOutputBackgroundColorForTextBannerTextView];
	}
	
	NSString *textColorAttr = [tag attributeAsString:@"text"];
	if (textColorAttr != nil) {
		XTHtmlColor *textHtmlColor = [XTHtmlColor forAttributeValue:textColorAttr];
		self.colorationHelper.bodyTextColor = textHtmlColor;
	} else {
		self.colorationHelper.bodyTextColor = nil;
	}
	
	NSString *inputColorAttr = [tag attributeAsString:@"input"];
	if (inputColorAttr != nil) {
		XTHtmlColor *inputHtmlColor = [XTHtmlColor forAttributeValue:inputColorAttr];
		self.colorationHelper.bodyInputColor = inputHtmlColor;
	} else {
		self.colorationHelper.bodyInputColor = nil;
		if (self.colorationHelper.bodyTextColor != nil) { //TODO !!! adapt: wtf?
			NSColor *resolvedTextColor = self.colorationHelper.bodyTextColor.color;
			if (resolvedTextColor != nil) {
				self.colorationHelper.bodyInputColor = self.colorationHelper.bodyTextColor;
			} else {
				int brkpt = 1;
			}
		}
	}
	
	[self.colorationHelper applyBodyTextAndInputColorsForceApply:NO];
	
	NSString *linkColorAttr = [tag attributeAsString:@"link"];
	if (linkColorAttr != nil) {
		XTHtmlColor *linkHtmlColor = [XTHtmlColor forAttributeValue:linkColorAttr];
		self.colorationHelper.bodyLinkColor = linkHtmlColor;
	} else {
		self.colorationHelper.bodyLinkColor = nil;
	}
	
	NSString *linkHoverColorAttr = [tag attributeAsString:@"hlink"];
	if (linkHoverColorAttr != nil) {
		XTHtmlColor *linkHoverHtmlColor = [XTHtmlColor forAttributeValue:linkHoverColorAttr];
		self.colorationHelper.bodyLinkHoverColor = linkHoverHtmlColor;
	} else {
		self.colorationHelper.bodyLinkHoverColor = nil;
	}
	
	NSString *linkActiveColorAttr = [tag attributeAsString:@"alink"];
	if (linkActiveColorAttr != nil) {
		XTHtmlColor *linkActiveHtmlColor = [XTHtmlColor forAttributeValue:linkActiveColorAttr];
		self.colorationHelper.bodyLinkActiveColor = linkActiveHtmlColor;
	} else {
		self.colorationHelper.bodyLinkActiveColor = nil;
	}
	
	NSString *linkVisitedColorAttr = [tag attributeAsString:@"vlink"];
	if (linkVisitedColorAttr != nil) {
		XTHtmlColor *linkVisitedHtmlColor = [XTHtmlColor forAttributeValue:linkVisitedColorAttr];
		self.colorationHelper.bodyLinkVisitedColor = linkVisitedHtmlColor;
	} else {
		self.colorationHelper.bodyLinkVisitedColor = nil;
	}
	
	[self.colorationHelper applyBodyLinkColor];
	[self updateCursorColor];
}

- (void)updateCursorColor
{
	[self.colorationHelper updateCursorColor:self.formattingSpecForHtmlTag];
}

- (NSArray *)handleHtmlTagText:(XTHtmlTagText *)tag
{
	return [self handleRegularText:tag.text verbatim:tag.verbatim];
}

- (NSArray *)handleHtmlTagWhitespace:(XTHtmlTagWhitespace *)tag
{
	XT_DEF_SELNAME;
	XT_TRACE_1(@"shouldWriteWhitespace=%d", self.shouldWriteWhitespace);
	
	NSArray *res = nil;
	
	if (self.receivingGameTitle) {
		res = [self makeArrayWithGameTitleElement:@" "];

	} else if (! [self shouldPrintOutputText]) {
		// nothing
		
	} else {
		if (self.formattingSpecForHtmlTag.formattingSpec.verbatim) {
			res = [self makeArrayWithRegularOutputElement:tag.text];
		} else {
			[self addPendingWhitespace:tag];
		}
	}
	
	return res;
}

//TODO fold into special spec handling below?
- (NSArray *)handleHtmlTagQuotedSpace:(XTHtmlTagQuotedSpace *)quotedSpace
{
	XT_TRACE_ENTRY;
	
	NSArray *res = nil;
	
	if (self.receivingGameTitle) {
		res = [self makeArrayWithGameTitleElement:@" "];
	} else if (! [self shouldPrintOutputText]) {
		// nothing
	} else {
		if (self.formattingSpecForHtmlTag.formattingSpec.verbatim) {
			res = [self makeArrayWithRegularOutputElement:@" "];
		} else {
			XTHtmlTagWhitespace *whitespace = [XTHtmlTagWhitespace tagWithText:[quotedSpace asString]];
			[self addPendingWhitespace:whitespace];
			self.shouldWriteWhitespace = YES;
			//XT_WARN_1(@"shouldWriteWhitespace=%d", self.shouldWriteWhitespace);
		}
	}
	
	return res;
}

- (NSArray *)handleHtmlTagSpecialSpace:(XTHtmlTagSpecialSpace *)specialSpace
{
	XT_TRACE_ENTRY;
	
	NSArray *res = nil;
	
	if (self.receivingGameTitle) {
		res = [self makeArrayWithGameTitleElement:@" "];
	} else if (! [self shouldPrintOutputText]) {
		// nothing
	} else {
		if (self.formattingSpecForHtmlTag.formattingSpec.verbatim) {
			NSString *str = [XTStringUtils stringForSpecialSpaceWhenMonoFont:specialSpace.ch];
			if (str.length >= 1) {
				res = [self makeArrayWithRegularOutputElement:str];
			}
		} else {
			[self addPendingSpecialSpace:specialSpace];
			self.shouldWriteWhitespace = YES;
			//XT_WARN_1(@"shouldWriteWhitespace=%d", self.shouldWriteWhitespace);
		}
	}
	
	return res;
}

- (NSArray *)handleHtmlTagNonbreakingSpace:(XTHtmlTagNonbreakingSpace *)nonbreakingSpace
{
	XT_TRACE_ENTRY;
	
	NSMutableArray *res = [NSMutableArray array];
	
	//TODO check logic vs reg'l text case
	if (self.receivingGameTitle) {
		NSArray *tempRes = [self makeArrayWithGameTitleElement:@" "];
		[res addObjectsFromArray:tempRes];
	} else if (! [self shouldPrintOutputText]) {
		// nothing
	} else {
		res = [self makeArrayWithPendingWhitespace];
		[self clearPendingWhitespace];
		//TODO use premade objs instead of "[self makeArrayWith" for common cases
		if (self.formattingSpecForHtmlTag.formattingSpec.verbatim) {
			NSArray *tempRes = [self makeArrayWithRegularOutputElement:@" "];
			[res addObjectsFromArray:tempRes];
		} else {
			//NSArray *tempRes = [self makeArrayWithRegularOutputElement:@"\u00A0"]; // the actual 0nbsp char caused unwanted indent when at start of line
			NSArray *tempRes = [self makeArrayWithRegularOutputElement:@" "];
			//TODO make pre-made obj
			[res addObjectsFromArray:tempRes];
		}
	}
	
	return res;
}

- (NSArray *)handleHtmlTagBannerClear:(XTHtmlTagBannerClear *)tag
{
	XTFormattedOutputElement *outputElement = [XTFormattedOutputElement bannerClearElement];
	NSArray *res = [NSArray arrayWithObject:outputElement];
	return res;
}

- (BOOL)shouldPrintOutputText
{
	XTFormattingSpecification *formattingSpec = self.formattingSpecForHtmlTag.formattingSpec;
	
	BOOL res = (! formattingSpec.inAboutBox) && (! [self suppressContentInTagBanner]);
	return res;
}

- (XTHtmlLinebreakHandler2State)getLineBreakHandlerState
{
	return self.linebreakHandler2.state;
}

- (void)setLineBreakHandlerState:(XTHtmlLinebreakHandler2State)lbh2State
{
	[self.linebreakHandler2 setState:lbh2State];
}

- (NSArray *)cancelOngoingTable
{
	//XT_DEF_SELNAME;
	//XT_WARN_0(@"");

	XTFormattingSpecificationForHtmlTag *formattingSpecForHtmlTag = self.formattingSpecForHtmlTag;

	NSArray *res;
	BOOL inTableCell = (formattingSpecForHtmlTag.formattingSpec.activeTextTableCell != nil) && (formattingSpecForHtmlTag.formattingSpec.activeTextTable != nil);
	if (inTableCell) {
		// end the ongoing cell's paragraph
		res = [self handleHtmlTagTdClose:nil];
	} else {
		res = self.emptyArray;
	}
	
	XTHtmlTagContainer *tagTable = (XTHtmlTagTable *)[XTHtmlUtils findAncestorTagContainerOfClass:[XTHtmlTagTable class]
																			 forDescendantTag:formattingSpecForHtmlTag.tagContainer
																			includeDescendant:YES];
	if (tagTable != nil) {
		[self recursiveCancelActiveTableState:tagTable];
	}
	
	return res;
}

- (void)recursiveCancelActiveTableState:(XTHtmlTagContainer *)tagContainer
{
	//XT_DEF_SELNAME;
	//XT_WARN_0(@"");
	
	XTFormattingSpecification *formattingSpec = tagContainer.formattingSpecForHtmlTag.formattingSpec;
	formattingSpec.activeTextTable = nil;
	formattingSpec.activeTextTableCell = nil;
	
	Class tagContainerClass = [XTHtmlTagContainer class];

	NSArray *contents = tagContainer.contents;
	for (XTHtmlTag *tag in contents) {
		if ([tag isKindOfClass:tagContainerClass]) {
			XTHtmlTagContainer *castTag = (XTHtmlTagContainer *)tag;
			[self recursiveCancelActiveTableState:castTag];
		}
	}
}

- (XTFormattingSpecification *)getFormattingSpecificationForBasefont
{
	XTFormattingSpecification *res = self.formattingSpecForHtmlTag.formattingSpec;
	return res;
}

- (void)setFormattingSpecificationForBasefont:(XTFormattingSpecification *)fmtSpec
{
	self.formattingSpecForHtmlTag.formattingSpec = fmtSpec;
}

@end
