[core] Add minimal line breaking support for RTL text.

This commit is contained in:
Chris Loer
2016-11-16 12:50:57 -08:00
committed by John Firebaugh
parent bceeba29a3
commit d4fc66af39
7 changed files with 52 additions and 37 deletions

View File

@@ -1,5 +1,6 @@
#pragma once
#include <mbgl/text/bidi.hpp>
#include <mbgl/tile/geometry_tile_data.hpp>
#include <mbgl/util/optional.hpp>
@@ -11,6 +12,7 @@ class SymbolFeature {
public:
GeometryCollection geometry;
optional<std::u16string> text;
optional<WritingDirection> writingDirection;
optional<std::string> icon;
std::size_t index;
};

View File

@@ -92,7 +92,9 @@ SymbolLayout::SymbolLayout(std::string bucketName_,
u8string = platform::lowercase(u8string);
}
ft.text = bidi.bidiTransform(util::utf8_to_utf16::convert(u8string));
std::u16string u16string = util::utf8_to_utf16::convert(u8string);
ft.text = bidi.bidiTransform(u16string);
ft.writingDirection = bidi.baseWritingDirection(u16string);
// Loop through all characters of this text and collect unique codepoints.
for (char16_t chr : *ft.text) {
@@ -196,6 +198,7 @@ void SymbolLayout::prepare(uintptr_t tileUID,
if (feature.text) {
shapedText = glyphSet->getShaping(
/* string */ *feature.text,
/* base direction of text */ *feature.writingDirection,
/* maxWidth: ems */ layout.symbolPlacement != SymbolPlacementType::Line ?
layout.textMaxWidth * 24 : 0,
/* lineHeight: ems */ layout.textLineHeight * 24,

View File

@@ -2,6 +2,7 @@
#include <mbgl/text/bidi.hpp>
#include <unicode/ubidi.h>
#include <unicode/ubiditransform.h>
#include <unicode/ushape.h>
namespace mbgl {
@@ -44,10 +45,12 @@ std::u16string BiDi::bidiTransform(const std::u16string& input) {
return std::u16string(outputText.get(), outputLength);
}
bool BiDi::baseDirectionRightToLeft(const std::u16string& input) {
WritingDirection BiDi::baseWritingDirection(const std::u16string& input) {
// This just looks for the first character with a strong direction property, it does not perform
// the BiDi algorithm
return ubidi_getBaseDirection(input.c_str(), static_cast<int32_t>(input.size())) == UBIDI_RTL;
return ubidi_getBaseDirection(input.c_str(), static_cast<int32_t>(input.size())) == UBIDI_RTL
? WritingDirection::RightToLeft
: WritingDirection::LeftToRight;
}
} // end namespace mbgl

View File

@@ -3,17 +3,20 @@
#include <string>
#include <mbgl/util/noncopyable.hpp>
#include <unicode/ubiditransform.h>
struct UBiDiTransform;
namespace mbgl {
enum class WritingDirection : bool { LeftToRight, RightToLeft };
class BiDi : private util::noncopyable {
public:
BiDi();
~BiDi();
std::u16string bidiTransform(const std::u16string&);
bool baseDirectionRightToLeft(const std::u16string&);
WritingDirection baseWritingDirection(const std::u16string&);
private:
UBiDiTransform* transform;

View File

@@ -2,6 +2,7 @@
#include <mbgl/platform/log.hpp>
#include <mbgl/math/minmax.hpp>
#include <mbgl/util/i18n.hpp>
#include <mbgl/text/bidi.hpp>
#include <cassert>
@@ -31,7 +32,7 @@ const std::map<uint32_t, SDFGlyph> &GlyphSet::getSDFs() const {
return sdfs;
}
const Shaping GlyphSet::getShaping(const std::u16string &string, const float maxWidth,
const Shaping GlyphSet::getShaping(const std::u16string &string, const WritingDirection writingDirection, const float maxWidth,
const float lineHeight, const float horizontalAlign,
const float verticalAlign, const float justify,
const float spacing, const Point<float> &translate) const {
@@ -56,7 +57,7 @@ const Shaping GlyphSet::getShaping(const std::u16string &string, const float max
return shaping;
lineWrap(shaping, lineHeight, maxWidth, horizontalAlign, verticalAlign, justify, translate,
util::i18n::allowsIdeographicBreaking(string));
util::i18n::allowsIdeographicBreaking(string), writingDirection);
return shaping;
}
@@ -90,7 +91,9 @@ void justifyLine(std::vector<PositionedGlyph> &positionedGlyphs, const std::map<
void GlyphSet::lineWrap(Shaping &shaping, const float lineHeight, float maxWidth,
const float horizontalAlign, const float verticalAlign,
const float justify, const Point<float> &translate,
bool useBalancedIdeographicBreaking) const {
bool useBalancedIdeographicBreaking, const WritingDirection writingDirection) const {
float lineFeedOffset = writingDirection == WritingDirection::RightToLeft ? -lineHeight : lineHeight;
uint32_t lastSafeBreak = 0;
uint32_t lengthBeforeCurrentLine = 0;
@@ -112,7 +115,7 @@ void GlyphSet::lineWrap(Shaping &shaping, const float lineHeight, float maxWidth
PositionedGlyph &shape = positionedGlyphs[i];
shape.x -= lengthBeforeCurrentLine;
shape.y += lineHeight * line;
shape.y += lineFeedOffset * line;
if (shape.x > maxWidth && lastSafeBreak > 0) {
@@ -120,7 +123,7 @@ void GlyphSet::lineWrap(Shaping &shaping, const float lineHeight, float maxWidth
maxLineLength = util::max(lineLength, maxLineLength);
for (uint32_t k = lastSafeBreak + 1; k <= i; k++) {
positionedGlyphs[k].y += lineHeight;
positionedGlyphs[k].y += lineFeedOffset;
positionedGlyphs[k].x -= lineLength;
}

View File

@@ -1,5 +1,6 @@
#pragma once
#include <mbgl/text/bidi.hpp>
#include <mbgl/text/glyph.hpp>
#include <mbgl/util/geometry.hpp>
@@ -9,12 +10,12 @@ class GlyphSet {
public:
void insert(uint32_t id, SDFGlyph&&);
const std::map<uint32_t, SDFGlyph> &getSDFs() const;
const Shaping getShaping(const std::u16string &string, float maxWidth, float lineHeight,
const Shaping getShaping(const std::u16string &string, const WritingDirection writingDirection, float maxWidth, float lineHeight,
float horizontalAlign, float verticalAlign, float justify,
float spacing, const Point<float> &translate) const;
void lineWrap(Shaping &shaping, float lineHeight, float maxWidth, float horizontalAlign,
float verticalAlign, float justify, const Point<float> &translate,
bool useBalancedIdeographicBreaking) const;
bool useBalancedIdeographicBreaking, const WritingDirection writingDirection) const;
private:
std::map<uint32_t, SDFGlyph> sdfs;

View File

@@ -9,21 +9,21 @@ const std::u16string bbb = u"b";
TEST(MergeLines, SameText) {
// merges lines with the same text
std::vector<mbgl::SymbolFeature> input1 = {
{ {{{0, 0}, {1, 0}, {2, 0}}}, aaa, {}, 0 },
{ {{{4, 0}, {5, 0}, {6, 0}}}, bbb, {}, 0 },
{ {{{8, 0}, {9, 0}}}, aaa, {}, 0 },
{ {{{2, 0}, {3, 0}, {4, 0}}}, aaa, {}, 0 },
{ {{{6, 0}, {7, 0}, {8, 0}}}, aaa, {}, 0 },
{ {{{5, 0}, {6, 0}}}, aaa, {}, 0 }
{ {{{0, 0}, {1, 0}, {2, 0}}}, aaa, mbgl::WritingDirection::LeftToRight, {}, 0 },
{ {{{4, 0}, {5, 0}, {6, 0}}}, bbb, mbgl::WritingDirection::LeftToRight, {}, 0 },
{ {{{8, 0}, {9, 0}}}, aaa, mbgl::WritingDirection::LeftToRight, {}, 0 },
{ {{{2, 0}, {3, 0}, {4, 0}}}, aaa, mbgl::WritingDirection::LeftToRight, {}, 0 },
{ {{{6, 0}, {7, 0}, {8, 0}}}, aaa, mbgl::WritingDirection::LeftToRight, {}, 0 },
{ {{{5, 0}, {6, 0}}}, aaa, mbgl::WritingDirection::LeftToRight, {}, 0 }
};
const std::vector<mbgl::SymbolFeature> expected1 = {
{ {{{0, 0}, {1, 0}, {2, 0}, {3, 0}, {4, 0}}}, aaa, {}, 0 },
{ {{{4, 0}, {5, 0}, {6, 0}}}, bbb, {}, 0 },
{ {{{5, 0}, {6, 0}, {7, 0}, {8, 0}, {9, 0}}}, aaa, {}, 0 },
{ {{}}, aaa, {}, 0 },
{ {{}}, aaa, {}, 0 },
{ {{}}, aaa, {}, 0 }
{ {{{0, 0}, {1, 0}, {2, 0}, {3, 0}, {4, 0}}}, aaa, mbgl::WritingDirection::LeftToRight, {}, 0 },
{ {{{4, 0}, {5, 0}, {6, 0}}}, bbb, mbgl::WritingDirection::LeftToRight, {}, 0 },
{ {{{5, 0}, {6, 0}, {7, 0}, {8, 0}, {9, 0}}}, aaa, mbgl::WritingDirection::LeftToRight, {}, 0 },
{ {{}}, aaa, mbgl::WritingDirection::LeftToRight, {}, 0 },
{ {{}}, aaa, mbgl::WritingDirection::LeftToRight, {}, 0 },
{ {{}}, aaa, mbgl::WritingDirection::LeftToRight, {}, 0 }
};
mbgl::util::mergeLines(input1);
@@ -36,15 +36,15 @@ TEST(MergeLines, SameText) {
TEST(MergeLines, BothEnds) {
// mergeLines handles merge from both ends
std::vector<mbgl::SymbolFeature> input2 = {
{ {{{0, 0}, {1, 0}, {2, 0}}}, aaa, {}, 0 },
{ {{{4, 0}, {5, 0}, {6, 0}}}, aaa, {}, 0 },
{ {{{2, 0}, {3, 0}, {4, 0}}}, aaa, {}, 0 }
{ {{{0, 0}, {1, 0}, {2, 0}}}, aaa, mbgl::WritingDirection::LeftToRight, {}, 0 },
{ {{{4, 0}, {5, 0}, {6, 0}}}, aaa, mbgl::WritingDirection::LeftToRight, {}, 0 },
{ {{{2, 0}, {3, 0}, {4, 0}}}, aaa, mbgl::WritingDirection::LeftToRight, {}, 0 }
};
const std::vector<mbgl::SymbolFeature> expected2 = {
{ {{{0, 0}, {1, 0}, {2, 0}, {3, 0}, {4, 0}, {5, 0}, {6, 0}}}, aaa, {}, 0 },
{ {{}}, aaa, {}, 0 },
{ {{}}, aaa, {}, 0 }
{ {{{0, 0}, {1, 0}, {2, 0}, {3, 0}, {4, 0}, {5, 0}, {6, 0}}}, aaa, mbgl::WritingDirection::LeftToRight, {}, 0 },
{ {{}}, aaa, mbgl::WritingDirection::LeftToRight, {}, 0 },
{ {{}}, aaa, mbgl::WritingDirection::LeftToRight, {}, 0 }
};
mbgl::util::mergeLines(input2);
@@ -57,15 +57,15 @@ TEST(MergeLines, BothEnds) {
TEST(MergeLines, CircularLines) {
// mergeLines handles circular lines
std::vector<mbgl::SymbolFeature> input3 = {
{ {{{0, 0}, {1, 0}, {2, 0}}}, aaa, {}, 0 },
{ {{{2, 0}, {3, 0}, {4, 0}}}, aaa, {}, 0 },
{ {{{4, 0}, {0, 0}}}, aaa, {}, 0 }
{ {{{0, 0}, {1, 0}, {2, 0}}}, aaa, mbgl::WritingDirection::LeftToRight, {}, 0 },
{ {{{2, 0}, {3, 0}, {4, 0}}}, aaa, mbgl::WritingDirection::LeftToRight, {}, 0 },
{ {{{4, 0}, {0, 0}}}, aaa, mbgl::WritingDirection::LeftToRight, {}, 0 }
};
const std::vector<mbgl::SymbolFeature> expected3 = {
{ {{{0, 0}, {1, 0}, {2, 0}, {3, 0}, {4, 0}, {0, 0}}}, aaa, {}, 0 },
{ {{}}, aaa, {}, 0 },
{ {{}}, aaa, {}, 0 }
{ {{{0, 0}, {1, 0}, {2, 0}, {3, 0}, {4, 0}, {0, 0}}}, aaa, mbgl::WritingDirection::LeftToRight, {}, 0 },
{ {{}}, aaa, mbgl::WritingDirection::LeftToRight, {}, 0 },
{ {{}}, aaa, mbgl::WritingDirection::LeftToRight, {}, 0 }
};
mbgl::util::mergeLines(input3);