#include #include #include #include #include #include #include #include #include #include #include #include #include namespace mbgl { void FontStack::insert(uint32_t id, const SDFGlyph &glyph) { std::lock_guard lock(mtx); metrics.emplace(id, glyph.metrics); bitmaps.emplace(id, glyph.bitmap); sdfs.emplace(id, glyph); } const std::map &FontStack::getMetrics() const { std::lock_guard lock(mtx); return metrics; } const std::map &FontStack::getSDFs() const { std::lock_guard lock(mtx); return sdfs; } const Shaping FontStack::getShaping(const std::u32string &string, const float maxWidth, const float lineHeight, const float horizontalAlign, const float verticalAlign, const float justify, const float spacing, const vec2 &translate) const { std::lock_guard lock(mtx); Shaping shaping; int32_t x = std::round(translate.x * 24); // one em const int32_t y = std::round(translate.y * 24); // one em // Loop through all characters of this label and shape. for (uint32_t chr : string) { shaping.emplace_back(chr, x, y); auto metric = metrics.find(chr); if (metric != metrics.end()) { x += metric->second.advance + spacing; } } if (!shaping.size()) return shaping; lineWrap(shaping, lineHeight, maxWidth, horizontalAlign, verticalAlign, justify); return shaping; } void align(Shaping &shaping, const float justify, const float horizontalAlign, const float verticalAlign, const uint32_t maxLineLength, const float lineHeight, const uint32_t line) { const float shiftX = (justify - horizontalAlign) * maxLineLength; const float shiftY = (-verticalAlign * (line + 1) + 0.5) * lineHeight; for (PositionedGlyph &glyph : shaping) { glyph.x += shiftX; glyph.y += shiftY; } } void justifyLine(Shaping &shaping, const std::map &metrics, uint32_t start, uint32_t end, float justify) { PositionedGlyph &glyph = shaping[end]; auto metric = metrics.find(glyph.glyph); if (metric != metrics.end()) { const uint32_t lastAdvance = metric->second.advance; const float lineIndent = float(glyph.x + lastAdvance) * justify; for (uint32_t j = start; j <= end; j++) { shaping[j].x -= lineIndent; } } } void FontStack::lineWrap(Shaping &shaping, const float lineHeight, const float maxWidth, const float horizontalAlign, const float verticalAlign, const float justify) const { uint32_t lastSafeBreak = 0; uint32_t lengthBeforeCurrentLine = 0; uint32_t lineStartIndex = 0; uint32_t line = 0; uint32_t maxLineLength = 0; if (maxWidth) { for (uint32_t i = 0; i < shaping.size(); i++) { PositionedGlyph &shape = shaping[i]; shape.x -= lengthBeforeCurrentLine; shape.y += lineHeight * line; if (shape.x > maxWidth && lastSafeBreak > 0) { uint32_t lineLength = shaping[lastSafeBreak + 1].x; maxLineLength = util::max(lineLength, maxLineLength); for (uint32_t k = lastSafeBreak + 1; k <= i; k++) { shaping[k].y += lineHeight; shaping[k].x -= lineLength; } if (justify) { justifyLine(shaping, metrics, lineStartIndex, lastSafeBreak - 1, justify); } lineStartIndex = lastSafeBreak + 1; lastSafeBreak = 0; lengthBeforeCurrentLine += lineLength; line++; } if (shape.glyph == 32) { lastSafeBreak = i; } } } if (!maxLineLength) maxLineLength = shaping.back().x; justifyLine(shaping, metrics, lineStartIndex, uint32_t(shaping.size()) - 1, justify); align(shaping, justify, horizontalAlign, verticalAlign, maxLineLength, lineHeight, line); } GlyphPBF::GlyphPBF(const std::string &glyphURL, const std::string &fontStack, GlyphRange glyphRange, const util::ptr &fileSource) : future(promise.get_future().share()) { // Load the glyph set URL std::string url = util::replaceTokens(glyphURL, [&](const std::string &name) -> std::string { if (name == "fontstack") return util::percentEncode(fontStack); if (name == "range") return std::to_string(glyphRange.first) + "-" + std::to_string(glyphRange.second); return ""; }); // The prepare call jumps back to the main thread. fileSource->prepare([&, url, fileSource] { auto request = fileSource->request(ResourceType::Glyphs, url); request->onload([&, url](const 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.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 = res.data; promise.set_value(*this); } }); request->oncancel([&]() { promise.set_exception(std::make_exception_ptr(std::runtime_error("Loading glyphs was canceled"))); }); }); } std::shared_future GlyphPBF::getFuture() { return future; } void GlyphPBF::parse(FontStack &stack) { std::lock_guard 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(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(); } GlyphStore::GlyphStore(const util::ptr &fileSource_) : fileSource(fileSource_) {} void GlyphStore::setURL(const std::string &url) { glyphURL = url; } void GlyphStore::waitForGlyphRanges(const std::string &fontStack, const std::set &glyphRanges) { // We are implementing a blocking wait with futures: Every GlyphSet has a future that we are // waiting for until it is loaded. if (glyphRanges.empty()) { return; } FontStack *stack = nullptr; std::vector> futures; futures.reserve(glyphRanges.size()); { std::lock_guard 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 &future : futures) { future.get().parse(*stack); } } std::shared_future GlyphStore::loadGlyphRange(const std::string &fontStack, std::map> &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(glyphURL, fontStack, range, fileSource)).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()).first; } return *stack_it->second.get(); } FontStack &GlyphStore::getFontStack(const std::string &fontStack) { std::lock_guard lock(mtx); return createFontStack(fontStack); } }