diff options
-rw-r--r-- | bin/style.js | 19 | ||||
-rw-r--r-- | include/llmr/geometry/glyph_atlas.hpp | 3 | ||||
-rw-r--r-- | include/llmr/map/map.hpp | 3 | ||||
-rw-r--r-- | include/llmr/map/tile_parser.hpp | 8 | ||||
-rw-r--r-- | include/llmr/platform/platform.hpp | 4 | ||||
-rw-r--r-- | include/llmr/renderer/text_bucket.hpp | 3 | ||||
-rw-r--r-- | include/llmr/text/glyph.hpp | 5 | ||||
-rw-r--r-- | include/llmr/text/glyph_store.hpp | 80 | ||||
-rw-r--r-- | include/llmr/text/placement.hpp | 4 | ||||
-rw-r--r-- | include/llmr/util/constants.hpp | 5 | ||||
-rw-r--r-- | include/llmr/util/utf.hpp | 45 | ||||
-rw-r--r-- | proto/glyphs.proto | 33 | ||||
-rwxr-xr-x | setup-libraries.sh | 4 | ||||
-rw-r--r-- | src/geometry/glyph_atlas.cpp | 2 | ||||
-rw-r--r-- | src/map/map.cpp | 2 | ||||
-rw-r--r-- | src/map/tile_parser.cpp | 107 | ||||
-rw-r--r-- | src/map/vector_tile_data.cpp | 2 | ||||
-rw-r--r-- | src/platform/request.cpp | 34 | ||||
-rw-r--r-- | src/renderer/text_bucket.cpp | 16 | ||||
-rw-r--r-- | src/text/glyph.cpp | 17 | ||||
-rw-r--r-- | src/text/glyph_store.cpp | 185 | ||||
-rw-r--r-- | src/text/placement.cpp | 33 | ||||
-rw-r--r-- | src/util/constants.cpp | 5 |
23 files changed, 540 insertions, 79 deletions
diff --git a/bin/style.js b/bin/style.js index a3c312f541..5538837288 100644 --- a/bin/style.js +++ b/bin/style.js @@ -267,6 +267,7 @@ module.exports = { "value": 10, "path": "curve", "text_field": "ele", + "font": "Open Sans Regular, Arial Unicode MS Regular", "fontSize": 10, "feature_type": "line", "type": "text" @@ -648,6 +649,7 @@ module.exports = { "layer": "country_label", "text_field": "name", "path": "horizontal", + "font": "Open Sans Regular, Arial Unicode MS Regular", "fontSize": 24, "feature_type": "point", "type": "text" @@ -664,6 +666,7 @@ module.exports = { "type": "text", "text_field": "name", "path": "curve", + "font": "Open Sans Regular, Arial Unicode MS Regular", "fontSize": 16 }, "marine_label_point_1": { @@ -675,6 +678,7 @@ module.exports = { "path": "horizontal", "field": "labelrank", "value": 1, + "font": "Open Sans Regular, Arial Unicode MS Regular", "fontSize": 30 }, "marine_label_point_2": { @@ -686,6 +690,7 @@ module.exports = { "path": "horizontal", "field": "labelrank", "value": 2, + "font": "Open Sans Regular, Arial Unicode MS Regular", "fontSize": 24 }, "marine_label_point_other": { @@ -697,6 +702,7 @@ module.exports = { "path": "horizontal", "field": "labelrank", "value": [3,4,5,6], + "font": "Open Sans Regular, Arial Unicode MS Regular", "fontSize": 18 }, "state_label": { @@ -704,6 +710,7 @@ module.exports = { "layer": "state_label", "text_field": "name", "path": "horizontal", + "font": "Open Sans Regular, Arial Unicode MS Regular", "fontSize": 16, "feature_type": "point", "type": "text", @@ -716,6 +723,7 @@ module.exports = { "value": "city", "text_field": "name", "path": "horizontal", + "font": "Open Sans Semibold, Arial Unicode MS Regular", "fontSize": 20, "feature_type": "point", "type": "text" @@ -727,6 +735,7 @@ module.exports = { "value": "town", "text_field": "name", "path": "horizontal", + "font": "Open Sans Semibold, Arial Unicode MS Regular", "fontSize": 24, "feature_type": "point", "type": "text" @@ -738,6 +747,7 @@ module.exports = { "value": "village", "text_field": "name", "path": "horizontal", + "font": "Open Sans Semibold, Arial Unicode MS Regular", "fontSize": 22, "feature_type": "point", "type": "text" @@ -753,6 +763,7 @@ module.exports = { ], "text_field": "name", "path": "horizontal", + "font": "Open Sans Semibold, Arial Unicode MS Regular", "fontSize": 18, "feature_type": "point", "type": "text" @@ -765,6 +776,7 @@ module.exports = { "text_field": "name", "path": "curve", "padding": 2, + "font": "Open Sans Regular, Arial Unicode MS Regular", "fontSize": 18, "feature_type": "line", "type": "text", @@ -778,6 +790,7 @@ module.exports = { "text_field": "name", "path": "curve", "padding": 2, + "font": "Open Sans Regular, Arial Unicode MS Regular", "fontSize": 16, "feature_type": "line", "type": "text", @@ -791,6 +804,7 @@ module.exports = { "text_field": "name", "path": "curve", "padding": 2, + "font": "Open Sans Regular, Arial Unicode MS Regular", "fontSize": 14, "feature_type": "line", "type": "text", @@ -801,6 +815,7 @@ module.exports = { "layer": "water_label", "text_field": "name", "path": "horizontal", + "font": "Open Sans Regular, Arial Unicode MS Regular", "fontSize": 12, "feature_type": "point", "type": "text" @@ -810,6 +825,7 @@ module.exports = { "layer": "waterway_label", "text_field": "name", "path": "curve", + "font": "Open Sans Regular, Arial Unicode MS Regular", "fontSize": 12, "textMinDistance": 15, "feature_type": "line", @@ -862,6 +878,7 @@ module.exports = { "text_field": "name", "path": "horizontal", "padding": 2, + "font": "Open Sans Regular, Arial Unicode MS Regular", "fontSize": 11, "feature_type": "point", "type": "text" @@ -874,6 +891,7 @@ module.exports = { "text_field": "name", "path": "horizontal", "padding": 2, + "font": "Open Sans Regular, Arial Unicode MS Regular", "fontSize": 10, "feature_type": "point", "type": "text" @@ -886,6 +904,7 @@ module.exports = { "text_field": "name", "path": "horizontal", "padding": 2, + "font": "Open Sans Regular, Arial Unicode MS Regular", "fontSize": 10, "feature_type": "point", "type": "text" diff --git a/include/llmr/geometry/glyph_atlas.hpp b/include/llmr/geometry/glyph_atlas.hpp index 312572f5ef..5bcd573f56 100644 --- a/include/llmr/geometry/glyph_atlas.hpp +++ b/include/llmr/geometry/glyph_atlas.hpp @@ -2,6 +2,7 @@ #define LLMR_GEOMETRY_GLYPH_ATLAS #include <llmr/geometry/binpack.hpp> +#include <llmr/text/glyph_store.hpp> #include <string> #include <set> @@ -30,7 +31,7 @@ public: Rect<uint16_t> addGlyph(uint64_t tile_id, const std::string& face_name, - const VectorTileGlyph& glyph); + const SDFGlyph& glyph); void removeGlyphs(uint64_t tile_id); void bind(); diff --git a/include/llmr/map/map.hpp b/include/llmr/map/map.hpp index ab1738f961..49050540c9 100644 --- a/include/llmr/map/map.hpp +++ b/include/llmr/map/map.hpp @@ -8,6 +8,7 @@ #include <llmr/map/transform.hpp> #include <llmr/style/style.hpp> #include <llmr/geometry/glyph_atlas.hpp> +#include <llmr/text/glyph_store.hpp> #include <llmr/renderer/painter.hpp> #include <llmr/util/noncopyable.hpp> #include <llmr/util/texturepool.hpp> @@ -98,6 +99,7 @@ public: inline const TransformState &getState() const { return state; } inline const Style &getStyle() const { return style; } inline GlyphAtlas &getGlyphAtlas() { return glyphAtlas; } + inline GlyphStore &getGlyphStore() { return glyphStore; } inline SpriteAtlas &getSpriteAtlas() { return spriteAtlas; } inline uv_loop_t *getLoop() { return loop; } inline time getAnimationTime() const { return animationTime; } @@ -152,6 +154,7 @@ private: Texturepool texturepool; Style style; GlyphAtlas glyphAtlas; + GlyphStore glyphStore; SpriteAtlas spriteAtlas; Painter painter; diff --git a/include/llmr/map/tile_parser.hpp b/include/llmr/map/tile_parser.hpp index 86545e4f76..5785c29b3a 100644 --- a/include/llmr/map/tile_parser.hpp +++ b/include/llmr/map/tile_parser.hpp @@ -4,11 +4,15 @@ #include <llmr/map/vector_tile_data.hpp> #include <llmr/map/vector_tile.hpp> #include <llmr/text/placement.hpp> +#include <llmr/text/glyph_store.hpp> +#include <llmr/text/glyph.hpp> +#include <llmr/util/utf.hpp> namespace llmr { class Style; class GlyphAtlas; +class GlyphStore; class SpriteAtlas; class LayerDescription; @@ -16,12 +20,13 @@ class Bucket; class TileParser { public: - TileParser(const std::string& data, VectorTileData& tile, const Style& style, GlyphAtlas& glyphAtlas, SpriteAtlas &spriteAtlas); + TileParser(const std::string& data, VectorTileData& tile, const Style& style, GlyphAtlas& glyphAtlas, GlyphStore &glyphStore, SpriteAtlas &spriteAtlas); private: bool obsolete() const; void parseGlyphs(); void parseStyleLayers(const std::vector<LayerDescription>& layers); + void addGlyph(uint64_t tileid, const std::string stackname, const std::u32string &string, const FontStack &fontStack, GlyphAtlas &glyphAtlas, GlyphPositions &face); std::unique_ptr<Bucket> createBucket(const BucketDescription& bucket_desc); std::unique_ptr<Bucket> createFillBucket(const VectorTileLayer& layer, const BucketDescription& bucket_desc); std::unique_ptr<Bucket> createLineBucket(const VectorTileLayer& layer, const BucketDescription& bucket_desc); @@ -35,6 +40,7 @@ private: VectorTileData& tile; const Style& style; GlyphAtlas& glyphAtlas; + GlyphStore &glyphStore; SpriteAtlas &spriteAtlas; Faces faces; Placement placement; diff --git a/include/llmr/platform/platform.hpp b/include/llmr/platform/platform.hpp index 972e7db6ba..d516378c2a 100644 --- a/include/llmr/platform/platform.hpp +++ b/include/llmr/platform/platform.hpp @@ -25,9 +25,11 @@ struct Response { // Makes an HTTP request of a URL, preferrably on a background thread, and calls a function with the // results in the original thread (which runs the libuv loop). +// If the loop pointer is NULL, the callback function will be called on an arbitrary thread. // Returns a cancellable request. std::shared_ptr<Request> request_http(const std::string &url, - std::function<void(Response *)> callback, uv_loop_t *loop); + std::function<void(Response *)> callback, + uv_loop_t *loop = nullptr); // Cancels an HTTP request. void cancel_request_http(const std::shared_ptr<Request> &req); diff --git a/include/llmr/renderer/text_bucket.hpp b/include/llmr/renderer/text_bucket.hpp index 2b73aa74da..ad282bdd0b 100644 --- a/include/llmr/renderer/text_bucket.hpp +++ b/include/llmr/renderer/text_bucket.hpp @@ -7,6 +7,7 @@ #include <llmr/geometry/elements_buffer.hpp> #include <llmr/map/vector_tile.hpp> #include <llmr/text/types.hpp> +#include <llmr/text/glyph.hpp> #include <memory> #include <map> #include <vector> @@ -36,7 +37,7 @@ public: PlacementRange placementRange, float zoom); void addFeature(const VectorTileFeature &feature, - const IndexedFaces &faces, + const GlyphPositions &face, const std::map<Value, Shaping> &shapings); void drawGlyphs(TextShader &shader); diff --git a/include/llmr/text/glyph.hpp b/include/llmr/text/glyph.hpp index 91531e24f3..e6138e712e 100644 --- a/include/llmr/text/glyph.hpp +++ b/include/llmr/text/glyph.hpp @@ -9,6 +9,11 @@ namespace llmr { +typedef std::pair<uint16_t, uint16_t> GlyphRange; + +// Note: this only works for the BMP +GlyphRange getGlyphRange(uint32_t glyph); + struct GlyphMetrics { operator bool() const { return width == 0 && height == 0 && advance == 0; diff --git a/include/llmr/text/glyph_store.hpp b/include/llmr/text/glyph_store.hpp new file mode 100644 index 0000000000..eb5d6038f1 --- /dev/null +++ b/include/llmr/text/glyph_store.hpp @@ -0,0 +1,80 @@ +#ifndef LLMR_TEXT_GLYPH_STORE +#define LLMR_TEXT_GLYPH_STORE + +#include <llmr/text/glyph.hpp> +#include <llmr/util/pbf.hpp> + +#include <cstdint> +#include <vector> +#include <future> +#include <map> +#include <set> +#include <unordered_map> + +namespace llmr { + + +class SDFGlyph { +public: + uint32_t id = 0; + + // A signed distance field of the glyph with a border of 3 pixels. + std::string bitmap; + + // Glyph metrics + GlyphMetrics metrics; +}; + +class FontStack { +public: + void insert(uint32_t id, const SDFGlyph &glyph); + const std::map<uint32_t, GlyphMetrics> &getMetrics() const; + const std::map<uint32_t, SDFGlyph> &getSDFs() const; + const Shaping getShaping(const std::u32string &string) const; + +private: + std::map<uint32_t, std::string> bitmaps; + std::map<uint32_t, GlyphMetrics> metrics; + std::map<uint32_t, SDFGlyph> sdfs; + mutable std::mutex mtx; +}; + +class GlyphPBF { +public: + GlyphPBF(const std::string &fontStack, GlyphRange glyphRange); + + void parse(FontStack &stack); + + std::shared_future<GlyphPBF &> getFuture(); + +private: + std::string data; + std::promise<GlyphPBF &> promise; + std::shared_future<GlyphPBF &> future; + std::mutex mtx; +}; + +// Manages Glyphrange PBF loading. +class GlyphStore { +public: + // Block until all specified GlyphRanges of the specified font stack are loaded. + void waitForGlyphRanges(const std::string &fontStack, const std::set<GlyphRange> &glyphRanges); + + FontStack &getFontStack(const std::string &fontStack); + +private: + // Loads an individual glyph range from the font stack and adds it to rangeSets + std::shared_future<GlyphPBF &> loadGlyphRange(const std::string &fontStack, std::map<GlyphRange, std::unique_ptr<GlyphPBF>> &rangeSets, GlyphRange range); + + FontStack &createFontStack(const std::string &fontStack); + +private: + std::unordered_map<std::string, std::map<GlyphRange, std::unique_ptr<GlyphPBF>>> ranges; + std::unordered_map<std::string, std::unique_ptr<FontStack>> stacks; + std::mutex mtx; +}; + + +} + +#endif diff --git a/include/llmr/text/placement.hpp b/include/llmr/text/placement.hpp index 8ec7df5e4f..87c47345ec 100644 --- a/include/llmr/text/placement.hpp +++ b/include/llmr/text/placement.hpp @@ -18,9 +18,9 @@ public: void addFeature(TextBucket &bucket, const std::vector<Coordinate> &line, const BucketGeometryDescription &info, - const IndexedFaces &faces, + const GlyphPositions &face, const Shaping &shaping); - float measureText(const IndexedFaces &faces, + float measureText(const GlyphPositions &face, const Shaping &shaping); private: diff --git a/include/llmr/util/constants.hpp b/include/llmr/util/constants.hpp index 70befcb379..f315e5171b 100644 --- a/include/llmr/util/constants.hpp +++ b/include/llmr/util/constants.hpp @@ -19,6 +19,11 @@ extern const bool styleParseWarnings; extern const bool spriteWarnings; extern const bool renderWarnings; extern const bool renderTree; +extern const bool labelTextMissingWarning; +extern const bool missingFontStackWarning; +extern const bool missingFontFaceWarning; +extern const bool glyphWarning; +extern const bool shapingWarning; } diff --git a/include/llmr/util/utf.hpp b/include/llmr/util/utf.hpp new file mode 100644 index 0000000000..92da93f018 --- /dev/null +++ b/include/llmr/util/utf.hpp @@ -0,0 +1,45 @@ +#ifndef LLMR_UTIL_UTF +#define LLMR_UTIL_UTF + +#include <memory> + +// g++/libstdc++ is missing c++11 codecvt support +#ifdef __linux__ +#include <boost/locale.hpp> +#else +#include <codecvt> +#include <locale> +#endif + +namespace llmr { + +namespace util { + +#ifdef __linux__ + +class utf8_to_utf32 { + public: + explicit utf8_to_utf32() {} + std::u32string convert(std::string const& utf8) { + return boost::locale::conv::utf_to_utf<char32_t>(utf8); + } +}; + +#else + +class utf8_to_utf32 { + public: + explicit utf8_to_utf32() + : utf32conv_() {} + std::u32string convert(std::string const& utf8) { + return utf32conv_.from_bytes(utf8); + } + private: + std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> utf32conv_; +}; + +#endif + +}} + +#endif diff --git a/proto/glyphs.proto b/proto/glyphs.proto new file mode 100644 index 0000000000..606050f7af --- /dev/null +++ b/proto/glyphs.proto @@ -0,0 +1,33 @@ +// Protocol Version 1 + +package llmr.glyphs; + +option optimize_for = LITE_RUNTIME; + +// Stores a glyph with metrics and optional SDF bitmap information. +message glyph { + required uint32 id = 1; + + // A signed distance field of the glyph with a border of 3 pixels. + optional bytes bitmap = 2; + + // Glyph metrics. + required uint32 width = 3; + required uint32 height = 4; + required sint32 left = 5; + required sint32 top = 6; + required uint32 advance = 7; +} + +// Stores fontstack information and a list of faces. +message fontstack { + required string name = 1; + required string range = 2; + repeated glyph glyphs = 3; +} + +message glyphs { + repeated fontstack stacks = 1; + + extensions 16 to 8191; +} diff --git a/setup-libraries.sh b/setup-libraries.sh index 954c980701..b92cd3f05a 100755 --- a/setup-libraries.sh +++ b/setup-libraries.sh @@ -74,7 +74,7 @@ source MacOSX.sh if [ ! -f out/build-cpp11-libcpp-x86_64/lib/libuv.a ] ; then ./scripts/build_libuv.sh ; fi if [ ! -f out/build-cpp11-libcpp-x86_64/lib/libssl.a ] ; then ./scripts/build_openssl.sh ; fi if [ ! -f out/build-cpp11-libcpp-x86_64/lib/libcurl.a ] ; then ./scripts/build_curl.sh ; fi - if [ ! -d out/build-cpp11-libcpp-x86_64/include/boost ] ; then ./scripts/build_boost.sh `pwd`/../../src/ `pwd`/../../linux/ `pwd`/../../common/ ; fi + if [ ! -d out/build-cpp11-libcpp-x86_64/include/boost ] ; then ./scripts/build_boost.sh `pwd`/../../src/ `pwd`/../../include/ `pwd`/../../linux/ `pwd`/../../common/ ; fi echo ' ...done' ./scripts/make_universal.sh @@ -92,7 +92,7 @@ source Linux.sh if [ ! -f out/build-cpp11-libstdcpp-gcc-x86_64/lib/libuv.a ] ; then ./scripts/build_libuv.sh ; fi if [ ! -f out/build-cpp11-libstdcpp-gcc-x86_64/lib/libssl.a ] ; then ./scripts/build_openssl.sh ; fi if [ ! -f out/build-cpp11-libstdcpp-gcc-x86_64/lib/libcurl.a ] ; then ./scripts/build_curl.sh ; fi - if [ ! -d out/build-cpp11-libstdcpp-gcc-x86_64/include/boost ] ; then ./scripts/build_boost.sh `pwd`/../../src/ `pwd`/../../linux/ `pwd`/../../common/ ; fi + if [ ! -d out/build-cpp11-libstdcpp-gcc-x86_64/include/boost ] ; then ./scripts/build_boost.sh `pwd`/../../src/ `pwd`/../../include/ `pwd`/../../linux/ `pwd`/../../common/ ; fi cd ../../ ./configure \ diff --git a/src/geometry/glyph_atlas.cpp b/src/geometry/glyph_atlas.cpp index 200d281f7c..f4defac692 100644 --- a/src/geometry/glyph_atlas.cpp +++ b/src/geometry/glyph_atlas.cpp @@ -22,7 +22,7 @@ GlyphAtlas::~GlyphAtlas() { } Rect<uint16_t> GlyphAtlas::addGlyph(uint64_t tile_id, const std::string& face_name, - const VectorTileGlyph& glyph) { + const SDFGlyph& glyph) { std::lock_guard<std::mutex> lock(mtx); // Use constant value for now. diff --git a/src/map/map.cpp b/src/map/map.cpp index 835edac0b0..7ac6dbe0e1 100644 --- a/src/map/map.cpp +++ b/src/map/map.cpp @@ -163,7 +163,7 @@ void Map::setup() { sources.emplace("outdoors", std::unique_ptr<Source>(new Source(*this, painter, - "http://api-maps-gl.tilestream.net/v3/mapbox.mapbox-terrain-v1,mapbox.mapbox-streets-v5/%d/%d/%d.gl.pbf", + "http://a.tiles.mapbox.com/v3/mapbox.mapbox-terrain-v1,mapbox.mapbox-streets-v5/%d/%d/%d.vector.pbf", Source::Type::vector, {{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 }}, 512, diff --git a/src/map/tile_parser.cpp b/src/map/tile_parser.cpp index 49444428ac..ff5d61a5e1 100644 --- a/src/map/tile_parser.cpp +++ b/src/map/tile_parser.cpp @@ -9,20 +9,22 @@ #include <llmr/util/raster.hpp> #include <llmr/util/constants.hpp> #include <llmr/geometry/glyph_atlas.hpp> +#include <llmr/text/glyph_store.hpp> +#include <llmr/text/glyph.hpp> #include <llmr/util/std.hpp> +#include <llmr/util/utf.hpp> using namespace llmr; - -TileParser::TileParser(const std::string& data, VectorTileData& tile, const Style& style, GlyphAtlas& glyphAtlas, SpriteAtlas &spriteAtlas) +TileParser::TileParser(const std::string& data, VectorTileData& tile, const Style& style, GlyphAtlas& glyphAtlas, GlyphStore &glyphStore, SpriteAtlas &spriteAtlas) : vector_data(pbf((const uint8_t *)data.data(), data.size())), tile(tile), style(style), glyphAtlas(glyphAtlas), + glyphStore(glyphStore), spriteAtlas(spriteAtlas), placement(tile.id.z) { - parseGlyphs(); parseStyleLayers(style.layers); } @@ -30,17 +32,13 @@ bool TileParser::obsolete() const { return tile.state == TileData::State::obsolete; } -void TileParser::parseGlyphs() { - for (const std::pair<std::string, const VectorTileFace> pair : vector_data.faces) { - const std::string &name = pair.first; - const VectorTileFace &face = pair.second; - - GlyphPositions &glyphs = faces[name]; - for (const VectorTileGlyph &glyph : face.glyphs) { - const Rect<uint16_t> rect = - glyphAtlas.addGlyph(tile.id.to_uint64(), name, glyph); - glyphs.emplace(glyph.id, Glyph{rect, glyph.metrics}); - } +void TileParser::addGlyph(uint64_t tileid, const std::string stackname, const std::u32string &string, const FontStack &fontStack, GlyphAtlas &glyphAtlas, GlyphPositions &face) { + std::map<uint32_t, SDFGlyph> sdfs = fontStack.getSDFs(); + // Loop through all characters and add glyph to atlas, positions. + for (uint32_t chr : string) { + const SDFGlyph sdf = sdfs[chr]; + const Rect<uint16_t> rect = glyphAtlas.addGlyph(tileid, stackname, sdf); + face.emplace(chr, Glyph{rect, sdf.metrics}); } } @@ -162,30 +160,79 @@ std::unique_ptr<Bucket> TileParser::createIconBucket(const VectorTileLayer& laye return obsolete() ? nullptr : std::move(bucket); } +typedef std::pair<uint16_t, uint16_t> GlyphRange; + std::unique_ptr<Bucket> TileParser::createTextBucket(const VectorTileLayer& layer, const BucketDescription& bucket_desc) { + std::unique_ptr<TextBucket> bucket = std::make_unique<TextBucket>( + tile.textVertexBuffer, tile.triangleElementsBuffer, bucket_desc, placement); + + util::utf8_to_utf32 ucs4conv; + + // Determine and load glyph ranges + { + std::set<GlyphRange> ranges; + + FilteredVectorTileLayer filtered_layer(layer, bucket_desc); + for (const pbf& feature_pbf : filtered_layer) { + if (obsolete()) return nullptr; + VectorTileFeature feature { feature_pbf, layer }; + + auto it_prop = feature.properties.find(bucket_desc.geometry.field); + if (it_prop == feature.properties.end()) { + // feature does not have the correct property + if (debug::labelTextMissingWarning) { + fprintf(stderr, "[WARNING] feature doesn't have property '%s' required for labelling\n", bucket_desc.geometry.field.c_str()); + } + continue; + } - // Determine the correct text stack. - if (!layer.shaping.size()) { - return nullptr; + const std::u32string string = ucs4conv.convert(toString(it_prop->second)); + + // Loop through all characters of this text and collect unique codepoints. + for (uint32_t chr : string) { + ranges.insert(getGlyphRange(chr)); + } + } + + glyphStore.waitForGlyphRanges(bucket_desc.geometry.font, ranges); } - // TODO: currently hardcoded to use the first font stack. - const std::map<Value, Shaping>& shaping = layer.shaping.begin()->second; + // Create a copy! + const FontStack &fontStack = glyphStore.getFontStack(bucket_desc.geometry.font); + std::map<Value, Shaping> shaping; + GlyphPositions face; + + // Shape and place all labels. + { + FilteredVectorTileLayer filtered_layer(layer, bucket_desc); + for (const pbf& feature_pbf : filtered_layer) { + if (obsolete()) return nullptr; + VectorTileFeature feature { feature_pbf, layer }; + + auto it_prop = feature.properties.find(bucket_desc.geometry.field); + if (it_prop == feature.properties.end()) { + // feature does not have the correct property + if (debug::labelTextMissingWarning) { + fprintf(stderr, "[WARNING] feature doesn't have property '%s' required for labelling\n", bucket_desc.geometry.field.c_str()); + } + continue; + } - const Faces& const_faces = faces; + const std::u32string string = ucs4conv.convert(toString(it_prop->second)); - IndexedFaces faces; - for (const std::string& face : layer.faces) { - auto it = const_faces.find(face); - if (it == const_faces.end()) { - // This layer references an unknown face. - return nullptr; + // Shape labels. + const Shaping shaped = fontStack.getShaping(string); + shaping.emplace(toString(it_prop->second), shaped); + + // Place labels. + addGlyph(tile.id.to_uint64(), bucket_desc.geometry.font, string, fontStack, glyphAtlas, face); } - faces.push_back(&it->second); } - std::unique_ptr<TextBucket> bucket = std::make_unique<TextBucket>( - tile.textVertexBuffer, tile.triangleElementsBuffer, bucket_desc, placement); - addBucketFeatures(bucket, layer, bucket_desc, faces, shaping); + // It looks like nearly the same interface through the rest + // of the stack. + addBucketFeatures(bucket, layer, bucket_desc, face, shaping); + return std::move(bucket); } + diff --git a/src/map/vector_tile_data.cpp b/src/map/vector_tile_data.cpp index 337e2fcc35..3c972aa319 100644 --- a/src/map/vector_tile_data.cpp +++ b/src/map/vector_tile_data.cpp @@ -22,7 +22,7 @@ void VectorTileData::parse() { // Parsing creates state that is encapsulated in TileParser. While parsing, // the TileParser object writes results into this objects. All other state // is going to be discarded afterwards. - TileParser parser(data, *this, map.getStyle(), map.getGlyphAtlas(), map.getSpriteAtlas()); + TileParser parser(data, *this, map.getStyle(), map.getGlyphAtlas(), map.getGlyphStore(), map.getSpriteAtlas()); } catch (const std::exception& ex) { fprintf(stderr, "[%p] exception [%d/%d/%d]... failed: %s\n", this, id.z, id.x, id.y, ex.what()); cancel(); diff --git a/src/platform/request.cpp b/src/platform/request.cpp index aadf83deab..f689517ab5 100644 --- a/src/platform/request.cpp +++ b/src/platform/request.cpp @@ -12,25 +12,33 @@ Request::Request(const std::string &url, res(std::make_unique<Response>(callback)), cancelled(false), loop(loop) { - // Add a check handle without a callback to keep the default loop running. - // We don't have a real handler attached to the default loop right from the - // beginning, because we're using asynchronous messaging to perform the actual - // request in the request thread. Only after the request is complete, we - // create an actual work request that is attached to the default loop. - async = new uv_async_t(); - async->data = new std::unique_ptr<Response>(); - uv_async_init(loop, async, complete); + if (loop) { + // Add a check handle without a callback to keep the default loop running. + // We don't have a real handler attached to the default loop right from the + // beginning, because we're using asynchronous messaging to perform the actual + // request in the request thread. Only after the request is complete, we + // create an actual work request that is attached to the default loop. + async = new uv_async_t(); + async->data = new std::unique_ptr<Response>(); + uv_async_init(loop, async, complete); + } } Request::~Request() { } void Request::complete() { - // We're scheduling the response callback to be invoked in the event loop. - // Since the Request object will be deleted before the callback is invoked, - // we move over the Response object to be owned by the async handle. - ((std::unique_ptr<Response> *)async->data)->swap(res); - uv_async_send(async); + if (loop) { + // We're scheduling the response callback to be invoked in the event loop. + // Since the Request object will be deleted before the callback is invoked, + // we move over the Response object to be owned by the async handle. + ((std::unique_ptr<Response> *)async->data)->swap(res); + uv_async_send(async); + } else { + // We're calling the response callback immediately. We're currently on an + // arbitrary thread, but that's okay. + res->callback(res.get()); + } } void Request::complete(uv_async_t *async) { diff --git a/src/renderer/text_bucket.cpp b/src/renderer/text_bucket.cpp index 0fd351343c..5823607acf 100644 --- a/src/renderer/text_bucket.cpp +++ b/src/renderer/text_bucket.cpp @@ -7,6 +7,8 @@ #include <llmr/style/style.hpp> #include <llmr/map/vector_tile.hpp> #include <llmr/text/placement.hpp> +#include <llmr/text/glyph_store.hpp> +#include <llmr/util/constants.hpp> #include <llmr/util/math.hpp> #include <llmr/platform/gl.hpp> @@ -98,17 +100,23 @@ void TextBucket::addGlyphs(const PlacedGlyphs &glyphs, float placementZoom, }; void TextBucket::addFeature(const VectorTileFeature &feature, - const IndexedFaces &faces, + const GlyphPositions &face, const std::map<Value, Shaping> &shapings) { auto it_prop = feature.properties.find(geom_desc.field); if (it_prop == feature.properties.end()) { // feature does not have the correct property + if (debug::labelTextMissingWarning) { + fprintf(stderr, "[WARNING] feature doesn't have property '%s' required for labelling\n", geom_desc.field.c_str()); + } return; } const Value &value = it_prop->second; - auto it_shaping = shapings.find(value); + auto it_shaping = shapings.find(toString(value)); if (it_shaping == shapings.end()) { + if (debug::shapingWarning) { + fprintf(stderr, "[WARNING] missing shaping for '%s'\n", toString(value).c_str()); + } // we lack shaping information for this label return; } @@ -127,14 +135,14 @@ void TextBucket::addFeature(const VectorTileFeature &feature, while ((cmd = geometry.next(x, y)) != Geometry::end) { if (cmd == Geometry::move_to) { if (!line.empty()) { - placement.addFeature(*this, line, geom_desc, faces, shaping); + placement.addFeature(*this, line, geom_desc, face, shaping); line.clear(); } } line.emplace_back(x, y); } if (line.size()) { - placement.addFeature(*this, line, geom_desc, faces, shaping); + placement.addFeature(*this, line, geom_desc, face, shaping); } } diff --git a/src/text/glyph.cpp b/src/text/glyph.cpp new file mode 100644 index 0000000000..882a8c493c --- /dev/null +++ b/src/text/glyph.cpp @@ -0,0 +1,17 @@ +#include <llmr/text/glyph.hpp> + +namespace llmr { + +// Note: this only works for the BMP +// Note: we could use a binary lookup table to get averaged constant time lookups, however, +// most of our lookups are going to be within the first 3 ranges listed here, so this is +// likely faster. +GlyphRange getGlyphRange(uint32_t glyph) { + unsigned start = (glyph/256) * 256; + unsigned end = (start + 255); + if (start > 65280) start = 65280; + if (end > 65533) end = 65533; + return { start, end }; +} + +} diff --git a/src/text/glyph_store.cpp b/src/text/glyph_store.cpp new file mode 100644 index 0000000000..e0404c08d1 --- /dev/null +++ b/src/text/glyph_store.cpp @@ -0,0 +1,185 @@ +#include <llmr/text/glyph_store.hpp> + +#include <llmr/util/std.hpp> +#include <llmr/util/string.hpp> +#include <llmr/util/utf.hpp> +#include <llmr/util/pbf.hpp> +#include <llmr/platform/platform.hpp> +#include <uv.h> +#include <algorithm> + +namespace llmr { + + +void FontStack::insert(uint32_t id, const SDFGlyph &glyph) { + std::lock_guard<std::mutex> lock(mtx); + metrics.emplace(id, glyph.metrics); + bitmaps.emplace(id, glyph.bitmap); + sdfs.emplace(id, glyph); +} + +const std::map<uint32_t, GlyphMetrics> &FontStack::getMetrics() const { + std::lock_guard<std::mutex> lock(mtx); + return metrics; +} + +const std::map<uint32_t, SDFGlyph> &FontStack::getSDFs() const { + std::lock_guard<std::mutex> lock(mtx); + return sdfs; +} + +const Shaping FontStack::getShaping(const std::u32string &string) const { + std::lock_guard<std::mutex> lock(mtx); + uint32_t i = 0; + uint32_t x = 0; + Shaping shaped; + // Loop through all characters of this label and shape. + for (uint32_t chr : string) { + GlyphPlacement glyph = GlyphPlacement(0, chr, x, 0); + shaped.push_back(glyph); + i++; + x += metrics.find(chr)->second.advance; + } + return shaped; +} + +GlyphPBF::GlyphPBF(const std::string &fontStack, GlyphRange glyphRange) + : future(promise.get_future().share()) +{ + // Load the glyph set URL + std::string url = util::sprintf<255>("http://mapbox.s3.amazonaws.com/gl-glyphs-256/%s/%d-%d.pbf", fontStack.c_str(), glyphRange.first, glyphRange.second); + + // TODO: Find more reliable URL normalization function + std::replace(url.begin(), url.end(), ' ', '+'); + + fprintf(stderr, url.c_str()); + + platform::request_http(url, [&](platform::Response *res) { + if (res->code != 200) { + // Something went wrong with loading the glyph pbf. Pass on the error to the future listeners. + const std::string msg = util::sprintf<255>("[ERROR] failed to load glyphs (%d): %s\n", res->code, res->error_message.c_str()); + promise.set_exception(std::make_exception_ptr(std::runtime_error(msg))); + } else { + // Transfer the data to the GlyphSet and signal its availability. + // Once it is available, the caller will need to call parse() to actually + // parse the data we received. We are not doing this here since this callback is being + // called from another (unknown) thread. + data.swap(res->body); + promise.set_value(*this); + } + }); +} + +std::shared_future<GlyphPBF &> GlyphPBF::getFuture() { + return future; +} + +void GlyphPBF::parse(FontStack &stack) { + std::lock_guard<std::mutex> lock(mtx); + + if (!data.size()) { + // If there is no data, this means we either haven't received any data, or + // we have already parsed the data. + return; + } + + // Parse the glyph PBF + pbf glyphs_pbf(reinterpret_cast<const uint8_t *>(data.data()), data.size()); + + while (glyphs_pbf.next()) { + if (glyphs_pbf.tag == 1) { // stacks + pbf fontstack_pbf = glyphs_pbf.message(); + while (fontstack_pbf.next()) { + if (fontstack_pbf.tag == 3) { // glyphs + pbf glyph_pbf = fontstack_pbf.message(); + + SDFGlyph glyph; + + while (glyph_pbf.next()) { + if (glyph_pbf.tag == 1) { // id + glyph.id = glyph_pbf.varint(); + } else if (glyph_pbf.tag == 2) { // bitmap + glyph.bitmap = glyph_pbf.string(); + } else if (glyph_pbf.tag == 3) { // width + glyph.metrics.width = glyph_pbf.varint(); + } else if (glyph_pbf.tag == 4) { // height + glyph.metrics.height = glyph_pbf.varint(); + } else if (glyph_pbf.tag == 5) { // left + glyph.metrics.left = glyph_pbf.svarint(); + } else if (glyph_pbf.tag == 6) { // top + glyph.metrics.top = glyph_pbf.svarint(); + } else if (glyph_pbf.tag == 7) { // advance + glyph.metrics.advance = glyph_pbf.varint(); + } else { + glyph_pbf.skip(); + } + } + + stack.insert(glyph.id, glyph); + } else { + fontstack_pbf.skip(); + } + } + } else { + glyphs_pbf.skip(); + } + } + + data.clear(); +} + + +void GlyphStore::waitForGlyphRanges(const std::string &fontStack, const std::set<GlyphRange> &glyphRanges) { + // We are implementing a blocking wait with futures: Every GlyphSet has a future that we are + // waiting for until it is loaded. + + FontStack *stack = nullptr; + + std::vector<std::shared_future<GlyphPBF &>> futures; + futures.reserve(glyphRanges.size()); + { + std::lock_guard<std::mutex> lock(mtx); + auto &rangeSets = ranges[fontStack]; + + stack = &createFontStack(fontStack); + + // Attempt to load the glyph range. If the GlyphSet already exists, we are getting back + // the same shared_future. + for (GlyphRange range : glyphRanges) { + futures.emplace_back(loadGlyphRange(fontStack, rangeSets, range)); + } + } + + // Now that we potentially created all GlyphSets, we are waiting for the results, one by one. + // When we get a result (or the GlyphSet is aready loaded), we are attempting to parse the + // GlyphSet. + for (std::shared_future<GlyphPBF &> &future : futures) { + future.get().parse(*stack); + } +} + +std::shared_future<GlyphPBF &> GlyphStore::loadGlyphRange(const std::string &name, std::map<GlyphRange, std::unique_ptr<GlyphPBF>> &rangeSets, const GlyphRange range) { + auto range_it = rangeSets.find(range); + if (range_it == rangeSets.end()) { + // We don't have this glyph set yet for this font stack. + range_it = rangeSets.emplace(range, std::make_unique<GlyphPBF>(name, range)).first; + } + + return range_it->second->getFuture(); +} + +FontStack &GlyphStore::createFontStack(const std::string &fontStack) { + auto stack_it = stacks.find(fontStack); + if (stack_it == stacks.end()) { + stack_it = stacks.emplace(fontStack, std::make_unique<FontStack>()).first; + } + return *stack_it->second.get(); +} + +FontStack &GlyphStore::getFontStack(const std::string &fontStack) { + std::lock_guard<std::mutex> lock(mtx); + return createFontStack(fontStack); +} + + +} diff --git a/src/text/placement.cpp b/src/text/placement.cpp index e1d7b55bc0..f57436bcb4 100644 --- a/src/text/placement.cpp +++ b/src/text/placement.cpp @@ -35,21 +35,12 @@ bool byScale(const Anchor &a, const Anchor &b) { return a.scale < b.scale; } static const Glyph null_glyph; inline const Glyph &getGlyph(const GlyphPlacement &placed, - const IndexedFaces &faces) { - if (placed.face < faces.size()) { - const GlyphPositions &face = *faces[placed.face]; - if (&face) { - auto it = face.find(placed.glyph); - if (it != face.end()) { - return it->second; - } else { - fprintf(stderr, "glyph %d does not exist\n", placed.glyph); - } - } else { - fprintf(stderr, "face pointer is null\n"); - } + const GlyphPositions &face) { + auto it = face.find(placed.glyph); + if (it != face.end()) { + return it->second; } else { - fprintf(stderr, "face does not exist\n"); + fprintf(stderr, "glyph %d does not exist\n", placed.glyph); } return null_glyph; @@ -147,7 +138,7 @@ void getSegmentGlyphs(std::back_insert_iterator<GlyphInstances> glyphs, void getGlyphs(PlacedGlyphs &glyphs, GlyphBoxes &boxes, Anchor &anchor, float advance, const Shaping &shaping, - const IndexedFaces &faces, float fontScale, + const GlyphPositions &face, float fontScale, bool horizontal, const std::vector<Coordinate> &line, float maxAngleDelta, float rotate) { // The total text advance is the width of this label. @@ -166,7 +157,7 @@ void getGlyphs(PlacedGlyphs &glyphs, GlyphBoxes &boxes, const uint32_t buffer = 3; for (const GlyphPlacement &placed : shaping) { - const Glyph &glyph = getGlyph(placed, faces); + const Glyph &glyph = getGlyph(placed, face); if (!glyph) { // This glyph is empty and doesn't have any pixels that we'd need to // show. @@ -259,7 +250,7 @@ void getGlyphs(PlacedGlyphs &glyphs, GlyphBoxes &boxes, void Placement::addFeature(TextBucket& bucket, const std::vector<Coordinate> &line, const BucketGeometryDescription &info, - const IndexedFaces &faces, + const GlyphPositions &face, const Shaping &shaping) { const bool horizontal = info.path == TextPathType::Horizontal; @@ -270,7 +261,7 @@ void Placement::addFeature(TextBucket& bucket, const float fontScale = (tileExtent / util::tileSize) / (glyphSize / info.size); - const float advance = measureText(faces, shaping); + const float advance = measureText(face, shaping); Anchors anchors; // fprintf(stderr, "adding feature with advance %f\n", advance); @@ -294,7 +285,7 @@ void Placement::addFeature(TextBucket& bucket, PlacedGlyphs glyphs; GlyphBoxes boxes; - getGlyphs(glyphs, boxes, anchor, advance, shaping, faces, fontScale, horizontal, + getGlyphs(glyphs, boxes, anchor, advance, shaping, face, fontScale, horizontal, line, maxAngleDelta, rotate); PlacementProperty place = collision.place(boxes, anchor, anchor.scale, maxPlacementScale, @@ -305,14 +296,14 @@ void Placement::addFeature(TextBucket& bucket, } } -float Placement::measureText(const IndexedFaces &faces, +float Placement::measureText(const GlyphPositions &face, const Shaping &shaping) { float advance = 0; // TODO: advance is not calculated correctly. we should instead use the // bounding box of the glyph placement. for (const GlyphPlacement &shape : shaping) { - advance += getGlyph(shape, faces).metrics.advance; + advance += getGlyph(shape, face).metrics.advance; } return advance; diff --git a/src/util/constants.cpp b/src/util/constants.cpp index 2a4ecd3747..f674649266 100644 --- a/src/util/constants.cpp +++ b/src/util/constants.cpp @@ -7,3 +7,8 @@ const bool llmr::debug::styleParseWarnings = false; const bool llmr::debug::spriteWarnings = false; const bool llmr::debug::renderWarnings = false; const bool llmr::debug::renderTree = false; +const bool llmr::debug::labelTextMissingWarning = true; +const bool llmr::debug::missingFontStackWarning = true; +const bool llmr::debug::missingFontFaceWarning = true; +const bool llmr::debug::glyphWarning = true; +const bool llmr::debug::shapingWarning = true; |