//////////////////////////////////////////////////////////////////////
// Includes
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#include "lgi/common/Lgi.h"
#include "lgi/common/Variant.h"
#include "lgi/common/FontSelect.h"
#include "lgi/common/GdiLeak.h"
#include "lgi/common/DisplayString.h"
#include "lgi/common/PixelRops.h"
#include "lgi/common/UnicodeString.h"

#ifdef FontChange
#undef FontChange
#endif

#ifdef LGI_SDL
#include "ftsynth.h"
#endif

#if WINNATIVE
static OsChar GDisplayStringDots[] = {'.', '.', '.', 0};
#endif

//345678123456781234567812345678
	//	2nd

#define DEBUG_CHAR_AT				0
#define DEBUG_BOUNDS				0

#if defined(__GTK_H__) || (defined(MAC) && !defined(LGI_SDL))
#define DISPLAY_STRING_FRACTIONAL_NATIVE	1
#else
#define DISPLAY_STRING_FRACTIONAL_NATIVE	0
#endif

#if defined(__GTK_H__)
struct Block : public LRect
{
	/// This points to somewhere in Ds->Str
	OsChar *Str = NULL;
	
	/// Bytes in this block
	int Bytes = 0;
	
	/// Utf-8 characters in this block
	int Chars = 0;
	
	/// Alternative font to get characters from (NULL if using the display string's font)
	LFont *Fnt = NULL;
	
	/// Layout for this block. Shouldn't ever be NULL. But shouldn't crash otherwise.
	Gtk::PangoLayout *Hnd = NULL;

	~Block()
	{
		if (Hnd)
			g_object_unref(Hnd);
	}
};

struct GDisplayStringPriv
{
	LDisplayString *Ds;
	LArray<Block> Blocks;
	bool Debug;
	int LastTabOffset;

	GDisplayStringPriv(LDisplayString *str) : Ds(str)
	{
		#if 0
		Debug = Stristr(Ds->Str, "(Jumping).wma") != 0;
		#else
		Debug = false;
		#endif
		LastTabOffset = -1;
	}

	~GDisplayStringPriv()
	{
	}

	void Create(Gtk::GtkPrintContext *PrintCtx)
	{
		auto *Fs = LFontSystem::Inst();
		auto *Fnt = Ds->Font;
		auto Tbl = Fnt->GetGlyphMap();

		int Chars = 0;
		LUtf8Ptr p(Ds->Str);
		auto *Start = p.GetPtr();
		if (Tbl)
		{
			int32 w;
			Block *b = NULL;
			auto DisplayCtx = LFontSystem::Inst()->GetContext();

			while ((w = (int32)p))
			{
				LFont *f;
				
				if (w >= 0x80 && !_HasUnicodeGlyph(Tbl, w))
					f = Fs->GetGlyph(w, Ds->Font);
				else
					f = Ds->Font;

				if (!b || (f != NULL && f != Fnt))
				{
					// Finish old block
					if (b)
					{
						b->Bytes = p.GetPtr() - Start;
						b->Chars = Chars;
						Chars = 0;
					}

					Start = p.GetPtr();

					if (f)
					{
						// Start new block...
						b = &Blocks.New();
						b->Str = (char*)Start;
						b->Bytes = -1; // unknown at this point
						if (f != Ds->Font)
							// External font
							b->Fnt = f;

						// Create a pango layout
						if (PrintCtx)
							b->Hnd = Gtk::gtk_print_context_create_pango_layout(PrintCtx);
						else
							b->Hnd = Gtk::pango_layout_new(DisplayCtx);
					}
					// else no font supports glyph

					Fnt = f;
				}
				// else no change in font
				
				p++;
				Chars++;
			}

			if (b)
			{
				// Finish the last block
				b->Bytes = p.GetPtr() - Start;
				b->Chars = Chars;
			}
				
			if (Debug)
			{
				// Print the block array
				for (size_t i=0; i<Blocks.Length(); i++)
				{
					auto &b = Blocks[i];
					printf("%s%p, %i/%i, %p, %p\n", i?"    ":"Blk: ", b.Str, b.Bytes, b.Chars, b.Fnt, b.Hnd);
				}
			}
		}
		else
		{
			while (p.GetPtr() - Start < Ds->StrWords)
			{
				p++;
				Chars++;
			}

			auto &b = Blocks.New();
			b.Str = (char*)Start;
			b.Bytes = p.GetPtr() - Start;
			b.Chars = Chars;
			if (PrintCtx)
				b.Hnd = Gtk::gtk_print_context_create_pango_layout(PrintCtx);
			else
				b.Hnd = Gtk::pango_layout_new(LFontSystem::Inst()->GetContext());
		}

		/* This could get stuck in an infinite loop. Leaving out for the moment.
		for (auto &b: Blocks)
		{
			if (b.Hnd == NULL && b.Fnt == NULL)
			{
				Blocks.Length(0);
				goto Start;
			}
		}
		*/
	}
	
	void UpdateTabs(int Offset, int Size, bool Debug = false)
	{
		if (Ds->Font &&
			Ds->Font->TabSize())
		{
			int Len = 16;
			LastTabOffset = Offset;
		
			Gtk::PangoTabArray *t = Gtk::pango_tab_array_new(Len, true);
			if (t)
			{
				for (int i=0; i<Len; i++)
				{
					int Pos = (i * Size) + Offset;
					Gtk::pango_tab_array_set_tab(t,
												i,
												Gtk::PANGO_TAB_LEFT,
												Pos);
					if (Debug)
						printf("%i, ", Pos);
				}
				if (Debug)
					printf("\n");
			
                		for (auto &b: Blocks)
                		{
                			if (b.Hnd)
						Gtk::pango_layout_set_tabs(b.Hnd, t);
				}

				Gtk::pango_tab_array_free(t);
			}
		}
	}
};
#endif

template<typename Out, typename In>
bool StringConvert(Out *&out, ssize_t &OutWords, const In *in, ssize_t InLen)
{
	if (!in)
	{
		out = 0;
		OutWords = 0;
		return false;
	}

	auto InSz = sizeof(In);
	auto OutSz = sizeof(Out);
	
	// Work out input size
	ssize_t InWords;
	if (InLen >= 0)
		InWords = InLen;
	else
		for (InWords = 0; in[InWords]; InWords++)
			;

	if (InSz == OutSz)
	{
		// No conversion optimization
		out = (Out*)Strdup(in, InWords);
		OutWords = out ? InWords : 0;
		return out != 0;
	}
	else
	{
		// Convert the string to new word size
		static const char *Cp[] = { NULL, "utf-8", "utf-16", NULL, "utf-32"};
		LAssert(OutSz <= 4 && InSz <= 4 && Cp[OutSz] && Cp[InSz]);
		out = (Out*) LNewConvertCp(Cp[OutSz], in, Cp[InSz], InWords*sizeof(In));
		OutWords = Strlen(out);
		return out != NULL;
	}
	
	return false;
}

//////////////////////////////////////////////////////////////////////
#define SubtractPtr(a, b)			(	((a)-(b)) / sizeof(*a)	)
#define VisibleTabChar				0x2192
#define IsTabChar(c)				(c == '\t') // || (c == VisibleTabChar && VisibleTab))

#if USE_CORETEXT
	#include <CoreFoundation/CFString.h>

	void LDisplayString::CreateAttrStr()
	{
		if (!Wide)
			return;

		if (AttrStr)
		{
			CFRelease(AttrStr);
			AttrStr = NULL;
		}

		wchar_t *w = Wide;
		CFStringRef string = CFStringCreateWithBytes(kCFAllocatorDefault, (const uint8_t*)w, StrlenW(w) * sizeof(*w), kCFStringEncodingUTF32LE, false);
		if (string)
		{
			CFDictionaryRef attributes = Font->GetAttributes();
			if (attributes)
				AttrStr = CFAttributedStringCreate(kCFAllocatorDefault, string, attributes);
			
			// else LAssert(0);
			
			CFRelease(string);
		}
	}
#endif

