Formatted / paragraph text

I’m implementing some alert functions and attempted to implement a word wrapped function.


static void DrawWordWrappedText(IGraphics& g, IText& style, std::string text, IRECT mRECT) {
    std::vector<std::string> words;
    std::string temp = "";
    for (int i = 0; i < text.length(); i++) {
      if (text[i] == ' ') {
        words.push_back(temp);
        temp = "";
      }
      else {
        temp.push_back(text[i]);
      }
    }

    int i = 0;
    float length = 0;
    temp = "";
    float y = mRECT.T+10;
    float y_inc = 25;
    while (i < words.size()) {
      length = 0;
      temp = "";
      while (length < mRECT.W()) {
        if (i == words.size()) {
          break;
        }
        temp.append(words[i]);
        temp.push_back(' ');
        length = g.MeasureText(style, temp.c_str(), mRECT); //Always returns mRect.W()…
        i++;
      }
      g.DrawText(style, temp.c_str(), mRECT.L+3, y);
      y += y_inc;
    }

  }

Ignore any inconsistency in the code- it’s very ugly right now because of debugging :slight_smile:

The issue is that the text measured is always the width of the IRECT passed through. (Because I’m not passing mRECT as a reference….? I’ve started to run out of ideas)

I’m using Skia, thanks for any help!

Since you are using Skia, you could use SkParagraph, which is the most advanced way of drawing formatted text in iPlug2. Unfortunately the pre-built skia libs zips are missing some headers that got moved in the latest skia. With a bit of manually copying the missing headers, you can do this:


/*
 ==============================================================================
 
 This file is part of the iPlug 2 library. Copyright (C) the iPlug 2 developers.
 
 See LICENSE.txt for  more info.
 
 ==============================================================================
 */

#pragma once

#ifndef IGRAPHICS_SKIA
#error This IControl only works with the Skia graphics backend
#endif

#include "IControl.h"
#include "IGraphicsSkia.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkColorFilter.h"
#include "include/core/SkColorPriv.h"
#include "include/core/SkFontMgr.h"
#include "include/core/SkGraphics.h"
#include "include/core/SkPath.h"
#include "include/core/SkRegion.h"
#include "include/core/SkShader.h"
#include "include/core/SkStream.h"
#include "include/core/SkTextBlob.h"
#include "include/core/SkTime.h"
#include "include/core/SkTypeface.h"
#include "include/effects/SkGradientShader.h"
#include "include/utils/SkRandom.h"
#include "modules/skparagraph/include/Paragraph.h"
#include "modules/skparagraph/include/TypefaceFontProvider.h"
#include "modules/skparagraph/src/ParagraphBuilderImpl.h"
#include "modules/skparagraph/src/ParagraphImpl.h"
#include "modules/skparagraph/src/TextLine.h"
#include "modules/skparagraph/utils/TestFontCollection.h"

#ifdef OS_WIN
#pragma comment(lib, "skparagraph.lib")
#pragma comment(lib, "skshaper.lib")
#endif

BEGIN_IPLUG_NAMESPACE
BEGIN_IGRAPHICS_NAMESPACE

using namespace skia::textlayout;


class ISkParagraphControl : public IControl
{
public:
  ISkParagraphControl(const IRECT& bounds)
  : IControl(bounds)
  {
  }
  
