odin-blend2d

Odin bindings to Blend2D
Log | Files | Refs | README | LICENSE

bl_demo_text.cpp (13295B)


      1 #include <blend2d.h>
      2 #include <chrono>
      3 
      4 #include "bl_qt_headers.h"
      5 #include "bl_qt_canvas.h"
      6 
      7 #include <QRegularExpression>
      8 
      9 class PerformanceTimer {
     10 public:
     11   typedef std::chrono::high_resolution_clock::time_point TimePoint;
     12 
     13   TimePoint _start_time {};
     14   TimePoint _end_time {};
     15 
     16   inline void start() {
     17     _start_time = std::chrono::high_resolution_clock::now();
     18   }
     19 
     20   inline void stop() {
     21     _end_time = std::chrono::high_resolution_clock::now();
     22   }
     23 
     24   inline double duration() const {
     25     std::chrono::duration<double> elapsed = _end_time - _start_time;
     26     return elapsed.count() * 1000;
     27   }
     28 };
     29 
     30 static void debug_glyph_buffer_sink(const char* message, size_t size, void* user_data) noexcept {
     31   BLString* buffer = static_cast<BLString*>(user_data);
     32   buffer->append(message, size);
     33   buffer->append('\n');
     34 }
     35 
     36 static bool is_tag_char(char c) noexcept { return uint8_t(c) >= 32u && uint8_t(c) < 128u; }
     37 
     38 static BLFontFeatureSettings parse_font_features(const QString& s) {
     39   BLFontFeatureSettings settings;
     40   QStringList parts = s.split(QRegularExpression("\\s+"), Qt::SkipEmptyParts);
     41 
     42   for (const QString& part : parts) {
     43     if (part.length() < 6u)
     44       continue;
     45 
     46     char tag0 = part[0].toLatin1();
     47     char tag1 = part[1].toLatin1();
     48     char tag2 = part[2].toLatin1();
     49     char tag3 = part[3].toLatin1();
     50     char eq   = part[4].toLatin1();
     51 
     52     if (is_tag_char(tag0) && is_tag_char(tag1) && is_tag_char(tag2) && is_tag_char(tag3) && eq == '=') {
     53       BLTag feature_tag = BL_MAKE_TAG(uint8_t(tag0), uint8_t(tag1), uint8_t(tag2), uint8_t(tag3));
     54       QString feature_value = part.sliced(5);
     55 
     56       bool ok;
     57       unsigned unsigned_value = feature_value.toUInt(&ok);
     58 
     59       if (ok) {
     60         settings.set_value(feature_tag, unsigned_value);
     61       }
     62     }
     63   }
     64 
     65   return settings;
     66 }
     67 
     68 class MainWindow : public QWidget {
     69   Q_OBJECT
     70 
     71 public:
     72   // Widgets.
     73   QComboBox* _renderer_select {};
     74   QComboBox* _style_select {};
     75   QLineEdit* _file_selected {};
     76   QPushButton* _file_selected_button {};
     77   QSlider* _slider {};
     78   QLineEdit* _text {};
     79   QLineEdit* _features_list {};
     80   QLineEdit* _features_select {};
     81   QBLCanvas* _canvas {};
     82   QCheckBox* _ot_debug {};
     83 
     84   int _qt_application_font_id = -1;
     85 
     86   // Loaded font.
     87   BLFontFace _bl_face;
     88   QFont _qt_font;
     89   QRawFont _qt_raw_font;
     90 
     91   MainWindow() {
     92     QVBoxLayout* vBox = new QVBoxLayout();
     93     vBox->setContentsMargins(0, 0, 0, 0);
     94     vBox->setSpacing(0);
     95 
     96     QGridLayout* grid = new QGridLayout();
     97     grid->setContentsMargins(5, 5, 5, 5);
     98     grid->setSpacing(5);
     99 
    100     _renderer_select = new QComboBox();
    101     QBLCanvas::init_renderer_select_box(_renderer_select);
    102 
    103     _style_select = new QComboBox();
    104     _style_select->addItem("Solid Color", QVariant(int(0)));
    105     _style_select->addItem("Linear Gradient", QVariant(int(1)));
    106     _style_select->addItem("Radial Gradient", QVariant(int(2)));
    107     _style_select->addItem("Conic Gradient", QVariant(int(3)));
    108 
    109     _file_selected = new QLineEdit("");
    110     _file_selected_button = new QPushButton("Select...");
    111     _slider = new QSlider();
    112     _canvas = new QBLCanvas();
    113 
    114     _slider->setOrientation(Qt::Horizontal);
    115     _slider->setMinimum(5);
    116     _slider->setMaximum(400);
    117     _slider->setSliderPosition(20);
    118 
    119     _text = new QLineEdit();
    120     _text->setText(QString("Test"));
    121 
    122     _features_list = new QLineEdit();
    123     _features_list->setReadOnly(true);
    124 
    125     _features_select = new QLineEdit();
    126 
    127     _ot_debug = new QCheckBox();
    128     _ot_debug->setText(QLatin1String("OpenType Dbg"));
    129 
    130     connect(_renderer_select, SIGNAL(activated(int)), SLOT(onRendererChanged(int)));
    131     connect(_style_select, SIGNAL(activated(int)), SLOT(onStyleChanged(int)));
    132     connect(_ot_debug, SIGNAL(stateChanged(int)), SLOT(valueChanged(int)));
    133     connect(_file_selected_button, SIGNAL(clicked()), SLOT(selectFile()));
    134     connect(_file_selected, SIGNAL(textChanged(const QString&)), SLOT(fileChanged(const QString&)));
    135     connect(_slider, SIGNAL(valueChanged(int)), SLOT(valueChanged(int)));
    136     connect(_text, SIGNAL(textChanged(const QString&)), SLOT(textChanged(const QString&)));
    137     connect(_features_select, SIGNAL(textChanged(const QString&)), SLOT(textChanged(const QString&)));
    138 
    139     _canvas->on_render_blend2d = std::bind(&MainWindow::on_render_blend2d, this, std::placeholders::_1);
    140     _canvas->on_render_qt = std::bind(&MainWindow::on_render_qt, this, std::placeholders::_1);
    141 
    142     grid->addWidget(new QLabel("Renderer:"), 0, 0);
    143     grid->addWidget(_renderer_select, 0, 1);
    144     grid->addWidget(_ot_debug, 0, 4);
    145 
    146     grid->addWidget(new QLabel("Style:"), 1, 0);
    147     grid->addWidget(_style_select, 1, 1);
    148 
    149     grid->addWidget(new QLabel("Font:"), 2, 0);
    150     grid->addWidget(_file_selected, 2, 1, 1, 3);
    151     grid->addWidget(_file_selected_button, 2, 4);
    152 
    153     grid->addWidget(new QLabel("Size:"), 3, 0);
    154     grid->addWidget(_slider, 3, 1, 1, 4);
    155 
    156     grid->addWidget(new QLabel("Font Features:"), 4, 0);
    157     grid->addWidget(_features_list, 4, 1, 1, 4);
    158 
    159     grid->addWidget(new QLabel("Active FEAT=V "), 5, 0);
    160     grid->addWidget(_features_select, 5, 1, 1, 4);
    161 
    162     grid->addWidget(new QLabel("Text:"), 6, 0);
    163     grid->addWidget(_text, 6, 1, 1, 4);
    164 
    165     vBox->addItem(grid);
    166     vBox->addWidget(_canvas);
    167 
    168     setLayout(vBox);
    169   }
    170 
    171   void keyPressEvent(QKeyEvent *event) override {}
    172   void mousePressEvent(QMouseEvent* event) override {}
    173   void mouseReleaseEvent(QMouseEvent* event) override {}
    174   void mouseMoveEvent(QMouseEvent* event) override {}
    175 
    176   void reloadFont(const char* file_name) {
    177     _bl_face.reset();
    178     if (_qt_application_font_id != -1) {
    179       QFontDatabase::removeApplicationFont(_qt_application_font_id);
    180     }
    181 
    182     BLArray<uint8_t> data_buffer;
    183     if (BLFileSystem::read_file(file_name, data_buffer) == BL_SUCCESS) {
    184       BLFontData fontData;
    185       if (fontData.create_from_data(data_buffer) == BL_SUCCESS) {
    186         _bl_face.create_from_data(fontData, 0);
    187 
    188         BLArray<BLTag> tags;
    189         _bl_face.get_feature_tags(&tags);
    190 
    191         QString tagsStringified;
    192         for (BLTag tag : tags) {
    193           char tag_string[4] = {
    194             char((tag >> 24) & 0xFF),
    195             char((tag >> 16) & 0xFF),
    196             char((tag >>  8) & 0xFF),
    197             char((tag >>  0) & 0xFF)
    198           };
    199 
    200           if (!tagsStringified.isEmpty()) {
    201             tagsStringified.append(QLatin1String(" ", 1));
    202           }
    203 
    204           tagsStringified.append(QLatin1String(tag_string, 4));
    205         }
    206 
    207         _features_list->setText(tagsStringified);
    208       }
    209 
    210       QByteArray qt_buffer(reinterpret_cast<const char*>(data_buffer.data()), data_buffer.size());
    211       _qt_application_font_id = QFontDatabase::addApplicationFontFromData(qt_buffer);
    212     }
    213   }
    214 
    215 private Q_SLOTS:
    216   Q_SLOT void onStyleChanged(int index) { _canvas->update_canvas(); }
    217   Q_SLOT void onRendererChanged(int index) { _canvas->set_renderer_type(_renderer_select->itemData(index).toInt()); }
    218 
    219   void selectFile() {
    220     QString file_name = _file_selected->text();
    221     QFileDialog dialog(this);
    222 
    223     if (!file_name.isEmpty())
    224       dialog.setDirectory(QFileInfo(file_name).absoluteDir().path());
    225 
    226     dialog.setAcceptMode(QFileDialog::AcceptOpen);
    227     dialog.setFileMode(QFileDialog::ExistingFile);
    228     dialog.setNameFilter(QString("Font File (*.ttf *.otf)"));
    229     dialog.setViewMode(QFileDialog::Detail);
    230 
    231     if (dialog.exec() == QDialog::Accepted) {
    232       file_name = dialog.selectedFiles()[0];
    233       _file_selected->setText(file_name);
    234     }
    235   }
    236 
    237   void fileChanged(const QString&) {
    238     QByteArray file_name_utf8 = _file_selected->text().toUtf8();
    239     file_name_utf8.append('\0');
    240 
    241     reloadFont(file_name_utf8.constData());
    242     _canvas->update_canvas();
    243   }
    244 
    245   void valueChanged(int value) {
    246     _canvas->update_canvas();
    247   }
    248 
    249   void textChanged(const QString&) {
    250     _canvas->update_canvas();
    251   }
    252 
    253 public:
    254   void on_render_blend2d(BLContext& ctx) noexcept {
    255     ctx.fill_all(BLRgba32(0xFF000000));
    256 
    257     int styleId = _style_select->currentIndex();
    258     BLVar style;
    259 
    260     switch (styleId) {
    261       default:
    262       case 0: {
    263         style = BLRgba32(0xFFFFFFFF);
    264         break;
    265       }
    266 
    267       case 1: {
    268         double w = _canvas->bl_image.width();
    269         double h = _canvas->bl_image.height();
    270 
    271         BLGradient g(BLLinearGradientValues(0, 0, w, h));
    272         g.add_stop(0.0, BLRgba32(0xFFFF0000));
    273         g.add_stop(0.5, BLRgba32(0xFFAF00AF));
    274         g.add_stop(1.0, BLRgba32(0xFF0000FF));
    275 
    276         style = g;
    277         break;
    278       }
    279 
    280       case 2: {
    281         double w = _canvas->bl_image.width();
    282         double h = _canvas->bl_image.height();
    283         double r = bl_min(w, h);
    284 
    285         BLGradient g(BLRadialGradientValues(w * 0.5, h * 0.5, w * 0.5, h * 0.5, r * 0.5));
    286         g.add_stop(0.0, BLRgba32(0xFFFF0000));
    287         g.add_stop(0.5, BLRgba32(0xFFAF00AF));
    288         g.add_stop(1.0, BLRgba32(0xFF0000FF));
    289 
    290         style = g;
    291         break;
    292       }
    293 
    294       case 3: {
    295         double w = _canvas->bl_image.width();
    296         double h = _canvas->bl_image.height();
    297 
    298         BLGradient g(BLConicGradientValues(w * 0.5, h * 0.5, 0.0));
    299         g.add_stop(0.00, BLRgba32(0xFFFF0000));
    300         g.add_stop(0.33, BLRgba32(0xFFAF00AF));
    301         g.add_stop(0.66, BLRgba32(0xFF0000FF));
    302         g.add_stop(1.00, BLRgba32(0xFFFF0000));
    303 
    304         style = g;
    305         break;
    306       }
    307     }
    308 
    309     BLFont font;
    310     BLFontFeatureSettings featureSettings = parse_font_features(_features_select->text());
    311     font.create_from_face(_bl_face, _slider->value(), featureSettings);
    312 
    313     // Qt uses UTF-16 strings, Blend2D can process them natively.
    314     QString text = _text->text();
    315     PerformanceTimer timer;
    316     timer.start();
    317     ctx.fill_utf16_text(BLPoint(10, 10 + font.size()), font, reinterpret_cast<const uint16_t*>(text.constData()), text.length(), style);
    318     timer.stop();
    319 
    320     if (_ot_debug->checkState() == Qt::Checked) {
    321       BLGlyphBuffer gb;
    322       BLString output;
    323       gb.set_debug_sink(debug_glyph_buffer_sink, &output);
    324       gb.set_utf16_text(reinterpret_cast<const uint16_t*>(text.constData()), text.length());
    325       font.shape(gb);
    326 
    327       BLFont smallFont;
    328       smallFont.create_from_face(_bl_face, 22.0f);
    329       BLFontMetrics metrics = smallFont.metrics();
    330 
    331       size_t i = 0;
    332       BLPoint pos(10, 10 + font.size() * 1.2 + smallFont.size());
    333       while (i < output.size()) {
    334         size_t end = bl_min(output.index_of('\n', i), output.size());
    335 
    336         BLRgba32 color = BLRgba32(0xFFFFFFFF);
    337         if (end - i > 0 && output.data()[i] == '[')
    338           color = BLRgba32(0xFFFFFF00);
    339 
    340         ctx.fill_utf8_text(pos, smallFont, output.data() + i, end - i, color);
    341         pos.y += metrics.ascent + metrics.descent;
    342         i = end + 1;
    343       }
    344     }
    345 
    346     _updateTitle(timer.duration());
    347   }
    348 
    349   void on_render_qt(QPainter& ctx) noexcept {
    350     ctx.fillRect(0, 0, _canvas->width(), _canvas->height(), QColor(0, 0, 0));
    351 
    352     if (_qt_application_font_id == -1)
    353       return;
    354 
    355     int styleId = _style_select->currentIndex();
    356     QBrush brush;
    357 
    358     switch (styleId) {
    359       default:
    360       case 0: {
    361         brush = QColor(255, 255, 255);
    362         break;
    363       }
    364 
    365       case 1: {
    366         double w = _canvas->bl_image.width();
    367         double h = _canvas->bl_image.height();
    368 
    369         QLinearGradient g(qreal(0), qreal(0), qreal(w), qreal(h));
    370         g.setColorAt(0.0f, QColor(0xFF, 0x00, 0x00));
    371         g.setColorAt(0.5f, QColor(0xAF, 0x00, 0xAF));
    372         g.setColorAt(1.0f, QColor(0x00, 0x00, 0xFF));
    373 
    374         brush = QBrush(g);
    375         break;
    376       }
    377 
    378       case 2: {
    379         double w = _canvas->bl_image.width();
    380         double h = _canvas->bl_image.height();
    381         double r = bl_min(w, h);
    382 
    383         QRadialGradient g(qreal(w * 0.5), qreal(h * 0.5), qreal(r * 0.5), qreal(w * 0.5), qreal(h * 0.5));
    384         g.setColorAt(0.0f, QColor(0xFF, 0x00, 0x00));
    385         g.setColorAt(0.5f, QColor(0xAF, 0x00, 0xAF));
    386         g.setColorAt(1.0f, QColor(0x00, 0x00, 0xFF));
    387 
    388         brush = QBrush(g);
    389         break;
    390       }
    391 
    392       case 3: {
    393         double w = _canvas->bl_image.width();
    394         double h = _canvas->bl_image.height();
    395 
    396         QConicalGradient g(qreal(w * 0.5), qreal(h * 0.5), 0.0);
    397         g.setColorAt(0.00f, QColor(0xFF, 0x00, 0x00));
    398         g.setColorAt(0.66f, QColor(0xAF, 0x00, 0xAF));
    399         g.setColorAt(0.33f, QColor(0x00, 0x00, 0xFF));
    400         g.setColorAt(1.00f, QColor(0xFF, 0x00, 0x00));
    401 
    402         brush = QBrush(g);
    403         break;
    404       }
    405     }
    406 
    407     QStringList families = QFontDatabase::applicationFontFamilies(_qt_application_font_id);
    408     QFont font = QFont(families[0]);
    409     font.setPixelSize(_slider->value());
    410     font.setHintingPreference(QFont::PreferNoHinting);
    411     ctx.setFont(font);
    412 
    413     QPen pen(brush, 1.0f);
    414     ctx.setPen(pen);
    415 
    416     PerformanceTimer timer;
    417     timer.start();
    418     ctx.drawText(QPointF(10, 10 + font.pixelSize()), _text->text());
    419     timer.stop();
    420     _updateTitle(timer.duration());
    421   }
    422 
    423   void _updateTitle(double duration) {
    424     char buf[256];
    425     snprintf(buf, 256, "Text Sample [Size %dpx TextRenderTime %0.3fms]", int(_slider->value()), duration);
    426 
    427     QString title = QString::fromUtf8(buf);
    428     if (title != windowTitle())
    429       setWindowTitle(title);
    430   }
    431 };
    432 
    433 int main(int argc, char *argv[]) {
    434   QApplication app(argc, argv);
    435   MainWindow win;
    436 
    437   win.resize(QSize(580, 520));
    438   win.show();
    439 
    440   return app.exec();
    441 }
    442 
    443 #include "bl_demo_text.moc"