LDisplayString::LDisplayString(LFont *f, const char *s, ssize_t l, LSurface *pdc)
{
	pDC = pdc;
	Font = f;
	
	#if LGI_DSP_STR_CACHE
	StringConvert(Wide, WideWords, s, l);
	#endif
	StringConvert(Str, StrWords, s, l);
	
	x = y = 0;
	xf = 0;
	yf = 0;
	DrawOffsetF = 0;
	LaidOut = 0;
	AppendDots = 0;
	VisibleTab = 0;
	
	#if defined __GTK_H__
	
		d = new GDisplayStringPriv(this);
		if (Font && Str)
		{
			LAssert(StrWords >= 0);
			if (StrWords > 0)
				d->Create(pDC ? pDC->GetPrintContext() : NULL);
		}
	
	#elif defined MAC && !defined(LGI_SDL)
	
		Hnd = 0;
		#if USE_CORETEXT
		AttrStr = NULL;
		#endif
		if (Font && Str && StrWords > 0)
		{
			#if USE_CORETEXT
				CreateAttrStr();
			#else
				ATSUCreateTextLayout(&Hnd);
			#endif
		}
	
	#endif
}

LDisplayString::LDisplayString(LFont *f, const char16 *s, ssize_t l, LSurface *pdc)
{
	pDC = pdc;
	Font = f;

	#if LGI_DSP_STR_CACHE
	StringConvert(Wide, WideWords, s, l);
	#endif
	StringConvert(Str, StrWords, s, l);
	
	x = y = 0;
	xf = 0;
	yf = 0;
	DrawOffsetF = 0;
	LaidOut = 0;
	AppendDots = 0;
	VisibleTab = 0;

	#if defined __GTK_H__
	
		d = new GDisplayStringPriv(this);
		if (Font && Str && StrWords > 0)
			d->Create(pDC ? pDC->GetPrintContext() : NULL);
	
	#elif defined MAC && !defined(LGI_SDL)
	
		Hnd = NULL;
		#if USE_CORETEXT
		AttrStr = NULL;
		#endif
		if (Font && Str && StrWords > 0)
		{
			#if USE_CORETEXT
				CreateAttrStr();
			#else
				OSStatus e = ATSUCreateTextLayout(&Hnd);
				if (e) printf("%s:%i - ATSUCreateTextLayout failed with %i.\n", _FL, (int)e);
			#endif
		}
	
	#endif
}

#ifdef _MSC_VER
LDisplayString::LDisplayString(LFont *f, const uint32_t *s, ssize_t l, LSurface *pdc)
{
	pDC = pdc;
	Font = f;
	x = y = 0;
	xf = 0;
	yf = 0;
	DrawOffsetF = 0;
	LaidOut = 0;
	AppendDots = 0;
	VisibleTab = 0;

	#if LGI_DSP_STR_CACHE
	StringConvert(Wide, WideWords, s, l);
	#endif
	StringConvert(Str, StrWords, s, l);

	#if defined __GTK_H__
	d = new GDisplayStringPriv(this);
	if (Font && Str && StrWords > 0)
		d->Create(pDC ? pDC->GetPrintContext() : NULL);
	#endif
}
#endif

LDisplayString::~LDisplayString()
{
	#if defined(LGI_SDL)
	
		Img.Reset();
	
	#elif defined __GTK_H__

		DeleteObj(d);

	#elif defined MAC

		#if USE_CORETEXT

			if (Hnd)
			{
				CFRelease(Hnd);
				Hnd = NULL;
			}
			if (AttrStr)
			{
				CFRelease(AttrStr);
				AttrStr = NULL;
			}

		#else

		if (Hnd)
			ATSUDisposeTextLayout(Hnd);

		#endif

	#endif
	
	DeleteArray(Str);
	#if LGI_DSP_STR_CACHE
	DeleteArray(Wide);
	#endif
}

void LDisplayString::DrawWhiteSpace(LSurface *pDC, char Ch, LRect &r)
{
	if (Ch == '\t')
	{
		r.Inset(3, 3);
		if (r.Y()/2 == 0)
			r.y2++;
		int Cy = (r.Y() >> 1);
		pDC->Line(r.x1, r.y1+Cy, r.x2, r.y1+Cy);
		pDC->Line(r.x2, r.y1+Cy, r.x2-Cy, r.y1);
		pDC->Line(r.x2, r.y1+Cy, r.x2-Cy, r.y2);
	}
	else // Space
	{
		int x = r.x1 + (r.X()>>1) - 1;
		int Cy = r.y1 + (int)ceil(Font->Ascent()) - 2;
		pDC->Rectangle(x, Cy, x+1, Cy+1);
	}
}

