summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris Loer <chris.loer@gmail.com>2017-03-10 09:02:07 -0800
committerChris Loer <chris.loer@gmail.com>2017-03-22 08:50:45 -0700
commite90966a9373d7aa7908b4a6fe39fa6cf5230cbc0 (patch)
treef75298824f64a157f6a6d69433bc653822c0a8e8
parent3c91b6b7de487993e75de552ba44249740644e42 (diff)
downloadqtlocation-mapboxgl-upstream/cloer_glyph_sanity_check.tar.gz
[core] De-mutex GlyphAtlas.upstream/cloer_glyph_sanity_check
- Expose glyph information to workers via message interface. - Symbol preparation for a tile now depends on all glyphs becoming available before it can start.
-rw-r--r--cmake/core-files.cmake1
-rw-r--r--src/mbgl/layout/symbol_layout.cpp90
-rw-r--r--src/mbgl/layout/symbol_layout.hpp9
-rw-r--r--src/mbgl/style/layers/symbol_layer_impl.cpp6
-rw-r--r--src/mbgl/style/layers/symbol_layer_impl.hpp3
-rw-r--r--src/mbgl/text/glyph.cpp2
-rw-r--r--src/mbgl/text/glyph.hpp19
-rw-r--r--src/mbgl/text/glyph_atlas.cpp243
-rw-r--r--src/mbgl/text/glyph_atlas.hpp68
-rw-r--r--src/mbgl/text/glyph_pbf.cpp3
-rw-r--r--src/mbgl/text/glyph_pbf.hpp6
-rw-r--r--src/mbgl/text/glyph_set.cpp275
-rw-r--r--src/mbgl/text/glyph_set.hpp31
-rw-r--r--src/mbgl/text/shaping.cpp280
-rw-r--r--src/mbgl/text/shaping.hpp14
-rw-r--r--src/mbgl/tile/geometry_tile.cpp13
-rw-r--r--src/mbgl/tile/geometry_tile.hpp9
-rw-r--r--src/mbgl/tile/geometry_tile_worker.cpp22
-rw-r--r--src/mbgl/tile/geometry_tile_worker.hpp6
-rw-r--r--src/mbgl/util/exclusive.hpp27
-rw-r--r--test/text/glyph_atlas.test.cpp44
-rw-r--r--test/text/glyph_pbf.test.cpp2
22 files changed, 631 insertions, 542 deletions
diff --git a/cmake/core-files.cmake b/cmake/core-files.cmake
index 45a3ed25b3..519c52a7fb 100644
--- a/cmake/core-files.cmake
+++ b/cmake/core-files.cmake
@@ -480,7 +480,6 @@ set(MBGL_CORE_FILES
src/mbgl/util/dtoa.cpp
src/mbgl/util/dtoa.hpp
src/mbgl/util/event.cpp
- src/mbgl/util/exclusive.hpp
src/mbgl/util/font_stack.cpp
src/mbgl/util/geo.cpp
src/mbgl/util/geojson.cpp
diff --git a/src/mbgl/layout/symbol_layout.cpp b/src/mbgl/layout/symbol_layout.cpp
index 3a2c082ad8..513a5e071e 100644
--- a/src/mbgl/layout/symbol_layout.cpp
+++ b/src/mbgl/layout/symbol_layout.cpp
@@ -7,10 +7,9 @@
#include <mbgl/style/layers/symbol_layer.hpp>
#include <mbgl/style/layers/symbol_layer_impl.hpp>
#include <mbgl/sprite/sprite_atlas.hpp>
-#include <mbgl/text/glyph_atlas.hpp>
#include <mbgl/text/get_anchors.hpp>
-#include <mbgl/text/glyph_atlas.hpp>
#include <mbgl/text/collision_tile.hpp>
+#include <mbgl/text/shaping.hpp>
#include <mbgl/util/constants.hpp>
#include <mbgl/util/utf.hpp>
#include <mbgl/util/token.hpp>
@@ -33,7 +32,8 @@ using namespace style;
SymbolLayout::SymbolLayout(const BucketParameters& parameters,
const std::vector<const Layer*>& layers,
const GeometryTileLayer& sourceLayer,
- SpriteAtlas& spriteAtlas_)
+ SpriteAtlas& spriteAtlas_,
+ GlyphDependencies& glyphDependencies)
: sourceLayerName(sourceLayer.getName()),
bucketName(layers.at(0)->getID()),
overscaling(parameters.tileID.overscaleFactor()),
@@ -93,7 +93,7 @@ SymbolLayout::SymbolLayout(const BucketParameters& parameters,
));
}
- // Determine and load glyph ranges
+ // Determine glyph dependencies
const size_t featureCount = sourceLayer.featureCount();
for (size_t i = 0; i < featureCount; ++i) {
auto feature = sourceLayer.getFeature(i);
@@ -139,9 +139,9 @@ SymbolLayout::SymbolLayout(const BucketParameters& parameters,
// Loop through all characters of this text and collect unique codepoints.
for (char16_t chr : *ft.text) {
- ranges.insert(getGlyphRange(chr));
+ glyphDependencies[layout.get<TextFont>()].insert(chr);
if (char16_t verticalChr = util::i18n::verticalizePunctuation(chr)) {
- ranges.insert(getGlyphRange(verticalChr));
+ glyphDependencies[layout.get<TextFont>()].insert(verticalChr);
}
}
}
@@ -164,13 +164,13 @@ bool SymbolLayout::hasSymbolInstances() const {
return !symbolInstances.empty();
}
-bool SymbolLayout::canPrepare(GlyphAtlas& glyphAtlas) {
- const bool hasTextField = layout.get<TextField>().match(
- [&] (const std::string& s) { return !s.empty(); },
- [&] (const auto&) { return true; }
- );
-
- if (hasTextField && !layout.get<TextFont>().empty() && !glyphAtlas.hasGlyphRanges(layout.get<TextFont>(), ranges)) {
+bool SymbolLayout::canPrepare(const GlyphPositionMap& glyphPositions) {
+ // TODO: This is a needlessly complex way to check if we can move to the next step, we really just want to wait until
+ // we've gotten a reply from 'getGlyphs'. I'm just keeping this in place here to reduce the number of moving parts in the refactor
+ const bool hasTextField = layout.get<TextField>().match([&] (const std::string& s) { return !s.empty(); },
+ [&] (const auto&) { return true; } );
+
+ if (hasTextField && glyphPositions.empty()) {
return false;
}
@@ -181,8 +181,7 @@ bool SymbolLayout::canPrepare(GlyphAtlas& glyphAtlas) {
return true;
}
-void SymbolLayout::prepare(uintptr_t tileUID,
- GlyphAtlas& glyphAtlas) {
+void SymbolLayout::prepare(const GlyphPositionMap& glyphs) {
float horizontalAlign = 0.5;
float verticalAlign = 0.5;
@@ -224,7 +223,6 @@ void SymbolLayout::prepare(uintptr_t tileUID,
layout.get<TextJustify>() == TextJustifyType::Left ? 0 :
0.5;
- auto glyphSet = glyphAtlas.getGlyphSet(layout.get<TextFont>());
const bool textAlongLine = layout.get<TextRotationAlignment>() == AlignmentType::Map &&
layout.get<SymbolPlacement>() == SymbolPlacementType::Line;
@@ -239,34 +237,33 @@ void SymbolLayout::prepare(uintptr_t tileUID,
// if feature has text, shape the text
if (feature.text) {
- auto getShaping = [&] (const std::u16string& text, WritingModeType writingMode) {
- const float oneEm = 24.0f;
- const Shaping result = glyphSet->getShaping(
- /* string */ text,
- /* maxWidth: ems */ layout.get<SymbolPlacement>() != SymbolPlacementType::Line ?
- layout.get<TextMaxWidth>() * oneEm : 0,
- /* lineHeight: ems */ layout.get<TextLineHeight>() * oneEm,
- /* horizontalAlign */ horizontalAlign,
- /* verticalAlign */ verticalAlign,
- /* justify */ justify,
- /* spacing: ems */ layout.get<TextLetterSpacing>() * oneEm,
- /* translate */ Point<float>(layout.get<TextOffset>()[0], layout.get<TextOffset>()[1]),
- /* verticalHeight */ oneEm,
- /* writingMode */ writingMode,
- /* bidirectional algorithm object */ bidi);
-
- // Add the glyphs we need for this label to the glyph atlas.
- if (result) {
- glyphAtlas.addGlyphs(tileUID, text, layout.get<TextFont>(), glyphSet, face);
+ auto glyphPositions = glyphs.find(layout.get<TextFont>());
+ if (glyphPositions != glyphs.end()) { // If there are no glyphs available for this feature, skip shaping
+ auto applyShaping = [&] (const std::u16string& text, WritingModeType writingMode) {
+ const float oneEm = 24.0f;
+ const Shaping result = getShaping(
+ /* string */ text,
+ /* maxWidth: ems */ layout.get<SymbolPlacement>() != SymbolPlacementType::Line ?
+ layout.get<TextMaxWidth>() * oneEm : 0,
+ /* lineHeight: ems */ layout.get<TextLineHeight>() * oneEm,
+ /* horizontalAlign */ horizontalAlign,
+ /* verticalAlign */ verticalAlign,
+ /* justify */ justify,
+ /* spacing: ems */ layout.get<TextLetterSpacing>() * oneEm,
+ /* translate */ Point<float>(layout.get<TextOffset>()[0], layout.get<TextOffset>()[1]),
+ /* verticalHeight */ oneEm,
+ /* writingMode */ writingMode,
+ /* bidirectional algorithm object */ bidi,
+ /* glyphs */ glyphPositions->second);
+
+ return result;
+ };
+
+ shapedTextOrientations.first = applyShaping(*feature.text, WritingModeType::Horizontal);
+
+ if (util::i18n::allowsVerticalWritingMode(*feature.text) && textAlongLine) {
+ shapedTextOrientations.second = applyShaping(util::i18n::verticalizePunctuation(*feature.text), WritingModeType::Vertical);
}
-
- return result;
- };
-
- shapedTextOrientations.first = getShaping(*feature.text, WritingModeType::Horizontal);
-
- if (util::i18n::allowsVerticalWritingMode(*feature.text) && textAlongLine) {
- shapedTextOrientations.second = getShaping(util::i18n::verticalizePunctuation(*feature.text), WritingModeType::Vertical);
}
}
@@ -291,7 +288,8 @@ void SymbolLayout::prepare(uintptr_t tileUID,
// if either shapedText or icon position is present, add the feature
if (shapedTextOrientations.first || shapedIcon) {
- addFeature(std::distance(features.begin(), it), feature, shapedTextOrientations, shapedIcon, face);
+ auto glyphPositionsIt = glyphs.find(layout.get<TextFont>());
+ addFeature(std::distance(features.begin(), it), feature, shapedTextOrientations, shapedIcon, glyphPositionsIt == glyphs.end() ? GlyphPositions() : glyphPositionsIt->second);
}
feature.geometry.clear();
@@ -304,7 +302,7 @@ void SymbolLayout::addFeature(const std::size_t index,
const SymbolFeature& feature,
const std::pair<Shaping, Shaping>& shapedTextOrientations,
const PositionedIcon& shapedIcon,
- const GlyphPositions& face) {
+ const GlyphPositions& glyphs) {
const float minScale = 0.5f;
const float glyphSize = 24.0f;
@@ -350,7 +348,7 @@ void SymbolLayout::addFeature(const std::size_t index,
symbolInstances.emplace_back(anchor, line, shapedTextOrientations, shapedIcon, layout, addToBuffers, symbolInstances.size(),
textBoxScale, textPadding, textPlacement,
iconBoxScale, iconPadding, iconPlacement,
- face, indexedFeature, index);
+ glyphs, indexedFeature, index);
};
const auto& type = feature.getType();
diff --git a/src/mbgl/layout/symbol_layout.hpp b/src/mbgl/layout/symbol_layout.hpp
index 5b14090d97..fe5be1e4a9 100644
--- a/src/mbgl/layout/symbol_layout.hpp
+++ b/src/mbgl/layout/symbol_layout.hpp
@@ -32,12 +32,12 @@ public:
SymbolLayout(const style::BucketParameters&,
const std::vector<const style::Layer*>&,
const GeometryTileLayer&,
- SpriteAtlas&);
+ SpriteAtlas&,
+ GlyphDependencies&);
- bool canPrepare(GlyphAtlas&);
+ bool canPrepare(const GlyphPositionMap& glyphs);
- void prepare(uintptr_t tileUID,
- GlyphAtlas&);
+ void prepare(const GlyphPositionMap& glyphs);
std::unique_ptr<SymbolBucket> place(CollisionTile&);
@@ -89,7 +89,6 @@ private:
bool sdfIcons = false;
bool iconsNeedLinear = false;
- GlyphRangeSet ranges;
std::vector<SymbolInstance> symbolInstances;
std::vector<SymbolFeature> features;
diff --git a/src/mbgl/style/layers/symbol_layer_impl.cpp b/src/mbgl/style/layers/symbol_layer_impl.cpp
index ff59b14d65..47d802a118 100644
--- a/src/mbgl/style/layers/symbol_layer_impl.cpp
+++ b/src/mbgl/style/layers/symbol_layer_impl.cpp
@@ -36,11 +36,13 @@ std::unique_ptr<Bucket> SymbolLayer::Impl::createBucket(const BucketParameters&,
std::unique_ptr<SymbolLayout> SymbolLayer::Impl::createLayout(const BucketParameters& parameters,
const std::vector<const Layer*>& group,
- const GeometryTileLayer& layer) const {
+ const GeometryTileLayer& layer,
+ GlyphDependencies& glyphDependencies) const {
return std::make_unique<SymbolLayout>(parameters,
group,
layer,
- *spriteAtlas);
+ *spriteAtlas,
+ glyphDependencies);
}
IconPaintProperties::Evaluated SymbolLayer::Impl::iconPaintProperties() const {
diff --git a/src/mbgl/style/layers/symbol_layer_impl.hpp b/src/mbgl/style/layers/symbol_layer_impl.hpp
index 1e9f05e4c7..16bd67a4d4 100644
--- a/src/mbgl/style/layers/symbol_layer_impl.hpp
+++ b/src/mbgl/style/layers/symbol_layer_impl.hpp
@@ -1,5 +1,6 @@
#pragma once
+#include <mbgl/text/glyph.hpp>
#include <mbgl/util/variant.hpp>
#include <mbgl/style/layer_impl.hpp>
#include <mbgl/style/layers/symbol_layer.hpp>
@@ -67,7 +68,7 @@ public:
std::unique_ptr<Bucket> createBucket(const BucketParameters&, const std::vector<const Layer*>&) const override;
std::unique_ptr<SymbolLayout> createLayout(const BucketParameters&, const std::vector<const Layer*>&,
- const GeometryTileLayer&) const;
+ const GeometryTileLayer&, GlyphDependencies&) const;
IconPaintProperties::Evaluated iconPaintProperties() const;
TextPaintProperties::Evaluated textPaintProperties() const;
diff --git a/src/mbgl/text/glyph.cpp b/src/mbgl/text/glyph.cpp
index 29929b73e6..74863d7435 100644
--- a/src/mbgl/text/glyph.cpp
+++ b/src/mbgl/text/glyph.cpp
@@ -3,7 +3,7 @@
namespace mbgl {
// Note: this only works for the BMP
-GlyphRange getGlyphRange(char16_t glyph) {
+GlyphRange getGlyphRange(GlyphID glyph) {
unsigned start = (glyph/256) * 256;
unsigned end = (start + 255);
if (start > 65280) start = 65280;
diff --git a/src/mbgl/text/glyph.hpp b/src/mbgl/text/glyph.hpp
index 175a36f0fa..6aa4e11b1c 100644
--- a/src/mbgl/text/glyph.hpp
+++ b/src/mbgl/text/glyph.hpp
@@ -1,6 +1,7 @@
#pragma once
#include <mbgl/text/glyph_range.hpp>
+#include <mbgl/util/font_stack.hpp>
#include <mbgl/util/rect.hpp>
#include <mbgl/util/traits.hpp>
#include <mbgl/util/image.hpp>
@@ -12,8 +13,11 @@
namespace mbgl {
+typedef char16_t GlyphID;
+typedef std::set<GlyphID> GlyphIDs;
+
// Note: this only works for the BMP
-GlyphRange getGlyphRange(char16_t glyph);
+GlyphRange getGlyphRange(GlyphID glyph);
struct GlyphMetrics {
explicit operator bool() const {
@@ -50,14 +54,14 @@ struct Glyph {
const GlyphMetrics metrics;
};
-typedef std::map<uint32_t, Glyph> GlyphPositions;
+typedef std::map<GlyphID, Glyph> GlyphPositions;
class PositionedGlyph {
public:
- explicit PositionedGlyph(uint32_t glyph_, float x_, float y_, float angle_)
+ explicit PositionedGlyph(GlyphID glyph_, float x_, float y_, float angle_)
: glyph(glyph_), x(x_), y(y_), angle(angle_) {}
- uint32_t glyph = 0;
+ GlyphID glyph = 0;
float x = 0;
float y = 0;
float angle = 0;
@@ -86,7 +90,7 @@ public:
// also need to be reencoded.
static constexpr const uint8_t borderSize = 3;
- uint32_t id = 0;
+ GlyphID id = 0;
// A signed distance field of the glyph with a border (see above).
AlphaImage bitmap;
@@ -121,4 +125,9 @@ constexpr WritingModeType operator~(WritingModeType value) {
return WritingModeType(~mbgl::underlying_type(value));
}
+typedef std::map<FontStack,GlyphIDs> GlyphDependencies;
+typedef std::map<FontStack,GlyphRangeSet> GlyphRangeDependencies;
+typedef std::map<FontStack,GlyphPositions> GlyphPositionMap;
+
+
} // end namespace mbgl
diff --git a/src/mbgl/text/glyph_atlas.cpp b/src/mbgl/text/glyph_atlas.cpp
index 1b3f7518b5..b242fb490a 100644
--- a/src/mbgl/text/glyph_atlas.cpp
+++ b/src/mbgl/text/glyph_atlas.cpp
@@ -22,81 +22,138 @@ GlyphAtlas::GlyphAtlas(const Size size, FileSource& fileSource_)
GlyphAtlas::~GlyphAtlas() = default;
-void GlyphAtlas::requestGlyphRange(const FontStack& fontStack, const GlyphRange& range) {
- std::lock_guard<std::mutex> lock(mutex);
- auto& rangeSets = entries[fontStack].ranges;
-
- const auto& rangeSetsIt = rangeSets.find(range);
- if (rangeSetsIt != rangeSets.end()) {
- return;
+GlyphSet& GlyphAtlas::getGlyphSet(const FontStack& fontStack) {
+ return entries[fontStack].glyphSet;
+}
+
+bool GlyphAtlas::hasGlyphRanges(const FontStack& fontStack, const GlyphRangeSet& ranges) const {
+ for (const auto& range : ranges) {
+ if (!hasGlyphRange(fontStack,range)) {
+ return false;
+ }
}
-
- rangeSets.emplace(std::piecewise_construct,
- std::forward_as_tuple(range),
- std::forward_as_tuple(this, fontStack, range, observer, fileSource));
+ return true;
+}
+
+bool rangeIsParsed(const std::map<GlyphRange, GlyphPBF>& ranges, const GlyphRange& range) {
+ auto rangeIt = ranges.find(range);
+ if (rangeIt == ranges.end())
+ return false;
+
+ return rangeIt->second.isParsed();
}
+
+bool GlyphAtlas::hasGlyphRange(const FontStack& fontStack, const GlyphRange& range) const {
+ auto entry = entries.find(fontStack);
+ if (entry == entries.end())
+ return false;
+
+ return rangeIsParsed(entry->second.ranges, range);
+}
+
+void GlyphAtlas::getGlyphs(GlyphRequestor& requestor, GlyphDependencies glyphDependencies) {
+ auto dependencies = std::make_shared<GlyphDependencies>(std::move(glyphDependencies));
+
+ // Figure out which glyph ranges need to be fetched. For each range that does need to
+ // be fetched, record an entry mapping the requestor to a shared pointer containing the
+ // dependencies. When the shared pointer becomes unique, we know that all the dependencies
+ // for that requestor have been fetched, and can notify it of completion.
+ for (const auto& dependency : *dependencies) {
+ const FontStack& fontStack = dependency.first;
+ const GlyphIDs& glyphIDs = dependency.second;
+
+ Entry& entry = entries[fontStack];
+
+ GlyphRangeSet processedRanges;
+ for (const auto& glyphID : glyphIDs) {
+ GlyphRange range = getGlyphRange(glyphID);
+ if (processedRanges.find(range) == processedRanges.end() && !rangeIsParsed(entry.ranges, range)) {
+ if (entry.ranges.find(range) == entry.ranges.end()) {
+ entry.ranges.emplace(std::piecewise_construct,
+ std::forward_as_tuple(range),
+ std::forward_as_tuple(this, fontStack, range, this, fileSource));
+ }
-bool GlyphAtlas::hasGlyphRanges(const FontStack& fontStack, const GlyphRangeSet& glyphRanges) {
- if (glyphRanges.empty()) {
- return true;
+ entry.ranges.find(range)->second.requestors[&requestor] = dependencies;
+ }
+ processedRanges.insert(range);
+ }
}
- std::lock_guard<std::mutex> lock(mutex);
- const auto& rangeSets = entries[fontStack].ranges;
+ // If the shared dependencies pointer is already unique, then all dependent glyph ranges
+ // have already been loaded. Send a notification immediately.
+ if (dependencies.unique()) {
+ addGlyphs(requestor, *dependencies);
+ }
+}
- bool hasRanges = true;
- for (const auto& range : glyphRanges) {
- const auto& rangeSetsIt = rangeSets.find(range);
- if (rangeSetsIt == rangeSets.end()) {
- // Push the request to the MapThread, so we can easly cancel
- // if it is still pending when we destroy this object.
- workQueue.push(std::bind(&GlyphAtlas::requestGlyphRange, this, fontStack, range));
+void GlyphAtlas::setObserver(GlyphAtlasObserver* observer_) {
+ observer = observer_;
+}
+
+void GlyphAtlas::onGlyphsError(const FontStack& fontStack, const GlyphRange& range, std::exception_ptr err) {
+ if (observer) {
+ observer->onGlyphsError(fontStack, range, err);
+ }
+}
- hasRanges = false;
- continue;
- }
+void GlyphAtlas::onGlyphsLoaded(const FontStack& fontStack, const GlyphRange& range) {
+ Entry& entry = entries[fontStack];
- if (!rangeSetsIt->second.isParsed()) {
- hasRanges = false;
+ auto it = entry.ranges.find(range);
+ if (it != entry.ranges.end()) {
+ for (auto& pair : it->second.requestors) {
+ GlyphRequestor& requestor = *pair.first;
+ const std::shared_ptr<GlyphDependencies>& dependencies = pair.second;
+ if (dependencies.unique()) {
+ addGlyphs(requestor, *dependencies);
+ }
}
+ it->second.requestors.clear();
}
- return hasRanges;
+ if (observer) {
+ observer->onGlyphsLoaded(fontStack, range);
+ }
}
-util::exclusive<GlyphSet> GlyphAtlas::getGlyphSet(const FontStack& fontStack) {
- auto lock = std::make_unique<std::lock_guard<std::mutex>>(mutex);
- return { &entries[fontStack].glyphSet, std::move(lock) };
-}
+void GlyphAtlas::addGlyphs(GlyphRequestor& requestor, const GlyphDependencies& glyphDependencies) {
+ GlyphPositionMap glyphPositions;
-void GlyphAtlas::setObserver(GlyphAtlasObserver* observer_) {
- observer = observer_;
-}
+ for (const auto& dependency : glyphDependencies) {
+ const FontStack& fontStack = dependency.first;
+ const GlyphIDs& glyphIDs = dependency.second;
-void GlyphAtlas::addGlyphs(uintptr_t tileUID,
- const std::u16string& text,
- const FontStack& fontStack,
- const util::exclusive<GlyphSet>& glyphSet,
- GlyphPositions& face)
-{
- const std::map<uint32_t, SDFGlyph>& sdfs = glyphSet->getSDFs();
+ GlyphPositions& positions = glyphPositions[fontStack];
+ Entry& entry = entries[fontStack];
+ const auto& sdfs = entry.glyphSet.getSDFs();
- for (char16_t chr : text)
- {
- auto sdf_it = sdfs.find(chr);
- if (sdf_it == sdfs.end()) {
- continue;
- }
+ for (const auto& glyphID : glyphIDs) {
+ auto it = sdfs.find(glyphID);
+ if (it == sdfs.end())
+ continue;
+
+ addGlyph(requestor, fontStack, it->second);
- const SDFGlyph& sdf = sdf_it->second;
- Rect<uint16_t> rect = addGlyph(tileUID, fontStack, sdf);
- face.emplace(chr, Glyph{rect, sdf.metrics});
+ // It's possible to have an SDF without a valid position (if the SDF was malformed).
+ // We indicate this case with Rect<uint16_t>(0,0,0,0).
+ auto glyphRect = entry.glyphValues.find(glyphID);
+ const Rect<uint16_t> rect = glyphRect == entry.glyphValues.end()
+ ? Rect<uint16_t>(0,0,0,0)
+ : glyphRect->second.rect;
+
+ positions.emplace(std::piecewise_construct,
+ std::forward_as_tuple(glyphID),
+ std::forward_as_tuple(rect, it->second.metrics));
+ }
}
+
+ requestor.onGlyphsAvailable(glyphPositions);
}
-Rect<uint16_t> GlyphAtlas::addGlyph(uintptr_t tileUID,
- const FontStack& fontStack,
- const SDFGlyph& glyph)
+void GlyphAtlas::addGlyph(GlyphRequestor& requestor,
+ const FontStack& fontStack,
+ const SDFGlyph& glyph)
{
std::map<uint32_t, GlyphValue>& face = entries[fontStack].glyphValues;
auto it = face.find(glyph.id);
@@ -104,15 +161,15 @@ Rect<uint16_t> GlyphAtlas::addGlyph(uintptr_t tileUID,
// The glyph is already in this texture.
if (it != face.end()) {
GlyphValue& value = it->second;
- value.ids.insert(tileUID);
- return value.rect;
+ value.ids.insert(&requestor);
+ return;
}
// Guard against glyphs that are too large, or that we don't need to place into the atlas since
// they don't have any pixels.
if (glyph.metrics.width == 0 || glyph.metrics.width >= 256 ||
glyph.metrics.height == 0 || glyph.metrics.height >= 256) {
- return Rect<uint16_t>{ 0, 0, 0, 0 };
+ return;
}
// Add a 1px border around every image.
@@ -129,53 +186,59 @@ Rect<uint16_t> GlyphAtlas::addGlyph(uintptr_t tileUID,
Rect<uint16_t> rect = bin.allocate(width, height);
if (rect.w == 0) {
Log::Error(Event::OpenGL, "glyph bitmap overflow");
- return rect;
+ return;
}
- face.emplace(glyph.id, GlyphValue { rect, tileUID });
+ face.emplace(glyph.id, GlyphValue { rect, &requestor });
AlphaImage::copy(glyph.bitmap, image, { 0, 0 }, { rect.x + padding, rect.y + padding }, glyph.bitmap.size);
dirty = true;
-
- return rect;
}
-void GlyphAtlas::removeGlyphs(uintptr_t tileUID) {
- std::lock_guard<std::mutex> lock(mutex);
+void GlyphAtlas::removeGlyphValues(GlyphRequestor& requestor, std::map<uint32_t, GlyphValue>& face) {
+ for (auto it = face.begin(); it != face.end(); /* we advance in the body */) {
+ GlyphValue& value = it->second;
+ value.ids.erase(&requestor);
- for (auto& entry : entries) {
- std::map<uint32_t, GlyphValue>& face = entry.second.glyphValues;
- for (auto it = face.begin(); it != face.end(); /* we advance in the body */) {
- GlyphValue& value = it->second;
- value.ids.erase(tileUID);
-
- if (value.ids.empty()) {
- const Rect<uint16_t>& rect = value.rect;
-
- // Clear out the bitmap.
- uint8_t *target = image.data.get();
- for (uint32_t y = 0; y < rect.h; y++) {
- uint32_t y1 = image.size.width * (rect.y + y) + rect.x;
- for (uint32_t x = 0; x < rect.w; x++) {
- target[y1 + x] = 0;
- }
+ if (value.ids.empty()) {
+ const Rect<uint16_t>& rect = value.rect;
+
+ // Clear out the bitmap.
+ uint8_t *target = image.data.get();
+ for (uint32_t y = 0; y < rect.h; y++) {
+ uint32_t y1 = image.size.width * (rect.y + y) + rect.x;
+ for (uint32_t x = 0; x < rect.w; x++) {
+ target[y1 + x] = 0;
}
+ }
- bin.release(rect);
+ bin.release(rect);
- // Make sure to post-increment the iterator: This will return the
- // current iterator, but will go to the next position before we
- // erase the element from the map. That way, the iterator stays
- // valid.
- face.erase(it++);
- } else {
- ++it;
- }
+ // Make sure to post-increment the iterator: This will return the
+ // current iterator, but will go to the next position before we
+ // erase the element from the map. That way, the iterator stays
+ // valid.
+ face.erase(it++);
+ } else {
+ ++it;
}
}
}
+void GlyphAtlas::removePendingRanges(mbgl::GlyphRequestor &requestor, std::map<GlyphRange, GlyphPBF> &ranges) {
+ for (auto it = ranges.begin(); it != ranges.end(); it++) {
+ it->second.requestors.erase(&requestor);
+ }
+}
+
+void GlyphAtlas::removeGlyphs(GlyphRequestor& requestor) {
+ for (auto& entry : entries) {
+ removeGlyphValues(requestor, entry.second.glyphValues);
+ removePendingRanges(requestor, entry.second.ranges);
+ }
+}
+
Size GlyphAtlas::getSize() const {
return image.size;
}
@@ -186,7 +249,7 @@ void GlyphAtlas::upload(gl::Context& context, gl::TextureUnit unit) {
} else if (dirty) {
context.updateTexture(*texture, image, unit);
}
-
+
dirty = false;
}
diff --git a/src/mbgl/text/glyph_atlas.hpp b/src/mbgl/text/glyph_atlas.hpp
index 8267630096..e40f1b5147 100644
--- a/src/mbgl/text/glyph_atlas.hpp
+++ b/src/mbgl/text/glyph_atlas.hpp
@@ -1,46 +1,55 @@
#pragma once
#include <mbgl/text/glyph.hpp>
+#include <mbgl/text/glyph_atlas_observer.hpp>
+#include <mbgl/text/glyph_range.hpp>
#include <mbgl/text/glyph_set.hpp>
#include <mbgl/geometry/binpack.hpp>
#include <mbgl/util/noncopyable.hpp>
#include <mbgl/util/optional.hpp>
#include <mbgl/util/font_stack.hpp>
-#include <mbgl/util/exclusive.hpp>
#include <mbgl/util/work_queue.hpp>
#include <mbgl/util/image.hpp>
#include <mbgl/gl/texture.hpp>
#include <mbgl/gl/object.hpp>
-#include <atomic>
#include <string>
#include <unordered_set>
#include <unordered_map>
-#include <mutex>
+
+class GlyphAtlasTest;
namespace mbgl {
class FileSource;
class GlyphPBF;
-class GlyphAtlasObserver;
namespace gl {
class Context;
} // namespace gl
-class GlyphAtlas : public util::noncopyable {
+class GlyphRequestor {
+public:
+ virtual void onGlyphsAvailable(GlyphPositionMap) = 0;
+};
+
+class GlyphAtlas : public util::noncopyable, public GlyphAtlasObserver {
public:
GlyphAtlas(Size, FileSource&);
~GlyphAtlas();
- util::exclusive<GlyphSet> getGlyphSet(const FontStack&);
-
- // Returns true if the set of GlyphRanges are available and parsed or false
- // if they are not. For the missing ranges, a request on the FileSource is
- // made and when the glyph if finally parsed, it gets added to the respective
- // GlyphSet and a signal is emitted to notify the observers. This method
- // can be called from any thread.
- bool hasGlyphRanges(const FontStack&, const GlyphRangeSet&);
+ GlyphSet& getGlyphSet(const FontStack&);
+
+ // Workers send a `getGlyphs` message to the main thread once they have determined
+ // which glyphs they will need. Invoking this method will increment reference
+ // counts for all the glyphs in `GlyphDependencies`. If all glyphs are already
+ // locally available, the observer will be notified that the glyphs are available
+ // immediately. Otherwise, a request on the FileSource is made, and when all glyphs
+ // are parsed and added to the atlas, the observer will be notified.
+ // Workers are given a copied 'GlyphPositions' map to use for placing their glyphs.
+ // The positions specified in this object are guaranteed to be
+ // valid for the lifetime of the tile.
+ void getGlyphs(GlyphRequestor& requestor, GlyphDependencies glyphs);
void setURL(const std::string &url) {
glyphURL = url;
@@ -52,12 +61,7 @@ public:
void setObserver(GlyphAtlasObserver* observer);
- void addGlyphs(uintptr_t tileUID,
- const std::u16string& text,
- const FontStack&,
- const util::exclusive<GlyphSet>&,
- GlyphPositions&);
- void removeGlyphs(uintptr_t tileUID);
+ void removeGlyphs(GlyphRequestor&);
// Binds the atlas texture to the GPU, and uploads data if it is out of date.
void bind(gl::Context&, gl::TextureUnit unit);
@@ -67,22 +71,29 @@ public:
void upload(gl::Context&, gl::TextureUnit unit);
Size getSize() const;
+
+ virtual void onGlyphsLoaded(const FontStack&, const GlyphRange&);
+ virtual void onGlyphsError(const FontStack&, const GlyphRange&, std::exception_ptr);
+
+ friend class ::GlyphAtlasTest;
private:
- void requestGlyphRange(const FontStack&, const GlyphRange&);
+ void addGlyphs(GlyphRequestor& requestor, const GlyphDependencies& glyphDependencies);
- Rect<uint16_t> addGlyph(uintptr_t tileID,
- const FontStack&,
- const SDFGlyph&);
+ // Only used by GlyphAtlasTest
+ bool hasGlyphRanges(const FontStack&, const GlyphRangeSet& ranges) const;
+ bool hasGlyphRange(const FontStack&, const GlyphRange& range) const;
+ void addGlyph(GlyphRequestor& requestor, const FontStack&, const SDFGlyph&);
+
FileSource& fileSource;
std::string glyphURL;
struct GlyphValue {
- GlyphValue(Rect<uint16_t> rect_, uintptr_t id)
+ GlyphValue(Rect<uint16_t> rect_, GlyphRequestor* id)
: rect(std::move(rect_)), ids({ id }) {}
Rect<uint16_t> rect;
- std::unordered_set<uintptr_t> ids;
+ std::unordered_set<GlyphRequestor*> ids;
};
struct Entry {
@@ -92,14 +103,15 @@ private:
};
std::unordered_map<FontStack, Entry, FontStackHash> entries;
- std::mutex mutex;
+
+ void removeGlyphValues(GlyphRequestor& requestor, std::map<uint32_t, GlyphValue>& face);
+ void removePendingRanges(GlyphRequestor& requestor, std::map<GlyphRange, GlyphPBF>& ranges);
- util::WorkQueue workQueue;
GlyphAtlasObserver* observer = nullptr;
BinPack<uint16_t> bin;
AlphaImage image;
- std::atomic<bool> dirty;
+ bool dirty;
mbgl::optional<gl::Texture> texture;
};
diff --git a/src/mbgl/text/glyph_pbf.cpp b/src/mbgl/text/glyph_pbf.cpp
index 5c57d278db..26eff812b7 100644
--- a/src/mbgl/text/glyph_pbf.cpp
+++ b/src/mbgl/text/glyph_pbf.cpp
@@ -16,6 +16,7 @@ namespace mbgl {
namespace {
+// Parses a Glyph Protobuf and inserts it into the GlyphAtlas. Must be called from main thread.
void parseGlyphPBF(GlyphSet& glyphSet, const GlyphRange& glyphRange, const std::string& data) {
protozero::pbf_reader glyphs_pbf(data);
@@ -117,7 +118,7 @@ GlyphPBF::GlyphPBF(GlyphAtlas* atlas,
observer->onGlyphsLoaded(fontStack, glyphRange);
} else {
try {
- parseGlyphPBF(**atlas->getGlyphSet(fontStack), glyphRange, *res.data);
+ parseGlyphPBF(atlas->getGlyphSet(fontStack), glyphRange, *res.data);
} catch (...) {
observer->onGlyphsError(fontStack, glyphRange, std::current_exception());
return;
diff --git a/src/mbgl/text/glyph_pbf.hpp b/src/mbgl/text/glyph_pbf.hpp
index 7412ebe411..d5b89cd107 100644
--- a/src/mbgl/text/glyph_pbf.hpp
+++ b/src/mbgl/text/glyph_pbf.hpp
@@ -8,10 +8,12 @@
#include <functional>
#include <string>
#include <memory>
+#include <unordered_map>
namespace mbgl {
class GlyphAtlas;
+class GlyphRequestor;
class GlyphAtlasObserver;
class AsyncRequest;
class FileSource;
@@ -29,10 +31,10 @@ public:
return parsed;
}
-private:
- std::atomic<bool> parsed;
+ bool parsed;
std::unique_ptr<AsyncRequest> req;
GlyphAtlasObserver* observer = nullptr;
+ std::unordered_map<GlyphRequestor*, std::shared_ptr<GlyphDependencies>> requestors;
};
} // namespace mbgl
diff --git a/src/mbgl/text/glyph_set.cpp b/src/mbgl/text/glyph_set.cpp
index 19a6e2cddd..b8e155502e 100644
--- a/src/mbgl/text/glyph_set.cpp
+++ b/src/mbgl/text/glyph_set.cpp
@@ -1,13 +1,6 @@
-#include <mbgl/math/minmax.hpp>
#include <mbgl/text/glyph_set.hpp>
-#include <mbgl/util/i18n.hpp>
#include <mbgl/util/logging.hpp>
-#include <boost/algorithm/string.hpp>
-
-#include <algorithm>
-#include <cassert>
-
namespace mbgl {
void GlyphSet::insert(uint32_t id, SDFGlyph&& glyph) {
@@ -34,272 +27,4 @@ const std::map<uint32_t, SDFGlyph>& GlyphSet::getSDFs() const {
return sdfs;
}
-const Shaping GlyphSet::getShaping(const std::u16string& logicalInput,
- const float maxWidth,
- const float lineHeight,
- const float horizontalAlign,
- const float verticalAlign,
- const float justify,
- const float spacing,
- const Point<float>& translate,
- const float verticalHeight,
- const WritingModeType writingMode,
- BiDi& bidi) const {
- Shaping shaping(translate.x * 24, translate.y * 24, writingMode);
-
- std::vector<std::u16string> reorderedLines =
- bidi.processText(logicalInput,
- determineLineBreaks(logicalInput, spacing, maxWidth, writingMode));
-
- shapeLines(shaping, reorderedLines, spacing, lineHeight, horizontalAlign, verticalAlign,
- justify, translate, verticalHeight, writingMode);
-
- return shaping;
-}
-
-void align(Shaping& shaping,
- const float justify,
- const float horizontalAlign,
- const float verticalAlign,
- const float maxLineLength,
- const float lineHeight,
- const std::size_t lineCount,
- const Point<float>& translate) {
- const float shiftX =
- (justify - horizontalAlign) * maxLineLength + ::round(translate.x * 24 /* one em */);
- const float shiftY =
- (-verticalAlign * lineCount + 0.5) * lineHeight + ::round(translate.y * 24 /* one em */);
-
- for (auto& glyph : shaping.positionedGlyphs) {
- glyph.x += shiftX;
- glyph.y += shiftY;
- }
-}
-
-// justify left = 0, right = 1, center = .5
-void justifyLine(std::vector<PositionedGlyph>& positionedGlyphs,
- const std::map<uint32_t, SDFGlyph>& sdfs,
- std::size_t start,
- std::size_t end,
- float justify) {
- if (!justify) {
- return;
- }
-
- PositionedGlyph& glyph = positionedGlyphs[end];
- auto it = sdfs.find(glyph.glyph);
- if (it != sdfs.end()) {
- const uint32_t lastAdvance = it->second.metrics.advance;
- const float lineIndent = float(glyph.x + lastAdvance) * justify;
-
- for (std::size_t j = start; j <= end; j++) {
- positionedGlyphs[j].x -= lineIndent;
- }
- }
-}
-
-float GlyphSet::determineAverageLineWidth(const std::u16string& logicalInput,
- const float spacing,
- float maxWidth) const {
- float totalWidth = 0;
-
- for (char16_t chr : logicalInput) {
- auto it = sdfs.find(chr);
- if (it != sdfs.end()) {
- totalWidth += it->second.metrics.advance + spacing;
- }
- }
-
- int32_t targetLineCount = std::fmax(1, std::ceil(totalWidth / maxWidth));
- return totalWidth / targetLineCount;
-}
-
-float calculateBadness(const float lineWidth, const float targetWidth, const float penalty, const bool isLastBreak) {
- const float raggedness = std::pow(lineWidth - targetWidth, 2);
- if (isLastBreak) {
- // Favor finals lines shorter than average over longer than average
- if (lineWidth < targetWidth) {
- return raggedness / 2;
- } else {
- return raggedness * 2;
- }
- }
- if (penalty < 0) {
- return raggedness - std::pow(penalty, 2);
- }
- return raggedness + std::pow(penalty, 2);
-}
-
-float calculatePenalty(char16_t codePoint, char16_t nextCodePoint) {
- float penalty = 0;
- // Force break on newline
- if (codePoint == 0x0a) {
- penalty -= 10000;
- }
- // Penalize open parenthesis at end of line
- if (codePoint == 0x28 || codePoint == 0xff08) {
- penalty += 50;
- }
-
- // Penalize close parenthesis at beginning of line
- if (nextCodePoint == 0x29 || nextCodePoint == 0xff09) {
- penalty += 50;
- }
-
- return penalty;
-}
-
-struct PotentialBreak {
- PotentialBreak(const std::size_t p_index, const float p_x, const PotentialBreak* p_priorBreak, const float p_badness)
- : index(p_index), x(p_x), priorBreak(p_priorBreak), badness(p_badness)
- {}
-
- const std::size_t index;
- const float x;
- const PotentialBreak* priorBreak;
- const float badness;
-};
-
-
-PotentialBreak evaluateBreak(const std::size_t breakIndex, const float breakX, const float targetWidth, const std::list<PotentialBreak>& potentialBreaks, const float penalty, const bool isLastBreak) {
- // We could skip evaluating breaks where the line length (breakX - priorBreak.x) > maxWidth
- // ...but in fact we allow lines longer than maxWidth (if there's no break points)
- // ...and when targetWidth and maxWidth are close, strictly enforcing maxWidth can give
- // more lopsided results.
-
- const PotentialBreak* bestPriorBreak = nullptr;
- float bestBreakBadness = calculateBadness(breakX, targetWidth, penalty, isLastBreak);
- for (const auto& potentialBreak : potentialBreaks) {
- const float lineWidth = breakX - potentialBreak.x;
- float breakBadness =
- calculateBadness(lineWidth, targetWidth, penalty, isLastBreak) + potentialBreak.badness;
- if (breakBadness <= bestBreakBadness) {
- bestPriorBreak = &potentialBreak;
- bestBreakBadness = breakBadness;
- }
- }
-
- return PotentialBreak(breakIndex, breakX, bestPriorBreak, bestBreakBadness);
-}
-
-std::set<std::size_t> leastBadBreaks(const PotentialBreak& lastLineBreak) {
- std::set<std::size_t> leastBadBreaks = { lastLineBreak.index };
- const PotentialBreak* priorBreak = lastLineBreak.priorBreak;
- while (priorBreak) {
- leastBadBreaks.insert(priorBreak->index);
- priorBreak = priorBreak->priorBreak;
- }
- return leastBadBreaks;
-}
-
-
-// We determine line breaks based on shaped text in logical order. Working in visual order would be
-// more intuitive, but we can't do that because the visual order may be changed by line breaks!
-std::set<std::size_t> GlyphSet::determineLineBreaks(const std::u16string& logicalInput,
- const float spacing,
- float maxWidth,
- const WritingModeType writingMode) const {
- if (!maxWidth || writingMode != WritingModeType::Horizontal) {
- return {};
- }
-
- if (logicalInput.empty()) {
- return {};
- }
-
- const float targetWidth = determineAverageLineWidth(logicalInput, spacing, maxWidth);
-
- std::list<PotentialBreak> potentialBreaks;
- float currentX = 0;
-
- for (std::size_t i = 0; i < logicalInput.size(); i++) {
- const char16_t codePoint = logicalInput[i];
- auto it = sdfs.find(codePoint);
- if (it != sdfs.end() && !boost::algorithm::is_any_of(u" \t\n\v\f\r")(codePoint)) {
- currentX += it->second.metrics.advance + spacing;
- }
-
- // Ideographic characters, spaces, and word-breaking punctuation that often appear without
- // surrounding spaces.
- if ((i < logicalInput.size() - 1) &&
- (util::i18n::allowsWordBreaking(codePoint) || util::i18n::allowsIdeographicBreaking(codePoint))) {
- potentialBreaks.push_back(evaluateBreak(i+1, currentX, targetWidth, potentialBreaks,
- calculatePenalty(codePoint, logicalInput[i+1]),
- false));
- }
- }
-
- return leastBadBreaks(evaluateBreak(logicalInput.size(), currentX, targetWidth, potentialBreaks, 0, true));
-}
-
-void GlyphSet::shapeLines(Shaping& shaping,
- const std::vector<std::u16string>& lines,
- const float spacing,
- const float lineHeight,
- const float horizontalAlign,
- const float verticalAlign,
- const float justify,
- const Point<float>& translate,
- const float verticalHeight,
- const WritingModeType writingMode) const {
-
- // the y offset *should* be part of the font metadata
- const int32_t yOffset = -17;
-
- float x = 0;
- float y = yOffset;
-
- float maxLineLength = 0;
-
- for (std::u16string line : lines) {
- // Collapse whitespace so it doesn't throw off justification
- boost::algorithm::trim_if(line, boost::algorithm::is_any_of(u" \t\n\v\f\r"));
-
- if (line.empty()) {
- y += lineHeight; // Still need a line feed after empty line
- continue;
- }
-
- std::size_t lineStartIndex = shaping.positionedGlyphs.size();
- for (char16_t chr : line) {
- auto it = sdfs.find(chr);
- if (it == sdfs.end()) {
- continue;
- }
-
- const SDFGlyph& glyph = it->second;
-
- if (writingMode == WritingModeType::Horizontal || !util::i18n::hasUprightVerticalOrientation(chr)) {
- shaping.positionedGlyphs.emplace_back(chr, x, y, 0);
- x += glyph.metrics.advance + spacing;
- } else {
- shaping.positionedGlyphs.emplace_back(chr, x, 0, -M_PI_2);
- x += verticalHeight + spacing;
- }
- }
-
- // Only justify if we placed at least one glyph
- if (shaping.positionedGlyphs.size() != lineStartIndex) {
- float lineLength = x - spacing; // Don't count trailing spacing
- maxLineLength = util::max(lineLength, maxLineLength);
-
- justifyLine(shaping.positionedGlyphs, sdfs, lineStartIndex,
- shaping.positionedGlyphs.size() - 1, justify);
- }
-
- x = 0;
- y += lineHeight;
- }
-
- align(shaping, justify, horizontalAlign, verticalAlign, maxLineLength, lineHeight,
- lines.size(), translate);
- const uint32_t height = lines.size() * lineHeight;
-
- // Calculate the bounding box
- shaping.top += -verticalAlign * height;
- shaping.bottom = shaping.top + height;
- shaping.left += -horizontalAlign * maxLineLength;
- shaping.right = shaping.left + maxLineLength;
-}
-
} // end namespace mbgl
diff --git a/src/mbgl/text/glyph_set.hpp b/src/mbgl/text/glyph_set.hpp
index 0342c82eb5..9f4bef94d2 100644
--- a/src/mbgl/text/glyph_set.hpp
+++ b/src/mbgl/text/glyph_set.hpp
@@ -1,6 +1,5 @@
#pragma once
-#include <mbgl/text/bidi.hpp>
#include <mbgl/text/glyph.hpp>
#include <mbgl/util/geometry.hpp>
@@ -10,38 +9,8 @@ class GlyphSet {
public:
void insert(uint32_t id, SDFGlyph&&);
const std::map<uint32_t, SDFGlyph>& getSDFs() const;
- const Shaping getShaping(const std::u16string& string,
- float maxWidth,
- float lineHeight,
- float horizontalAlign,
- float verticalAlign,
- float justify,
- float spacing,
- const Point<float>& translate,
- float verticalHeight,
- const WritingModeType,
- BiDi& bidi) const;
private:
- float determineAverageLineWidth(const std::u16string& logicalInput,
- const float spacing,
- float maxWidth) const;
- std::set<std::size_t> determineLineBreaks(const std::u16string& logicalInput,
- const float spacing,
- float maxWidth,
- const WritingModeType) const;
-
- void shapeLines(Shaping& shaping,
- const std::vector<std::u16string>& lines,
- const float spacing,
- float lineHeight,
- float horizontalAlign,
- float verticalAlign,
- float justify,
- const Point<float>& translate,
- float verticalHeight,
- const WritingModeType) const;
-
std::map<uint32_t, SDFGlyph> sdfs;
};
diff --git a/src/mbgl/text/shaping.cpp b/src/mbgl/text/shaping.cpp
index e68566d419..7285d9c812 100644
--- a/src/mbgl/text/shaping.cpp
+++ b/src/mbgl/text/shaping.cpp
@@ -1,5 +1,12 @@
#include <mbgl/text/shaping.hpp>
+#include <mbgl/util/i18n.hpp>
#include <mbgl/layout/symbol_feature.hpp>
+#include <mbgl/math/minmax.hpp>
+#include <mbgl/text/bidi.hpp>
+
+#include <boost/algorithm/string.hpp>
+
+#include <algorithm>
namespace mbgl {
@@ -14,4 +21,277 @@ PositionedIcon shapeIcon(const SpriteAtlasElement& image, const std::array<float
return PositionedIcon(image, y1, y2, x1, x2, iconRotation);
}
+void align(Shaping& shaping,
+ const float justify,
+ const float horizontalAlign,
+ const float verticalAlign,
+ const float maxLineLength,
+ const float lineHeight,
+ const std::size_t lineCount,
+ const Point<float>& translate) {
+ const float shiftX =
+ (justify - horizontalAlign) * maxLineLength + ::round(translate.x * 24 /* one em */);
+ const float shiftY =
+ (-verticalAlign * lineCount + 0.5) * lineHeight + ::round(translate.y * 24 /* one em */);
+
+ for (auto& glyph : shaping.positionedGlyphs) {
+ glyph.x += shiftX;
+ glyph.y += shiftY;
+ }
+}
+
+// justify left = 0, right = 1, center = .5
+void justifyLine(std::vector<PositionedGlyph>& positionedGlyphs,
+ const GlyphPositions& glyphs,
+ std::size_t start,
+ std::size_t end,
+ float justify) {
+ if (!justify) {
+ return;
+ }
+
+ PositionedGlyph& glyph = positionedGlyphs[end];
+ auto it = glyphs.find(glyph.glyph);
+ if (it != glyphs.end()) {
+ const uint32_t lastAdvance = it->second.metrics.advance;
+ const float lineIndent = float(glyph.x + lastAdvance) * justify;
+
+ for (std::size_t j = start; j <= end; j++) {
+ positionedGlyphs[j].x -= lineIndent;
+ }
+ }
+}
+
+float determineAverageLineWidth(const std::u16string& logicalInput,
+ const float spacing,
+ float maxWidth,
+ const GlyphPositions& glyphs) {
+ float totalWidth = 0;
+
+ for (char16_t chr : logicalInput) {
+ auto it = glyphs.find(chr);
+ if (it != glyphs.end()) {
+ totalWidth += it->second.metrics.advance + spacing;
+ }
+ }
+
+ int32_t targetLineCount = std::fmax(1, std::ceil(totalWidth / maxWidth));
+ return totalWidth / targetLineCount;
+}
+
+float calculateBadness(const float lineWidth, const float targetWidth, const float penalty, const bool isLastBreak) {
+ const float raggedness = std::pow(lineWidth - targetWidth, 2);
+ if (isLastBreak) {
+ // Favor finals lines shorter than average over longer than average
+ if (lineWidth < targetWidth) {
+ return raggedness / 2;
+ } else {
+ return raggedness * 2;
+ }
+ }
+ if (penalty < 0) {
+ return raggedness - std::pow(penalty, 2);
+ }
+ return raggedness + std::pow(penalty, 2);
+}
+
+float calculatePenalty(char16_t codePoint, char16_t nextCodePoint) {
+ float penalty = 0;
+ // Force break on newline
+ if (codePoint == 0x0a) {
+ penalty -= 10000;
+ }
+ // Penalize open parenthesis at end of line
+ if (codePoint == 0x28 || codePoint == 0xff08) {
+ penalty += 50;
+ }
+
+ // Penalize close parenthesis at beginning of line
+ if (nextCodePoint == 0x29 || nextCodePoint == 0xff09) {
+ penalty += 50;
+ }
+
+ return penalty;
+}
+
+struct PotentialBreak {
+ PotentialBreak(const std::size_t p_index, const float p_x, const PotentialBreak* p_priorBreak, const float p_badness)
+ : index(p_index), x(p_x), priorBreak(p_priorBreak), badness(p_badness)
+ {}
+
+ const std::size_t index;
+ const float x;
+ const PotentialBreak* priorBreak;
+ const float badness;
+};
+
+
+PotentialBreak evaluateBreak(const std::size_t breakIndex, const float breakX, const float targetWidth, const std::list<PotentialBreak>& potentialBreaks, const float penalty, const bool isLastBreak) {
+ // We could skip evaluating breaks where the line length (breakX - priorBreak.x) > maxWidth
+ // ...but in fact we allow lines longer than maxWidth (if there's no break points)
+ // ...and when targetWidth and maxWidth are close, strictly enforcing maxWidth can give
+ // more lopsided results.
+
+ const PotentialBreak* bestPriorBreak = nullptr;
+ float bestBreakBadness = calculateBadness(breakX, targetWidth, penalty, isLastBreak);
+ for (const auto& potentialBreak : potentialBreaks) {
+ const float lineWidth = breakX - potentialBreak.x;
+ float breakBadness =
+ calculateBadness(lineWidth, targetWidth, penalty, isLastBreak) + potentialBreak.badness;
+ if (breakBadness <= bestBreakBadness) {
+ bestPriorBreak = &potentialBreak;
+ bestBreakBadness = breakBadness;
+ }
+ }
+
+ return PotentialBreak(breakIndex, breakX, bestPriorBreak, bestBreakBadness);
+}
+
+std::set<std::size_t> leastBadBreaks(const PotentialBreak& lastLineBreak) {
+ std::set<std::size_t> leastBadBreaks = { lastLineBreak.index };
+ const PotentialBreak* priorBreak = lastLineBreak.priorBreak;
+ while (priorBreak) {
+ leastBadBreaks.insert(priorBreak->index);
+ priorBreak = priorBreak->priorBreak;
+ }
+ return leastBadBreaks;
+}
+
+
+// We determine line breaks based on shaped text in logical order. Working in visual order would be
+// more intuitive, but we can't do that because the visual order may be changed by line breaks!
+std::set<std::size_t> determineLineBreaks(const std::u16string& logicalInput,
+ const float spacing,
+ float maxWidth,
+ const WritingModeType writingMode,
+ const GlyphPositions& glyphs) {
+ if (!maxWidth || writingMode != WritingModeType::Horizontal) {
+ return {};
+ }
+
+ if (logicalInput.empty()) {
+ return {};
+ }
+
+ const float targetWidth = determineAverageLineWidth(logicalInput, spacing, maxWidth, glyphs);
+
+ std::list<PotentialBreak> potentialBreaks;
+ float currentX = 0;
+
+ for (std::size_t i = 0; i < logicalInput.size(); i++) {
+ const char16_t codePoint = logicalInput[i];
+ auto it = glyphs.find(codePoint);
+ if (it != glyphs.end() && !boost::algorithm::is_any_of(u" \t\n\v\f\r")(codePoint)) {
+ currentX += it->second.metrics.advance + spacing;
+ }
+
+ // Ideographic characters, spaces, and word-breaking punctuation that often appear without
+ // surrounding spaces.
+ if ((i < logicalInput.size() - 1) &&
+ (util::i18n::allowsWordBreaking(codePoint) || util::i18n::allowsIdeographicBreaking(codePoint))) {
+ potentialBreaks.push_back(evaluateBreak(i+1, currentX, targetWidth, potentialBreaks,
+ calculatePenalty(codePoint, logicalInput[i+1]),
+ false));
+ }
+ }
+
+ return leastBadBreaks(evaluateBreak(logicalInput.size(), currentX, targetWidth, potentialBreaks, 0, true));
+}
+
+void shapeLines(Shaping& shaping,
+ const std::vector<std::u16string>& lines,
+ const float spacing,
+ const float lineHeight,
+ const float horizontalAlign,
+ const float verticalAlign,
+ const float justify,
+ const Point<float>& translate,
+ const float verticalHeight,
+ const WritingModeType writingMode,
+ const GlyphPositions& glyphs) {
+
+ // the y offset *should* be part of the font metadata
+ const int32_t yOffset = -17;
+
+ float x = 0;
+ float y = yOffset;
+
+ float maxLineLength = 0;
+
+ for (std::u16string line : lines) {
+ // Collapse whitespace so it doesn't throw off justification
+ boost::algorithm::trim_if(line, boost::algorithm::is_any_of(u" \t\n\v\f\r"));
+
+ if (line.empty()) {
+ y += lineHeight; // Still need a line feed after empty line
+ continue;
+ }
+
+ std::size_t lineStartIndex = shaping.positionedGlyphs.size();
+ for (char16_t chr : line) {
+ auto it = glyphs.find(chr);
+ if (it == glyphs.end()) {
+ continue;
+ }
+
+ const Glyph& glyph = it->second;
+
+ if (writingMode == WritingModeType::Horizontal || !util::i18n::hasUprightVerticalOrientation(chr)) {
+ shaping.positionedGlyphs.emplace_back(chr, x, y, 0);
+ x += glyph.metrics.advance + spacing;
+ } else {
+ shaping.positionedGlyphs.emplace_back(chr, x, 0, -M_PI_2);
+ x += verticalHeight + spacing;
+ }
+ }
+
+ // Only justify if we placed at least one glyph
+ if (shaping.positionedGlyphs.size() != lineStartIndex) {
+ float lineLength = x - spacing; // Don't count trailing spacing
+ maxLineLength = util::max(lineLength, maxLineLength);
+
+ justifyLine(shaping.positionedGlyphs, glyphs, lineStartIndex,
+ shaping.positionedGlyphs.size() - 1, justify);
+ }
+
+ x = 0;
+ y += lineHeight;
+ }
+
+ align(shaping, justify, horizontalAlign, verticalAlign, maxLineLength, lineHeight,
+ lines.size(), translate);
+ const uint32_t height = lines.size() * lineHeight;
+
+ // Calculate the bounding box
+ shaping.top += -verticalAlign * height;
+ shaping.bottom = shaping.top + height;
+ shaping.left += -horizontalAlign * maxLineLength;
+ shaping.right = shaping.left + maxLineLength;
+}
+
+const Shaping getShaping(const std::u16string& logicalInput,
+ const float maxWidth,
+ const float lineHeight,
+ const float horizontalAlign,
+ const float verticalAlign,
+ const float justify,
+ const float spacing,
+ const Point<float>& translate,
+ const float verticalHeight,
+ const WritingModeType writingMode,
+ BiDi& bidi,
+ const GlyphPositions& glyphs) {
+ Shaping shaping(translate.x * 24, translate.y * 24, writingMode);
+
+ std::vector<std::u16string> reorderedLines =
+ bidi.processText(logicalInput,
+ determineLineBreaks(logicalInput, spacing, maxWidth, writingMode, glyphs));
+
+ shapeLines(shaping, reorderedLines, spacing, lineHeight, horizontalAlign, verticalAlign,
+ justify, translate, verticalHeight, writingMode, glyphs);
+
+ return shaping;
+}
+
+
} // namespace mbgl
diff --git a/src/mbgl/text/shaping.hpp b/src/mbgl/text/shaping.hpp
index 1b7b8b2733..fc404e3a97 100644
--- a/src/mbgl/text/shaping.hpp
+++ b/src/mbgl/text/shaping.hpp
@@ -9,6 +9,7 @@ namespace mbgl {
class SpriteAtlasElement;
class SymbolFeature;
+class BiDi;
class PositionedIcon {
public:
@@ -37,5 +38,18 @@ public:
};
PositionedIcon shapeIcon(const SpriteAtlasElement&, const std::array<float, 2>& iconOffset, const float iconRotation);
+
+const Shaping getShaping(const std::u16string& string,
+ float maxWidth,
+ float lineHeight,
+ float horizontalAlign,
+ float verticalAlign,
+ float justify,
+ float spacing,
+ const Point<float>& translate,
+ float verticalHeight,
+ const WritingModeType,
+ BiDi& bidi,
+ const GlyphPositions& glyphs);
} // namespace mbgl
diff --git a/src/mbgl/tile/geometry_tile.cpp b/src/mbgl/tile/geometry_tile.cpp
index 5f1fc5de66..602c43148a 100644
--- a/src/mbgl/tile/geometry_tile.cpp
+++ b/src/mbgl/tile/geometry_tile.cpp
@@ -32,12 +32,13 @@ GeometryTile::GeometryTile(const OverscaledTileID& id_,
worker(parameters.workerScheduler,
ActorRef<GeometryTile>(*this, mailbox),
id_,
- *parameters.style.glyphAtlas,
obsolete,
- parameters.mode) {
+ parameters.mode),
+ glyphAtlas(*parameters.style.glyphAtlas) {
}
GeometryTile::~GeometryTile() {
+ glyphAtlas.removeGlyphs(*this);
cancel();
}
@@ -130,6 +131,14 @@ void GeometryTile::onError(std::exception_ptr err) {
availableData = DataAvailability::All;
observer->onTileError(*this, err);
}
+
+void GeometryTile::onGlyphsAvailable(GlyphPositionMap glyphPositions) {
+ worker.invoke(&GeometryTileWorker::onGlyphsAvailable, std::move(glyphPositions));
+}
+
+void GeometryTile::getGlyphs(GlyphDependencies glyphDependencies) {
+ glyphAtlas.getGlyphs(*this, std::move(glyphDependencies));
+}
Bucket* GeometryTile::getBucket(const Layer& layer) {
const auto& buckets = layer.is<SymbolLayer>() ? symbolBuckets : nonSymbolBuckets;
diff --git a/src/mbgl/tile/geometry_tile.hpp b/src/mbgl/tile/geometry_tile.hpp
index cabe193467..c7b0264c5e 100644
--- a/src/mbgl/tile/geometry_tile.hpp
+++ b/src/mbgl/tile/geometry_tile.hpp
@@ -2,6 +2,7 @@
#include <mbgl/tile/tile.hpp>
#include <mbgl/tile/geometry_tile_worker.hpp>
+#include <mbgl/text/glyph_atlas.hpp>
#include <mbgl/text/placement_config.hpp>
#include <mbgl/util/feature.hpp>
#include <mbgl/actor/actor.hpp>
@@ -24,7 +25,7 @@ class UpdateParameters;
class SourceQueryOptions;
} // namespace style
-class GeometryTile : public Tile {
+class GeometryTile : public Tile, public GlyphRequestor {
public:
GeometryTile(const OverscaledTileID&,
std::string sourceID,
@@ -38,6 +39,8 @@ public:
void setPlacementConfig(const PlacementConfig&) override;
void symbolDependenciesChanged() override;
void redoLayout() override;
+
+ void getGlyphs(GlyphDependencies);
Bucket* getBucket(const style::Layer&) override;
@@ -71,6 +74,8 @@ public:
void onPlacement(PlacementResult);
void onError(std::exception_ptr);
+
+ virtual void onGlyphsAvailable(GlyphPositionMap) override;
protected:
const GeometryTileData* getData() {
@@ -87,6 +92,8 @@ private:
std::shared_ptr<Mailbox> mailbox;
Actor<GeometryTileWorker> worker;
+ GlyphAtlas& glyphAtlas;
+
uint64_t correlationID = 0;
optional<PlacementConfig> requestedConfig;
diff --git a/src/mbgl/tile/geometry_tile_worker.cpp b/src/mbgl/tile/geometry_tile_worker.cpp
index b1fd7a852e..0d17526cb1 100644
--- a/src/mbgl/tile/geometry_tile_worker.cpp
+++ b/src/mbgl/tile/geometry_tile_worker.cpp
@@ -2,7 +2,6 @@
#include <mbgl/tile/geometry_tile_data.hpp>
#include <mbgl/tile/geometry_tile.hpp>
#include <mbgl/text/collision_tile.hpp>
-#include <mbgl/text/glyph_atlas.hpp>
#include <mbgl/layout/symbol_layout.hpp>
#include <mbgl/style/bucket_parameters.hpp>
#include <mbgl/style/group_by_layout.hpp>
@@ -25,19 +24,16 @@ using namespace style;
GeometryTileWorker::GeometryTileWorker(ActorRef<GeometryTileWorker> self_,
ActorRef<GeometryTile> parent_,
OverscaledTileID id_,
- GlyphAtlas& glyphAtlas_,
const std::atomic<bool>& obsolete_,
const MapMode mode_)
: self(std::move(self_)),
parent(std::move(parent_)),
id(std::move(id_)),
- glyphAtlas(glyphAtlas_),
obsolete(obsolete_),
mode(mode_) {
}
GeometryTileWorker::~GeometryTileWorker() {
- glyphAtlas.removeGlyphs(reinterpret_cast<uintptr_t>(this));
}
/*
@@ -168,6 +164,11 @@ void GeometryTileWorker::symbolDependenciesChanged() {
}
}
+void GeometryTileWorker::onGlyphsAvailable(GlyphPositionMap glyphs) {
+ glyphPositions = std::move(glyphs);
+ symbolDependenciesChanged(); // TODO: This is a clumsy way to join the "glyphs loaded" and "symbol dependencies changed" signals
+}
+
void GeometryTileWorker::coalesced() {
try {
switch (state) {
@@ -215,6 +216,8 @@ void GeometryTileWorker::redoLayout() {
std::unordered_map<std::string, std::shared_ptr<Bucket>> buckets;
auto featureIndex = std::make_unique<FeatureIndex>();
BucketParameters parameters { id, mode };
+ GlyphDependencies glyphDependencies;
+ glyphPositions.clear();
std::vector<std::vector<const Layer*>> groups = groupByLayout(*layers);
for (auto& group : groups) {
@@ -242,7 +245,7 @@ void GeometryTileWorker::redoLayout() {
if (leader.is<SymbolLayer>()) {
symbolLayoutMap.emplace(leader.getID(),
- leader.as<SymbolLayer>()->impl->createLayout(parameters, group, *geometryLayer));
+ leader.as<SymbolLayer>()->impl->createLayout(parameters, group, *geometryLayer, glyphDependencies));
} else {
const Filter& filter = leader.baseImpl->filter;
const std::string& sourceLayerID = leader.baseImpl->sourceLayer;
@@ -276,6 +279,10 @@ void GeometryTileWorker::redoLayout() {
symbolLayouts.push_back(std::move(it->second));
}
}
+
+ if (!glyphDependencies.empty()) {
+ parent.invoke(&GeometryTile::getGlyphs, std::move(glyphDependencies));
+ }
parent.invoke(&GeometryTile::onLayout, GeometryTile::LayoutResult {
std::move(buckets),
@@ -313,10 +320,9 @@ void GeometryTileWorker::attemptPlacement() {
}
if (symbolLayout->state == SymbolLayout::Pending) {
- if (symbolLayout->canPrepare(glyphAtlas)) {
+ if (symbolLayout->canPrepare(glyphPositions)) {
symbolLayout->state = SymbolLayout::Prepared;
- symbolLayout->prepare(reinterpret_cast<uintptr_t>(this),
- glyphAtlas);
+ symbolLayout->prepare(glyphPositions);
} else {
canPlace = false;
}
diff --git a/src/mbgl/tile/geometry_tile_worker.hpp b/src/mbgl/tile/geometry_tile_worker.hpp
index 91bf81a697..3a62203b10 100644
--- a/src/mbgl/tile/geometry_tile_worker.hpp
+++ b/src/mbgl/tile/geometry_tile_worker.hpp
@@ -2,6 +2,7 @@
#include <mbgl/map/mode.hpp>
#include <mbgl/tile/tile_id.hpp>
+#include <mbgl/text/glyph.hpp>
#include <mbgl/text/placement_config.hpp>
#include <mbgl/actor/actor_ref.hpp>
#include <mbgl/util/optional.hpp>
@@ -25,7 +26,6 @@ public:
GeometryTileWorker(ActorRef<GeometryTileWorker> self,
ActorRef<GeometryTile> parent,
OverscaledTileID,
- GlyphAtlas&,
const std::atomic<bool>&,
const MapMode);
~GeometryTileWorker();
@@ -34,6 +34,8 @@ public:
void setData(std::unique_ptr<const GeometryTileData>, uint64_t correlationID);
void setPlacementConfig(PlacementConfig, uint64_t correlationID);
void symbolDependenciesChanged();
+
+ void onGlyphsAvailable(GlyphPositionMap glyphs);
private:
void coalesce();
@@ -46,7 +48,6 @@ private:
ActorRef<GeometryTile> parent;
const OverscaledTileID id;
- GlyphAtlas& glyphAtlas;
const std::atomic<bool>& obsolete;
const MapMode mode;
@@ -66,6 +67,7 @@ private:
optional<PlacementConfig> placementConfig;
std::vector<std::unique_ptr<SymbolLayout>> symbolLayouts;
+ GlyphPositionMap glyphPositions;
};
} // namespace mbgl
diff --git a/src/mbgl/util/exclusive.hpp b/src/mbgl/util/exclusive.hpp
deleted file mode 100644
index 844588dc90..0000000000
--- a/src/mbgl/util/exclusive.hpp
+++ /dev/null
@@ -1,27 +0,0 @@
-#pragma once
-
-#include <memory>
-#include <mutex>
-
-
-namespace mbgl {
-namespace util {
-
-template <class T>
-class exclusive {
-public:
- exclusive(T* val, std::unique_ptr<std::lock_guard<std::mutex>> mtx) : ptr(val), lock(std::move(mtx)) {}
-
- T* operator->() { return ptr; }
- const T* operator->() const { return ptr; }
- T* operator*() { return ptr; }
- const T* operator*() const { return ptr; }
-
-private:
- T *ptr;
- std::unique_ptr<std::lock_guard<std::mutex>> lock;
-};
-
-
-} // end namespace util
-} // end namespace mbgl
diff --git a/test/text/glyph_atlas.test.cpp b/test/text/glyph_atlas.test.cpp
index 3679cabc1b..314773b215 100644
--- a/test/text/glyph_atlas.test.cpp
+++ b/test/text/glyph_atlas.test.cpp
@@ -11,12 +11,13 @@
using namespace mbgl;
-class GlyphAtlasTest {
+class GlyphAtlasTest : public GlyphRequestor {
public:
util::RunLoop loop;
StubFileSource fileSource;
StubStyleObserver observer;
GlyphAtlas glyphAtlas{ { 32, 32 }, fileSource };
+ GlyphPositionMap glyphPositions;
void run(const std::string& url, const FontStack& fontStack, const GlyphRangeSet& glyphRanges) {
// Squelch logging.
@@ -24,14 +25,30 @@ public:
glyphAtlas.setObserver(&observer);
glyphAtlas.setURL(url);
+ GlyphDependencies glyphDependencies;
+ for (const auto& range : glyphRanges) {
+ glyphDependencies[fontStack].insert(range.first);
+ }
+ glyphAtlas.getGlyphs(*this, glyphDependencies);
glyphAtlas.hasGlyphRanges(fontStack, glyphRanges);
loop.run();
}
+ void addGlyphs(GlyphRequestor& requestor, const GlyphDependencies& glyphDependencies) {
+ glyphAtlas.addGlyphs(requestor, glyphDependencies);
+ }
+
+ bool hasGlyphRanges(const FontStack& fontStack, const GlyphRangeSet& ranges) const {
+ return glyphAtlas.hasGlyphRanges(fontStack, ranges);
+ }
void end() {
loop.stop();
}
+
+ virtual void onGlyphsAvailable(GlyphPositionMap positions) {
+ glyphPositions = std::move(positions);
+ }
};
TEST(GlyphAtlas, LoadingSuccess) {
@@ -50,11 +67,11 @@ TEST(GlyphAtlas, LoadingSuccess) {
};
test.observer.glyphsLoaded = [&] (const FontStack&, const GlyphRange&) {
- if (!test.glyphAtlas.hasGlyphRanges({{"Test Stack"}}, {{0, 255}, {256, 511}}))
+ if (!test.hasGlyphRanges({{"Test Stack"}}, {{0, 255}, {256, 511}}))
return;
- auto glyphSet = test.glyphAtlas.getGlyphSet({{"Test Stack"}});
- ASSERT_FALSE(glyphSet->getSDFs().empty());
+ auto& glyphSet = test.glyphAtlas.getGlyphSet({{"Test Stack"}});
+ ASSERT_FALSE(glyphSet.getSDFs().empty());
test.end();
};
@@ -83,8 +100,8 @@ TEST(GlyphAtlas, LoadingFail) {
EXPECT_TRUE(error != nullptr);
EXPECT_EQ(util::toString(error), "Failed by the test case");
- ASSERT_TRUE(test.glyphAtlas.getGlyphSet({{"Test Stack"}})->getSDFs().empty());
- ASSERT_FALSE(test.glyphAtlas.hasGlyphRanges({{"Test Stack"}}, {{0, 255}}));
+ ASSERT_TRUE(test.glyphAtlas.getGlyphSet({{"Test Stack"}}).getSDFs().empty());
+ ASSERT_FALSE(test.hasGlyphRanges({{"Test Stack"}}, {{0, 255}}));
test.end();
};
@@ -111,8 +128,8 @@ TEST(GlyphAtlas, LoadingCorrupted) {
EXPECT_TRUE(error != nullptr);
EXPECT_EQ(util::toString(error), "unknown pbf field type exception");
- ASSERT_TRUE(test.glyphAtlas.getGlyphSet({{"Test Stack"}})->getSDFs().empty());
- ASSERT_FALSE(test.glyphAtlas.hasGlyphRanges({{"Test Stack"}}, {{0, 255}}));
+ ASSERT_TRUE(test.glyphAtlas.getGlyphSet({{"Test Stack"}}).getSDFs().empty());
+ ASSERT_FALSE(test.hasGlyphRanges({{"Test Stack"}}, {{0, 255}}));
test.end();
};
@@ -145,19 +162,20 @@ TEST(GlyphAtlas, InvalidSDFGlyph) {
const FontStack fontStack{ "Mock Font" };
GlyphAtlasTest test;
- GlyphPositions positions;
- auto glyphSet = test.glyphAtlas.getGlyphSet(fontStack);
- glyphSet->insert(66, SDFGlyph{ 66 /* ASCII 'B' */,
+ auto& glyphSet = test.glyphAtlas.getGlyphSet(fontStack);
+ glyphSet.insert(66, SDFGlyph{ 66 /* ASCII 'B' */,
AlphaImage({7, 7}), /* correct */
{ 1 /* width */, 1 /* height */, 0 /* left */, 0 /* top */,
0 /* advance */ } });
- glyphSet->insert(67, SDFGlyph{ 67 /* ASCII 'C' */,
+ glyphSet.insert(67, SDFGlyph{ 67 /* ASCII 'C' */,
AlphaImage({518, 8}), /* correct */
{ 512 /* width */, 2 /* height */, 0 /* left */, 0 /* top */,
0 /* advance */ } });
- test.glyphAtlas.addGlyphs(1, std::u16string{u"ABC"}, fontStack, glyphSet, positions);
+ GlyphDependencies glyphDependencies = {{fontStack, {'A','B','C'}}};
+ test.addGlyphs(test, glyphDependencies);
+ GlyphPositions positions = test.glyphPositions[fontStack];
ASSERT_EQ(2u, positions.size());
diff --git a/test/text/glyph_pbf.test.cpp b/test/text/glyph_pbf.test.cpp
index be3ca3359b..48b2d7a07c 100644
--- a/test/text/glyph_pbf.test.cpp
+++ b/test/text/glyph_pbf.test.cpp
@@ -44,7 +44,7 @@ TEST(GlyphPBF, Parsing) {
glyphAtlasObserver.glyphsLoaded = [&](const FontStack&, const GlyphRange&) {
loop.stop();
- const auto& sdfs = glyphAtlas.getGlyphSet(fontStack)->getSDFs();
+ const auto& sdfs = glyphAtlas.getGlyphSet(fontStack).getSDFs();
// The fake glyphs don't contain a glyph that has the ID 0; it only contains glyphs with
// undefined IDs, but the parser should remove them.