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"