void LDisplayString::Layout(bool Debug)
{
	if (LaidOut || !Font)
		return;

	LaidOut = 1;

	#if defined(HAIKU)

		if (!Font)
		{
			LgiTrace("%s:%i - Missing pointer: %p\n", _FL, Font);
			return;
		}

		BFont *fnt = Font->Handle();
		if (!fnt)
		{
			LgiTrace("%s:%i - Missing handle. %p/%p\n", _FL, fnt);
			return;
		}
		
		auto &l = Info[0];
		l.Str = Str;
		l.Len = Str ? strlen(Str) : 0;

		const char *strArr[] = { Str };
		const int32 strLen[] = { l.Len };
		float width[1] = { 0 };
		fnt->GetStringWidths(strArr, strLen, 1, width);
		
		x = l.X = (int)(width[0] + 0.5);
		
		font_height height = {0};
		fnt->GetHeight(&height);
		y = height.ascent + height.descent + height.leading;

		// printf("Layout str=%s sz=%i,%i\n", Str, x, y);
	
	#elif defined(LGI_SDL)
	
		FT_Face Fnt = Font->Handle();
		FT_Error error;
		
		if (!Fnt || !Str)
			return;
		
		// Create an array of glyph indexes
		LArray<uint32> Glyphs;
		for (OsChar *s = Str; *s; s++)
		{
			FT_UInt index = FT_Get_Char_Index(Fnt, *s);
			if (index)
				Glyphs.Add(index);
		}
		
		// Measure the string...
		LPoint Sz;
		int FontHt = Font->GetHeight();
		int AscentF = (int) (Font->Ascent() * FScale);
		int LoadMode = FT_LOAD_FORCE_AUTOHINT;
		for (unsigned i=0; i<Glyphs.Length(); i++)
		{
			error = FT_Load_Glyph(Fnt, Glyphs[i], LoadMode);
			if (error == 0)
			{
				int PyF = AscentF - Fnt->glyph->metrics.horiBearingY;

				Sz.x += Fnt->glyph->metrics.horiAdvance;
				Sz.y = MAX(Sz.y, PyF + Fnt->glyph->metrics.height);
			}
		}
		
		// Create the memory context to draw into
		xf = Sz.x;
		x = ((Sz.x + FScale - 1) >> FShift) + 1;
		yf = FontHt << FShift;
		y = FontHt; // ((Sz.y + FScale - 1) >> FShift) + 1;
		
		if (Img.Reset(new LMemDC(x, y, CsIndex8)))
		{
			// Clear the context to black
			Img->Colour(0);
			Img->Rectangle();

			bool IsItalic = Font->Italic();
			
			int CurX = 0;
			int FBaseline = Fnt->size->metrics.ascender;
			for (unsigned i=0; i<Glyphs.Length(); i++)
			{
				error = FT_Load_Glyph(Fnt, Glyphs[i], LoadMode);
				if (error == 0)
				{
					if (IsItalic)
						FT_GlyphSlot_Oblique(Fnt->glyph);

					error = FT_Render_Glyph(Fnt->glyph, FT_RENDER_MODE_NORMAL);
					if (error == 0)
					{
						FT_Bitmap &bmp = Fnt->glyph->bitmap;
						if (bmp.buffer)
						{
							// Copy rendered glyph into our image memory
							int Px = (CurX + (FScale >> 1)) >> FShift;
							int PyF = AscentF - Fnt->glyph->metrics.horiBearingY;
							int Py = PyF >> FShift;
							int Skip = 0;
							
							if (Fnt->glyph->format == FT_GLYPH_FORMAT_BITMAP)
							{
								Px += Fnt->glyph->bitmap_left;
								Skip = Fnt->glyph->bitmap_left < 0 ? -Fnt->glyph->bitmap_left : 0;
								Py = (AscentF >> FShift) - Fnt->glyph->bitmap_top;
							}
							
							LAssert(Px + bmp.width <= Img->X());
							for (int y=0; y<bmp.rows; y++)
							{
								uint8_t *in = bmp.buffer + (y * bmp.pitch);
								uint8_t *out = (*Img)[Py+y];
								if (out)
								{
									LAssert(Px+Skip >= 0);
									out += Px+Skip;
									memcpy(out, in+Skip, bmp.width-Skip);
								}
								/*
								else
								{
									LAssert(!"No scanline?");
									break;
								}
								*/
							}
						}
						
						if (i < Glyphs.Length() - 1)
						{
							FT_Vector kerning;
							FT_Get_Kerning(Fnt, Glyphs[i], Glyphs[i+1], FT_KERNING_DEFAULT, &kerning);
							CurX += Fnt->glyph->metrics.horiAdvance + kerning.x;
						}
						else
						{
							CurX += Fnt->glyph->metrics.horiAdvance;
						}
					}
				}
			}
		}
		else LgiTrace("::Layout Create MemDC failed\n");
	
	#elif defined(__GTK_H__)
	
		y = Font->GetHeight();
		yf = y * PANGO_SCALE;
		if (!d->Blocks.Length() || !Font->Handle())
			return;

		LUtf8Ptr Utf(Str);
		int32 Wide;
		while (*Utf.GetPtr())
		{
			Wide = Utf;
			if (!Wide)
			{
				LgiTrace("%s:%i - Not utf8\n", _FL);
				return;
			}
			Utf++;
		}
	
		LFontSystem *FSys = LFontSystem::Inst();
		Gtk::pango_context_set_font_description(FSys->GetContext(), Font->Handle());

		int TabSizePx = Font->TabSize();
		int TabSizeF = TabSizePx * FScale;
		int TabOffsetF = DrawOffsetF % TabSizeF;
		int OffsetF = TabOffsetF ? TabSizeF - TabOffsetF : 0;
		d->UpdateTabs(OffsetF / FScale, Font->TabSize());

		if (Font->Underline())
		{
			Gtk::PangoAttrList *attrs = Gtk::pango_attr_list_new();
			Gtk::PangoAttribute *attr = Gtk::pango_attr_underline_new(Gtk::PANGO_UNDERLINE_SINGLE);
			Gtk::pango_attr_list_insert(attrs, attr);
			for (auto &b: d->Blocks)
				Gtk::pango_layout_set_attributes(b.Hnd, attrs);
			Gtk::pango_attr_list_unref(attrs);
		}
		
		int Fx = 0;
		for (auto &b: d->Blocks)
		{
			int bx = 0, by = 0;

			if (b.Hnd)
			{
				if (!LIsUtf8(b.Str, b.Bytes))
				{
					LgiTrace("Invalid UTF8: '%.*S'\n", (int)b.Bytes, b.Str);
				}
				else
				{
					Gtk::pango_layout_set_text(b.Hnd, b.Str, b.Bytes);
				}
				Gtk::pango_layout_get_size(b.Hnd, &bx, &by);
			}
			else if (b.Fnt)
			{
				b.Fnt->_Measure(bx, by, b.Str, b.Bytes);
				bx <<= FShift;
				by <<= FShift;
			}

			b.ZOff(bx-1, by-1);
			b.Offset(Fx, 0);
			xf += bx;
			yf = MAX(yf, by);
		}

		x = (xf + PANGO_SCALE - 1) / PANGO_SCALE;
		#if 1
		y = Font->GetHeight();
		#else
		y = (yf + PANGO_SCALE - 1) / PANGO_SCALE;
		#endif
		if (y > Font->GetHeight())
		{
			printf("%s:%i - Height error: %i > %i\n", _FL, y, Font->GetHeight());
		}
	
	#elif defined MAC && !defined(LGI_SDL)
	
		#if USE_CORETEXT

			int height = Font->GetHeight();
			y = height;

			if (AttrStr)
			{
				LAssert(!Hnd);
				Hnd = CTLineCreateWithAttributedString(AttrStr);
				if (Hnd)
				{
					CGFloat ascent = 0.0;
					CGFloat descent = 0.0;
					CGFloat leading = 0.0;
					double width = CTLineGetTypographicBounds(Hnd, &ascent, &descent, &leading);
					x = ceil(width);
					xf = width * FScale;
					yf = height * FScale;
				}
			}
	
		#else
	
			if (!Hnd || !Str)
				return;

			OSStatus e = ATSUSetTextPointerLocation(Hnd, Str, 0, len, len);
			if (e)
			{
				char *a = 0;
				StringConvert(a, NULL, Str, len);
				printf("%s:%i - ATSUSetTextPointerLocation failed with errorcode %i (%s)\n", _FL, (int)e, a);
				DeleteArray(a);
				return;
			}

			e = ATSUSetRunStyle(Hnd, Font->Handle(), 0, len);
			if (e)
			{
				char *a = 0;
				StringConvert(a, NULL, Str, len);
				printf("%s:%i - ATSUSetRunStyle failed with errorcode %i (%s)\n", _FL, (int)e, a);
				DeleteArray(a);
				return;
			}

			ATSUTextMeasurement fTextBefore;
			ATSUTextMeasurement fTextAfter;

			if (pDC)
			{
				OsPainter dc = pDC->Handle();
				ATSUAttributeTag		Tags[1] = {kATSUCGContextTag};
				ByteCount				Sizes[1] = {sizeof(CGContextRef)};
				ATSUAttributeValuePtr	Values[1] = {&dc};

				e = ATSUSetLayoutControls(Hnd, 1, Tags, Sizes, Values);
				if (e) printf("%s:%i - ATSUSetLayoutControls failed (e=%i)\n", _FL, (int)e);
			}
			
			ATSUTab Tabs[32];
			for (int i=0; i<CountOf(Tabs); i++)
			{
				Tabs[i].tabPosition = (i * Font->TabSize()) << 16;
				Tabs[i].tabType = kATSULeftTab;
			}
			e = ATSUSetTabArray(Hnd, Tabs, CountOf(Tabs));
			if (e) printf("%s:%i - ATSUSetTabArray failed (e=%i)\n", _FL, (int)e);

			e = ATSUGetUnjustifiedBounds(	Hnd,
											kATSUFromTextBeginning,
											kATSUToTextEnd,
											&fTextBefore,
											&fTextAfter,
											&fAscent,
											&fDescent);
			if (e)
			{
				char *a = 0;
				StringConvert(a, NULL, Str, len);
				printf("%s:%i - ATSUGetUnjustifiedBounds failed with errorcode %i (%s)\n", _FL, (int)e, a);
				DeleteArray(a);
				return;
			}

			xf = fTextAfter - fTextBefore;
			yf = fAscent + fDescent;
			x = (xf + 0xffff) >> 16;
			y = (yf + 0xffff) >> 16;
			ATSUSetTransientFontMatching(Hnd, true);

		#endif
	
	#elif defined WINNATIVE
	
		int sx, sy, i = 0;
		if (!Font)
			return;
		if (!Font->Handle())
			Font->Create();
		
		y = Font->GetHeight();

		LFontSystem *Sys = LFontSystem::Inst();
		if (Sys && Str)
		{
			LFont *f = Font;
			bool GlyphSub = Font->SubGlyphs();

			Info[i].Str = Str;

			int TabSize = Font->TabSize() ? Font->TabSize() : 32;

			bool WasTab = IsTabChar(*Str);
			f = GlyphSub ? Sys->GetGlyph(*Str, Font) : Font;
			if (f && f != Font)
			{
				f->Size(Font->Size());
				f->SetWeight(Font->GetWeight());
				if (!f->Handle())
					f->Create();
			}

			bool Debug = WasTab;			
			uint32_t u32;
			for (LUnicodeString<wchar_t> u(Str, StrWords); true; u++)
			{
				u32 = *u;
				LFont *n = GlyphSub ? Sys->GetGlyph(u32, Font) : Font;
				bool Change =	n != f ||						// The font changed
								(IsTabChar(u32) ^ WasTab) ||	// Entering/leaving a run of tabs
								!u32 ||							// Hit a NULL character
								(u.Get() - Info[i].Str) >= 1000;		// This is to stop very long segments not rendering
				if (Change)
				{
					// End last segment
					if (n && n != Font)
					{
						n->Size(Font->Size());
						n->SetWeight(Font->GetWeight());
						if (!n->Handle())
							n->Create();
					}

					Info[i].Len = (int) (u.Get() - Info[i].Str);
					if (Info[i].Len)
					{
						if (WasTab)
						{
							// Handle tab(s)
							for (int t=0; t<Info[i].Len; t++)
							{
								int Dx = TabSize - ((Info[i].X + x + GetDrawOffset()) % TabSize);
								Info[i].X += Dx;
							}
							x += Info[i].X;
						}
						else
						{
							LFont *m = f;

							#if 0
							// This code is causing email to display very slowly in Scribe...
							// I guess I should rewrite the glyph substitution code to be
							// better aware of point size differences.
							if (f && (f->GetHeight() > Font->GetHeight()))
							{
								Info[i].SizeDelta = -1;
								f->PointSize(Font->PointSize() + Info[i].SizeDelta);
								f->Create();
							}
							#endif

							if (!f)
							{
								// no font, so ? out the chars... as they aren't available anyway
								// printf("Font Cache Miss, Len=%i\n\t", Info[i].Len);
								m = Font;
								for (int n=0; n<Info[i].Len; n++)
								{
									Info[i].Str[n] = '?';
								}
							}
							m->_Measure(sx, sy, Info[i].Str, Info[i].Len);
							x += Info[i].X = sx > 0xffff ? 0xffff : sx;
						}

						auto Ch = Info[i].First();
						Info[i].FontId = !f || Font == f ? 0 : Sys->Lut[Ch];

						i++;
					}

					f = n;

					// Start next segment
					WasTab = IsTabChar(u32);
					Info[i].Str = u.Get();
				}

				if (!u32) break;
			}

			if (Info.Length() > 0 && Info.Last().Len == 0)
			{
				Info.Length(Info.Length()-1);
			}
		}
		
		xf = x;
		yf = y;

	#endif
}

