diff options
Diffstat (limited to 'src/mbgl/text')
-rw-r--r-- | src/mbgl/text/collision_feature.cpp | 2 | ||||
-rw-r--r-- | src/mbgl/text/collision_feature.hpp | 2 | ||||
-rw-r--r-- | src/mbgl/text/collision_tile.hpp | 9 | ||||
-rw-r--r-- | src/mbgl/text/get_anchors.cpp | 2 | ||||
-rw-r--r-- | src/mbgl/text/glyph.hpp | 39 | ||||
-rw-r--r-- | src/mbgl/text/glyph_atlas.cpp | 285 | ||||
-rw-r--r-- | src/mbgl/text/glyph_atlas.hpp | 107 | ||||
-rw-r--r-- | src/mbgl/text/glyph_manager.cpp | 145 | ||||
-rw-r--r-- | src/mbgl/text/glyph_manager.hpp | 68 | ||||
-rw-r--r-- | src/mbgl/text/glyph_manager_observer.hpp (renamed from src/mbgl/text/glyph_atlas_observer.hpp) | 4 | ||||
-rw-r--r-- | src/mbgl/text/glyph_pbf.cpp | 10 | ||||
-rw-r--r-- | src/mbgl/text/glyph_pbf.hpp | 18 | ||||
-rw-r--r-- | src/mbgl/text/glyph_range.hpp | 4 | ||||
-rw-r--r-- | src/mbgl/text/quads.cpp | 36 | ||||
-rw-r--r-- | src/mbgl/text/quads.hpp | 6 | ||||
-rw-r--r-- | src/mbgl/text/shaping.cpp | 34 | ||||
-rw-r--r-- | src/mbgl/text/shaping.hpp | 17 |
17 files changed, 360 insertions, 428 deletions
diff --git a/src/mbgl/text/collision_feature.cpp b/src/mbgl/text/collision_feature.cpp index 885ba5c426..71d7cc74e0 100644 --- a/src/mbgl/text/collision_feature.cpp +++ b/src/mbgl/text/collision_feature.cpp @@ -71,7 +71,7 @@ void CollisionFeature::bboxifyLabel(const GeometryCoordinates& line, GeometryCoo p = line[index]; } while (anchorDistance > -labelLength / 2); - float segmentLength = util::dist<float>(line[index], line[index + 1]); + auto segmentLength = util::dist<float>(line[index], line[index + 1]); for (unsigned int i = 0; i < nBoxes; i++) { // the distance the box will be from the anchor diff --git a/src/mbgl/text/collision_feature.hpp b/src/mbgl/text/collision_feature.hpp index 006a47eb74..c94ec23513 100644 --- a/src/mbgl/text/collision_feature.hpp +++ b/src/mbgl/text/collision_feature.hpp @@ -34,7 +34,7 @@ public: class CollisionFeature { public: enum class AlignmentType : bool { - Straight = 0, + Straight = false, Curved }; diff --git a/src/mbgl/text/collision_tile.hpp b/src/mbgl/text/collision_tile.hpp index ea4324edaf..bdacbb7437 100644 --- a/src/mbgl/text/collision_tile.hpp +++ b/src/mbgl/text/collision_tile.hpp @@ -17,6 +17,7 @@ #pragma GCC diagnostic ignored "-Wshorten-64-to-32" #pragma GCC diagnostic ignored "-Wunused-local-typedefs" #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#pragma GCC diagnostic ignored "-Wmisleading-indentation" #include <boost/geometry.hpp> #include <boost/geometry/geometries/point.hpp> #include <boost/geometry/geometries/box.hpp> @@ -28,10 +29,10 @@ namespace mbgl { namespace bg = boost::geometry; namespace bgm = bg::model; namespace bgi = bg::index; -typedef bgm::point<float, 2, bg::cs::cartesian> CollisionPoint; -typedef bgm::box<CollisionPoint> Box; -typedef std::tuple<Box, CollisionBox, IndexedSubfeature> CollisionTreeBox; -typedef bgi::rtree<CollisionTreeBox, bgi::linear<16, 4>> Tree; +using CollisionPoint = bgm::point<float, 2, bg::cs::cartesian>; +using Box = bgm::box<CollisionPoint>; +using CollisionTreeBox = std::tuple<Box, CollisionBox, IndexedSubfeature>; +using Tree = bgi::rtree<CollisionTreeBox, bgi::linear<16, 4>>; class IndexedSubfeature; diff --git a/src/mbgl/text/get_anchors.cpp b/src/mbgl/text/get_anchors.cpp index 82702b20f0..d41faf2a71 100644 --- a/src/mbgl/text/get_anchors.cpp +++ b/src/mbgl/text/get_anchors.cpp @@ -34,7 +34,7 @@ static Anchors resample(const GeometryCoordinates& line, const GeometryCoordinate& a = *(it); const GeometryCoordinate& b = *(it + 1); - const float segmentDist = util::dist<float>(a, b); + const auto segmentDist = util::dist<float>(a, b); const float angle = util::angle_to(b, a); while (markedDistance + spacing < distance + segmentDist) { diff --git a/src/mbgl/text/glyph.hpp b/src/mbgl/text/glyph.hpp index 9cf39de840..b9eaedd302 100644 --- a/src/mbgl/text/glyph.hpp +++ b/src/mbgl/text/glyph.hpp @@ -5,6 +5,8 @@ #include <mbgl/util/rect.hpp> #include <mbgl/util/traits.hpp> #include <mbgl/util/optional.hpp> +#include <mbgl/util/immutable.hpp> +#include <mbgl/util/image.hpp> #include <cstdint> #include <vector> @@ -13,8 +15,8 @@ namespace mbgl { -typedef char16_t GlyphID; -typedef std::set<GlyphID> GlyphIDs; +using GlyphID = char16_t; +using GlyphIDs = std::set<GlyphID>; // Note: this only works for the BMP GlyphRange getGlyphRange(GlyphID glyph); @@ -35,13 +37,23 @@ inline bool operator==(const GlyphMetrics& lhs, const GlyphMetrics& rhs) { lhs.advance == rhs.advance; } -struct Glyph { - Rect<uint16_t> rect; +class Glyph { +public: + // We're using this value throughout the Mapbox GL ecosystem. If this is different, the glyphs + // also need to be reencoded. + static constexpr const uint8_t borderSize = 3; + + GlyphID id = 0; + + // A signed distance field of the glyph with a border (see above). + AlphaImage bitmap; + + // Glyph metrics GlyphMetrics metrics; }; -typedef std::map<GlyphID, optional<Glyph>> GlyphPositions; -typedef std::map<FontStack, GlyphPositions> GlyphPositionMap; +using Glyphs = std::map<GlyphID, optional<Immutable<Glyph>>>; +using GlyphMap = std::map<FontStack, Glyphs>; class PositionedGlyph { public: @@ -58,14 +70,14 @@ enum class WritingModeType : uint8_t; class Shaping { public: - explicit Shaping() : top(0), bottom(0), left(0), right(0) {} + explicit Shaping() = default; explicit Shaping(float x, float y, WritingModeType writingMode_) : top(y), bottom(y), left(x), right(x), writingMode(writingMode_) {} std::vector<PositionedGlyph> positionedGlyphs; - int32_t top; - int32_t bottom; - int32_t left; - int32_t right; + int32_t top = 0; + int32_t bottom = 0; + int32_t left = 0; + int32_t right = 0; WritingModeType writingMode; explicit operator bool() const { return !positionedGlyphs.empty(); } @@ -97,8 +109,7 @@ constexpr WritingModeType operator~(WritingModeType value) { return WritingModeType(~mbgl::underlying_type(value)); } -typedef std::map<FontStack,GlyphIDs> GlyphDependencies; -typedef std::map<FontStack,GlyphRangeSet> GlyphRangeDependencies; - +using GlyphDependencies = std::map<FontStack,GlyphIDs>; +using GlyphRangeDependencies = std::map<FontStack,GlyphRangeSet>; } // end namespace mbgl diff --git a/src/mbgl/text/glyph_atlas.cpp b/src/mbgl/text/glyph_atlas.cpp index 4feaab01f9..1b98ea36bf 100644 --- a/src/mbgl/text/glyph_atlas.cpp +++ b/src/mbgl/text/glyph_atlas.cpp @@ -1,262 +1,65 @@ #include <mbgl/text/glyph_atlas.hpp> -#include <mbgl/text/glyph_atlas_observer.hpp> -#include <mbgl/text/glyph_pbf.hpp> -#include <mbgl/gl/context.hpp> -#include <mbgl/util/logging.hpp> -#include <mbgl/util/platform.hpp> -#include <mbgl/storage/file_source.hpp> -#include <mbgl/storage/resource.hpp> -#include <mbgl/storage/response.hpp> -#include <cassert> -#include <algorithm> +#include <mapbox/shelf-pack.hpp> namespace mbgl { -static GlyphAtlasObserver nullObserver; +static constexpr uint32_t padding = 1; -GlyphAtlas::GlyphAtlas(const Size size, FileSource& fileSource_) - : fileSource(fileSource_), - observer(&nullObserver), - bin(size.width, size.height), - image(size), - dirty(true) { -} - -GlyphAtlas::~GlyphAtlas() = default; - -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; - Entry& entry = entries[fontStack]; - - const GlyphIDs& glyphIDs = dependency.second; - GlyphRangeSet ranges; - for (const auto& glyphID : glyphIDs) { - ranges.insert(getGlyphRange(glyphID)); - } - - for (const auto& range : ranges) { - auto it = entry.ranges.find(range); - if (it == entry.ranges.end() || !it->second.parsed) { - GlyphRequest& request = requestRange(entry, fontStack, range); - request.requestors[&requestor] = dependencies; - } - } - } - - // 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); - } -} - -GlyphAtlas::GlyphRequest& GlyphAtlas::requestRange(Entry& entry, const FontStack& fontStack, const GlyphRange& range) { - GlyphRequest& request = entry.ranges[range]; - - if (request.req) { - return request; - } - - request.req = fileSource.request(Resource::glyphs(glyphURL, fontStack, range), [this, fontStack, range](Response res) { - processResponse(res, fontStack, range); - }); +GlyphAtlas makeGlyphAtlas(const GlyphMap& glyphs) { + GlyphAtlas result; - return request; -} + mapbox::ShelfPack::ShelfPackOptions options; + options.autoResize = true; + mapbox::ShelfPack pack(0, 0, options); -void GlyphAtlas::processResponse(const Response& res, const FontStack& fontStack, const GlyphRange& range) { - if (res.error) { - observer->onGlyphsError(fontStack, range, std::make_exception_ptr(std::runtime_error(res.error->message))); - return; - } + for (const auto& glyphMapEntry : glyphs) { + const FontStack& fontStack = glyphMapEntry.first; + GlyphPositionMap& positions = result.positions[fontStack]; - if (res.notModified) { - return; - } + for (const auto& entry : glyphMapEntry.second) { + if (entry.second && (*entry.second)->bitmap.valid()) { + const Glyph& glyph = **entry.second; - Entry& entry = entries[fontStack]; - GlyphRequest& request = entry.ranges[range]; + const mapbox::Bin& bin = *pack.packOne(-1, + glyph.bitmap.size.width + 2 * padding, + glyph.bitmap.size.height + 2 * padding); - if (!res.noContent) { - std::vector<SDFGlyph> glyphs; - - try { - glyphs = parseGlyphPBF(range, *res.data); - } catch (...) { - observer->onGlyphsError(fontStack, range, std::current_exception()); - return; - } - - for (auto& glyph : glyphs) { - auto it = entry.glyphs.find(glyph.id); - if (it == entry.glyphs.end()) { - // Glyph doesn't exist yet. - entry.glyphs.emplace(glyph.id, GlyphValue { - std::move(glyph.bitmap), - std::move(glyph.metrics), - {}, {} + result.image.resize({ + static_cast<uint32_t>(pack.width()), + static_cast<uint32_t>(pack.height()) }); - } else if (it->second.metrics == glyph.metrics) { - if (it->second.bitmap != glyph.bitmap) { - // The actual bitmap was updated; this is unsupported. - Log::Warning(Event::Glyph, "Modified glyph changed bitmap represenation"); - } - // At least try to update it in case it's currently unused. - // If it is already used, we won't attempt to update the glyph atlas texture. - it->second.bitmap = std::move(glyph.bitmap); - } else { - // The metrics were updated; this is unsupported. - Log::Warning(Event::Glyph, "Modified glyph has different metrics"); - return; - } - } - } - - request.parsed = true; - - for (auto& pair : request.requestors) { - GlyphRequestor& requestor = *pair.first; - const std::shared_ptr<GlyphDependencies>& dependencies = pair.second; - if (dependencies.unique()) { - addGlyphs(requestor, *dependencies); - } - } - - request.requestors.clear(); - - observer->onGlyphsLoaded(fontStack, range); -} - -void GlyphAtlas::setObserver(GlyphAtlasObserver* observer_) { - observer = observer_ ? observer_ : &nullObserver; -} -void GlyphAtlas::addGlyphs(GlyphRequestor& requestor, const GlyphDependencies& glyphDependencies) { - GlyphPositionMap glyphPositions; - - for (const auto& dependency : glyphDependencies) { - const FontStack& fontStack = dependency.first; - const GlyphIDs& glyphIDs = dependency.second; - - GlyphPositions& positions = glyphPositions[fontStack]; - Entry& entry = entries[fontStack]; - - for (const auto& glyphID : glyphIDs) { - // Make a glyph position entry even if we didn't get an SDF for the glyph. During layout, - // an empty optional is treated as "loaded but nothing to show", wheras no entry in the - // positions map means "not loaded yet". - optional<Glyph>& glyph = positions[glyphID]; - - auto it = entry.glyphs.find(glyphID); - if (it == entry.glyphs.end()) - continue; - - it->second.ids.insert(&requestor); - - glyph = Glyph { - addGlyph(it->second), - it->second.metrics - }; - } - } - - requestor.onGlyphsAvailable(glyphPositions); -} - -Rect<uint16_t> GlyphAtlas::addGlyph(GlyphValue& value) { - // The glyph is already in this texture. - if (value.rect) { - return *value.rect; - } - - // We don't need to add glyphs without a bitmap (e.g. whitespace). - if (!value.bitmap.valid()) { - return {}; - } - - // Add a 1px border around every image. - const uint32_t padding = 1; - uint16_t width = value.bitmap.size.width + 2 * padding; - uint16_t height = value.bitmap.size.height + 2 * padding; - - // Increase to next number divisible by 4, but at least 1. - // This is so we can scale down the texture coordinates and pack them - // into 2 bytes rather than 4 bytes. - width += (4 - width % 4); - height += (4 - height % 4); - - Rect<uint16_t> rect = bin.allocate(width, height); - if (rect.w == 0) { - Log::Error(Event::OpenGL, "glyph bitmap overflow"); - return {}; - } - - AlphaImage::copy(value.bitmap, image, { 0, 0 }, { rect.x + padding, rect.y + padding }, value.bitmap.size); - value.rect = rect; - dirty = true; - - return rect; -} - -void GlyphAtlas::removeGlyphValues(GlyphRequestor& requestor, std::map<GlyphID, GlyphValue>& values) { - for (auto it = values.begin(); it != values.end(); it++) { - GlyphValue& value = it->second; - if (value.ids.erase(&requestor) && value.ids.empty() && value.rect) { - 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; - } + AlphaImage::copy(glyph.bitmap, + result.image, + { 0, 0 }, + { + bin.x + padding, + bin.y + padding + }, + glyph.bitmap.size); + + positions.emplace(glyph.id, + GlyphPosition { + Rect<uint16_t> { + static_cast<uint16_t>(bin.x), + static_cast<uint16_t>(bin.y), + static_cast<uint16_t>(bin.w), + static_cast<uint16_t>(bin.h) + }, + glyph.metrics + }); } - - bin.release(rect); - value.rect = {}; } } -} - -void GlyphAtlas::removePendingRanges(mbgl::GlyphRequestor& requestor, std::map<GlyphRange, GlyphRequest>& 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.glyphs); - removePendingRanges(requestor, entry.second.ranges); - } -} - -Size GlyphAtlas::getSize() const { - return image.size; -} - -void GlyphAtlas::upload(gl::Context& context, gl::TextureUnit unit) { - if (!texture) { - texture = context.createTexture(image, unit); - } else if (dirty) { - context.updateTexture(*texture, image, unit); - } - - dirty = false; -} + pack.shrink(); + result.image.resize({ + static_cast<uint32_t>(pack.width()), + static_cast<uint32_t>(pack.height()) + }); -void GlyphAtlas::bind(gl::Context& context, gl::TextureUnit unit) { - upload(context, unit); - context.bindTexture(*texture, unit, gl::TextureFilter::Linear); + return result; } } // namespace mbgl diff --git a/src/mbgl/text/glyph_atlas.hpp b/src/mbgl/text/glyph_atlas.hpp index ad9cf35adc..bb9115e4b4 100644 --- a/src/mbgl/text/glyph_atlas.hpp +++ b/src/mbgl/text/glyph_atlas.hpp @@ -1,110 +1,25 @@ #pragma once #include <mbgl/text/glyph.hpp> -#include <mbgl/text/glyph_atlas_observer.hpp> -#include <mbgl/text/glyph_range.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/work_queue.hpp> -#include <mbgl/util/image.hpp> -#include <mbgl/gl/texture.hpp> -#include <mbgl/gl/object.hpp> -#include <string> -#include <unordered_set> -#include <unordered_map> - -class GlyphAtlasTest; +#include <mapbox/shelf-pack.hpp> namespace mbgl { -class FileSource; -class AsyncRequest; -class Response; - -namespace gl { -class Context; -} // namespace gl - -class GlyphRequestor { -public: - virtual ~GlyphRequestor() = default; - virtual void onGlyphsAvailable(GlyphPositionMap) = 0; +struct GlyphPosition { + Rect<uint16_t> rect; + GlyphMetrics metrics; }; - -class GlyphAtlas : public util::noncopyable { -public: - GlyphAtlas(Size, FileSource&); - ~GlyphAtlas(); - - // 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&, GlyphDependencies); - void removeGlyphs(GlyphRequestor&); - - void setURL(const std::string& url) { - glyphURL = url; - } - - void setObserver(GlyphAtlasObserver*); - - // Binds the atlas texture to the GPU, and uploads data if it is out of date. - void bind(gl::Context&, gl::TextureUnit unit); - - // Uploads the texture to the GPU to be available when we need it. This is a lazy operation; - // the texture is only bound when the data is out of date (=dirty). - void upload(gl::Context&, gl::TextureUnit unit); - - Size getSize() const; -private: - FileSource& fileSource; - std::string glyphURL; +using GlyphPositionMap = std::map<GlyphID, GlyphPosition>; +using GlyphPositions = std::map<FontStack, GlyphPositionMap>; - struct GlyphValue { - AlphaImage bitmap; - GlyphMetrics metrics; - optional<Rect<uint16_t>> rect; - std::unordered_set<GlyphRequestor*> ids; - }; - - struct GlyphRequest { - bool parsed = false; - std::unique_ptr<AsyncRequest> req; - std::unordered_map<GlyphRequestor*, std::shared_ptr<GlyphDependencies>> requestors; - }; - - struct Entry { - std::map<GlyphRange, GlyphRequest> ranges; - std::map<GlyphID, GlyphValue> glyphs; - }; - - std::unordered_map<FontStack, Entry, FontStackHash> entries; - - GlyphRequest& requestRange(Entry&, const FontStack&, const GlyphRange&); - void processResponse(const Response&, const FontStack&, const GlyphRange&); - - void addGlyphs(GlyphRequestor&, const GlyphDependencies&); - Rect<uint16_t> addGlyph(GlyphValue&); - - void removeGlyphValues(GlyphRequestor&, std::map<GlyphID, GlyphValue>&); - void removePendingRanges(GlyphRequestor&, std::map<GlyphRange, GlyphRequest>&); - - GlyphAtlasObserver* observer = nullptr; - - BinPack<uint16_t> bin; +class GlyphAtlas { +public: AlphaImage image; - bool dirty; - mbgl::optional<gl::Texture> texture; + GlyphPositions positions; }; +GlyphAtlas makeGlyphAtlas(const GlyphMap&); + } // namespace mbgl diff --git a/src/mbgl/text/glyph_manager.cpp b/src/mbgl/text/glyph_manager.cpp new file mode 100644 index 0000000000..916d39ae62 --- /dev/null +++ b/src/mbgl/text/glyph_manager.cpp @@ -0,0 +1,145 @@ +#include <mbgl/text/glyph_manager.hpp> +#include <mbgl/text/glyph_manager_observer.hpp> +#include <mbgl/text/glyph_pbf.hpp> +#include <mbgl/storage/file_source.hpp> +#include <mbgl/storage/resource.hpp> +#include <mbgl/storage/response.hpp> + +namespace mbgl { + +static GlyphManagerObserver nullObserver; + +GlyphManager::GlyphManager(FileSource& fileSource_) + : fileSource(fileSource_), + observer(&nullObserver) { +} + +GlyphManager::~GlyphManager() = default; + +void GlyphManager::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; + Entry& entry = entries[fontStack]; + + const GlyphIDs& glyphIDs = dependency.second; + GlyphRangeSet ranges; + for (const auto& glyphID : glyphIDs) { + ranges.insert(getGlyphRange(glyphID)); + } + + for (const auto& range : ranges) { + auto it = entry.ranges.find(range); + if (it == entry.ranges.end() || !it->second.parsed) { + GlyphRequest& request = requestRange(entry, fontStack, range); + request.requestors[&requestor] = dependencies; + } + } + } + + // If the shared dependencies pointer is already unique, then all dependent glyph ranges + // have already been loaded. Send a notification immediately. + if (dependencies.unique()) { + notify(requestor, *dependencies); + } +} + +GlyphManager::GlyphRequest& GlyphManager::requestRange(Entry& entry, const FontStack& fontStack, const GlyphRange& range) { + GlyphRequest& request = entry.ranges[range]; + + if (request.req) { + return request; + } + + request.req = fileSource.request(Resource::glyphs(glyphURL, fontStack, range), [this, fontStack, range](Response res) { + processResponse(res, fontStack, range); + }); + + return request; +} + +void GlyphManager::processResponse(const Response& res, const FontStack& fontStack, const GlyphRange& range) { + if (res.error) { + observer->onGlyphsError(fontStack, range, std::make_exception_ptr(std::runtime_error(res.error->message))); + return; + } + + if (res.notModified) { + return; + } + + Entry& entry = entries[fontStack]; + GlyphRequest& request = entry.ranges[range]; + + if (!res.noContent) { + std::vector<Glyph> glyphs; + + try { + glyphs = parseGlyphPBF(range, *res.data); + } catch (...) { + observer->onGlyphsError(fontStack, range, std::current_exception()); + return; + } + + for (auto& glyph : glyphs) { + entry.glyphs.erase(glyph.id); + entry.glyphs.emplace(glyph.id, makeMutable<Glyph>(std::move(glyph))); + } + } + + request.parsed = true; + + for (auto& pair : request.requestors) { + GlyphRequestor& requestor = *pair.first; + const std::shared_ptr<GlyphDependencies>& dependencies = pair.second; + if (dependencies.unique()) { + notify(requestor, *dependencies); + } + } + + request.requestors.clear(); + + observer->onGlyphsLoaded(fontStack, range); +} + +void GlyphManager::setObserver(GlyphManagerObserver* observer_) { + observer = observer_ ? observer_ : &nullObserver; +} + +void GlyphManager::notify(GlyphRequestor& requestor, const GlyphDependencies& glyphDependencies) { + GlyphMap response; + + for (const auto& dependency : glyphDependencies) { + const FontStack& fontStack = dependency.first; + const GlyphIDs& glyphIDs = dependency.second; + + Glyphs& glyphs = response[fontStack]; + Entry& entry = entries[fontStack]; + + for (const auto& glyphID : glyphIDs) { + auto it = entry.glyphs.find(glyphID); + if (it != entry.glyphs.end()) { + glyphs.emplace(*it); + } else { + glyphs.emplace(glyphID, std::experimental::nullopt); + } + } + } + + requestor.onGlyphsAvailable(response); +} + +void GlyphManager::removeRequestor(GlyphRequestor& requestor) { + for (auto& entry : entries) { + for (auto& range : entry.second.ranges) { + range.second.requestors.erase(&requestor); + } + } +} + +} // namespace mbgl diff --git a/src/mbgl/text/glyph_manager.hpp b/src/mbgl/text/glyph_manager.hpp new file mode 100644 index 0000000000..00df079462 --- /dev/null +++ b/src/mbgl/text/glyph_manager.hpp @@ -0,0 +1,68 @@ +#pragma once + +#include <mbgl/text/glyph.hpp> +#include <mbgl/text/glyph_manager_observer.hpp> +#include <mbgl/text/glyph_range.hpp> +#include <mbgl/util/noncopyable.hpp> +#include <mbgl/util/font_stack.hpp> +#include <mbgl/util/immutable.hpp> + +#include <string> +#include <unordered_map> + +namespace mbgl { + +class FileSource; +class AsyncRequest; +class Response; + +class GlyphRequestor { +public: + virtual ~GlyphRequestor() = default; + virtual void onGlyphsAvailable(GlyphMap) = 0; +}; + +class GlyphManager : public util::noncopyable { +public: + GlyphManager(FileSource&); + ~GlyphManager(); + + // Workers send a `getGlyphs` message to the main thread once they have determined + // their `GlyphDependencies`. If all glyphs are already locally available, GlyphManager + // will provide them to the requestor immediately. Otherwise, it makes a request on the + // FileSource is made for each range neeed, and notifies the observer when all are + // complete. + void getGlyphs(GlyphRequestor&, GlyphDependencies); + void removeRequestor(GlyphRequestor&); + + void setURL(const std::string& url) { + glyphURL = url; + } + + void setObserver(GlyphManagerObserver*); + +private: + FileSource& fileSource; + std::string glyphURL; + + struct GlyphRequest { + bool parsed = false; + std::unique_ptr<AsyncRequest> req; + std::unordered_map<GlyphRequestor*, std::shared_ptr<GlyphDependencies>> requestors; + }; + + struct Entry { + std::map<GlyphRange, GlyphRequest> ranges; + std::map<GlyphID, Immutable<Glyph>> glyphs; + }; + + std::unordered_map<FontStack, Entry, FontStackHash> entries; + + GlyphRequest& requestRange(Entry&, const FontStack&, const GlyphRange&); + void processResponse(const Response&, const FontStack&, const GlyphRange&); + void notify(GlyphRequestor&, const GlyphDependencies&); + + GlyphManagerObserver* observer = nullptr; +}; + +} // namespace mbgl diff --git a/src/mbgl/text/glyph_atlas_observer.hpp b/src/mbgl/text/glyph_manager_observer.hpp index 9841017117..b8678e060a 100644 --- a/src/mbgl/text/glyph_atlas_observer.hpp +++ b/src/mbgl/text/glyph_manager_observer.hpp @@ -8,9 +8,9 @@ namespace mbgl { -class GlyphAtlasObserver { +class GlyphManagerObserver { public: - virtual ~GlyphAtlasObserver() = default; + virtual ~GlyphManagerObserver() = default; virtual void onGlyphsLoaded(const FontStack&, const GlyphRange&) {} virtual void onGlyphsError(const FontStack&, const GlyphRange&, std::exception_ptr) {} diff --git a/src/mbgl/text/glyph_pbf.cpp b/src/mbgl/text/glyph_pbf.cpp index 033f50fe9c..cfaf803f75 100644 --- a/src/mbgl/text/glyph_pbf.cpp +++ b/src/mbgl/text/glyph_pbf.cpp @@ -4,8 +4,8 @@ namespace mbgl { -std::vector<SDFGlyph> parseGlyphPBF(const GlyphRange& glyphRange, const std::string& data) { - std::vector<SDFGlyph> result; +std::vector<Glyph> parseGlyphPBF(const GlyphRange& glyphRange, const std::string& data) { + std::vector<Glyph> result; result.reserve(256); protozero::pbf_reader glyphs_pbf(data); @@ -15,7 +15,7 @@ std::vector<SDFGlyph> parseGlyphPBF(const GlyphRange& glyphRange, const std::str while (fontstack_pbf.next(3)) { auto glyph_pbf = fontstack_pbf.get_message(); - SDFGlyph glyph; + Glyph glyph; protozero::data_view glyphData; bool hasID = false, hasWidth = false, hasHeight = false, hasLeft = false, @@ -73,8 +73,8 @@ std::vector<SDFGlyph> parseGlyphPBF(const GlyphRange& glyphRange, const std::str // with the implicit border size, otherwise we expect there to be no bitmap at all. if (glyph.metrics.width && glyph.metrics.height) { const Size size { - glyph.metrics.width + 2 * SDFGlyph::borderSize, - glyph.metrics.height + 2 * SDFGlyph::borderSize + glyph.metrics.width + 2 * Glyph::borderSize, + glyph.metrics.height + 2 * Glyph::borderSize }; if (size.area() != glyphData.size()) { diff --git a/src/mbgl/text/glyph_pbf.hpp b/src/mbgl/text/glyph_pbf.hpp index 162aeed93a..28a28b4114 100644 --- a/src/mbgl/text/glyph_pbf.hpp +++ b/src/mbgl/text/glyph_pbf.hpp @@ -2,28 +2,12 @@ #include <mbgl/text/glyph.hpp> #include <mbgl/text/glyph_range.hpp> -#include <mbgl/util/image.hpp> #include <string> #include <vector> namespace mbgl { -class SDFGlyph { -public: - // We're using this value throughout the Mapbox GL ecosystem. If this is different, the glyphs - // also need to be reencoded. - static constexpr const uint8_t borderSize = 3; - - GlyphID id = 0; - - // A signed distance field of the glyph with a border (see above). - AlphaImage bitmap; - - // Glyph metrics - GlyphMetrics metrics; -}; - -std::vector<SDFGlyph> parseGlyphPBF(const GlyphRange&, const std::string& data); +std::vector<Glyph> parseGlyphPBF(const GlyphRange&, const std::string& data); } // namespace mbgl diff --git a/src/mbgl/text/glyph_range.hpp b/src/mbgl/text/glyph_range.hpp index dd39e092b7..74afb73dfc 100644 --- a/src/mbgl/text/glyph_range.hpp +++ b/src/mbgl/text/glyph_range.hpp @@ -7,7 +7,7 @@ namespace mbgl { -typedef std::pair<uint16_t, uint16_t> GlyphRange; +using GlyphRange = std::pair<uint16_t, uint16_t>; struct GlyphRangeHash { std::size_t operator()(const GlyphRange& glyphRange) const { @@ -15,7 +15,7 @@ struct GlyphRangeHash { } }; -typedef std::unordered_set<GlyphRange, GlyphRangeHash> GlyphRangeSet; +using GlyphRangeSet = std::unordered_set<GlyphRange, GlyphRangeHash>; constexpr uint32_t GLYPHS_PER_GLYPH_RANGE = 256; constexpr uint32_t GLYPH_RANGES_PER_FONT_STACK = 256; diff --git a/src/mbgl/text/quads.cpp b/src/mbgl/text/quads.cpp index e1a9699835..ab10c5a6b7 100644 --- a/src/mbgl/text/quads.cpp +++ b/src/mbgl/text/quads.cpp @@ -22,13 +22,17 @@ SymbolQuad getIconQuad(const Anchor& anchor, const float layoutTextSize, const style::SymbolPlacementType placement, const Shaping& shapedText) { - auto image = *shapedIcon.image(); + const ImagePosition& image = shapedIcon.image(); + // If you have a 10px icon that isn't perfectly aligned to the pixel grid it will cover 11 actual + // pixels. The quad needs to be padded to account for this, otherwise they'll look slightly clipped + // on one edge in some cases. const float border = 1.0; - auto left = shapedIcon.left() - border; - auto right = left + image.pos.w / image.relativePixelRatio; - auto top = shapedIcon.top() - border; - auto bottom = top + image.pos.h / image.relativePixelRatio; + + float top = shapedIcon.top() - border / image.pixelRatio; + float left = shapedIcon.left() - border / image.pixelRatio; + float bottom = shapedIcon.bottom() + border / image.pixelRatio; + float right = shapedIcon.right() + border / image.pixelRatio; Point<float> tl; Point<float> tr; Point<float> br; @@ -92,7 +96,15 @@ SymbolQuad getIconQuad(const Anchor& anchor, br = util::matrixMultiply(matrix, br); } - return SymbolQuad { tl, tr, bl, br, image.pos, 0, 0, anchor.point, globalMinScale, std::numeric_limits<float>::infinity(), shapedText.writingMode }; + // Icon quad is padded, so texture coordinates also need to be padded. + Rect<uint16_t> textureRect { + static_cast<uint16_t>(image.textureRect.x - border), + static_cast<uint16_t>(image.textureRect.y - border), + static_cast<uint16_t>(image.textureRect.w + border * 2), + static_cast<uint16_t>(image.textureRect.h + border * 2) + }; + + return SymbolQuad { tl, tr, bl, br, textureRect, 0, 0, anchor.point, globalMinScale, std::numeric_limits<float>::infinity(), shapedText.writingMode }; } struct GlyphInstance { @@ -108,7 +120,7 @@ struct GlyphInstance { const float angle = 0.0f; }; -typedef std::vector<GlyphInstance> GlyphInstances; +using GlyphInstances = std::vector<GlyphInstance>; struct VirtualSegment { Point<float> anchor; @@ -177,7 +189,7 @@ inline Point<float> getVirtualSegmentAnchor(const Point<float>& segmentBegin, co inline float getMinScaleForSegment(const float glyphDistanceFromAnchor, const Point<float>& segmentAnchor, const Point<float>& segmentEnd) { - const float distanceFromAnchorToEnd = util::dist<float>(segmentAnchor, segmentEnd); + const auto distanceFromAnchorToEnd = util::dist<float>(segmentAnchor, segmentEnd); return glyphDistanceFromAnchor / distanceFromAnchorToEnd; } @@ -295,18 +307,18 @@ SymbolQuads getGlyphQuads(Anchor& anchor, const GeometryCoordinates& line, const SymbolLayoutProperties::Evaluated& layout, const style::SymbolPlacementType placement, - const GlyphPositions& face) { + const GlyphPositionMap& positions) { const float textRotate = layout.get<TextRotate>() * util::DEG2RAD; const bool keepUpright = layout.get<TextKeepUpright>(); SymbolQuads quads; for (const PositionedGlyph &positionedGlyph: shapedText.positionedGlyphs) { - auto face_it = face.find(positionedGlyph.glyph); - if (face_it == face.end() || !face_it->second || !(*face_it->second).rect.hasArea()) + auto positionsIt = positions.find(positionedGlyph.glyph); + if (positionsIt == positions.end()) continue; - const Glyph& glyph = *face_it->second; + const GlyphPosition& glyph = positionsIt->second; const Rect<uint16_t>& rect = glyph.rect; const float centerX = (positionedGlyph.x + glyph.metrics.advance / 2.0f) * boxScale; diff --git a/src/mbgl/text/quads.hpp b/src/mbgl/text/quads.hpp index 333000627b..b29f6b0ad3 100644 --- a/src/mbgl/text/quads.hpp +++ b/src/mbgl/text/quads.hpp @@ -1,6 +1,6 @@ #pragma once -#include <mbgl/text/glyph.hpp> +#include <mbgl/text/glyph_atlas.hpp> #include <mbgl/style/types.hpp> #include <mbgl/style/layers/symbol_layer_properties.hpp> #include <mbgl/tile/geometry_tile_data.hpp> @@ -49,7 +49,7 @@ public: WritingModeType writingMode; }; -typedef std::vector<SymbolQuad> SymbolQuads; +using SymbolQuads = std::vector<SymbolQuad>; SymbolQuad getIconQuad(const Anchor& anchor, const PositionedIcon& shapedIcon, @@ -65,6 +65,6 @@ SymbolQuads getGlyphQuads(Anchor& anchor, const GeometryCoordinates& line, const style::SymbolLayoutProperties::Evaluated&, style::SymbolPlacementType placement, - const GlyphPositions& face); + const GlyphPositionMap& positions); } // namespace mbgl diff --git a/src/mbgl/text/shaping.cpp b/src/mbgl/text/shaping.cpp index 78aa142c61..338abe2e43 100644 --- a/src/mbgl/text/shaping.cpp +++ b/src/mbgl/text/shaping.cpp @@ -10,19 +10,15 @@ namespace mbgl { -optional<PositionedIcon> PositionedIcon::shapeIcon(const SpriteAtlasElement& image, const std::array<float, 2>& iconOffset, const float iconRotation) { - if (!image.pos.hasArea()) { - return {}; - } - +PositionedIcon PositionedIcon::shapeIcon(const ImagePosition& image, const std::array<float, 2>& iconOffset, const float iconRotation) { float dx = iconOffset[0]; float dy = iconOffset[1]; - float x1 = dx - image.width/ 2.0f; - float x2 = x1 + image.width; - float y1 = dy - image.height / 2.0f; - float y2 = y1 + image.height; + float x1 = dx - image.displaySize()[0] / 2.0f; + float x2 = x1 + image.displaySize()[0]; + float y1 = dy - image.displaySize()[1] / 2.0f; + float y2 = y1 + image.displaySize()[1]; - return { PositionedIcon { image, y1, y2, x1, x2, iconRotation } }; + return PositionedIcon { image, y1, y2, x1, x2, iconRotation }; } void align(Shaping& shaping, @@ -46,7 +42,7 @@ void align(Shaping& shaping, // justify left = 0, right = 1, center = .5 void justifyLine(std::vector<PositionedGlyph>& positionedGlyphs, - const GlyphPositions& glyphs, + const Glyphs& glyphs, std::size_t start, std::size_t end, float justify) { @@ -57,7 +53,7 @@ void justifyLine(std::vector<PositionedGlyph>& positionedGlyphs, PositionedGlyph& glyph = positionedGlyphs[end]; auto it = glyphs.find(glyph.glyph); if (it != glyphs.end() && it->second) { - const uint32_t lastAdvance = it->second->metrics.advance; + 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++) { @@ -69,13 +65,13 @@ void justifyLine(std::vector<PositionedGlyph>& positionedGlyphs, float determineAverageLineWidth(const std::u16string& logicalInput, const float spacing, float maxWidth, - const GlyphPositions& glyphs) { + const Glyphs& glyphs) { float totalWidth = 0; for (char16_t chr : logicalInput) { auto it = glyphs.find(chr); if (it != glyphs.end() && it->second) { - totalWidth += it->second->metrics.advance + spacing; + totalWidth += (*it->second)->metrics.advance + spacing; } } @@ -168,7 +164,7 @@ std::set<std::size_t> determineLineBreaks(const std::u16string& logicalInput, const float spacing, float maxWidth, const WritingModeType writingMode, - const GlyphPositions& glyphs) { + const Glyphs& glyphs) { if (!maxWidth || writingMode != WritingModeType::Horizontal) { return {}; } @@ -186,7 +182,7 @@ std::set<std::size_t> determineLineBreaks(const std::u16string& logicalInput, const char16_t codePoint = logicalInput[i]; auto it = glyphs.find(codePoint); if (it != glyphs.end() && it->second && !boost::algorithm::is_any_of(u" \t\n\v\f\r")(codePoint)) { - currentX += it->second->metrics.advance + spacing; + currentX += (*it->second)->metrics.advance + spacing; } // Ideographic characters, spaces, and word-breaking punctuation that often appear without @@ -212,7 +208,7 @@ void shapeLines(Shaping& shaping, const Point<float>& translate, const float verticalHeight, const WritingModeType writingMode, - const GlyphPositions& glyphs) { + const Glyphs& glyphs) { // the y offset *should* be part of the font metadata const int32_t yOffset = -17; @@ -238,7 +234,7 @@ void shapeLines(Shaping& shaping, continue; } - const Glyph& glyph = *it->second; + const Glyph& glyph = **it->second; if (writingMode == WritingModeType::Horizontal || !util::i18n::hasUprightVerticalOrientation(chr)) { shaping.positionedGlyphs.emplace_back(chr, x, y, 0); @@ -284,7 +280,7 @@ const Shaping getShaping(const std::u16string& logicalInput, const float verticalHeight, const WritingModeType writingMode, BiDi& bidi, - const GlyphPositions& glyphs) { + const Glyphs& glyphs) { Shaping shaping(translate.x, translate.y, writingMode); std::vector<std::u16string> reorderedLines = diff --git a/src/mbgl/text/shaping.hpp b/src/mbgl/text/shaping.hpp index b7eee5a5db..ca475e2a6c 100644 --- a/src/mbgl/text/shaping.hpp +++ b/src/mbgl/text/shaping.hpp @@ -1,32 +1,29 @@ #pragma once #include <mbgl/text/glyph.hpp> -#include <mbgl/sprite/sprite_atlas.hpp> -#include <mbgl/style/image.hpp> -#include <mbgl/util/optional.hpp> +#include <mbgl/renderer/image_atlas.hpp> namespace mbgl { -class SpriteAtlasElement; class SymbolFeature; class BiDi; class PositionedIcon { private: - PositionedIcon(const SpriteAtlasElement& image_, + PositionedIcon(ImagePosition image_, float top_, float bottom_, float left_, float right_, float angle_) - : _image(image_), + : _image(std::move(image_)), _top(top_), _bottom(bottom_), _left(left_), _right(right_), _angle(angle_) {} - optional<SpriteAtlasElement> _image; + ImagePosition _image; float _top; float _bottom; float _left; @@ -34,9 +31,9 @@ private: float _angle; public: - static optional<PositionedIcon> shapeIcon(const class SpriteAtlasElement&, const std::array<float, 2>& iconOffset, const float iconRotation); + static PositionedIcon shapeIcon(const ImagePosition&, const std::array<float, 2>& iconOffset, const float iconRotation); - optional<class SpriteAtlasElement> image() const { return _image; } + const ImagePosition& image() const { return _image; } float top() const { return _top; } float bottom() const { return _bottom; } float left() const { return _left; } @@ -55,6 +52,6 @@ const Shaping getShaping(const std::u16string& string, float verticalHeight, const WritingModeType, BiDi& bidi, - const GlyphPositions& glyphs); + const Glyphs& glyphs); } // namespace mbgl |