diff --git a/interface/resources/qml/TextOverlayElement.qml b/interface/resources/qml/TextOverlayElement.qml new file mode 100644 index 0000000000..492a38ee0b --- /dev/null +++ b/interface/resources/qml/TextOverlayElement.qml @@ -0,0 +1,21 @@ +import Hifi 1.0 +import QtQuick 2.3 +import QtQuick.Controls 1.2 + +TextOverlayElement { + id: root + Rectangle { + color: root.backgroundColor + anchors.fill: parent + Text { + x: root.leftMargin + y: root.topMargin + id: text + objectName: "textElement" + text: root.text + color: root.textColor + font.family: root.fontFamily + font.pointSize: root.fontSize + } + } +} diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 027ad159d0..5ba02221e1 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -92,7 +92,6 @@ #include #include #include -#include #include #include #include diff --git a/interface/src/Util.cpp b/interface/src/Util.cpp index 9a013c8f6f..5d625ba3a3 100644 --- a/interface/src/Util.cpp +++ b/interface/src/Util.cpp @@ -23,7 +23,6 @@ #include #include -#include #include "InterfaceConfig.h" #include "world.h" @@ -79,27 +78,6 @@ const glm::vec3 randVector() { return glm::vec3(randFloat() - 0.5f, randFloat() - 0.5f, randFloat() - 0.5f) * 2.0f; } -static TextRenderer* textRenderer(int mono) { - static TextRenderer* monoRenderer = TextRenderer::getInstance(MONO_FONT_FAMILY); - static TextRenderer* proportionalRenderer = TextRenderer::getInstance(SANS_FONT_FAMILY, - -1, -1, false, TextRenderer::SHADOW_EFFECT); - static TextRenderer* inconsolataRenderer = TextRenderer::getInstance(INCONSOLATA_FONT_FAMILY, -1, INCONSOLATA_FONT_WEIGHT, - false); - switch (mono) { - case 1: - return monoRenderer; - case 2: - return inconsolataRenderer; - case 0: - default: - return proportionalRenderer; - } -} - -int widthText(float scale, int mono, char const* string) { - return textRenderer(mono)->computeExtent(string).x; // computeWidth(string) * (scale / 0.10); -} - void renderCollisionOverlay(int width, int height, float magnitude, float red, float blue, float green) { const float MIN_VISIBLE_COLLISION = 0.01f; if (magnitude > MIN_VISIBLE_COLLISION) { diff --git a/interface/src/Util.h b/interface/src/Util.h index a422918d9a..2599847f7e 100644 --- a/interface/src/Util.h +++ b/interface/src/Util.h @@ -22,7 +22,6 @@ float randFloat(); const glm::vec3 randVector(); void renderWorldBox(gpu::Batch& batch); -int widthText(float scale, int mono, char const* string); void renderCollisionOverlay(int width, int height, float magnitude, float red = 0, float blue = 0, float green = 0); diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 6374b6b10b..239359c1cf 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -281,7 +281,7 @@ enum TextRendererType { static TextRenderer3D* textRenderer(TextRendererType type) { static TextRenderer3D* chatRenderer = TextRenderer3D::getInstance(SANS_FONT_FAMILY, -1, - false, TextRenderer3D::SHADOW_EFFECT); + false, SHADOW_EFFECT); static TextRenderer3D* displayNameRenderer = TextRenderer3D::getInstance(SANS_FONT_FAMILY); switch(type) { diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 053ce7b3cc..93303c3536 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -19,6 +19,7 @@ #include +#include #include #include #include @@ -31,7 +32,7 @@ #include #include #include -#include +#include #include #include "devices/Faceshift.h" diff --git a/interface/src/ui/overlays/ImageOverlay.cpp b/interface/src/ui/overlays/ImageOverlay.cpp index ce6e12d73e..73a3f7e39c 100644 --- a/interface/src/ui/overlays/ImageOverlay.cpp +++ b/interface/src/ui/overlays/ImageOverlay.cpp @@ -55,15 +55,15 @@ void ImageOverlay::render(RenderArgs* args) { _isLoaded = true; _texture = DependencyManager::get()->getTexture(_imageURL); } - // If we are not visible or loaded, return. If we are trying to render an // image but the texture hasn't loaded, return. if (!_visible || !_isLoaded || (_renderImage && !_texture->isLoaded())) { return; } + auto geometryCache = DependencyManager::get(); gpu::Batch& batch = *args->_batch; - + geometryCache->useSimpleDrawPipeline(batch); if (_renderImage) { batch.setResourceTexture(0, _texture->getGPUTexture()); } else { diff --git a/interface/src/ui/overlays/TextOverlay.cpp b/interface/src/ui/overlays/TextOverlay.cpp index 3f033d9266..466e536189 100644 --- a/interface/src/ui/overlays/TextOverlay.cpp +++ b/interface/src/ui/overlays/TextOverlay.cpp @@ -10,15 +10,70 @@ // include this before QGLWidget, which includes an earlier version of OpenGL #include "InterfaceConfig.h" - +#include "Application.h" #include "TextOverlay.h" - +#include "OffscreenUi.h" +#include "text/FontFamilies.h" +#include #include #include +#include +#include #include #include -#include +#include +#include +#define TEXT_OVERLAY_PROPERTY(type, name, initialValue) \ + Q_PROPERTY(type name READ name WRITE set##name NOTIFY name##Changed) \ +public: \ + type name() { return _##name; }; \ + void set##name(const type& name) { \ + if (name != _##name) { \ + _##name = name; \ + emit name##Changed(); \ + } \ + } \ +private: \ + type _##name{ initialValue }; + + +class TextOverlayElement : public QQuickItem { + Q_OBJECT + HIFI_QML_DECL +private: + TEXT_OVERLAY_PROPERTY(QString, text, "") + TEXT_OVERLAY_PROPERTY(QString, fontFamily, SANS_FONT_FAMILY) + TEXT_OVERLAY_PROPERTY(QString, textColor, "#ffffffff") + TEXT_OVERLAY_PROPERTY(QString, backgroundColor, "#B2000000") + TEXT_OVERLAY_PROPERTY(qreal, fontSize, 18) + TEXT_OVERLAY_PROPERTY(qreal, leftMargin, 0) + TEXT_OVERLAY_PROPERTY(qreal, topMargin, 0) + +public: + TextOverlayElement(QQuickItem* parent = nullptr) : QQuickItem(parent) { + } + +signals: + void textChanged(); + void fontFamilyChanged(); + void fontSizeChanged(); + void leftMarginChanged(); + void topMarginChanged(); + void textColorChanged(); + void backgroundColorChanged(); +}; + +HIFI_QML_DEF(TextOverlayElement) + +QString toQmlColor(const glm::vec4& v) { + QString templat("#%1%2%3%4"); + return templat. + arg((int)(v.a * 255), 2, 16, QChar('0')). + arg((int)(v.r * 255), 2, 16, QChar('0')). + arg((int)(v.g * 255), 2, 16, QChar('0')). + arg((int)(v.b * 255), 2, 16, QChar('0')); +} TextOverlay::TextOverlay() : _backgroundColor(DEFAULT_BACKGROUND_COLOR), @@ -27,7 +82,20 @@ TextOverlay::TextOverlay() : _topMargin(DEFAULT_MARGIN), _fontSize(DEFAULT_FONTSIZE) { - _textRenderer = TextRenderer::getInstance(SANS_FONT_FAMILY, _fontSize, DEFAULT_FONT_WEIGHT); + + qApp->postLambdaEvent([=] { + static std::once_flag once; + std::call_once(once, [] { + TextOverlayElement::registerType(); + }); + auto offscreenUi = DependencyManager::get(); + TextOverlayElement::show([=](QQmlContext* context, QObject* object) { + _qmlElement = static_cast(object); + }); + }); + while (!_qmlElement) { + QThread::msleep(1); + } } TextOverlay::TextOverlay(const TextOverlay* textOverlay) : @@ -39,11 +107,21 @@ TextOverlay::TextOverlay(const TextOverlay* textOverlay) : _topMargin(textOverlay->_topMargin), _fontSize(textOverlay->_fontSize) { - _textRenderer = TextRenderer::getInstance(SANS_FONT_FAMILY, _fontSize, DEFAULT_FONT_WEIGHT); + qApp->postLambdaEvent([=] { + auto offscreenUi = DependencyManager::get(); + TextOverlayElement::show([this](QQmlContext* context, QObject* object) { + _qmlElement = static_cast(object); + }); + }); + while (!_qmlElement) { + QThread::sleep(1); + } } TextOverlay::~TextOverlay() { - delete _textRenderer; + if (_qmlElement) { + _qmlElement->deleteLater(); + } } xColor TextOverlay::getBackgroundColor() { @@ -65,44 +143,29 @@ xColor TextOverlay::getBackgroundColor() { return result; } - void TextOverlay::render(RenderArgs* args) { - if (!_visible) { - return; // do nothing if we're not visible + if (_visible != _qmlElement->isVisible()) { + _qmlElement->setVisible(_visible); } + float pulseLevel = updatePulse(); + static float _oldPulseLevel = 0.0f; + if (pulseLevel != _oldPulseLevel) { - const float MAX_COLOR = 255.0f; - xColor backgroundColor = getBackgroundColor(); - glm::vec4 quadColor(backgroundColor.red / MAX_COLOR, backgroundColor.green / MAX_COLOR, backgroundColor.blue / MAX_COLOR, - getBackgroundAlpha()); - - int left = _bounds.left(); - int right = _bounds.right() + 1; - int top = _bounds.top(); - int bottom = _bounds.bottom() + 1; - - glm::vec2 topLeft(left, top); - glm::vec2 bottomRight(right, bottom); - glBindTexture(GL_TEXTURE_2D, 0); - DependencyManager::get()->renderQuad(topLeft, bottomRight, quadColor); - - const int leftAdjust = -1; // required to make text render relative to left edge of bounds - const int topAdjust = -2; // required to make text render relative to top edge of bounds - int x = _bounds.left() + _leftMargin + leftAdjust; - int y = _bounds.top() + _topMargin + topAdjust; - - float alpha = getAlpha(); - glm::vec4 textColor = {_color.red / MAX_COLOR, _color.green / MAX_COLOR, _color.blue / MAX_COLOR, alpha }; - _textRenderer->draw(x, y, _text, textColor); + } } + void TextOverlay::setProperties(const QScriptValue& properties) { Overlay2D::setProperties(properties); - + _qmlElement->setX(_bounds.left()); + _qmlElement->setY(_bounds.top()); + _qmlElement->setWidth(_bounds.width()); + _qmlElement->setHeight(_bounds.height()); + _qmlElement->settextColor(toQmlColor(vec4(toGlm(_color), _alpha))); QScriptValue font = properties.property("font"); if (font.isObject()) { if (font.property("size").isValid()) { - setFontSize(font.property("size").toInt32()); + setFontSize(font.property("size").toInt32() / 1.2); } } @@ -126,6 +189,7 @@ void TextOverlay::setProperties(const QScriptValue& properties) { if (properties.property("backgroundAlpha").isValid()) { _backgroundAlpha = properties.property("backgroundAlpha").toVariant().toFloat(); } + _qmlElement->setbackgroundColor(toQmlColor(vec4(toGlm(_backgroundColor), _backgroundAlpha))); if (properties.property("leftMargin").isValid()) { setLeftMargin(properties.property("leftMargin").toVariant().toInt()); @@ -166,15 +230,35 @@ QScriptValue TextOverlay::getProperty(const QString& property) { } QSizeF TextOverlay::textSize(const QString& text) const { - auto extents = _textRenderer->computeExtent(text); - - return QSizeF(extents.x, extents.y); + int lines = 1; + foreach(QChar c, text) { + if (c == "\n") { + ++lines; + } + } + QFontMetrics fm(QFont(SANS_FONT_FAMILY, _fontSize)); + QSizeF result = QSizeF(fm.width(text), fm.lineSpacing() * lines); + return result; } void TextOverlay::setFontSize(int fontSize) { _fontSize = fontSize; - - auto oldTextRenderer = _textRenderer; - _textRenderer = TextRenderer::getInstance(SANS_FONT_FAMILY, _fontSize, DEFAULT_FONT_WEIGHT); - delete oldTextRenderer; + _qmlElement->setfontSize(fontSize); } + +void TextOverlay::setText(const QString& text) { + _text = text; + _qmlElement->settext(text); +} + +void TextOverlay::setLeftMargin(int margin) { + _leftMargin = margin; + _qmlElement->setleftMargin(margin); +} + +void TextOverlay::setTopMargin(int margin) { + _topMargin = margin; + _qmlElement->settopMargin(margin); +} + +#include "TextOverlay.moc" \ No newline at end of file diff --git a/interface/src/ui/overlays/TextOverlay.h b/interface/src/ui/overlays/TextOverlay.h index 32786c3220..7ada26f892 100644 --- a/interface/src/ui/overlays/TextOverlay.h +++ b/interface/src/ui/overlays/TextOverlay.h @@ -26,7 +26,7 @@ const int DEFAULT_MARGIN = 10; const int DEFAULT_FONTSIZE = 12; const int DEFAULT_FONT_WEIGHT = 50; -class TextRenderer; +class TextOverlayElement; class TextOverlay : public Overlay2D { Q_OBJECT @@ -45,9 +45,9 @@ public: float getBackgroundAlpha() const { return _backgroundAlpha; } // setters - void setText(const QString& text) { _text = text; } - void setLeftMargin(int margin) { _leftMargin = margin; } - void setTopMargin(int margin) { _topMargin = margin; } + void setText(const QString& text); + void setLeftMargin(int margin); + void setTopMargin(int margin); void setFontSize(int fontSize); virtual void setProperties(const QScriptValue& properties); @@ -57,9 +57,7 @@ public: QSizeF textSize(const QString& text) const; // Pixels private: - - TextRenderer* _textRenderer = nullptr; - + TextOverlayElement* _qmlElement{ nullptr }; QString _text; xColor _backgroundColor; float _backgroundAlpha; diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index 95147ef2b1..219d066611 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -17,7 +17,6 @@ #include #include #include -#include #include #include #include diff --git a/libraries/render-utils/src/TextRenderer.cpp b/libraries/render-utils/src/TextRenderer.cpp deleted file mode 100644 index 61a7ce78cf..0000000000 --- a/libraries/render-utils/src/TextRenderer.cpp +++ /dev/null @@ -1,576 +0,0 @@ -// -// TextRenderer.cpp -// interface/src/ui -// -// Created by Andrzej Kapolka on 4/24/13. -// Copyright 2013 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - - -#include -#include -#include -#include -#include -#include -#include - -#ifdef __GNUC__ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdouble-promotion" -#endif - -// FIXME, decouple from the GL headers -#include -#include -#include -#include - -#ifdef __GNUC__ -#pragma GCC diagnostic pop -#endif - -#include -#include - -#include "gpu/GLBackend.h" -#include "gpu/Stream.h" - -#include "GLMHelpers.h" -#include "MatrixStack.h" -#include "RenderUtilsLogging.h" -#include "TextRenderer.h" - -#include "sdf_text_vert.h" -#include "sdf_text_frag.h" - -// Helper functions for reading binary data from an IO device -template -void readStream(QIODevice & in, T & t) { - in.read((char*) &t, sizeof(t)); -} - -template -void readStream(QIODevice & in, T (&t)[N]) { - in.read((char*) t, N); -} - -template -void fillBuffer(QBuffer & buffer, T (&t)[N]) { - buffer.setData((const char*) t, N); -} - -// FIXME support the shadow effect, or remove it from the API -// FIXME figure out how to improve the anti-aliasing on the -// interior of the outline fonts - -// stores the font metrics for a single character -struct Glyph { - QChar c; - glm::vec2 texOffset; - glm::vec2 texSize; - glm::vec2 size; - glm::vec2 offset; - float d; // xadvance - adjusts character positioning - size_t indexOffset; - - QRectF bounds() const; - QRectF textureBounds(const glm::vec2 & textureSize) const; - - void read(QIODevice & in); -}; - -void Glyph::read(QIODevice & in) { - uint16_t charcode; - readStream(in, charcode); - c = charcode; - readStream(in, texOffset); - readStream(in, size); - readStream(in, offset); - readStream(in, d); - texSize = size; -} - -const float DEFAULT_POINT_SIZE = 12; - -class Font { -public: - - Font(); - - using TexturePtr = QSharedPointer < QOpenGLTexture >; - using VertexArrayPtr = QSharedPointer< QOpenGLVertexArrayObject >; - using ProgramPtr = QSharedPointer < QOpenGLShaderProgram >; - using BufferPtr = QSharedPointer < QOpenGLBuffer >; - - // maps characters to cached glyph info - // HACK... the operator[] const for QHash returns a - // copy of the value, not a const value reference, so - // we declare the hash as mutable in order to avoid such - // copies - mutable QHash _glyphs; - - // the id of the glyph texture to which we're currently writing - GLuint _currentTextureID; - - int _pointSize; - - // the height of the current row of characters - int _rowHeight; - - QString _family; - float _fontSize { 0 }; - float _leading { 0 }; - float _ascent { 0 }; - float _descent { 0 }; - float _spaceWidth { 0 }; - - BufferPtr _vertices; - BufferPtr _indices; - TexturePtr _texture; - VertexArrayPtr _vao; - QImage _image; - ProgramPtr _program; - - const Glyph & getGlyph(const QChar & c) const; - void read(QIODevice& path); - // Initialize the OpenGL structures - void setupGL(); - - glm::vec2 computeExtent(const QString & str) const; - - glm::vec2 computeTokenExtent(const QString & str) const; - - glm::vec2 drawString(float x, float y, const QString & str, - const glm::vec4& color, TextRenderer::EffectType effectType, - const glm::vec2& bound); - -private: - QStringList tokenizeForWrapping(const QString & str) const; - - bool _initialized; -}; - -static QHash LOADED_FONTS; - -Font* loadFont(QIODevice& fontFile) { - Font* result = new Font(); - result->read(fontFile); - return result; -} - -Font* loadFont(const QString& family) { - if (!LOADED_FONTS.contains(family)) { - - const QString SDFF_COURIER_PRIME_FILENAME = ":/CourierPrime.sdff"; - const QString SDFF_INCONSOLATA_MEDIUM_FILENAME = ":/InconsolataMedium.sdff"; - const QString SDFF_ROBOTO_FILENAME = ":/Roboto.sdff"; - const QString SDFF_TIMELESS_FILENAME = ":/Timeless.sdff"; - - QString loadFilename; - - if (family == MONO_FONT_FAMILY) { - loadFilename = SDFF_COURIER_PRIME_FILENAME; - } else if (family == INCONSOLATA_FONT_FAMILY) { - loadFilename = SDFF_INCONSOLATA_MEDIUM_FILENAME; - } else if (family == SANS_FONT_FAMILY) { - loadFilename = SDFF_ROBOTO_FILENAME; - } else { - if (!LOADED_FONTS.contains(SERIF_FONT_FAMILY)) { - loadFilename = SDFF_TIMELESS_FILENAME; - } else { - LOADED_FONTS[family] = LOADED_FONTS[SERIF_FONT_FAMILY]; - } - } - - if (!loadFilename.isEmpty()) { - QFile fontFile(loadFilename); - fontFile.open(QIODevice::ReadOnly); - - qCDebug(renderutils) << "Loaded font" << loadFilename << "from Qt Resource System."; - - LOADED_FONTS[family] = loadFont(fontFile); - } - } - return LOADED_FONTS[family]; -} - -Font::Font() : _initialized(false) { - static bool fontResourceInitComplete = false; - if (!fontResourceInitComplete) { - Q_INIT_RESOURCE(fonts); - fontResourceInitComplete = true; - } -} - -// NERD RAGE: why doesn't QHash have a 'const T & operator[] const' member -const Glyph & Font::getGlyph(const QChar & c) const { - if (!_glyphs.contains(c)) { - return _glyphs[QChar('?')]; - } - return _glyphs[c]; -} - -void Font::read(QIODevice& in) { - uint8_t header[4]; - readStream(in, header); - if (memcmp(header, "SDFF", 4)) { - qFatal("Bad SDFF file"); - } - - uint16_t version; - readStream(in, version); - - // read font name - _family = ""; - if (version > 0x0001) { - char c; - readStream(in, c); - while (c) { - _family += c; - readStream(in, c); - } - } - - // read font data - readStream(in, _leading); - readStream(in, _ascent); - readStream(in, _descent); - readStream(in, _spaceWidth); - _fontSize = _ascent + _descent; - _rowHeight = _fontSize + _descent; - - // Read character count - uint16_t count; - readStream(in, count); - // read metrics data for each character - QVector glyphs(count); - // std::for_each instead of Qt foreach because we need non-const references - std::for_each(glyphs.begin(), glyphs.end(), [&](Glyph & g) { - g.read(in); - }); - - // read image data - if (!_image.loadFromData(in.readAll(), "PNG")) { - qFatal("Failed to read SDFF image"); - } - - _glyphs.clear(); - foreach(Glyph g, glyphs) { - // Adjust the pixel texture coordinates into UV coordinates, - glm::vec2 imageSize = toGlm(_image.size()); - g.texSize /= imageSize; - g.texOffset /= imageSize; - // store in the character to glyph hash - _glyphs[g.c] = g; - }; -} - -struct TextureVertex { - glm::vec2 pos; - glm::vec2 tex; - TextureVertex() { - } - TextureVertex(const glm::vec2 & pos, const glm::vec2 & tex) : - pos(pos), tex(tex) { - } - TextureVertex(const QPointF & pos, const QPointF & tex) : - pos(pos.x(), pos.y()), tex(tex.x(), tex.y()) { - } -}; - -struct QuadBuilder { - TextureVertex vertices[4]; - QuadBuilder(const QRectF & r, const QRectF & tr) { - vertices[0] = TextureVertex(r.bottomLeft(), tr.topLeft()); - vertices[1] = TextureVertex(r.bottomRight(), tr.topRight()); - vertices[2] = TextureVertex(r.topRight(), tr.bottomRight()); - vertices[3] = TextureVertex(r.topLeft(), tr.bottomLeft()); - } -}; - -QRectF Glyph::bounds() const { - return glmToRect(offset, size); -} - -QRectF Glyph::textureBounds(const glm::vec2 & textureSize) const { - return glmToRect(texOffset, texSize); -} - -void Font::setupGL() { - if (_initialized) { - return; - } - _initialized = true; - - _texture = TexturePtr( - new QOpenGLTexture(_image, QOpenGLTexture::GenerateMipMaps)); - _program = ProgramPtr(new QOpenGLShaderProgram()); - if (!_program->create()) { - qFatal("Could not create text shader"); - } - if (!_program->addShaderFromSourceCode(QOpenGLShader::Vertex, sdf_text_vert) || // - !_program->addShaderFromSourceCode(QOpenGLShader::Fragment, sdf_text_frag) || // - !_program->link()) { - qFatal("%s", _program->log().toLocal8Bit().constData()); - } - - std::vector vertexData; - std::vector indexData; - vertexData.reserve(_glyphs.size() * 4); - std::for_each(_glyphs.begin(), _glyphs.end(), [&](Glyph & m) { - GLuint index = (GLuint)vertexData.size(); - - QRectF bounds = m.bounds(); - QRectF texBounds = m.textureBounds(toGlm(_image.size())); - QuadBuilder qb(bounds, texBounds); - for (int i = 0; i < 4; ++i) { - vertexData.push_back(qb.vertices[i]); - } - - m.indexOffset = indexData.size() * sizeof(GLuint); - // FIXME use triangle strips + primitive restart index - indexData.push_back(index + 0); - indexData.push_back(index + 1); - indexData.push_back(index + 2); - indexData.push_back(index + 0); - indexData.push_back(index + 2); - indexData.push_back(index + 3); - }); - - _vao = VertexArrayPtr(new QOpenGLVertexArrayObject()); - _vao->create(); - _vao->bind(); - - _vertices = BufferPtr(new QOpenGLBuffer(QOpenGLBuffer::VertexBuffer)); - _vertices->create(); - _vertices->bind(); - _vertices->allocate(&vertexData[0], - sizeof(TextureVertex) * vertexData.size()); - _indices = BufferPtr(new QOpenGLBuffer(QOpenGLBuffer::IndexBuffer)); - _indices->create(); - _indices->bind(); - _indices->allocate(&indexData[0], sizeof(GLuint) * indexData.size()); - - GLsizei stride = (GLsizei) sizeof(TextureVertex); - void* offset = (void*) offsetof(TextureVertex, tex); - int posLoc = _program->attributeLocation("Position"); - int texLoc = _program->attributeLocation("TexCoord"); - glEnableVertexAttribArray(posLoc); - glVertexAttribPointer(posLoc, 2, GL_FLOAT, false, stride, nullptr); - glEnableVertexAttribArray(texLoc); - glVertexAttribPointer(texLoc, 2, GL_FLOAT, false, stride, offset); - _vao->release(); -} - -// FIXME there has to be a cleaner way of doing this -QStringList Font::tokenizeForWrapping(const QString & str) const { - QStringList result; - foreach(const QString & token1, str.split(" ")) { - bool lineFeed = false; - if (token1.isEmpty()) { - result << token1; - continue; - } - foreach(const QString & token2, token1.split("\n")) { - if (lineFeed) { - result << "\n"; - } - if (token2.size()) { - result << token2; - } - lineFeed = true; - } - } - return result; -} - - -glm::vec2 Font::computeTokenExtent(const QString & token) const { - glm::vec2 advance(0, _rowHeight - _descent); - foreach(QChar c, token) { - assert(c != ' ' && c != '\n'); - const Glyph & m = getGlyph(c); - advance.x += m.d; - } - return advance; -} - - -glm::vec2 Font::computeExtent(const QString & str) const { - glm::vec2 extent(0, _rowHeight - _descent); - // FIXME, come up with a better method of splitting text - // that will allow wrapping but will preserve things like - // tabs or consecutive spaces - bool firstTokenOnLine = true; - float lineWidth = 0.0f; - QStringList tokens = tokenizeForWrapping(str); - foreach(const QString & token, tokens) { - if (token == "\n") { - extent.x = std::max(lineWidth, extent.x); - lineWidth = 0.0f; - extent.y += _rowHeight; - firstTokenOnLine = true; - continue; - } - if (!firstTokenOnLine) { - lineWidth += _spaceWidth; - } - lineWidth += computeTokenExtent(token).x; - firstTokenOnLine = false; - } - extent.x = std::max(lineWidth, extent.x); - return extent; -} - -// FIXME support the maxWidth parameter and allow the text to automatically wrap -// even without explicit line feeds. -glm::vec2 Font::drawString(float x, float y, const QString & str, - const glm::vec4& color, TextRenderer::EffectType effectType, - const glm::vec2& bounds) { - - setupGL(); - - // Stores how far we've moved from the start of the string, in DTP units - glm::vec2 advance(0, -_rowHeight - _descent); - - _program->bind(); - _program->setUniformValue("Color", color.r, color.g, color.b, color.a); - _program->setUniformValue("Projection", - fromGlm(MatrixStack::projection().top())); - if (effectType == TextRenderer::OUTLINE_EFFECT) { - _program->setUniformValue("Outline", true); - } - // Needed? - glEnable(GL_TEXTURE_2D); - _texture->bind(); - _vao->bind(); - - MatrixStack & mv = MatrixStack::modelview(); - // scale the modelview into font units - mv.translate(glm::vec3(0, _ascent, 0)); - foreach(const QString & token, tokenizeForWrapping(str)) { - if (token == "\n") { - advance.x = 0.0f; - advance.y -= _rowHeight; - // If we've wrapped right out of the bounds, then we're - // done with rendering the tokens - if (bounds.y > 0 && std::abs(advance.y) > bounds.y) { - break; - } - continue; - } - - glm::vec2 tokenExtent = computeTokenExtent(token); - if (bounds.x > 0 && advance.x > 0) { - // We check if we'll be out of bounds - if (advance.x + tokenExtent.x >= bounds.x) { - // We're out of bounds, so wrap to the next line - advance.x = 0.0f; - advance.y -= _rowHeight; - // If we've wrapped right out of the bounds, then we're - // done with rendering the tokens - if (bounds.y > 0 && std::abs(advance.y) > bounds.y) { - break; - } - } - } - - foreach(const QChar & c, token) { - // get metrics for this character to speed up measurements - const Glyph & m = getGlyph(c); - // We create an offset vec2 to hold the local offset of this character - // This includes compensating for the inverted Y axis of the font - // coordinates - glm::vec2 offset(advance); - offset.y -= m.size.y; - // Bind the new position - mv.withPush([&] { - mv.translate(offset); - // FIXME find a better (and GL ES 3.1 compatible) way of rendering the text - // that doesn't involve a single GL call per character. - // Most likely an 'indirect' call or an 'instanced' call. - _program->setUniformValue("ModelView", fromGlm(mv.top())); - glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, (void*)(m.indexOffset)); - }); - advance.x += m.d; - } - advance.x += _spaceWidth; - } - - _vao->release(); - _texture->release(); // TODO: Brad & Sam, let's discuss this. Without this non-textured quads get their colors borked. - _program->release(); - - return advance; -} - -TextRenderer* TextRenderer::getInstance(const char* family, float pointSize, - int weight, bool italic, EffectType effect, int effectThickness, - const QColor& color) { - if (pointSize < 0) { - pointSize = DEFAULT_POINT_SIZE; - } - return new TextRenderer(family, pointSize, weight, italic, effect, - effectThickness, color); -} - -TextRenderer::TextRenderer(const char* family, float pointSize, int weight, - bool italic, EffectType effect, int effectThickness, - const QColor& color) : - _effectType(effect), _effectThickness(effectThickness), _pointSize(pointSize), _color(color), _font(loadFont(family)) { - if (!_font) { - qWarning() << "Unable to load font with family " << family; - _font = loadFont("Courier"); - } - if (1 != _effectThickness) { - qWarning() << "Effect thickness not current supported"; - } - if (NO_EFFECT != _effectType && OUTLINE_EFFECT != _effectType) { - qWarning() << "Effect thickness not current supported"; - } -} - -TextRenderer::~TextRenderer() { -} - -glm::vec2 TextRenderer::computeExtent(const QString & str) const { - float scale = (_pointSize / DEFAULT_POINT_SIZE) * 0.25f; - if (_font) { - return _font->computeExtent(str) * scale; - } - return glm::vec2(0.1f,0.1f); -} - -float TextRenderer::draw(float x, float y, const QString & str, - const glm::vec4& color, const glm::vec2 & bounds) { - glm::vec4 actualColor(color); - if (actualColor.r < 0) { - actualColor = toGlm(_color); - } - - float scale = (_pointSize / DEFAULT_POINT_SIZE) * 0.25f; - glm::vec2 result; - - MatrixStack::withPushAll([&] { - MatrixStack & mv = MatrixStack::modelview(); - MatrixStack & pr = MatrixStack::projection(); - gpu::GLBackend::fetchMatrix(GL_MODELVIEW_MATRIX, mv.top()); - gpu::GLBackend::fetchMatrix(GL_PROJECTION_MATRIX, pr.top()); - - // scale the modelview into font units - // FIXME migrate the constant scale factor into the geometry of the - // fonts so we don't have to flip the Y axis here and don't have to - // scale at all. - mv.translate(glm::vec2(x, y)).scale(glm::vec3(scale, -scale, scale)); - // The font does all the OpenGL work - if (_font) { - result = _font->drawString(x, y, str, actualColor, _effectType, bounds / scale); - } - }); - return result.x; -} - diff --git a/libraries/render-utils/src/TextRenderer.h b/libraries/render-utils/src/TextRenderer.h deleted file mode 100644 index 85bb18cec9..0000000000 --- a/libraries/render-utils/src/TextRenderer.h +++ /dev/null @@ -1,89 +0,0 @@ -// -// TextRenderer.h -// interface/src/ui -// -// Created by Andrzej Kapolka on 4/26/13. -// Copyright 2013 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#ifndef hifi_TextRenderer_h -#define hifi_TextRenderer_h - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -// a special "character" that renders as a solid block -const char SOLID_BLOCK_CHAR = 127; - -// the standard sans serif font family -#define SANS_FONT_FAMILY "Helvetica" - -// the standard sans serif font family -#define SERIF_FONT_FAMILY "Timeless" - -// the standard mono font family -#define MONO_FONT_FAMILY "Courier" - -// the Inconsolata font family -#ifdef Q_OS_WIN -#define INCONSOLATA_FONT_FAMILY "Fixedsys" -#define INCONSOLATA_FONT_WEIGHT QFont::Normal -#else -#define INCONSOLATA_FONT_FAMILY "Inconsolata" -#define INCONSOLATA_FONT_WEIGHT QFont::Bold -#endif - -class Font; - -// TextRenderer is actually a fairly thin wrapper around a Font class -// defined in the cpp file. -class TextRenderer { -public: - enum EffectType { NO_EFFECT, SHADOW_EFFECT, OUTLINE_EFFECT }; - - static TextRenderer* getInstance(const char* family, float pointSize = -1, int weight = -1, bool italic = false, - EffectType effect = NO_EFFECT, int effectThickness = 1, const QColor& color = QColor(255, 255, 255)); - - ~TextRenderer(); - - glm::vec2 computeExtent(const QString & str) const; - - float draw( - float x, float y, - const QString & str, - const glm::vec4& color = glm::vec4(-1.0f), - const glm::vec2& bounds = glm::vec2(-1.0f)); - -private: - TextRenderer(const char* family, float pointSize = -1, int weight = -1, bool italic = false, - EffectType effect = NO_EFFECT, int effectThickness = 1, const QColor& color = QColor(255, 255, 255)); - - // the type of effect to apply - const EffectType _effectType; - - // the thickness of the effect - const int _effectThickness; - - const float _pointSize; - - // text color - const QColor _color; - - Font * _font; -}; - - -#endif // hifi_TextRenderer_h diff --git a/libraries/render-utils/src/TextRenderer3D.cpp b/libraries/render-utils/src/TextRenderer3D.cpp index 973cddc4d7..a93ba1daec 100644 --- a/libraries/render-utils/src/TextRenderer3D.cpp +++ b/libraries/render-utils/src/TextRenderer3D.cpp @@ -10,6 +10,7 @@ // #include "TextRenderer3D.h" +#include #include #include @@ -20,6 +21,8 @@ #include #include +#include "text/Font.h" + #include "GLMHelpers.h" #include "MatrixStack.h" #include "RenderUtilsLogging.h" @@ -30,421 +33,22 @@ #include "GeometryCache.h" #include "DeferredLightingEffect.h" -// Helper functions for reading binary data from an IO device -template -void readStream(QIODevice& in, T& t) { - in.read((char*) &t, sizeof(t)); +const float TextRenderer3D::DEFAULT_POINT_SIZE = 12; + +TextRenderer3D* TextRenderer3D::getInstance(const char* family, float pointSize, + bool bold, bool italic, EffectType effect, int effectThickness) { + return new TextRenderer3D(family, pointSize, false, italic, effect, effectThickness); } -template -void readStream(QIODevice& in, T (&t)[N]) { - in.read((char*) t, N); -} - -template -void fillBuffer(QBuffer& buffer, T (&t)[N]) { - buffer.setData((const char*) t, N); -} - -// stores the font metrics for a single character -struct Glyph3D { - QChar c; - glm::vec2 texOffset; - glm::vec2 texSize; - glm::vec2 size; - glm::vec2 offset; - float d; // xadvance - adjusts character positioning - size_t indexOffset; - - // We adjust bounds because offset is the bottom left corner of the font but the top left corner of a QRect - QRectF bounds() const { return glmToRect(offset, size).translated(0.0f, -size.y); } - QRectF textureBounds() const { return glmToRect(texOffset, texSize); } - - void read(QIODevice& in); -}; - -void Glyph3D::read(QIODevice& in) { - uint16_t charcode; - readStream(in, charcode); - c = charcode; - readStream(in, texOffset); - readStream(in, size); - readStream(in, offset); - readStream(in, d); - texSize = size; -} - -struct TextureVertex { - glm::vec2 pos; - glm::vec2 tex; - TextureVertex() {} - TextureVertex(const glm::vec2& pos, const glm::vec2& tex) : pos(pos), tex(tex) {} -}; - -struct QuadBuilder { - TextureVertex vertices[4]; - QuadBuilder(const glm::vec2& min, const glm::vec2& size, - const glm::vec2& texMin, const glm::vec2& texSize) { - // min = bottomLeft - vertices[0] = TextureVertex(min, - texMin + glm::vec2(0.0f, texSize.y)); - vertices[1] = TextureVertex(min + glm::vec2(size.x, 0.0f), - texMin + texSize); - vertices[2] = TextureVertex(min + size, - texMin + glm::vec2(texSize.x, 0.0f)); - vertices[3] = TextureVertex(min + glm::vec2(0.0f, size.y), - texMin); - } - QuadBuilder(const Glyph3D& glyph, const glm::vec2& offset) : - QuadBuilder(offset + glm::vec2(glyph.offset.x, glyph.offset.y - glyph.size.y), glyph.size, - glyph.texOffset, glyph.texSize) {} - -}; - -class Font3D { -public: - Font3D(); - - void read(QIODevice& path); - - glm::vec2 computeExtent(const QString& str) const; - float getFontSize() const { return _fontSize; } - - // Render string to batch - void drawString(gpu::Batch& batch, float x, float y, const QString& str, - const glm::vec4* color, TextRenderer3D::EffectType effectType, - const glm::vec2& bound); - -private: - QStringList tokenizeForWrapping(const QString& str) const; - QStringList splitLines(const QString& str) const; - glm::vec2 computeTokenExtent(const QString& str) const; - - const Glyph3D& getGlyph(const QChar& c) const; - - void setupGPU(); - - // maps characters to cached glyph info - // HACK... the operator[] const for QHash returns a - // copy of the value, not a const value reference, so - // we declare the hash as mutable in order to avoid such - // copies - mutable QHash _glyphs; - - // Font characteristics - QString _family; - float _fontSize = 0.0f; - float _leading = 0.0f; - float _ascent = 0.0f; - float _descent = 0.0f; - float _spaceWidth = 0.0f; - - bool _initialized = false; - - // gpu structures - gpu::PipelinePointer _pipeline; - gpu::TexturePointer _texture; - gpu::Stream::FormatPointer _format; - gpu::BufferPointer _verticesBuffer; - gpu::BufferStreamPointer _stream; - unsigned int _numVertices = 0; - - int _fontLoc = -1; - int _outlineLoc = -1; - int _colorLoc = -1; - - // last string render characteristics - QString _lastStringRendered; - glm::vec2 _lastBounds; -}; - -static QHash LOADED_FONTS; - -Font3D* loadFont3D(QIODevice& fontFile) { - Font3D* result = new Font3D(); - result->read(fontFile); - return result; -} - -Font3D* loadFont3D(const QString& family) { - if (!LOADED_FONTS.contains(family)) { - - const QString SDFF_COURIER_PRIME_FILENAME = ":/CourierPrime.sdff"; - const QString SDFF_INCONSOLATA_MEDIUM_FILENAME = ":/InconsolataMedium.sdff"; - const QString SDFF_ROBOTO_FILENAME = ":/Roboto.sdff"; - const QString SDFF_TIMELESS_FILENAME = ":/Timeless.sdff"; - - QString loadFilename; - - if (family == MONO_FONT_FAMILY) { - loadFilename = SDFF_COURIER_PRIME_FILENAME; - } else if (family == INCONSOLATA_FONT_FAMILY) { - loadFilename = SDFF_INCONSOLATA_MEDIUM_FILENAME; - } else if (family == SANS_FONT_FAMILY) { - loadFilename = SDFF_ROBOTO_FILENAME; - } else { - if (!LOADED_FONTS.contains(SERIF_FONT_FAMILY)) { - loadFilename = SDFF_TIMELESS_FILENAME; - } else { - LOADED_FONTS[family] = LOADED_FONTS[SERIF_FONT_FAMILY]; - } - } - - if (!loadFilename.isEmpty()) { - QFile fontFile(loadFilename); - fontFile.open(QIODevice::ReadOnly); - - qCDebug(renderutils) << "Loaded font" << loadFilename << "from Qt Resource System."; - - LOADED_FONTS[family] = loadFont3D(fontFile); - } - } - return LOADED_FONTS[family]; -} - -Font3D::Font3D() { - static bool fontResourceInitComplete = false; - if (!fontResourceInitComplete) { - Q_INIT_RESOURCE(fonts); - fontResourceInitComplete = true; - } -} - -// NERD RAGE: why doesn't QHash have a 'const T & operator[] const' member -const Glyph3D& Font3D::getGlyph(const QChar& c) const { - if (!_glyphs.contains(c)) { - return _glyphs[QChar('?')]; - } - return _glyphs[c]; -} - -QStringList Font3D::splitLines(const QString& str) const { - return str.split('\n'); -} - -QStringList Font3D::tokenizeForWrapping(const QString& str) const { - QStringList tokens; - for(auto line : splitLines(str)) { - if (!tokens.empty()) { - tokens << QString('\n'); - } - tokens << line.split(' '); - } - return tokens; -} - -glm::vec2 Font3D::computeTokenExtent(const QString& token) const { - glm::vec2 advance(0, _fontSize); - foreach(QChar c, token) { - Q_ASSERT(c != '\n'); - advance.x += (c == ' ') ? _spaceWidth : getGlyph(c).d; - } - return advance; -} - -glm::vec2 Font3D::computeExtent(const QString& str) const { - glm::vec2 extent = glm::vec2(0.0f, 0.0f); - - QStringList lines{ splitLines(str) }; - if (!lines.empty()) { - for(const auto& line : lines) { - glm::vec2 tokenExtent = computeTokenExtent(line); - extent.x = std::max(tokenExtent.x, extent.x); - } - extent.y = lines.count() * _fontSize; - } - return extent; -} - -void Font3D::read(QIODevice& in) { - uint8_t header[4]; - readStream(in, header); - if (memcmp(header, "SDFF", 4)) { - qFatal("Bad SDFF file"); - } - - uint16_t version; - readStream(in, version); - - // read font name - _family = ""; - if (version > 0x0001) { - char c; - readStream(in, c); - while (c) { - _family += c; - readStream(in, c); - } - } - - // read font data - readStream(in, _leading); - readStream(in, _ascent); - readStream(in, _descent); - readStream(in, _spaceWidth); - _fontSize = _ascent + _descent; - - // Read character count - uint16_t count; - readStream(in, count); - // read metrics data for each character - QVector glyphs(count); - // std::for_each instead of Qt foreach because we need non-const references - std::for_each(glyphs.begin(), glyphs.end(), [&](Glyph3D& g) { - g.read(in); - }); - - // read image data - QImage image; - if (!image.loadFromData(in.readAll(), "PNG")) { - qFatal("Failed to read SDFF image"); - } - - _glyphs.clear(); - glm::vec2 imageSize = toGlm(image.size()); - foreach(Glyph3D g, glyphs) { - // Adjust the pixel texture coordinates into UV coordinates, - g.texSize /= imageSize; - g.texOffset /= imageSize; - // store in the character to glyph hash - _glyphs[g.c] = g; - }; - - image = image.convertToFormat(QImage::Format_RGBA8888); - - gpu::Element formatGPU = gpu::Element(gpu::VEC3, gpu::UINT8, gpu::RGB); - gpu::Element formatMip = gpu::Element(gpu::VEC3, gpu::UINT8, gpu::RGB); - if (image.hasAlphaChannel()) { - formatGPU = gpu::Element(gpu::VEC4, gpu::UINT8, gpu::RGBA); - formatMip = gpu::Element(gpu::VEC4, gpu::UINT8, gpu::BGRA); - } - _texture = gpu::TexturePointer(gpu::Texture::create2D(formatGPU, image.width(), image.height(), - gpu::Sampler(gpu::Sampler::FILTER_MIN_POINT_MAG_LINEAR))); - _texture->assignStoredMip(0, formatMip, image.byteCount(), image.constBits()); - _texture->autoGenerateMips(-1); -} - -void Font3D::setupGPU() { - if (!_initialized) { - _initialized = true; - - // Setup render pipeline - auto vertexShader = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(sdf_text3D_vert))); - auto pixelShader = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(sdf_text3D_frag))); - gpu::ShaderPointer program = gpu::ShaderPointer(gpu::Shader::createProgram(vertexShader, pixelShader)); - - gpu::Shader::BindingSet slotBindings; - gpu::Shader::makeProgram(*program, slotBindings); - - _fontLoc = program->getTextures().findLocation("Font"); - _outlineLoc = program->getUniforms().findLocation("Outline"); - _colorLoc = program->getUniforms().findLocation("Color"); - - gpu::StatePointer state = gpu::StatePointer(new gpu::State()); - state->setCullMode(gpu::State::CULL_BACK); - state->setDepthTest(true, true, gpu::LESS_EQUAL); - state->setBlendFunction(true, - gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, - gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); - _pipeline = gpu::PipelinePointer(gpu::Pipeline::create(program, state)); - - // Sanity checks - static const int OFFSET = offsetof(TextureVertex, tex); - assert(OFFSET == sizeof(glm::vec2)); - assert(sizeof(glm::vec2) == 2 * sizeof(float)); - assert(sizeof(TextureVertex) == 2 * sizeof(glm::vec2)); - assert(sizeof(QuadBuilder) == 4 * sizeof(TextureVertex)); - - // Setup rendering structures - _format.reset(new gpu::Stream::Format()); - _format->setAttribute(gpu::Stream::POSITION, 0, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::XYZ), 0); - _format->setAttribute(gpu::Stream::TEXCOORD, 0, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV), OFFSET); - } -} - -void Font3D::drawString(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4* color, - TextRenderer3D::EffectType effectType, const glm::vec2& bounds) { - if (str == "") { - return; - } - - if (str != _lastStringRendered || bounds != _lastBounds) { - _verticesBuffer.reset(new gpu::Buffer()); - _numVertices = 0; - _lastStringRendered = str; - _lastBounds = bounds; - - // Top left of text - glm::vec2 advance = glm::vec2(x, y); - foreach(const QString& token, tokenizeForWrapping(str)) { - bool isNewLine = (token == QString('\n')); - bool forceNewLine = false; - - // Handle wrapping - if (!isNewLine && (bounds.x != -1) && (advance.x + computeExtent(token).x > x + bounds.x)) { - // We are out of the x bound, force new line - forceNewLine = true; - } - if (isNewLine || forceNewLine) { - // Character return, move the advance to a new line - advance = glm::vec2(x, advance.y - _leading); - - if (isNewLine) { - // No need to draw anything, go directly to next token - continue; - } else if (computeExtent(token).x > bounds.x) { - // token will never fit, stop drawing - break; - } - } - if ((bounds.y != -1) && (advance.y - _fontSize < -y - bounds.y)) { - // We are out of the y bound, stop drawing - break; - } - - // Draw the token - if (!isNewLine) { - for (auto c : token) { - auto glyph = _glyphs[c]; - - QuadBuilder qd(glyph, advance - glm::vec2(0.0f, _ascent)); - _verticesBuffer->append(sizeof(QuadBuilder), (const gpu::Byte*)&qd); - _numVertices += 4; - - // Advance by glyph size - advance.x += glyph.d; - } - - // Add space after all non return tokens - advance.x += _spaceWidth; - } - } - } - - setupGPU(); - batch.setPipeline(_pipeline); - batch.setResourceTexture(_fontLoc, _texture); - batch._glUniform1i(_outlineLoc, (effectType == TextRenderer3D::OUTLINE_EFFECT)); - batch._glUniform4fv(_colorLoc, 1, (const GLfloat*)color); - - batch.setInputFormat(_format); - batch.setInputBuffer(0, _verticesBuffer, 0, _format->getChannels().at(0)._stride); - batch.draw(gpu::QUADS, _numVertices, 0); -} - -TextRenderer3D* TextRenderer3D::getInstance(const char* family, - int weight, bool italic, EffectType effect, int effectThickness) { - return new TextRenderer3D(family, weight, italic, effect, effectThickness); -} - -TextRenderer3D::TextRenderer3D(const char* family, int weight, bool italic, - EffectType effect, int effectThickness) : +TextRenderer3D::TextRenderer3D(const char* family, float pointSize, int weight, bool italic, + EffectType effect, int effectThickness) : + _pointSize(pointSize), _effectType(effect), _effectThickness(effectThickness), - _font(loadFont3D(family)) { + _font(Font::load(family)) { if (!_font) { qWarning() << "Unable to load font with family " << family; - _font = loadFont3D("Courier"); + _font = Font::load("Courier"); } if (1 != _effectThickness) { qWarning() << "Effect thickness not currently supported"; diff --git a/libraries/render-utils/src/TextRenderer3D.h b/libraries/render-utils/src/TextRenderer3D.h index c4200a8d23..c5af61a252 100644 --- a/libraries/render-utils/src/TextRenderer3D.h +++ b/libraries/render-utils/src/TextRenderer3D.h @@ -15,37 +15,25 @@ #include #include -// the standard sans serif font family -#define SANS_FONT_FAMILY "Helvetica" -// the standard sans serif font family -#define SERIF_FONT_FAMILY "Timeless" - -// the standard mono font family -#define MONO_FONT_FAMILY "Courier" - -// the Inconsolata font family -#ifdef Q_OS_WIN -#define INCONSOLATA_FONT_FAMILY "Fixedsys" -#define INCONSOLATA_FONT_WEIGHT QFont::Normal -#else -#define INCONSOLATA_FONT_FAMILY "Inconsolata" -#define INCONSOLATA_FONT_WEIGHT QFont::Bold -#endif namespace gpu { class Batch; } -class Font3D; +class Font; + +#include "text/EffectType.h" +#include "text/FontFamilies.h" // TextRenderer3D is actually a fairly thin wrapper around a Font class // defined in the cpp file. class TextRenderer3D { public: - enum EffectType { NO_EFFECT, SHADOW_EFFECT, OUTLINE_EFFECT }; + static const float DEFAULT_POINT_SIZE; + + static TextRenderer3D* getInstance(const char* family, float pointSize = DEFAULT_POINT_SIZE, + bool bold = false, bool italic = false, EffectType effect = NO_EFFECT, int effectThickness = 1); - static TextRenderer3D* getInstance(const char* family, int weight = -1, bool italic = false, - EffectType effect = NO_EFFECT, int effectThickness = 1); ~TextRenderer3D(); glm::vec2 computeExtent(const QString& str) const; @@ -55,7 +43,7 @@ public: const glm::vec2& bounds = glm::vec2(-1.0f)); private: - TextRenderer3D(const char* family, int weight = -1, bool italic = false, + TextRenderer3D(const char* family, float pointSize, int weight = -1, bool italic = false, EffectType effect = NO_EFFECT, int effectThickness = 1); // the type of effect to apply @@ -66,8 +54,9 @@ private: // text color glm::vec4 _color; + float _pointSize{ DEFAULT_POINT_SIZE }; - Font3D* _font; + Font* _font; }; diff --git a/libraries/render-utils/src/sdf_text.slf b/libraries/render-utils/src/sdf_text.slf deleted file mode 100644 index 1affbe4c57..0000000000 --- a/libraries/render-utils/src/sdf_text.slf +++ /dev/null @@ -1,49 +0,0 @@ -<@include gpu/Config.slh@> -<$VERSION_HEADER$> -// Generated on <$_SCRIBE_DATE$> -// sdf_text.frag -// fragment shader -// -// Created by Bradley Austin Davis on 2015-02-04 -// Based on fragment shader code from -// https://github.com/paulhoux/Cinder-Samples/blob/master/TextRendering/include/text/Text.cpp -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html - -uniform sampler2D Font; -uniform vec4 Color; -uniform bool Outline; - -varying vec2 vTexCoord; - -const float gamma = 2.6; -const float smoothing = 100.0; -const float interiorCutoff = 0.8; -const float outlineExpansion = 0.2; - -void main() { - // retrieve signed distance - float sdf = texture2D(Font, vTexCoord).r; - if (Outline) { - if (sdf > interiorCutoff) { - sdf = 1.0 - sdf; - } else { - sdf += outlineExpansion; - } - } - // perform adaptive anti-aliasing of the edges - // The larger we're rendering, the less anti-aliasing we need - float s = smoothing * length(fwidth(vTexCoord)); - float w = clamp( s, 0.0, 0.5); - float a = smoothstep(0.5 - w, 0.5 + w, sdf); - - // gamma correction for linear attenuation - a = pow(a, 1.0 / gamma); - - if (a < 0.01) { - discard; - } - - // final color - gl_FragColor = vec4(Color.rgb, a); -} \ No newline at end of file diff --git a/libraries/render-utils/src/sdf_text.slv b/libraries/render-utils/src/sdf_text.slv deleted file mode 100644 index 27db1c4985..0000000000 --- a/libraries/render-utils/src/sdf_text.slv +++ /dev/null @@ -1,24 +0,0 @@ -<@include gpu/Config.slh@> -<$VERSION_HEADER$> -// Generated on <$_SCRIBE_DATE$> -// sdf_text.vert -// vertex shader -// -// Created by Brad Davis on 10/14/13. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -uniform mat4 Projection; -uniform mat4 ModelView; - -attribute vec2 Position; -attribute vec2 TexCoord; - -varying vec2 vTexCoord; - -void main() { - vTexCoord = TexCoord; - gl_Position = Projection * ModelView * vec4(Position, 0.0, 1.0); -} \ No newline at end of file diff --git a/libraries/render-utils/src/text/EffectType.h b/libraries/render-utils/src/text/EffectType.h new file mode 100644 index 0000000000..63ec820036 --- /dev/null +++ b/libraries/render-utils/src/text/EffectType.h @@ -0,0 +1,15 @@ +// +// Created by Bradley Austin Davis on 2015/07/16 +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#pragma once +#ifndef hifi_EffectType_h +#define hifi_EffectType_h + +enum EffectType { NO_EFFECT, SHADOW_EFFECT, OUTLINE_EFFECT }; + +#endif diff --git a/libraries/render-utils/src/text/Font.cpp b/libraries/render-utils/src/text/Font.cpp new file mode 100644 index 0000000000..8e83c55fec --- /dev/null +++ b/libraries/render-utils/src/text/Font.cpp @@ -0,0 +1,366 @@ +#include "Font.h" + +#include +#include + +#include + +#include "sdf_text3D_vert.h" +#include "sdf_text3D_frag.h" +#include "sdf_text_vert.h" +#include "sdf_text_frag.h" + +#include "../RenderUtilsLogging.h" +#include "FontFamilies.h" + +struct TextureVertex { + glm::vec2 pos; + glm::vec2 tex; + TextureVertex() {} + TextureVertex(const glm::vec2& pos, const glm::vec2& tex) : pos(pos), tex(tex) {} +}; + +struct QuadBuilder { + TextureVertex vertices[4]; + QuadBuilder(const glm::vec2& min, const glm::vec2& size, + const glm::vec2& texMin, const glm::vec2& texSize) { + // min = bottomLeft + vertices[0] = TextureVertex(min, + texMin + glm::vec2(0.0f, texSize.y)); + vertices[1] = TextureVertex(min + glm::vec2(size.x, 0.0f), + texMin + texSize); + vertices[2] = TextureVertex(min + size, + texMin + glm::vec2(texSize.x, 0.0f)); + vertices[3] = TextureVertex(min + glm::vec2(0.0f, size.y), + texMin); + } + QuadBuilder(const Glyph& glyph, const glm::vec2& offset) : + QuadBuilder(offset + glm::vec2(glyph.offset.x, glyph.offset.y - glyph.size.y), glyph.size, + glyph.texOffset, glyph.texSize) {} + +}; + + + +static QHash LOADED_FONTS; + +Font* Font::load(QIODevice& fontFile) { + Font* result = new Font(); + result->read(fontFile); + return result; +} + +Font* Font::load(const QString& family) { + if (!LOADED_FONTS.contains(family)) { + + const QString SDFF_COURIER_PRIME_FILENAME = ":/CourierPrime.sdff"; + const QString SDFF_INCONSOLATA_MEDIUM_FILENAME = ":/InconsolataMedium.sdff"; + const QString SDFF_ROBOTO_FILENAME = ":/Roboto.sdff"; + const QString SDFF_TIMELESS_FILENAME = ":/Timeless.sdff"; + + QString loadFilename; + + if (family == MONO_FONT_FAMILY) { + loadFilename = SDFF_COURIER_PRIME_FILENAME; + } else if (family == INCONSOLATA_FONT_FAMILY) { + loadFilename = SDFF_INCONSOLATA_MEDIUM_FILENAME; + } else if (family == SANS_FONT_FAMILY) { + loadFilename = SDFF_ROBOTO_FILENAME; + } else { + if (!LOADED_FONTS.contains(SERIF_FONT_FAMILY)) { + loadFilename = SDFF_TIMELESS_FILENAME; + } else { + LOADED_FONTS[family] = LOADED_FONTS[SERIF_FONT_FAMILY]; + } + } + + if (!loadFilename.isEmpty()) { + QFile fontFile(loadFilename); + fontFile.open(QIODevice::ReadOnly); + + qCDebug(renderutils) << "Loaded font" << loadFilename << "from Qt Resource System."; + + LOADED_FONTS[family] = load(fontFile); + } + } + return LOADED_FONTS[family]; +} + +Font::Font() { + static bool fontResourceInitComplete = false; + if (!fontResourceInitComplete) { + Q_INIT_RESOURCE(fonts); + fontResourceInitComplete = true; + } +} + +// NERD RAGE: why doesn't QHash have a 'const T & operator[] const' member +const Glyph& Font::getGlyph(const QChar& c) const { + if (!_glyphs.contains(c)) { + return _glyphs[QChar('?')]; + } + return _glyphs[c]; +} + +QStringList Font::splitLines(const QString& str) const { + return str.split('\n'); +} + +QStringList Font::tokenizeForWrapping(const QString& str) const { + QStringList tokens; + for(auto line : splitLines(str)) { + if (!tokens.empty()) { + tokens << QString('\n'); + } + tokens << line.split(' '); + } + return tokens; +} + +glm::vec2 Font::computeTokenExtent(const QString& token) const { + glm::vec2 advance(0, _fontSize); + foreach(QChar c, token) { + Q_ASSERT(c != '\n'); + advance.x += (c == ' ') ? _spaceWidth : getGlyph(c).d; + } + return advance; +} + +glm::vec2 Font::computeExtent(const QString& str) const { + glm::vec2 extent = glm::vec2(0.0f, 0.0f); + + QStringList lines{ splitLines(str) }; + if (!lines.empty()) { + for(const auto& line : lines) { + glm::vec2 tokenExtent = computeTokenExtent(line); + extent.x = std::max(tokenExtent.x, extent.x); + } + extent.y = lines.count() * _fontSize; + } + return extent; +} + +void Font::read(QIODevice& in) { + uint8_t header[4]; + readStream(in, header); + if (memcmp(header, "SDFF", 4)) { + qFatal("Bad SDFF file"); + } + + uint16_t version; + readStream(in, version); + + // read font name + _family = ""; + if (version > 0x0001) { + char c; + readStream(in, c); + while (c) { + _family += c; + readStream(in, c); + } + } + + // read font data + readStream(in, _leading); + readStream(in, _ascent); + readStream(in, _descent); + readStream(in, _spaceWidth); + _fontSize = _ascent + _descent; + + // Read character count + uint16_t count; + readStream(in, count); + // read metrics data for each character + QVector glyphs(count); + // std::for_each instead of Qt foreach because we need non-const references + std::for_each(glyphs.begin(), glyphs.end(), [&](Glyph& g) { + g.read(in); + }); + + // read image data + QImage image; + if (!image.loadFromData(in.readAll(), "PNG")) { + qFatal("Failed to read SDFF image"); + } + + _glyphs.clear(); + glm::vec2 imageSize = toGlm(image.size()); + foreach(Glyph g, glyphs) { + // Adjust the pixel texture coordinates into UV coordinates, + g.texSize /= imageSize; + g.texOffset /= imageSize; + // store in the character to glyph hash + _glyphs[g.c] = g; + }; + + image = image.convertToFormat(QImage::Format_RGBA8888); + + gpu::Element formatGPU = gpu::Element(gpu::VEC3, gpu::UINT8, gpu::RGB); + gpu::Element formatMip = gpu::Element(gpu::VEC3, gpu::UINT8, gpu::RGB); + if (image.hasAlphaChannel()) { + formatGPU = gpu::Element(gpu::VEC4, gpu::UINT8, gpu::RGBA); + formatMip = gpu::Element(gpu::VEC4, gpu::UINT8, gpu::BGRA); + } + _texture = gpu::TexturePointer(gpu::Texture::create2D(formatGPU, image.width(), image.height(), + gpu::Sampler(gpu::Sampler::FILTER_MIN_POINT_MAG_LINEAR))); + _texture->assignStoredMip(0, formatMip, image.byteCount(), image.constBits()); + _texture->autoGenerateMips(-1); +} + +void Font::setupGPU() { + if (!_initialized) { + _initialized = true; + + // Setup render pipeline + { + auto vertexShader = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(sdf_text3D_vert))); + auto pixelShader = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(sdf_text3D_frag))); + gpu::ShaderPointer program = gpu::ShaderPointer(gpu::Shader::createProgram(vertexShader, pixelShader)); + + gpu::Shader::BindingSet slotBindings; + gpu::Shader::makeProgram(*program, slotBindings); + + _fontLoc = program->getTextures().findLocation("Font"); + _outlineLoc = program->getUniforms().findLocation("Outline"); + _colorLoc = program->getUniforms().findLocation("Color"); + + gpu::StatePointer state = gpu::StatePointer(new gpu::State()); + state->setCullMode(gpu::State::CULL_BACK); + state->setDepthTest(true, true, gpu::LESS_EQUAL); + state->setBlendFunction(true, + gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, + gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); + _pipeline = gpu::PipelinePointer(gpu::Pipeline::create(program, state)); + } + + { + auto vertexShader = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(sdf_text_vert))); + auto pixelShader = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(sdf_text_frag))); + gpu::ShaderPointer program = gpu::ShaderPointer(gpu::Shader::createProgram(vertexShader, pixelShader)); + + gpu::Shader::BindingSet slotBindings; + gpu::Shader::makeProgram(*program, slotBindings); + + _fontLoc2D = program->getTextures().findLocation("Font"); + _outlineLoc2D = program->getUniforms().findLocation("Outline"); + _colorLoc2D = program->getUniforms().findLocation("Color"); + + gpu::StatePointer state = gpu::StatePointer(new gpu::State()); + state->setCullMode(gpu::State::CULL_BACK); + state->setDepthTest(false); + state->setBlendFunction(false, + gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, + gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); + _pipeline2D = gpu::PipelinePointer(gpu::Pipeline::create(program, state)); + } + + // Sanity checks + static const int OFFSET = offsetof(TextureVertex, tex); + assert(OFFSET == sizeof(glm::vec2)); + assert(sizeof(glm::vec2) == 2 * sizeof(float)); + assert(sizeof(TextureVertex) == 2 * sizeof(glm::vec2)); + assert(sizeof(QuadBuilder) == 4 * sizeof(TextureVertex)); + + // Setup rendering structures + _format.reset(new gpu::Stream::Format()); + _format->setAttribute(gpu::Stream::POSITION, 0, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::XYZ), 0); + _format->setAttribute(gpu::Stream::TEXCOORD, 0, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV), OFFSET); + } +} + +void Font::rebuildVertices(float x, float y, const QString& str, const glm::vec2& bounds) { + _verticesBuffer.reset(new gpu::Buffer()); + _numVertices = 0; + _lastStringRendered = str; + _lastBounds = bounds; + + // Top left of text + glm::vec2 advance = glm::vec2(x, y); + foreach(const QString& token, tokenizeForWrapping(str)) { + bool isNewLine = (token == QString('\n')); + bool forceNewLine = false; + + // Handle wrapping + if (!isNewLine && (bounds.x != -1) && (advance.x + computeExtent(token).x > x + bounds.x)) { + // We are out of the x bound, force new line + forceNewLine = true; + } + if (isNewLine || forceNewLine) { + // Character return, move the advance to a new line + advance = glm::vec2(x, advance.y - _leading); + + if (isNewLine) { + // No need to draw anything, go directly to next token + continue; + } else if (computeExtent(token).x > bounds.x) { + // token will never fit, stop drawing + break; + } + } + if ((bounds.y != -1) && (advance.y - _fontSize < -y - bounds.y)) { + // We are out of the y bound, stop drawing + break; + } + + // Draw the token + if (!isNewLine) { + for (auto c : token) { + auto glyph = _glyphs[c]; + + QuadBuilder qd(glyph, advance - glm::vec2(0.0f, _ascent)); + _verticesBuffer->append(sizeof(QuadBuilder), (const gpu::Byte*)&qd); + _numVertices += 4; + + // Advance by glyph size + advance.x += glyph.d; + } + + // Add space after all non return tokens + advance.x += _spaceWidth; + } + } +} + +void Font::drawString(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4* color, + EffectType effectType, const glm::vec2& bounds) { + if (str == "") { + return; + } + + if (str != _lastStringRendered || bounds != _lastBounds) { + rebuildVertices(x, y, str, bounds); + } + + setupGPU(); + + batch.setPipeline(_pipeline); + batch.setResourceTexture(_fontLoc, _texture); + batch._glUniform1i(_outlineLoc, (effectType == OUTLINE_EFFECT)); + batch._glUniform4fv(_colorLoc, 1, (const GLfloat*)color); + + batch.setInputFormat(_format); + batch.setInputBuffer(0, _verticesBuffer, 0, _format->getChannels().at(0)._stride); + batch.draw(gpu::QUADS, _numVertices, 0); +} + +void Font::drawString2D(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4* color, + EffectType effectType, const glm::vec2& bounds) { + if (str == "") { + return; + } + + if (str != _lastStringRendered || bounds != _lastBounds) { + rebuildVertices(x, y, str, bounds); + } + + setupGPU(); + + batch.setPipeline(_pipeline2D); + batch.setResourceTexture(_fontLoc2D, _texture); + batch._glUniform1i(_outlineLoc2D, (effectType == OUTLINE_EFFECT)); + batch._glUniform4fv(_colorLoc2D, 1, (const GLfloat*)color); + batch.setInputFormat(_format); + batch.setInputBuffer(0, _verticesBuffer, 0, _format->getChannels().at(0)._stride); + batch.draw(gpu::QUADS, _numVertices, 0); +} diff --git a/libraries/render-utils/src/text/Font.h b/libraries/render-utils/src/text/Font.h new file mode 100644 index 0000000000..98d0a31cf4 --- /dev/null +++ b/libraries/render-utils/src/text/Font.h @@ -0,0 +1,88 @@ +// +// Created by Bradley Austin Davis on 2015/07/16 +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#pragma once +#ifndef hifi_Font_h +#define hifi_Font_h + +#include "Glyph.h" +#include "EffectType.h" +#include +#include + +class Font { +public: + Font(); + + void read(QIODevice& path); + + glm::vec2 computeExtent(const QString& str) const; + float getFontSize() const { return _fontSize; } + + // Render string to batch + void drawString(gpu::Batch& batch, float x, float y, const QString& str, + const glm::vec4* color, EffectType effectType, + const glm::vec2& bound); + + void drawString2D(gpu::Batch& batch, float x, float y, const QString& str, + const glm::vec4* color, EffectType effectType, + const glm::vec2& bound); + + static Font* load(QIODevice& fontFile); + static Font* load(const QString& family); + +private: + QStringList tokenizeForWrapping(const QString& str) const; + QStringList splitLines(const QString& str) const; + glm::vec2 computeTokenExtent(const QString& str) const; + + const Glyph& getGlyph(const QChar& c) const; + void rebuildVertices(float x, float y, const QString& str, const glm::vec2& bounds); + + void setupGPU(); + + // maps characters to cached glyph info + // HACK... the operator[] const for QHash returns a + // copy of the value, not a const value reference, so + // we declare the hash as mutable in order to avoid such + // copies + mutable QHash _glyphs; + + // Font characteristics + QString _family; + float _fontSize = 0.0f; + float _leading = 0.0f; + float _ascent = 0.0f; + float _descent = 0.0f; + float _spaceWidth = 0.0f; + + bool _initialized = false; + + // gpu structures + gpu::PipelinePointer _pipeline; + gpu::PipelinePointer _pipeline2D; + gpu::TexturePointer _texture; + gpu::Stream::FormatPointer _format; + gpu::BufferPointer _verticesBuffer; + gpu::BufferStreamPointer _stream; + unsigned int _numVertices = 0; + + int _fontLoc = -1; + int _outlineLoc = -1; + int _colorLoc = -1; + int _fontLoc2D = -1; + int _outlineLoc2D = -1; + int _colorLoc2D = -1; + + // last string render characteristics + QString _lastStringRendered; + glm::vec2 _lastBounds; +}; + +#endif + diff --git a/libraries/render-utils/src/text/FontFamilies.h b/libraries/render-utils/src/text/FontFamilies.h new file mode 100644 index 0000000000..3c4186f5c4 --- /dev/null +++ b/libraries/render-utils/src/text/FontFamilies.h @@ -0,0 +1,31 @@ +// +// Created by Bradley Austin Davis on 2015/07/16 +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#pragma once +#ifndef hifi_FontFamilies_h +#define hifi_FontFamilies_h + +// the standard sans serif font family +#define SANS_FONT_FAMILY "Helvetica" + +// the standard sans serif font family +#define SERIF_FONT_FAMILY "Timeless" + +// the standard mono font family +#define MONO_FONT_FAMILY "Courier" + +// the Inconsolata font family +#ifdef Q_OS_WIN +#define INCONSOLATA_FONT_FAMILY "Fixedsys" +#define INCONSOLATA_FONT_WEIGHT QFont::Normal +#else +#define INCONSOLATA_FONT_FAMILY "Inconsolata" +#define INCONSOLATA_FONT_WEIGHT QFont::Bold +#endif + +#endif diff --git a/libraries/render-utils/src/text/Glyph.cpp b/libraries/render-utils/src/text/Glyph.cpp new file mode 100644 index 0000000000..0354b1057c --- /dev/null +++ b/libraries/render-utils/src/text/Glyph.cpp @@ -0,0 +1,22 @@ +#include "Glyph.h" +#include + +// We adjust bounds because offset is the bottom left corner of the font but the top left corner of a QRect +QRectF Glyph::bounds() const { + return glmToRect(offset, size).translated(0.0f, -size.y); +} + +QRectF Glyph::textureBounds() const { + return glmToRect(texOffset, texSize); +} + +void Glyph::read(QIODevice& in) { + uint16_t charcode; + readStream(in, charcode); + c = charcode; + readStream(in, texOffset); + readStream(in, size); + readStream(in, offset); + readStream(in, d); + texSize = size; +} diff --git a/libraries/render-utils/src/text/Glyph.h b/libraries/render-utils/src/text/Glyph.h new file mode 100644 index 0000000000..3cb08cc7e2 --- /dev/null +++ b/libraries/render-utils/src/text/Glyph.h @@ -0,0 +1,38 @@ +// +// Created by Bradley Austin Davis on 2015/07/16 +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#pragma once +#ifndef hifi_Glyph_h +#define hifi_Glyph_h + +#include + +#include +#include +#include + +#include + +// stores the font metrics for a single character +struct Glyph { + QChar c; + vec2 texOffset; + vec2 texSize; + vec2 size; + vec2 offset; + float d; // xadvance - adjusts character positioning + size_t indexOffset; + + // We adjust bounds because offset is the bottom left corner of the font but the top left corner of a QRect + QRectF bounds() const; + QRectF textureBounds() const; + + void read(QIODevice& in); +}; + +#endif diff --git a/libraries/shared/src/StreamHelpers.h b/libraries/shared/src/StreamHelpers.h new file mode 100644 index 0000000000..f13c15e45f --- /dev/null +++ b/libraries/shared/src/StreamHelpers.h @@ -0,0 +1,32 @@ +// +// Created by Bradley Austin Davis 2015/07/16 +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#pragma once +#ifndef hifi_StreamHelpers_h +#define hifi_StreamHelpers_h + +#include +#include + +// Helper functions for reading binary data from an IO device +template +inline void readStream(QIODevice& in, T& t) { + in.read((char*) &t, sizeof(t)); +} + +template +inline void readStream(QIODevice& in, T (&t)[N]) { + in.read((char*) t, N); +} + +template +inline void fillBuffer(QBuffer& buffer, T (&t)[N]) { + buffer.setData((const char*) t, N); +} + +#endif