int LDisplayString::GetDrawOffset()
{
	return DrawOffsetF >> FShift;
}

void LDisplayString::SetDrawOffset(int Px)
{
	if (LaidOut)
		LAssert(!"No point setting TabOrigin after string is laid out.\n");
	DrawOffsetF = Px << FShift;
}

bool LDisplayString::ShowVisibleTab()
{
	return VisibleTab;
}

void LDisplayString::ShowVisibleTab(bool i)
{
	VisibleTab = i;
}

bool LDisplayString::IsTruncated()
{
	return AppendDots;
}

void LDisplayString::TruncateWithDots(int Width)
{
	Layout();
	
	#if defined __GTK_H__
		
	int Fx = 0;
	int Fwid = Width << FShift;
	for (auto &b: d->Blocks)
	{
		if (Fwid < Fx + b.X())
		{
			if (b.Hnd)
			{
				Gtk::pango_layout_set_ellipsize(b.Hnd, Gtk::PANGO_ELLIPSIZE_END);
				Gtk::pango_layout_set_width(b.Hnd, Fwid - Fx);
				Gtk::pango_layout_set_single_paragraph_mode(b.Hnd, true);
			}
			else if (b.Fnt)
			{
			}
			break;
		}

		Fx += b.X();
	}
	
	#elif WINNATIVE
	
	if (Width < X() + 8)
	{
		ssize_t c = CharAt(Width);
		if (c >= 0 && c < StrWords)
		{
			if (c > 0) c--; // fudge room for dots
			if (c > 0) c--;
			
			AppendDots = 1;
			
			if (Info.Length())
			{
				int Width = 0;
				int Pos = 0;
				for (int i=0; i<Info.Length(); i++)
				{
					if (c >= Pos &&
						c < Pos + Info[i].Len)
					{
						Info[i].Len = (int) (c - Pos);
						Info[i].Str[Info[i].Len] = 0;

						LFont *f = Font;
						if (Info[i].FontId)
						{
							LFontSystem *Sys = LFontSystem::Inst();
							f = Sys->Font[Info[i].FontId];
							f->PointSize(Font->PointSize() + Info[i].SizeDelta);
							if (!f->Handle())
							{
								f->Create();
							}
						}
						else
						{
							f = Font;
						}
						
						if (f)
						{
							int Sx, Sy;
							f->_Measure(Sx, Sy, Info[i].Str, Info[i].Len);
							Info[i].X = Sx;
							Width += Info[i].X;
						}
						
						Info.Length(i + 1);						
						break;
					}
					
					Pos += Info[i].Len;
					Width += Info[i].X;
				}
				
				int DotsX, DotsY;
				Font->_Measure(DotsX, DotsY, GDisplayStringDots, 3);
				x = Width + DotsX;
			}
		}
	}
	
	#elif defined(LGI_SDL)
	
	#elif defined(MAC)
	
		#if USE_CORETEXT

			if (Hnd)
			{
				/*
				CFAttributedStringRef truncationString = CFAttributedStringCreate(NULL, CFSTR("\u2026"), Font->GetAttributes());
				if (truncationString)
				{
					CTLineRef truncationToken = CTLineCreateWithAttributedString(truncationString);
					CFRelease(truncationString);
					if (truncationToken)
					{
						CTLineRef TruncatedLine = CTLineCreateTruncatedLine(Hnd, Width, kCTLineTruncationEnd, truncationToken);
						CFRelease(truncationToken);
						if (TruncatedLine)
						{
							CFRelease(Hnd);
							Hnd = TruncatedLine;
						}
					}
				}
				 */
			}
	
		#endif
	
	#endif
}

