From 931b5d8481e1bf80032960297cae5a3a72408972 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Fri, 8 Mar 2024 00:45:06 -0600 Subject: [PATCH] Add visibility to icon draw items --- scwx-qt/gl/geo_texture2d.vert | 4 + scwx-qt/gl/map_color.vert | 4 + scwx-qt/gl/texture2d_array.vert | 26 ++- scwx-qt/gl/threshold.geom | 4 +- scwx-qt/source/scwx/qt/gl/draw/icons.cpp | 221 ++++++++++++++----- scwx-qt/source/scwx/qt/gl/draw/icons.hpp | 30 ++- scwx-qt/source/scwx/qt/map/overlay_layer.cpp | 61 +++-- 7 files changed, 248 insertions(+), 102 deletions(-) diff --git a/scwx-qt/gl/geo_texture2d.vert b/scwx-qt/gl/geo_texture2d.vert index 7977af95..a3adeed6 100644 --- a/scwx-qt/gl/geo_texture2d.vert +++ b/scwx-qt/gl/geo_texture2d.vert @@ -25,6 +25,7 @@ out VertexData vec3 texCoord; vec4 color; ivec2 timeRange; + bool displayed; } vsOut; smooth out vec3 texCoord; @@ -41,6 +42,9 @@ vec2 latLngToScreenCoordinate(in vec2 latLng) void main() { + // Always set displayed to true + vsOut.displayed = true; + // Pass the threshold and time range to the geometry shader vsOut.threshold = aThreshold; vsOut.timeRange = aTimeRange; diff --git a/scwx-qt/gl/map_color.vert b/scwx-qt/gl/map_color.vert index 4319310f..d9c207b4 100644 --- a/scwx-qt/gl/map_color.vert +++ b/scwx-qt/gl/map_color.vert @@ -16,12 +16,16 @@ out VertexData vec3 texCoord; vec4 color; ivec2 timeRange; + bool displayed; } vsOut; smooth out vec4 color; void main() { + // Always set displayed to true + vsOut.displayed = true; + // Pass the threshold and time range to the geometry shader vsOut.threshold = aThreshold; vsOut.timeRange = aTimeRange; diff --git a/scwx-qt/gl/texture2d_array.vert b/scwx-qt/gl/texture2d_array.vert index cd250c55..e42f31d4 100644 --- a/scwx-qt/gl/texture2d_array.vert +++ b/scwx-qt/gl/texture2d_array.vert @@ -7,20 +7,42 @@ layout (location = 1) in vec2 aXYOffset; layout (location = 2) in vec3 aTexCoord; layout (location = 3) in vec4 aModulate; layout (location = 4) in float aAngleDeg; +layout (location = 5) in int aDisplayed; uniform mat4 uMVPMatrix; +out VertexData +{ + int threshold; + vec3 texCoord; + vec4 color; + ivec2 timeRange; + bool displayed; +} vsOut; + smooth out vec3 texCoord; smooth out vec4 color; void main() { + // Always set threshold and time range to zero + vsOut.threshold = 0; + vsOut.timeRange = ivec2(0, 0); + + // Pass displayed to the geometry shader + vsOut.displayed = (aDisplayed != 0); + + // Pass the texture coordinate and color modulate to the geometry and + // fragment shaders + vsOut.texCoord = aTexCoord; + vsOut.color = aModulate; + texCoord = aTexCoord; + color = aModulate; + // Rotate clockwise float angle = aAngleDeg * DEG2RAD; mat2 rotate = mat2(cos(angle), -sin(angle), sin(angle), cos(angle)); gl_Position = uMVPMatrix * vec4(aVertex + rotate * aXYOffset, 0.0f, 1.0f); - texCoord = aTexCoord; - color = aModulate; } diff --git a/scwx-qt/gl/threshold.geom b/scwx-qt/gl/threshold.geom index dec09b01..53bde434 100644 --- a/scwx-qt/gl/threshold.geom +++ b/scwx-qt/gl/threshold.geom @@ -12,6 +12,7 @@ in VertexData vec3 texCoord; vec4 color; ivec2 timeRange; + bool displayed; } gsIn[]; smooth out vec3 texCoord; @@ -19,7 +20,8 @@ smooth out vec4 color; void main() { - if ((gsIn[0].threshold <= 0 || // If Threshold: 0 was specified, no threshold + if (gsIn[0].displayed && + (gsIn[0].threshold <= 0 || // If Threshold: 0 was specified, no threshold gsIn[0].threshold >= uMapDistance || // If Threshold is above current map distance gsIn[0].threshold >= 999) && // If Threshold: 999 was specified (or greater), no threshold (gsIn[0].timeRange[0] == 0 || // If there is no start time specified diff --git a/scwx-qt/source/scwx/qt/gl/draw/icons.cpp b/scwx-qt/source/scwx/qt/gl/draw/icons.cpp index d2c3d709..39dbd9ad 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/icons.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/icons.cpp @@ -7,6 +7,7 @@ #include #include +#include namespace scwx { @@ -24,7 +25,7 @@ static constexpr std::size_t kNumRectangles = 1; static constexpr std::size_t kNumTriangles = kNumRectangles * 2; static constexpr std::size_t kVerticesPerTriangle = 3; static constexpr std::size_t kVerticesPerRectangle = kVerticesPerTriangle * 2; -static constexpr std::size_t kPointsPerVertex = 9; +static constexpr std::size_t kPointsPerVertex = 10; static constexpr std::size_t kPointsPerTexCoord = 3; static constexpr std::size_t kIconBufferLength = kNumTriangles * kVerticesPerTriangle * kPointsPerVertex; @@ -34,12 +35,15 @@ static constexpr std::size_t kTextureBufferLength = struct IconDrawItem : types::EventHandler { boost::gil::rgba32f_pixel_t modulate_ {1.0f, 1.0f, 1.0f, 1.0f}; + bool visible_ {true}; double x_ {}; double y_ {}; units::degrees angle_ {}; std::string iconSheet_ {}; std::size_t iconIndex_ {}; std::string hoverText_ {}; + + std::shared_ptr iconInfo_ {}; }; class Icons::Impl @@ -67,9 +71,14 @@ public: ~Impl() {} - void UpdateBuffers(); - void UpdateTextureBuffer(); - void Update(bool textureAtlasChanged); + void UpdateBuffers(); + static void UpdateSingleBuffer(const std::shared_ptr& di, + std::size_t iconIndex, + std::vector& iconBuffer, + std::vector& hoverIcons); + void UpdateTextureBuffer(); + void UpdateModifiedIconBuffers(); + void Update(bool textureAtlasChanged); std::shared_ptr context_; @@ -77,6 +86,8 @@ public: bool dirty_ {false}; bool lastTextureAtlasChanged_ {false}; + boost::unordered_flat_set> dirtyIcons_ {}; + std::mutex iconMutex_; boost::unordered_flat_map> @@ -120,6 +131,7 @@ void Icons::Initialize() p->shaderProgram_ = p->context_->GetShaderProgram( {{GL_VERTEX_SHADER, ":/gl/texture2d_array.vert"}, + {GL_GEOMETRY_SHADER, ":/gl/threshold.geom"}, {GL_FRAGMENT_SHADER, ":/gl/texture2d_array.frag"}}); p->uMVPMatrixLocation_ = p->shaderProgram_->GetUniformLocation("uMVPMatrix"); @@ -167,6 +179,15 @@ void Icons::Initialize() reinterpret_cast(8 * sizeof(float))); gl.glEnableVertexAttribArray(4); + // aAngle + gl.glVertexAttribPointer(5, + 1, + GL_FLOAT, + GL_FALSE, + kPointsPerVertex * sizeof(float), + reinterpret_cast(9 * sizeof(float))); + gl.glEnableVertexAttribArray(5); + gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[1]); gl.glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); @@ -292,12 +313,20 @@ std::shared_ptr Icons::AddIcon() return p->newIconList_.emplace_back(std::make_shared()); } +void Icons::SetIconVisible(const std::shared_ptr& di, + bool visible) +{ + di->visible_ = visible; + p->dirtyIcons_.insert(di); +} + void Icons::SetIconTexture(const std::shared_ptr& di, const std::string& iconSheet, std::size_t iconIndex) { di->iconSheet_ = iconSheet; di->iconIndex_ = iconIndex; + p->dirtyIcons_.insert(di); } void Icons::SetIconLocation(const std::shared_ptr& di, @@ -306,12 +335,14 @@ void Icons::SetIconLocation(const std::shared_ptr& di, { di->x_ = x; di->y_ = y; + p->dirtyIcons_.insert(di); } void Icons::SetIconAngle(const std::shared_ptr& di, units::angle::degrees angle) { di->angle_ = angle; + p->dirtyIcons_.insert(di); } void Icons::SetIconModulate(const std::shared_ptr& di, @@ -321,18 +352,21 @@ void Icons::SetIconModulate(const std::shared_ptr& di, modulate[1] / 255.0f, modulate[2] / 255.0f, modulate[3] / 255.0f}; + p->dirtyIcons_.insert(di); } void Icons::SetIconModulate(const std::shared_ptr& di, boost::gil::rgba32f_pixel_t modulate) { di->modulate_ = modulate; + p->dirtyIcons_.insert(di); } void Icons::SetIconHoverText(const std::shared_ptr& di, const std::string& text) { di->hoverText_ = text; + p->dirtyIcons_.insert(di); } void Icons::FinishIcons() @@ -374,7 +408,8 @@ void Icons::Impl::UpdateBuffers() continue; } - auto& icon = it->second; + auto& icon = it->second; + di->iconInfo_ = icon; // Validate icon if (di->iconIndex_ >= icon->numIcons_) @@ -387,61 +422,115 @@ void Icons::Impl::UpdateBuffers() // Icon is valid, add to valid icon list newValidIconList_.push_back(di); - // Base X/Y offsets in pixels - const float x = static_cast(di->x_); - const float y = static_cast(di->y_); + // Update icon buffer + UpdateSingleBuffer( + di, newValidIconList_.size() - 1, newIconBuffer_, newHoverIcons_); + } - // Icon size - const float iw = static_cast(icon->iconWidth_); - const float ih = static_cast(icon->iconHeight_); + // All icons have been updated + dirtyIcons_.clear(); +} - // Hot X/Y (zero-based icon center) - const float hx = static_cast(icon->hotX_); - const float hy = static_cast(icon->hotY_); +void Icons::Impl::UpdateSingleBuffer(const std::shared_ptr& di, + std::size_t iconIndex, + std::vector& iconBuffer, + std::vector& hoverIcons) +{ + auto& icon = di->iconInfo_; - // Final X/Y offsets in pixels - const float lx = std::roundf(-hx); - const float rx = std::roundf(lx + iw); - const float ty = std::roundf(+hy); - const float by = std::roundf(ty - ih); + // Base X/Y offsets in pixels + const float x = static_cast(di->x_); + const float y = static_cast(di->y_); - // Angle in degrees - units::angle::degrees angle = di->angle_; - const float a = angle.value(); + // Icon size + const float iw = static_cast(icon->iconWidth_); + const float ih = static_cast(icon->iconHeight_); - // Modulate color - const float mc0 = di->modulate_[0]; - const float mc1 = di->modulate_[1]; - const float mc2 = di->modulate_[2]; - const float mc3 = di->modulate_[3]; + // Hot X/Y (zero-based icon center) + const float hx = static_cast(icon->hotX_); + const float hy = static_cast(icon->hotY_); - newIconBuffer_.insert(newIconBuffer_.end(), - { - // Icon - x, y, lx, by, mc0, mc1, mc2, mc3, a, // BL - x, y, lx, ty, mc0, mc1, mc2, mc3, a, // TL - x, y, rx, by, mc0, mc1, mc2, mc3, a, // BR - x, y, rx, by, mc0, mc1, mc2, mc3, a, // BR - x, y, rx, ty, mc0, mc1, mc2, mc3, a, // TR - x, y, lx, ty, mc0, mc1, mc2, mc3, a // TL - }); + // Final X/Y offsets in pixels + const float lx = std::roundf(-hx); + const float rx = std::roundf(lx + iw); + const float ty = std::roundf(+hy); + const float by = std::roundf(ty - ih); - if (!di->hoverText_.empty() || di->event_ != nullptr) + // Angle in degrees + units::angle::degrees angle = di->angle_; + const float a = angle.value(); + + // Modulate color + const float mc0 = di->modulate_[0]; + const float mc1 = di->modulate_[1]; + const float mc2 = di->modulate_[2]; + const float mc3 = di->modulate_[3]; + + // Visibility + const float v = static_cast(di->visible_); + + // Icon initializer list data + const auto iconData = { + // Icon + x, y, lx, by, mc0, mc1, mc2, mc3, a, v, // BL + x, y, lx, ty, mc0, mc1, mc2, mc3, a, v, // TL + x, y, rx, by, mc0, mc1, mc2, mc3, a, v, // BR + x, y, rx, by, mc0, mc1, mc2, mc3, a, v, // BR + x, y, rx, ty, mc0, mc1, mc2, mc3, a, v, // TR + x, y, lx, ty, mc0, mc1, mc2, mc3, a, v // TL + }; + + // Buffer position data + auto iconBufferPosition = iconBuffer.end(); + auto iconBufferOffset = iconIndex * kIconBufferLength; + + if (iconBufferOffset < iconBuffer.size()) + { + iconBufferPosition = iconBuffer.begin() + iconBufferOffset; + } + + if (iconBufferPosition == iconBuffer.cend()) + { + iconBuffer.insert(iconBufferPosition, iconData); + } + else + { + std::copy(iconData.begin(), iconData.end(), iconBufferPosition); + } + + auto hoverIt = std::find_if(hoverIcons.begin(), + hoverIcons.end(), + [&di](auto& entry) { return entry.di_ == di; }); + + if (di->visible_ && (!di->hoverText_.empty() || di->event_ != nullptr)) + { + const units::angle::radians radians = angle; + + const float cosAngle = cosf(static_cast(radians.value())); + const float sinAngle = sinf(static_cast(radians.value())); + + const glm::mat2 rotate {cosAngle, -sinAngle, sinAngle, cosAngle}; + + const glm::vec2 otl = rotate * glm::vec2 {lx, ty}; + const glm::vec2 otr = rotate * glm::vec2 {rx, ty}; + const glm::vec2 obl = rotate * glm::vec2 {lx, by}; + const glm::vec2 obr = rotate * glm::vec2 {rx, by}; + + if (hoverIt == hoverIcons.end()) { - const units::angle::radians radians = angle; - - const float cosAngle = cosf(static_cast(radians.value())); - const float sinAngle = sinf(static_cast(radians.value())); - - const glm::mat2 rotate {cosAngle, -sinAngle, sinAngle, cosAngle}; - - const glm::vec2 otl = rotate * glm::vec2 {lx, ty}; - const glm::vec2 otr = rotate * glm::vec2 {rx, ty}; - const glm::vec2 obl = rotate * glm::vec2 {lx, by}; - const glm::vec2 obr = rotate * glm::vec2 {rx, by}; - - newHoverIcons_.emplace_back(IconHoverEntry {di, otl, otr, obl, obr}); + hoverIcons.emplace_back(IconHoverEntry {di, otl, otr, obl, obr}); } + else + { + hoverIt->otl_ = otl; + hoverIt->otr_ = otr; + hoverIt->obl_ = obl; + hoverIt->obr_ = obr; + } + } + else if (hoverIt != hoverIcons.end()) + { + hoverIcons.erase(hoverIt); } } @@ -533,10 +622,40 @@ void Icons::Impl::UpdateTextureBuffer() } } +void Icons::Impl::UpdateModifiedIconBuffers() +{ + // Update buffers for modified icons + for (auto& di : dirtyIcons_) + { + // Find modified icon in the current list + auto it = + std::find(currentIconList_.cbegin(), currentIconList_.cend(), di); + + // Ignore invalid icons + if (it == currentIconList_.cend()) + { + continue; + } + + auto iconIndex = std::distance(currentIconList_.cbegin(), it); + + UpdateSingleBuffer(di, iconIndex, currentIconBuffer_, currentHoverIcons_); + } + + // Clear list of modified icons + if (!dirtyIcons_.empty()) + { + dirtyIcons_.clear(); + dirty_ = true; + } +} + void Icons::Impl::Update(bool textureAtlasChanged) { gl::OpenGLFunctions& gl = context_->gl(); + UpdateModifiedIconBuffers(); + // If the texture atlas has changed if (dirty_ || textureAtlasChanged || lastTextureAtlasChanged_) { diff --git a/scwx-qt/source/scwx/qt/gl/draw/icons.hpp b/scwx-qt/source/scwx/qt/gl/draw/icons.hpp index f9e4c91c..3041a962 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/icons.hpp +++ b/scwx-qt/source/scwx/qt/gl/draw/icons.hpp @@ -96,6 +96,12 @@ public: */ std::shared_ptr AddIcon(); + /** + * @param [in] di Icon draw item + * @param [in] visible Visibility of the icon + */ + void SetIconVisible(const std::shared_ptr& di, bool visible); + /** * Sets the texture of an icon. * @@ -103,9 +109,9 @@ public: * @param [in] iconSheet The name of the icon sheet in the texture atlas * @param [in] iconIndex The zero-based index of the icon in the icon sheet */ - static void SetIconTexture(const std::shared_ptr& di, - const std::string& iconSheet, - std::size_t iconIndex); + void SetIconTexture(const std::shared_ptr& di, + const std::string& iconSheet, + std::size_t iconIndex); /** * Sets the location of an icon. @@ -114,7 +120,7 @@ public: * @param [in] x The x location of the icon in pixels. * @param [in] y The y location of the icon in pixels. */ - static void + void SetIconLocation(const std::shared_ptr& di, double x, double y); /** @@ -123,8 +129,8 @@ public: * @param [in] di Icon draw item * @param [in] angle Angle in degrees */ - static void SetIconAngle(const std::shared_ptr& di, - units::angle::degrees angle); + void SetIconAngle(const std::shared_ptr& di, + units::angle::degrees angle); /** * Sets the modulate color of an icon. @@ -132,8 +138,8 @@ public: * @param [in] di Icon draw item * @param [in] modulate Modulate color */ - static void SetIconModulate(const std::shared_ptr& di, - boost::gil::rgba8_pixel_t modulate); + void SetIconModulate(const std::shared_ptr& di, + boost::gil::rgba8_pixel_t modulate); /** * Sets the modulate color of an icon. @@ -141,8 +147,8 @@ public: * @param [in] di Icon draw item * @param [in] modulate Modulate color */ - static void SetIconModulate(const std::shared_ptr& di, - boost::gil::rgba32f_pixel_t modulate); + void SetIconModulate(const std::shared_ptr& di, + boost::gil::rgba32f_pixel_t modulate); /** * Sets the hover text of an icon. @@ -150,8 +156,8 @@ public: * @param [in] di Icon draw item * @param [in] text Hover text */ - static void SetIconHoverText(const std::shared_ptr& di, - const std::string& text); + void SetIconHoverText(const std::shared_ptr& di, + const std::string& text); /** * Finalizes the draw item after adding new icons. diff --git a/scwx-qt/source/scwx/qt/map/overlay_layer.cpp b/scwx-qt/source/scwx/qt/map/overlay_layer.cpp index 52e1418c..d00fb717 100644 --- a/scwx-qt/source/scwx/qt/map/overlay_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/overlay_layer.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -74,7 +75,6 @@ public: types::GetTextureName(types::ImageTexture::MapTilerLogo)}; std::shared_ptr compassIcon_ {}; - bool compassIconDirty_ {false}; double lastBearing_ {0.0}; std::shared_ptr mapLogoIcon_ {}; @@ -147,8 +147,7 @@ void OverlayLayer::Initialize() p->icons_->StartIcons(); p->compassIcon_ = p->icons_->AddIcon(); - gl::draw::Icons::SetIconTexture( - p->compassIcon_, p->cardinalPointIconName_, 0); + p->icons_->SetIconTexture(p->compassIcon_, p->cardinalPointIconName_, 0); gl::draw::Icons::RegisterEventHandler( p->compassIcon_, [this](QEvent* ev) @@ -157,18 +156,16 @@ void OverlayLayer::Initialize() { case QEvent::Type::Enter: // Highlight icon on mouse enter - gl::draw::Icons::SetIconModulate( + p->icons_->SetIconModulate( p->compassIcon_, boost::gil::rgba32f_pixel_t {1.5f, 1.5f, 1.5f, 1.0f}); - p->compassIconDirty_ = true; break; case QEvent::Type::Leave: // Restore icon on mouse leave - gl::draw::Icons::SetIconModulate( + p->icons_->SetIconModulate( p->compassIcon_, boost::gil::rgba32f_pixel_t {1.0f, 1.0f, 1.0f, 1.0f}); - p->compassIconDirty_ = true; break; case QEvent::Type::MouseButtonPress: @@ -196,13 +193,11 @@ void OverlayLayer::Initialize() p->mapLogoIcon_ = p->icons_->AddIcon(); if (context()->map_provider() == MapProvider::Mapbox) { - gl::draw::Icons::SetIconTexture( - p->mapLogoIcon_, p->mapboxLogoImageName_, 0); + p->icons_->SetIconTexture(p->mapLogoIcon_, p->mapboxLogoImageName_, 0); } else if (context()->map_provider() == MapProvider::MapTiler) { - gl::draw::Icons::SetIconTexture( - p->mapLogoIcon_, p->mapTilerLogoImageName_, 0); + p->icons_->SetIconTexture(p->mapLogoIcon_, p->mapTilerLogoImageName_, 0); } p->icons_->FinishIcons(); @@ -283,42 +278,29 @@ void OverlayLayer::Render(const QMapLibre::CustomLayerRenderParameters& params) ImGui::GetFontSize() != p->lastFontSize_) { // Set the compass icon in the upper right, below the sweep time window - gl::draw::Icons::SetIconLocation(p->compassIcon_, - params.width - 24, - params.height - - (ImGui::GetFontSize() + 32)); - p->compassIconDirty_ = true; + p->icons_->SetIconLocation(p->compassIcon_, + params.width - 24, + params.height - (ImGui::GetFontSize() + 32)); } if (params.bearing != p->lastBearing_) { if (params.bearing == 0.0) { // Use cardinal point icon when bearing is oriented north-up - gl::draw::Icons::SetIconTexture( + p->icons_->SetIconTexture( p->compassIcon_, p->cardinalPointIconName_, 0); - gl::draw::Icons::SetIconAngle(p->compassIcon_, - units::angle::degrees {0.0}); + p->icons_->SetIconAngle(p->compassIcon_, + units::angle::degrees {0.0}); } else { // Use rotated compass icon when bearing is rotated away from north-up - gl::draw::Icons::SetIconTexture( - p->compassIcon_, p->compassIconName_, 0); - gl::draw::Icons::SetIconAngle( + p->icons_->SetIconTexture(p->compassIcon_, p->compassIconName_, 0); + p->icons_->SetIconAngle( p->compassIcon_, units::angle::degrees {-45 - params.bearing}); } - - // Mark icon for re-drawing - p->compassIconDirty_ = true; } - if (p->compassIconDirty_) - { - // Update icon render buffers - p->icons_->FinishIcons(); - } - - DrawLayer::Render(params); if (radarProductView != nullptr) { @@ -381,18 +363,25 @@ void OverlayLayer::Render(const QMapLibre::CustomLayerRenderParameters& params) ImGui::End(); } + auto& generalSettings = settings::GeneralSettings::Instance(); + QMargins colorTableMargins = context()->color_table_margins(); if (colorTableMargins != p->lastColorTableMargins_ || p->firstRender_) { // Draw map logo with a 10x10 indent from the bottom left - gl::draw::Icons::SetIconLocation(p->mapLogoIcon_, - 10 + colorTableMargins.left(), - 10 + colorTableMargins.bottom()); + p->icons_->SetIconLocation(p->mapLogoIcon_, + 10 + colorTableMargins.left(), + 10 + colorTableMargins.bottom()); p->icons_->FinishIcons(); } + p->icons_->SetIconVisible(p->mapLogoIcon_, + generalSettings.show_map_logo().GetValue()); + + DrawLayer::Render(params); auto mapCopyrights = context()->map_copyrights(); - if (mapCopyrights.length() > 0) + if (mapCopyrights.length() > 0 && + generalSettings.show_map_attribution().GetValue()) { auto attributionFont = manager::FontManager::Instance().GetImGuiFont( types::FontCategory::Attribution);