summaryrefslogtreecommitdiff
path: root/src/mbgl/text
diff options
context:
space:
mode:
authorChris Loer <chris.loer@gmail.com>2017-03-31 14:53:18 -0700
committerChris Loer <chris.loer@mapbox.com>2017-04-04 11:33:12 -0700
commit5cdf838a387cae446dba500ac49a1c5524bf7949 (patch)
tree3b438034a7842c36a7804096785fca1a6ad6fa80 /src/mbgl/text
parent64beba3accb0f2088b2e01fad710f915c81d99c7 (diff)
downloadqtlocation-mapboxgl-5cdf838a387cae446dba500ac49a1c5524bf7949.tar.gz
[core] De-mutex GlyphAtlas and SpriteAtlas
- Expose glyph and icon information to workers via message interface. - Glyph/SpriteAtlas track which tiles have outstanding requests and send messages to them when glyphs/icons become available. - Remove obsolete "updateSymbolDependentTiles" pathway - Symbol preparation for a tile now depends on all glyphs becoming available before it can start. - Start tracking individual icons needed for a tile, although we don't do anything with the information yet. - Introduce typedef for GlyphID
Diffstat (limited to 'src/mbgl/text')
-rw-r--r--src/mbgl/text/glyph.cpp2
-rw-r--r--src/mbgl/text/glyph.hpp19
-rw-r--r--src/mbgl/text/glyph_atlas.cpp245
-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.cpp288
-rw-r--r--src/mbgl/text/shaping.hpp14
10 files changed, 514 insertions, 437 deletions
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..ade2734ca9 100644
--- a/src/mbgl/text/glyph_atlas.cpp
+++ b/src/mbgl/text/glyph_atlas.cpp
@@ -22,81 +22,140 @@ 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;
+ GlyphRangeSet loadedRanges;
-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) {
+ loadedRanges.insert(getGlyphRange(glyphID));
+ 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, loadedRanges);
}
-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 +163,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 +188,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 +251,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..120f19accc 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, GlyphRangeSet) = 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 ea0dd123db..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, translate.y, 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);
- const float shiftY =
- (-verticalAlign * lineCount + 0.5) * lineHeight + ::round(translate.y);
-
- 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..aa760ea4fe 100644
--- a/src/mbgl/text/shaping.cpp
+++ b/src/mbgl/text/shaping.cpp
@@ -1,17 +1,297 @@
#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 {
PositionedIcon shapeIcon(const SpriteAtlasElement& image, const std::array<float, 2>& iconOffset, const float iconRotation) {
float dx = iconOffset[0];
float dy = iconOffset[1];
- float x1 = dx - image.spriteImage->getWidth() / 2.0f;
- float x2 = x1 + image.spriteImage->getWidth();
- float y1 = dy - image.spriteImage->getHeight() / 2.0f;
- float y2 = y1 + image.spriteImage->getHeight();
+ float x1 = dx - image.width/ 2.0f;
+ float x2 = x1 + image.width;
+ float y1 = dy - image.height / 2.0f;
+ float y2 = y1 + image.height;
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);
+ const float shiftY =
+ (-verticalAlign * lineCount + 0.5) * lineHeight + ::round(translate.y);
+
+ 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, translate.y, 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