ssize_t LDisplayString::CharAt(int Px, LPxToIndexType Type)
{
	Layout();
	if (Px < 0)
	{
		return 0;
	}
	else if (Px >= (int)x)
	{
		#if defined __GTK_H__
			if (Str)
			{
				GUtf8Str u(Str);
				return u.GetChars();
			}
			return 0;
		#else
			#if LGI_DSP_STR_CACHE
				return WideWords;
			#else
				return StrWords;
			#endif
		#endif
	}

	int Status = -1;

	#if defined __GTK_H__
	
		int Fx = 0;
		int Fpos = Px << FShift;
		Status = 0;
		for (auto &b: d->Blocks)
		{
			int Index = 0, Trailing = 0;
			int Foffset = Fpos - Fx;
			
			if (b.Hnd && Gtk::pango_layout_xy_to_index(b.Hnd, Foffset, 0, &Index, &Trailing))
			{
				if (d->Debug)
					printf("CharAt(%g) x=%g Status=%i Foffset=%g index=%i trailing=%i\n",
						(double)Fpos/FScale, (double)b.X()/FScale, Status,
						(double)Foffset/FScale, Index, Trailing);

				GUtf8Str u(Str);
				while ((OsChar*)u.GetPtr() < Str + Index + Trailing)
				{
					u++;
					Status++;
				}
				
				return Status;
			}
			else
			{
				if (d->Debug)
					printf("CharAt(%g) x=%g Status=%i Chars=%i\n",
						(double)Fpos/FScale, (double)b.X()/FScale, Status, b.Chars);
					
				Status += b.Chars;
			}

			Fx += b.X();
		}
	
	#elif defined MAC && !defined(LGI_SDL)

		if (Hnd && Str)
		{
			
			#if USE_CORETEXT

				CGPoint pos = { (CGFloat)Px, 1.0f };
				CFIndex utf16 = CTLineGetStringIndexForPosition(Hnd, pos);
			
				// 'utf16' is in UTF-16, and the API needs to return a UTF-32 index...
				// So convert between the 2 here...
				LAssert(Str != NULL);
				int utf32 = 0;
				for (int i=0; Str[i] && i<utf16;)
				{
					if (IsUtf16_Lead(Str[i]))
					{
						if (!Str[i+1])
							break;
						i += 2;
					}
					else i++;
					utf32++;
				}
			
				// printf("utf=%i,%i\n", (int)utf16, utf32);
				return utf32;
			
			#else

				UniCharArrayOffset Off = 0;
				Boolean IsLeading;
				OSStatus e = ATSUPositionToOffset(Hnd, FloatToFixed(Px), FloatToFixed(y / 2), &Off, &IsLeading, &Off2);
				if (e) printf("%s:%i - ATSUPositionToOffset failed with %i, CharAt(%i) x=%i len=%i\n", _FL, (int)e, Px, x, len);
				else Status = Off;

			#endif
		}
	
	#elif defined(LGI_SDL)
	
		LAssert(!"Impl me");
	
	#else // This case is for Win32 and Haiku.
	
		#if defined(WINNATIVE)	
		LFontSystem *Sys = LFontSystem::Inst();
		if (Info.Length() && Font && Sys)		
		#endif
		{
			int TabSize = Font->TabSize() ? Font->TabSize() : 32;
			int Cx = 0;
			int Char = 0;

			#if DEBUG_CHAR_AT
			printf("CharAt(%i) Str='%s'\n", Px, Str);
			#endif

			for (int i=0; i<Info.Length() && Status < 0; i++)
			{
				if (Px < Cx)
				{
					Status = Char;
					#if DEBUG_CHAR_AT
					printf("\tPx<Cx %i<%i\n", Px, Cx);
					#endif
					break;
				}

				if (Px >= Cx && Px < Cx + Info[i].X)
				{
					// The position is in this block of characters
					if (IsTabChar(Info[i].Str[0]))
					{
						// Search through tab block
						for (int t=0; t<Info[i].Len; t++)
						{
							int TabX = TabSize - (Cx % TabSize);
							if (Px >= Cx && Px < Cx + TabX)
							{
								Status = Char;
								#if DEBUG_CHAR_AT
								printf("\tIn tab block %i\n", i);
								#endif
								break;
							}
							Cx += TabX;
							Char++;
						}
					}
					else
					{
						// Find the pos in this block
						LFont *f = Font;

						#if defined(WIN32)
						if (Info[i].FontId)
						{
							f = Sys->Font[Info[i].FontId];
							f->Colour(Font->Fore(), Font->Back());
							f->Size(Font->Size());
							if (!f->Handle())
							{
								f->Create();
							}
						}

						int Fit = f->_CharAt(Px - Cx, Info[i].Str, Info[i].Len, Type);
						#if DEBUG_CHAR_AT
						printf("\tNon tab block %i, Fit=%i, Px-Cx=%i-%i=%i, Str='%.5s'\n",
							i, Fit, Px, Cx, Px-Cx, Info[i].Str);
						#endif
						if (Fit >= 0)
						{
							Status = Char + Fit;
						}
						else
						{
							Status = -1;
						}
						break;
						#endif
					}
				}
				else
				{
					// Not in this block, skip the whole lot
					Cx += Info[i].X;
					Char += Info[i].Len;
				}
			}

			if (Status < 0)
			{
				Status = Char;
			}
		}
	
	#endif

	return Status;
}

ssize_t LDisplayString::Length()
{
	return StrWords;
}

/* If this is only impl for windows either, impl for everything or don't use.
void LDisplayString::Length(int New)
{
	Layout();

	#if WINNATIVE
	
	if (New < len)
	{
		LFontSystem *Sys = LFontSystem::Inst();
		
		int CurX = 0;
		int CurLen = 0;
		for (int i=0; i<Info.Length(); i++)
		{
			// Is the cut point in this block?
			if (New >= CurLen && New < CurLen + Info[i].Len )
			{
				// In this block
				int Offset = New - CurLen;
				Info[i].Len = Offset;
				Info[i].Str[Info[i].Len] = 0;

				// Get the font for this block of characters
				LFont *f = 0;
				if (Info[i].FontId)
				{
					f = Sys->Font[Info[i].FontId];
					f->PointSize(Font->PointSize());
					f->Transparent(Font->Transparent());
					if (!f->Handle())
					{
						f->Create();
					}
				}
				else
				{
					f = Font;
				}

				// Chop the current block and re-measure
				int ChoppedX, Unused;
				f->_Measure(ChoppedX, Unused, Info[i].Str, Info[i].Len);
				Info[i].X = ChoppedX;
				x = CurX + Info[i].X;
				Info.Length(i + 1);
				
				// Leave the loop
				break;
			}
			
			CurX += Info[i].X;
			CurLen += Info[i].Len;
		}
	}
	else
	{
		printf("%s:%i - New>=Len (%i>=" LPrintfSSizeT" )\n", _FL, New, len);
	}

	#endif
}
*/

int LDisplayString::X()
{
	Layout();
	return x;
}

int LDisplayString::Y()
{
	Layout();
	return y;
}

LPoint LDisplayString::Size()
{
	Layout();
	return LPoint(x, y);
}

#if defined LGI_SDL

template<typename OutPx>
bool CompositeText8Alpha(LSurface *Out, LSurface *In, LFont *Font, int px, int py, LBlitRegions &Clip)
{
	OutPx map[256];

	if (!Out || !In || !Font)
		return false;

	// FIXME, do blt clipping here...

	// Create colour map of the foreground/background colours		
	uint8_t *Div255 = Div255Lut;
	LColour fore = Font->Fore();
	LRgb24 fore_px;
	fore_px.r = fore.r();
	fore_px.g = fore.g();
	fore_px.b = fore.b();
	if (Font->Transparent())
	{
		for (int a=0; a<256; a++)
		{
			map[a].r = Div255[a * fore_px.r];
			map[a].g = Div255[a * fore_px.g];
			map[a].b = Div255[a * fore_px.b];
			map[a].a = a;
		}
	}
	else
	{
		LColour back = Font->Back();
		LRgb24 back_px;
		back_px.r = back.r();
		back_px.g = back.g();
		back_px.b = back.b();

		for (int a=0; a<256; a++)
		{
			int oma = 255 - a;
			map[a].r = (oma * back_px.r) + (a * fore_px.r) / 255;
			map[a].g = (oma * back_px.g) + (a * fore_px.g) / 255;
			map[a].b = (oma * back_px.b) + (a * fore_px.b) / 255;
			map[a].a = 255;
		}
	}

	uint8_t *StartOfBuffer = (*Out)[0];
	uint8_t *EndOfBuffer = StartOfBuffer + (Out->GetRowStep() * Out->Y());

	for (unsigned y=Clip.SrcClip.y1; y<=Clip.SrcClip.y2; y++)
	{
		OutPx *d = ((OutPx*) (*Out)[py + y]) + Clip.DstClip.x1;
		uint8_t *i = (*In)[y];
		if (!i) return false;
		uint8_t *e = i + Clip.DstClip.X();

		LAssert((uint8_t*)d >= StartOfBuffer);
		
		if (Font->Transparent())
		{
			uint8_t a, o;
			OutPx *s;
			
			while (i < e)
			{
				// Alpha blend map and output pixel
				a = *i++;
				switch (a)
				{
					case 0:
						break;
					case 255:
						// Copy
						*d = map[a];
						break;
					default:
						// Blend
						o = 255 - a;
						s = map + a;
						#define NonPreMulOver32NoAlpha(c)		d->c = ((s->c * a) + (Div255[d->c * 255] * o)) / 255
						NonPreMulOver32NoAlpha(r);
						NonPreMulOver32NoAlpha(g);
						NonPreMulOver32NoAlpha(b);
						break;
				}
				d++;
			}
		}
		else
		{
			while (i < e)
			{
				// Copy rop
				*d++ = map[*i++];
			}
		}

		LAssert((uint8_t*)d <= EndOfBuffer);
	}
	
	return true;
}

