diff --git a/core/base/inc/TVirtualX.h b/core/base/inc/TVirtualX.h index 28a671a2fae6e..895e3e8f81a67 100644 --- a/core/base/inc/TVirtualX.h +++ b/core/base/inc/TVirtualX.h @@ -42,6 +42,7 @@ R__EXTERN Atom_t gROOT_MESSAGE; class TPoint; class TString; class TGWin32Command; +class TTFhandle; class TVirtualX : public TNamed, public TAttLine, public TAttFill, public TAttText, public TAttMarker { @@ -133,6 +134,7 @@ class TVirtualX : public TNamed, public TAttLine, public TAttFill, public TAttTe virtual void DrawPolyMarkerW(WinContext_t wctxt, Int_t n, TPoint *xy); virtual void DrawTextW(WinContext_t wctxt, Int_t x, Int_t y, Float_t angle, Float_t mgn, const char *text, ETextMode mode); virtual void DrawTextW(WinContext_t wctxt, Int_t x, Int_t y, Float_t angle, Float_t mgn, const wchar_t *text, ETextMode mode); + virtual void DrawTTFglyphsW(WinContext_t wctxt, Int_t x, Int_t y, TTFhandle &handle, ETextMode mode); virtual Int_t WriteGIFW(WinContext_t wctxt, const char *name); virtual Bool_t GetTextExtentA(Font_t font, Double_t size, UInt_t &w, UInt_t &h, const char *mess); diff --git a/core/base/src/TVirtualX.cxx b/core/base/src/TVirtualX.cxx index 356399aad0d4e..6cb34fbc06e5d 100644 --- a/core/base/src/TVirtualX.cxx +++ b/core/base/src/TVirtualX.cxx @@ -540,6 +540,15 @@ void TVirtualX::DrawTextW(WinContext_t /* wctxt */, Int_t x, Int_t y, Float_t an DrawText(x, y, angle, mgn, text, mode); } +//////////////////////////////////////////////////////////////////////////////// +/// Draw TTFglyphs on specified window +/// Will be invoked only if HasTTFonts method returns true + +void TVirtualX::DrawTTFglyphsW(WinContext_t /* wctxt */, Int_t /* x */, Int_t /* y */, TTFhandle &/* handle */, ETextMode /* mode */) +{ +} + + //////////////////////////////////////////////////////////////////////////////// /// Save specified window as GIF image diff --git a/graf2d/asimage/CMakeLists.txt b/graf2d/asimage/CMakeLists.txt index 8a0a67e7012ab..48a3143979bee 100644 --- a/graf2d/asimage/CMakeLists.txt +++ b/graf2d/asimage/CMakeLists.txt @@ -24,7 +24,6 @@ ROOT_STANDARD_LIBRARY_PACKAGE(ASImage -writeEmptyRootPCM LIBRARIES libAfterImage - Freetype::Freetype DEPENDENCIES Core Graf diff --git a/graf2d/asimage/inc/TASImage.h b/graf2d/asimage/inc/TASImage.h index 0091df910bb2f..032e2ee963b2c 100644 --- a/graf2d/asimage/inc/TASImage.h +++ b/graf2d/asimage/inc/TASImage.h @@ -27,6 +27,7 @@ struct ASImage; struct ASVisual; class TBrowser; class THashTable; +class TTFhandle; class TASImage : public TImage { @@ -47,7 +48,7 @@ class TASImage : public TImage { inline Int_t Idx(Int_t idx); void FillRectangleInternal(UInt_t col, Int_t x, Int_t y, UInt_t width, UInt_t height); void DrawTextTTF(Int_t x, Int_t y, const char *text, Int_t size, UInt_t color, const char *font_name, Float_t angle); - void DrawFTGlyph(void *bitmap, UInt_t color, Int_t x, Int_t y, TVirtualPad *clippad = nullptr, Int_t offx = 0, Int_t offy = 0); + void DrawFTGlyphs(TTFhandle &ttf, UInt_t color, Int_t x, Int_t y, TVirtualPad *clippad = nullptr, Int_t offx = 0, Int_t offy = 0); void SetDefaults(); void CreateThumbnail(); void DestroyImage(); diff --git a/graf2d/asimage/src/TASImage.cxx b/graf2d/asimage/src/TASImage.cxx index 86f4fb56692cd..f3589b32740b5 100644 --- a/graf2d/asimage/src/TASImage.cxx +++ b/graf2d/asimage/src/TASImage.cxx @@ -57,10 +57,6 @@ Several examples showing how to use this class are available in the ROOT tutorials: `$ROOTSYS/tutorials/visualisation/image/` */ -#include -#include FT_FREETYPE_H -#include FT_GLYPH_H - #include "RConfigure.h" #include "TArrayD.h" #include "TArrayL.h" @@ -82,7 +78,7 @@ ROOT tutorials: `$ROOTSYS/tutorials/visualisation/image/` #include "TStyle.h" #include "TSystem.h" #include "TText.h" -#include "TTF.h" +#include "TTFhandle.h" #include "TVectorD.h" #include "TVirtualPad.h" #include "TVirtualPadPainter.h" @@ -5604,56 +5600,10 @@ void TASImage::DrawWideLine(UInt_t x1, UInt_t y1, UInt_t x2, UInt_t y2, } //////////////////////////////////////////////////////////////////////////////// -/// Draw glyph bitmap. +/// Draw TTF glyphs . -void TASImage::DrawFTGlyph(void *bitmap, UInt_t color, Int_t bx, Int_t by, TVirtualPad *clippad, Int_t offx, Int_t offy) +void TASImage::DrawFTGlyphs(TTFhandle &ttf, UInt_t color, Int_t px, Int_t py, TVirtualPad *clippad, Int_t offx, Int_t offy) { - UInt_t col[5]; - Bool_t has_alpha = (color & 0xff000000) != 0xff000000; - - FT_Bitmap *source = (FT_Bitmap *) bitmap; - - auto ndots = source->width * source->rows; - ULong_t r = 0, g = 0, b = 0; - - const Int_t y0 = by > 0 ? by * fImage->width : 0; - Int_t yy = y0; - for (UInt_t y = 0; y < source->rows; y++) { - Int_t byy = by + y; - if ((byy >= (Int_t) fImage->height) || (byy < 0)) continue; - - for (UInt_t x = 0; x < source->width; x++) { - Int_t bxx = bx + x; - if ((bxx >= (Int_t) fImage->width) || (bxx < 0)) continue; - - auto idx = Idx(bxx + yy); - r += ((fImage->alt.argb32[idx] & 0xff0000) >> 16); - g += ((fImage->alt.argb32[idx] & 0x00ff00) >> 8); - b += (fImage->alt.argb32[idx] & 0x0000ff); - } - yy += fImage->width; - } - if (ndots > 0) { - r /= ndots; - g /= ndots; - b /= ndots; - } - - col[0] = (r << 16) + (g << 8) + b; - col[4] = color; - Int_t col4r = (col[4] & 0xff0000) >> 16; - Int_t col4g = (col[4] & 0x00ff00) >> 8; - Int_t col4b = (col[4] & 0x0000ff); - - // interpolate between fore and background colors - for (Int_t x = 3; x > 0; x--) { - Int_t xx = 4 - x; - Int_t colxr = (col4r*x + r*xx) >> 2; - Int_t colxg = (col4g*x + g*xx) >> 2; - Int_t colxb = (col4b*x + b*xx) >> 2; - col[x] = (colxr << 16) + (colxg << 8) + colxb; - } - Int_t clipx1 = 0, clipx2 = 0, clipy1 = 0, clipy2 = 0; Bool_t noClip = kTRUE; @@ -5666,33 +5616,95 @@ void TASImage::DrawFTGlyph(void *bitmap, UInt_t color, Int_t bx, Int_t by, TVirt noClip = kFALSE; } - yy = y0; - UChar_t *s = source->buffer; + UInt_t col[5]; + Bool_t has_alpha = (color & 0xff000000) != 0xff000000; + + ttf.SetSmoothing(kTRUE); - for (UInt_t y = 0; y < source->rows; y++) { - Int_t byy = by + y; + for (UInt_t nglyph = 0; nglyph < ttf.GetNumGlyphs(); nglyph++) { + Int_t bx = 0, by = 0; + UChar_t *buffer = nullptr; + UInt_t width = 0, rows = 0, pitch = 0; + if (!ttf.GetGlyphData(nglyph, bx, by, buffer, width, rows, pitch)) + continue; - for (UInt_t x = 0; x < source->width; x++) { - Int_t bxx = bx + x; + bx += px; + by += py; - UChar_t d = *s++ & 0xff; - d = ((d + 10) * 5) >> 8; - if (d > 4) d = 4; + auto ndots = width * rows; + ULong_t r = 0, g = 0, b = 0; - if (d > 0) { - if (noClip || ((bxx < clipx2) && (bxx >= clipx1) && - (byy >= clipy2) && (byy < clipy1))) { - auto idx = Idx(bxx + yy); - auto acolor = (ARGB32) col[d]; - if (has_alpha) { - _alphaBlend(&fImage->alt.argb32[idx], &acolor); - } else { - fImage->alt.argb32[idx] = acolor; + const Int_t y0 = by > 0 ? by * fImage->width : 0; + Int_t yy = y0; + for (UInt_t y = 0; y < rows; y++) { + Int_t byy = by + y; + if ((byy >= (Int_t) fImage->height) || (byy < 0)) continue; + + for (UInt_t x = 0; x < width; x++) { + Int_t bxx = bx + x; + if ((bxx >= (Int_t) fImage->width) || (bxx < 0)) continue; + + auto idx = Idx(bxx + yy); + r += ((fImage->alt.argb32[idx] & 0xff0000) >> 16); + g += ((fImage->alt.argb32[idx] & 0x00ff00) >> 8); + b += (fImage->alt.argb32[idx] & 0x0000ff); + } + yy += fImage->width; + } + if (ndots > 0) { + r /= ndots; + g /= ndots; + b /= ndots; + } + + col[0] = (r << 16) + (g << 8) + b; + col[4] = color; + Int_t col4r = (col[4] & 0xff0000) >> 16; + Int_t col4g = (col[4] & 0x00ff00) >> 8; + Int_t col4b = (col[4] & 0x0000ff); + + // interpolate between fore and background colors + for (Int_t x = 3; x > 0; x--) { + Int_t xx = 4 - x; + Int_t colxr = (col4r*x + r*xx) >> 2; + Int_t colxg = (col4g*x + g*xx) >> 2; + Int_t colxb = (col4b*x + b*xx) >> 2; + col[x] = (colxr << 16) + (colxg << 8) + colxb; + } + + yy = y0; + + for (UInt_t y = 0; y < rows; y++) { + Int_t byy = by + y; + + UChar_t *s = buffer; + + for (UInt_t x = 0; x < width; x++) { + Int_t bxx = bx + x; + + UChar_t d = *s++ & 0xff; + d = ((d + 10) * 5) >> 8; + if (d > 4) d = 4; + + if (d > 0) { + if (noClip || ((bxx < clipx2) && (bxx >= clipx1) && + (byy >= clipy2) && (byy < clipy1))) { + auto idx = Idx(bxx + yy); + auto acolor = (ARGB32) col[d]; + if (has_alpha) { + _alphaBlend(&fImage->alt.argb32[idx], &acolor); + } else { + fImage->alt.argb32[idx] = acolor; + } } } } + + // ttf has own alignment for rows + buffer += pitch; + + yy += fImage->width; } - yy += fImage->width; } } @@ -5705,27 +5717,20 @@ void TASImage::DrawText(TText *text, Int_t x, Int_t y) DrawTextOnPad(text, x, y, gPad, 0, 0); } - //////////////////////////////////////////////////////////////////////////////// /// Draw text at the pixel position (x,y) checking clip on pad. void TASImage::DrawTextOnPad(TText *text, Int_t x, Int_t y, TVirtualPad *pad, Int_t offx, Int_t offy) { - if (!text || !InitImage("DrawTextOnPad")) + if (!text || !pad || !InitImage("DrawTextOnPad")) return; TTFhandle ttf; // set text font ttf.SetTextFont(text->GetTextFont()); - - UInt_t padw = pad ? pad->GetPadWidth() : 100; - UInt_t padh = pad ? pad->GetPadHeight() : 100; - // set text size - Float_t ttfsize = text->GetTextSize() * TMath::Min(padw, padh); - ttf.SetTextSize(ttfsize*kScale); - + ttf.SetTextSize(text->GetTextSizePixels(*pad)*kScale); // set text angle ttf.SetRotationMatrix(text->GetTextAngle()); @@ -5747,86 +5752,8 @@ void TASImage::DrawTextOnPad(TText *text, Int_t x, Int_t y, TVirtualPad *pad, In ARGB32 color = ARGB32_White; parse_argb_color(col->AsHexString(), &color); - // Align() - Int_t align = 0; - Int_t txalh = text->GetTextAlign()/10; - Int_t txalv = text->GetTextAlign()%10; - - switch (txalh) { - case 0 : - case 1 : - switch (txalv) { //left - case 1 : - align = 7; //bottom - break; - case 2 : - align = 4; //center - break; - case 3 : - align = 1; //top - break; - } - break; - case 2 : - switch (txalv) { //center - case 1 : - align = 8; //bottom - break; - case 2 : - align = 5; //center - break; - case 3 : - align = 2; //top - break; - } - break; - case 3 : - switch (txalv) { //right - case 1 : - align = 9; //bottom - break; - case 2 : - align = 6; //center - break; - case 3 : - align = 3; //top - break; - } - break; - } - - FT_Vector ftal; - - // vertical alignment - if (align == 1 || align == 2 || align == 3) { - ftal.y = ttf.GetAscent(); - } else if (align == 4 || align == 5 || align == 6) { - ftal.y = ttf.GetAscent() / 2; - } else { - ftal.y = 0; - } - - // horizontal alignment - if (align == 3 || align == 6 || align == 9) { - ftal.x = ttf.GetWidth(); - } else if (align == 2 || align == 5 || align == 8) { - ftal.x = ttf.GetWidth() / 2; - } else { - ftal.x = 0; - } - - FT_Vector_Transform(&ftal, ttf.GetRotMatrix()); - ftal.x = (ftal.x >> 6); - ftal.y = (ftal.y >> 6); - - for (UInt_t n = 0; n < ttf.GetNumGlyphs(); n++) { - if (auto bitmap = ttf.GetGlyphBitmap(n, kTRUE)) { - Int_t bx = x - ftal.x + bitmap->left; - Int_t by = y + ftal.y - bitmap->top; - - DrawFTGlyph(&bitmap->bitmap, color, bx, by, pad, offx, offy); - } - } + if (ttf.ApplyAlignRotate(x, y, text->GetTextAlign(), GetWidth(), GetHeight())) + DrawFTGlyphs(ttf, color, x, y, pad, offx, offy); } //////////////////////////////////////////////////////////////////////////////// @@ -5846,18 +5773,8 @@ void TASImage::DrawTextTTF(Int_t x, Int_t y, const char *text, Int_t size, ttf.PrepareString(text); ttf.LayoutGlyphs(); - // compute the size and position that will contain the text - // Int_t Xoff = TMath::Max(0, (Int_t) -ttf.GetBox().xMin); - Int_t Yoff = TMath::Max(0, (Int_t) -ttf.GetBox().yMin); - Int_t h = ttf.GetBox().yMax + Yoff; - - for (UInt_t n = 0; n < ttf.GetNumGlyphs(); n++) { - if (auto bitmap = ttf.GetGlyphBitmap(n, kTRUE)) { - Int_t bx = x + bitmap->left; - Int_t by = y + h - bitmap->top; - DrawFTGlyph(&bitmap->bitmap, color, bx, by); - } - } + if (ttf.ApplyAlignRotate(x, y, 11, GetWidth(), GetHeight())) + DrawFTGlyphs(ttf, color, x, y); } //////////////////////////////////////////////////////////////////////////////// diff --git a/graf2d/cocoa/inc/TGQuartz.h b/graf2d/cocoa/inc/TGQuartz.h index 325046ab70ec1..e39e755488641 100644 --- a/graf2d/cocoa/inc/TGQuartz.h +++ b/graf2d/cocoa/inc/TGQuartz.h @@ -88,6 +88,7 @@ class TGQuartz : public TGCocoa { void DrawPolyMarkerW(WinContext_t wctxt, Int_t n, TPoint *xy) override; void DrawTextW(WinContext_t wctxt, Int_t x, Int_t y, Float_t angle, Float_t mgn, const char *text, ETextMode mode) override; void DrawTextW(WinContext_t wctxt, Int_t x, Int_t y, Float_t angle, Float_t mgn, const wchar_t *text, ETextMode mode) override; + void DrawTTFglyphsW(WinContext_t wctxt, Int_t x, Int_t y, TTFhandle &ttf, ETextMode mode) override; Bool_t GetTextExtentA(Font_t font, Double_t size, UInt_t &w, UInt_t &h, const char *mess) override; Bool_t GetTextExtentA(Font_t font, Double_t size, UInt_t &w, UInt_t &h, const wchar_t *mess) override; @@ -104,8 +105,6 @@ class TGQuartz : public TGCocoa { bool fUseAA; bool fUseFAAA; - void DrawFTGlyph(void *pixmap, void *source, ULong_t fore, ULong_t back, Int_t bx, Int_t by); - void SetAA(); TAttFill &GetAttFill(WinContext_t wctxt); TAttLine &GetAttLine(WinContext_t wctxt); diff --git a/graf2d/cocoa/src/TGQuartz.mm b/graf2d/cocoa/src/TGQuartz.mm index 7ee0508a2c785..09d5018121ff3 100644 --- a/graf2d/cocoa/src/TGQuartz.mm +++ b/graf2d/cocoa/src/TGQuartz.mm @@ -20,10 +20,6 @@ #include -# include -# include FT_FREETYPE_H -# include FT_GLYPH_H - #include "QuartzFillArea.h" #include "TColorGradient.h" #include "QuartzMarker.h" @@ -35,6 +31,7 @@ #include "QuartzText.h" #include "QuartzLine.h" #include "CocoaUtils.h" +#include "TTFhandle.h" #include "TGQuartz.h" #include "TString.h" #include "TPoint.h" @@ -43,7 +40,6 @@ #include "TROOT.h" #include "TMath.h" #include "TEnv.h" -#include "TTF.h" // To scale fonts to the same size as the TTF version const Float_t kScale = 0.93376068; @@ -525,56 +521,44 @@ void ConvertPointsROOTToCocoa(Int_t nPoints, const TPoint *xy, std::vector * const) GetPixmapDrawable(drawable0, "DrawTextW"); - if (!drawable) - return; - + UInt_t width = 0, height = 0; + Int_t xy = 0; + GetWindowSize((Drawable_t) drawable0.fID, xy, xy, width, height); - //Do not draw anything, or CoreText will create some small (but not of size 0 font). auto &att = GetAttText(wctxt); - if (att.GetTextSize() < 1.5)//Do not draw anything, or CoreText will create some small (but not of size 0 font). + //Do not draw anything, or CoreText will create some small (but not of size 0 font). + if (att.GetTextSize() < 1.5) return; - TTFhandle::SetSmoothing(kTRUE); - TTFhandle ttf; + ttf.SetSmoothing(kTRUE); ttf.SetTextFont(att.GetTextFont()); ttf.SetTextSize(att.GetTextSize()); ttf.SetRotationMatrix(angle); ttf.PrepareString(text); ttf.LayoutGlyphs(); - Int_t txalh = att.GetTextAlign() / 10; - Int_t txalv = att.GetTextAlign() % 10; - FT_Vector align_vect; ///< alignment vector - - // const EAlign align = EAlign(fTextAlign); - // vertical alignment - if (txalv == 3) // align == kTLeft || align == kTCenter || align == kTRight) - align_vect.y = ttf.GetAscent(); - else if (txalv == 2) // if (align == kMLeft || align == kMCenter || align == kMRight) { - align_vect.y = ttf.GetAscent() / 2; - else - align_vect.y = 0; - - // horizontal alignment - if (txalh == 3) // align == kTRight || align == kMRight || align == kBRight) { - align_vect.x = ttf.GetWidth(); - else if (txalh == 2) // (align == kTCenter || align == kMCenter || align == kBCenter) { - align_vect.x = ttf.GetWidth() / 2; - else - align_vect.x = 0; - - FT_Vector_Transform(&align_vect, ttf.GetRotMatrix()); - //This shift is from the original code. - align_vect.x = align_vect.x >> 6; - align_vect.y = align_vect.y >> 6; + if (ttf.ApplyAlignRotate(x, y, att.GetTextAlign(), width, height)) + DrawTTFglyphsW(wctxt, x, y, ttf, mode); +} +//______________________________________________________________________________ +void TGQuartz::DrawTTFglyphsW(WinContext_t wctxt, Int_t x1, Int_t y1, TTFhandle &ttf, ETextMode mode) +{ + auto drawable0 = (NSObject * const) wctxt; + if (!drawable0) + return; - //This code is a modified (for Quartz) version of TG11TTF text drawing + if ([drawable0 isDirectDraw]) + return; + auto drawable = (NSObject * const) GetPixmapDrawable(drawable0, "DrawTTFglyphsW"); + if (!drawable) + return; + + //This code is a modified (for Quartz) version of TG11TTF text drawing QuartzPixmap *dstPixmap = nil; if ([drawable isKindOfClass : [QuartzPixmap class]]) dstPixmap = (QuartzPixmap *)drawable; @@ -583,38 +567,17 @@ void ConvertPointsROOTToCocoa(Int_t nPoints, const TPoint *xy, std::vector= (Int_t)width || y1 + h <= 0 || y1 >= (Int_t)height) - return; + Int_t w = ttf.GetGlyphsWidth(); + Int_t h = ttf.GetGlyphsHeight(); //By default, all pixels are set to 0 (all components, that's what code in TGX11TTF also does here). Util::NSScopeGuard pixmap([[QuartzPixmap alloc] initWithW : w H : h scaleFactor : 1.f]); if (!pixmap.Get()) { - Error("DrawTextW", "pixmap creation failed"); + Error("DrawTTFglyphsW", "pixmap creation failed"); return; } @@ -631,7 +594,7 @@ void ConvertPointsROOTToCocoa(Int_t nPoints, const TPoint *xy, std::vectorleft + xOff; - const Int_t by = h - bitmap->top - yOff; + for (UInt_t nglyph = 0; nglyph < ttf.GetNumGlyphs(); nglyph++) { + Int_t bx = 0, by = 0; + UChar_t *buffer = nullptr; + UInt_t width = 0, rows = 0, pitch = 0; + if (!ttf.GetGlyphData(nglyph, bx, by, buffer, width, rows, pitch)) + continue; + + if (ttf.GetSmoothing()) { + ColorStruct_t col[5]; + // background kClear, i.e. transparent, we take as background color + // the average of the rgb values of all pixels covered by this character + if (mode == kClear) { + const UInt_t maxDots = TMath::Min((UInt_t) 50000, width * rows); + + //In original code, they first have to extract + //pixels and call XQueryColors. + //I have only one loop here. + ULong_t r = 0, g = 0, b = 0; + UInt_t dotCnt = 0; + for (unsigned y = 0; y < rows; y++) { + for (unsigned x = 0; x < width; x++) { + if (x + bx < pixmap.Get().fWidth && y + by < pixmap.Get().fHeight) { + const unsigned char * const pixels = pixmap.Get().fData + (y + by) * pixmap.Get().fWidth * 4 + (x + bx) * 4; + r += UShort_t(pixels[0] / 255. * 0xffff); + g += UShort_t(pixels[1] / 255. * 0xffff); + b += UShort_t(pixels[2] / 255. * 0xffff); + if (++dotCnt >= maxDots) + break; + } + } + } - DrawFTGlyph(pixmap.Get(), &bitmap->bitmap, TGCocoa::GetPixel(att.GetTextColor()), - mode == kClear ? ULong_t(-1) : 0xffffff, bx, by); + if (dotCnt > 0) { + r /= dotCnt; + g /= dotCnt; + b /= dotCnt; + } + + col[0].fRed = (UShort_t) r; + col[0].fGreen = (UShort_t) g; + col[0].fBlue = (UShort_t) b; + } else { + // request background color + col[0].fPixel = back; + TGCocoa::QueryColor(kNone, col[0]); + } + + // request foreground color + col[4].fPixel = fore; + TGCocoa::QueryColor(kNone, col[4]);//calculate fRed/fGreen/fBlue triple from fPixel. + + // interpolate between fore and background colors + for (int x = 3; x > 0; --x) { + col[x].fRed = (col[4].fRed * x + col[0].fRed * (4 - x)) / 4; + col[x].fGreen = (col[4].fGreen * x + col[0].fGreen * (4 - x)) / 4; + col[x].fBlue = (col[4].fBlue * x + col[0].fBlue * (4 - x)) / 4; + TGCocoa::AllocColor(kNone, col[x]);//Calculate fPixel from fRed/fGreen/fBlue triplet. + } + + // put smoothed character, character pixmap values are an index + // into the 5 colors used for aliasing (4 = foreground, 0 = background) + const unsigned char *s = buffer; + for (unsigned y = 0; y < rows; ++y) { + for (unsigned x = 0; x < width; ++x) { + unsigned char d = (((*s++ & 0xff) + 10) * 5) / 256; + if (d > 4) + d = 4; + if (d > 0) { + const UChar_t pixel[] = {UChar_t(double(col[d].fRed) / 0xffff * 255), + UChar_t(double(col[d].fGreen) / 0xffff * 255), + UChar_t(double(col[d].fBlue) / 0xffff * 255), 255}; + [pixmap.Get() putPixel : pixel X : bx + x Y : by + y]; + } + } + } + } else { + // no smoothing, just put character using foreground color + unsigned char rgba[4] = {}; + rgba[3] = 255; + X11::PixelToRGB(fore, rgba); + unsigned char d = 0; + + const unsigned char *row = buffer; + for (unsigned y = 0; y < rows; ++y) { + unsigned n = 0; + const unsigned char *s = row; + for (unsigned x = 0; x < width; ++x) { + if (!n) + d = *s++; + + if (TESTBIT(d,7 - n)) + [pixmap.Get() putPixel : rgba X : bx + x Y : by + y]; + + if (++n == kBitsPerByte) + n = 0; + } + + row += pitch; + } } } @@ -671,6 +731,8 @@ void ConvertPointsROOTToCocoa(Int_t nPoints, const TPoint *xy, std::vectorwidth * source->rows); - - //In original code, they first have to extract - //pixels and call XQueryColors. - //I have only one loop here. - ULong_t r = 0, g = 0, b = 0; - UInt_t dotCnt = 0; - for (unsigned y = 0; y < source->rows; y++) { - for (unsigned x = 0; x < source->width; x++) { - if (x + bx < pixmap.fWidth && y + by < pixmap.fHeight) { - const unsigned char * const pixels = pixmap.fData + (y + by) * pixmap.fWidth * 4 + (x + bx) * 4; - r += UShort_t(pixels[0] / 255. * 0xffff); - g += UShort_t(pixels[1] / 255. * 0xffff); - b += UShort_t(pixels[2] / 255. * 0xffff); - if (++dotCnt >= maxDots) - break; - } - } - } - - if (dotCnt > 0) { - r /= dotCnt; - g /= dotCnt; - b /= dotCnt; - } - - col[0].fRed = (UShort_t) r; - col[0].fGreen = (UShort_t) g; - col[0].fBlue = (UShort_t) b; - } else { - // request background color - col[0].fPixel = back; - TGCocoa::QueryColor(kNone, col[0]); - } - - // request foreground color - col[4].fPixel = fore; - TGCocoa::QueryColor(kNone, col[4]);//calculate fRed/fGreen/fBlue triple from fPixel. - - // interpolate between fore and background colors - for (int x = 3; x > 0; --x) { - col[x].fRed = (col[4].fRed * x + col[0].fRed * (4 - x)) / 4; - col[x].fGreen = (col[4].fGreen * x + col[0].fGreen * (4 - x)) / 4; - col[x].fBlue = (col[4].fBlue * x + col[0].fBlue * (4 - x)) / 4; - TGCocoa::AllocColor(kNone, col[x]);//Calculate fPixel from fRed/fGreen/fBlue triplet. - } - - // put smoothed character, character pixmap values are an index - // into the 5 colors used for aliasing (4 = foreground, 0 = background) - const unsigned char *s = source->buffer; - for (unsigned y = 0; y < source->rows; ++y) { - for (unsigned x = 0; x < source->width; ++x) { - unsigned char d = (((*s++ & 0xff) + 10) * 5) / 256; - if (d > 4) - d = 4; - if (d > 0) { - const UChar_t pixel[] = {UChar_t(double(col[d].fRed) / 0xffff * 255), - UChar_t(double(col[d].fGreen) / 0xffff * 255), - UChar_t(double(col[d].fBlue) / 0xffff * 255), 255}; - [pixmap putPixel : pixel X : bx + x Y : by + y]; - } - } - } - } else { - // no smoothing, just put character using foreground color - unsigned char rgba[4] = {}; - rgba[3] = 255; - X11::PixelToRGB(fore, rgba); - unsigned char d = 0; - - const unsigned char *row = source->buffer; - for (unsigned y = 0; y < source->rows; ++y) { - unsigned n = 0; - const unsigned char *s = row; - for (unsigned x = 0; x < source->width; ++x) { - if (!n) - d = *s++; - - if (TESTBIT(d,7 - n)) - [pixmap putPixel : rgba X : bx + x Y : by + y]; - - if (++n == kBitsPerByte) - n = 0; - } - - row += source->pitch; - } - } -} //Aux. functions. diff --git a/graf2d/gpad/inc/TPadPainter.h b/graf2d/gpad/inc/TPadPainter.h index 71f2cfd0c90d8..6e21569e6c490 100644 --- a/graf2d/gpad/inc/TPadPainter.h +++ b/graf2d/gpad/inc/TPadPainter.h @@ -23,9 +23,8 @@ or gl pad painter. class TVirtualPad; class TPadPainter : public TPadPainterBase { - WinContext_t fWinContext; - Int_t fDoubleBuffer; - TVirtualPad *fPad = nullptr; +protected: + Int_t fDoubleBuffer = 1; public: TPadPainter(); @@ -77,16 +76,11 @@ class TPadPainter : public TPadPainterBase { void DrawPolyMarker(Int_t n, const Double_t *x, const Double_t *y) override; void DrawPolyMarker(Int_t n, const Float_t *x, const Float_t *y) override; - void DrawText(Double_t x, Double_t y, const char *text, ETextMode mode) override; - void DrawText(Double_t x, Double_t y, const wchar_t *text, ETextMode mode) override; - void DrawTextNDC(Double_t u, Double_t v, const char *text, ETextMode mode) override; - void DrawTextNDC(Double_t u, Double_t v, const wchar_t *text, ETextMode mode) override; + void DrawTTFglyphs(Int_t x, Int_t y, TTFhandle &ttf, ETextMode mode) override; //jpg, png, bmp, gif output. void SaveImage(TVirtualPad *pad, const char *fileName, Int_t type) const override; - void OnPad(TVirtualPad *pad) override { fPad = pad; } - Bool_t IsNative() const override { return kTRUE; } Bool_t IsCocoa() const override; diff --git a/graf2d/gpad/inc/TPadPainterBase.h b/graf2d/gpad/inc/TPadPainterBase.h index 2ec100c8a8bb7..e6f04b10d5805 100644 --- a/graf2d/gpad/inc/TPadPainterBase.h +++ b/graf2d/gpad/inc/TPadPainterBase.h @@ -18,8 +18,13 @@ #include "TAttMarker.h" #include "TAttText.h" +class TTFhandle; + class TPadPainterBase : public TVirtualPadPainter { protected: + WinContext_t fWinContext = (WinContext_t) 0; + TVirtualPad *fPad = nullptr; + TAttFill fAttFill; ///< current fill attributes TAttLine fAttLine; ///< current line attributes TAttMarker fAttMarker; ///< current marker attributes @@ -77,6 +82,9 @@ class TPadPainterBase : public TVirtualPadPainter { /// _____________________________________________________________________ + void OnPad(TVirtualPad *pad) override { fPad = pad; } + + const TAttFill &GetAttFill() const override { return fAttFill; } const TAttLine &GetAttLine() const override { return fAttLine; } const TAttMarker &GetAttMarker()const override { return fAttMarker; } @@ -106,12 +114,19 @@ class TPadPainterBase : public TVirtualPadPainter { att.Copy(fAttText); } + virtual void DrawTTFglyphs(Int_t x, Int_t y, TTFhandle &ttf, ETextMode mode); + void GetTextExtent(Font_t font, Double_t size, UInt_t &w, UInt_t &h, const char *mess) override; void GetTextExtent(Font_t font, Double_t size, UInt_t &w, UInt_t &h, const wchar_t *mess) override; void GetTextAscentDescent(Font_t font, Double_t size, UInt_t &a, UInt_t &d, const char *mess) override; void GetTextAscentDescent(Font_t font, Double_t size, UInt_t &a, UInt_t &d, const wchar_t *mess) override; UInt_t GetTextAdvance(Font_t font, Double_t size, const char *text, Bool_t kern) override; + void DrawText(Double_t x, Double_t y, const char *text, ETextMode mode) override; + void DrawText(Double_t x, Double_t y, const wchar_t *text, ETextMode mode) override; + void DrawTextNDC(Double_t u, Double_t v, const char *text, ETextMode mode) override; + void DrawTextNDC(Double_t u, Double_t v, const wchar_t *text, ETextMode mode) override; + ClassDefOverride(TPadPainterBase, 0)//Pad painter with attributes handling }; diff --git a/graf2d/gpad/src/TPadPainter.cxx b/graf2d/gpad/src/TPadPainter.cxx index faef99146dea6..3ac2b9883cc92 100644 --- a/graf2d/gpad/src/TPadPainter.cxx +++ b/graf2d/gpad/src/TPadPainter.cxx @@ -70,8 +70,6 @@ Implement TVirtualPadPainter which abstracts painting operations. TPadPainter::TPadPainter() { - fDoubleBuffer = 1; - fWinContext = (WinContext_t) 0; } /* @@ -486,7 +484,6 @@ void TPadPainter::DrawPolyMarker(Int_t n, const Double_t *x, const Double_t *y) DrawPolyMarkerAux(gPad, fWinContext, fDoubleBuffer, n, x, y); } - //////////////////////////////////////////////////////////////////////////////// /// Paint polymarker. @@ -500,46 +497,14 @@ void TPadPainter::DrawPolyMarker(Int_t n, const Float_t *x, const Float_t *y) DrawPolyMarkerAux(gPad, fWinContext, fDoubleBuffer, n, x, y); } - //////////////////////////////////////////////////////////////////////////////// -/// Paint text. +/// Paint TTF glyps on virtualx device -void TPadPainter::DrawText(Double_t x, Double_t y, const char *text, ETextMode mode) +void TPadPainter::DrawTTFglyphs(Int_t x, Int_t y, TTFhandle &ttf, ETextMode mode) { - const Int_t px = gPad->XtoPixel(x); - const Int_t py = gPad->YtoPixel(y); - const Double_t angle = GetTextAngle(); - const Double_t mgn = GetTextMagnitude(); - gVirtualX->DrawTextW(fWinContext, px, py, angle, mgn, text, (TVirtualX::ETextMode)mode); + gVirtualX->DrawTTFglyphsW(fWinContext, x, y, ttf, (TVirtualX::ETextMode) mode); } - -//////////////////////////////////////////////////////////////////////////////// -/// Special version working with wchar_t and required by TMathText. - -void TPadPainter::DrawText(Double_t x, Double_t y, const wchar_t *text, ETextMode mode) -{ - const Int_t px = gPad->XtoPixel(x); - const Int_t py = gPad->YtoPixel(y); - const Double_t angle = GetTextAngle(); - const Double_t mgn = GetTextMagnitude(); - gVirtualX->DrawTextW(fWinContext, px, py, angle, mgn, text, (TVirtualX::ETextMode)mode); -} - - -//////////////////////////////////////////////////////////////////////////////// -/// Paint text in normalized coordinates. - -void TPadPainter::DrawTextNDC(Double_t u, Double_t v, const char *text, ETextMode mode) -{ - const Int_t px = gPad->UtoPixel(u); - const Int_t py = gPad->VtoPixel(v); - const Double_t angle = GetTextAngle(); - const Double_t mgn = GetTextMagnitude(); - gVirtualX->DrawTextW(fWinContext, px, py, angle, mgn, text, (TVirtualX::ETextMode)mode); -} - - //////////////////////////////////////////////////////////////////////////////// /// Save the image displayed in the canvas pointed by "pad" into a binary file. @@ -606,18 +571,6 @@ void TPadPainter::SaveImage(TVirtualPad *pad, const char *fileName, Int_t type) } -//////////////////////////////////////////////////////////////////////////////// -/// Paint text in normalized coordinates. - -void TPadPainter::DrawTextNDC(Double_t u, Double_t v, const wchar_t *text, ETextMode mode) -{ - const Int_t px = gPad->UtoPixel(u); - const Int_t py = gPad->VtoPixel(v); - const Double_t angle = GetTextAngle(); - const Double_t mgn = GetTextMagnitude(); - gVirtualX->DrawTextW(fWinContext, px, py, angle, mgn, text, (TVirtualX::ETextMode)mode); -} - //Aux. private functions. namespace { diff --git a/graf2d/gpad/src/TPadPainterBase.cxx b/graf2d/gpad/src/TPadPainterBase.cxx index 0e3c2f37cb477..6d9815708b634 100644 --- a/graf2d/gpad/src/TPadPainterBase.cxx +++ b/graf2d/gpad/src/TPadPainterBase.cxx @@ -12,9 +12,11 @@ #include "TPadPainterBase.h" #include "TColor.h" -#include "TTF.h" #include "TVirtualX.h" +#include "TVirtualPad.h" #include "TMathBase.h" +#include "TError.h" +#include "TTFhandle.h" /** \class TPadPainterBase \ingroup gpad @@ -102,8 +104,8 @@ void TPadPainterBase::GetTextAscentDescent(Font_t font, Double_t size, UInt_t &a ttf.SetTextSize(size * GetTTFScale()); UInt_t w, h; ttf.GetTextExtent(w, h, mess); - a = ttf.GetBox().yMax; - d = TMath::Abs(ttf.GetBox().yMin); + a = ttf.GetBoxYMax(); + d = TMath::Abs(ttf.GetBoxYMin()); } } @@ -129,8 +131,8 @@ void TPadPainterBase::GetTextAscentDescent(Font_t font, Double_t size, UInt_t &a ttf.SetTextSize(size * GetTTFScale()); UInt_t w, h; ttf.GetTextExtent(w, h, mess); - a = ttf.GetBox().yMax; - d = TMath::Abs(ttf.GetBox().yMin); + a = ttf.GetBoxYMax(); + d = TMath::Abs(ttf.GetBoxYMin()); } } @@ -154,3 +156,108 @@ UInt_t TPadPainterBase::GetTextAdvance(Font_t font, Double_t size, const char *m ttf.GetTextAdvance(a, mess); return a; } + +//////////////////////////////////////////////////////////////////////////////// +/// Performs rendering of TTF glyphs on output device +/// Can be implemented in derived classes instead of implementing +/// 4 different signatures of DrawText + +void TPadPainterBase::DrawTTFglyphs([[maybe_unused]] Int_t x, [[maybe_unused]] Int_t y, [[maybe_unused]] TTFhandle &ttf, [[maybe_unused]] ETextMode mode) +{ +} + +//////////////////////////////////////////////////////////////////////////////// +/// Paint text. + +void TPadPainterBase::DrawText(Double_t x, Double_t y, const char *text, ETextMode mode) +{ + Int_t px = fPad->XtoPixel(x); + Int_t py = fPad->YtoPixel(y); + const TAttText &att = GetAttText(); + + if (HasTTFonts()) { + TTFhandle ttf; + ttf.SetTextFont(att.GetTextFont()); + ttf.SetTextSize(att.GetTextSizePixels(*fPad)); + ttf.SetRotationMatrix(att.GetTextAngle()); + ttf.PrepareString(text); + ttf.LayoutGlyphs(); + if (ttf.ApplyAlignRotate(px, py, att.GetTextAlign(), fPad->GetPadWidth(), fPad->GetPadHeight())) + DrawTTFglyphs(px, py, ttf, mode); + } else if (fWinContext && gVirtualX) { + gVirtualX->DrawTextW(fWinContext, px, py, att.GetTextAngle(), GetTextMagnitude(), text, + (TVirtualX::ETextMode)mode); + } +} + +//////////////////////////////////////////////////////////////////////////////// +/// Paint wtext. + +void TPadPainterBase::DrawText(Double_t x, Double_t y, const wchar_t *text, ETextMode mode) +{ + Int_t px = fPad->XtoPixel(x); + Int_t py = fPad->YtoPixel(y); + const TAttText &att = GetAttText(); + + if (HasTTFonts()) { + TTFhandle ttf; + ttf.SetTextFont(att.GetTextFont()); + ttf.SetTextSize(att.GetTextSizePixels(*fPad)); + ttf.SetRotationMatrix(att.GetTextAngle()); + ttf.PrepareString(text); + ttf.LayoutGlyphs(); + if (ttf.ApplyAlignRotate(px, py, att.GetTextAlign(), fPad->GetPadWidth(), fPad->GetPadHeight())) + DrawTTFglyphs(px, py, ttf, mode); + } else if (fWinContext && gVirtualX) { + gVirtualX->DrawTextW(fWinContext, px, py, att.GetTextAngle(), GetTextMagnitude(), text, + (TVirtualX::ETextMode)mode); + } +} + +//////////////////////////////////////////////////////////////////////////////// +/// Paint text at NDC coordinates. + +void TPadPainterBase::DrawTextNDC(Double_t u, Double_t v, const char *text, ETextMode mode) +{ + Int_t px = fPad->UtoPixel(u); + Int_t py = fPad->VtoPixel(v); + const TAttText &att = GetAttText(); + + if (HasTTFonts()) { + TTFhandle ttf; + ttf.SetTextFont(att.GetTextFont()); + ttf.SetTextSize(att.GetTextSizePixels(*fPad)); + ttf.SetRotationMatrix(att.GetTextAngle()); + ttf.PrepareString(text); + ttf.LayoutGlyphs(); + if (ttf.ApplyAlignRotate(px, py, att.GetTextAlign(), fPad->GetPadWidth(), fPad->GetPadHeight())) + DrawTTFglyphs(px, py, ttf, mode); + } else if (fWinContext && gVirtualX) { + gVirtualX->DrawTextW(fWinContext, px, py, att.GetTextAngle(), GetTextMagnitude(), text, + (TVirtualX::ETextMode)mode); + } +} + +//////////////////////////////////////////////////////////////////////////////// +/// Paint wtext at NDC coordinates. + +void TPadPainterBase::DrawTextNDC(Double_t u, Double_t v, const wchar_t *text, ETextMode mode) +{ + Int_t px = fPad->UtoPixel(u); + Int_t py = fPad->VtoPixel(v); + const TAttText &att = GetAttText(); + + if (HasTTFonts()) { + TTFhandle ttf; + ttf.SetTextFont(att.GetTextFont()); + ttf.SetTextSize(att.GetTextSizePixels(*fPad)); + ttf.SetRotationMatrix(att.GetTextAngle()); + ttf.PrepareString(text); + ttf.LayoutGlyphs(); + if (ttf.ApplyAlignRotate(px, py, att.GetTextAlign(), fPad->GetPadWidth(), fPad->GetPadHeight())) + DrawTTFglyphs(px, py, ttf, mode); + } else if (fWinContext && gVirtualX) { + gVirtualX->DrawTextW(fWinContext, px, py, att.GetTextAngle(), GetTextMagnitude(), text, + (TVirtualX::ETextMode)mode); + } +} diff --git a/graf2d/graf/CMakeLists.txt b/graf2d/graf/CMakeLists.txt index da2585cb59fc2..2fcf8ff8cf6f7 100644 --- a/graf2d/graf/CMakeLists.txt +++ b/graf2d/graf/CMakeLists.txt @@ -46,6 +46,7 @@ ROOT_STANDARD_LIBRARY_PACKAGE(Graf TPoints.h TPolyLine.h TTF.h + TTFhandle.h TText.h TWbox.h SOURCES @@ -83,6 +84,7 @@ ROOT_STANDARD_LIBRARY_PACKAGE(Graf src/TPoints.cxx src/TPolyLine.cxx src/TTF.cxx + src/TTFhandle.cxx src/TText.cxx src/TWbox.cxx DICTIONARY_OPTIONS diff --git a/graf2d/graf/inc/TTF.h b/graf2d/graf/inc/TTF.h index 60dc87c37d279..8f3cb195d2ef2 100644 --- a/graf2d/graf/inc/TTF.h +++ b/graf2d/graf/inc/TTF.h @@ -1,7 +1,7 @@ // @(#)root/graf:$Id$ // Author: Olivier Couet 01/10/2002 // Author: Fons Rademakers 21/11/1998 -// Author: Sergey Linev 29/14/2026 +// Author: Sergey Linev 29/04/2026 /************************************************************************* * Copyright (C) 1995-2026, Rene Brun and Fons Rademakers. * @@ -14,9 +14,8 @@ #ifndef ROOT_TTF #define ROOT_TTF - #include "Rtypes.h" -#include + /// @cond DOXYGEN_IGNORE // Forward declare for the headers: @@ -49,8 +48,9 @@ extern "C" { /// @endcond -class TTFhandle; -struct TTFontHandle; +#include "TTFhandle.h" + + class TTF { @@ -68,7 +68,7 @@ class TTF { FT_Vector fPos; ///< position of glyph origin FT_Glyph fImage{nullptr}; ///< glyph image TTGlyph(UInt_t indx = 0) : fIndex(indx) {} - ~TTGlyph(); + ~TTGlyph() {} }; TTF() {} @@ -117,71 +117,4 @@ class TTF { ClassDef(TTF,0) //Interface to TTF font handling }; -class TTFhandle { - friend class TTF; - - private: - TTFontHandle *fFont = nullptr; ///< selected font - Int_t fAscent = 0; ///< string ascent, used to compute Y alignment - FT_BBox fCBox; ///< string control box - std::vector fGlyphs; ///< glyphs - Bool_t fKerning = kTRUE; ///< use kerning (true by default) - std::unique_ptr fRotMatrix; ///< rotation matrix - Int_t fTBlankW = 0; ///< trailing blanks width - Int_t fWidth = 0; ///< string width, used to compute X alignment - - static Bool_t fgHinting; ///< use hinting (false by default) - static Bool_t fgSmoothing; ///< use anti-aliasing (true when >8 planes, false otherwise) - - UInt_t CharToUnicode(UInt_t code); - void ComputeTrailingBlanksWidth(Int_t n); - - Int_t SelectFontHandle(Int_t arg, const char *name = nullptr); - - /// Thread-local wrapper to the FreeType library - struct FT_Library_Wrapper; - static thread_local FT_Library_Wrapper fFT_Library; - - public: - TTFhandle(); - virtual ~TTFhandle(); - - TTF::TTGlyph *GetGlyphs() { return fGlyphs.data(); } - UInt_t GetNumGlyphs() const { return fGlyphs.size(); } - FT_BitmapGlyph GetGlyphBitmap(UInt_t n, Bool_t smooth = kFALSE); - FT_Face GetFontFace() const; - Int_t GetAscent() const { return fAscent; } - Bool_t GetKerning() const { return fKerning; } - FT_Matrix *GetRotMatrix() const { return fRotMatrix.get(); } - Int_t GetTrailingBlanksWidth() const { return fTBlankW; } - Int_t GetWidth() const { return fWidth; } - const FT_BBox &GetBox() const { return fCBox; } - - void SetKerning(Bool_t state) { fKerning = state; } - void SetTextFont(Font_t fontnumber); - Int_t SetTextFont(const char *fontname, Int_t italic = 0); - Bool_t SetTextSize(Float_t textsize); - - void LayoutGlyphs(); - void PrepareString(const char *string); - void PrepareString(const wchar_t *string); - void SetRotationMatrix(Float_t angle); - void CleanupGlyphs(); - - void GetTextExtent(UInt_t &w, UInt_t &h, const char *text); - void GetTextExtent(UInt_t &w, UInt_t &h, const wchar_t *text); - void GetTextAdvance(UInt_t &a, const char *text); - - void Version(Int_t &major, Int_t &minor, Int_t &patch); - - static Bool_t Init(); - static Bool_t GetHinting(); - static Bool_t GetSmoothing(); - static void SetHinting(Bool_t state); - static void SetSmoothing(Bool_t state); - - ClassDef(TTFhandle, 0) // Dynamic interface to TTF - -}; - #endif diff --git a/graf2d/graf/inc/TTFhandle.h b/graf2d/graf/inc/TTFhandle.h new file mode 100644 index 0000000000000..358a4381c2158 --- /dev/null +++ b/graf2d/graf/inc/TTFhandle.h @@ -0,0 +1,101 @@ +// @(#)root/graf:$Id$ +// Author: Sergey Linev 25/08/2026 + +/************************************************************************* + * Copyright (C) 1995-2026, Rene Brun and Fons Rademakers. * + * All rights reserved. * + * * + * For the licensing terms see $ROOTSYS/LICENSE. * + * For the list of contributors see $ROOTSYS/README/CREDITS. * + *************************************************************************/ + +#ifndef ROOT_TTFhandle +#define ROOT_TTFhandle + + +#include "Rtypes.h" +#include + +class TTF; + +class TTFhandle { + friend class TTF; + + private: + + struct GlyphStruct; + struct FontStruct; + + Bool_t fKerning = kTRUE; ///< use kerning (true by default) + Bool_t fSmoothing = kTRUE; ///< use anti-aliasing (true when >8 planes, false otherwise) + Bool_t fHinting = kFALSE; ///< use hinting (false by default) + + FontStruct *fFont = nullptr; ///< selected font + long fRotationXX = 0, fRotationXY = 0; ///< rotation matrix members + std::vector fGlyphs; ///< glyphs + Int_t fAscent = 0; ///< string ascent, used to compute Y alignment + long xMin = 0, yMin = 0, xMax = 0, yMax = 0; ///< boundaries + Int_t fTBlankW = 0; ///< trailing blanks width + Int_t fWidth = 0; ///< string width, used to compute X alignment + + UInt_t CharToUnicode(UInt_t code); + void ComputeTrailingBlanksWidth(Int_t n); + + Int_t SelectFontHandle(Int_t arg, const char *name = nullptr); + + /// Thread-local wrapper to the FreeType library + struct FT_Library_Wrapper; + static thread_local FT_Library_Wrapper fFT_Library; + + void FillTTFGlypths(void *vect); + + public: + TTFhandle(); + virtual ~TTFhandle(); + + void SetSmoothing(Bool_t state) { fSmoothing = state; } + void SetHinting(Bool_t state) { fHinting = state; } + void SetKerning(Bool_t state) { fKerning = state; } + void SetTextFont(Font_t fontnumber); + Int_t SetTextFont(const char *fontname, Int_t italic = 0); + Bool_t SetTextSize(Float_t textsize); + void SetRotationMatrix(Float_t angle); + + Bool_t GetSmoothing() const { return fSmoothing; } + Bool_t GetHinting() const { return fHinting; } + Bool_t GetKerning() const { return fKerning; } + void* GetFontFace() const; + + void PrepareString(const char *string); + void PrepareString(const wchar_t *string); + void LayoutGlyphs(); + + Int_t GetGlyphsWidth() const; + Int_t GetGlyphsHeight() const; + + Int_t GetAscent() const { return fAscent; } + Int_t GetTrailingBlanksWidth() const { return fTBlankW; } + Int_t GetWidth() const { return fWidth; } + long GetBoxXMin() const { return xMin; } + long GetBoxXMax() const { return xMax; } + long GetBoxYMin() const { return yMin; } + long GetBoxYMax() const { return yMax; } + + UInt_t GetNumGlyphs() const; + Bool_t ApplyAlignRotate(Int_t &px, Int_t &py, Int_t align, Int_t pad_width, Int_t pad_height); + Bool_t GetGlyphData(UInt_t n, Int_t &offx, Int_t &offy, UChar_t *&buffer, UInt_t &width, UInt_t &rows, UInt_t &pitch); + void CleanupGlyphs(); + + void GetTextExtent(UInt_t &w, UInt_t &h, const char *text); + void GetTextExtent(UInt_t &w, UInt_t &h, const wchar_t *text); + void GetTextAdvance(UInt_t &a, const char *text); + + void Version(Int_t &major, Int_t &minor, Int_t &patch); + + static Bool_t Init(); + + ClassDef(TTFhandle, 0) // Dynamic interface to TTF + +}; + +#endif diff --git a/graf2d/graf/src/TMathText.cxx b/graf2d/graf/src/TMathText.cxx index e78fdffeadaea..88b95dc271e9c 100644 --- a/graf2d/graf/src/TMathText.cxx +++ b/graf2d/graf/src/TMathText.cxx @@ -14,7 +14,7 @@ #include #include FT_FREETYPE_H #include FT_GLYPH_H -#include "TTF.h" +#include "TTFhandle.h" #include "TMathText.h" #include "TMath.h" #include "TVirtualPad.h" @@ -223,7 +223,7 @@ class TMathTextRenderer : public TText, public TAttFill, h.SetTextFont(is_cyrillic_or_cjk(character) ? root_cjk_face_number() : root_face_number(family)); h.SetTextSize(_current_font_size[family] * _pad_scale); - auto font_face = h.GetFontFace(); + auto font_face = (FT_Face) h.GetFontFace(); if (!font_face || font_face->units_per_EM == 0) return mathtext::bounding_box_t(0, 0, 0, 0, 0, 0); diff --git a/graf2d/graf/src/TTF.cxx b/graf2d/graf/src/TTF.cxx index c026beb98de4f..a11abe4bac104 100644 --- a/graf2d/graf/src/TTF.cxx +++ b/graf2d/graf/src/TTF.cxx @@ -11,14 +11,6 @@ *************************************************************************/ -/** \class TTFhandle -\ingroup BasicGraphics - -Dynamic handle to work with freetype 2 library. -in ROOT7 TTFhandle will be renamed into TTF class -*/ - - #include #include FT_FREETYPE_H #include FT_GLYPH_H @@ -29,599 +21,6 @@ in ROOT7 TTFhandle will be renamed into TTF class #include "TMath.h" #include "TError.h" -// to scale fonts to the same size as the old TT version -const Float_t kScale = 0.93376068; - -Bool_t TTFhandle::fgHinting = kFALSE; -Bool_t TTFhandle::fgSmoothing = kTRUE; - -/// Free all resources of this glyph. -TTF::TTGlyph::~TTGlyph() -{ - FT_Done_Glyph(fImage); -} - -struct TTFontHandle { - std::string name; - FT_Face face = nullptr; - FT_CharMap charmap = nullptr; - bool is_symbol() const - { - return (name == "wingding.ttf") || (name.find("symbol.ttf") == 0); - } -}; - -//////////////////////////////////////////////////////////////////////////////// -/// Thread-local wrapper to the freetype library. -/// The library gets initialised on demand when Get() is called. -/// It auto-destructs when the thread exits. -struct TTFhandle::FT_Library_Wrapper { - FT_Library _library = nullptr; - FT_Library_Wrapper() = default; - FT_Library_Wrapper(FT_Library_Wrapper const &) = delete; - FT_Library_Wrapper(FT_Library_Wrapper &&) = delete; - FT_Library_Wrapper &operator=(FT_Library_Wrapper const &) = delete; - FT_Library_Wrapper &operator=(FT_Library_Wrapper &&) = delete; - - FT_Library Get() - { - if (!_library && FT_Init_FreeType(&_library) != 0) { - Error("TTF.cxx", "error initializing FreeType"); - _library = nullptr; - } - return _library; - } - - ~FT_Library_Wrapper() - { - if (_library) - FT_Done_FreeType(_library); - } -}; - -thread_local TTFhandle::FT_Library_Wrapper TTFhandle::fFT_Library; - -//////////////////////////////////////////////////////////////////////////////// - -TTFhandle::TTFhandle() -{ - // Ensure that there's a freetype library in our thread - fFT_Library.Get(); -} - -//////////////////////////////////////////////////////////////////////////////// - -TTFhandle::~TTFhandle() -{ - CleanupGlyphs(); -} - -//////////////////////////////////////////////////////////////////////////////// -/// Map char to unicode. Returns 0 in case no mapping exists. - -UInt_t TTFhandle::CharToUnicode(UInt_t code) -{ - FT_Face face = fFont ? fFont->face : nullptr; - if (!face) - return 0; - - if (!fFont->charmap) { - Int_t n = face->num_charmaps; - for (Int_t i = 0; i < n; i++) { - FT_CharMap charmap = face->charmaps[i]; - auto platform = charmap->platform_id; - auto encoding = charmap->encoding_id; - if ((platform == 3 && encoding == 1) || - (platform == 0 && encoding == 0) || - (platform == 1 && encoding == 0 && fFont->is_symbol())) - { - fFont->charmap = charmap; - if (FT_Set_Charmap(face, charmap)) - Error("TTF::CharToUnicode", "error in FT_Set_CharMap"); - break; - } - } - } - return FT_Get_Char_Index(face, (FT_ULong)code); -} - -//////////////////////////////////////////////////////////////////////////////// -/// Compute the trailing blanks width. It is use to compute the text width in GetTextExtent -/// `n` is the number of trailing blanks in a string. - -void TTFhandle::ComputeTrailingBlanksWidth(Int_t n) -{ - fTBlankW = 0; - if (n && fFont) { - FT_Face face = fFont->face; - char space = ' '; - FT_UInt load_flags = FT_LOAD_DEFAULT; - if (!fgHinting) load_flags |= FT_LOAD_NO_HINTING; - FT_Load_Char(face, space, load_flags); - - FT_GlyphSlot slot = face->glyph; - FT_Pos advance_x = slot->advance.x; - Int_t advance_x_pixels = advance_x >> 6; - - fTBlankW = advance_x_pixels * n; - } -} - -//////////////////////////////////////////////////////////////////////////////// -/// Get width (w) and height (h) when text is horizontal. - -void TTFhandle::GetTextExtent(UInt_t &w, UInt_t &h, const char *text) -{ - SetRotationMatrix(0); - PrepareString(text); - LayoutGlyphs(); - Int_t Xoff = 0; if (fCBox.xMin < 0) Xoff = -fCBox.xMin; - Int_t Yoff = 0; if (fCBox.yMin < 0) Yoff = -fCBox.yMin; - w = fCBox.xMax + Xoff + GetTrailingBlanksWidth(); - h = fCBox.yMax + Yoff; - CleanupGlyphs(); -} - -//////////////////////////////////////////////////////////////////////////////// -/// Get advance (a) when text is horizontal. - -void TTFhandle::GetTextAdvance(UInt_t &a, const char *text) -{ - SetRotationMatrix(0); - PrepareString(text); - LayoutGlyphs(); - a = GetWidth() >> 6; - CleanupGlyphs(); -} - -//////////////////////////////////////////////////////////////////////////////// -/// Get width (w) and height (h) when text is horizontal. - -void TTFhandle::GetTextExtent(UInt_t &w, UInt_t &h, const wchar_t *text) -{ - SetRotationMatrix(0); - PrepareString(text); - LayoutGlyphs(); - Int_t Xoff = 0; if (fCBox.xMin < 0) Xoff = -fCBox.xMin; - Int_t Yoff = 0; if (fCBox.yMin < 0) Yoff = -fCBox.yMin; - w = fCBox.xMax + Xoff + GetTrailingBlanksWidth(); - h = fCBox.yMax + Yoff; - CleanupGlyphs(); -} - -//////////////////////////////////////////////////////////////////////////////// -/// Compute the glyphs positions, fgAscent and fgWidth (needed for alignment). -/// Perform the Glyphs transformation. -/// Compute the string control box. -/// If required take the "kerning" into account. -/// SetRotation and PrepareString should have been called before. - -void TTFhandle::LayoutGlyphs() -{ - FT_Vector origin; - FT_UInt load_flags; - FT_UInt prev_index = 0; - - fAscent = 0; - fWidth = 0; - - load_flags = FT_LOAD_DEFAULT; - if (!fgHinting) - load_flags |= FT_LOAD_NO_HINTING; - - fCBox.xMin = fCBox.yMin = 32000; - fCBox.xMax = fCBox.yMax = -32000; - - FT_Face face = fFont ? fFont->face : nullptr; - if (!face) - return; - - for (auto &glyph : fGlyphs) { - - // compute glyph origin - if (fKerning) { - if (prev_index) { - FT_Vector kern; - FT_Get_Kerning(face, prev_index, glyph.fIndex, - fgHinting ? ft_kerning_default : ft_kerning_unfitted, - &kern); - fWidth += kern.x; - } - prev_index = glyph.fIndex; - } - - origin.x = fWidth; - origin.y = 0; - - // clear existing image if there is one - if (glyph.fImage) { - FT_Done_Glyph(glyph.fImage); - glyph.fImage = nullptr; - } - - // load the glyph image (in its native format) - if (FT_Load_Glyph(face, glyph.fIndex, load_flags)) - continue; - - // extract the glyph image - if (FT_Get_Glyph(face->glyph, &glyph.fImage)) - continue; - - glyph.fPos = origin; - fWidth += face->glyph->advance.x; - fAscent = TMath::Max((Int_t)(face->glyph->metrics.horiBearingY), fAscent); - - // transform the glyphs - FT_Vector_Transform(&glyph.fPos, fRotMatrix.get()); - if (FT_Glyph_Transform(glyph.fImage, fRotMatrix.get(), &glyph.fPos)) - continue; - - // compute the string control box - FT_BBox bbox; - FT_Glyph_Get_CBox(glyph.fImage, ft_glyph_bbox_pixels, &bbox); - if (bbox.xMin < fCBox.xMin) fCBox.xMin = bbox.xMin; - if (bbox.yMin < fCBox.yMin) fCBox.yMin = bbox.yMin; - if (bbox.xMax > fCBox.xMax) fCBox.xMax = bbox.xMax; - if (bbox.yMax > fCBox.yMax) fCBox.yMax = bbox.yMax; - } -} - -//////////////////////////////////////////////////////////////////////////////// -/// Return bitmap for specified glyph - -FT_BitmapGlyph TTFhandle::GetGlyphBitmap(UInt_t n, Bool_t smooth) -{ - if (n >= fGlyphs.size()) - return nullptr; - - if (FT_Glyph_To_Bitmap(&fGlyphs[n].fImage, smooth || GetSmoothing() ? ft_render_mode_normal : ft_render_mode_mono, nullptr, 1)) - return nullptr; - - return (FT_BitmapGlyph) fGlyphs[n].fImage; -} - -//////////////////////////////////////////////////////////////////////////////// -/// Remove temporary data created by LayoutGlyphs - -void TTFhandle::CleanupGlyphs() -{ - fGlyphs.clear(); -} - -//////////////////////////////////////////////////////////////////////////////// -/// Put the characters in "string" in the "glyphs" array. - -void TTFhandle::PrepareString(const char *string) -{ - CleanupGlyphs(); - - const unsigned char *p = (const unsigned char*) string; - - Int_t NbTBlank = 0; // number of trailing blanks - - while (*p) { - UInt_t index = CharToUnicode((FT_ULong)*p); - if (index != 0) - fGlyphs.emplace_back(index); - if (*p == ' ') - NbTBlank++; - else - NbTBlank = 0; - p++; - } - - ComputeTrailingBlanksWidth(NbTBlank); -} - -//////////////////////////////////////////////////////////////////////////////// -/// Put the characters in "string" in the "glyphs" array. - -void TTFhandle::PrepareString(const wchar_t *string) -{ - CleanupGlyphs(); - - FT_Face face = fFont ? fFont->face : nullptr; - if (!face) - return; - - const wchar_t *p = string; - - Int_t NbTBlank = 0; // number of trailing blanks - - while (*p) { - UInt_t index = FT_Get_Char_Index(face, (FT_ULong) *p); - if (index != 0) - fGlyphs.emplace_back(index); - if (*p == ' ') - NbTBlank++; - else - NbTBlank = 0; - p++; - } - - ComputeTrailingBlanksWidth(NbTBlank); -} - -//////////////////////////////////////////////////////////////////////////////// -/// Return current font index - -FT_Face TTFhandle::GetFontFace() const -{ - return fFont ? fFont->face : nullptr; -} - -//////////////////////////////////////////////////////////////////////////////// -/// Set the rotation matrix used to rotate the font outlines. - -void TTFhandle::SetRotationMatrix(Float_t angle) -{ - Float_t rangle = angle * TMath::Pi() / 180.; // Angle in radian -#if defined(FREETYPE_PATCH) && \ - (FREETYPE_MAJOR == 2) && (FREETYPE_MINOR == 1) && (FREETYPE_PATCH == 2) - Float_t sin = TMath::Sin(rangle); - Float_t cos = TMath::Cos(rangle); -#else - Float_t sin = TMath::Sin(-rangle); - Float_t cos = TMath::Cos(-rangle); -#endif - - if (!fRotMatrix) - fRotMatrix = std::make_unique(); - - fRotMatrix->xx = (FT_Fixed) (cos * (1<<16)); - fRotMatrix->xy = (FT_Fixed) (sin * (1<<16)); - fRotMatrix->yx = -fRotMatrix->xy; - fRotMatrix->yy = fRotMatrix->xx; -} - -//////////////////////////////////////////////////////////////////////////////// -/// Return thread_local instance of TTFontHandle for speified font - -Int_t TTFhandle::SelectFontHandle(Int_t arg, const char *name) -{ - thread_local std::map _fonts; - - fFont = nullptr; - - if (arg == 111) { - // select any existing font, fallback solution for some errors in SetTextFont - if (!_fonts.empty()) - fFont = &(_fonts.begin()->second); - Warning("TTFhandle::SetTextFont", "%s, using %s", name, fFont ? fFont->name.c_str() : ""); - return fFont ? 0 : 1; - } - - if (arg >= 0) { - auto iter = _fonts.find(name); - if (iter != _fonts.end()) { - fFont = &iter->second; - return 0; - } - if (arg == 0) - return 1; - _fonts[name] = { name, nullptr, nullptr }; - fFont = &_fonts[name]; - return 0; - } - - for (auto &font : _fonts) { - if (font.second.face) { - FT_Done_Face(font.second.face); - font.second.face = nullptr; - } - } - _fonts.clear(); - return 0; -} - - -//////////////////////////////////////////////////////////////////////////////// -/// Set text font to specified name. -/// - font : font name -/// - italic : the fonts should be slanted. Used for symbol font. -/// -/// Set text font to specified name. This function returns 0 if -/// the specified font is found, 1 if not. - -Int_t TTFhandle::SetTextFont(const char *fontname, Int_t italic) -{ - fFont = nullptr; - - if (!fontname || !*fontname) - return SelectFontHandle(111, "no font name specified"); - - const char *basename = gSystem->BaseName(fontname); - - if (SelectFontHandle(1, TString::Format("%s%s", basename, italic ? ".italic" : ""))) { - Fatal("SetTextFont", "Fail to create font handle for font %s", basename); - return 1; - } - - // font face exists and initialized - if (fFont->face) - return 0; - - auto lib = fFT_Library.Get(); - if (!lib) { - Error("SetTextFont", "no free type library initialized"); - return 1; - } - - // try to load font (font must be in Root.TTFontPath resource) - const char *ttpath = gEnv->GetValue("Root.TTFontPath", TROOT::GetTTFFontDir()); - - TString fname = fontname; - const char *ttfont = gSystem->FindFile(ttpath, fname, kReadPermission); - - if (!ttfont) - return SelectFontHandle(111, TString::Format("font file %s not found in path %s", fontname, ttpath)); - - if (FT_New_Face(lib, ttfont, 0, &fFont->face)) - return SelectFontHandle(111, TString::Format("error loading font %s", ttfont)); - - if (italic) { - FT_Matrix slantMat; - slantMat.xx = (1 << 16); - slantMat.xy = ((1 << 16) >> 2); - slantMat.yx = 0; - slantMat.yy = (1 << 16); - FT_Set_Transform(fFont->face, &slantMat, nullptr); - } - - return 0; -} - -//////////////////////////////////////////////////////////////////////////////// -/// Set specified font. -/// List of the currently supported fonts (screen and PostScript) -/// -/// | Font number | TTF Names | PostScript/PDF Names | -/// |-------------|---------------------------|-------------------------------| -/// | 1 | Free Serif Italic | Times-Italic | -/// | 2 | Free Serif Bold | Times-Bold | -/// | 3 | Free Serif Bold Italic | Times-BoldItalic | -/// | 4 | Tex Gyre Regular | Helvetica | -/// | 5 | Tex Gyre Italic | Helvetica-Oblique | -/// | 6 | Tex Gyre Bold | Helvetica-Bold | -/// | 7 | Tex Gyre Bold Italic | Helvetica-BoldOblique | -/// | 8 | Free Mono | Courier | -/// | 9 | Free Mono Oblique | Courier-Oblique | -/// | 10 | Free Mono Bold | Courier-Bold | -/// | 11 | Free Mono Bold Oblique | Courier-BoldOblique | -/// | 12 | Symbol | Symbol | -/// | 13 | Free Serif | Times-Roman | -/// | 14 | Wingdings | ZapfDingbats | - -void TTFhandle::SetTextFont(Font_t fontnumber) -{ - // Added by cholm for use of DFSG - fonts - based on Kevins fix. - // Table of Microsoft and (for non-MSFT operating systems) backup - // FreeFont TTF fonts. - static const char *fonttable[][2] = { - { "Root.TTFont.0", "FreeSansBold.otf" }, - { "Root.TTFont.1", "FreeSerifItalic.otf" }, - { "Root.TTFont.2", "FreeSerifBold.otf" }, - { "Root.TTFont.3", "FreeSerifBoldItalic.otf" }, - { "Root.TTFont.4", "texgyreheros-regular.otf" }, - { "Root.TTFont.5", "texgyreheros-italic.otf" }, - { "Root.TTFont.6", "texgyreheros-bold.otf" }, - { "Root.TTFont.7", "texgyreheros-bolditalic.otf" }, - { "Root.TTFont.8", "FreeMono.otf" }, - { "Root.TTFont.9", "FreeMonoOblique.otf" }, - { "Root.TTFont.10", "FreeMonoBold.otf" }, - { "Root.TTFont.11", "FreeMonoBoldOblique.otf" }, - { "Root.TTFont.12", "symbol.ttf" }, - { "Root.TTFont.13", "FreeSerif.otf" }, - { "Root.TTFont.14", "wingding.ttf" }, - { "Root.TTFont.15", "symbol.ttf" }, - { "Root.TTFont.STIXGen", "STIXGeneral.otf" }, - { "Root.TTFont.STIXGenIt", "STIXGeneralItalic.otf" }, - { "Root.TTFont.STIXGenBd", "STIXGeneralBol.otf" }, - { "Root.TTFont.STIXGenBdIt", "STIXGeneralBolIta.otf" }, - { "Root.TTFont.STIXSiz1Sym", "STIXSiz1Sym.otf" }, - { "Root.TTFont.STIXSiz1SymBd", "STIXSiz1SymBol.otf" }, - { "Root.TTFont.STIXSiz2Sym", "STIXSiz2Sym.otf" }, - { "Root.TTFont.STIXSiz2SymBd", "STIXSiz2SymBol.otf" }, - { "Root.TTFont.STIXSiz3Sym", "STIXSiz3Sym.otf" }, - { "Root.TTFont.STIXSiz3SymBd", "STIXSiz3SymBol.otf" }, - { "Root.TTFont.STIXSiz4Sym", "STIXSiz4Sym.otf" }, - { "Root.TTFont.STIXSiz4SymBd", "STIXSiz4SymBol.otf" }, - { "Root.TTFont.STIXSiz5Sym", "STIXSiz5Sym.otf" }, - { "Root.TTFont.ME", "DroidSansFallback.ttf" }, - { "Root.TTFont.CJKMing", "DroidSansFallback.ttf" }, - { "Root.TTFont.CJKGothic", "DroidSansFallback.ttf" } - }; - - static int fontset = -1; - int thisset = fontset; - - int fontid = fontnumber / 10; - if (fontid < 0 || fontid > 31) fontid = 0; - - if (thisset == -1) { - // try to load font (font must be in Root.TTFontPath resource) - // to see which fontset we have available - const char *ttpath = gEnv->GetValue("Root.TTFontPath", - TROOT::GetTTFFontDir()); - TString fname = gEnv->GetValue(fonttable[fontid][0], fonttable[fontid][1]); - const char *ttfont = gSystem->FindFile(ttpath, fname, kReadPermission); - thisset = ttfont ? 0 : 1; - } - Int_t italic = fontid == 15 ? 1 : 0; - auto ret = SetTextFont(gEnv->GetValue(fonttable[fontid][thisset], fonttable[fontid][1]), italic); - - // Do not define font set is we're loading the symbol.ttf - it's - // the same in both cases. - if (ret == 0 && fontid != 12) - fontset = thisset; -} - -//////////////////////////////////////////////////////////////////////////////// -/// Set current text size. - -Bool_t TTFhandle::SetTextSize(Float_t textsize) -{ - if (textsize < 0) - return kFALSE; - - if (!fFont || !fFont->face) { - Error("TTFhandle::SetTextSize", "current font not selected"); - return kFALSE; - } - - Int_t tsize = (Int_t)(textsize*kScale+0.5) << 6; - FT_Error err = FT_Set_Char_Size(fFont->face, tsize, tsize, 72, 72); - - if (err) - Error("TTFhandle::SetTextSize", "error in FT_Set_Char_Size: 0x%x (input size %f, calc. size 0x%x)", err, textsize, tsize); - - return !err; -} - -//////////////////////////////////////////////////////////////////////////////// - -void TTFhandle::Version(Int_t &major, Int_t &minor, Int_t &patch) -{ - FT_Library_Version(fFT_Library.Get(), &major, &minor, &patch); -} - - -//////////////////////////////////////////////////////////////////////////////// - -Bool_t TTFhandle::Init() -{ - return fFT_Library.Get() != nullptr; -} - - -//////////////////////////////////////////////////////////////////////////////// - -Bool_t TTFhandle::GetHinting() -{ - return fgHinting; -} - -//////////////////////////////////////////////////////////////////////////////// - -Bool_t TTFhandle::GetSmoothing() -{ - return fgSmoothing; -} - -//////////////////////////////////////////////////////////////////////////////// - -void TTFhandle::SetHinting(Bool_t state) -{ - fgHinting = state; -} - -//////////////////////////////////////////////////////////////////////////////// - -void TTFhandle::SetSmoothing(Bool_t state) -{ - fgSmoothing = state; -} - /** \class TTF \ingroup BasicGraphics @@ -629,7 +28,7 @@ void TTFhandle::SetSmoothing(Bool_t state) Interface to the freetype 2 library. Implements old static API. Unitl ROOT7 just redirects to static TTFhandle instance, -then TTFhandle will be renamed into TTF class +then only TTFhandle class will remains */ thread_local TTF gCleanupTTF; // Allows to call "Cleanup" at the end of the session @@ -655,7 +54,7 @@ void TTF::Init() Bool_t TTF::GetHinting() { - return TTFhandle::GetHinting(); + return fgHandle ? fgHandle->GetHinting() : kFALSE; } //////////////////////////////////////////////////////////////////////////////// @@ -669,7 +68,7 @@ Bool_t TTF::GetKerning() Bool_t TTF::GetSmoothing() { - return TTFhandle::GetSmoothing(); + return fgHandle ? fgHandle->GetSmoothing() : kTRUE; } //////////////////////////////////////////////////////////////////////////////// @@ -704,7 +103,15 @@ Int_t TTF::GetNumGlyphs() FT_Matrix *TTF::GetRotMatrix() { - return fgHandle ? fgHandle->GetRotMatrix() : nullptr; + static FT_Matrix m; + if (fgHandle && (fgHandle->fRotationXX || fgHandle->fRotationXX)) { + m.xx = m.yy = fgHandle->fRotationXX; + m.xy = fgHandle->fRotationXY; + m.yx = -fgHandle->fRotationXY; + return &m; + } + + return nullptr; } //////////////////////////////////////////////////////////////////////////////// @@ -718,15 +125,26 @@ Int_t TTF::GetTrailingBlanksWidth() const FT_BBox &TTF::GetBox() { - static FT_BBox dummy; - return fgHandle ? fgHandle->GetBox() : dummy; + static FT_BBox bbox; + if (fgHandle) { + bbox.xMin = fgHandle->GetBoxXMin(); + bbox.yMin = fgHandle->GetBoxYMin(); + bbox.xMax = fgHandle->GetBoxXMax(); + bbox.yMax = fgHandle->GetBoxYMax(); + } + return bbox; } //////////////////////////////////////////////////////////////////////////////// TTF::TTGlyph *TTF::GetGlyphs() { - return fgHandle ? fgHandle->GetGlyphs() : nullptr; + static std::vector vect; + + if (fgHandle) + fgHandle->FillTTFGlypths(&vect); + + return vect.data(); } //////////////////////////////////////////////////////////////////////////////// @@ -752,7 +170,8 @@ void TTF::SetRotationMatrix(Float_t angle) void TTF::SetHinting(Bool_t state) { - TTFhandle::SetHinting(state); + Init(); + fgHandle->SetHinting(state); } //////////////////////////////////////////////////////////////////////////////// @@ -769,7 +188,8 @@ void TTF::SetKerning(Bool_t state) void TTF::SetSmoothing(Bool_t state) { - TTFhandle::SetSmoothing(state); + Init(); + fgHandle->SetSmoothing(state); } //////////////////////////////////////////////////////////////////////////////// diff --git a/graf2d/graf/src/TTFhandle.cxx b/graf2d/graf/src/TTFhandle.cxx new file mode 100644 index 0000000000000..5ab03c5962dd9 --- /dev/null +++ b/graf2d/graf/src/TTFhandle.cxx @@ -0,0 +1,729 @@ +// @(#)root/graf:$Id$ +// Author: Sergey Linev 25/08/2026 + +/************************************************************************* + * Copyright (C) 1995-2026, Rene Brun and Fons Rademakers. * + * All rights reserved. * + * * + * For the licensing terms see $ROOTSYS/LICENSE. * + * For the list of contributors see $ROOTSYS/README/CREDITS. * + *************************************************************************/ + + +/** \class TTFhandle +\ingroup BasicGraphics + +Dynamic handle to work with freetype 2 library. +in ROOT7 TTFhandle will completely replace TTF class +*/ + + +#include +#include FT_FREETYPE_H +#include FT_GLYPH_H + +#include "TROOT.h" +#include "TTF.h" +#include "TSystem.h" +#include "TEnv.h" +#include "TMath.h" +#include "TError.h" + +// to scale fonts to the same size as the old TT version +const Float_t kScale = 0.93376068; + +struct TTFhandle::GlyphStruct { +public: + UInt_t fIndex{0}; ///< glyph index in face + FT_Vector fPos; ///< position of glyph origin + FT_Glyph fImage{nullptr}; ///< glyph image + GlyphStruct(UInt_t indx = 0) : fIndex(indx) {} + ~GlyphStruct() { FT_Done_Glyph(fImage); } +}; + + +struct TTFhandle::FontStruct { + std::string name; + FT_Face face = nullptr; + FT_CharMap charmap = nullptr; + bool is_symbol() const + { + return (name == "wingding.ttf") || (name.find("symbol.ttf") == 0); + } +}; + +//////////////////////////////////////////////////////////////////////////////// +/// Thread-local wrapper to the freetype library. +/// The library gets initialised on demand when Get() is called. +/// It auto-destructs when the thread exits. +struct TTFhandle::FT_Library_Wrapper { + FT_Library _library = nullptr; + FT_Library_Wrapper() = default; + FT_Library_Wrapper(FT_Library_Wrapper const &) = delete; + FT_Library_Wrapper(FT_Library_Wrapper &&) = delete; + FT_Library_Wrapper &operator=(FT_Library_Wrapper const &) = delete; + FT_Library_Wrapper &operator=(FT_Library_Wrapper &&) = delete; + + FT_Library Get() + { + if (!_library && FT_Init_FreeType(&_library) != 0) { + Error("TTF.cxx", "error initializing FreeType"); + _library = nullptr; + } + return _library; + } + + ~FT_Library_Wrapper() + { + if (_library) + FT_Done_FreeType(_library); + } +}; + +thread_local TTFhandle::FT_Library_Wrapper TTFhandle::fFT_Library; + +//////////////////////////////////////////////////////////////////////////////// + +TTFhandle::TTFhandle() +{ + // Ensure that there's a freetype library in our thread + fFT_Library.Get(); +} + +//////////////////////////////////////////////////////////////////////////////// + +TTFhandle::~TTFhandle() +{ + CleanupGlyphs(); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Map char to unicode. Returns 0 in case no mapping exists. + +UInt_t TTFhandle::CharToUnicode(UInt_t code) +{ + FT_Face face = fFont ? fFont->face : nullptr; + if (!face) + return 0; + + if (!fFont->charmap) { + Int_t n = face->num_charmaps; + for (Int_t i = 0; i < n; i++) { + FT_CharMap charmap = face->charmaps[i]; + auto platform = charmap->platform_id; + auto encoding = charmap->encoding_id; + if ((platform == 3 && encoding == 1) || + (platform == 0 && encoding == 0) || + (platform == 1 && encoding == 0 && fFont->is_symbol())) + { + fFont->charmap = charmap; + if (FT_Set_Charmap(face, charmap)) + Error("TTF::CharToUnicode", "error in FT_Set_CharMap"); + break; + } + } + } + return FT_Get_Char_Index(face, (FT_ULong)code); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Compute the trailing blanks width. It is use to compute the text width in GetTextExtent +/// `n` is the number of trailing blanks in a string. + +void TTFhandle::ComputeTrailingBlanksWidth(Int_t n) +{ + fTBlankW = 0; + if (n && fFont) { + FT_Face face = fFont->face; + char space = ' '; + FT_UInt load_flags = FT_LOAD_DEFAULT; + if (!fHinting) load_flags |= FT_LOAD_NO_HINTING; + FT_Load_Char(face, space, load_flags); + + FT_GlyphSlot slot = face->glyph; + FT_Pos advance_x = slot->advance.x; + Int_t advance_x_pixels = advance_x >> 6; + + fTBlankW = advance_x_pixels * n; + } +} + +//////////////////////////////////////////////////////////////////////////////// +/// Get width (w) and height (h) when text is horizontal. + +void TTFhandle::GetTextExtent(UInt_t &w, UInt_t &h, const char *text) +{ + SetRotationMatrix(0); + PrepareString(text); + LayoutGlyphs(); + w = GetGlyphsWidth() + GetTrailingBlanksWidth(); + h = GetGlyphsHeight(); + CleanupGlyphs(); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Get advance (a) when text is horizontal. + +void TTFhandle::GetTextAdvance(UInt_t &a, const char *text) +{ + SetRotationMatrix(0); + PrepareString(text); + LayoutGlyphs(); + a = GetWidth() >> 6; + CleanupGlyphs(); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Get width (w) and height (h) when text is horizontal. + +void TTFhandle::GetTextExtent(UInt_t &w, UInt_t &h, const wchar_t *text) +{ + SetRotationMatrix(0); + PrepareString(text); + LayoutGlyphs(); + w = GetGlyphsWidth() + GetTrailingBlanksWidth(); + h = GetGlyphsHeight(); + CleanupGlyphs(); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Compute the glyphs positions, fgAscent and fgWidth (needed for alignment). +/// Perform the Glyphs transformation. +/// Compute the string control box. +/// If required take the "kerning" into account. +/// SetRotation and PrepareString should have been called before. + +void TTFhandle::LayoutGlyphs() +{ + FT_Vector origin; + FT_UInt load_flags; + FT_UInt prev_index = 0; + + fAscent = 0; + fWidth = 0; + + load_flags = FT_LOAD_DEFAULT; + if (!fHinting) + load_flags |= FT_LOAD_NO_HINTING; + + xMin = yMin = 32000; + xMax = yMax = -32000; + + FT_Face face = fFont ? fFont->face : nullptr; + if (!face) + return; + + for (auto &glyph : fGlyphs) { + + // compute glyph origin + if (fKerning) { + if (prev_index) { + FT_Vector kern; + FT_Get_Kerning(face, prev_index, glyph.fIndex, + fHinting ? ft_kerning_default : ft_kerning_unfitted, + &kern); + fWidth += kern.x; + } + prev_index = glyph.fIndex; + } + + origin.x = fWidth; + origin.y = 0; + + // clear existing image if there is one + if (glyph.fImage) { + FT_Done_Glyph(glyph.fImage); + glyph.fImage = nullptr; + } + + // load the glyph image (in its native format) + if (FT_Load_Glyph(face, glyph.fIndex, load_flags)) + continue; + + // extract the glyph image + if (FT_Get_Glyph(face->glyph, &glyph.fImage)) + continue; + + glyph.fPos = origin; + fWidth += face->glyph->advance.x; + fAscent = TMath::Max((Int_t)(face->glyph->metrics.horiBearingY), fAscent); + + // transform the glyphs + FT_Matrix m, *matrix_arg = nullptr; + + if (fRotationXX || fRotationXY) { + m.xx = m.yy = fRotationXX; + m.xy = fRotationXY; + m.yx = -fRotationXY; + matrix_arg = &m; + } + + FT_Vector_Transform(&glyph.fPos, matrix_arg); + if (FT_Glyph_Transform(glyph.fImage, matrix_arg, &glyph.fPos)) + continue; + + // compute the string control box + FT_BBox bbox; + FT_Glyph_Get_CBox(glyph.fImage, ft_glyph_bbox_pixels, &bbox); + if (bbox.xMin < xMin) xMin = bbox.xMin; + if (bbox.yMin < yMin) yMin = bbox.yMin; + if (bbox.xMax > xMax) xMax = bbox.xMax; + if (bbox.yMax > yMax) yMax = bbox.yMax; + } +} + +//////////////////////////////////////////////////////////////////////////////// +/// return number of glyphs + +UInt_t TTFhandle::GetNumGlyphs() const +{ + return fGlyphs.size(); +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Fill vector from TTF class +/// Only for backward compatibility of old API + +void TTFhandle::FillTTFGlypths(void *data) +{ + auto &vect = *((std::vector *) data); + vect.resize(fGlyphs.size()); + for (std::size_t i = 0; i < fGlyphs.size(); ++i) { + auto &tgt = vect[i]; + auto &src = fGlyphs[i]; + + tgt.fIndex = src.fIndex; + tgt.fPos = src.fPos; + tgt.fImage = src.fImage; + } +} + +//////////////////////////////////////////////////////////////////////////////// +/// Apply align and configured rotation matrix to text position +/// px and py will be shifted to the place where glyph drawing can be started +/// Method returns false when glyphs not need to be drawn +/// while position is outside of specified pad dimentsions + +Bool_t TTFhandle::ApplyAlignRotate(Int_t &px, Int_t &py, Int_t align, Int_t pad_width, Int_t pad_height) +{ + Int_t txalh = align / 10; + Int_t txalv = align % 10; + + FT_Vector alignVector; + + switch (txalh) { + case 2: alignVector.x = GetWidth() / 2; break; //center + case 3: alignVector.x = GetWidth(); break; //right + default: alignVector.x = 0; break; // left + } + + switch (txalv) { + case 2: alignVector.y = GetAscent() / 2; break; // middle + case 3: alignVector.y = GetAscent(); break; //top + default: alignVector.y = 0; break; //bottom + } + + FT_Matrix m, *matrix_arg = nullptr; + + if (fRotationXX || fRotationXY) { + m.xx = m.yy = fRotationXX; + m.xy = fRotationXY; + m.yx = -fRotationXY; + matrix_arg = &m; + } + + FT_Vector_Transform(&alignVector, matrix_arg); + + Int_t Xoff = TMath::Max(0, (Int_t) -xMin); + Int_t Yoff = TMath::Max(0, (Int_t) -yMin); + Int_t w = GetGlyphsWidth(); + Int_t h = GetGlyphsHeight(); + + // If w or h is 0, very likely the string is only blank characters + if (w <= 0 || h <= 0) + return kFALSE; + + Int_t x1 = px - Xoff - (alignVector.x >> 6); + Int_t y1 = py + Yoff + (alignVector.y >> 6) - h; + + // If string falls outside window, there is probably no need to draw it. + if (x1 + w <= 0 || x1 >= pad_width || y1 + h <= 0 || y1 >= pad_height) + return kFALSE; + + // do not draw text, which size is significantly larger than available pad + if ((w > 10 * pad_width) || (h > 10 * pad_height)) + return kFALSE; + + px = x1; + py = y1; + return kTRUE; +} + +//////////////////////////////////////////////////////////////////////////////// +/// Returns width of all glyphs + +Int_t TTFhandle::GetGlyphsWidth() const +{ + return xMax + TMath::Max(0, (Int_t) -xMin); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Returns height of all glyphs + +Int_t TTFhandle::GetGlyphsHeight() const +{ + return yMax + TMath::Max(0, (Int_t) -yMin); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Returns data for glyph bitmap +/// Instead direct access to FT_BitmapGlyph one can obtain all relevant fields +/// Thus one do not requires work with TrueType classes directly +/// Return kFALSE when glyph not exists or if it width is zero + +Bool_t TTFhandle::GetGlyphData(UInt_t n, Int_t &offx, Int_t &offy, UChar_t *&buffer, UInt_t &width, UInt_t &rows, UInt_t &pitch) +{ + if (n >= fGlyphs.size()) + return kFALSE; + + if (FT_Glyph_To_Bitmap(&fGlyphs[n].fImage, GetSmoothing() ? ft_render_mode_normal : ft_render_mode_mono, nullptr, 1)) + return kFALSE; + + auto glyph = fGlyphs[n].fImage; + if (!glyph || (glyph->format != FT_GLYPH_FORMAT_BITMAP)) + return kFALSE; + + // 2. Safe to typecast to FT_BitmapGlyph + FT_BitmapGlyph bitmap_glyph = (FT_BitmapGlyph)glyph; + + auto &bmp = bitmap_glyph->bitmap; + if (!bmp.width) + return kFALSE; + + offx = TMath::Max(0, (Int_t) -xMin) + bitmap_glyph->left; + offy = yMax - bitmap_glyph->top; + + buffer = bmp.buffer; + width = bmp.width; + rows = bmp.rows; + pitch = bmp.pitch; + return kTRUE; +} + +//////////////////////////////////////////////////////////////////////////////// +/// Remove temporary data created by LayoutGlyphs + +void TTFhandle::CleanupGlyphs() +{ + fGlyphs.clear(); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Put the characters in "string" in the "glyphs" array. + +void TTFhandle::PrepareString(const char *string) +{ + CleanupGlyphs(); + + const unsigned char *p = (const unsigned char*) string; + + Int_t NbTBlank = 0; // number of trailing blanks + + while (*p) { + UInt_t index = CharToUnicode((FT_ULong)*p); + if (index != 0) + fGlyphs.emplace_back(index); + if (*p == ' ') + NbTBlank++; + else + NbTBlank = 0; + p++; + } + + ComputeTrailingBlanksWidth(NbTBlank); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Put the characters in "string" in the "glyphs" array. + +void TTFhandle::PrepareString(const wchar_t *string) +{ + CleanupGlyphs(); + + FT_Face face = fFont ? fFont->face : nullptr; + if (!face) + return; + + const wchar_t *p = string; + + Int_t NbTBlank = 0; // number of trailing blanks + + while (*p) { + UInt_t index = FT_Get_Char_Index(face, (FT_ULong) *p); + if (index != 0) + fGlyphs.emplace_back(index); + if (*p == ' ') + NbTBlank++; + else + NbTBlank = 0; + p++; + } + + ComputeTrailingBlanksWidth(NbTBlank); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Return current font index + +void *TTFhandle::GetFontFace() const +{ + return fFont ? (void *) fFont->face : nullptr; +} + +//////////////////////////////////////////////////////////////////////////////// +/// Set the rotation matrix used to rotate the font outlines. + +void TTFhandle::SetRotationMatrix(Float_t angle) +{ + fRotationXX = fRotationXY = 0; + if (!angle) + return; + + Float_t rangle = angle * TMath::Pi() / 180.; // Angle in radian +#if defined(FREETYPE_PATCH) && \ + (FREETYPE_MAJOR == 2) && (FREETYPE_MINOR == 1) && (FREETYPE_PATCH == 2) + Float_t sin = TMath::Sin(rangle); + Float_t cos = TMath::Cos(rangle); +#else + Float_t sin = TMath::Sin(-rangle); + Float_t cos = TMath::Cos(-rangle); +#endif + + fRotationXX = (FT_Fixed) (cos * (1<<16)); + fRotationXY = (FT_Fixed) (sin * (1<<16)); + +// fRotMatrix->xx = (FT_Fixed) (cos * (1<<16)); +// fRotMatrix->xy = (FT_Fixed) (sin * (1<<16)); +// fRotMatrix->yx = -fRotMatrix->xy; +// fRotMatrix->yy = fRotMatrix->xx; +} + +//////////////////////////////////////////////////////////////////////////////// +/// Return thread_local instance of FontStruct for speified font + +Int_t TTFhandle::SelectFontHandle(Int_t arg, const char *name) +{ + thread_local std::map _fonts; + + fFont = nullptr; + + if (arg == 111) { + // select any existing font, fallback solution for some errors in SetTextFont + if (!_fonts.empty()) + fFont = &(_fonts.begin()->second); + Warning("TTFhandle::SetTextFont", "%s, using %s", name, fFont ? fFont->name.c_str() : ""); + return fFont ? 0 : 1; + } + + if (arg >= 0) { + auto iter = _fonts.find(name); + if (iter != _fonts.end()) { + fFont = &iter->second; + return 0; + } + if (arg == 0) + return 1; + _fonts[name] = { name, nullptr, nullptr }; + fFont = &_fonts[name]; + return 0; + } + + for (auto &font : _fonts) { + if (font.second.face) { + FT_Done_Face(font.second.face); + font.second.face = nullptr; + } + } + _fonts.clear(); + return 0; +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Set text font to specified name. +/// - font : font name +/// - italic : the fonts should be slanted. Used for symbol font. +/// +/// Set text font to specified name. This function returns 0 if +/// the specified font is found, 1 if not. + +Int_t TTFhandle::SetTextFont(const char *fontname, Int_t italic) +{ + fFont = nullptr; + + if (!fontname || !*fontname) + return SelectFontHandle(111, "no font name specified"); + + const char *basename = gSystem->BaseName(fontname); + + if (SelectFontHandle(1, TString::Format("%s%s", basename, italic ? ".italic" : ""))) { + Fatal("SetTextFont", "Fail to create font handle for font %s", basename); + return 1; + } + + // font face exists and initialized + if (fFont->face) + return 0; + + auto lib = fFT_Library.Get(); + if (!lib) { + Error("SetTextFont", "no free type library initialized"); + return 1; + } + + // try to load font (font must be in Root.TTFontPath resource) + const char *ttpath = gEnv->GetValue("Root.TTFontPath", TROOT::GetTTFFontDir()); + + TString fname = fontname; + const char *ttfont = gSystem->FindFile(ttpath, fname, kReadPermission); + + if (!ttfont) + return SelectFontHandle(111, TString::Format("font file %s not found in path %s", fontname, ttpath)); + + if (FT_New_Face(lib, ttfont, 0, &fFont->face)) + return SelectFontHandle(111, TString::Format("error loading font %s", ttfont)); + + if (italic) { + FT_Matrix slantMat; + slantMat.xx = (1 << 16); + slantMat.xy = ((1 << 16) >> 2); + slantMat.yx = 0; + slantMat.yy = (1 << 16); + FT_Set_Transform(fFont->face, &slantMat, nullptr); + } + + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// +/// Set specified font. +/// List of the currently supported fonts (screen and PostScript) +/// +/// | Font number | TTF Names | PostScript/PDF Names | +/// |-------------|---------------------------|-------------------------------| +/// | 1 | Free Serif Italic | Times-Italic | +/// | 2 | Free Serif Bold | Times-Bold | +/// | 3 | Free Serif Bold Italic | Times-BoldItalic | +/// | 4 | Tex Gyre Regular | Helvetica | +/// | 5 | Tex Gyre Italic | Helvetica-Oblique | +/// | 6 | Tex Gyre Bold | Helvetica-Bold | +/// | 7 | Tex Gyre Bold Italic | Helvetica-BoldOblique | +/// | 8 | Free Mono | Courier | +/// | 9 | Free Mono Oblique | Courier-Oblique | +/// | 10 | Free Mono Bold | Courier-Bold | +/// | 11 | Free Mono Bold Oblique | Courier-BoldOblique | +/// | 12 | Symbol | Symbol | +/// | 13 | Free Serif | Times-Roman | +/// | 14 | Wingdings | ZapfDingbats | + +void TTFhandle::SetTextFont(Font_t fontnumber) +{ + // Added by cholm for use of DFSG - fonts - based on Kevins fix. + // Table of Microsoft and (for non-MSFT operating systems) backup + // FreeFont TTF fonts. + static const char *fonttable[][2] = { + { "Root.TTFont.0", "FreeSansBold.otf" }, + { "Root.TTFont.1", "FreeSerifItalic.otf" }, + { "Root.TTFont.2", "FreeSerifBold.otf" }, + { "Root.TTFont.3", "FreeSerifBoldItalic.otf" }, + { "Root.TTFont.4", "texgyreheros-regular.otf" }, + { "Root.TTFont.5", "texgyreheros-italic.otf" }, + { "Root.TTFont.6", "texgyreheros-bold.otf" }, + { "Root.TTFont.7", "texgyreheros-bolditalic.otf" }, + { "Root.TTFont.8", "FreeMono.otf" }, + { "Root.TTFont.9", "FreeMonoOblique.otf" }, + { "Root.TTFont.10", "FreeMonoBold.otf" }, + { "Root.TTFont.11", "FreeMonoBoldOblique.otf" }, + { "Root.TTFont.12", "symbol.ttf" }, + { "Root.TTFont.13", "FreeSerif.otf" }, + { "Root.TTFont.14", "wingding.ttf" }, + { "Root.TTFont.15", "symbol.ttf" }, + { "Root.TTFont.STIXGen", "STIXGeneral.otf" }, + { "Root.TTFont.STIXGenIt", "STIXGeneralItalic.otf" }, + { "Root.TTFont.STIXGenBd", "STIXGeneralBol.otf" }, + { "Root.TTFont.STIXGenBdIt", "STIXGeneralBolIta.otf" }, + { "Root.TTFont.STIXSiz1Sym", "STIXSiz1Sym.otf" }, + { "Root.TTFont.STIXSiz1SymBd", "STIXSiz1SymBol.otf" }, + { "Root.TTFont.STIXSiz2Sym", "STIXSiz2Sym.otf" }, + { "Root.TTFont.STIXSiz2SymBd", "STIXSiz2SymBol.otf" }, + { "Root.TTFont.STIXSiz3Sym", "STIXSiz3Sym.otf" }, + { "Root.TTFont.STIXSiz3SymBd", "STIXSiz3SymBol.otf" }, + { "Root.TTFont.STIXSiz4Sym", "STIXSiz4Sym.otf" }, + { "Root.TTFont.STIXSiz4SymBd", "STIXSiz4SymBol.otf" }, + { "Root.TTFont.STIXSiz5Sym", "STIXSiz5Sym.otf" }, + { "Root.TTFont.ME", "DroidSansFallback.ttf" }, + { "Root.TTFont.CJKMing", "DroidSansFallback.ttf" }, + { "Root.TTFont.CJKGothic", "DroidSansFallback.ttf" } + }; + + static int fontset = -1; + int thisset = fontset; + + int fontid = fontnumber / 10; + if (fontid < 0 || fontid > 31) fontid = 0; + + if (thisset == -1) { + // try to load font (font must be in Root.TTFontPath resource) + // to see which fontset we have available + const char *ttpath = gEnv->GetValue("Root.TTFontPath", + TROOT::GetTTFFontDir()); + TString fname = gEnv->GetValue(fonttable[fontid][0], fonttable[fontid][1]); + const char *ttfont = gSystem->FindFile(ttpath, fname, kReadPermission); + thisset = ttfont ? 0 : 1; + } + Int_t italic = fontid == 15 ? 1 : 0; + auto ret = SetTextFont(gEnv->GetValue(fonttable[fontid][thisset], fonttable[fontid][1]), italic); + + // Do not define font set is we're loading the symbol.ttf - it's + // the same in both cases. + if (ret == 0 && fontid != 12) + fontset = thisset; +} + +//////////////////////////////////////////////////////////////////////////////// +/// Set current text size. + +Bool_t TTFhandle::SetTextSize(Float_t textsize) +{ + if (textsize < 0) + return kFALSE; + + if (!fFont || !fFont->face) { + Error("TTFhandle::SetTextSize", "current font not selected"); + return kFALSE; + } + + Int_t tsize = (Int_t)(textsize*kScale+0.5) << 6; + FT_Error err = FT_Set_Char_Size(fFont->face, tsize, tsize, 72, 72); + + if (err) + Error("TTFhandle::SetTextSize", "error in FT_Set_Char_Size: 0x%x (input size %f, calc. size 0x%x)", err, textsize, tsize); + + return !err; +} + +//////////////////////////////////////////////////////////////////////////////// + +void TTFhandle::Version(Int_t &major, Int_t &minor, Int_t &patch) +{ + FT_Library_Version(fFT_Library.Get(), &major, &minor, &patch); +} + + +//////////////////////////////////////////////////////////////////////////////// + +Bool_t TTFhandle::Init() +{ + return fFT_Library.Get() != nullptr; +} diff --git a/graf2d/win32gdk/inc/TGWin32.h b/graf2d/win32gdk/inc/TGWin32.h index 48c518e4a4364..a6cda0451c3b1 100644 --- a/graf2d/win32gdk/inc/TGWin32.h +++ b/graf2d/win32gdk/inc/TGWin32.h @@ -66,11 +66,9 @@ class TExMap; class TGWin32 : public TVirtualX { private: - void DrawFTGlyph(void *source, ULong_t fore, ULong_t back, GdkImage *xim, - Int_t bx, Int_t by); GdkImage *GetBackground(WinContext_t wctxt, Int_t x, Int_t y, UInt_t w, UInt_t h); - template + template void DrawTextHelper(WinContext_t wctxt, Int_t x, Int_t y, Float_t angle, const CharType *text, ETextMode mode); @@ -227,6 +225,9 @@ class TGWin32 : public TVirtualX { const char *text, ETextMode mode) override; void DrawTextW(WinContext_t wctxt, Int_t x, Int_t y, Float_t angle, Float_t mgn, const wchar_t *text, ETextMode mode) override; + void DrawTTFglyphsW(WinContext_t wctxt, Int_t x1, Int_t y1, TTFhandle &ttf, ETextMode mode) override; + + //---- Methods used for GUI ----- void GetWindowAttributes(Window_t id, WindowAttributes_t &attr) override; diff --git a/graf2d/win32gdk/src/TGWin32.cxx b/graf2d/win32gdk/src/TGWin32.cxx index 82c9c038f46e9..87e8e3d07dcea 100644 --- a/graf2d/win32gdk/src/TGWin32.cxx +++ b/graf2d/win32gdk/src/TGWin32.cxx @@ -22,9 +22,6 @@ This code was initially developed in the context of HIGZ and PAW by Olivier Couet (package X11INT). */ -#include -#include FT_FREETYPE_H -#include FT_GLYPH_H #include "TGWin32.h" #include #include @@ -41,7 +38,7 @@ by Olivier Couet (package X11INT). #include "TApplication.h" #include "TColor.h" #include "TPoint.h" -#include "TTF.h" +#include "TTFhandle.h" #include "TMath.h" #include "TStorage.h" #include "TStyle.h" @@ -120,9 +117,6 @@ void gdk_win32_draw_lines (GdkDrawable *drawable, }; -enum EAlign { kAlignNone, kTLeft, kTCenter, kTRight, kMLeft, kMCenter, kMRight, - kBLeft, kBCenter, kBRight }; - const int kMAXGC = 7, kGCline = 0, kGCmark = 1, kGCfill = 2, kGCtext = 3, kGCinvt = 4, kGCdash = 5, kGCpxmp = 6; @@ -164,8 +158,6 @@ struct XWindow_t { std::vector markerShape; ///< marker shape points Int_t markerLineWidth = 0; ///< line width used for marker TAttText fAttText; ///< current text attributes - EAlign textAlign = kAlignNone; ///< selected text align - FT_Vector alignVector; ///< alignment vector }; @@ -1048,8 +1040,6 @@ Int_t TGWin32::OpenDisplay(const char *dpyName) SetName("Win32TTF"); SetTitle("ROOT interface to Win32 with TrueType fonts"); - TTFhandle::SetSmoothing(fDepth > 8); - TGWin32VirtualXProxy::fMaxResponseTime = 1000; fHasTTFonts = TTFhandle::Init(); return 0; @@ -1110,101 +1100,6 @@ void TGWin32::QueryColors(GdkColormap *cmap, GdkColor *color, Int_t ncolors) } } -//////////////////////////////////////////////////////////////////////////////// -/// Draw FT_Bitmap bitmap to xim image at position bx,by using specified -/// foreground color. - -void TGWin32::DrawFTGlyph(void *_source, ULong_t fore, ULong_t back, - GdkImage *xim, Int_t bx, Int_t by) -{ - FT_Bitmap *source = (FT_Bitmap *) _source; - - UChar_t *s = source->buffer; - - if (TTFhandle::GetSmoothing()) { - - GdkColor col[5]; - - // background kClear, i.e. transparent, we take as background color - // the average of the rgb values of all pixels covered by this character - if (back == (ULong_t) -1) { - const UInt_t ndots = TMath::Min((UInt_t) 50000, source->width * source->rows); - std::vector bcol(ndots); - if (!bcol.size()) - return; - - UInt_t dotcnt = 0; - for (unsigned y = 0; y < source->rows; y++) { - for (unsigned x = 0; x < source->width; x++) { - bcol[dotcnt].pixel = GetPixelImage((Drawable_t)xim, bx + x, by + y); - if (++dotcnt >= bcol.size()) - break; - } - } - QueryColors(fColormap, bcol.data(), bcol.size()); - ULong_t r = 0, g = 0, b = 0; - for (auto &bc : bcol) { - r += bc.red; - g += bc.green; - b += bc.blue; - } - col[0].red = (UShort_t) (r / bcol.size()); - col[0].green = (UShort_t) (g / bcol.size()); - col[0].blue = (UShort_t) (b / bcol.size()); - } else { - // query background color - col[0].pixel = back; - QueryColors(fColormap, &col[0], 1); - } - - // query foreground color - col[4].pixel = fore; - QueryColors(fColormap, &col[4], 1); - - // calculate the 3 smooting colors - // (interpolation between fore- and background colors) - - // interpolate between fore and backgound colors - for (int x = 3; x > 0; x--) { - col[x].red = (col[4].red *x + col[0].red *(4-x)) /4; - col[x].green = (col[4].green*x + col[0].green*(4-x)) /4; - col[x].blue = (col[4].blue *x + col[0].blue *(4-x)) /4; - if (!AllocColor(fColormap, &col[x])) { - Warning("DrawImage", "cannot allocate smoothing color"); - col[x].pixel = col[x+1].pixel; - } - } - - // put smoothed character, character pixmap values are an index - // into the 5 colors used for aliasing (4 = foreground, 0 = background) - for (unsigned y = 0; y < source->rows; y++) { - for (unsigned x = 0; x < source->width; x++) { - UChar_t d = (((*s++ & 0xff) + 10) * 5) / 256; - if (d > 4) d = 4; - if (d > 0) - PutPixel((Drawable_t)xim, bx + x, by + y, col[d].pixel); - } - } - } else { - // no smoothing, just put character using foreground color - UChar_t* row = s; - for (unsigned y = 0; y < source->rows; y++) { - unsigned n = 0; - s = row; - UChar_t d; - for (unsigned x = 0; x < source->width; x++) { - if (n == 0) - d = *s++; - if (TESTBIT(d, 7-n)) - PutPixel((Drawable_t)xim, bx + x, by + y, fore); - if (++n == kBitsPerByte) - n = 0; - } - row += source->pitch; - } - } -} - //////////////////////////////////////////////////////////////////////////////// /// Draw text using TrueType fonts. If TrueType fonts are not available the /// text is drawn with TGWin32::DrawText. @@ -1216,7 +1111,7 @@ void TGWin32::DrawText(Int_t x, Int_t y, Float_t angle, Float_t mgn, } -template +template void TGWin32::DrawTextHelper(WinContext_t wctxt, Int_t x, Int_t y, Float_t angle, const CharType *text, ETextMode mode) { @@ -1224,7 +1119,12 @@ void TGWin32::DrawTextHelper(WinContext_t wctxt, Int_t x, Int_t y, Float_t angle if (!ctxt) return; + UInt_t width = 0, height = 0; + Int_t xy; + GetWindowSize((Drawable_t) ctxt->drawing, xy, xy, width, height); + TTFhandle ttf; + ttf.SetSmoothing(fDepth > 8); ttf.SetTextFont(ctxt->fAttText.GetTextFont()); ttf.SetTextSize(ctxt->fAttText.GetTextSize()); ttf.SetRotationMatrix(angle); @@ -1232,50 +1132,21 @@ void TGWin32::DrawTextHelper(WinContext_t wctxt, Int_t x, Int_t y, Float_t angle ttf.LayoutGlyphs(); - auto align = ctxt->textAlign; - - // vertical alignment - if (align == kTLeft || align == kTCenter || align == kTRight) { - ctxt->alignVector.y = ttf.GetAscent(); - } else if (align == kMLeft || align == kMCenter || align == kMRight) { - ctxt->alignVector.y = ttf.GetAscent() / 2; - } else { - ctxt->alignVector.y = 0; - } - // horizontal alignment - if (align == kTRight || align == kMRight || align == kBRight) { - ctxt->alignVector.x = ttf.GetWidth(); - } else if (align == kTCenter || align == kMCenter || align == kBCenter) { - ctxt->alignVector.x = ttf.GetWidth()/2; - } else { - ctxt->alignVector.x = 0; - } - - FT_Vector_Transform(&ctxt->alignVector, ttf.GetRotMatrix()); - ctxt->alignVector.x = ctxt->alignVector.x >> 6; - ctxt->alignVector.y = ctxt->alignVector.y >> 6; - - GdkGCValues gcvals; - - // compute the size and position of the XImage that will contain the text - Int_t Xoff = TMath::Max(0, (Int_t) -ttf.GetBox().xMin); - Int_t Yoff = TMath::Max(0, (Int_t) -ttf.GetBox().yMin); - Int_t w = ttf.GetBox().xMax + Xoff; - Int_t h = ttf.GetBox().yMax + Yoff; - Int_t x1 = x - Xoff - ctxt->alignVector.x; - Int_t y1 = y + Yoff + ctxt->alignVector.y - h; + if (ttf.ApplyAlignRotate(x, y, ctxt->fAttText.GetTextAlign(), width, height)) + DrawTTFglyphsW(wctxt, x, y, ttf, mode); +} - UInt_t width = 0, height = 0; - Int_t xy; - GetWindowSize((Drawable_t) ctxt->drawing, xy, xy, width, height); +//////////////////////////////////////////////////////////////////////////////// +/// Draw TTF glyphs on the specified window context - // If w or h is 0, very likely the string is only blank characters - if (w <= 0 || h <= 0) +void TGWin32::DrawTTFglyphsW(WinContext_t wctxt, Int_t x1, Int_t y1, TTFhandle &ttf, ETextMode mode) +{ + auto ctxt = (XWindow_t *) wctxt; + if (!ctxt) return; - // If string falls outside window, there is probably no need to draw it. - if (x1 + w <= 0 || x1 >= (Int_t)width || y1 + h <= 0 || y1 >= (Int_t) height) - return; + Int_t w = ttf.GetGlyphsWidth(); + Int_t h = ttf.GetGlyphsHeight(); // create the XImage that will contain the text UInt_t depth = fDepth; @@ -1286,7 +1157,8 @@ void TGWin32::DrawTextHelper(WinContext_t wctxt, Int_t x, Int_t y, Float_t angle // memset(xim->data, 0, xim->bytes_per_line * h); ULong_t pixel; - ULong_t bg; + ULong_t back; + GdkGCValues gcvals; gdk_gc_get_values(ctxt->fGClist[kGCtext], &gcvals); @@ -1295,14 +1167,13 @@ void TGWin32::DrawTextHelper(WinContext_t wctxt, Int_t x, Int_t y, Float_t angle // if mode == kClear we need to get an image of the background GdkImage *bim = GetBackground(wctxt, x1, y1, w, h); if (!bim) { - Error("DrawText", "error getting background image"); + Error("DrawTTFglyphsW", "error getting background image"); return; } // and copy it into the text image - Int_t xo = 0, yo = 0; - if (x1 < 0) xo = -x1; - if (y1 < 0) yo = -y1; + Int_t xo = x1 < 0 ? -x1 : 0; + Int_t yo = y1 < 0 ? -y1 : 0; for (int yp = 0; yp < (int) bim->height; yp++) { for (int xp = 0; xp < (int) bim->width; xp++) { @@ -1313,7 +1184,6 @@ void TGWin32::DrawTextHelper(WinContext_t wctxt, Int_t x, Int_t y, Float_t angle gdk_image_unref((GdkImage *)bim); - bg = (ULong_t) -1; } else { // if mode == kOpaque its simple, we just draw the background @@ -1323,29 +1193,110 @@ void TGWin32::DrawTextHelper(WinContext_t wctxt, Int_t x, Int_t y, Float_t angle } else { pixel = GetPixelImage((Drawable_t)bim, 0, 0); } - Int_t xo = 0, yo = 0; - if (x1 < 0) xo = -x1; - if (y1 < 0) yo = -y1; + Int_t xo = x1 < 0 ? -x1 : 0; + Int_t yo = y1 < 0 ? -y1 : 0; for (int yp = 0; yp < h; yp++) { - for (int xp = 0; xp < (int) w; xp++) { + for (int xp = 0; xp < w; xp++) { PutPixel((Drawable_t)xim, xo+xp, yo+yp, pixel); } } if (bim) { gdk_image_unref((GdkImage *)bim); - bg = (ULong_t) -1; + mode = kClear; } else { - bg = pixel; + back = pixel; } } // paint the glyphs in the XImage - for (UInt_t n = 0; n < ttf.GetNumGlyphs(); n++) { - if (auto bitmap = ttf.GetGlyphBitmap(n)) { - Int_t bx = bitmap->left + Xoff; - Int_t by = h - bitmap->top - Yoff; - DrawFTGlyph(&bitmap->bitmap, gcvals.foreground.pixel, bg, xim, bx, by); + for (UInt_t nglyph = 0; nglyph < ttf.GetNumGlyphs(); nglyph++) { + Int_t bx = 0, by = 0; + UChar_t *buffer = nullptr; + UInt_t width = 0, rows = 0, pitch = 0; + if (!ttf.GetGlyphData(nglyph, bx, by, buffer, width, rows, pitch)) + continue; + + if (ttf.GetSmoothing()) { + + GdkColor col[5]; + + // background kClear, i.e. transparent, we take as background color + // the average of the rgb values of all pixels covered by this character + if (mode == kClear) { + const UInt_t ndots = TMath::Min((UInt_t) 50000, width * rows); + std::vector bcol(ndots); + if (!bcol.size()) + return; + + for (UInt_t y = 0, dotcnt = 0; y < rows; y++) + for (UInt_t x = 0; (x < width) && (dotcnt < bcol.size()); x++) + bcol[dotcnt++].pixel = GetPixelImage((Drawable_t)xim, bx + x, by + y); + QueryColors(fColormap, bcol.data(), bcol.size()); + ULong_t r = 0, g = 0, b = 0; + for (auto &bc : bcol) { + r += bc.red; + g += bc.green; + b += bc.blue; + } + col[0].red = (UShort_t) (r / bcol.size()); + col[0].green = (UShort_t) (g / bcol.size()); + col[0].blue = (UShort_t) (b / bcol.size()); + } else { + // query background color + col[0].pixel = back; + QueryColors(fColormap, &col[0], 1); + } + + // query foreground color + col[4].pixel = gcvals.foreground.pixel; + QueryColors(fColormap, &col[4], 1); + + // calculate the 3 smooting colors + // (interpolation between fore- and background colors) + + // interpolate between fore and backgound colors + for (int x = 3; x > 0; x--) { + col[x].red = (col[4].red *x + col[0].red *(4-x)) /4; + col[x].green = (col[4].green*x + col[0].green*(4-x)) /4; + col[x].blue = (col[4].blue *x + col[0].blue *(4-x)) /4; + if (!AllocColor(fColormap, &col[x])) { + Warning("DrawImage", "cannot allocate smoothing color"); + col[x].pixel = col[x+1].pixel; + } + } + + // put smoothed character, character pixmap values are an index + // into the 5 colors used for aliasing (4 = foreground, 0 = background) + UChar_t* row = buffer; + for (unsigned y = 0; y < rows; y++) { + UChar_t *s = row; + for (unsigned x = 0; x < width; x++) { + UChar_t d = (((*s++ & 0xff) + 10) * 5) / 256; + if (d > 4) + d = 4; + if (d > 0) + PutPixel((Drawable_t)xim, bx + x, by + y, col[d].pixel); + } + row += pitch; + } + } else { + // no smoothing, just put character using foreground color + UChar_t* row = buffer; + for (unsigned y = 0; y < rows; y++) { + unsigned n = 0; + UChar_t *s = row; + UChar_t d; + for (unsigned x = 0; x < width; x++) { + if (n == 0) + d = *s++; + if (TESTBIT(d, 7-n)) + PutPixel((Drawable_t)xim, bx + x, by + y, gcvals.foreground.pixel); + if (++n == kBitsPerByte) + n = 0; + } + row += pitch; + } } } @@ -3362,54 +3313,6 @@ void TGWin32::SetAttText(WinContext_t wctxt, const TAttText &att) if (!ctxt) return; - Int_t txalh = att.GetTextAlign() / 10; - Int_t txalv = att.GetTextAlign() % 10; - - ctxt->textAlign = kAlignNone; - - switch (txalh) { - case 0 : - case 1 : - switch (txalv) { //left - case 1 : - ctxt->textAlign = kBLeft; //bottom - break; - case 2 : - ctxt->textAlign = kMLeft; //middle - break; - case 3 : - ctxt->textAlign = kTLeft; //top - break; - } - break; - case 2 : - switch (txalv) { //center - case 1 : - ctxt->textAlign = kBCenter; //bottom - break; - case 2 : - ctxt->textAlign = kMCenter; //middle - break; - case 3 : - ctxt->textAlign = kTCenter; //top - break; - } - break; - case 3 : - switch (txalv) { //right - case 1 : - ctxt->textAlign = kBRight; //bottom - break; - case 2 : - ctxt->textAlign = kMRight; //center - break; - case 3 : - ctxt->textAlign = kTRight; //top - break; - } - break; - } - SetColor(ctxt, ctxt->fGClist[kGCtext], att.GetTextColor()); GdkGCValues values; diff --git a/graf2d/x11/inc/TGX11.h b/graf2d/x11/inc/TGX11.h index 94c4bc4caa8c6..5856512a965a6 100644 --- a/graf2d/x11/inc/TGX11.h +++ b/graf2d/x11/inc/TGX11.h @@ -118,17 +118,10 @@ friend struct XWindow_t; Bool_t fHasTTFonts; ///< True when TrueType fonts are used Bool_t fHasXft; ///< True when XftFonts are used - // needed by TGX11TTF - enum EAlign { - kAlignNone, - kTLeft, kTCenter, kTRight, kMLeft, kMCenter, kMRight, - kBLeft, kBCenter, kBRight }; - Bool_t AllocColor(Colormap cmap, RXColor *color); void QueryColors(Colormap cmap, RXColor *colors, Int_t ncolors); Window_t GetWindow(WinContext_t wctxt) const; void *GetGCW(WinContext_t wctxt, Int_t which) const; - EAlign GetTextAlignW(WinContext_t wctxt) const; const TAttText& GetTextAttW(WinContext_t wctxt) const; XColor_t &GetColor(Int_t cid); diff --git a/graf2d/x11/src/TGX11.cxx b/graf2d/x11/src/TGX11.cxx index 35be7943ae2cd..625b7179c8950 100644 --- a/graf2d/x11/src/TGX11.cxx +++ b/graf2d/x11/src/TGX11.cxx @@ -90,6 +90,17 @@ const int kMAXGC = 7, static GC gGCecho; // Input echo +const int kAlignNone = 0, + kTLeft = 1, + kTCenter = 2, + kTRight = 3, + kMLeft = 4, + kMCenter = 5, + kMRight = 6, + kBLeft = 7, + kBCenter = 8, + kBRight = 9; + /// Description of a X11 window. struct XWindow_t { @@ -126,7 +137,7 @@ struct XWindow_t { std::vector markerShape; ///< marker shape points Int_t markerLineWidth = 0; ///< line width used for marker TAttText fAttText; ///< current text attribute - TGX11::EAlign textAlign = TGX11::kAlignNone; ///< selected text align + Int_t textAlign = kAlignNone; ///< selected text align XFontStruct *textFont = nullptr; ///< selected text font }; @@ -861,12 +872,12 @@ void TGX11::DrawTextW(WinContext_t wctxt, Int_t x, Int_t y, Float_t angle, Float case kClear: XRotDrawAlignedString((Display*)fDisplay, ctxt->textFont, angle, - ctxt->fDrawing, ctxt->fGClist[kGCtext], x, y, (char*)text, (int) ctxt->textAlign); + ctxt->fDrawing, ctxt->fGClist[kGCtext], x, y, (char*)text, ctxt->textAlign); break; case kOpaque: XRotDrawAlignedImageString((Display*)fDisplay, ctxt->textFont, angle, - ctxt->fDrawing, ctxt->fGClist[kGCtext], x, y, (char*)text, (int) ctxt->textAlign); + ctxt->fDrawing, ctxt->fGClist[kGCtext], x, y, (char*)text, ctxt->textAlign); break; default: @@ -1072,16 +1083,6 @@ void *TGX11::GetGCW(WinContext_t wctxt, Int_t which) const return &ctxt->fGClist[which]; } -//////////////////////////////////////////////////////////////////////////////// -/// Return text align value for specified window context. -/// Protected method used by TGX11TTF. - -TGX11::EAlign TGX11::GetTextAlignW(WinContext_t wctxt) const -{ - auto ctxt = (XWindow_t *) wctxt; - return ctxt ? ctxt->textAlign : kAlignNone; -} - //////////////////////////////////////////////////////////////////////////////// /// Return text attributes for specified window context. /// Protected method used by TGX11TTF. diff --git a/graf2d/x11ttf/inc/TGX11TTF.h b/graf2d/x11ttf/inc/TGX11TTF.h index 171bf34e73049..721237b7be787 100644 --- a/graf2d/x11ttf/inc/TGX11TTF.h +++ b/graf2d/x11ttf/inc/TGX11TTF.h @@ -29,11 +29,9 @@ class TGX11TTF : public TGX11 { static Bool_t gXftInit; ///< does xft was initialized #endif - void DrawFTGlyph(void *source, ULong_t fore, ULong_t back, RXImage *xim, - Int_t bx, Int_t by); RXImage *GetBackground(WinContext_t wctxt, Int_t x, Int_t y, UInt_t w, UInt_t h); - template + template void DrawTextHelper(WinContext_t wctxt, Int_t x, Int_t y, Float_t angle, Float_t mgn, const CharType *text, ETextMode mode); @@ -48,6 +46,8 @@ class TGX11TTF : public TGX11 { void DrawTextW(WinContext_t wctxt, Int_t x, Int_t y, Float_t angle, Float_t mgn, const wchar_t *text, ETextMode mode) override; + void DrawTTFglyphsW(WinContext_t wctxt, Int_t x, Int_t y, TTFhandle &ttf, ETextMode mode) override; + #ifdef R__HAS_XFT //---- Methods used text/fonts handling via Xft ----- //void SetClipRectangles(GContext_t gc, Int_t x, Int_t y, Rectangle_t *recs, Int_t n); diff --git a/graf2d/x11ttf/src/TGX11TTF.cxx b/graf2d/x11ttf/src/TGX11TTF.cxx index 307d973cb9a54..f6ee2d5510d17 100644 --- a/graf2d/x11ttf/src/TGX11TTF.cxx +++ b/graf2d/x11ttf/src/TGX11TTF.cxx @@ -21,16 +21,14 @@ shared library containing this class is loaded the global gVirtualX is redirected to point to this class. */ -#include -#include -#include FT_FREETYPE_H -#include FT_GLYPH_H #include "TGX11TTF.h" -#include "TEnv.h" -#include "TTF.h" -#include "TMathBase.h" +#ifdef R__HAS_XFT +#include "THashTable.h" +#include "TRefCnt.h" +#include +#endif #include #include @@ -39,16 +37,20 @@ is redirected to point to this class. #include #include + +#include + +#include "TEnv.h" +#include "TTFhandle.h" +#include "TMathBase.h" + + struct RXColor:XColor{}; struct RVisual:Visual{}; struct RXImage:XImage{}; #ifdef R__HAS_XFT -#include "THashTable.h" -#include "TRefCnt.h" -#include - ///////////////////////// xft font data ////////////////////////////////////// class TXftFontData : public TNamed, public TRefCnt { public: @@ -196,107 +198,11 @@ Bool_t TGX11TTF::Init(void *display) #endif Bool_t r = TGX11::Init(display); - TTFhandle::SetSmoothing(fDepth > 8); - return r; } -//////////////////////////////////////////////////////////////////////////////// -/// Draw FT_Bitmap bitmap to xim image at position bx,by using specified -/// foreground color. - -void TGX11TTF::DrawFTGlyph(void *_source, ULong_t fore, ULong_t back, - RXImage *xim, Int_t bx, Int_t by) -{ - auto source = (FT_Bitmap *) _source; - if (!source->width) - return; - - UChar_t *s = source->buffer; - - if (TTFhandle::GetSmoothing()) { - RXColor col[5]; - - // background kClear, i.e. transparent, we take as background color - // the average of the rgb values of all pixels covered by this character - if (back == (ULong_t) -1) { - const UInt_t ndots = TMath::Min((UInt_t) 50000, source->width * source->rows); - - std::vector bcol(ndots); - if (!bcol.size()) - return; - UInt_t dotcnt = 0; - for (unsigned y = 0; y < source->rows; y++) { - for (unsigned x = 0; x < source->width; x++) { -/// bc->pixel = XGetPixel(xim, bx + x, by - c->TTF::GetAscent() + y); - auto &bc = bcol[dotcnt]; - bc.pixel = XGetPixel(xim, bx + x, by + y); - bc.flags = DoRed | DoGreen | DoBlue; - if (++dotcnt >= bcol.size()) break; - } - } - QueryColors(fColormap, bcol.data(), bcol.size()); - ULong_t r = 0, g = 0, b = 0; - for (auto &entry : bcol) { - r += entry.red; - g += entry.green; - b += entry.blue; - } - col[0].red = (UShort_t) (r / bcol.size()); - col[0].green = (UShort_t) (g / bcol.size()); - col[0].blue = (UShort_t) (b / bcol.size()); - } else { - // just request rgb value for background color - col[0].pixel = back; - col[0].flags = DoRed | DoGreen | DoBlue; - QueryColors(fColormap, &col[0], 1); - } - // request rgb value for foreground color - col[4].pixel = fore; - col[4].flags = DoRed | DoGreen | DoBlue; - QueryColors(fColormap, &col[4], 1); - - // recalculate the 3 smoothing colors - // (interpolation between fore- and background colors) - for (int x = 3; x > 0; x--) { - col[x].red = (col[4].red *x + col[0].red *(4-x)) /4; - col[x].green = (col[4].green*x + col[0].green*(4-x)) /4; - col[x].blue = (col[4].blue *x + col[0].blue *(4-x)) /4; - if (!AllocColor(fColormap, &col[x])) { - Warning("DrawFTGlyph", "cannot allocate smoothing color"); - col[x].pixel = col[x+1].pixel; - } - } - - // put smoothed character, character pixmap values are an index - // into the 5 colors used for aliasing (4 = foreground, 0 = background) - for (unsigned y = 0; y < source->rows; y++) { - for (unsigned x = 0; x < source->width; x++) { - UChar_t d = TMath::Min((UChar_t) 4, (UChar_t)((((*s++ & 0xff) + 10) * 5) / 256)); - if (d > 0) - XPutPixel(xim, bx + x, by + y, col[d].pixel); - } - } - } else { - // no smoothing, just put character using foreground color - UChar_t *row = s; - for (unsigned y = 0; y < source->rows; y++) { - unsigned n = 0; - UChar_t d = 0; - s = row; - for (unsigned x = 0; x < source->width; x++) { - if (n == 0) d = *s++; - if (TESTBIT(d,7-n)) - XPutPixel(xim, bx + x, by + y, fore); - if (++n == kBitsPerByte) n = 0; - } - row += source->pitch; - } - } -} - -template +template void TGX11TTF::DrawTextHelper(WinContext_t wctxt, Int_t x, Int_t y, Float_t angle, Float_t mgn, const CharType *text, ETextMode mode) { @@ -308,66 +214,34 @@ void TGX11TTF::DrawTextHelper(WinContext_t wctxt, Int_t x, Int_t y, Float_t angl if (!wctxt) return; + Window_t cws = GetWindow(wctxt); + UInt_t width, height; + Int_t xy; + GetWindowSize(cws, xy, xy, width, height); + auto &att = GetTextAttW(wctxt); - auto align = GetTextAlignW(wctxt); TTFhandle ttf; + ttf.SetSmoothing(fDepth > 8); ttf.SetTextFont(att.GetTextFont()); ttf.SetTextSize(att.GetTextSize()); ttf.SetRotationMatrix(angle); ttf.PrepareString(text); ttf.LayoutGlyphs(); + if (ttf.ApplyAlignRotate(x, y, att.GetTextAlign(), width, height)) + DrawTTFglyphsW(wctxt, x, y, ttf, mode); +} - FT_Vector align_vect; ///< alignment vector - // vertical alignment - if (align == kTLeft || align == kTCenter || align == kTRight) { - align_vect.y = ttf.GetAscent(); - } else if (align == kMLeft || align == kMCenter || align == kMRight) { - align_vect.y = ttf.GetAscent() / 2; - } else { - align_vect.y = 0; - } - - // horizontal alignment - if (align == kTRight || align == kMRight || align == kBRight) { - align_vect.x = ttf.GetWidth(); - } else if (align == kTCenter || align == kMCenter || align == kBCenter) { - align_vect.x = ttf.GetWidth() / 2; - } else { - align_vect.x = 0; - } - - FT_Vector_Transform(&align_vect, ttf.GetRotMatrix()); - align_vect.x = align_vect.x >> 6; - align_vect.y = align_vect.y >> 6; - - Int_t Xoff = TMath::Max(0, (Int_t) -ttf.GetBox().xMin); - Int_t Yoff = TMath::Max(0, (Int_t) -ttf.GetBox().yMin); - Int_t w = ttf.GetBox().xMax + Xoff; - Int_t h = ttf.GetBox().yMax + Yoff; - // If w or h is 0, very likely the string is only blank characters - if (w <= 0 || h <= 0) - return; +//////////////////////////////////////////////////////////////////////////////// +/// Draw TTF glyphs on the specified window context - Int_t x1 = x - Xoff - align_vect.x; - Int_t y1 = y + Yoff + align_vect.y - h; +void TGX11TTF::DrawTTFglyphsW(WinContext_t wctxt, Int_t x1, Int_t y1, TTFhandle &ttf, ETextMode mode) +{ + Int_t w = ttf.GetGlyphsWidth(); + Int_t h = ttf.GetGlyphsHeight(); Window_t cws = GetWindow(wctxt); - UInt_t width, height; - Int_t xy; - GetWindowSize(cws, xy, xy, width, height); - - // If string falls outside window, there is probably no need to draw it. - if (x + w <= 0 || x >= (Int_t)width || y + h <= 0 || y >= (Int_t)height) - return; - - // If w or h are much larger than the window size, there is probably no need - // to draw it. Moreover a to large text size may produce a Seg Fault in - // malloc in DrawTextW. - if (((UInt_t) w > 10 * width) || ((UInt_t) h > 10 * height)) - return; - // create the XImage that will contain the text UInt_t depth = fDepth; XImage *xim = XCreateImage((Display*)fDisplay, fVisual, depth, ZPixmap, 0, nullptr, w, h, @@ -380,11 +254,10 @@ void TGX11TTF::DrawTextHelper(WinContext_t wctxt, Int_t x, Int_t y, Float_t angl xim->data = (char *) malloc(xim->bytes_per_line * h); memset(xim->data, 0, xim->bytes_per_line * h); - ULong_t bg; XGCValues values; auto gc = (GC *) GetGCW(wctxt, 3); if (!gc) { - Error("DrawTextW", "error getting Graphics Context"); + Error("DrawTTFglyphsW", "error getting Graphics Context"); return; } XGetGCValues((Display*)fDisplay, *gc, GCForeground | GCBackground, &values); @@ -394,35 +267,113 @@ void TGX11TTF::DrawTextHelper(WinContext_t wctxt, Int_t x, Int_t y, Float_t angl // if mode == kClear we need to get an image of the background XImage *bim = GetBackground(wctxt, x1, y1, w, h); if (!bim) { - Error("DrawTextW", "error getting background image"); + Error("DrawTTFglyphsW", "error getting background image"); return; } // and copy it into the text image - Int_t xo = 0, yo = 0; - if (x1 < 0) xo = -x1; - if (y1 < 0) yo = -y1; + Int_t xo = x1 < 0 ? -x1 : 0; + Int_t yo = y1 < 0 ? -y1 : 0; for (int yp = 0; yp < (int) bim->height; yp++) { for (int xp = 0; xp < (int) bim->width; xp++) { ULong_t pixel = XGetPixel(bim, xp, yp); - XPutPixel(xim, xo+xp, yo+yp, pixel); + XPutPixel(xim, xo + xp, yo + yp, pixel); } } XDestroyImage(bim); - bg = (ULong_t) -1; } else { // if mode == kOpaque its simple, we just draw the background XAddPixel(xim, values.background); - bg = values.background; } // paint the glyphs in the XImage - for (UInt_t n = 0; n < ttf.GetNumGlyphs(); n++) { - if (auto bitmap = ttf.GetGlyphBitmap(n)) { - Int_t bx = bitmap->left + Xoff; - Int_t by = h - bitmap->top - Yoff; - DrawFTGlyph(&bitmap->bitmap, values.foreground, bg, (RXImage *)xim, bx, by); + for (UInt_t nglyph = 0; nglyph < ttf.GetNumGlyphs(); nglyph++) { + Int_t bx = 0, by = 0; + UChar_t *buffer = nullptr; + UInt_t width = 0, rows = 0, pitch = 0; + if (!ttf.GetGlyphData(nglyph, bx, by, buffer, width, rows, pitch)) + continue; + + if (ttf.GetSmoothing()) { + RXColor col[5]; + + // background kClear, i.e. transparent, we take as background color + // the average of the rgb values of all pixels covered by this character + if (mode == kClear) { + const UInt_t ndots = TMath::Min((UInt_t) 50000, width * rows); + + std::vector bcol(ndots); + if (!bcol.size()) { + Error("DrawTTFglyphsW", "Allocation failure with bcol vector"); + return; + } + for (unsigned y = 0, dotcnt = 0; y < rows; y++) { + for (unsigned x = 0; (x < width) && (dotcnt < bcol.size()); x++) { + bcol[dotcnt].pixel = XGetPixel(xim, bx + x, by + y); + bcol[dotcnt].flags = DoRed | DoGreen | DoBlue; + dotcnt++; + } + } + QueryColors(fColormap, bcol.data(), bcol.size()); + ULong_t r = 0, g = 0, b = 0; + for (auto &entry : bcol) { + r += entry.red; + g += entry.green; + b += entry.blue; + } + col[0].red = (UShort_t) (r / bcol.size()); + col[0].green = (UShort_t) (g / bcol.size()); + col[0].blue = (UShort_t) (b / bcol.size()); + } else { + // just request rgb value for background color + col[0].pixel = values.background; + col[0].flags = DoRed | DoGreen | DoBlue; + QueryColors(fColormap, &col[0], 1); + } + + // request rgb value for foreground color + col[4].pixel = values.foreground; + col[4].flags = DoRed | DoGreen | DoBlue; + QueryColors(fColormap, &col[4], 1); + + // recalculate the 3 smoothing colors + // (interpolation between fore- and background colors) + for (int x = 3; x > 0; x--) { + col[x].red = (col[4].red *x + col[0].red *(4-x)) /4; + col[x].green = (col[4].green*x + col[0].green*(4-x)) /4; + col[x].blue = (col[4].blue *x + col[0].blue *(4-x)) /4; + if (!AllocColor(fColormap, &col[x])) { + Warning("DrawTTFglyphsW", "cannot allocate smoothing color"); + col[x].pixel = col[x+1].pixel; + } + } + + UChar_t *s = buffer; + // put smoothed character, character pixmap values are an index + // into the 5 colors used for aliasing (4 = foreground, 0 = background) + for (unsigned y = 0; y < rows; y++) { + for (unsigned x = 0; x < width; x++) { + UChar_t d = TMath::Min((UChar_t) 4, (UChar_t)((((*s++ & 0xff) + 10) * 5) / 256)); + if (d > 0) + XPutPixel(xim, bx + x, by + y, col[d].pixel); + } + } + } else { + // no smoothing, just put character using foreground color + UChar_t *row = buffer; + for (unsigned y = 0; y < rows; y++) { + unsigned n = 0; + UChar_t d = 0; + UChar_t *s = row; + for (unsigned x = 0; x < width; x++) { + if (n == 0) d = *s++; + if (TESTBIT(d,7-n)) + XPutPixel(xim, bx + x, by + y, values.foreground); + if (++n == kBitsPerByte) n = 0; + } + row += pitch; + } } }