diff --git a/Ide/win/LgiIde.vcxproj b/Ide/win/LgiIde.vcxproj --- a/Ide/win/LgiIde.vcxproj +++ b/Ide/win/LgiIde.vcxproj @@ -1,278 +1,278 @@  Debug Win32 Debug x64 Release Win32 Release x64 LgiIde {967AF7EF-7C17-4FCC-997E-BED17F886F07} LgiIde_vc8 Win32Proj 10.0 Application v142 Unicode true Application v142 Unicode true Application v142 Unicode Application v142 Unicode <_ProjectFileVersion>12.0.30501.0 $(Platform)$(Configuration)14\ $(Platform)$(Configuration)14\ true $(VC_IncludePath);$(WindowsSDK_IncludePath);..\..\..\..\..\CodeLib\libpng-1.6.26 $(Platform)$(Configuration)14\ $(Platform)$(Configuration)14\ true $(VC_IncludePath);$(WindowsSDK_IncludePath);..\..\..\..\..\CodeLib\libpng-1.6.26 $(Platform)$(Configuration)14\ $(Platform)$(Configuration)14\ false $(VC_IncludePath);$(WindowsSDK_IncludePath);..\..\..\..\..\CodeLib\libpng-1.6.26 $(Platform)$(Configuration)14\ $(Platform)$(Configuration)14\ false $(VC_IncludePath);$(WindowsSDK_IncludePath);..\..\..\..\..\CodeLib\libpng-1.6.26 Disabled ..\..\include;..\..\include\lgi\win;..\..\..\..\..\CodeLib\zlib;..\..\..\..\..\CodeLib\libpng;..\..\..\..\..\CodeLib\libpng\win32;..\..\..\..\..\CodeLib\zlib\win32;..\resources;C:\Program Files (x86)\OpenSSL-Win32\include;%(AdditionalIncludeDirectories) WIN32;_DEBUG;WINDOWS;%(PreprocessorDefinitions) true EnableFastChecks MultiThreadedDebugDLL Level2 ProgramDatabase imm32.lib;Mincore.lib;%(AdditionalDependencies) $(OutDir)$(TargetFileName) ..\lib;%(AdditionalLibraryDirectories) true Windows false MachineX86 Disabled ..\..\include;..\..\include\lgi\win;..\..\..\..\..\CodeLib\zlib;..\..\..\..\..\CodeLib\libpng;..\..\..\..\..\CodeLib\libpng\win32;..\..\..\..\..\CodeLib\zlib\win32;..\resources;C:\Program Files\OpenSSL-Win64\include;%(AdditionalIncludeDirectories) WIN32;_DEBUG;WINDOWS;%(PreprocessorDefinitions) EnableFastChecks MultiThreadedDebugDLL Level2 ProgramDatabase imm32.lib;%(AdditionalDependencies) $(OutDir)$(TargetFileName) ..\lib;%(AdditionalLibraryDirectories) true Windows false ..\..\src\win\General\Lgi.manifest ..\..\include;..\..\include\lgi\win;..\..\..\..\..\CodeLib\zlib;..\..\..\..\..\CodeLib\libpng;..\..\..\..\..\CodeLib\libpng\win32;..\..\..\..\..\CodeLib\zlib\win32;.\Resources;C:\Program Files (x86)\OpenSSL-Win32\include;%(AdditionalIncludeDirectories) WIN32;NDEBUG;WINDOWS;%(PreprocessorDefinitions) MultiThreadedDLL Level2 ProgramDatabase imm32.lib;Mincore.lib;%(AdditionalDependencies) $(OutDir)$(TargetFileName) ..\lib;%(AdditionalLibraryDirectories) true Windows true true false MachineX86 ..\..\include;..\..\include\lgi\win;..\..\..\..\..\CodeLib\zlib;..\..\..\..\..\CodeLib\libpng;..\..\..\..\..\CodeLib\libpng\win32;..\..\..\..\..\CodeLib\zlib\win32;..\resources;C:\Program Files\OpenSSL-Win64\include;%(AdditionalIncludeDirectories) WIN32;NDEBUG;WINDOWS;%(PreprocessorDefinitions) MultiThreadedDLL Level2 ProgramDatabase imm32.lib;%(AdditionalDependencies) $(OutDir)$(TargetFileName) ..\lib;%(AdditionalLibraryDirectories) true Windows true true false ..\..\src\win\General\Lgi.manifest - + {95df9ca4-6d37-4a85-a648-80c2712e0da1} false true false true \ No newline at end of file diff --git a/include/lgi/common/DisplayString.h b/include/lgi/common/DisplayString.h --- a/include/lgi/common/DisplayString.h +++ b/include/lgi/common/DisplayString.h @@ -1,307 +1,299 @@ #ifndef _GDISPLAY_STRING_H_ #define _GDISPLAY_STRING_H_ #include "lgi/common/Font.h" #if defined(LGI_SDL) #elif defined(LINUX) namespace Pango { #include "pango/pango.h" #include "pango/pangocairo.h" } #endif #if !defined(_MSC_VER) || defined(__GTK_H__) #define LGI_DSP_STR_CACHE 1 #endif /// \brief Cache for text measuring, glyph substitution and painting /// /// To paint text onto the screen several stages need to be implemented to /// properly support unicode on multiple platforms. This class addresses all /// of those needs and then allows you to cache the results to reduce text /// related workload. /// /// The first stage is converting text into the native format for the /// OS's API. This usually involved converting the text to wide characters for /// Linux or Windows, or Utf-8 for Haiku. Then the text is converted into runs of /// characters that can be rendered in the same font. If glyph substitution is /// required to render the characters a separate run is used with a different /// font ID. Finally you can measure or paint these runs of text. Also tab characters /// are expanded to the current tab size setting. class LgiClass LDisplayString { protected: LSurface *pDC; LFont *Font; uint32_t x, y; int xf, yf; int DrawOffsetF; // Flags uint8_t LaidOut : 1; uint8_t AppendDots : 1; uint8_t VisibleTab : 1; /* String data: Str Wide Windows: utf-16 utf-16 MacCarbon: utf-16 utf-32 MacCocoa: utf-16 utf-32 Gtk3: utf-8 utf-32 Haiku: utf-8 utf-32 */ OsChar *Str; ssize_t StrWords; #if LGI_DSP_STR_CACHE wchar_t *Wide; ssize_t WideWords; #endif #if defined(LGI_SDL) LAutoPtr Img; #elif defined(__GTK_H__) struct GDisplayStringPriv *d; friend struct GDisplayStringPriv; #elif defined(MAC) #if USE_CORETEXT CTLineRef Hnd; CFAttributedStringRef AttrStr; void CreateAttrStr(); #else ATSUTextLayout Hnd; ATSUTextMeasurement fAscent; ATSUTextMeasurement fDescent; #endif #elif defined(WINNATIVE) || defined(HAIKU) class CharInfo { public: - OsChar *Str; - int Len; - int X; - uint8_t FontId; - int8 SizeDelta; - - CharInfo() - { - Str = 0; - Len = 0; - X = 0; - FontId = 0; - SizeDelta = 0; - } + OsChar *Str = NULL; + int Len = 0; + int X = 0; + uint8_t FontId = 0; + int8_t SizeDelta = 0; + uint8_t Missing = false; uint32_t First() { auto s = (const uint16*)Str; ssize_t l = Len * sizeof(*Str); return LgiUtf16To32(s, l); } }; LArray Info; #endif void Layout(bool Debug = false); void DrawWhiteSpace(LSurface *pDC, char Ch, LRect &r); public: // Turn on debug logging... bool _debug = false; /// Constructor LDisplayString ( /// The base font. Must not be destroyed during the lifetime of this object. LFont *f, /// Utf-8 input string const char *s, /// Number of bytes in the input string or -1 for NULL terminated. ssize_t l = -1, LSurface *pdc = 0 ); /// Constructor LDisplayString ( /// The base font. Must not be destroyed during the lifetime of this object. LFont *f, /// A wide character input string const char16 *s, /// The number of characters in the input string (NOT the number of bytes) or -1 for NULL terminated ssize_t l = -1, LSurface *pdc = 0 ); #ifdef _MSC_VER /// Constructor LDisplayString ( /// The base font. Must not be destroyed during the lifetime of this object. LFont *f, /// A wide character input string const uint32_t *s, /// The number of characters in the input string (NOT the number of bytes) or -1 for NULL terminated ssize_t l = -1, LSurface *pdc = 0 ); #endif virtual ~LDisplayString(); LDisplayString &operator=(const LDisplayString &s) { LAssert(0); return *this; } /// \returns the draw offset used to calculate tab spacing (in Px) int GetDrawOffset(); /// Sets the draw offset used to calculate tab spacing (in Px) void SetDrawOffset(int Px); /// Returns the ShowVisibleTab setting. /// Treats Unicode-2192 (left arrow) as a tab char bool ShowVisibleTab(); /// Sets the ShowVisibleTab setting. /// Treats Unicode-2192 (left arrow) as a tab char void ShowVisibleTab(bool i); /// \returns the font used to create the layout LFont *GetFont() { return Font; }; /// Fits string into 'width' pixels, truncating with '...' if it's not going to fit void TruncateWithDots ( /// The number of pixels the string has to fit int Width ); /// Returns true if the string is trucated bool IsTruncated(); /// Returns the words (not chars) in the OsChar string ssize_t Length(); /* /// Sets the number of words in the OsChar string void Length(int NewLen); */ /// Returns the pointer to the native string operator const char16*() { #if LGI_DSP_STR_CACHE return Wide; #else return Str; #endif } /// \returns the number of words in the wide string ssize_t WideLength() { #if LGI_DSP_STR_CACHE return WideWords; #else return StrWords; #endif } // API that use full pixel sizes: /// Returns the width of the whole string int X(); /// Returns the height of the whole string int Y(); /// Returns the width and height of the whole string LPoint Size(); /// Returns the number of characters that fit in 'x' pixels. ssize_t CharAt(int x, LPxToIndexType Type = LgiTruncate); /// Draws the string onto a device surface void Draw ( /// The output device LSurface *pDC, /// The x coordinate of the top left corner of the output box int x, /// The y coordinate of the top left corner of the output box int y, /// An optional clipping rectangle. If the font is not transparent this rectangle will be filled with the background colour. LRect *r = NULL, /// Turn on debug logging bool Debug = false ); /// Draws the string onto a device surface void DrawCenter ( /// The output device LSurface *pDC, /// An rectangle to center in. If the font is not transparent this rectangle will be filled with the background colour. LRect *r ) { if (r) Draw(pDC, r->x1 + ((r->X() - X()) >> 1), r->y1 + ((r->Y() - Y()) >> 1), r); } // API that uses fractional pixel sizes. enum { #if defined LGI_SDL FShift = 6, FScale = 1 << FShift, #elif defined __GTK_H__ /// This is the value for 1 pixel. FScale = PANGO_SCALE, /// This is bitwise shift to convert between integer and fractional FShift = 10 #elif defined MAC /// This is the value for 1 pixel. FScale = 0x10000, /// This is bitwise shift to convert between integer and fractional FShift = 16 #else /// This is the value for 1 pixel. FScale = 1, /// This is bitwise shift to convert between integer and fractional FShift = 0 #endif }; /// \returns the draw offset used to calculate tab spacing (in fractional units) int GetDrawOffsetF(); /// Sets the draw offset used to calculate tab spacing (in fractional units) void SetDrawOffsetF(int Fpx); /// \returns the fractional width of the string int FX(); /// \returns the fractional height of the string int FY(); /// \returns both the fractional width and height of the string LPoint FSize(); /// Draws the string using fractional co-ordinates. void FDraw ( /// The output device LSurface *pDC, /// The fractional x coordinate of the top left corner of the output box int fx, /// The fractional y coordinate of the top left corner of the output box int fy, /// [Optional] fractional clipping rectangle. If the font is not transparent /// this rectangle will be filled with the background colour. LRect *frc = NULL, /// [Optional] print debug info bool Debug = false ); }; #endif diff --git a/src/common/Gdc2/Font/DisplayString.cpp b/src/common/Gdc2/Font/DisplayString.cpp --- a/src/common/Gdc2/Font/DisplayString.cpp +++ b/src/common/Gdc2/Font/DisplayString.cpp @@ -1,2293 +1,2299 @@ ////////////////////////////////////////////////////////////////////// // Includes #include #include #include #include #ifdef HAIKU #include #endif #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 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; iStrWords) { 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 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 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(LGI_SDL) FT_Face Fnt = Font->Handle(); FT_Error error; if (!Fnt || !Str) return; // Create an array of glyph indexes LArray 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; iglyph->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; iglyph); 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= 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; iTabSize()) << 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(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; } int tabSize = Font->TabSize() ? Font->TabSize() : 32; font_height height = {0}; fnt->GetHeight(&height); yf = y = height.ascent + height.descent + height.leading; if (!Str) return; LUtf8Ptr start(Str); int isTab = -1; for (LUtf8Ptr p(Str); true; p++) { int32_t ch = p; if (isTab < 0) { isTab = IsTabChar(ch); } else if (!ch || IsTabChar(ch) ^ isTab) { auto &l = Info.New(); l.Str = start.GetPtr(); l.Len = p.GetPtr() - start.GetPtr(); const char *strArr[] = { l.Str }; const int32 strLen[] = { l.Len }; float width[1] = { 0 }; fnt->GetStringWidths(strArr, strLen, 1, width); if (isTab) { // Handle tab(s) for (int t=0; tHandle()) 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 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 + LFont *n = u32 > ' ' && 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) + auto &CurInfo = Info[i]; + CurInfo.Len = (int) (u.Get() - CurInfo.Str); + if (CurInfo.Len) { if (WasTab) { // Handle tab(s) - for (int t=0; tGetHeight() > Font->GetHeight())) { - Info[i].SizeDelta = -1; - f->PointSize(Font->PointSize() + Info[i].SizeDelta); + CurInfo.SizeDelta = -1; + f->PointSize(Font->PointSize() + CurInfo.SizeDelta); f->Create(); } #endif - if (!f) + if (u32 && !f) { // no font, so ? out the chars... as they aren't available anyway - // printf("Font Cache Miss, Len=%i\n\t", Info[i].Len); + LgiTrace("MissingGlyph: %i 0x%x\n", u32, u32); + CurInfo.Missing = true; + #if 0 + for (int n=0; n_Measure(sx, sy, Info[i].Str, Info[i].Len); - x += Info[i].X = sx > 0xffff ? 0xffff : sx; + + m->_Measure(sx, sy, CurInfo.Str, CurInfo.Len); + x += CurInfo.X = sx > 0xffff ? 0xffff : sx; } - auto Ch = Info[i].First(); - Info[i].FontId = !f || Font == f ? 0 : Sys->Lut[Ch]; + auto Ch = CurInfo.First(); + CurInfo.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= 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) { LUtf8Str 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); LUtf8Str 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(LGI_SDL) LAssert(!"Impl me"); #elif defined(MAC) 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] && iTabSize() ? 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= 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= 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(); } } #endif 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; } } 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; } 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 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 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 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; } font_height height = {0}; fnt->GetHeight(&height); /* if (_debug) printf(" trans=%i height=%g,%g,%g\n", Font->Transparent(), height.ascent, height.descent, height.leading); */ if (!Font->Transparent()) { view->SetHighColor(Font->Back()); if (r) view->FillRect(*r); else view->FillRect(BRect(px, py, px+x, py+y)); } auto locked = view->LockLooper(); view->SetFont(fnt); view->SetHighColor(Font->Fore()); int cx = px; for (auto &i: Info) { /* if (_debug) printf(" info=%.*s c=%i,%i\n", i.Len, i.Str, cx, py); */ view->DrawString(i.Str, i.Len, BPoint(cx, py + height.ascent)); cx += i.X; } BRegion region; view->GetClippingRegion(®ion); if (locked) view->UnlockLooper(); #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(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; iFont[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 (Info[i].Missing) + f->Colour(LColour::Red.Mix(cFore), cBack); } 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; nColour(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 yf; } 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) { LUtf8Str 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 } diff --git a/LgiTest/Mac/Info.plist b/test/GuiTest/Mac/Info.plist rename from LgiTest/Mac/Info.plist rename to test/GuiTest/Mac/Info.plist diff --git a/LgiTest/Mac/LgiTest.xcodeproj/project.pbxproj b/test/GuiTest/Mac/LgiTest.xcodeproj/project.pbxproj rename from LgiTest/Mac/LgiTest.xcodeproj/project.pbxproj rename to test/GuiTest/Mac/LgiTest.xcodeproj/project.pbxproj diff --git a/LgiTest/Mac/LgiTest.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/test/GuiTest/Mac/LgiTest.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename from LgiTest/Mac/LgiTest.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to test/GuiTest/Mac/LgiTest.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/LgiTest/Main.cpp b/test/GuiTest/Main.cpp rename from LgiTest/Main.cpp rename to test/GuiTest/Main.cpp diff --git a/LgiTest/Test.sln b/test/GuiTest/Test.sln rename from LgiTest/Test.sln rename to test/GuiTest/Test.sln diff --git a/LgiTest/Test.vcxproj b/test/GuiTest/Test.vcxproj rename from LgiTest/Test.vcxproj rename to test/GuiTest/Test.vcxproj diff --git a/LgiTest/Test.vcxproj.filters b/test/GuiTest/Test.vcxproj.filters rename from LgiTest/Test.vcxproj.filters rename to test/GuiTest/Test.vcxproj.filters diff --git a/HtmlEditor/CMakeLists.txt b/test/HtmlEditor/CMakeLists.txt rename from HtmlEditor/CMakeLists.txt rename to test/HtmlEditor/CMakeLists.txt diff --git a/HtmlEditor/HtmlEdit.sln b/test/HtmlEditor/HtmlEdit.sln rename from HtmlEditor/HtmlEdit.sln rename to test/HtmlEditor/HtmlEdit.sln diff --git a/HtmlEditor/HtmlEdit.vcxproj b/test/HtmlEditor/HtmlEdit.vcxproj rename from HtmlEditor/HtmlEdit.vcxproj rename to test/HtmlEditor/HtmlEdit.vcxproj diff --git a/HtmlEditor/HtmlEdit.vcxproj.filters b/test/HtmlEditor/HtmlEdit.vcxproj.filters rename from HtmlEditor/HtmlEdit.vcxproj.filters rename to test/HtmlEditor/HtmlEdit.vcxproj.filters diff --git a/HtmlEditor/HtmlEditor.xml b/test/HtmlEditor/HtmlEditor.xml rename from HtmlEditor/HtmlEditor.xml rename to test/HtmlEditor/HtmlEditor.xml diff --git a/HtmlEditor/Makefile b/test/HtmlEditor/Makefile rename from HtmlEditor/Makefile rename to test/HtmlEditor/Makefile diff --git a/HtmlEditor/Rich.cpp b/test/HtmlEditor/Rich.cpp rename from HtmlEditor/Rich.cpp rename to test/HtmlEditor/Rich.cpp diff --git a/HtmlEditor/Rich.rc b/test/HtmlEditor/Rich.rc rename from HtmlEditor/Rich.rc rename to test/HtmlEditor/Rich.rc diff --git a/HtmlEditor/Test/Reply.html b/test/HtmlEditor/Test/Reply.html rename from HtmlEditor/Test/Reply.html rename to test/HtmlEditor/Test/Reply.html diff --git a/HtmlEditor/Test/Reply2.html b/test/HtmlEditor/Test/Reply2.html rename from HtmlEditor/Test/Reply2.html rename to test/HtmlEditor/Test/Reply2.html diff --git a/HtmlEditor/TestCases/example.html b/test/HtmlEditor/TestCases/example.html rename from HtmlEditor/TestCases/example.html rename to test/HtmlEditor/TestCases/example.html diff --git a/HtmlEditor/TestCases/hr.html b/test/HtmlEditor/TestCases/hr.html rename from HtmlEditor/TestCases/hr.html rename to test/HtmlEditor/TestCases/hr.html diff --git a/HtmlEditor/TestCases/reply-test.html b/test/HtmlEditor/TestCases/reply-test.html rename from HtmlEditor/TestCases/reply-test.html rename to test/HtmlEditor/TestCases/reply-test.html diff --git a/HtmlEditor/TestCases/sig.html b/test/HtmlEditor/TestCases/sig.html rename from HtmlEditor/TestCases/sig.html rename to test/HtmlEditor/TestCases/sig.html diff --git a/HtmlEditor/TestCases/sig2.html b/test/HtmlEditor/TestCases/sig2.html rename from HtmlEditor/TestCases/sig2.html rename to test/HtmlEditor/TestCases/sig2.html diff --git a/HtmlEditor/icon1.ico b/test/HtmlEditor/icon1.ico rename from HtmlEditor/icon1.ico rename to test/HtmlEditor/icon1.ico diff --git a/HtmlEditor/resource.h b/test/HtmlEditor/resource.h rename from HtmlEditor/resource.h rename to test/HtmlEditor/resource.h diff --git a/HtmlEditor/tools.bmp b/test/HtmlEditor/tools.bmp rename from HtmlEditor/tools.bmp rename to test/HtmlEditor/tools.bmp diff --git a/HtmlTest/CMakeLists.txt b/test/HtmlViewer/CMakeLists.txt rename from HtmlTest/CMakeLists.txt rename to test/HtmlViewer/CMakeLists.txt diff --git a/HtmlTest/HtmlTest.sln b/test/HtmlViewer/HtmlTest.sln rename from HtmlTest/HtmlTest.sln rename to test/HtmlViewer/HtmlTest.sln diff --git a/HtmlTest/HtmlTest.vcxproj b/test/HtmlViewer/HtmlTest.vcxproj rename from HtmlTest/HtmlTest.vcxproj rename to test/HtmlViewer/HtmlTest.vcxproj diff --git a/HtmlTest/HtmlTest.vcxproj.filters b/test/HtmlViewer/HtmlTest.vcxproj.filters rename from HtmlTest/HtmlTest.vcxproj.filters rename to test/HtmlViewer/HtmlTest.vcxproj.filters diff --git a/HtmlTest/HtmlTestSuite.lr8 b/test/HtmlViewer/HtmlTestSuite.lr8 rename from HtmlTest/HtmlTestSuite.lr8 rename to test/HtmlViewer/HtmlTestSuite.lr8 diff --git a/HtmlTest/MacCocoa/Assets.xcassets/AppIcon.appiconset/Contents.json b/test/HtmlViewer/MacCocoa/Assets.xcassets/AppIcon.appiconset/Contents.json rename from HtmlTest/MacCocoa/Assets.xcassets/AppIcon.appiconset/Contents.json rename to test/HtmlViewer/MacCocoa/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/HtmlTest/MacCocoa/Assets.xcassets/Contents.json b/test/HtmlViewer/MacCocoa/Assets.xcassets/Contents.json rename from HtmlTest/MacCocoa/Assets.xcassets/Contents.json rename to test/HtmlViewer/MacCocoa/Assets.xcassets/Contents.json diff --git a/HtmlTest/MacCocoa/Base.lproj/MainMenu.xib b/test/HtmlViewer/MacCocoa/Base.lproj/MainMenu.xib rename from HtmlTest/MacCocoa/Base.lproj/MainMenu.xib rename to test/HtmlViewer/MacCocoa/Base.lproj/MainMenu.xib diff --git a/HtmlTest/MacCocoa/HtmlTest.entitlements b/test/HtmlViewer/MacCocoa/HtmlTest.entitlements rename from HtmlTest/MacCocoa/HtmlTest.entitlements rename to test/HtmlViewer/MacCocoa/HtmlTest.entitlements diff --git a/HtmlTest/MacCocoa/HtmlTest.xcodeproj/project.pbxproj b/test/HtmlViewer/MacCocoa/HtmlTest.xcodeproj/project.pbxproj rename from HtmlTest/MacCocoa/HtmlTest.xcodeproj/project.pbxproj rename to test/HtmlViewer/MacCocoa/HtmlTest.xcodeproj/project.pbxproj diff --git a/HtmlTest/MacCocoa/HtmlTest.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/test/HtmlViewer/MacCocoa/HtmlTest.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename from HtmlTest/MacCocoa/HtmlTest.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to test/HtmlViewer/MacCocoa/HtmlTest.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/HtmlTest/MacCocoa/Info.plist b/test/HtmlViewer/MacCocoa/Info.plist rename from HtmlTest/MacCocoa/Info.plist rename to test/HtmlViewer/MacCocoa/Info.plist diff --git a/HtmlTest/MacCocoa/create_project.py b/test/HtmlViewer/MacCocoa/create_project.py rename from HtmlTest/MacCocoa/create_project.py rename to test/HtmlViewer/MacCocoa/create_project.py diff --git a/HtmlTest/Makefile b/test/HtmlViewer/Makefile rename from HtmlTest/Makefile rename to test/HtmlViewer/Makefile diff --git a/HtmlTest/TestSuite.cpp b/test/HtmlViewer/TestSuite.cpp rename from HtmlTest/TestSuite.cpp rename to test/HtmlViewer/TestSuite.cpp diff --git a/HtmlTest/TestSuite.xml b/test/HtmlViewer/TestSuite.xml rename from HtmlTest/TestSuite.xml rename to test/HtmlViewer/TestSuite.xml diff --git a/HtmlTest/resdefs.h b/test/HtmlViewer/resdefs.h rename from HtmlTest/resdefs.h rename to test/HtmlViewer/resdefs.h diff --git a/tests/UnitTests.sln b/test/UnitTests/UnitTests.sln rename from tests/UnitTests.sln rename to test/UnitTests/UnitTests.sln diff --git a/tests/UnitTests.vcxproj b/test/UnitTests/UnitTests.vcxproj rename from tests/UnitTests.vcxproj rename to test/UnitTests/UnitTests.vcxproj diff --git a/tests/UnitTests.vcxproj.filters b/test/UnitTests/UnitTests.vcxproj.filters rename from tests/UnitTests.vcxproj.filters rename to test/UnitTests/UnitTests.vcxproj.filters diff --git a/tests/src/AutoPtrTest.cpp b/test/UnitTests/src/AutoPtrTest.cpp rename from tests/src/AutoPtrTest.cpp rename to test/UnitTests/src/AutoPtrTest.cpp diff --git a/tests/src/BitsTest.cpp b/test/UnitTests/src/BitsTest.cpp rename from tests/src/BitsTest.cpp rename to test/UnitTests/src/BitsTest.cpp diff --git a/tests/src/ContainerTests.cpp b/test/UnitTests/src/ContainerTests.cpp rename from tests/src/ContainerTests.cpp rename to test/UnitTests/src/ContainerTests.cpp diff --git a/tests/src/CssTest.cpp b/test/UnitTests/src/CssTest.cpp rename from tests/src/CssTest.cpp rename to test/UnitTests/src/CssTest.cpp diff --git a/tests/src/JsonTest.cpp b/test/UnitTests/src/JsonTest.cpp rename from tests/src/JsonTest.cpp rename to test/UnitTests/src/JsonTest.cpp diff --git a/tests/src/MatrixTest.cpp b/test/UnitTests/src/MatrixTest.cpp rename from tests/src/MatrixTest.cpp rename to test/UnitTests/src/MatrixTest.cpp diff --git a/tests/src/RangeTest.cpp b/test/UnitTests/src/RangeTest.cpp rename from tests/src/RangeTest.cpp rename to test/UnitTests/src/RangeTest.cpp diff --git a/tests/src/StringClassTests.cpp b/test/UnitTests/src/StringClassTests.cpp rename from tests/src/StringClassTests.cpp rename to test/UnitTests/src/StringClassTests.cpp diff --git a/tests/src/StringPipeTests.cpp b/test/UnitTests/src/StringPipeTests.cpp rename from tests/src/StringPipeTests.cpp rename to test/UnitTests/src/StringPipeTests.cpp diff --git a/tests/src/UnitTests.cpp b/test/UnitTests/src/UnitTests.cpp rename from tests/src/UnitTests.cpp rename to test/UnitTests/src/UnitTests.cpp diff --git a/tests/src/UnitTests.h b/test/UnitTests/src/UnitTests.h rename from tests/src/UnitTests.h rename to test/UnitTests/src/UnitTests.h