template<typename OutPx>
bool CompositeText8NoAlpha(LSurface *Out, LSurface *In, LFont *Font, int px, int py, LBlitRegions &Clip)
{
	LRgba32 map[256];

	if (!Out || !In || !Font)
		return false;

	// FIXME, do blt clipping here...

	// Create colour map of the foreground/background colours		
	uint8_t *DivLut = Div255Lut;
	LColour fore = Font->Fore();
	LRgb24 fore_px;
	fore_px.r = fore.r();
	fore_px.g = fore.g();
	fore_px.b = fore.b();
	if (Font->Transparent())
	{
		for (int a=0; a<256; a++)
		{
			map[a].r = DivLut[a * fore_px.r];
			map[a].g = DivLut[a * fore_px.g];
			map[a].b = DivLut[a * fore_px.b];
			map[a].a = a;
		}
	}
	else
	{
		LColour back = Font->Back();
		LRgb24 back_px;
		back_px.r = back.r();
		back_px.g = back.g();
		back_px.b = back.b();

		for (int a=0; a<256; a++)
		{
			int oma = 255 - a;
			map[a].r = DivLut[(oma * back_px.r) + (a * fore_px.r)];
			map[a].g = DivLut[(oma * back_px.g) + (a * fore_px.g)];
			map[a].b = DivLut[(oma * back_px.b) + (a * fore_px.b)];
			map[a].a = 255;
		}
	}

	uint8_t *StartOfBuffer = (*Out)[0];
	uint8_t *EndOfBuffer = StartOfBuffer + (Out->GetRowStep() * Out->Y());

	for (int y=Clip.SrcClip.y1; y<=Clip.SrcClip.y2; y++)
	{
		OutPx *dst = (OutPx*) (*Out)[py + y];
		if (!dst)
			continue;
		dst += Clip.DstClip.x1;
		if ((uint8_t*)dst < StartOfBuffer)
			continue;
		
		uint8_t *i = (*In)[y];
		if (!i) return false;
		uint8_t *e = i + Clip.DstClip.X();
		LRgba32 *src;

		LAssert((uint8_t*)dst >= StartOfBuffer);
		
		if (Font->Transparent())
		{
			uint8_t a, oma;
			
			while (i < e)
			{
				// Alpha blend map and output pixel
				a = *i++;
				src = map + a;
				switch (a)
				{
					case 0:
						break;
					case 255:
						// Copy
						dst->r = src->r;
						dst->g = src->g;
						dst->b = src->b;
						break;
					default:
						// Blend
						OverPm32toPm24(src, dst);
						break;
				}
				dst++;
			}
		}
		else
		{
			while (i < e)
			{
				// Copy rop
				src = map + *i++;
				dst->r = src->r;
				dst->g = src->g;
				dst->b = src->b;
				dst++;
			}
		}

		LAssert((uint8_t*)dst <= EndOfBuffer);
	}
	
	return true;
}

template<typename OutPx>
bool CompositeText5NoAlpha(LSurface *Out, LSurface *In, LFont *Font, int px, int py, LBlitRegions &Clip)
{
	OutPx map[256];

	if (!Out || !In || !Font)
		return false;

	// FIXME, do blt clipping here...
	#define MASK_5BIT 0x1f
	#define MASK_6BIT 0x3f

	// Create colour map of the foreground/background colours		
	uint8_t *Div255 = Div255Lut;
	LColour fore = Font->Fore();
	LRgb24 fore_px;
	fore_px.r = fore.r();
	fore_px.g = fore.g();
	fore_px.b = fore.b();
	if (Font->Transparent())
	{
		for (int a=0; a<256; a++)
		{
			map[a].r = (int)Div255[a * fore_px.r] >> 3;
			map[a].g = (int)Div255[a * fore_px.g] >> 2;
			map[a].b = (int)Div255[a * fore_px.b] >> 3;
		}
	}
	else
	{
		LColour back = Font->Back();
		LRgb24 back_px;
		back_px.r = back.r();
		back_px.g = back.g();
		back_px.b = back.b();

		for (int a=0; a<256; a++)
		{
			int oma = 255 - a;
			map[a].r = Div255[(oma * back_px.r) + (a * fore_px.r)] >> 3;
			map[a].g = Div255[(oma * back_px.g) + (a * fore_px.g)] >> 2;
			map[a].b = Div255[(oma * back_px.b) + (a * fore_px.b)] >> 3;
		}
	}

	uint8_t *StartOfBuffer = (*Out)[0];
	uint8_t *EndOfBuffer = StartOfBuffer + (Out->GetRowStep() * Out->Y());

	for (unsigned y=Clip.SrcClip.y1; y<=Clip.SrcClip.y2; y++)
	{
		OutPx *dst = ((OutPx*) (*Out)[py + y]);
		if (!dst)
			continue;
		dst += Clip.DstClip.x1;
		uint8_t *i = (*In)[y];
		if (!i) return false;
		uint8_t *e = i + Clip.DstClip.X();

		LAssert((uint8_t*)dst >= StartOfBuffer);
		
		if (Font->Transparent())
		{
			uint8_t a;
			OutPx *src;
			
			while (i < e)
			{
				// Alpha blend map and output pixel
				a = *i++;
				switch (a)
				{
					case 0:
						break;
					case 255:
						// Copy
						*dst = map[a];
						break;
					default:
					{
						// Blend
						#if 0
						uint8_t oma = 255 - a;
						src = map + a;
						LRgb24 d = { G5bitTo8bit(dst->r),
									 G6bitTo8bit(dst->g),
									 G5bitTo8bit(dst->b)};
						LRgb24 s = { G5bitTo8bit(src->r),
									 G6bitTo8bit(src->g),
									 G5bitTo8bit(src->b)};
						dst->r = Div255[(oma * d.r) + (a * s.r)] >> 3;
						dst->g = Div255[(oma * d.g) + (a * s.g)] >> 2;
						dst->b = Div255[(oma * d.b) + (a * s.b)] >> 3;
						#else
						uint8_t a5 = a >> 3;
						uint8_t a6 = a >> 2;
						uint8_t oma5 = MASK_5BIT - a5;
						uint8_t oma6 = MASK_6BIT - a6;
						src = map + a;
						dst->r = ((oma5 * (uint8_t)dst->r) + (a5 * (uint8_t)src->r)) / MASK_5BIT;
						dst->g = ((oma6 * (uint8_t)dst->g) + (a6 * (uint8_t)src->g)) / MASK_6BIT;
						dst->b = ((oma5 * (uint8_t)dst->b) + (a5 * (uint8_t)src->b)) / MASK_5BIT;
						#endif
						break;
					}
				}
				dst++;
			}
		}
		else
		{
			while (i < e)
			{
				// Copy rop
				*dst++ = map[*i++];
			}
		}
		
		LAssert((uint8_t*)dst <= EndOfBuffer);
	}
	
	return true;
}

#endif

