diff --git a/IGraphics/Controls/IAttributedTextControl.h b/IGraphics/Controls/IAttributedTextControl.h new file mode 100644 index 0000000000..f298d12c3e --- /dev/null +++ b/IGraphics/Controls/IAttributedTextControl.h @@ -0,0 +1,123 @@ +/* + ============================================================================== + + This file is part of the iPlug 2 library. Copyright (C) the iPlug 2 developers. + + See LICENSE.txt for more info. + + ============================================================================== +*/ + +#pragma once + +/** + * @file + * @copydoc IAboutBoxControl + */ + +#include "IControl.h" + +BEGIN_IPLUG_NAMESPACE +BEGIN_IGRAPHICS_NAMESPACE + +/* An IControl for drawing attributed text to a layer using the native text routines, e.g. CoreGraphics */ + +class IAttributedTextControl : public IEditableTextControl +{ +public: + IAttributedTextControl(const IRECT& bounds, const char* str, const IText& text = DEFAULT_TEXT) + : IEditableTextControl(bounds, str, text) + { + } + + int DrawAttributedText(IGraphics& g, const char* str, const IRECT& textArea, const IText& text) + { + auto* vg = static_cast(g.GetDrawContext()); + + float w, h; + auto imageData = g.DrawAttributedText(str, text, w, h); + int imgID = nvgCreateImageRGBA(vg, ceil(w * g.GetTotalScale()), ceil(h * g.GetTotalScale()), 0, imageData.Get()); + + float x, y; + + switch (text.mAlign) + { + case EAlign::Near: + x = textArea.L; + break; + case EAlign::Center: + x = textArea.MW() - (w / 2.0); + break; + case EAlign::Far: + x = textArea.R; + break; + } + + switch (text.mVAlign) + { + case EVAlign::Top: + y = textArea.T; + break; + case EVAlign::Middle: + y = textArea.MH() - (h / 2.0); + break; + case EVAlign::Bottom: + y = textArea.B - h; + break; + } + + nvgBeginPath(vg); + nvgRect(vg, x, y, w, h); + NVGpaint imgPaint = nvgImagePattern(vg, x, y, w, h, 0, imgID, 1.f); + nvgFillPaint(vg, imgPaint); + nvgFill(vg); + + // Clean up + // nvgDeleteImage(vg, imgID); + + return imgID; + } + + void Draw(IGraphics& g) override + { + int imgID = -1; + if (!g.CheckLayer(mLayer)) + { + g.StartLayer(this, mRECT); + imgID = DrawAttributedText(g, mStr.Get(), mRECT.GetPadded(-10.f), mText); + mLayer = g.EndLayer(); + } + + g.DrawLayer(mLayer, &mBlend); + + if (imgID > -1) + { + auto* vg = static_cast(g.GetDrawContext()); + nvgDeleteImage(vg, imgID); + } + } + + void OnRescale() override + { + if (mLayer) + mLayer->Invalidate(); + } + + void OnResize() override + { + if (mLayer) + mLayer->Invalidate(); + } + + void SetStr(const char* str) override + { + ITextControl::SetStr(str); + mLayer->Invalidate(); + } +private: + ILayerPtr mLayer; +}; + + +END_IGRAPHICS_NAMESPACE +END_IPLUG_NAMESPACE diff --git a/IGraphics/IGraphics.h b/IGraphics/IGraphics.h index 0d4a4a0d7f..9f6243a112 100644 --- a/IGraphics/IGraphics.h +++ b/IGraphics/IGraphics.h @@ -970,6 +970,14 @@ class IGraphics /** Get the app group ID on macOS and iOS, returns emtpy string on other OSs */ virtual const char* GetAppGroupID() const { return ""; } + /** Draw text to a bitmap. May be used to render colour emojis etc. using platform text rendering + * @param str UTF8 encoded string to render + * @param text The IText style for the text + * @param width The width of the drawn text in pixels, without any draw or screen scaling applied + * @param height The height of the drawn text in pixels, without any draw or screen scaling applied + * @return Bitmap data containing the rendered text */ + virtual RawBitmapData DrawAttributedText(const char* str, const IText& text, float& width, float& height) { return RawBitmapData(); } + protected: /* Activate the context for the view (GL only) */ virtual void ActivateGLContext() {}; diff --git a/IGraphics/Platforms/IGraphicsIOS.h b/IGraphics/Platforms/IGraphicsIOS.h index 040cc0f163..86535276da 100644 --- a/IGraphics/Platforms/IGraphicsIOS.h +++ b/IGraphics/Platforms/IGraphicsIOS.h @@ -74,6 +74,7 @@ class IGraphicsIOS final : public IGRAPHICS_DRAW_CLASS EUIAppearance GetUIAppearance() const override; + RawBitmapData DrawAttributedText(const char* str, const IText& text, float& width, float& height) override; protected: PlatformFontPtr LoadPlatformFont(const char* fontID, const char* fileNameOrResID) override; PlatformFontPtr LoadPlatformFont(const char* fontID, const char* fontName, ETextStyle style) override; diff --git a/IGraphics/Platforms/IGraphicsIOS.mm b/IGraphics/Platforms/IGraphicsIOS.mm index 1898fb6b75..1d817fb9cd 100644 --- a/IGraphics/Platforms/IGraphicsIOS.mm +++ b/IGraphics/Platforms/IGraphicsIOS.mm @@ -367,6 +367,61 @@ float GetScaleForScreen(int plugWidth, int plugHeight) } } +RawBitmapData IGraphicsIOS::DrawAttributedText(const char* str, const IText& text, float& width, float& height) +{ + auto scaleFactor = GetTotalScale(); + CFStringRef string = CFStringCreateWithCString(NULL, str, kCFStringEncodingUTF8); + CFMutableAttributedStringRef attrString = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0); + CFAttributedStringReplaceString(attrString, CFRangeMake(0, 0), string); + CFRelease(string); + + CFStringRef fontName = CFStringCreateWithCString(NULL, text.mFont, kCFStringEncodingUTF8); + CoreTextFontDescriptor* CTFontDescriptor = CoreTextHelpers::GetCTFontDescriptor(text, sFontDescriptorCache); + double ratio = CTFontDescriptor->GetEMRatio()/* * GetDrawScale()*/; + CTFontRef font = CTFontCreateWithName(fontName, text.mSize * ratio, NULL); + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + CGFloat components[] = {text.mFGColor.R / 255.0, text.mFGColor.G / 255.0, text.mFGColor.B / 255.0, text.mFGColor.A / 255.0}; // RGBA + CGColorRef color = CGColorCreate(colorSpace, components); + CFAttributedStringSetAttribute(attrString, CFRangeMake(0, CFAttributedStringGetLength(attrString)), kCTFontAttributeName, font); + CFAttributedStringSetAttribute(attrString, CFRangeMake(0, CFAttributedStringGetLength(attrString)), kCTForegroundColorAttributeName, color); + CFRelease(color); + CGColorSpaceRelease(colorSpace); + CFRelease(font); + CFRelease(fontName); + + CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(attrString); + CGSize textSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRangeMake(0, CFAttributedStringGetLength(attrString)), NULL, CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX), NULL); + + CGMutablePathRef path = CGPathCreateMutable(); + CGRect bounds = CGRectMake(0, 0, textSize.width, textSize.height); // Create bounds according to the text size + CGPathAddRect(path, NULL, bounds); + + CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, CFAttributedStringGetLength(attrString)), path, NULL); + + CFRelease(framesetter); + CFRelease(attrString); + + width = textSize.width; + height = textSize.height; + int bitmapWidth = ceil(width * scaleFactor); + int bitmapHeight = ceil(height * scaleFactor); + + RawBitmapData imageData; + imageData.Resize(4 * bitmapWidth * bitmapHeight); + imageData.SetToZero(); + CGContextRef bitmapContext = CGBitmapContextCreate(imageData.Get(), bitmapWidth, bitmapHeight, 8, bitmapWidth * 4, CGColorSpaceCreateDeviceRGB(), kCGImageAlphaPremultipliedLast); + CGContextScaleCTM(bitmapContext, scaleFactor, scaleFactor); + CTFrameDraw(frame, bitmapContext); + CGContextFlush(bitmapContext); + + CFRelease(frame); + CGPathRelease(path); + CGContextRelease(bitmapContext); + + return imageData; +} + + #if defined IGRAPHICS_NANOVG #include "IGraphicsNanoVG.cpp" #elif defined IGRAPHICS_SKIA diff --git a/IGraphics/Platforms/IGraphicsMac.h b/IGraphics/Platforms/IGraphicsMac.h index 63eb1ab14e..2157c5c39d 100644 --- a/IGraphics/Platforms/IGraphicsMac.h +++ b/IGraphics/Platforms/IGraphicsMac.h @@ -74,6 +74,9 @@ class IGraphicsMac final : public IGRAPHICS_DRAW_CLASS float MeasureText(const IText& text, const char* str, IRECT& bounds) const override; EUIAppearance GetUIAppearance() const override; + + RawBitmapData DrawAttributedText(const char* str, const IText& text, float& width, float& height) override; + protected: IPopupMenu* CreatePlatformPopupMenu(IPopupMenu& menu, const IRECT bounds, bool& isAsync) override; diff --git a/IGraphics/Platforms/IGraphicsMac.mm b/IGraphics/Platforms/IGraphicsMac.mm index 24c8283eee..4ab2693a56 100644 --- a/IGraphics/Platforms/IGraphicsMac.mm +++ b/IGraphics/Platforms/IGraphicsMac.mm @@ -725,6 +725,60 @@ static int GetSystemVersion() return EUIAppearance::Light; } +RawBitmapData IGraphicsMac::DrawAttributedText(const char* str, const IText& text, float& width, float& height) +{ + auto scaleFactor = GetTotalScale(); + CFStringRef string = CFStringCreateWithCString(NULL, str, kCFStringEncodingUTF8); + CFMutableAttributedStringRef attrString = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0); + CFAttributedStringReplaceString(attrString, CFRangeMake(0, 0), string); + CFRelease(string); + + CFStringRef fontName = CFStringCreateWithCString(NULL, text.mFont, kCFStringEncodingUTF8); + CoreTextFontDescriptor* CTFontDescriptor = CoreTextHelpers::GetCTFontDescriptor(text, sFontDescriptorCache); + double ratio = CTFontDescriptor->GetEMRatio()/* * GetDrawScale()*/; + CTFontRef font = CTFontCreateWithName(fontName, text.mSize * ratio, NULL); + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + CGFloat components[] = {text.mFGColor.R / 255.0, text.mFGColor.G / 255.0, text.mFGColor.B / 255.0, text.mFGColor.A / 255.0}; // RGBA + CGColorRef color = CGColorCreate(colorSpace, components); + CFAttributedStringSetAttribute(attrString, CFRangeMake(0, CFAttributedStringGetLength(attrString)), kCTFontAttributeName, font); + CFAttributedStringSetAttribute(attrString, CFRangeMake(0, CFAttributedStringGetLength(attrString)), kCTForegroundColorAttributeName, color); + CFRelease(color); + CGColorSpaceRelease(colorSpace); + CFRelease(font); + CFRelease(fontName); + + CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(attrString); + CGSize textSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRangeMake(0, CFAttributedStringGetLength(attrString)), NULL, CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX), NULL); + + CGMutablePathRef path = CGPathCreateMutable(); + CGRect bounds = CGRectMake(0, 0, textSize.width, textSize.height); // Create bounds according to the text size + CGPathAddRect(path, NULL, bounds); + + CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, CFAttributedStringGetLength(attrString)), path, NULL); + + CFRelease(framesetter); + CFRelease(attrString); + + width = textSize.width; + height = textSize.height; + int bitmapWidth = ceil(width * scaleFactor); + int bitmapHeight = ceil(height * scaleFactor); + + RawBitmapData imageData; + imageData.Resize(4 * bitmapWidth * bitmapHeight); + imageData.SetToZero(); + CGContextRef bitmapContext = CGBitmapContextCreate(imageData.Get(), bitmapWidth, bitmapHeight, 8, bitmapWidth * 4, CGColorSpaceCreateDeviceRGB(), kCGImageAlphaPremultipliedLast); + CGContextScaleCTM(bitmapContext, scaleFactor, scaleFactor); + CTFrameDraw(frame, bitmapContext); + CGContextFlush(bitmapContext); + + CFRelease(frame); + CGPathRelease(path); + CGContextRelease(bitmapContext); + + return imageData; +} + void IGraphicsMac::ActivateGLContext() { #ifdef IGRAPHICS_GL