Merge pull request #264 from mapbox/glyphrange
Add glyph range PBF loading support
This commit is contained in:
19
bin/style.js
19
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"
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
80
include/llmr/text/glyph_store.hpp
Normal file
80
include/llmr/text/glyph_store.hpp
Normal file
@@ -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
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
|
||||
45
include/llmr/util/utf.hpp
Normal file
45
include/llmr/util/utf.hpp
Normal file
@@ -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
|
||||
33
proto/glyphs.proto
Normal file
33
proto/glyphs.proto
Normal file
@@ -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;
|
||||
}
|
||||
@@ -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 \
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
// Determine the correct text stack.
|
||||
if (!layer.shaping.size()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// TODO: currently hardcoded to use the first font stack.
|
||||
const std::map<Value, Shaping>& shaping = layer.shaping.begin()->second;
|
||||
|
||||
const Faces& const_faces = faces;
|
||||
|
||||
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;
|
||||
}
|
||||
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);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// 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 std::u32string string = ucs4conv.convert(toString(it_prop->second));
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
// It looks like nearly the same interface through the rest
|
||||
// of the stack.
|
||||
addBucketFeatures(bucket, layer, bucket_desc, face, shaping);
|
||||
|
||||
return std::move(bucket);
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
17
src/text/glyph.cpp
Normal file
17
src/text/glyph.cpp
Normal file
@@ -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 };
|
||||
}
|
||||
|
||||
}
|
||||
185
src/text/glyph_store.cpp
Normal file
185
src/text/glyph_store.cpp
Normal file
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user