void LDisplayString::Draw(LSurface *pDC, int px, int py, LRect *r, bool Debug)
{
	Layout();

	#if DISPLAY_STRING_FRACTIONAL_NATIVE

	// GTK / Mac use fractional pixels, so call the fractional version:
	LRect rc;
	if (r)
	{
		rc = *r;
		rc.x1 <<= FShift;
		rc.y1 <<= FShift;
		#if defined(MAC) && !defined(__GTK_H__)
		rc.x2 <<= FShift;
		rc.y2 <<= FShift;
		#else
		rc.x2 = (rc.x2 + 1) << FShift;
		rc.y2 = (rc.y2 + 1) << FShift;
		#endif
	}
	
	FDraw(pDC, px << FShift, py << FShift, r ? &rc : NULL, Debug);
	
	#elif defined(HAIKU)
	
	if (!Font || !pDC)
	{
		LgiTrace("%s:%i - No ptr: %p/%p.\n", _FL, Font, pDC);
		return;
	}
	
	BFont *fnt = Font->Handle();
	BView *view = pDC->Handle();
	if (!fnt || !view)
	{
		LgiTrace("%s:%i - No handle: %p/%p(%s).\n", _FL, fnt, view, pDC->GetClass());
		return;
	}
	
	if (!Info.Length())
	{
		LgiTrace("%s:%i - No layout.\n", _FL);
		return;
	}
	
	view->SetHighColor(Font->Fore());
	
	font_height height = {0};
	fnt->GetHeight(&height);

	auto &i = Info[0];
	view->SetFont(fnt);
	view->DrawString(i.Str, i.Len, BPoint(px, py + height.ascent));	
	
	#elif defined(LGI_SDL)
	
	if (Img && pDC && pDC->Y() > 0 && (*pDC)[0])
	{
		int Ox = 0, Oy = 0;
		pDC->GetOrigin(Ox, Oy);
		LBlitRegions Clip(pDC, px-Ox, py-Oy, Img, r);
		LColourSpace DstCs = pDC->GetColourSpace();
		switch (DstCs)
		{
			#define DspStrCase(px_fmt, comp)											\
				case Cs##px_fmt:														\
					CompositeText##comp<G##px_fmt>(pDC, Img, Font, px-Ox, py-Oy, Clip);	\
					break;
			
			DspStrCase(Rgb16, 5NoAlpha)
			DspStrCase(Bgr16, 5NoAlpha)

			DspStrCase(Rgb24, 8NoAlpha)
			DspStrCase(Bgr24, 8NoAlpha)
			DspStrCase(Rgbx32, 8NoAlpha)
			DspStrCase(Bgrx32, 8NoAlpha)
			DspStrCase(Xrgb32, 8NoAlpha)
			DspStrCase(Xbgr32, 8NoAlpha)
			DspStrCase(Rgba32, 8Alpha)
			DspStrCase(Bgra32, 8Alpha)
			DspStrCase(Argb32, 8Alpha)
			DspStrCase(Abgr32, 8Alpha)
			default:
				LgiTrace("%s:%i - LDisplayString::Draw Unsupported colour space.\n", _FL);
				// LAssert(!"Unsupported colour space.");
				break;
			
			#undef DspStrCase
		}
	}
	else
		LgiTrace("::Draw argument error.\n");

	#elif defined WINNATIVE
	
	if (Info.Length() && pDC && Font)
	{
		LFontSystem *Sys = LFontSystem::Inst();
		COLOUR Old = pDC->Colour();
		int TabSize = Font->TabSize() ? Font->TabSize() : 32;
		int Ox = px;
		LColour cFore = Font->Fore();
		LColour cBack = Font->Back();
		LColour cWhitespace;

		if (VisibleTab)
		{
			cWhitespace = Font->WhitespaceColour();
			LAssert(cWhitespace.IsValid());
		}

		for (int i=0; i<Info.Length(); i++)
		{
			LFont *f = 0;

			// Get the font for this block of characters
			if (Info[i].FontId)
			{
				f = Sys->Font[Info[i].FontId];

				f->Colour(cFore, cBack);

				auto Sz = Font->Size();
				Sz.Value += Info[i].SizeDelta;
				f->Size(Sz);
				f->Transparent(Font->Transparent());
				f->Underline(Font->Underline());
				if (!f->Handle())
				{
					f->Create();
				}
			}
			else
			{
				f = Font;
			}

			if (f)
			{
				LRect b;
				if (r)
				{
					b.x1 = i ? px : r->x1;
					b.y1 = r->y1;
					b.x2 = i < Info.Length() - 1 ? px + Info[i].X - 1 : r->x2;
					b.y2 = r->y2;
				}
				else
				{
					b.x1 = px;
					b.y1 = py;
					b.x2 = px + Info[i].X - 1;
					b.y2 = py + Y() - 1;
				}
				
				if (b.Valid())
				{
					if (IsTabChar(*Info[i].Str))
					{
						// Invisible tab... draw blank space
						if (!Font->Transparent())
						{
							pDC->Colour(cBack);
							pDC->Rectangle(&b);
						}

						if (VisibleTab)
						{
							int X = px;
							for (int n=0; n<Info[i].Len; n++)
							{
								int Dx = TabSize - ((X - Ox + GetDrawOffset()) % TabSize);
								LRect r(X, b.y1, X + Dx - 1, b.y2);
								pDC->Colour(cWhitespace);
								DrawWhiteSpace(pDC, '\t', r);
								X += Dx;
							}
						}
					}
					else
					{
						// Draw the character(s)
						LColour Fg = f->Fore();
						LAssert(Fg.IsValid());
						f->_Draw(pDC, px, py, Info[i].Str, Info[i].Len, &b, Fg);
						
						if (VisibleTab)
						{
							OsChar *start = Info[i].Str;
							OsChar *s = start;
							OsChar *e = s + Info[i].Len;
							int Sp = -1;
							while (s < e)
							{
								if (*s == ' ')
								{
									int Sx, Sy;
									if (Sp < 0) f->_Measure(Sp, Sy, s, 1);
									f->_Measure(Sx, Sy, start, (int)(s - start));
									LRect r(0, 0, Sp-1, Sy-1);
									r.Offset(px + Sx, py);
									pDC->Colour(cWhitespace);
									DrawWhiteSpace(pDC, ' ', r);
								}
								s++;
							}
						}
					}
				}
			}

			// Inc my position
			px += Info[i].X;
		}
		
		if (AppendDots)
		{
			int Sx, Sy;
			Font->_Measure(Sx, Sy, GDisplayStringDots, 3);

			LRect b;
			if (r)
			{
				b.x1 = px;
				b.y1 = r->y1;
				b.x2 = min(px + Sx - 1, r->x2);
				b.y2 = r->y2;
			}
			else
			{
				b.x1 = px;
				b.y1 = py;
				b.x2 = px + Sx - 1;
				b.y2 = py + Y() - 1;
			}

			LColour Fg = Font->Fore();
			Font->_Draw(pDC, px, py, GDisplayStringDots, 3, &b, Fg);
		}

		pDC->Colour(Old);
	}
	else if (r &&
			Font &&
			!Font->Transparent())
	{
		pDC->Colour(Font->Back());
		pDC->Rectangle(r);
	}
	
	#endif
}

int LDisplayString::GetDrawOffsetF()
{
	return DrawOffsetF;
}

void LDisplayString::SetDrawOffsetF(int Fpx)
{
	if (LaidOut)
		LAssert(!"No point setting TabOrigin after string is laid out.\n");
	DrawOffsetF = Fpx;
}

int LDisplayString::FX()
{
	Layout();
	return xf;
}

int LDisplayString::FY()
{
	Layout();
	return y;
}

LPoint LDisplayString::FSize()
{
	Layout();
	return LPoint(xf, yf);
}