  void Draw(IGraphics& g) override
  {
    g.FillRect(COLOR_GREEN, mRECT);
    SkCanvas* canvas = (SkCanvas*) g.GetDrawContext();
    DoDrawContent(canvas);
  }
  
//  void DoDrawContent(SkCanvas* canvas)
//  {
//    float w = mRECT.W();
//    //    float h = mRECT.H();
//
//    const std::vector<
//    std::tuple<std::string, bool, bool, int, SkColor, SkColor, bool, TextDecorationStyle>>
//    gParagraph = { {"monospace", true, false, 14, SK_ColorWHITE, SK_ColorRED, true,
//      TextDecorationStyle::kDashed},
//      {"Assyrian", false, false, 20, SK_ColorWHITE, SK_ColorBLUE, false,
//        TextDecorationStyle::kDotted},
//      {"serif", true, true, 10, SK_ColorWHITE, SK_ColorRED, true,
//        TextDecorationStyle::kDouble},
//      {"Arial", false, true, 16, SK_ColorGRAY, SK_ColorGREEN, true,
//        TextDecorationStyle::kSolid},
//      {"sans-serif", false, false, 8, SK_ColorWHITE, SK_ColorRED, false,
//        TextDecorationStyle::kWavy} };
//    SkAutoCanvasRestore acr(canvas, true);
//
//    //    canvas->clipRect(SkRect::MakeWH(w, h));
//    canvas->drawColor(SK_ColorWHITE);
//
//    SkScalar margin = 20;
//
//    SkPaint paint;
//    paint.setAntiAlias(true);
//    paint.setColor(SK_ColorWHITE);
//
//    SkPaint blue;
//    blue.setColor(SK_ColorBLUE);
//
//    TextStyle defaultStyle;
//    defaultStyle.setBackgroundColor(blue);
//    defaultStyle.setForegroundColor(paint);
//    ParagraphStyle paraStyle;
//
//    auto fontCollection = sk_make_sp<FontCollection>();
//    fontCollection->setDefaultFontManager(SkFontMgr::RefDefault());
//    for (auto i = 1; i < 5; ++i) {
//      defaultStyle.setFontSize(24 * i);
//      paraStyle.setTextStyle(defaultStyle);
//      ParagraphBuilderImpl builder(paraStyle, fontCollection);
//      std::string name = "Paragraph: " + std::to_string(24 * i);
//      builder.addText(name.c_str(), name.length());
//      for (auto para : gParagraph) {
//        TextStyle style;
//        style.setFontFamilies({ SkString(std::get<0>(para).c_str()) });
//        SkFontStyle fontStyle(std::get<1>(para) ? SkFontStyle::Weight::kBold_Weight
//                              : SkFontStyle::Weight::kNormal_Weight,
//                              SkFontStyle::Width::kNormal_Width,
//                              std::get<2>(para) ? SkFontStyle::Slant::kItalic_Slant
//                              : SkFontStyle::Slant::kUpright_Slant);
//        style.setFontStyle(fontStyle);
//        style.setFontSize(std::get<3>(para) * i);
//        SkPaint background;
//        background.setColor(std::get<4>(para));
//        style.setBackgroundColor(background);
//        SkPaint foreground;
//        foreground.setColor(std::get<5>(para));
//        foreground.setAntiAlias(true);
//        style.setForegroundColor(foreground);
//        if (std::get<6>(para)) {
//          style.addShadow(TextShadow(SK_ColorBLACK, SkPoint::Make(5, 5), 2));
//        }
//
//        auto decoration = (i % 4);
//        if (decoration == 3) {
//          decoration = 4;
//        }
//
//        bool test = (TextDecoration)decoration != TextDecoration::kNoDecoration;
//        std::string deco = std::to_string((int)decoration);
//        if (test) {
//          style.setDecoration((TextDecoration)decoration);
//          style.setDecorationStyle(std::get<7>(para));
//          style.setDecorationColor(std::get<5>(para));
//        }
//        builder.pushStyle(style);
//        std::string name = " " + std::get<0>(para) + " " +
//        (std::get<1>(para) ? ", bold" : "") +
//        (std::get<2>(para) ? ", italic" : "") + " " +
//        std::to_string(std::get<3>(para) * i) +
//        (std::get<4>(para) != SK_ColorRED ? ", background" : "") +
//        (std::get<5>(para) != SK_ColorWHITE ? ", foreground" : "") +
//        (std::get<6>(para) ? ", shadow" : "") +
//        (test ? ", decorations " + deco : "") + ";";
//        builder.addText(name.c_str(), name.length());
//        builder.pop();
//      }
//
//      auto paragraph = builder.Build();
//      paragraph->layout(w - margin * 2);
//      paragraph->paint(canvas, mRECT.L + margin, mRECT.T + margin);
//
//      canvas->translate(0, paragraph->getHeight());
//    }
//  }
  
    void DoDrawContent(SkCanvas* canvas)
    {
      const std::u16string text = u"The quick brown fox \U0001f98a ate a zesty ham burger fons \U0001f354."
        "The \U0001f469\u200D\U0001f469\u200D\U0001f467\u200D\U0001f467 laughed.";
      canvas->drawColor(SK_ColorWHITE);

      auto fontCollection = sk_make_sp<FontCollection>();
      fontCollection->setDefaultFontManager(SkFontMgr::RefDefault());
      
      ParagraphStyle paragraph_style;
      paragraph_style.setMaxLines(7);
      paragraph_style.setEllipsis(u"\u2026");
      ParagraphBuilderImpl builder(paragraph_style, fontCollection);
      TextStyle text_style;
      text_style.setColor(SK_ColorBLACK);
      text_style.setFontFamilies({ SkString("Roboto"), SkString("Noto Color Emoji") });
      text_style.setFontSize(60);
      builder.pushStyle(text_style);
      builder.addText(text);
      auto paragraph = builder.Build();
      assert(paragraph);
      paragraph->layout(mRECT.W());
      paragraph->paint(canvas, mRECT.L, mRECT.T);
    }
};

END_IGRAPHICS_NAMESPACE
END_IPLUG_NAMESPACE

1 Like

Latest prebuilt libraries now include everything necessary to use SKParagraph as above

2 Likes

Instead of creating a new thread again, I’ll just ask this here because it’s quite similar.

Is there a control to enter paragraph text? I’m working on a preset saving window that should provide name, description, (etc) inputs. Is there anything in iPlug to create a multi-line textbox, or would it be better for me to just create a new control from scratch for that purpose?
Thanks!

AFAIK you have to do it on your own (at least i did)…
@Youlean did also one and provided a gist which maybe fit your needs, or at least gives some ideas:

Looks promising, thanks!