void LDisplayString::FDraw(LSurface *pDC, int fx, int fy, LRect *frc, bool Debug)
{
	Layout(Debug);

	#if !DISPLAY_STRING_FRACTIONAL_NATIVE

		// Windows doesn't use fractional pixels, so call the integer version:
		LRect rc;
		if (frc)
		{
			rc = *frc;
			rc.x1 >>= FShift;
			rc.y1 >>= FShift;
			rc.x2 >>= FShift;
			rc.y2 >>= FShift;
		}
		
		Draw(pDC, fx >> FShift, fy >> FShift, frc ? &rc : NULL, Debug);

	#elif defined __GTK_H__
	
		Gtk::cairo_t *cr = pDC->Handle();
		if (!cr)
		{
			LAssert(!"Can't get cairo.");
			return;
		}

		pango_context_set_font_description(LFontSystem::Inst()->GetContext(), Font->Handle());
		cairo_save(cr);

		LColour b = Font->Back();
		double Dx = ((double)fx / FScale);
		double Dy = ((double)fy / FScale);
		double rx, ry, rw, rh;
		if (!Font->Transparent())
		{
			// Background fill
			cairo_set_source_rgb(cr, (double)b.r()/255.0, (double)b.g()/255.0, (double)b.b()/255.0); cairo_new_path(cr);
			if (frc)
			{
				rx = ((double)frc->x1 / FScale);
				ry = ((double)frc->y1 / FScale);
				rw = (double)frc->X() / FScale;
				rh = (double)frc->Y() / FScale;
			}
			else
			{
				rx = Dx;
				ry = Dy;
				rw = x;
				rh = y;
			}

			cairo_rectangle(cr, rx, ry, rw, rh);
			cairo_fill(cr);
			if (frc)
			{
				cairo_rectangle(cr, rx, ry, rw, rh);
				cairo_clip(cr);
			}
		}
		else if (frc)
		{
			rx = ((double)frc->x1 / FScale);
			ry = ((double)frc->y1 / FScale);
			rw = (double)frc->X() / FScale;
			rh = (double)frc->Y() / FScale;

			cairo_rectangle(cr, rx, ry, rw, rh);
			cairo_clip(cr);
		}

		cairo_translate(cr, Dx, Dy);

		LColour f = Font->Fore();
		for (auto &b: d->Blocks)
		{
			double Bx = ((double)b.X()) / FScale;
			
			#if DEBUG_BOUNDS
			double By = Font->GetHeight();
			cairo_set_source_rgb(cr, 1.0, 0.0, 0.0);
			cairo_rectangle(cr, 0, 0, Bx, By);
			cairo_rectangle(cr, 1, 1, Bx - 2.0, By - 2.0);
			cairo_set_fill_rule(cr, Gtk::CAIRO_FILL_RULE_EVEN_ODD);
			cairo_fill(cr);
			#endif
			
			if (b.Hnd)
			{
				cairo_set_source_rgb(	cr,
										(double)f.r()/255.0,
										(double)f.g()/255.0,
										(double)f.b()/255.0);
				pango_cairo_show_layout(cr, b.Hnd);

	    		if (VisibleTab && Str)
	    		{
	    			GUtf8Str Ptr(Str);
	    			auto Ws = Font->WhitespaceColour();
	    			pDC->Colour(Ws);
	    			
	    			for (int32 u, Idx = 0 ; (u = Ptr); Idx++)
	    			{
	    				if (IsTabChar(u) || u == ' ')
	    				{
	    					Gtk::PangoRectangle pos;
	    					Gtk::pango_layout_index_to_pos(b.Hnd, Idx, &pos);
	    					LRect r(0, 0, pos.width / FScale, pos.height / FScale);
	    					r.Offset(pos.x / FScale, pos.y / FScale);
	    					DrawWhiteSpace(pDC, u, r);
	    				}
	    				Ptr++;
	    			}
	    		}
			}
			else if (b.Fnt)
			{
				b.Fnt->Transparent(Font->Transparent());
				b.Fnt->Back(Font->Back());
				b.Fnt->_Draw(pDC, 0, 0, b.Str, b.Bytes, NULL, f);
			}
			else LAssert(0);
			
			cairo_translate(cr, Bx, 0);
		}
		
		cairo_restore(cr);
	
	#elif defined MAC && !defined(LGI_SDL)

		int Ox = 0, Oy = 0;
		int px = fx >> FShift;
		int py = fy >> FShift;
		LRect rc;
		if (frc)
			rc.Set(	frc->x1 >> FShift,
					frc->y1 >> FShift,
					frc->x2 >> FShift,
					frc->y2 >> FShift);

		if (pDC && !pDC->IsScreen())
			pDC->GetOrigin(Ox, Oy);

		if (pDC && !Font->Transparent())
		{
			LColour Old = pDC->Colour(Font->Back());
			if (frc)
			{
				pDC->Rectangle(&rc);
			}
			else
			{
				LRect a(px, py, px + x - 1, py + y - 1);
				pDC->Rectangle(&a);
			}
			pDC->Colour(Old);
		}
		
		if (Hnd && pDC && StrWords > 0)
		{
			OsPainter				dc = pDC->Handle();

			#if USE_CORETEXT

				int y = (pDC->Y() - py + Oy);

				CGContextSaveGState(dc);
				pDC->Colour(Font->Fore());
			
				if (pDC->IsScreen())
				{
					if (frc)
					{
						CGRect rect = rc;
						rect.size.width += 1.0;
						rect.size.height += 1.0;
						CGContextClipToRect(dc, rect);
					}
					CGContextTranslateCTM(dc, 0, pDC->Y()-1);
					CGContextScaleCTM(dc, 1.0, -1.0);
				}
				else
				{
					if (frc)
					{
						CGContextSaveGState(dc);
						
						CGRect rect = rc;
						rect.origin.x -= Ox;
						rect.origin.y = pDC->Y() - rect.origin.y + Oy - rect.size.height;
						rect.size.width += 1.0;
						rect.size.height += 1.0;
						CGContextClipToRect(dc, rect);
					}
				}

				CGFloat Tx = (CGFloat)fx / FScale - Ox;
				CGFloat Ty = (CGFloat)y - Font->Ascent();
				CGContextSetTextPosition(dc, Tx, Ty);

				CTLineDraw(Hnd, dc);

				CGContextRestoreGState(dc);

			#else

				ATSUAttributeTag		Tags[1] = {kATSUCGContextTag};
				ByteCount				Sizes[1] = {sizeof(CGContextRef)};
				ATSUAttributeValuePtr	Values[1] = {&dc};

				e = ATSUSetLayoutControls(Hnd, 1, Tags, Sizes, Values);
				if (e)
				{
					printf("%s:%i - ATSUSetLayoutControls failed (e=%i)\n", _FL, (int)e);
				}
				else
				{
					// Set style attr
					ATSURGBAlphaColor c;
					LColour Fore = Font->Fore();
					c.red	= (double) Fore.r() / 255.0;
					c.green = (double) Fore.g() / 255.0;
					c.blue	= (double) Fore.b() / 255.0;
					c.alpha = 1.0;
					
					ATSUAttributeTag Tags[]			= {kATSURGBAlphaColorTag};
					ATSUAttributeValuePtr Values[]	= {&c};
					ByteCount Lengths[]				= {sizeof(c)};
					
					e = ATSUSetAttributes(	Font->Handle(),
											CountOf(Tags),
											Tags,
											Lengths,
											Values);
					if (e)
					{
						printf("%s:%i - Error setting font attr (e=%i)\n", _FL, (int)e);
					}
					else
					{
						int y = (pDC->Y() - py + Oy);

						if (pDC->IsScreen())
						{
							CGContextSaveGState(dc);

							if (frc)
							{
								CGRect rect = rc;
								rect.size.width += 1.0;
								rect.size.height += 1.0;
								CGContextClipToRect(dc, rect);
							}
							CGContextTranslateCTM(dc, 0, pDC->Y()-1);
							CGContextScaleCTM(dc, 1.0, -1.0);

							e = ATSUDrawText(Hnd, kATSUFromTextBeginning, kATSUToTextEnd, fx - Long2Fix(Ox), Long2Fix(y) - fAscent);

							CGContextRestoreGState(dc);
						}
						else
						{
							if (frc)
							{
								CGContextSaveGState(dc);
								
								CGRect rect = rc;
								rect.origin.x -= Ox;
								rect.origin.y = pDC->Y() - rect.origin.y + Oy - rect.size.height;
								rect.size.width += 1.0;
								rect.size.height += 1.0;
								CGContextClipToRect(dc, rect);
							}
							
							e = ATSUDrawText(Hnd, kATSUFromTextBeginning, kATSUToTextEnd, Long2Fix(px - Ox), Long2Fix(y) - fAscent);
							
							if (frc)
								CGContextRestoreGState(dc);
						}
						if (e)
						{
							char *a = 0;
							StringConvert(a, NULL, Str, len);
							printf("%s:%i - ATSUDrawText failed with %i, len=%i, str=%.20s\n", _FL, (int)e, len, a);
							DeleteArray(a);
						}
					}
				}
			#endif
		}
	
	#endif
}

