[automerger skipped] Fix fvar table size validation logic - DO NOT MERGE
am: 1a408ab0ef -s ours
Change-Id: I71406c299dd82409fabec994bf46026135c3ffaa
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..e0611e3
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,8 @@
+BasedOnStyle: Google
+DerivePointerAlignment: false
+IndentWidth: 4
+ConstructorInitializerIndentWidth: 8
+ContinuationIndentWidth: 8
+ColumnLimit: 100
+AllowShortFunctionsOnASingleLine: Inline
+AccessModifierOffset: -4
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
new file mode 100644
index 0000000..f031ffc
--- /dev/null
+++ b/PREUPLOAD.cfg
@@ -0,0 +1,5 @@
+[Builtin Hooks]
+clang_format = true
+
+[Builtin Hooks Options]
+clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions h,cpp
diff --git a/app/Android.bp b/app/Android.bp
index e8085c1..a74ca7e 100644
--- a/app/Android.bp
+++ b/app/Android.bp
@@ -27,4 +27,6 @@
],
srcs: ["HyphTool.cpp"],
+
+ cflags: ["-Wall", "-Werror"],
}
diff --git a/app/HyphTool.cpp b/app/HyphTool.cpp
index 403d374..33d42b4 100644
--- a/app/HyphTool.cpp
+++ b/app/HyphTool.cpp
@@ -1,17 +1,17 @@
-#include <stdio.h>
-#include <sys/stat.h>
#include <string.h>
-
-#include "unicode/locid.h"
-#include "utils/Log.h"
-
+#include <sys/stat.h>
+#include <cstdio>
#include <vector>
-#include <minikin/Hyphenator.h>
+
+#include <unicode/locid.h>
+#include <utils/Log.h>
+
+#include "minikin/Hyphenator.h"
using minikin::HyphenationType;
using minikin::Hyphenator;
-Hyphenator* loadHybFile(const char* fn, int minPrefix, int minSuffix) {
+Hyphenator* loadHybFile(const char* fn, int minPrefix, int minSuffix, const char* language) {
struct stat statbuf;
int status = stat(fn, &statbuf);
if (status < 0) {
@@ -32,11 +32,11 @@
delete[] buf;
return nullptr;
}
- return Hyphenator::loadBinary(buf, minPrefix, minSuffix);
+ return Hyphenator::loadBinary(buf, minPrefix, minSuffix, language);
}
int main(int argc, char** argv) {
- Hyphenator* hyph = loadHybFile("/tmp/en.hyb", 2, 3); // should also be configurable
+ Hyphenator* hyph = loadHybFile("/tmp/en.hyb", 2, 3, "en"); // should also be configurable
std::vector<HyphenationType> result;
std::vector<uint16_t> word;
if (argc < 2) {
@@ -53,7 +53,7 @@
// ASCII (or possibly ISO Latin 1), but kinda painful to do utf conversion :(
word.push_back(c);
}
- hyph->hyphenate(&result, word.data(), word.size(), icu::Locale::getUS());
+ hyph->hyphenate(word, &result);
for (size_t i = 0; i < len; i++) {
if (result[i] != HyphenationType::DONT_BREAK) {
printf("-");
diff --git a/doc/minikin_style.md b/doc/minikin_style.md
new file mode 100644
index 0000000..3c1c521
--- /dev/null
+++ b/doc/minikin_style.md
@@ -0,0 +1,39 @@
+### Minikin Style Guide
+
+The C++ style in Minikin follows Android Framework C++ Code Style Guide except for following rules:
+
+ * Order of include
+
+ In dir/foo.cc or dir/foo_test.cc, whose main purpose is to implement or test the stuff in
+ dir2/foo2.h, order your includes as follows:
+
+ 1. dir2/foo.h
+ 2. A blank line
+ 3. C system files
+ 4. C++ system files
+ 5. A blank line
+ 6. Other libraries' files
+ 7. A blank line
+ 8. Minikin public files
+ 9. A blank line
+ 10. Minikin private files
+
+ For example,
+ ```
+ #include "minikin/Layout.h" // The corresponding header file.
+
+ #include <math.h> // C system header files.
+ #include <string> // C++ system header files.
+
+ #include <hb.h> // Other library, HarfBuzz, header file.
+ #include <log/log.h> // Other library, Android, header file.
+ #include <unicode/ubidi.h> // Other library, ICU, header file.
+
+ #include "minikin/Emoji.h" // The minikin public header file.
+ #include "HbFontCache.h" // The minikin private header file.
+ ```
+
+ * "<>" vs ""
+
+ * `#include <...>` should be used for non local library files.
+ * `#include "..."` should be used for minikin header files.
diff --git a/include/minikin/AndroidLineBreakerHelper.h b/include/minikin/AndroidLineBreakerHelper.h
new file mode 100644
index 0000000..462644d
--- /dev/null
+++ b/include/minikin/AndroidLineBreakerHelper.h
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MINIKIN_ANDROID_LINE_BREAKER_HELPERS_H
+#define MINIKIN_ANDROID_LINE_BREAKER_HELPERS_H
+
+#include <algorithm>
+
+#include "minikin/LineBreaker.h"
+
+namespace minikin {
+namespace android {
+
+class AndroidLineWidth : public LineWidth {
+public:
+ AndroidLineWidth(float firstWidth, int32_t firstLineCount, float restWidth,
+ const std::vector<float>& indents, const std::vector<float>& leftPaddings,
+ const std::vector<float>& rightPaddings, int32_t indentsAndPaddingsOffset)
+ : mFirstWidth(firstWidth),
+ mFirstLineCount(firstLineCount),
+ mRestWidth(restWidth),
+ mIndents(indents),
+ mLeftPaddings(leftPaddings),
+ mRightPaddings(rightPaddings),
+ mOffset(indentsAndPaddingsOffset) {}
+
+ float getAt(size_t lineNo) const override {
+ const float width = ((ssize_t)lineNo < (ssize_t)mFirstLineCount) ? mFirstWidth : mRestWidth;
+ return std::max(0.0f, width - get(mIndents, lineNo));
+ }
+
+ float getMin() const override {
+ // A simpler algorithm would have been simply looping until the larger of
+ // mFirstLineCount and mIndents.size()-mOffset, but that does unnecessary calculations
+ // when mFirstLineCount is large. Instead, we measure the first line, all the lines that
+ // have an indent, and the first line after firstWidth ends and restWidth starts.
+ float minWidth = std::min(getAt(0), getAt(mFirstLineCount));
+ for (size_t lineNo = 1; lineNo + mOffset < mIndents.size(); lineNo++) {
+ minWidth = std::min(minWidth, getAt(lineNo));
+ }
+ return minWidth;
+ }
+
+ float getLeftPaddingAt(size_t lineNo) const override { return get(mLeftPaddings, lineNo); }
+
+ float getRightPaddingAt(size_t lineNo) const override { return get(mRightPaddings, lineNo); }
+
+private:
+ float get(const std::vector<float>& vec, size_t lineNo) const {
+ if (vec.empty()) {
+ return 0;
+ }
+ const size_t index = lineNo + mOffset;
+ if (index < vec.size()) {
+ return vec[index];
+ } else {
+ return vec.back();
+ }
+ }
+
+ const float mFirstWidth;
+ const int32_t mFirstLineCount;
+ const float mRestWidth;
+ const std::vector<float>& mIndents;
+ const std::vector<float>& mLeftPaddings;
+ const std::vector<float>& mRightPaddings;
+ const int32_t mOffset;
+};
+
+class StaticLayoutNative {
+public:
+ StaticLayoutNative(BreakStrategy strategy, HyphenationFrequency frequency, bool isJustified,
+ std::vector<float>&& indents, std::vector<float>&& leftPaddings,
+ std::vector<float>&& rightPaddings)
+ : mStrategy(strategy),
+ mFrequency(frequency),
+ mIsJustified(isJustified),
+ mIndents(std::move(indents)),
+ mLeftPaddings(std::move(leftPaddings)),
+ mRightPaddings(std::move(rightPaddings)) {}
+
+ LineBreakResult computeBreaks(const U16StringPiece& textBuf, const MeasuredText& measuredText,
+ // Line width arguments
+ float firstWidth, int32_t firstWidthLineCount, float restWidth,
+ int32_t indentsOffset,
+ // Tab stop arguments
+ const int32_t* tabStops, int32_t tabStopSize,
+ int32_t defaultTabStopWidth) const {
+ AndroidLineWidth lineWidth(firstWidth, firstWidthLineCount, restWidth, mIndents,
+ mLeftPaddings, mRightPaddings, indentsOffset);
+ return breakIntoLines(textBuf, mStrategy, mFrequency, mIsJustified, measuredText, lineWidth,
+ TabStops(tabStops, tabStopSize, defaultTabStopWidth));
+ }
+
+ inline BreakStrategy getStrategy() const { return mStrategy; }
+ inline HyphenationFrequency getFrequency() const { return mFrequency; }
+ inline bool isJustified() const { return mIsJustified; }
+
+private:
+ const BreakStrategy mStrategy;
+ const HyphenationFrequency mFrequency;
+ const bool mIsJustified;
+ const std::vector<float> mIndents;
+ const std::vector<float> mLeftPaddings;
+ const std::vector<float> mRightPaddings;
+};
+
+} // namespace android
+} // namespace minikin
+
+#endif // MINIKIN_ANDROID_LINE_BREAKER_HELPERS_H
diff --git a/include/minikin/Characters.h b/include/minikin/Characters.h
new file mode 100644
index 0000000..074d134
--- /dev/null
+++ b/include/minikin/Characters.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MINIKIN_CHARACTERS_H
+#define MINIKIN_CHARACTERS_H
+
+#include <cstdint>
+
+namespace minikin {
+
+// Code point order
+constexpr uint32_t CHAR_LINE_FEED = 0x000A;
+constexpr uint32_t CHAR_CARRIAGE_RETURN = 0x000D;
+constexpr uint32_t CHAR_TAB = 0x0009;
+constexpr uint32_t CHAR_HYPHEN_MINUS = 0x002D;
+constexpr uint32_t CHAR_NBSP = 0x00A0;
+constexpr uint32_t CHAR_SOFT_HYPHEN = 0x00AD;
+constexpr uint32_t CHAR_MIDDLE_DOT = 0x00B7;
+constexpr uint32_t CHAR_ARMENIAN_HYPHEN = 0x058A;
+constexpr uint32_t CHAR_MAQAF = 0x05BE;
+constexpr uint32_t CHAR_UCAS_HYPHEN = 0x1400;
+constexpr uint32_t CHAR_ZWJ = 0x200D;
+constexpr uint32_t CHAR_HYPHEN = 0x2010;
+
+} // namespace minikin
+
+#endif // MINIKIN_CHARACTERS_H
diff --git a/include/minikin/CmapCoverage.h b/include/minikin/CmapCoverage.h
index af5960d..583593d 100644
--- a/include/minikin/CmapCoverage.h
+++ b/include/minikin/CmapCoverage.h
@@ -17,7 +17,7 @@
#ifndef MINIKIN_CMAP_COVERAGE_H
#define MINIKIN_CMAP_COVERAGE_H
-#include <minikin/SparseBitSet.h>
+#include "minikin/SparseBitSet.h"
#include <memory>
#include <vector>
@@ -27,7 +27,7 @@
class CmapCoverage {
public:
static SparseBitSet getCoverage(const uint8_t* cmap_data, size_t cmap_size,
- std::vector<std::unique_ptr<SparseBitSet>>* out);
+ std::vector<std::unique_ptr<SparseBitSet>>* out);
};
} // namespace minikin
diff --git a/include/minikin/Emoji.h b/include/minikin/Emoji.h
index 2826173..046a9d6 100644
--- a/include/minikin/Emoji.h
+++ b/include/minikin/Emoji.h
@@ -31,4 +31,3 @@
UCharDirection emojiBidiOverride(const void* context, UChar32 c);
} // namespace minikin
-
diff --git a/include/minikin/FontCollection.h b/include/minikin/FontCollection.h
index 138ba45..642bb6f 100644
--- a/include/minikin/FontCollection.h
+++ b/include/minikin/FontCollection.h
@@ -21,11 +21,14 @@
#include <unordered_set>
#include <vector>
-#include <minikin/MinikinFont.h>
-#include <minikin/FontFamily.h>
+#include "minikin/FontFamily.h"
+#include "minikin/MinikinFont.h"
namespace minikin {
+// The maximum number of font families.
+constexpr uint32_t MAX_FAMILY_COUNT = 254;
+
class FontCollection {
public:
explicit FontCollection(const std::vector<std::shared_ptr<FontFamily>>& typefaces);
@@ -37,8 +40,8 @@
int end;
};
- void itemize(const uint16_t *string, size_t string_length, FontStyle style,
- std::vector<Run>* result) const;
+ void itemize(const uint16_t* string, size_t string_length, const MinikinPaint& paint,
+ std::vector<Run>* result) const;
// Returns true if there is a glyph for the code point and variation selector pair.
// Returns false if no fonts have a glyph for the code point and variation
@@ -50,12 +53,10 @@
// Creates new FontCollection based on this collection while applying font variations. Returns
// nullptr if none of variations apply to this collection.
- std::shared_ptr<FontCollection>
- createCollectionWithVariation(const std::vector<FontVariation>& variations);
+ std::shared_ptr<FontCollection> createCollectionWithVariation(
+ const std::vector<FontVariation>& variations);
- const std::unordered_set<AxisTag>& getSupportedTags() const {
- return mSupportedAxes;
- }
+ const std::unordered_set<AxisTag>& getSupportedTags() const { return mSupportedAxes; }
uint32_t getId() const;
@@ -77,21 +78,21 @@
void init(const std::vector<std::shared_ptr<FontFamily>>& typefaces);
const std::shared_ptr<FontFamily>& getFamilyForChar(uint32_t ch, uint32_t vs,
- uint32_t langListId, int variant) const;
+ uint32_t localeListId,
+ FontFamily::Variant variant) const;
- uint32_t calcFamilyScore(uint32_t ch, uint32_t vs, int variant, uint32_t langListId,
- const std::shared_ptr<FontFamily>& fontFamily) const;
+ uint32_t calcFamilyScore(uint32_t ch, uint32_t vs, FontFamily::Variant variant,
+ uint32_t localeListId,
+ const std::shared_ptr<FontFamily>& fontFamily) const;
- uint32_t calcCoverageScore(uint32_t ch, uint32_t vs,
- const std::shared_ptr<FontFamily>& fontFamily) const;
+ uint32_t calcCoverageScore(uint32_t ch, uint32_t vs, uint32_t localeListId,
+ const std::shared_ptr<FontFamily>& fontFamily) const;
- static uint32_t calcLanguageMatchingScore(uint32_t userLangListId,
- const FontFamily& fontFamily);
+ static uint32_t calcLocaleMatchingScore(uint32_t userLocaleListId,
+ const FontFamily& fontFamily);
- static uint32_t calcVariantMatchingScore(int variant, const FontFamily& fontFamily);
-
- // static for allocating unique id's
- static uint32_t sNextId;
+ static uint32_t calcVariantMatchingScore(FontFamily::Variant variant,
+ const FontFamily& fontFamily);
// unique id for this font collection (suitable for cache key)
uint32_t mId;
diff --git a/include/minikin/FontFamily.h b/include/minikin/FontFamily.h
index 04c95bc..66fe3a8 100644
--- a/include/minikin/FontFamily.h
+++ b/include/minikin/FontFamily.h
@@ -22,73 +22,25 @@
#include <unordered_set>
#include <vector>
-#include <hb.h>
-
-#include <utils/TypeHelpers.h>
-
-#include <minikin/SparseBitSet.h>
+#include "minikin/FontStyle.h"
+#include "minikin/HbUtils.h"
+#include "minikin/Macros.h"
+#include "minikin/SparseBitSet.h"
namespace minikin {
+class Font;
class MinikinFont;
-// FontStyle represents all style information needed to select an actual font
-// from a collection. The implementation is packed into two 32-bit words
-// so it can be efficiently copied, embedded in other objects, etc.
-class FontStyle {
-public:
- FontStyle() : FontStyle(0 /* variant */, 4 /* weight */, false /* italic */) {}
- FontStyle(int weight, bool italic) : FontStyle(0 /* variant */, weight, italic) {}
- FontStyle(uint32_t langListId) // NOLINT(implicit)
- : FontStyle(langListId, 0 /* variant */, 4 /* weight */, false /* italic */) {}
-
- FontStyle(int variant, int weight, bool italic);
- FontStyle(uint32_t langListId, int variant, int weight, bool italic);
-
- int getWeight() const { return bits & kWeightMask; }
- bool getItalic() const { return (bits & kItalicMask) != 0; }
- int getVariant() const { return (bits >> kVariantShift) & kVariantMask; }
- uint32_t getLanguageListId() const { return mLanguageListId; }
-
- bool operator==(const FontStyle other) const {
- return bits == other.bits && mLanguageListId == other.mLanguageListId;
- }
-
- android::hash_t hash() const;
-
- // Looks up a language list from an internal cache and returns its ID.
- // If the passed language list is not in the cache, registers it and returns newly assigned ID.
- static uint32_t registerLanguageList(const std::string& languages);
-private:
- static const uint32_t kWeightMask = (1 << 4) - 1;
- static const uint32_t kItalicMask = 1 << 4;
- static const int kVariantShift = 5;
- static const uint32_t kVariantMask = (1 << 2) - 1;
-
- static uint32_t pack(int variant, int weight, bool italic);
-
- uint32_t bits;
- uint32_t mLanguageListId;
-};
-
-enum FontVariant {
- VARIANT_DEFAULT = 0,
- VARIANT_COMPACT = 1,
- VARIANT_ELEGANT = 2,
-};
-
-inline android::hash_t hash_type(const FontStyle &style) {
- return style.hash();
-}
-
// attributes representing transforms (fake bold, fake italic) to match styles
class FontFakery {
public:
- FontFakery() : mFakeBold(false), mFakeItalic(false) { }
- FontFakery(bool fakeBold, bool fakeItalic) : mFakeBold(fakeBold), mFakeItalic(fakeItalic) { }
+ FontFakery() : mFakeBold(false), mFakeItalic(false) {}
+ FontFakery(bool fakeBold, bool fakeItalic) : mFakeBold(fakeBold), mFakeItalic(fakeItalic) {}
// TODO: want to support graded fake bolding
bool isFakeBold() { return mFakeBold; }
bool isFakeItalic() { return mFakeItalic; }
+
private:
bool mFakeBold;
bool mFakeItalic;
@@ -96,22 +48,73 @@
struct FakedFont {
// ownership is the enclosing FontCollection
- MinikinFont* font;
+ const Font* font;
FontFakery fakery;
};
typedef uint32_t AxisTag;
-struct Font {
- Font(const std::shared_ptr<MinikinFont>& typeface, FontStyle style);
- Font(std::shared_ptr<MinikinFont>&& typeface, FontStyle style);
- Font(Font&& o);
- Font(const Font& o);
+// Represents a single font file.
+class Font {
+public:
+ class Builder {
+ public:
+ Builder(const std::shared_ptr<MinikinFont>& typeface) : mTypeface(typeface) {}
- std::shared_ptr<MinikinFont> typeface;
- FontStyle style;
+ // Override the font style. If not called, info from OS/2 table is used.
+ Builder& setStyle(FontStyle style) {
+ mWeight = style.weight();
+ mSlant = style.slant();
+ mIsWeightSet = mIsSlantSet = true;
+ return *this;
+ }
- std::unordered_set<AxisTag> getSupportedAxesLocked() const;
+ // Override the font weight. If not called, info from OS/2 table is used.
+ Builder& setWeight(uint16_t weight) {
+ mWeight = weight;
+ mIsWeightSet = true;
+ return *this;
+ }
+
+ // Override the font slant. If not called, info from OS/2 table is used.
+ Builder& setSlant(FontStyle::Slant slant) {
+ mSlant = slant;
+ mIsSlantSet = true;
+ return *this;
+ }
+
+ Font build();
+
+ private:
+ std::shared_ptr<MinikinFont> mTypeface;
+ uint16_t mWeight = static_cast<uint16_t>(FontStyle::Weight::NORMAL);
+ FontStyle::Slant mSlant = FontStyle::Slant::UPRIGHT;
+ bool mIsWeightSet = false;
+ bool mIsSlantSet = false;
+ };
+
+ Font(Font&& o) = default;
+ Font& operator=(Font&& o) = default;
+
+ inline const std::shared_ptr<MinikinFont>& typeface() const { return mTypeface; }
+ inline FontStyle style() const { return mStyle; }
+ inline const HbFontUniquePtr& baseFont() const { return mBaseFont; }
+
+ std::unordered_set<AxisTag> getSupportedAxes() const;
+
+private:
+ // Use Builder instead.
+ Font(std::shared_ptr<MinikinFont>&& typeface, FontStyle style, HbFontUniquePtr&& baseFont)
+ : mTypeface(std::move(typeface)), mStyle(style), mBaseFont(std::move(baseFont)) {}
+
+ static HbFontUniquePtr prepareFont(const std::shared_ptr<MinikinFont>& typeface);
+ static FontStyle analyzeStyle(const HbFontUniquePtr& font);
+
+ std::shared_ptr<MinikinFont> mTypeface;
+ FontStyle mStyle;
+ HbFontUniquePtr mBaseFont;
+
+ MINIKIN_PREVENT_COPY_AND_ASSIGN(Font);
};
struct FontVariation {
@@ -122,25 +125,27 @@
class FontFamily {
public:
- explicit FontFamily(std::vector<Font>&& fonts);
- FontFamily(int variant, std::vector<Font>&& fonts);
- FontFamily(uint32_t langId, int variant, std::vector<Font>&& fonts);
+ // Must be the same value as FontConfig.java
+ enum class Variant : uint8_t {
+ DEFAULT = 0, // Must be the same as FontConfig.VARIANT_DEFAULT
+ COMPACT = 1, // Must be the same as FontConfig.VARIANT_COMPACT
+ ELEGANT = 2, // Must be the same as FontConfig.VARIANT_ELEGANT
+ };
- // TODO: Good to expose FontUtil.h.
- static bool analyzeStyle(const std::shared_ptr<MinikinFont>& typeface, int* weight,
- bool* italic);
+ explicit FontFamily(std::vector<Font>&& fonts);
+ FontFamily(Variant variant, std::vector<Font>&& fonts);
+ FontFamily(uint32_t localeListId, Variant variant, std::vector<Font>&& fonts);
+
FakedFont getClosestMatch(FontStyle style) const;
- uint32_t langId() const { return mLangId; }
- int variant() const { return mVariant; }
+ uint32_t localeListId() const { return mLocaleListId; }
+ Variant variant() const { return mVariant; }
// API's for enumerating the fonts in a family. These don't guarantee any particular order
size_t getNumFonts() const { return mFonts.size(); }
- const std::shared_ptr<MinikinFont>& getFont(size_t index) const {
- return mFonts[index].typeface;
- }
- FontStyle getStyle(size_t index) const { return mFonts[index].style; }
- bool isColorEmojiFamily() const;
+ const Font* getFont(size_t index) const { return &mFonts[index]; }
+ FontStyle getStyle(size_t index) const { return mFonts[index].style(); }
+ bool isColorEmojiFamily() const { return mIsColorEmoji; }
const std::unordered_set<AxisTag>& supportedAxes() const { return mSupportedAxes; }
// Get Unicode coverage.
@@ -161,17 +166,16 @@
private:
void computeCoverage();
- uint32_t mLangId;
- int mVariant;
+ uint32_t mLocaleListId;
+ Variant mVariant;
std::vector<Font> mFonts;
std::unordered_set<AxisTag> mSupportedAxes;
+ bool mIsColorEmoji;
SparseBitSet mCoverage;
std::vector<std::unique_ptr<SparseBitSet>> mCmapFmt14Coverage;
- // Forbid copying and assignment.
- FontFamily(const FontFamily&) = delete;
- void operator=(const FontFamily&) = delete;
+ MINIKIN_PREVENT_COPY_AND_ASSIGN(FontFamily);
};
} // namespace minikin
diff --git a/include/minikin/FontStyle.h b/include/minikin/FontStyle.h
new file mode 100644
index 0000000..73cf427
--- /dev/null
+++ b/include/minikin/FontStyle.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MINIKIN_FONT_STYLE_H
+#define MINIKIN_FONT_STYLE_H
+
+namespace minikin {
+
+// FontStyle represents style information.
+class FontStyle {
+public:
+ enum class Weight : uint16_t {
+ THIN = 100,
+ EXTRA_LIGHT = 200,
+ LIGHT = 300,
+ NORMAL = 400,
+ MEDIUM = 500,
+ SEMI_BOLD = 600,
+ BOLD = 700,
+ EXTRA_BOLD = 800,
+ BLACK = 900,
+ EXTRA_BLACK = 1000,
+ };
+
+ enum class Slant : bool {
+ ITALIC = true,
+ UPRIGHT = false,
+ };
+
+ constexpr FontStyle() : FontStyle(Weight::NORMAL, Slant::UPRIGHT) {}
+ constexpr explicit FontStyle(Weight weight) : FontStyle(weight, Slant::UPRIGHT) {}
+ constexpr explicit FontStyle(Slant slant) : FontStyle(Weight::NORMAL, slant) {}
+ constexpr FontStyle(Weight weight, Slant slant)
+ : FontStyle(static_cast<uint16_t>(weight), slant) {}
+ constexpr FontStyle(uint16_t weight, Slant slant) : mWeight(weight), mSlant(slant) {}
+
+ constexpr uint16_t weight() const { return mWeight; }
+ constexpr Slant slant() const { return mSlant; }
+
+ constexpr bool operator==(const FontStyle& other) const {
+ return weight() == other.weight() && slant() == other.slant();
+ }
+
+ constexpr uint32_t identifier() const {
+ return (static_cast<uint32_t>(weight()) << 16) | static_cast<uint32_t>(slant());
+ }
+
+private:
+ uint16_t mWeight;
+ Slant mSlant;
+};
+
+} // namespace minikin
+
+#endif // MINIKIN_FONT_STYLE_H
diff --git a/include/minikin/GraphemeBreak.h b/include/minikin/GraphemeBreak.h
index f1b5102..8d8e359 100644
--- a/include/minikin/GraphemeBreak.h
+++ b/include/minikin/GraphemeBreak.h
@@ -17,29 +17,25 @@
#ifndef MINIKIN_GRAPHEME_BREAK_H
#define MINIKIN_GRAPHEME_BREAK_H
+#include <cstdint>
+
namespace minikin {
class GraphemeBreak {
public:
// These values must be kept in sync with CURSOR_AFTER etc in Paint.java
- enum MoveOpt {
- AFTER = 0,
- AT_OR_AFTER = 1,
- BEFORE = 2,
- AT_OR_BEFORE = 3,
- AT = 4
- };
+ enum MoveOpt { AFTER = 0, AT_OR_AFTER = 1, BEFORE = 2, AT_OR_BEFORE = 3, AT = 4 };
// Determine whether the given offset is a grapheme break.
// This implementation generally follows Unicode's UTR #29 extended
// grapheme break, with various tweaks.
static bool isGraphemeBreak(const float* advances, const uint16_t* buf, size_t start,
- size_t count, size_t offset);
+ size_t count, size_t offset);
// Matches Android's Java API. Note, return (size_t)-1 for AT to
// signal non-break because unsigned return type.
static size_t getTextRunCursor(const float* advances, const uint16_t* buf, size_t start,
- size_t count, size_t offset, MoveOpt opt);
+ size_t count, size_t offset, MoveOpt opt);
};
} // namespace minikin
diff --git a/include/minikin/HbUtils.h b/include/minikin/HbUtils.h
new file mode 100644
index 0000000..68e4ab8
--- /dev/null
+++ b/include/minikin/HbUtils.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MINIKIN_HB_UTILS_H
+#define MINIKIN_HB_UTILS_H
+
+#include <hb.h>
+#include <memory>
+
+namespace minikin {
+
+struct HbBlobDeleter {
+ void operator()(hb_blob_t* v) { hb_blob_destroy(v); }
+};
+
+struct HbFaceDeleter {
+ void operator()(hb_face_t* v) { hb_face_destroy(v); }
+};
+
+struct HbFontDeleter {
+ void operator()(hb_font_t* v) { hb_font_destroy(v); }
+};
+
+struct HbBufferDeleter {
+ void operator()(hb_buffer_t* v) { hb_buffer_destroy(v); }
+};
+
+using HbBlobUniquePtr = std::unique_ptr<hb_blob_t, HbBlobDeleter>;
+using HbFaceUniquePtr = std::unique_ptr<hb_face_t, HbFaceDeleter>;
+using HbFontUniquePtr = std::unique_ptr<hb_font_t, HbFontDeleter>;
+using HbBufferUniquePtr = std::unique_ptr<hb_buffer_t, HbBufferDeleter>;
+
+} // namespace minikin
+
+#endif // MINIKIN_HB_UTILS_H
diff --git a/include/minikin/Hyphenator.h b/include/minikin/Hyphenator.h
index 2b8ccb7..26c2a19 100644
--- a/include/minikin/Hyphenator.h
+++ b/include/minikin/Hyphenator.h
@@ -18,15 +18,25 @@
* An implementation of Liang's hyphenation algorithm.
*/
-#include "unicode/locid.h"
-#include <memory>
-#include <unordered_map>
-
#ifndef MINIKIN_HYPHENATOR_H
#define MINIKIN_HYPHENATOR_H
+#include <string>
+#include <vector>
+
+#include "minikin/Characters.h"
+#include "minikin/U16StringPiece.h"
+
namespace minikin {
+class Hyphenator;
+
+// Registers the hyphenator.
+// This doesn't take ownership of the hyphenator but we don't need to care about the ownership.
+// In Android, the Hyphenator is allocated in Zygote and never gets released.
+void addHyphenator(const std::string& localeStr, const Hyphenator* hyphenator);
+void addHyphenatorAlias(const std::string& fromLocaleStr, const std::string& toLocaleStr);
+
enum class HyphenationType : uint8_t {
// Note: There are implicit assumptions scattered in the code that DONT_BREAK is 0.
@@ -47,8 +57,8 @@
// as "l-/l".
BREAK_AND_REPLACE_WITH_HYPHEN = 6,
// Break the line, and repeat the hyphen (which is the last character) at the beginning of the
- // next line. Used in Polish, where "czerwono-niebieska" should hyphenate as
- // "czerwono-/-niebieska".
+ // next line. Used in Polish (where "czerwono-niebieska" should hyphenate as
+ // "czerwono-/-niebieska") and Slovenian.
BREAK_AND_INSERT_HYPHEN_AT_NEXT_LINE = 7,
// Break the line, insert a ZWJ and hyphen at the first line, and a ZWJ at the second line.
// This is used in Arabic script, mostly for writing systems of Central Asia. It's our default
@@ -56,62 +66,107 @@
BREAK_AND_INSERT_HYPHEN_AND_ZWJ = 8
};
-// The hyphen edit represents an edit to the string when a word is
-// hyphenated. The most common hyphen edit is adding a "-" at the end
-// of a syllable, but nonstandard hyphenation allows for more choices.
-// Note that a HyphenEdit can hold two types of edits at the same time,
+// The hyphen edit represents an edit to the string when a word is hyphenated.
+// The most common hyphen edit is adding a "-" at the end of a syllable, but nonstandard hyphenation
+// allows for more choices.
// One at the beginning of the string/line and one at the end.
-class HyphenEdit {
-public:
- static const uint32_t NO_EDIT = 0x00;
+enum class EndHyphenEdit : uint8_t {
+ // Note that everything inserting characters must have a value greater than or equal to
+ // INSERT_HYPHEN.
+ NO_EDIT = 0b000,
+ REPLACE_WITH_HYPHEN = 0b001,
- static const uint32_t INSERT_HYPHEN_AT_END = 0x01;
- static const uint32_t INSERT_ARMENIAN_HYPHEN_AT_END = 0x02;
- static const uint32_t INSERT_MAQAF_AT_END = 0x03;
- static const uint32_t INSERT_UCAS_HYPHEN_AT_END = 0x04;
- static const uint32_t INSERT_ZWJ_AND_HYPHEN_AT_END = 0x05;
- static const uint32_t REPLACE_WITH_HYPHEN_AT_END = 0x06;
- static const uint32_t BREAK_AT_END = 0x07;
-
- static const uint32_t INSERT_HYPHEN_AT_START = 0x01 << 3;
- static const uint32_t INSERT_ZWJ_AT_START = 0x02 << 3;
- static const uint32_t BREAK_AT_START = 0x03 << 3;
-
- // Keep in sync with the definitions in the Java code at:
- // frameworks/base/graphics/java/android/graphics/Paint.java
- static const uint32_t MASK_END_OF_LINE = 0x07;
- static const uint32_t MASK_START_OF_LINE = 0x03 << 3;
-
- inline static bool isReplacement(uint32_t hyph) {
- return hyph == REPLACE_WITH_HYPHEN_AT_END;
- }
-
- inline static bool isInsertion(uint32_t hyph) {
- return (hyph == INSERT_HYPHEN_AT_END
- || hyph == INSERT_ARMENIAN_HYPHEN_AT_END
- || hyph == INSERT_MAQAF_AT_END
- || hyph == INSERT_UCAS_HYPHEN_AT_END
- || hyph == INSERT_ZWJ_AND_HYPHEN_AT_END
- || hyph == INSERT_HYPHEN_AT_START
- || hyph == INSERT_ZWJ_AT_START);
- }
-
- const static uint32_t* getHyphenString(uint32_t hyph);
- static uint32_t editForThisLine(HyphenationType type);
- static uint32_t editForNextLine(HyphenationType type);
-
- HyphenEdit() : hyphen(NO_EDIT) { }
- HyphenEdit(uint32_t hyphenInt) : hyphen(hyphenInt) { } // NOLINT(implicit)
- uint32_t getHyphen() const { return hyphen; }
- bool operator==(const HyphenEdit &other) const { return hyphen == other.hyphen; }
-
- uint32_t getEnd() const { return hyphen & MASK_END_OF_LINE; }
- uint32_t getStart() const { return hyphen & MASK_START_OF_LINE; }
-
-private:
- uint32_t hyphen;
+ INSERT_HYPHEN = 0b010,
+ INSERT_ARMENIAN_HYPHEN = 0b011,
+ INSERT_MAQAF = 0b100,
+ INSERT_UCAS_HYPHEN = 0b101,
+ INSERT_ZWJ_AND_HYPHEN = 0b110,
};
+enum class StartHyphenEdit : uint8_t {
+ NO_EDIT = 0b00,
+
+ INSERT_HYPHEN = 0b01,
+ INSERT_ZWJ = 0b10,
+};
+
+typedef uint8_t HyphenEdit;
+constexpr uint8_t START_BITS_SHIFT = 3;
+// The following two masks must keep in sync with the definitions in the Java code at:
+// frameworks/base/graphics/java/android/graphics/Paint.java
+constexpr uint8_t MASK_END_OF_LINE = 0b00111;
+constexpr uint8_t MASK_START_OF_LINE = 0b11000;
+
+inline HyphenEdit packHyphenEdit(StartHyphenEdit start, EndHyphenEdit end) {
+ return static_cast<uint8_t>(start) << START_BITS_SHIFT | static_cast<uint8_t>(end);
+}
+
+inline EndHyphenEdit endHyphenEdit(HyphenEdit hyphenEdit) {
+ return static_cast<EndHyphenEdit>(hyphenEdit & MASK_END_OF_LINE);
+}
+
+inline StartHyphenEdit startHyphenEdit(HyphenEdit hyphenEdit) {
+ return static_cast<StartHyphenEdit>(hyphenEdit >> START_BITS_SHIFT);
+}
+
+inline bool isReplacement(EndHyphenEdit hyph) {
+ return hyph == EndHyphenEdit::REPLACE_WITH_HYPHEN;
+}
+
+inline bool isInsertion(StartHyphenEdit hyph) {
+ return hyph != StartHyphenEdit::NO_EDIT;
+}
+
+inline bool isInsertion(EndHyphenEdit hyph) {
+ return static_cast<uint8_t>(hyph) >= static_cast<uint8_t>(EndHyphenEdit::INSERT_HYPHEN);
+}
+
+template <typename T, size_t size>
+constexpr size_t ARRAYSIZE(T const (&)[size]) {
+ return size;
+}
+constexpr uint32_t HYPHEN_STR_ZWJ[] = {CHAR_ZWJ};
+constexpr uint32_t HYPHEN_STR_HYPHEN[] = {CHAR_HYPHEN};
+constexpr uint32_t HYPHEN_STR_ARMENIAN_HYPHEN[] = {CHAR_ARMENIAN_HYPHEN};
+constexpr uint32_t HYPHEN_STR_MAQAF[] = {CHAR_MAQAF};
+constexpr uint32_t HYPHEN_STR_UCAS_HYPHEN[] = {CHAR_UCAS_HYPHEN};
+constexpr uint32_t HYPHEN_STR_ZWJ_AND_HYPHEN[] = {CHAR_ZWJ, CHAR_HYPHEN};
+constexpr std::pair<const uint32_t*, size_t> EMPTY_HYPHEN_STR(nullptr, 0);
+#define MAKE_HYPHEN_STR(chars) std::make_pair((chars), ARRAYSIZE(chars))
+
+inline std::pair<const uint32_t*, size_t> getHyphenString(StartHyphenEdit hyph) {
+ if (hyph == StartHyphenEdit::INSERT_ZWJ) {
+ return MAKE_HYPHEN_STR(HYPHEN_STR_ZWJ);
+ } else if (hyph == StartHyphenEdit::INSERT_HYPHEN) {
+ return MAKE_HYPHEN_STR(HYPHEN_STR_HYPHEN);
+ } else {
+ return EMPTY_HYPHEN_STR;
+ }
+}
+
+inline std::pair<const uint32_t*, size_t> getHyphenString(EndHyphenEdit hyph) {
+ switch (hyph) {
+ case EndHyphenEdit::REPLACE_WITH_HYPHEN: // fall through
+ case EndHyphenEdit::INSERT_HYPHEN:
+ return MAKE_HYPHEN_STR(HYPHEN_STR_HYPHEN);
+ case EndHyphenEdit::INSERT_ARMENIAN_HYPHEN:
+ return MAKE_HYPHEN_STR(HYPHEN_STR_ARMENIAN_HYPHEN);
+ case EndHyphenEdit::INSERT_MAQAF:
+ return MAKE_HYPHEN_STR(HYPHEN_STR_MAQAF);
+ case EndHyphenEdit::INSERT_UCAS_HYPHEN:
+ return MAKE_HYPHEN_STR(HYPHEN_STR_UCAS_HYPHEN);
+ case EndHyphenEdit::INSERT_ZWJ_AND_HYPHEN:
+ return MAKE_HYPHEN_STR(HYPHEN_STR_ZWJ_AND_HYPHEN);
+ case EndHyphenEdit::NO_EDIT:
+ default:
+ return EMPTY_HYPHEN_STR;
+ }
+}
+#undef MAKE_HYPHEN_STR
+
+EndHyphenEdit editForThisLine(HyphenationType type);
+StartHyphenEdit editForNextLine(HyphenationType type);
+
// hyb file header; implementation details are in the .cpp file
struct Header;
@@ -121,10 +176,19 @@
// the vector is a "hyphenation type" for a potential hyphenation that can be applied at the
// corresponding code unit offset in the word.
//
+ // out must have at least the length of the word capacity.
+ //
// Example: word is "hyphen", result is the following, corresponding to "hy-phen":
// [DONT_BREAK, DONT_BREAK, BREAK_AND_INSERT_HYPHEN, DONT_BREAK, DONT_BREAK, DONT_BREAK]
- void hyphenate(std::vector<HyphenationType>* result, const uint16_t* word, size_t len,
- const icu::Locale& locale);
+ void hyphenate(const U16StringPiece& word, HyphenationType* out) const;
+
+ // Compute the hyphenation of a word.
+ //
+ // out will be resized to word length.
+ void hyphenate(const U16StringPiece& word, std::vector<HyphenationType>* out) const {
+ out->resize(word.size());
+ return hyphenate(word, out->data());
+ }
// Returns true if the codepoint is like U+2010 HYPHEN in line breaking and usage: a character
// immediately after which line breaks are allowed, but words containing it should not be
@@ -135,39 +199,50 @@
// the caller is responsible for ensuring that the lifetime of the pattern data is
// at least as long as the Hyphenator object.
+ // This class doesn't copy or take ownership of patternData. Caller must keep the data valid
+ // until this instance is deleted.
// Note: nullptr is valid input, in which case the hyphenator only processes soft hyphens.
- static Hyphenator* loadBinary(const uint8_t* patternData, size_t minPrefix, size_t minSuffix);
+ static Hyphenator* loadBinary(const uint8_t* patternData, size_t minPrefix, size_t minSuffix,
+ const std::string& locale);
private:
+ enum class HyphenationLocale : uint8_t {
+ OTHER = 0,
+ CATALAN = 1,
+ POLISH = 2,
+ SLOVENIAN = 3,
+ };
+
+ // Use Hyphenator::loadBinary instead.
+ Hyphenator(const uint8_t* patternData, size_t minPrefix, size_t minSuffix,
+ HyphenationLocale hyphenLocale);
+
// apply various hyphenation rules including hard and soft hyphens, ignoring patterns
- void hyphenateWithNoPatterns(HyphenationType* result, const uint16_t* word, size_t len,
- const icu::Locale& locale);
+ void hyphenateWithNoPatterns(const U16StringPiece& word, HyphenationType* out) const;
// Try looking up word in alphabet table, return DONT_BREAK if any code units fail to map.
// Otherwise, returns BREAK_AND_INSERT_HYPHEN, BREAK_AND_INSERT_ARMENIAN_HYPHEN, or
// BREAK_AND_DONT_INSERT_HYPHEN based on the the script of the characters seen.
// Note that this method writes len+2 entries into alpha_codes (including start and stop)
- HyphenationType alphabetLookup(uint16_t* alpha_codes, const uint16_t* word, size_t len);
+ HyphenationType alphabetLookup(uint16_t* alpha_codes, const U16StringPiece& word) const;
// calculate hyphenation from patterns, assuming alphabet lookup has already been done
- void hyphenateFromCodes(HyphenationType* result, const uint16_t* codes, size_t len,
- HyphenationType hyphenValue);
+ void hyphenateFromCodes(const uint16_t* codes, size_t len, HyphenationType hyphenValue,
+ HyphenationType* out) const;
// See also LONGEST_HYPHENATED_WORD in LineBreaker.cpp. Here the constant is used so
// that temporary buffers can be stack-allocated without waste, which is a slightly
// different use case. It measures UTF-16 code units.
static const size_t MAX_HYPHENATED_SIZE = 64;
- const uint8_t* patternData;
- size_t minPrefix, minSuffix;
+ const uint8_t* mPatternData;
+ const size_t mMinPrefix, mMinSuffix;
+ const HyphenationLocale mHyphenationLocale;
// accessors for binary data
- const Header* getHeader() const {
- return reinterpret_cast<const Header*>(patternData);
- }
-
+ const Header* getHeader() const { return reinterpret_cast<const Header*>(mPatternData); }
};
} // namespace minikin
-#endif // MINIKIN_HYPHENATOR_H
+#endif // MINIKIN_HYPHENATOR_H
diff --git a/include/minikin/Layout.h b/include/minikin/Layout.h
index 6d1de2f..ba32d98 100644
--- a/include/minikin/Layout.h
+++ b/include/minikin/Layout.h
@@ -17,15 +17,22 @@
#ifndef MINIKIN_LAYOUT_H
#define MINIKIN_LAYOUT_H
-#include <hb.h>
-
#include <memory>
+#include <unordered_map>
#include <vector>
-#include <minikin/FontCollection.h>
+#include <gtest/gtest_prod.h>
+#include <utils/JenkinsHash.h>
+
+#include "minikin/FontCollection.h"
+#include "minikin/Range.h"
+#include "minikin/U16StringPiece.h"
namespace minikin {
+class Layout;
+struct LayoutPieces;
+
struct LayoutGlyph {
// index into mFaces and mHbFonts vectors. We could imagine
// moving this into a run length representation, because it's
@@ -39,45 +46,64 @@
float y;
};
-// Internal state used during layout operation
-struct LayoutContext;
-
-enum {
- kBidi_LTR = 0,
- kBidi_RTL = 1,
- kBidi_Default_LTR = 2,
- kBidi_Default_RTL = 3,
- kBidi_Force_LTR = 4,
- kBidi_Force_RTL = 5,
-
- kBidi_Mask = 0x7
+// Must be the same value with Paint.java
+enum class Bidi : uint8_t {
+ LTR = 0b0000, // Must be same with Paint.BIDI_LTR
+ RTL = 0b0001, // Must be same with Paint.BIDI_RTL
+ DEFAULT_LTR = 0b0010, // Must be same with Paint.BIDI_DEFAULT_LTR
+ DEFAULT_RTL = 0b0011, // Must be same with Paint.BIDI_DEFAULT_RTL
+ FORCE_LTR = 0b0100, // Must be same with Paint.BIDI_FORCE_LTR
+ FORCE_RTL = 0b0101, // Must be same with Paint.BIDI_FORCE_RTL
};
+inline bool isRtl(Bidi bidi) {
+ return static_cast<uint8_t>(bidi) & 0b0001;
+}
+inline bool isOverride(Bidi bidi) {
+ return static_cast<uint8_t>(bidi) & 0b0100;
+}
+
// Lifecycle and threading assumptions for Layout:
// The object is assumed to be owned by a single thread; multiple threads
// may not mutate it at the same time.
class Layout {
public:
-
- Layout() : mGlyphs(), mAdvances(), mFaces(), mAdvance(0), mBounds() {
+ Layout()
+ : mGlyphs(),
+ mAdvances(),
+ mExtents(),
+ mFaces(),
+ mAdvance(0),
+ mBounds() {
mBounds.setEmpty();
}
Layout(Layout&& layout) = default;
- // Forbid copying and assignment.
- Layout(const Layout&) = delete;
- void operator=(const Layout&) = delete;
+ Layout(const Layout&) = default;
+ Layout& operator=(const Layout&) = default;
void dump() const;
- void doLayout(const uint16_t* buf, size_t start, size_t count, size_t bufSize,
- int bidiFlags, const FontStyle &style, const MinikinPaint &paint,
- const std::shared_ptr<FontCollection>& collection);
+ void doLayout(const U16StringPiece& str, const Range& range, Bidi bidiFlags,
+ const MinikinPaint& paint, StartHyphenEdit startHyphen, EndHyphenEdit endHyphen);
- static float measureText(const uint16_t* buf, size_t start, size_t count, size_t bufSize,
- int bidiFlags, const FontStyle &style, const MinikinPaint &paint,
- const std::shared_ptr<FontCollection>& collection, float* advances);
+ void doLayoutWithPrecomputedPieces(const U16StringPiece& str, const Range& range,
+ Bidi bidiFlags, const MinikinPaint& paint,
+ StartHyphenEdit startHyphen, EndHyphenEdit endHyphen,
+ const LayoutPieces& pieces);
+ static std::pair<float, MinikinRect> getBoundsWithPrecomputedPieces(const U16StringPiece& str,
+ const Range& range,
+ Bidi bidiFlags,
+ const MinikinPaint& paint,
+ const LayoutPieces& pieces);
+
+ static float measureText(const U16StringPiece& str, const Range& range, Bidi bidiFlags,
+ const MinikinPaint& paint, StartHyphenEdit startHyphen,
+ EndHyphenEdit endHyphen, float* advances, MinikinExtent* extents,
+ LayoutPieces* pieces);
+
+ inline const std::vector<float>& advances() const { return mAdvances; }
// public accessors
size_t nGlyphs() const;
@@ -91,22 +117,42 @@
// Get advances, copying into caller-provided buffer. The size of this
// buffer must match the length of the string (count arg to doLayout).
- void getAdvances(float* advances);
+ void getAdvances(float* advances) const;
+
+ // Get extents, copying into caller-provided buffer. The size of this buffer must match the
+ // length of the string (count arg to doLayout).
+ void getExtents(MinikinExtent* extents) const;
// The i parameter is an offset within the buf relative to start, it is < count, where
// start and count are the parameters to doLayout
float getCharAdvance(size_t i) const { return mAdvances[i]; }
void getBounds(MinikinRect* rect) const;
+ const MinikinRect& getBounds() const { return mBounds; }
// Purge all caches, useful in low memory conditions
static void purgeCaches();
+ // Dump minikin internal statistics, cache usage, cache hit ratio, etc.
+ static void dumpMinikinStats(int fd);
+
+ uint32_t getMemoryUsage() const {
+ return sizeof(LayoutGlyph) * nGlyphs() + sizeof(float) * mAdvances.size() +
+ sizeof(MinikinExtent) * mExtents.size() + sizeof(FakedFont) * mFaces.size() +
+ sizeof(float /* mAdvance */) + sizeof(MinikinRect /* mBounds */);
+ }
+
+ // Append another layout (for example, cached value) into this one
+ void appendLayout(const Layout& src, size_t start, float extraAdvance);
+
private:
friend class LayoutCacheKey;
+ friend class LayoutCache;
- // Find a face in the mFaces vector, or create a new entry
- int findFace(const FakedFont& face, LayoutContext* ctx);
+ FRIEND_TEST(LayoutTest, doLayoutWithPrecomputedPiecesTest);
+
+ // Find a face in the mFaces vector. If not found, push back the entry to mFaces.
+ uint8_t findOrPushBackFace(const FakedFont& face);
// Clears layout, ready to be used again
void reset();
@@ -114,24 +160,31 @@
// Lay out a single bidi run
// When layout is not null, layout info will be stored in the object.
// When advances is not null, measurement results will be stored in the array.
- static float doLayoutRunCached(const uint16_t* buf, size_t runStart, size_t runLength,
- size_t bufSize, bool isRtl, LayoutContext* ctx, size_t dstStart,
- const std::shared_ptr<FontCollection>& collection, Layout* layout, float* advances);
+ static float doLayoutRunCached(const U16StringPiece& textBuf, const Range& range, bool isRtl,
+ const MinikinPaint& paint, size_t dstStart,
+ StartHyphenEdit startHyphen, EndHyphenEdit endHyphen,
+ const LayoutPieces* lpIn, Layout* layout, float* advances,
+ MinikinExtent* extents, MinikinRect* bounds,
+ LayoutPieces* lpOut);
// Lay out a single word
static float doLayoutWord(const uint16_t* buf, size_t start, size_t count, size_t bufSize,
- bool isRtl, LayoutContext* ctx, size_t bufStart,
- const std::shared_ptr<FontCollection>& collection, Layout* layout, float* advances);
+ bool isRtl, const MinikinPaint& paint, size_t bufStart,
+ StartHyphenEdit startHyphen, EndHyphenEdit endHyphen,
+ const LayoutPieces* lpIn, Layout* layout, float* advances,
+ MinikinExtent* extents, MinikinRect* bounds, LayoutPieces* lpOut);
// Lay out a single bidi run
- void doLayoutRun(const uint16_t* buf, size_t start, size_t count, size_t bufSize,
- bool isRtl, LayoutContext* ctx, const std::shared_ptr<FontCollection>& collection);
-
- // Append another layout (for example, cached value) into this one
- void appendLayout(Layout* src, size_t start, float extraAdvance);
+ void doLayoutRun(const uint16_t* buf, size_t start, size_t count, size_t bufSize, bool isRtl,
+ const MinikinPaint& paint, StartHyphenEdit startHyphen,
+ EndHyphenEdit endHyphen);
std::vector<LayoutGlyph> mGlyphs;
+
+ // The following three vectors are defined per code unit, so their length is identical to the
+ // input text.
std::vector<float> mAdvances;
+ std::vector<MinikinExtent> mExtents;
std::vector<FakedFont> mFaces;
float mAdvance;
diff --git a/include/minikin/LayoutCache.h b/include/minikin/LayoutCache.h
new file mode 100644
index 0000000..e99fbe4
--- /dev/null
+++ b/include/minikin/LayoutCache.h
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MINIKIN_LAYOUT_CACHE_H
+#define MINIKIN_LAYOUT_CACHE_H
+
+#include "minikin/Layout.h"
+
+#include <mutex>
+
+#include <utils/JenkinsHash.h>
+#include <utils/LruCache.h>
+
+namespace minikin {
+
+// Layout cache datatypes
+class LayoutCacheKey {
+public:
+ LayoutCacheKey(const U16StringPiece& text, const Range& range, const MinikinPaint& paint,
+ bool dir, StartHyphenEdit startHyphen, EndHyphenEdit endHyphen)
+ : mChars(text.data()),
+ mNchars(text.size()),
+ mStart(range.getStart()),
+ mCount(range.getLength()),
+ mId(paint.font->getId()),
+ mStyle(paint.fontStyle),
+ mSize(paint.size),
+ mScaleX(paint.scaleX),
+ mSkewX(paint.skewX),
+ mLetterSpacing(paint.letterSpacing),
+ mWordSpacing(paint.wordSpacing),
+ mPaintFlags(paint.paintFlags),
+ mLocaleListId(paint.localeListId),
+ mFamilyVariant(paint.familyVariant),
+ mStartHyphen(startHyphen),
+ mEndHyphen(endHyphen),
+ mIsRtl(dir),
+ mHash(computeHash()) {}
+
+ bool operator==(const LayoutCacheKey& o) const {
+ return mId == o.mId && mStart == o.mStart && mCount == o.mCount && mStyle == o.mStyle &&
+ mSize == o.mSize && mScaleX == o.mScaleX && mSkewX == o.mSkewX &&
+ mLetterSpacing == o.mLetterSpacing && mWordSpacing == o.mWordSpacing &&
+ mPaintFlags == o.mPaintFlags && mLocaleListId == o.mLocaleListId &&
+ mFamilyVariant == o.mFamilyVariant && mStartHyphen == o.mStartHyphen &&
+ mEndHyphen == o.mEndHyphen && mIsRtl == o.mIsRtl && mNchars == o.mNchars &&
+ !memcmp(mChars, o.mChars, mNchars * sizeof(uint16_t));
+ }
+
+ android::hash_t hash() const { return mHash; }
+
+ void copyText() {
+ uint16_t* charsCopy = new uint16_t[mNchars];
+ memcpy(charsCopy, mChars, mNchars * sizeof(uint16_t));
+ mChars = charsCopy;
+ }
+ void freeText() {
+ delete[] mChars;
+ mChars = NULL;
+ }
+
+ void doLayout(Layout* layout, const MinikinPaint& paint) const {
+ layout->mAdvances.resize(mCount, 0);
+ layout->mExtents.resize(mCount);
+ layout->doLayoutRun(mChars, mStart, mCount, mNchars, mIsRtl, paint, mStartHyphen,
+ mEndHyphen);
+ }
+
+ uint32_t getMemoryUsage() const { return sizeof(LayoutCacheKey) + sizeof(uint16_t) * mNchars; }
+
+private:
+ const uint16_t* mChars;
+ size_t mNchars;
+ size_t mStart;
+ size_t mCount;
+ uint32_t mId; // for the font collection
+ FontStyle mStyle;
+ float mSize;
+ float mScaleX;
+ float mSkewX;
+ float mLetterSpacing;
+ float mWordSpacing;
+ int32_t mPaintFlags;
+ uint32_t mLocaleListId;
+ FontFamily::Variant mFamilyVariant;
+ StartHyphenEdit mStartHyphen;
+ EndHyphenEdit mEndHyphen;
+ bool mIsRtl;
+ // Note: any fields added to MinikinPaint must also be reflected here.
+ // TODO: language matching (possibly integrate into style)
+ android::hash_t mHash;
+
+ android::hash_t computeHash() const {
+ uint32_t hash = android::JenkinsHashMix(0, mId);
+ hash = android::JenkinsHashMix(hash, mStart);
+ hash = android::JenkinsHashMix(hash, mCount);
+ hash = android::JenkinsHashMix(hash, android::hash_type(mStyle.identifier()));
+ hash = android::JenkinsHashMix(hash, android::hash_type(mSize));
+ hash = android::JenkinsHashMix(hash, android::hash_type(mScaleX));
+ hash = android::JenkinsHashMix(hash, android::hash_type(mSkewX));
+ hash = android::JenkinsHashMix(hash, android::hash_type(mLetterSpacing));
+ hash = android::JenkinsHashMix(hash, android::hash_type(mWordSpacing));
+ hash = android::JenkinsHashMix(hash, android::hash_type(mPaintFlags));
+ hash = android::JenkinsHashMix(hash, android::hash_type(mLocaleListId));
+ hash = android::JenkinsHashMix(hash,
+ android::hash_type(static_cast<uint8_t>(mFamilyVariant)));
+ hash = android::JenkinsHashMix(
+ hash,
+ android::hash_type(static_cast<uint8_t>(packHyphenEdit(mStartHyphen, mEndHyphen))));
+ hash = android::JenkinsHashMix(hash, android::hash_type(mIsRtl));
+ hash = android::JenkinsHashMixShorts(hash, mChars, mNchars);
+ return android::JenkinsHashWhiten(hash);
+ }
+};
+
+class LayoutCache : private android::OnEntryRemoved<LayoutCacheKey, Layout*> {
+public:
+ void clear() {
+ std::lock_guard<std::mutex> lock(mMutex);
+ mCache.clear();
+ }
+
+ // Do not use LayoutCache inside the callback function, otherwise dead-lock may happen.
+ template <typename F>
+ void getOrCreate(const U16StringPiece& text, const Range& range, const MinikinPaint& paint,
+ bool dir, StartHyphenEdit startHyphen, EndHyphenEdit endHyphen, F& f) {
+ LayoutCacheKey key(text, range, paint, dir, startHyphen, endHyphen);
+ if (paint.skipCache()) {
+ Layout layoutForWord;
+ key.doLayout(&layoutForWord, paint);
+ f(layoutForWord);
+ return;
+ }
+
+ mRequestCount++;
+ {
+ std::lock_guard<std::mutex> lock(mMutex);
+ Layout* layout = mCache.get(key);
+ if (layout != nullptr) {
+ mCacheHitCount++;
+ f(*layout);
+ return;
+ }
+ }
+ // Doing text layout takes long time, so releases the mutex during doing layout.
+ // Don't care even if we do the same layout in other thred.
+ key.copyText();
+ std::unique_ptr<Layout> layout = std::make_unique<Layout>();
+ key.doLayout(layout.get(), paint);
+ f(*layout);
+ {
+ std::lock_guard<std::mutex> lock(mMutex);
+ mCache.put(key, layout.release());
+ }
+ }
+
+ void dumpStats(int fd) {
+ std::lock_guard<std::mutex> lock(mMutex);
+ dprintf(fd, "\nLayout Cache Info:\n");
+ dprintf(fd, " Usage: %zd/%zd entries\n", mCache.size(), kMaxEntries);
+ float ratio = (mRequestCount == 0) ? 0 : mCacheHitCount / (float)mRequestCount;
+ dprintf(fd, " Hit ratio: %d/%d (%f)\n", mCacheHitCount, mRequestCount, ratio);
+ }
+
+ static LayoutCache& getInstance() {
+ static LayoutCache cache(kMaxEntries);
+ return cache;
+ }
+
+protected:
+ LayoutCache(uint32_t maxEntries) : mCache(maxEntries), mRequestCount(0), mCacheHitCount(0) {
+ mCache.setOnEntryRemovedListener(this);
+ }
+
+private:
+ // callback for OnEntryRemoved
+ void operator()(LayoutCacheKey& key, Layout*& value) {
+ key.freeText();
+ delete value;
+ }
+
+ android::LruCache<LayoutCacheKey, Layout*> mCache GUARDED_BY(mMutex);
+
+ int32_t mRequestCount;
+ int32_t mCacheHitCount;
+
+ // static const size_t kMaxEntries = LruCache<LayoutCacheKey, Layout*>::kUnlimitedCapacity;
+
+ // TODO: eviction based on memory footprint; for now, we just use a constant
+ // number of strings
+ static const size_t kMaxEntries = 5000;
+
+ std::mutex mMutex;
+};
+
+inline android::hash_t hash_type(const LayoutCacheKey& key) {
+ return key.hash();
+}
+
+} // namespace minikin
+#endif // MINIKIN_LAYOUT_CACHE_H
diff --git a/include/minikin/LayoutPieces.h b/include/minikin/LayoutPieces.h
new file mode 100644
index 0000000..f581372
--- /dev/null
+++ b/include/minikin/LayoutPieces.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MINIKIN_LAYOUT_PIECES_H
+#define MINIKIN_LAYOUT_PIECES_H
+
+#include <unordered_map>
+
+#include "minikin/Layout.h"
+#include "minikin/LayoutCache.h"
+
+namespace minikin {
+
+struct LayoutPieces {
+ struct KeyHasher {
+ std::size_t operator()(const LayoutCacheKey& key) const { return key.hash(); }
+ };
+
+ LayoutPieces() {}
+
+ ~LayoutPieces() {
+ for (const auto it : offsetMap) {
+ const_cast<LayoutCacheKey*>(&it.first)->freeText();
+ }
+ }
+
+ std::unordered_map<LayoutCacheKey, Layout, KeyHasher> offsetMap;
+
+ void insert(const U16StringPiece& textBuf, const Range& range, const MinikinPaint& paint,
+ bool dir, StartHyphenEdit startEdit, EndHyphenEdit endEdit, const Layout& layout) {
+ auto result = offsetMap.emplace(
+ std::piecewise_construct,
+ std::forward_as_tuple(textBuf, range, paint, dir, startEdit, endEdit),
+ std::forward_as_tuple(layout));
+ if (result.second) {
+ const_cast<LayoutCacheKey*>(&result.first->first)->copyText();
+ }
+ }
+
+ template <typename F>
+ void getOrCreate(const U16StringPiece& textBuf, const Range& range, const MinikinPaint& paint,
+ bool dir, StartHyphenEdit startEdit, EndHyphenEdit endEdit, F& f) const {
+ auto it = offsetMap.find(LayoutCacheKey(textBuf, range, paint, dir, startEdit, endEdit));
+ if (it == offsetMap.end()) {
+ LayoutCache::getInstance().getOrCreate(textBuf, range, paint, dir, startEdit, endEdit,
+ f);
+ } else {
+ f(it->second);
+ }
+ }
+
+ uint32_t getMemoryUsage() const {
+ uint32_t result = 0;
+ for (const auto& i : offsetMap) {
+ result += i.first.getMemoryUsage() + i.second.getMemoryUsage();
+ }
+ return result;
+ }
+};
+
+} // namespace minikin
+
+#endif // MINIKIN_LAYOUT_PIECES_H
diff --git a/include/minikin/LineBreaker.h b/include/minikin/LineBreaker.h
index d7a20e9..2974c20 100644
--- a/include/minikin/LineBreaker.h
+++ b/include/minikin/LineBreaker.h
@@ -22,243 +22,107 @@
#ifndef MINIKIN_LINE_BREAKER_H
#define MINIKIN_LINE_BREAKER_H
-#include <gtest/gtest_prod.h>
-#include "unicode/brkiter.h"
-#include "unicode/locid.h"
-#include <cmath>
+#include <deque>
#include <vector>
+
#include "minikin/FontCollection.h"
-#include "minikin/Hyphenator.h"
+#include "minikin/Layout.h"
+#include "minikin/Macros.h"
+#include "minikin/MeasuredText.h"
#include "minikin/MinikinFont.h"
-#include "minikin/WordBreaker.h"
+#include "minikin/Range.h"
+#include "minikin/U16StringPiece.h"
namespace minikin {
-enum BreakStrategy {
- kBreakStrategy_Greedy = 0,
- kBreakStrategy_HighQuality = 1,
- kBreakStrategy_Balanced = 2
+enum class BreakStrategy : uint8_t {
+ Greedy = 0,
+ HighQuality = 1,
+ Balanced = 2,
};
-enum HyphenationFrequency {
- kHyphenationFrequency_None = 0,
- kHyphenationFrequency_Normal = 1,
- kHyphenationFrequency_Full = 2
+enum class HyphenationFrequency : uint8_t {
+ None = 0,
+ Normal = 1,
+ Full = 2,
};
-// TODO: want to generalize to be able to handle array of line widths
-class LineWidths {
- public:
- void setWidths(float firstWidth, int firstWidthLineCount, float restWidth) {
- mFirstWidth = firstWidth;
- mFirstWidthLineCount = firstWidthLineCount;
- mRestWidth = restWidth;
- }
- void setIndents(const std::vector<float>& indents) {
- mIndents = indents;
- }
- bool isConstant() const {
- // technically mFirstWidthLineCount == 0 would count too, but doesn't actually happen
- return mRestWidth == mFirstWidth && mIndents.empty();
- }
- float getLineWidth(int line) const {
- float width = (line < mFirstWidthLineCount) ? mFirstWidth : mRestWidth;
- if (!mIndents.empty()) {
- if ((size_t)line < mIndents.size()) {
- width -= mIndents[line];
- } else {
- width -= mIndents.back();
- }
- }
- return width;
- }
- void clear() {
- mIndents.clear();
- }
- private:
- float mFirstWidth;
- int mFirstWidthLineCount;
- float mRestWidth;
- std::vector<float> mIndents;
-};
+class Hyphenator;
+class WordBreaker;
class TabStops {
- public:
- void set(const int* stops, size_t nStops, int tabWidth) {
- if (stops != nullptr) {
- mStops.assign(stops, stops + nStops);
- } else {
- mStops.clear();
+public:
+ // Caller must free stops. stops can be nullprt.
+ TabStops(const int32_t* stops, size_t nStops, int32_t tabWidth)
+ : mStops(stops), mStopsSize(nStops), mTabWidth(tabWidth) {}
+
+ float nextTab(float widthSoFar) const {
+ for (size_t i = 0; i < mStopsSize; i++) {
+ if (mStops[i] > widthSoFar) {
+ return mStops[i];
}
- mTabWidth = tabWidth;
}
- float nextTab(float widthSoFar) const {
- for (size_t i = 0; i < mStops.size(); i++) {
- if (mStops[i] > widthSoFar) {
- return mStops[i];
- }
- }
- return floor(widthSoFar / mTabWidth + 1) * mTabWidth;
- }
- private:
- std::vector<int> mStops;
- int mTabWidth;
+ return floor(widthSoFar / mTabWidth + 1) * mTabWidth;
+ }
+
+private:
+ const int32_t* mStops;
+ size_t mStopsSize;
+ int32_t mTabWidth;
};
-class LineBreaker {
- public:
- const static int kTab_Shift = 29; // keep synchronized with TAB_MASK in StaticLayout.java
+// Implement this for the additional information during line breaking.
+// The functions in this class's interface may be called several times. The implementation
+// must return the same value for the same input.
+class LineWidth {
+public:
+ virtual ~LineWidth() {}
- // Note: Locale persists across multiple invocations (it is not cleaned up by finish()),
- // explicitly to avoid the cost of creating ICU BreakIterator objects. It should always
- // be set on the first invocation, but callers are encouraged not to call again unless
- // locale has actually changed.
- // That logic could be here but it's better for performance that it's upstream because of
- // the cost of constructing and comparing the ICU Locale object.
- // Note: caller is responsible for managing lifetime of hyphenator
- void setLocales(const char* locales, const std::vector<Hyphenator*>& hyphenators);
+ // Called to find out the width for the line. This must not return negative values.
+ virtual float getAt(size_t lineNo) const = 0;
- void resize(size_t size) {
- mTextBuf.resize(size);
- mCharWidths.resize(size);
- }
+ // Called to find out the minimum line width. This mut not return negative values.
+ virtual float getMin() const = 0;
- size_t size() const {
- return mTextBuf.size();
- }
+ // Called to find out the available left-side padding for the line.
+ virtual float getLeftPaddingAt(size_t lineNo) const = 0;
- uint16_t* buffer() {
- return mTextBuf.data();
- }
-
- float* charWidths() {
- return mCharWidths.data();
- }
-
- // set text to current contents of buffer
- void setText();
-
- void setLineWidths(float firstWidth, int firstWidthLineCount, float restWidth);
-
- void setIndents(const std::vector<float>& indents);
-
- void setTabStops(const int* stops, size_t nStops, int tabWidth) {
- mTabStops.set(stops, nStops, tabWidth);
- }
-
- BreakStrategy getStrategy() const { return mStrategy; }
-
- void setStrategy(BreakStrategy strategy) { mStrategy = strategy; }
-
- void setJustified(bool justified) { mJustified = justified; }
-
- HyphenationFrequency getHyphenationFrequency() const { return mHyphenationFrequency; }
-
- void setHyphenationFrequency(HyphenationFrequency frequency) {
- mHyphenationFrequency = frequency;
- }
-
- // TODO: this class is actually fairly close to being general and not tied to using
- // Minikin to do the shaping of the strings. The main thing that would need to be changed
- // is having some kind of callback (or virtual class, or maybe even template), which could
- // easily be instantiated with Minikin's Layout. Future work for when needed.
- float addStyleRun(MinikinPaint* paint, const std::shared_ptr<FontCollection>& typeface,
- FontStyle style, size_t start, size_t end, bool isRtl);
-
- void addReplacement(size_t start, size_t end, float width);
-
- size_t computeBreaks();
-
- const int* getBreaks() const {
- return mBreaks.data();
- }
-
- const float* getWidths() const {
- return mWidths.data();
- }
-
- const int* getFlags() const {
- return mFlags.data();
- }
-
- void finish();
-
- private:
- // ParaWidth is used to hold cumulative width from beginning of paragraph. Note that for
- // very large paragraphs, accuracy could degrade using only 32-bit float. Note however
- // that float is used extensively on the Java side for this. This is a typedef so that
- // we can easily change it based on performance/accuracy tradeoff.
- typedef double ParaWidth;
-
- // A single candidate break
- struct Candidate {
- size_t offset; // offset to text buffer, in code units
- size_t prev; // index to previous break
- ParaWidth preBreak; // width of text until this point, if we decide to not break here
- ParaWidth postBreak; // width of text until this point, if we decide to break here
- float penalty; // penalty of this break (for example, hyphen penalty)
- float score; // best score found for this break
- size_t lineNumber; // only updated for non-constant line widths
- size_t preSpaceCount; // preceding space count before breaking
- size_t postSpaceCount; // preceding space count after breaking
- HyphenationType hyphenType;
- };
-
- float currentLineWidth() const;
-
- void addWordBreak(size_t offset, ParaWidth preBreak, ParaWidth postBreak,
- size_t preSpaceCount, size_t postSpaceCount, float penalty, HyphenationType hyph);
-
- void addCandidate(Candidate cand);
- void pushGreedyBreak();
-
- // push an actual break to the output. Takes care of setting flags for tab
- void pushBreak(int offset, float width, uint8_t hyphenEdit);
-
- float getSpaceWidth() const;
-
- void computeBreaksGreedy();
-
- void computeBreaksOptimal(bool isRectangular);
-
- void finishBreaksOptimal();
-
- WordBreaker mWordBreaker;
- icu::Locale mLocale;
- std::vector<uint16_t>mTextBuf;
- std::vector<float>mCharWidths;
-
- Hyphenator* mHyphenator;
- std::vector<HyphenationType> mHyphBuf;
-
- // layout parameters
- BreakStrategy mStrategy = kBreakStrategy_Greedy;
- HyphenationFrequency mHyphenationFrequency = kHyphenationFrequency_Normal;
- bool mJustified;
- LineWidths mLineWidths;
- TabStops mTabStops;
-
- // result of line breaking
- std::vector<int> mBreaks;
- std::vector<float> mWidths;
- std::vector<int> mFlags;
-
- ParaWidth mWidth = 0;
- std::vector<Candidate> mCandidates;
- float mLinePenalty = 0.0f;
-
- // the following are state for greedy breaker (updated while adding style runs)
- size_t mLastBreak;
- size_t mBestBreak;
- float mBestScore;
- ParaWidth mPreBreak; // prebreak of last break
- uint32_t mLastHyphenation; // hyphen edit of last break kept for next line
- int mFirstTabIndex;
- size_t mSpaceCount;
-
- FRIEND_TEST(LineBreakerTest, setLocales);
+ // Called to find out the available right-side padding for the line.
+ virtual float getRightPaddingAt(size_t lineNo) const = 0;
};
+struct LineBreakResult {
+public:
+ LineBreakResult() = default;
+
+ // Following five vectors have the same length.
+ // TODO: Introduce individual line info struct if copy cost in JNI is negligible.
+ std::vector<int> breakPoints;
+ std::vector<float> widths;
+ std::vector<float> ascents;
+ std::vector<float> descents;
+ std::vector<int> flags;
+
+ LineBreakResult(LineBreakResult&&) = default;
+ LineBreakResult& operator=(LineBreakResult&&) = default;
+
+ void reverse() {
+ std::reverse(breakPoints.begin(), breakPoints.end());
+ std::reverse(widths.begin(), widths.end());
+ std::reverse(ascents.begin(), ascents.end());
+ std::reverse(descents.begin(), descents.end());
+ std::reverse(flags.begin(), flags.end());
+ }
+
+private:
+ MINIKIN_PREVENT_COPY_AND_ASSIGN(LineBreakResult);
+};
+
+LineBreakResult breakIntoLines(const U16StringPiece& textBuffer, BreakStrategy strategy,
+ HyphenationFrequency frequency, bool justified,
+ const MeasuredText& measuredText, const LineWidth& lineWidth,
+ const TabStops& tabStops);
+
} // namespace minikin
#endif // MINIKIN_LINE_BREAKER_H
diff --git a/include/minikin/LocaleList.h b/include/minikin/LocaleList.h
new file mode 100644
index 0000000..bfc26c7
--- /dev/null
+++ b/include/minikin/LocaleList.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MINIKIN_LOCALE_H
+#define MINIKIN_LOCALE_H
+
+#include <string>
+
+namespace minikin {
+
+// Looks up a locale list from an internal cache and returns its ID.
+// If the passed locale list is not in the cache, registers it and returns newly assigned ID.
+// TODO: Introduce LocaleId type.
+uint32_t registerLocaleList(const std::string& locales);
+
+} // namespace minikin
+
+#endif // MINIKIN_LOCALE_H
diff --git a/include/minikin/Macros.h b/include/minikin/Macros.h
new file mode 100644
index 0000000..be1b713
--- /dev/null
+++ b/include/minikin/Macros.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef MINIKIN_MACROS_H
+#define MINIKIN_MACROS_H
+
+#define MINIKIN_PREVENT_COPY_AND_ASSIGN(Type) \
+ Type(const Type&) = delete; \
+ Type& operator=(const Type&) = delete
+
+#define MINIKIN_PREVENT_COPY_ASSIGN_AND_MOVE(Type) \
+ Type(const Type&) = delete; \
+ Type& operator=(const Type&) = delete; \
+ Type(Type&&) = delete; \
+ Type& operator=(Type&&) = delete
+
+// Following thread annotations are partially copied from Abseil thread_annotations.h file.
+// https://github.com/abseil/abseil-cpp/blob/master/absl/base/thread_annotations.h
+
+#if defined(__clang__)
+#define THREAD_ANNOTATION_ATTRIBUTE__(x) __attribute__((x))
+#else
+#define THREAD_ANNOTATION_ATTRIBUTE__(x) // no-op
+#endif
+
+// GUARDED_BY()
+//
+// Documents if a shared field or global variable needs to be protected by a
+// mutex. GUARDED_BY() allows the user to specify a particular mutex that
+// should be held when accessing the annotated variable.
+//
+// Example:
+//
+// Mutex mu;
+// int p1 GUARDED_BY(mu);
+#define GUARDED_BY(x) THREAD_ANNOTATION_ATTRIBUTE__(guarded_by(x))
+
+// EXCLUSIVE_LOCKS_REQUIRED()
+//
+// Documents a function that expects a mutex to be held prior to entry.
+// The mutex is expected to be held both on entry to, and exit from, the
+// function.
+//
+// Example:
+//
+// Mutex mu1, mu2;
+// int a GUARDED_BY(mu1);
+// int b GUARDED_BY(mu2);
+//
+// void foo() EXCLUSIVE_LOCKS_REQUIRED(mu1, mu2) { ... };
+#define EXCLUSIVE_LOCKS_REQUIRED(...) \
+ THREAD_ANNOTATION_ATTRIBUTE__(exclusive_locks_required(__VA_ARGS__))
+
+#endif // MINIKIN_MACROS_H
diff --git a/include/minikin/MeasuredText.h b/include/minikin/MeasuredText.h
new file mode 100644
index 0000000..d33a1ca
--- /dev/null
+++ b/include/minikin/MeasuredText.h
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MINIKIN_MEASURED_TEXT_H
+#define MINIKIN_MEASURED_TEXT_H
+
+#include <deque>
+#include <vector>
+
+#include "minikin/FontCollection.h"
+#include "minikin/Layout.h"
+#include "minikin/LayoutPieces.h"
+#include "minikin/Macros.h"
+#include "minikin/MinikinFont.h"
+#include "minikin/Range.h"
+#include "minikin/U16StringPiece.h"
+
+namespace minikin {
+
+class Run {
+public:
+ Run(const Range& range) : mRange(range) {}
+ virtual ~Run() {}
+
+ // Returns true if this run is RTL. Otherwise returns false.
+ virtual bool isRtl() const = 0;
+
+ // Returns true if this run is a target of hyphenation. Otherwise return false.
+ virtual bool canHyphenate() const = 0;
+
+ // Returns the locale list ID for this run.
+ virtual uint32_t getLocaleListId() const = 0;
+
+ // Fills the each character's advances, extents and overhangs.
+ virtual void getMetrics(const U16StringPiece& text, float* advances, MinikinExtent* extents,
+ LayoutPieces* piece) const = 0;
+
+ virtual std::pair<float, MinikinRect> getBounds(const U16StringPiece& text, const Range& range,
+ const LayoutPieces& pieces) const = 0;
+
+ // Following two methods are only called when the implementation returns true for
+ // canHyphenate method.
+
+ // Returns the paint pointer used for this run.
+ // Returns null if canHyphenate has not returned true.
+ virtual const MinikinPaint* getPaint() const { return nullptr; }
+
+ // Measures the hyphenation piece and fills each character's advances and overhangs.
+ virtual float measureHyphenPiece(const U16StringPiece& /* text */,
+ const Range& /* hyphenPieceRange */,
+ StartHyphenEdit /* startHyphen */,
+ EndHyphenEdit /* endHyphen */, float* /* advances */,
+ LayoutPieces* /* pieces */) const {
+ return 0.0;
+ }
+
+ inline const Range& getRange() const { return mRange; }
+
+protected:
+ const Range mRange;
+};
+
+class StyleRun : public Run {
+public:
+ StyleRun(const Range& range, MinikinPaint&& paint, bool isRtl)
+ : Run(range), mPaint(std::move(paint)), mIsRtl(isRtl) {}
+
+ bool canHyphenate() const override { return true; }
+ uint32_t getLocaleListId() const override { return mPaint.localeListId; }
+ bool isRtl() const override { return mIsRtl; }
+
+ void getMetrics(const U16StringPiece& text, float* advances, MinikinExtent* extents,
+ LayoutPieces* pieces) const override {
+ Bidi bidiFlag = mIsRtl ? Bidi::FORCE_RTL : Bidi::FORCE_LTR;
+ Layout::measureText(text, mRange, bidiFlag, mPaint, StartHyphenEdit::NO_EDIT,
+ EndHyphenEdit::NO_EDIT, advances, extents, pieces);
+ }
+
+ std::pair<float, MinikinRect> getBounds(const U16StringPiece& text, const Range& range,
+ const LayoutPieces& pieces) const override {
+ Bidi bidiFlag = mIsRtl ? Bidi::FORCE_RTL : Bidi::FORCE_LTR;
+ return Layout::getBoundsWithPrecomputedPieces(text, range, bidiFlag, mPaint, pieces);
+ }
+
+ const MinikinPaint* getPaint() const override { return &mPaint; }
+
+ float measureHyphenPiece(const U16StringPiece& text, const Range& range,
+ StartHyphenEdit startHyphen, EndHyphenEdit endHyphen, float* advances,
+ LayoutPieces* pieces) const override {
+ Bidi bidiFlag = mIsRtl ? Bidi::FORCE_RTL : Bidi::FORCE_LTR;
+ return Layout::measureText(text, range, bidiFlag, mPaint, startHyphen, endHyphen, advances,
+ nullptr /* extent */, pieces);
+ }
+
+private:
+ MinikinPaint mPaint;
+ const bool mIsRtl;
+};
+
+class ReplacementRun : public Run {
+public:
+ ReplacementRun(const Range& range, float width, uint32_t localeListId)
+ : Run(range), mWidth(width), mLocaleListId(localeListId) {}
+
+ bool isRtl() const { return false; }
+ bool canHyphenate() const { return false; }
+ uint32_t getLocaleListId() const { return mLocaleListId; }
+
+ void getMetrics(const U16StringPiece& /* unused */, float* advances,
+ MinikinExtent* /* unused */, LayoutPieces* /* pieces */) const override {
+ advances[0] = mWidth;
+ // TODO: Get the extents information from the caller.
+ }
+
+ std::pair<float, MinikinRect> getBounds(const U16StringPiece& /* text */,
+ const Range& /* range */,
+ const LayoutPieces& /* pieces */) const override {
+ // Bounding Box is not used in replacement run.
+ return std::make_pair(mWidth, MinikinRect());
+ }
+
+private:
+ const float mWidth;
+ const uint32_t mLocaleListId;
+};
+
+// Represents a hyphenation break point.
+struct HyphenBreak {
+ // The break offset.
+ uint32_t offset;
+
+ // The hyphenation type.
+ HyphenationType type;
+
+ // The width of preceding piece after break at hyphenation point.
+ float first;
+
+ // The width of following piece after break at hyphenation point.
+ float second;
+
+ HyphenBreak(uint32_t offset, HyphenationType type, float first, float second)
+ : offset(offset), type(type), first(first), second(second) {}
+};
+
+class MeasuredText {
+public:
+ // Character widths.
+ std::vector<float> widths;
+
+ // Font vertical extents for characters.
+ // TODO: Introduce compression for extents. Usually, this has the same values for all chars.
+ std::vector<MinikinExtent> extents;
+
+ // Hyphenation points.
+ std::vector<HyphenBreak> hyphenBreaks;
+
+ // The style information.
+ std::vector<std::unique_ptr<Run>> runs;
+
+ // The copied layout pieces for construcing final layouts.
+ // TODO: Stop assigning width/extents if layout pieces are available for reducing memory impact.
+ LayoutPieces layoutPieces;
+
+ uint32_t getMemoryUsage() const {
+ return sizeof(float) * widths.size() + sizeof(MinikinExtent) * extents.size() +
+ sizeof(HyphenBreak) * hyphenBreaks.size() + layoutPieces.getMemoryUsage();
+ }
+
+ void buildLayout(const U16StringPiece& textBuf, const Range& range, const MinikinPaint& paint,
+ Bidi bidiFlag, StartHyphenEdit startHyphen, EndHyphenEdit endHyphen,
+ Layout* layout);
+ MinikinRect getBounds(const U16StringPiece& textBuf, const Range& range);
+
+ MeasuredText(MeasuredText&&) = default;
+ MeasuredText& operator=(MeasuredText&&) = default;
+
+ MINIKIN_PREVENT_COPY_AND_ASSIGN(MeasuredText);
+
+private:
+ friend class MeasuredTextBuilder;
+
+ void measure(const U16StringPiece& textBuf, bool computeHyphenation, bool computeLayout);
+
+ // Use MeasuredTextBuilder instead.
+ MeasuredText(const U16StringPiece& textBuf, std::vector<std::unique_ptr<Run>>&& runs,
+ bool computeHyphenation, bool computeLayout)
+ : widths(textBuf.size()), extents(textBuf.size()), runs(std::move(runs)) {
+ measure(textBuf, computeHyphenation, computeLayout);
+ }
+};
+
+class MeasuredTextBuilder {
+public:
+ MeasuredTextBuilder() {}
+
+ void addStyleRun(int32_t start, int32_t end, MinikinPaint&& paint, bool isRtl) {
+ mRuns.emplace_back(std::make_unique<StyleRun>(Range(start, end), std::move(paint), isRtl));
+ }
+
+ void addReplacementRun(int32_t start, int32_t end, float width, uint32_t localeListId) {
+ mRuns.emplace_back(
+ std::make_unique<ReplacementRun>(Range(start, end), width, localeListId));
+ }
+
+ template <class T, typename... Args>
+ void addCustomRun(Args&&... args) {
+ mRuns.emplace_back(std::make_unique<T>(std::forward<Args>(args)...));
+ }
+
+ std::unique_ptr<MeasuredText> build(const U16StringPiece& textBuf, bool computeHyphenation,
+ bool computeLayout) {
+ // Unable to use make_unique here since make_unique is not a friend of MeasuredText.
+ return std::unique_ptr<MeasuredText>(
+ new MeasuredText(textBuf, std::move(mRuns), computeHyphenation, computeLayout));
+ }
+
+ MINIKIN_PREVENT_COPY_ASSIGN_AND_MOVE(MeasuredTextBuilder);
+
+private:
+ std::vector<std::unique_ptr<Run>> mRuns;
+};
+
+} // namespace minikin
+
+#endif // MINIKIN_MEASURED_TEXT_H
diff --git a/include/minikin/Measurement.h b/include/minikin/Measurement.h
index b00c212..55c8474 100644
--- a/include/minikin/Measurement.h
+++ b/include/minikin/Measurement.h
@@ -17,15 +17,15 @@
#ifndef MINIKIN_MEASUREMENT_H
#define MINIKIN_MEASUREMENT_H
-#include <minikin/Layout.h>
+#include <cstdint>
namespace minikin {
float getRunAdvance(const float* advances, const uint16_t* buf, size_t start, size_t count,
- size_t offset);
+ size_t offset);
size_t getOffsetForAdvance(const float* advances, const uint16_t* buf, size_t start, size_t count,
- float advance);
+ float advance);
} // namespace minikin
diff --git a/include/minikin/MinikinFont.h b/include/minikin/MinikinFont.h
index 01af786..f57e4f0 100644
--- a/include/minikin/MinikinFont.h
+++ b/include/minikin/MinikinFont.h
@@ -18,38 +18,64 @@
#define MINIKIN_FONT_H
#include <string>
-#include <memory>
-#include <minikin/FontFamily.h>
-#include <minikin/Hyphenator.h>
+#include "minikin/FontFamily.h"
+#include "minikin/Hyphenator.h"
// An abstraction for platform fonts, allowing Minikin to be used with
// multiple actual implementations of fonts.
namespace minikin {
+class FontCollection;
class MinikinFont;
// Possibly move into own .h file?
// Note: if you add a field here, either add it to LayoutCacheKey or to skipCache()
struct MinikinPaint {
- MinikinPaint() : font(nullptr), size(0), scaleX(0), skewX(0), letterSpacing(0), wordSpacing(0),
- paintFlags(0), fakery(), hyphenEdit(), fontFeatureSettings() { }
+ MinikinPaint(const std::shared_ptr<FontCollection>& font)
+ : size(0),
+ scaleX(0),
+ skewX(0),
+ letterSpacing(0),
+ wordSpacing(0),
+ paintFlags(0),
+ localeListId(0),
+ familyVariant(FontFamily::Variant::DEFAULT),
+ fontFeatureSettings(),
+ font(font) {}
- bool skipCache() const {
- return !fontFeatureSettings.empty();
- }
+ bool skipCache() const { return !fontFeatureSettings.empty(); }
- MinikinFont *font;
float size;
float scaleX;
float skewX;
float letterSpacing;
float wordSpacing;
uint32_t paintFlags;
- FontFakery fakery;
- HyphenEdit hyphenEdit;
+ uint32_t localeListId;
+ FontStyle fontStyle;
+ FontFamily::Variant familyVariant;
std::string fontFeatureSettings;
+ std::shared_ptr<FontCollection> font;
+
+ void copyFrom(const MinikinPaint& paint) { *this = paint; }
+
+ MinikinPaint(MinikinPaint&&) = default;
+ MinikinPaint& operator=(MinikinPaint&&) = default;
+
+ inline bool operator==(const MinikinPaint& paint) {
+ return size == paint.size && scaleX == paint.scaleX && skewX == paint.skewX &&
+ letterSpacing == paint.letterSpacing && wordSpacing == paint.wordSpacing &&
+ paintFlags == paint.paintFlags && localeListId == paint.localeListId &&
+ fontStyle == paint.fontStyle && familyVariant == paint.familyVariant &&
+ fontFeatureSettings == paint.fontFeatureSettings && font.get() == paint.font.get();
+ }
+
+private:
+ // Forbid implicit copy and assign. Use copyFrom instead.
+ MinikinPaint(const MinikinPaint&) = default;
+ MinikinPaint& operator=(const MinikinPaint&) = default;
};
// Only a few flags affect layout, but those that do should have values
@@ -59,10 +85,11 @@
};
struct MinikinRect {
- float mLeft, mTop, mRight, mBottom;
- bool isEmpty() const {
- return mLeft == mRight || mTop == mBottom;
- }
+ float mLeft = 0.0;
+ float mTop = 0.0;
+ float mRight = 0.0;
+ float mBottom = 0.0;
+ bool isEmpty() const { return mLeft == mRight || mTop == mBottom; }
void set(const MinikinRect& r) {
mLeft = r.mLeft;
mTop = r.mTop;
@@ -75,42 +102,49 @@
mRight += dx;
mBottom += dy;
}
- void setEmpty() {
- mLeft = mTop = mRight = mBottom = 0;
- }
+ void setEmpty() { mLeft = mTop = mRight = mBottom = 0.0; }
void join(const MinikinRect& r);
};
-// Callback for freeing data
-typedef void (*MinikinDestroyFunc) (void* data);
+// For holding vertical extents.
+struct MinikinExtent {
+ float ascent = 0.0; // negative
+ float descent = 0.0; // positive
+ float line_gap = 0.0; // positive
+
+ void reset() { ascent = descent = line_gap = 0.0; }
+
+ void extendBy(const MinikinExtent& e) {
+ ascent = std::min(ascent, e.ascent);
+ descent = std::max(descent, e.descent);
+ line_gap = std::max(line_gap, e.line_gap);
+ }
+};
class MinikinFont {
public:
explicit MinikinFont(int32_t uniqueId) : mUniqueId(uniqueId) {}
- virtual ~MinikinFont();
+ virtual ~MinikinFont() {}
- virtual float GetHorizontalAdvance(uint32_t glyph_id,
- const MinikinPaint &paint) const = 0;
+ virtual float GetHorizontalAdvance(uint32_t glyph_id, const MinikinPaint& paint,
+ const FontFakery& fakery) const = 0;
- virtual void GetBounds(MinikinRect* bounds, uint32_t glyph_id,
- const MinikinPaint &paint) const = 0;
+ virtual void GetBounds(MinikinRect* bounds, uint32_t glyph_id, const MinikinPaint& paint,
+ const FontFakery& fakery) const = 0;
+
+ virtual void GetFontExtent(MinikinExtent* extent, const MinikinPaint& paint,
+ const FontFakery& fakery) const = 0;
// Override if font can provide access to raw data
- virtual const void* GetFontData() const {
- return nullptr;
- }
+ virtual const void* GetFontData() const { return nullptr; }
// Override if font can provide access to raw data
- virtual size_t GetFontSize() const {
- return 0;
- }
+ virtual size_t GetFontSize() const { return 0; }
// Override if font can provide access to raw data.
// Returns index within OpenType collection
- virtual int GetFontIndex() const {
- return 0;
- }
+ virtual int GetFontIndex() const { return 0; }
virtual const std::vector<minikin::FontVariation>& GetAxes() const = 0;
@@ -120,11 +154,11 @@
}
static uint32_t MakeTag(char c1, char c2, char c3, char c4) {
- return ((uint32_t)c1 << 24) | ((uint32_t)c2 << 16) |
- ((uint32_t)c3 << 8) | (uint32_t)c4;
+ return ((uint32_t)c1 << 24) | ((uint32_t)c2 << 16) | ((uint32_t)c3 << 8) | (uint32_t)c4;
}
int32_t GetUniqueId() const { return mUniqueId; }
+
private:
const int32_t mUniqueId;
};
diff --git a/include/minikin/Range.h b/include/minikin/Range.h
new file mode 100644
index 0000000..4c7fbfa
--- /dev/null
+++ b/include/minikin/Range.h
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MINIKIN_RANGE_H
+#define MINIKIN_RANGE_H
+
+#include <algorithm>
+#include <limits>
+#include <utility>
+
+namespace minikin {
+
+// An undirected range.
+class Range {
+public:
+ static constexpr uint32_t NOWHERE = std::numeric_limits<uint32_t>::max();
+
+ // start must be smaller than or equal to end otherwise the behavior is undefined.
+ Range(uint32_t start, uint32_t end) : mStart(start), mEnd(end) {}
+ Range() : Range(NOWHERE, NOWHERE) {}
+
+ Range(const Range&) = default;
+ Range& operator=(const Range&) = default;
+
+ static Range invalidRange() { return Range(NOWHERE, NOWHERE); }
+ inline bool isValid() const { return mStart != NOWHERE && mEnd != NOWHERE; }
+
+ inline uint32_t getStart() const { return mStart; } // inclusive
+ inline void setStart(uint32_t start) { mStart = start; } // inclusive
+
+ inline uint32_t getEnd() const { return mEnd; } // exclusive
+ inline void setEnd(uint32_t end) { mEnd = end; } // exclusive
+
+ inline uint32_t getLength() const { return mEnd - mStart; }
+
+ inline bool isEmpty() const { return mStart == mEnd; }
+
+ inline uint32_t toRangeOffset(uint32_t globalPos) const { return globalPos - mStart; }
+ inline uint32_t toGlobalOffset(uint32_t rangePos) const { return mStart + rangePos; }
+
+ // The behavior is undefined if pos is out of range.
+ inline std::pair<Range, Range> split(uint32_t pos) const {
+ return std::make_pair(Range(mStart, pos), Range(pos, mEnd));
+ }
+
+ inline bool contains(const Range& other) const {
+ return mStart <= other.mStart && other.mEnd <= mEnd;
+ }
+
+ // Returns true if the pos is in this range.
+ // For example,
+ // const Range range(1, 2); // 1 is inclusive, 2 is exclusive.
+ // range.contains(0); // false
+ // range.contains(1); // true
+ // range.contains(2); // false
+ inline bool contains(uint32_t pos) const { return mStart <= pos && pos < mEnd; }
+
+ // Returns true if left and right intersect.
+ inline static bool intersects(const Range& left, const Range& right) {
+ return left.isValid() && right.isValid() && left.mStart < right.mEnd &&
+ right.mStart < left.mEnd;
+ }
+ inline static Range intersection(const Range& left, const Range& right) {
+ return Range(std::max(left.mStart, right.mStart), std::min(left.mEnd, right.mEnd));
+ }
+
+ // Returns merged range. This method assumes left and right are not invalid ranges and they have
+ // an intersection.
+ static Range merge(const Range& left, const Range& right) {
+ return Range({std::min(left.mStart, right.mStart), std::max(left.mEnd, right.mEnd)});
+ }
+
+ inline bool operator==(const Range& o) const { return mStart == o.mStart && mEnd == o.mEnd; }
+
+ inline bool operator!=(const Range& o) const { return !(*this == o); }
+
+private:
+ // Helper class for "for (uint32_t i : range)" style for-loop.
+ class RangeIterator {
+ public:
+ RangeIterator(uint32_t pos) : mPos(pos) {}
+
+ inline bool operator!=(const RangeIterator& o) const { return o.mPos != mPos; }
+ inline uint32_t operator*() const { return mPos; }
+ inline RangeIterator& operator++() {
+ mPos++;
+ return *this;
+ }
+
+ private:
+ uint32_t mPos;
+ };
+
+public:
+ inline RangeIterator begin() const { return RangeIterator(mStart); }
+ inline RangeIterator end() const { return RangeIterator(mEnd); }
+
+private:
+ uint32_t mStart;
+ uint32_t mEnd;
+};
+
+} // namespace minikin
+
+#endif // MINIKIN_RANGE_H
diff --git a/include/minikin/SparseBitSet.h b/include/minikin/SparseBitSet.h
index 62aece2..9ccef12 100644
--- a/include/minikin/SparseBitSet.h
+++ b/include/minikin/SparseBitSet.h
@@ -17,9 +17,8 @@
#ifndef MINIKIN_SPARSE_BIT_SET_H
#define MINIKIN_SPARSE_BIT_SET_H
-#include <stdint.h>
#include <sys/types.h>
-
+#include <cstdint>
#include <memory>
// ---------------------------------------------------------------------------
@@ -49,15 +48,13 @@
// Determine whether the value is included in the set
bool get(uint32_t ch) const {
if (ch >= mMaxVal) return false;
- const uint32_t *bitmap = &mBitmaps[mIndices[ch >> kLogValuesPerPage]];
+ const uint32_t* bitmap = &mBitmaps[mIndices[ch >> kLogValuesPerPage]];
uint32_t index = ch & kPageMask;
return (bitmap[index >> kLogBitsPerEl] & (kElFirst >> (index & kElMask))) != 0;
}
// One more than the maximum value in the set, or zero if empty
- uint32_t length() const {
- return mMaxVal;
- }
+ uint32_t length() const { return mMaxVal; }
// The next set bit starting at fromIndex, inclusive, or kNotFound
// if none exists.
@@ -96,4 +93,4 @@
} // namespace minikin
-#endif // MINIKIN_SPARSE_BIT_SET_H
+#endif // MINIKIN_SPARSE_BIT_SET_H
diff --git a/include/minikin/U16StringPiece.h b/include/minikin/U16StringPiece.h
new file mode 100644
index 0000000..fe92635
--- /dev/null
+++ b/include/minikin/U16StringPiece.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MINIKIN_U16STRING_PIECE_H
+#define MINIKIN_U16STRING_PIECE_H
+
+#include <vector>
+
+#include "minikin/Range.h"
+
+namespace minikin {
+
+class U16StringPiece {
+public:
+ U16StringPiece() : mData(nullptr), mLength(0) {}
+ U16StringPiece(const uint16_t* data, uint32_t length) : mData(data), mLength(length) {}
+ U16StringPiece(const std::vector<uint16_t>& v) // Intentionally not explicit.
+ : mData(v.data()), mLength(static_cast<uint32_t>(v.size())) {}
+ template <uint32_t length>
+ U16StringPiece(uint16_t const (&data)[length]) : mData(data), mLength(length) {}
+
+ U16StringPiece(const U16StringPiece&) = default;
+ U16StringPiece& operator=(const U16StringPiece&) = default;
+
+ inline const uint16_t* data() const { return mData; }
+ inline uint32_t size() const { return mLength; }
+ inline uint32_t length() const { return mLength; }
+
+ // Undefined behavior if pos is out of range.
+ inline const uint16_t& at(uint32_t pos) const { return mData[pos]; }
+ inline const uint16_t& operator[](uint32_t pos) const { return mData[pos]; }
+
+ inline U16StringPiece substr(const Range& range) const {
+ return U16StringPiece(mData + range.getStart(), range.getLength());
+ }
+
+ inline bool hasChar(uint16_t c) const {
+ const uint16_t* end = mData + mLength;
+ return std::find(mData, end, c) != end;
+ }
+
+private:
+ const uint16_t* mData;
+ uint32_t mLength;
+};
+
+} // namespace minikin
+
+#endif // MINIKIN_U16STRING_PIECE_H
diff --git a/include/minikin/WordBreaker.h b/include/minikin/WordBreaker.h
deleted file mode 100644
index 6971ce2..0000000
--- a/include/minikin/WordBreaker.h
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/**
- * A wrapper around ICU's line break iterator, that gives customized line
- * break opportunities, as well as identifying words for the purpose of
- * hyphenation.
- */
-
-#ifndef MINIKIN_WORD_BREAKER_H
-#define MINIKIN_WORD_BREAKER_H
-
-#include "unicode/brkiter.h"
-#include <memory>
-
-namespace minikin {
-
-class WordBreaker {
-public:
- ~WordBreaker() {
- finish();
- }
-
- void setLocale(const icu::Locale& locale);
-
- void setText(const uint16_t* data, size_t size);
-
- // Advance iterator to next word break. Return offset, or -1 if EOT
- ssize_t next();
-
- // Current offset of iterator, equal to 0 at BOT or last return from next()
- ssize_t current() const;
-
- // After calling next(), wordStart() and wordEnd() are offsets defining the previous
- // word. If wordEnd <= wordStart, it's not a word for the purpose of hyphenation.
- ssize_t wordStart() const;
-
- ssize_t wordEnd() const;
-
- int breakBadness() const;
-
- void finish();
-
-private:
- int32_t iteratorNext();
- void detectEmailOrUrl();
- ssize_t findNextBreakInEmailOrUrl();
-
- std::unique_ptr<icu::BreakIterator> mBreakIterator;
- UText mUText = UTEXT_INITIALIZER;
- const uint16_t* mText = nullptr;
- size_t mTextSize;
- ssize_t mLast;
- ssize_t mCurrent;
- bool mIteratorWasReset;
-
- // state for the email address / url detector
- ssize_t mScanOffset;
- bool mInEmailOrUrl;
-};
-
-} // namespace minikin
-
-#endif // MINIKIN_WORD_BREAKER_H
diff --git a/libs/minikin/Android.bp b/libs/minikin/Android.bp
index df8ab9a..4793850 100644
--- a/libs/minikin/Android.bp
+++ b/libs/minikin/Android.bp
@@ -25,24 +25,29 @@
srcs: [
"Hyphenator.cpp",
],
+ cflags: ["-Wall", "-Werror"],
target: {
android: {
srcs: [
+ "BidiUtils.cpp",
"CmapCoverage.cpp",
"Emoji.cpp",
"FontCollection.cpp",
"FontFamily.cpp",
- "FontLanguage.cpp",
- "FontLanguageListCache.cpp",
"FontUtils.cpp",
"GraphemeBreak.cpp",
- "HbFontCache.cpp",
+ "GreedyLineBreaker.cpp",
+ "HyphenatorMap.cpp",
"Layout.cpp",
"LayoutUtils.cpp",
"LineBreaker.cpp",
+ "LineBreakerUtil.cpp",
+ "Locale.cpp",
+ "LocaleListCache.cpp",
+ "MeasuredText.cpp",
"Measurement.cpp",
"MinikinInternal.cpp",
- "MinikinFont.cpp",
+ "OptimalLineBreaker.cpp",
"SparseBitSet.cpp",
"WordBreaker.cpp",
],
@@ -68,11 +73,12 @@
"-Werror",
"-Wall",
"-Wextra",
+ "-Wthread-safety",
],
product_variables: {
debuggable: {
- // Enable race detection on eng and userdebug build.
- cppflags: ["-DENABLE_RACE_DETECTION"],
+ // Enable assertion on eng and userdebug build.
+ cppflags: ["-DENABLE_ASSERTION"],
},
},
shared_libs: [
diff --git a/libs/minikin/BidiUtils.cpp b/libs/minikin/BidiUtils.cpp
new file mode 100644
index 0000000..c397837
--- /dev/null
+++ b/libs/minikin/BidiUtils.cpp
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "Minikin"
+
+#include "BidiUtils.h"
+
+#include <algorithm>
+
+#include <unicode/ubidi.h>
+#include <unicode/utf16.h>
+
+#include "minikin/Emoji.h"
+
+#include "MinikinInternal.h"
+
+namespace minikin {
+
+static inline UBiDiLevel bidiToUBidiLevel(Bidi bidi) {
+ switch (bidi) {
+ case Bidi::LTR:
+ return 0x00;
+ case Bidi::RTL:
+ return 0x01;
+ case Bidi::DEFAULT_LTR:
+ return UBIDI_DEFAULT_LTR;
+ case Bidi::DEFAULT_RTL:
+ return UBIDI_DEFAULT_RTL;
+ case Bidi::FORCE_LTR:
+ case Bidi::FORCE_RTL:
+ MINIKIN_NOT_REACHED("FORCE_LTR/FORCE_RTL can not be converted to UBiDiLevel.");
+ return 0x00;
+ default:
+ MINIKIN_NOT_REACHED("Unknown Bidi value.");
+ return 0x00;
+ }
+}
+
+BidiText::RunInfo BidiText::getRunInfoAt(uint32_t runOffset) const {
+ MINIKIN_ASSERT(runOffset < mRunCount, "Out of range access. %d/%d", runOffset, mRunCount);
+ if (mRunCount == 1) {
+ // Single run. No need to iteract with UBiDi.
+ return {mRange, mIsRtl};
+ }
+
+ int32_t startRun = -1;
+ int32_t lengthRun = -1;
+ const UBiDiDirection runDir = ubidi_getVisualRun(mBidi.get(), runOffset, &startRun, &lengthRun);
+ if (startRun == -1 || lengthRun == -1) {
+ ALOGE("invalid visual run");
+ return {Range::invalidRange(), false};
+ }
+ const uint32_t runStart = std::max(static_cast<uint32_t>(startRun), mRange.getStart());
+ const uint32_t runEnd = std::min(static_cast<uint32_t>(startRun + lengthRun), mRange.getEnd());
+ if (runEnd <= runStart) {
+ // skip the empty run.
+ return {Range::invalidRange(), false};
+ }
+ return {Range(runStart, runEnd), (runDir == UBIDI_RTL)};
+}
+
+BidiText::BidiText(const U16StringPiece& textBuf, const Range& range, Bidi bidiFlags)
+ : mRange(range), mIsRtl(isRtl(bidiFlags)), mRunCount(1 /* by default, single run */) {
+ if (isOverride(bidiFlags)) {
+ // force single run.
+ return;
+ }
+
+ mBidi.reset(ubidi_open());
+ if (!mBidi) {
+ ALOGE("error creating bidi object");
+ return;
+ }
+ UErrorCode status = U_ZERO_ERROR;
+ // Set callbacks to override bidi classes of new emoji
+ ubidi_setClassCallback(mBidi.get(), emojiBidiOverride, nullptr, nullptr, nullptr, &status);
+ if (!U_SUCCESS(status)) {
+ ALOGE("error setting bidi callback function, status = %d", status);
+ return;
+ }
+
+ const UBiDiLevel bidiReq = bidiToUBidiLevel(bidiFlags);
+ ubidi_setPara(mBidi.get(), reinterpret_cast<const UChar*>(textBuf.data()), textBuf.size(),
+ bidiReq, nullptr, &status);
+ if (!U_SUCCESS(status)) {
+ ALOGE("error calling ubidi_setPara, status = %d", status);
+ return;
+ }
+ // RTL paragraphs get an odd level, while LTR paragraphs get an even level,
+ const bool paraIsRTL = ubidi_getParaLevel(mBidi.get()) & 0x01;
+ const ssize_t rc = ubidi_countRuns(mBidi.get(), &status);
+ if (!U_SUCCESS(status) || rc < 0) {
+ ALOGW("error counting bidi runs, status = %d", status);
+ return;
+ }
+ if (rc == 0) {
+ mIsRtl = paraIsRTL;
+ return;
+ }
+ if (rc == 1) {
+ // If the paragraph is a single run, override the paragraph dirction with the run
+ // (actually the whole text) direction.
+ const UBiDiDirection runDir = ubidi_getVisualRun(mBidi.get(), 0, nullptr, nullptr);
+ mIsRtl = (runDir == UBIDI_RTL);
+ return;
+ }
+ mRunCount = rc;
+}
+
+} // namespace minikin
diff --git a/libs/minikin/BidiUtils.h b/libs/minikin/BidiUtils.h
new file mode 100644
index 0000000..14678d8
--- /dev/null
+++ b/libs/minikin/BidiUtils.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MINIKIN_BIDI_UTILS_H
+#define MINIKIN_BIDI_UTILS_H
+
+#define LOG_TAG "Minikin"
+
+#include "minikin/Layout.h"
+
+#include <memory>
+
+#include <unicode/ubidi.h>
+
+#include "minikin/Macros.h"
+#include "minikin/U16StringPiece.h"
+
+namespace minikin {
+
+struct UBiDiDeleter {
+ void operator()(UBiDi* v) { ubidi_close(v); }
+};
+
+using UBiDiUniquePtr = std::unique_ptr<UBiDi, UBiDiDeleter>;
+
+// A helper class for iterating the bidi run transitions.
+class BidiText {
+public:
+ struct RunInfo {
+ Range range;
+ bool isRtl;
+ };
+
+ BidiText(const U16StringPiece& textBuf, const Range& range, Bidi bidiFlags);
+
+ RunInfo getRunInfoAt(uint32_t runOffset) const;
+
+ class iterator {
+ public:
+ inline bool operator==(const iterator& o) const {
+ return mRunOffset == o.mRunOffset && mParent == o.mParent;
+ }
+
+ inline bool operator!=(const iterator& o) const { return !(*this == o); }
+
+ inline RunInfo operator*() const { return mParent->getRunInfoAt(mRunOffset); }
+
+ inline iterator& operator++() {
+ mRunOffset++;
+ return *this;
+ }
+
+ private:
+ friend class BidiText;
+
+ iterator(const BidiText* parent, uint32_t runOffset)
+ : mParent(parent), mRunOffset(runOffset) {}
+
+ const BidiText* mParent;
+ uint32_t mRunOffset;
+ };
+
+ inline iterator begin() const { return iterator(this, 0); }
+ inline iterator end() const { return iterator(this, mRunCount); }
+
+private:
+ UBiDiUniquePtr mBidi; // Maybe null for single run.
+ const Range mRange; // The range in the original buffer. Used for range check.
+ bool mIsRtl; // The paragraph direction.
+ uint32_t mRunCount; // The number of the bidi run in this text.
+
+ MINIKIN_PREVENT_COPY_AND_ASSIGN(BidiText);
+};
+
+} // namespace minikin
+
+#endif // MINIKIN_BIDI_UTILS_H
diff --git a/libs/minikin/CmapCoverage.cpp b/libs/minikin/CmapCoverage.cpp
index 78e7b0d..f6143d3 100644
--- a/libs/minikin/CmapCoverage.cpp
+++ b/libs/minikin/CmapCoverage.cpp
@@ -16,24 +16,17 @@
// Determine coverage of font given its raw "cmap" OpenType table
-#define LOG_TAG "Minikin"
+#include "minikin/CmapCoverage.h"
#include <algorithm>
#include <vector>
-using std::vector;
-#include <log/log.h>
+#include "minikin/SparseBitSet.h"
-#include <minikin/SparseBitSet.h>
-#include <minikin/CmapCoverage.h>
#include "MinikinInternal.h"
-#include <MinikinInternal.h>
-
namespace minikin {
-constexpr uint32_t U32MAX = std::numeric_limits<uint32_t>::max();
-
// These could perhaps be optimized to use __builtin_bswap16 and friends.
static uint32_t readU16(const uint8_t* data, size_t offset) {
return ((uint32_t)data[offset]) << 8 | ((uint32_t)data[offset + 1]);
@@ -41,20 +34,17 @@
static uint32_t readU24(const uint8_t* data, size_t offset) {
return ((uint32_t)data[offset]) << 16 | ((uint32_t)data[offset + 1]) << 8 |
- ((uint32_t)data[offset + 2]);
+ ((uint32_t)data[offset + 2]);
}
static uint32_t readU32(const uint8_t* data, size_t offset) {
return ((uint32_t)data[offset]) << 24 | ((uint32_t)data[offset + 1]) << 16 |
- ((uint32_t)data[offset + 2]) << 8 | ((uint32_t)data[offset + 3]);
+ ((uint32_t)data[offset + 2]) << 8 | ((uint32_t)data[offset + 3]);
}
// The start must be larger than or equal to coverage.back() if coverage is not empty.
// Returns true if the range is appended. Otherwise returns false as an error.
-static bool addRange(vector<uint32_t> &coverage, uint32_t start, uint32_t end) {
-#ifdef VERBOSE_DEBUG
- ALOGD("adding range %d-%d\n", start, end);
-#endif
+static bool addRange(std::vector<uint32_t>& coverage, uint32_t start, uint32_t end) {
if (coverage.empty() || coverage.back() < start) {
coverage.push_back(start);
coverage.push_back(end);
@@ -71,39 +61,30 @@
}
}
-struct Range {
- uint32_t start; // inclusive
- uint32_t end; // exclusive
-
- static Range InvalidRange() {
- return Range({ U32MAX, U32MAX });
+// Returns true if the range is appended. Otherwise returns false as an error.
+static bool addRangeCmap4(std::vector<uint32_t>& coverage, uint32_t start, uint32_t end) {
+ if (!coverage.empty() && coverage.back() > end) {
+ // Reject unordered end code points.
+ return false;
}
-
- inline bool isValid() const {
- return start != U32MAX && end != U32MAX;
+ if (coverage.empty() || coverage.back() < start) {
+ coverage.push_back(start);
+ coverage.push_back(end);
+ return true;
+ } else {
+ coverage.back() = end;
+ return true;
}
+}
- // Returns true if left and right intersect.
- inline static bool intersects(const Range& left, const Range& right) {
- return left.isValid() && right.isValid() &&
- left.start < right.end && right.start < left.end;
- }
-
- // Returns merged range. This method assumes left and right are not invalid ranges and they have
- // an intersection.
- static Range merge(const Range& left, const Range& right) {
- return Range({ std::min(left.start, right.start), std::max(left.end, right.end) });
- }
-};
-
-// Returns Range from given ranges vector. Returns InvalidRange if i is out of range.
+// Returns Range from given ranges vector. Returns invalidRange if i is out of range.
static inline Range getRange(const std::vector<uint32_t>& r, size_t i) {
- return i + 1 < r.size() ? Range({ r[i], r[i + 1] }) : Range::InvalidRange();
+ return i + 1 < r.size() ? Range({r[i], r[i + 1]}) : Range::invalidRange();
}
// Merge two sorted lists of ranges into one sorted list.
-static std::vector<uint32_t> mergeRanges(
- const std::vector<uint32_t>& lRanges, const std::vector<uint32_t>& rRanges) {
+static std::vector<uint32_t> mergeRanges(const std::vector<uint32_t>& lRanges,
+ const std::vector<uint32_t>& rRanges) {
std::vector<uint32_t> out;
const size_t lsize = lRanges.size();
@@ -119,7 +100,7 @@
// No ranges left in rRanges. Just put all remaining ranges in lRanges.
do {
Range r = getRange(lRanges, li);
- addRange(out, r.start, r.end); // Input is sorted. Never returns false.
+ addRange(out, r.getStart(), r.getEnd()); // Input is sorted. Never returns false.
li += 2;
} while (li < lsize);
break;
@@ -127,17 +108,19 @@
// No ranges left in lRanges. Just put all remaining ranges in rRanges.
do {
Range r = getRange(rRanges, ri);
- addRange(out, r.start, r.end); // Input is sorted. Never returns false.
+ addRange(out, r.getStart(), r.getEnd()); // Input is sorted. Never returns false.
ri += 2;
} while (ri < rsize);
break;
} else if (!Range::intersects(left, right)) {
// No intersection. Add smaller range.
- if (left.start < right.start) {
- addRange(out, left.start, left.end); // Input is sorted. Never returns false.
+ if (left.getStart() < right.getStart()) {
+ // Input is sorted. Never returns false.
+ addRange(out, left.getStart(), left.getEnd());
li += 2;
} else {
- addRange(out, right.start, right.end); // Input is sorted. Never returns false.
+ // Input is sorted. Never returns false.
+ addRange(out, right.getStart(), right.getEnd());
ri += 2;
}
} else {
@@ -157,7 +140,8 @@
right = getRange(rRanges, ri);
}
}
- addRange(out, merged.start, merged.end); // Input is sorted. Never returns false.
+ // Input is sorted. Never returns false.
+ addRange(out, merged.getStart(), merged.getEnd());
}
}
@@ -165,7 +149,7 @@
}
// Get the coverage information out of a Format 4 subtable, storing it in the coverage vector
-static bool getCoverageFormat4(vector<uint32_t>& coverage, const uint8_t* data, size_t size) {
+static bool getCoverageFormat4(std::vector<uint32_t>& coverage, const uint8_t* data, size_t size) {
const size_t kSegCountOffset = 6;
const size_t kEndCountOffset = 14;
const size_t kHeaderSize = 16;
@@ -189,13 +173,13 @@
if (rangeOffset == 0) {
uint32_t delta = readU16(data, kHeaderSize + 2 * (2 * segCount + i));
if (((end + delta) & 0xffff) > end - start) {
- if (!addRange(coverage, start, end + 1)) {
+ if (!addRangeCmap4(coverage, start, end + 1)) {
return false;
}
} else {
for (uint32_t j = start; j < end + 1; j++) {
if (((j + delta) & 0xffff) != 0) {
- if (!addRange(coverage, j, j + 1)) {
+ if (!addRangeCmap4(coverage, j, j + 1)) {
return false;
}
}
@@ -203,15 +187,15 @@
}
} else {
for (uint32_t j = start; j < end + 1; j++) {
- uint32_t actualRangeOffset = kHeaderSize + 6 * segCount + rangeOffset +
- (i + j - start) * 2;
+ uint32_t actualRangeOffset =
+ kHeaderSize + 6 * segCount + rangeOffset + (i + j - start) * 2;
if (actualRangeOffset + 2 > size) {
// invalid rangeOffset is considered a "warning" by OpenType Sanitizer
continue;
}
uint32_t glyphId = readU16(data, actualRangeOffset);
if (glyphId != 0) {
- if (!addRange(coverage, j, j + 1)) {
+ if (!addRangeCmap4(coverage, j, j + 1)) {
return false;
}
}
@@ -222,7 +206,7 @@
}
// Get the coverage information out of a Format 12 subtable, storing it in the coverage vector
-static bool getCoverageFormat12(vector<uint32_t>& coverage, const uint8_t* data, size_t size) {
+static bool getCoverageFormat12(std::vector<uint32_t>& coverage, const uint8_t* data, size_t size) {
const size_t kNGroupsOffset = 12;
const size_t kFirstGroupOffset = 16;
const size_t kGroupSize = 12;
@@ -303,8 +287,8 @@
// function assumes code points in both default UVS Table and non-default UVS table are stored in
// ascending order. This is required by the standard.
static bool getVSCoverage(std::vector<uint32_t>* out_ranges, const uint8_t* data, size_t size,
- uint32_t defaultUVSTableOffset, uint32_t nonDefaultUVSTableOffset,
- const SparseBitSet& baseCoverage) {
+ uint32_t defaultUVSTableOffset, uint32_t nonDefaultUVSTableOffset,
+ const SparseBitSet& baseCoverage) {
// Need to merge supported ranges from default UVS Table and non-default UVS Table.
// First, collect all supported code points from non default UVS table.
std::vector<uint32_t> rangesFromNonDefaultUVSTable;
@@ -384,7 +368,8 @@
}
static void getCoverageFormat14(std::vector<std::unique_ptr<SparseBitSet>>* out,
- const uint8_t* data, size_t size, const SparseBitSet& baseCoverage) {
+ const uint8_t* data, size_t size,
+ const SparseBitSet& baseCoverage) {
constexpr size_t kHeaderSize = 10;
constexpr size_t kRecordSize = 11;
constexpr size_t kLengthOffset = 2;
@@ -425,7 +410,7 @@
}
std::vector<uint32_t> ranges;
if (!getVSCoverage(&ranges, data, length, defaultUVSOffset, nonDefaultUVSOffset,
- baseCoverage)) {
+ baseCoverage)) {
continue;
}
if (out->size() < vsIndex + 1) {
@@ -438,7 +423,7 @@
}
SparseBitSet CmapCoverage::getCoverage(const uint8_t* cmap_data, size_t cmap_size,
- std::vector<std::unique_ptr<SparseBitSet>>* out) {
+ std::vector<std::unique_ptr<SparseBitSet>>* out) {
constexpr size_t kHeaderSize = 4;
constexpr size_t kNumTablesOffset = 2;
constexpr size_t kTableSize = 8;
@@ -533,7 +518,7 @@
const uint8_t* tableData = cmap_data + bestTableOffset;
const size_t tableSize = cmap_size - bestTableOffset;
bool success;
- vector<uint32_t> coverageVec;
+ std::vector<uint32_t> coverageVec;
if (bestTableFormat == 4) {
success = getCoverageFormat4(coverageVec, tableData, tableSize);
} else {
diff --git a/libs/minikin/Emoji.cpp b/libs/minikin/Emoji.cpp
index fbe68ca..ad83189 100644
--- a/libs/minikin/Emoji.cpp
+++ b/libs/minikin/Emoji.cpp
@@ -14,26 +14,23 @@
* limitations under the License.
*/
-#include <minikin/Emoji.h>
+#include "minikin/Emoji.h"
namespace minikin {
bool isNewEmoji(uint32_t c) {
- // Emoji characters new in Unicode emoji 5.0.
- // From http://www.unicode.org/Public/emoji/5.0/emoji-data.txt
- // TODO: Remove once emoji-data.text 5.0 is in ICU or update to 6.0.
- if (c < 0x1F6F7 || c > 0x1F9E6) {
+ // Emoji characters new in Unicode emoji 11
+ // From https://www.unicode.org/Public/emoji/11.0/emoji-data.txt
+ // TODO: Remove once emoji-data.text 11 is in ICU or update to 11.
+ if (c < 0x1F6F9 || c > 0x1F9FF) {
// Optimization for characters outside the new emoji range.
return false;
}
- return (0x1F6F7 <= c && c <= 0x1F6F8)
- || c == 0x1F91F
- || (0x1F928 <= c && c <= 0x1F92F)
- || (0x1F931 <= c && c <= 0x1F932)
- || c == 0x1F94C
- || (0x1F95F <= c && c <= 0x1F96B)
- || (0x1F992 <= c && c <= 0x1F997)
- || (0x1F9D0 <= c && c <= 0x1F9E6);
+ return c == 0x265F || c == 0x267E || c == 0x1F6F9 || (0x1F94D <= c && c <= 0x1F94F) ||
+ (0x1F96C <= c && c <= 0x1F970) || (0x1F973 <= c && c <= 0x1F976) || c == 0x1F97A ||
+ (0x1F97C <= c && c <= 0x1F97F) || (0x1F998 <= c && c <= 0x1F9A2) ||
+ (0x1F9B0 <= c && c <= 0x1F9B9) || (0x1F9C1 <= c && c <= 0x1F9C2) ||
+ (0x1F9E7 <= c && c <= 0x1F9FF);
}
bool isEmoji(uint32_t c) {
@@ -53,12 +50,10 @@
if (c == 0x1F91D || c == 0x1F93C) {
return true;
}
- // Emoji Modifier Base characters new in Unicode emoji 5.0.
- // From http://www.unicode.org/Public/emoji/5.0/emoji-data.txt
- // TODO: Remove once emoji-data.text 5.0 is in ICU or update to 6.0.
- if (c == 0x1F91F
- || (0x1F931 <= c && c <= 0x1F932)
- || (0x1F9D1 <= c && c <= 0x1F9DD)) {
+ // Emoji Modifier Base characters new in Unicode emoji 11
+ // From https://www.unicode.org/Public/emoji/11.0/emoji-data.txt
+ // TODO: Remove once emoji-data.text 11 is in ICU or update to 11.
+ if ((0x1F9B5 <= c && c <= 0x1F9B6) || (0x1F9B8 <= c && c <= 0x1F9B9)) {
return true;
}
return u_hasBinaryProperty(c, UCHAR_EMOJI_MODIFIER_BASE);
@@ -74,4 +69,3 @@
}
} // namespace minikin
-
diff --git a/libs/minikin/FontCollection.cpp b/libs/minikin/FontCollection.cpp
index 9abe84d..3d44ab5 100644
--- a/libs/minikin/FontCollection.cpp
+++ b/libs/minikin/FontCollection.cpp
@@ -14,21 +14,21 @@
* limitations under the License.
*/
-// #define VERBOSE_DEBUG
-
#define LOG_TAG "Minikin"
+#include "minikin/FontCollection.h"
+
#include <algorithm>
#include <log/log.h>
-#include "unicode/unistr.h"
-#include "unicode/unorm2.h"
+#include <unicode/unistr.h>
+#include <unicode/unorm2.h>
-#include "FontLanguage.h"
-#include "FontLanguageListCache.h"
+#include "minikin/Emoji.h"
+
+#include "Locale.h"
+#include "LocaleListCache.h"
#include "MinikinInternal.h"
-#include <minikin/Emoji.h>
-#include <minikin/FontCollection.h>
using std::vector;
@@ -36,13 +36,13 @@
template <typename T>
static inline T max(T a, T b) {
- return a>b ? a : b;
+ return a > b ? a : b;
}
const uint32_t EMOJI_STYLE_VS = 0xFE0F;
const uint32_t TEXT_STYLE_VS = 0xFE0E;
-uint32_t FontCollection::sNextId = 0;
+static std::atomic<uint32_t> gNextCollectionId = {0};
FontCollection::FontCollection(std::shared_ptr<FontFamily>&& typeface) : mMaxChar(0) {
std::vector<std::shared_ptr<FontFamily>> typefaces;
@@ -50,19 +50,14 @@
init(typefaces);
}
-FontCollection::FontCollection(const vector<std::shared_ptr<FontFamily>>& typefaces) :
- mMaxChar(0) {
+FontCollection::FontCollection(const vector<std::shared_ptr<FontFamily>>& typefaces) : mMaxChar(0) {
init(typefaces);
}
void FontCollection::init(const vector<std::shared_ptr<FontFamily>>& typefaces) {
- android::AutoMutex _l(gMinikinLock);
- mId = sNextId++;
+ mId = gNextCollectionId++;
vector<uint32_t> lastChar;
size_t nTypefaces = typefaces.size();
-#ifdef VERBOSE_DEBUG
- ALOGD("nTypefaces = %zd\n", nTypefaces);
-#endif
const FontStyle defaultStyle;
for (size_t i = 0; i < nTypefaces; i++) {
const std::shared_ptr<FontFamily>& family = typefaces[i];
@@ -81,10 +76,9 @@
mSupportedAxes.insert(supportedAxes.begin(), supportedAxes.end());
}
nTypefaces = mFamilies.size();
- LOG_ALWAYS_FATAL_IF(nTypefaces == 0,
- "Font collection must have at least one valid typeface");
- LOG_ALWAYS_FATAL_IF(nTypefaces > 254,
- "Font collection may only have up to 254 font families.");
+ MINIKIN_ASSERT(nTypefaces > 0, "Font collection must have at least one valid typeface");
+ MINIKIN_ASSERT(nTypefaces <= MAX_FAMILY_COUNT,
+ "Font collection may only have up to %d font families.", MAX_FAMILY_COUNT);
size_t nPages = (mMaxChar + kPageMask) >> kLogCharsPerPage;
// TODO: Use variation selector map for mRanges construction.
// A font can have a glyph for a base code point and variation selector pair but no glyph for
@@ -94,18 +88,12 @@
Range dummy;
mRanges.push_back(dummy);
Range* range = &mRanges.back();
-#ifdef VERBOSE_DEBUG
- ALOGD("i=%zd: range start = %zd\n", i, offset);
-#endif
range->start = mFamilyVec.size();
for (size_t j = 0; j < nTypefaces; j++) {
if (lastChar[j] < (i + 1) << kLogCharsPerPage) {
const std::shared_ptr<FontFamily>& family = mFamilies[j];
mFamilyVec.push_back(static_cast<uint8_t>(j));
uint32_t nextChar = family->getCoverage().nextSetBit((i + 1) << kLogCharsPerPage);
-#ifdef VERBOSE_DEBUG
- ALOGD("nextChar = %d (j = %zd)\n", nextChar, j);
-#endif
lastChar[j] = nextChar;
}
}
@@ -113,7 +101,7 @@
}
// See the comment in Range for more details.
LOG_ALWAYS_FATAL_IF(mFamilyVec.size() >= 0xFFFF,
- "Exceeded the maximum indexable cmap coverage.");
+ "Exceeded the maximum indexable cmap coverage.");
}
// Special scores for the font fallback.
@@ -123,12 +111,12 @@
// Calculates a font score.
// The score of the font family is based on three subscores.
// - Coverage Score: How well the font family covers the given character or variation sequence.
-// - Language Score: How well the font family is appropriate for the language.
+// - Locale Score: How well the font family is appropriate for the locale.
// - Variant Score: Whether the font family matches the variant. Note that this variant is not the
// one in BCP47. This is our own font variant (e.g., elegant, compact).
//
// Then, there is a priority for these three subscores as follow:
-// Coverage Score > Language Score > Variant Score
+// Coverage Score > Locale Score > Variant Score
// The returned score reflects this priority order.
//
// Note that there are two special scores.
@@ -136,22 +124,22 @@
// base character.
// - kFirstFontScore: When the font is the first font family in the collection and it supports the
// given character or variation sequence.
-uint32_t FontCollection::calcFamilyScore(uint32_t ch, uint32_t vs, int variant, uint32_t langListId,
- const std::shared_ptr<FontFamily>& fontFamily) const {
-
- const uint32_t coverageScore = calcCoverageScore(ch, vs, fontFamily);
+uint32_t FontCollection::calcFamilyScore(uint32_t ch, uint32_t vs, FontFamily::Variant variant,
+ uint32_t localeListId,
+ const std::shared_ptr<FontFamily>& fontFamily) const {
+ const uint32_t coverageScore = calcCoverageScore(ch, vs, localeListId, fontFamily);
if (coverageScore == kFirstFontScore || coverageScore == kUnsupportedFontScore) {
// No need to calculate other scores.
return coverageScore;
}
- const uint32_t languageScore = calcLanguageMatchingScore(langListId, *fontFamily);
+ const uint32_t localeScore = calcLocaleMatchingScore(localeListId, *fontFamily);
const uint32_t variantScore = calcVariantMatchingScore(variant, *fontFamily);
// Subscores are encoded into 31 bits representation to meet the subscore priority.
- // The highest 2 bits are for coverage score, then following 28 bits are for language score,
+ // The highest 2 bits are for coverage score, then following 28 bits are for locale score,
// then the last 1 bit is for variant score.
- return coverageScore << 29 | languageScore << 1 | variantScore;
+ return coverageScore << 29 | localeScore << 1 | variantScore;
}
// Calculates a font score based on variation sequence coverage.
@@ -164,8 +152,8 @@
// - Returns 2 if the vs is a text variation selector (U+FE0E) and if the font is not an emoji font.
// - Returns 1 if the variation selector is not specified or if the font family only supports the
// variation sequence's base character.
-uint32_t FontCollection::calcCoverageScore(uint32_t ch, uint32_t vs,
- const std::shared_ptr<FontFamily>& fontFamily) const {
+uint32_t FontCollection::calcCoverageScore(uint32_t ch, uint32_t vs, uint32_t localeListId,
+ const std::shared_ptr<FontFamily>& fontFamily) const {
const bool hasVSGlyph = (vs != 0) && fontFamily->hasGlyph(ch, vs);
if (!hasVSGlyph && !fontFamily->getCoverage().get(ch)) {
// The font doesn't support either variation sequence or even the base character.
@@ -178,34 +166,36 @@
return kFirstFontScore;
}
- if (vs == 0) {
- return 1;
- }
-
- if (hasVSGlyph) {
+ if (vs != 0 && hasVSGlyph) {
return 3;
}
- if (vs == EMOJI_STYLE_VS || vs == TEXT_STYLE_VS) {
- const FontLanguages& langs = FontLanguageListCache::getById(fontFamily->langId());
- bool hasEmojiFlag = false;
- for (size_t i = 0; i < langs.size(); ++i) {
- if (langs[i].getEmojiStyle() == FontLanguage::EMSTYLE_EMOJI) {
- hasEmojiFlag = true;
+ bool colorEmojiRequest;
+ if (vs == EMOJI_STYLE_VS) {
+ colorEmojiRequest = true;
+ } else if (vs == TEXT_STYLE_VS) {
+ colorEmojiRequest = false;
+ } else {
+ switch (LocaleListCache::getById(localeListId).getEmojiStyle()) {
+ case EmojiStyle::EMOJI:
+ colorEmojiRequest = true;
break;
- }
- }
-
- if (vs == EMOJI_STYLE_VS) {
- return hasEmojiFlag ? 2 : 1;
- } else { // vs == TEXT_STYLE_VS
- return hasEmojiFlag ? 1 : 2;
+ case EmojiStyle::TEXT:
+ colorEmojiRequest = false;
+ break;
+ case EmojiStyle::EMPTY:
+ case EmojiStyle::DEFAULT:
+ default:
+ // Do not give any extra score for the default emoji style.
+ return 1;
+ break;
}
}
- return 1;
+
+ return colorEmojiRequest == fontFamily->isColorEmojiFamily() ? 2 : 1;
}
-// Calculate font scores based on the script matching, subtag matching and primary langauge matching.
+// Calculate font scores based on the script matching, subtag matching and primary locale matching.
//
// 1. If only the font's language matches or there is no matches between requested font and
// supported font, then the font obtains a score of 0.
@@ -214,24 +204,24 @@
// 3. Regarding to two elements matchings, language-and-subtag matching has a score of 4, while
// language-and-script obtains a socre of 3 with the same reason above.
//
-// If two languages in the requested list have the same language score, the font matching with
-// higher priority language gets a higher score. For example, in the case the user requested
-// language list is "ja-Jpan,en-Latn". The score of for the font of "ja-Jpan" gets a higher score
-// than the font of "en-Latn".
+// If two locales in the requested list have the same locale score, the font matching with higher
+// priority locale gets a higher score. For example, in the case the user requested locale list is
+// "ja-Jpan,en-Latn". The score of for the font of "ja-Jpan" gets a higher score than the font of
+// "en-Latn".
//
-// To achieve score calculation with priorities, the language score is determined as follows:
-// LanguageScore = s(0) * 5^(m - 1) + s(1) * 5^(m - 2) + ... + s(m - 2) * 5 + s(m - 1)
-// Here, m is the maximum number of languages to be compared, and s(i) is the i-th language's
-// matching score. The possible values of s(i) are 0, 1, 2, 3 and 4.
-uint32_t FontCollection::calcLanguageMatchingScore(
- uint32_t userLangListId, const FontFamily& fontFamily) {
- const FontLanguages& langList = FontLanguageListCache::getById(userLangListId);
- const FontLanguages& fontLanguages = FontLanguageListCache::getById(fontFamily.langId());
+// To achieve score calculation with priorities, the locale score is determined as follows:
+// LocaleScore = s(0) * 5^(m - 1) + s(1) * 5^(m - 2) + ... + s(m - 2) * 5 + s(m - 1)
+// Here, m is the maximum number of locales to be compared, and s(i) is the i-th locale's matching
+// score. The possible values of s(i) are 0, 1, 2, 3 and 4.
+uint32_t FontCollection::calcLocaleMatchingScore(uint32_t userLocaleListId,
+ const FontFamily& fontFamily) {
+ const LocaleList& localeList = LocaleListCache::getById(userLocaleListId);
+ const LocaleList& fontLocaleList = LocaleListCache::getById(fontFamily.localeListId());
- const size_t maxCompareNum = std::min(langList.size(), FONT_LANGUAGES_LIMIT);
+ const size_t maxCompareNum = std::min(localeList.size(), FONT_LOCALE_LIMIT);
uint32_t score = 0;
for (size_t i = 0; i < maxCompareNum; ++i) {
- score = score * 5u + langList[i].calcScoreFor(fontLanguages);
+ score = score * 5u + localeList[i].calcScoreFor(fontLocaleList);
}
return score;
}
@@ -239,8 +229,20 @@
// Calculates a font score based on variant ("compact" or "elegant") matching.
// - Returns 1 if the font doesn't have variant or the variant matches with the text style.
// - No score if the font has a variant but it doesn't match with the text style.
-uint32_t FontCollection::calcVariantMatchingScore(int variant, const FontFamily& fontFamily) {
- return (fontFamily.variant() == 0 || fontFamily.variant() == variant) ? 1 : 0;
+uint32_t FontCollection::calcVariantMatchingScore(FontFamily::Variant variant,
+ const FontFamily& fontFamily) {
+ const FontFamily::Variant familyVariant = fontFamily.variant();
+ if (familyVariant == FontFamily::Variant::DEFAULT) {
+ return 1;
+ }
+ if (familyVariant == variant) {
+ return 1;
+ }
+ if (variant == FontFamily::Variant::DEFAULT && familyVariant == FontFamily::Variant::COMPACT) {
+ // If default is requested, prefer compat variation.
+ return 1;
+ }
+ return 0;
}
// Implement heuristic for choosing best-match font. Here are the rules:
@@ -248,8 +250,8 @@
// 2. Calculate a score for the font family. See comments in calcFamilyScore for the detail.
// 3. Highest score wins, with ties resolved to the first font.
// This method never returns nullptr.
-const std::shared_ptr<FontFamily>& FontCollection::getFamilyForChar(uint32_t ch, uint32_t vs,
- uint32_t langListId, int variant) const {
+const std::shared_ptr<FontFamily>& FontCollection::getFamilyForChar(
+ uint32_t ch, uint32_t vs, uint32_t localeListId, FontFamily::Variant variant) const {
if (ch >= mMaxChar) {
return mFamilies[0];
}
@@ -257,18 +259,15 @@
Range range = mRanges[ch >> kLogCharsPerPage];
if (vs != 0) {
- range = { 0, static_cast<uint16_t>(mFamilies.size()) };
+ range = {0, static_cast<uint16_t>(mFamilies.size())};
}
-#ifdef VERBOSE_DEBUG
- ALOGD("querying range %zd:%zd\n", range.start, range.end);
-#endif
int bestFamilyIndex = -1;
uint32_t bestScore = kUnsupportedFontScore;
for (size_t i = range.start; i < range.end; i++) {
const std::shared_ptr<FontFamily>& family =
vs == 0 ? mFamilies[mFamilyVec[i]] : mFamilies[i];
- const uint32_t score = calcFamilyScore(ch, vs, variant, langListId, family);
+ const uint32_t score = calcFamilyScore(ch, vs, variant, localeListId, family);
if (score == kFirstFontScore) {
// If the first font family supports the given character or variation sequence, always
// use it.
@@ -288,7 +287,7 @@
if (U_SUCCESS(errorCode) && len > 0) {
int off = 0;
U16_NEXT_UNSAFE(decomposed, off, ch);
- return getFamilyForChar(ch, vs, langListId, variant);
+ return getFamilyForChar(ch, vs, localeListId, variant);
}
}
return mFamilies[0];
@@ -301,33 +300,27 @@
// properly by Minikin or HarfBuzz even if the font does not explicitly support them and it's
// usually meaningless to switch to a different font to display them.
static bool doesNotNeedFontSupport(uint32_t c) {
- return c == 0x00AD // SOFT HYPHEN
- || c == 0x034F // COMBINING GRAPHEME JOINER
- || c == 0x061C // ARABIC LETTER MARK
- || (0x200C <= c && c <= 0x200F) // ZERO WIDTH NON-JOINER..RIGHT-TO-LEFT MARK
- || (0x202A <= c && c <= 0x202E) // LEFT-TO-RIGHT EMBEDDING..RIGHT-TO-LEFT OVERRIDE
- || (0x2066 <= c && c <= 0x2069) // LEFT-TO-RIGHT ISOLATE..POP DIRECTIONAL ISOLATE
- || c == 0xFEFF // BYTE ORDER MARK
- || isVariationSelector(c);
+ return c == 0x00AD // SOFT HYPHEN
+ || c == 0x034F // COMBINING GRAPHEME JOINER
+ || c == 0x061C // ARABIC LETTER MARK
+ || (0x200C <= c && c <= 0x200F) // ZERO WIDTH NON-JOINER..RIGHT-TO-LEFT MARK
+ || (0x202A <= c && c <= 0x202E) // LEFT-TO-RIGHT EMBEDDING..RIGHT-TO-LEFT OVERRIDE
+ || (0x2066 <= c && c <= 0x2069) // LEFT-TO-RIGHT ISOLATE..POP DIRECTIONAL ISOLATE
+ || c == 0xFEFF // BYTE ORDER MARK
+ || isVariationSelector(c);
}
// Characters where we want to continue using existing font run instead of
// recomputing the best match in the fallback list.
static const uint32_t stickyWhitelist[] = {
- '!',
- ',',
- '-',
- '.',
- ':',
- ';',
- '?',
- 0x00A0, // NBSP
- 0x2010, // HYPHEN
- 0x2011, // NB_HYPHEN
- 0x202F, // NNBSP
- 0x2640, // FEMALE_SIGN,
- 0x2642, // MALE_SIGN,
- 0x2695, // STAFF_OF_AESCULAPIUS
+ '!', ',', '-', '.', ':', ';', '?',
+ 0x00A0, // NBSP
+ 0x2010, // HYPHEN
+ 0x2011, // NB_HYPHEN
+ 0x202F, // NNBSP
+ 0x2640, // FEMALE_SIGN,
+ 0x2642, // MALE_SIGN,
+ 0x2695, // STAFF_OF_AESCULAPIUS
};
static bool isStickyWhitelisted(uint32_t c) {
@@ -342,7 +335,7 @@
}
bool FontCollection::hasVariationSelector(uint32_t baseCodepoint,
- uint32_t variationSelector) const {
+ uint32_t variationSelector) const {
if (!isVariationSelector(variationSelector)) {
return false;
}
@@ -357,9 +350,6 @@
}
}
- // TODO: We can remove this lock by precomputing color emoji information.
- android::AutoMutex _l(gMinikinLock);
-
// Even if there is no cmap format 14 subtable entry for the given sequence, should return true
// for <char, text presentation selector> case since we have special fallback rule for the
// sequence. Note that we don't need to restrict this to already standardized variation
@@ -378,10 +368,12 @@
constexpr uint32_t REPLACEMENT_CHARACTER = 0xFFFD;
-void FontCollection::itemize(const uint16_t *string, size_t string_size, FontStyle style,
- vector<Run>* result) const {
- const uint32_t langListId = style.getLanguageListId();
- int variant = style.getVariant();
+void FontCollection::itemize(const uint16_t* string, size_t string_size, const MinikinPaint& paint,
+ vector<Run>* result) const {
+ const FontFamily::Variant familyVariant = paint.familyVariant;
+ const FontStyle style = paint.fontStyle;
+ const uint32_t localeListId = paint.localeListId;
+
const FontFamily* lastFamily = nullptr;
Run* run = nullptr;
@@ -424,7 +416,7 @@
if (!shouldContinueRun) {
const std::shared_ptr<FontFamily>& family = getFamilyForChar(
- ch, isVariationSelector(nextCh) ? nextCh : 0, langListId, variant);
+ ch, isVariationSelector(nextCh) ? nextCh : 0, localeListId, familyVariant);
if (utf16Pos == 0 || family.get() != lastFamily) {
size_t start = utf16Pos;
// Workaround for combining marks and emoji modifiers until we implement
@@ -433,8 +425,8 @@
// character to the new run. U+20E3 COMBINING ENCLOSING KEYCAP, used in emoji, is
// handled properly by this since it's a combining mark too.
if (utf16Pos != 0 &&
- (isCombining(ch) || (isEmojiModifier(ch) && isEmojiBase(prevCh))) &&
- family != nullptr && family->getCoverage().get(prevCh)) {
+ (isCombining(ch) || (isEmojiModifier(ch) && isEmojiBase(prevCh))) &&
+ family != nullptr && family->getCoverage().get(prevCh)) {
const size_t prevChLength = U16_LENGTH(prevCh);
if (run != nullptr) {
run->end -= prevChLength;
@@ -491,7 +483,7 @@
return nullptr;
}
- std::vector<std::shared_ptr<FontFamily> > families;
+ std::vector<std::shared_ptr<FontFamily>> families;
for (const std::shared_ptr<FontFamily>& family : mFamilies) {
std::shared_ptr<FontFamily> newFamily = family->createFamilyWithVariation(variations);
if (newFamily) {
diff --git a/libs/minikin/FontFamily.cpp b/libs/minikin/FontFamily.cpp
index a93cb4f..b95d858 100644
--- a/libs/minikin/FontFamily.cpp
+++ b/libs/minikin/FontFamily.cpp
@@ -16,113 +16,115 @@
#define LOG_TAG "Minikin"
-#include <stdint.h>
-#include <stdlib.h>
-#include <string.h>
+#include "minikin/FontFamily.h"
+#include <cstdint>
+#include <vector>
+
+#include <hb-ot.h>
+#include <hb.h>
#include <log/log.h>
#include <utils/JenkinsHash.h>
-#include <hb.h>
-#include <hb-ot.h>
+#include "minikin/CmapCoverage.h"
+#include "minikin/HbUtils.h"
+#include "minikin/MinikinFont.h"
-#include "FontLanguage.h"
-#include "FontLanguageListCache.h"
#include "FontUtils.h"
-#include "HbFontCache.h"
+#include "Locale.h"
+#include "LocaleListCache.h"
#include "MinikinInternal.h"
-#include <minikin/CmapCoverage.h>
-#include <minikin/MinikinFont.h>
-#include <minikin/FontFamily.h>
-#include <minikin/MinikinFont.h>
-
-using std::vector;
namespace minikin {
-FontStyle::FontStyle(int variant, int weight, bool italic)
- : FontStyle(FontLanguageListCache::kEmptyListId, variant, weight, italic) {
-}
-
-FontStyle::FontStyle(uint32_t languageListId, int variant, int weight, bool italic)
- : bits(pack(variant, weight, italic)), mLanguageListId(languageListId) {
-}
-
-android::hash_t FontStyle::hash() const {
- uint32_t hash = android::JenkinsHashMix(0, bits);
- hash = android::JenkinsHashMix(hash, mLanguageListId);
- return android::JenkinsHashWhiten(hash);
-}
-
-// static
-uint32_t FontStyle::registerLanguageList(const std::string& languages) {
- android::AutoMutex _l(gMinikinLock);
- return FontLanguageListCache::getId(languages);
-}
-
-// static
-uint32_t FontStyle::pack(int variant, int weight, bool italic) {
- return (weight & kWeightMask) | (italic ? kItalicMask : 0) | (variant << kVariantShift);
-}
-
-Font::Font(const std::shared_ptr<MinikinFont>& typeface, FontStyle style)
- : typeface(typeface), style(style) {
-}
-
-Font::Font(std::shared_ptr<MinikinFont>&& typeface, FontStyle style)
- : typeface(typeface), style(style) {
-}
-
-std::unordered_set<AxisTag> Font::getSupportedAxesLocked() const {
- const uint32_t fvarTag = MinikinFont::MakeTag('f', 'v', 'a', 'r');
- HbBlob fvarTable(getFontTable(typeface.get(), fvarTag));
- if (fvarTable.size() == 0) {
- return std::unordered_set<AxisTag>();
+Font Font::Builder::build() {
+ if (mIsWeightSet && mIsSlantSet) {
+ // No need to read OS/2 header of the font file.
+ return Font(std::move(mTypeface), FontStyle(mWeight, mSlant), prepareFont(mTypeface));
}
+ HbFontUniquePtr font = prepareFont(mTypeface);
+ FontStyle styleFromFont = analyzeStyle(font);
+ if (!mIsWeightSet) {
+ mWeight = styleFromFont.weight();
+ }
+ if (!mIsSlantSet) {
+ mSlant = styleFromFont.slant();
+ }
+ return Font(std::move(mTypeface), FontStyle(mWeight, mSlant), std::move(font));
+}
+
+// static
+HbFontUniquePtr Font::prepareFont(const std::shared_ptr<MinikinFont>& typeface) {
+ const char* buf = reinterpret_cast<const char*>(typeface->GetFontData());
+ size_t size = typeface->GetFontSize();
+ uint32_t ttcIndex = typeface->GetFontIndex();
+
+ HbBlobUniquePtr blob(hb_blob_create(buf, size, HB_MEMORY_MODE_READONLY, nullptr, nullptr));
+ HbFaceUniquePtr face(hb_face_create(blob.get(), ttcIndex));
+ HbFontUniquePtr parent(hb_font_create(face.get()));
+ hb_ot_font_set_funcs(parent.get());
+
+ uint32_t upem = hb_face_get_upem(face.get());
+ hb_font_set_scale(parent.get(), upem, upem);
+
+ HbFontUniquePtr font(hb_font_create_sub_font(parent.get()));
+ std::vector<hb_variation_t> variations;
+ variations.reserve(typeface->GetAxes().size());
+ for (const FontVariation& variation : typeface->GetAxes()) {
+ variations.push_back({variation.axisTag, variation.value});
+ }
+ hb_font_set_variations(font.get(), variations.data(), variations.size());
+ return font;
+}
+
+// static
+FontStyle Font::analyzeStyle(const HbFontUniquePtr& font) {
+ HbBlob os2Table(font, MinikinFont::MakeTag('O', 'S', '/', '2'));
+ if (!os2Table) {
+ return FontStyle();
+ }
+
+ int weight;
+ bool italic;
+ if (!::minikin::analyzeStyle(os2Table.get(), os2Table.size(), &weight, &italic)) {
+ return FontStyle();
+ }
+ // TODO: Update weight/italic based on fvar value.
+ return FontStyle(static_cast<uint16_t>(weight), static_cast<FontStyle::Slant>(italic));
+}
+
+std::unordered_set<AxisTag> Font::getSupportedAxes() const {
+ HbBlob fvarTable(mBaseFont, MinikinFont::MakeTag('f', 'v', 'a', 'r'));
+ if (!fvarTable) {
+ return std::unordered_set<AxisTag>();
+ }
std::unordered_set<AxisTag> supportedAxes;
analyzeAxes(fvarTable.get(), fvarTable.size(), &supportedAxes);
return supportedAxes;
}
-Font::Font(Font&& o) {
- typeface = std::move(o.typeface);
- style = o.style;
- o.typeface = nullptr;
-}
+FontFamily::FontFamily(std::vector<Font>&& fonts)
+ : FontFamily(Variant::DEFAULT, std::move(fonts)) {}
-Font::Font(const Font& o) {
- typeface = o.typeface;
- style = o.style;
-}
+FontFamily::FontFamily(Variant variant, std::vector<Font>&& fonts)
+ : FontFamily(LocaleListCache::kEmptyListId, variant, std::move(fonts)) {}
-// static
-FontFamily::FontFamily(std::vector<Font>&& fonts) : FontFamily(0 /* variant */, std::move(fonts)) {
-}
-
-FontFamily::FontFamily(int variant, std::vector<Font>&& fonts)
- : FontFamily(FontLanguageListCache::kEmptyListId, variant, std::move(fonts)) {
-}
-
-FontFamily::FontFamily(uint32_t langId, int variant, std::vector<Font>&& fonts)
- : mLangId(langId), mVariant(variant), mFonts(std::move(fonts)) {
+FontFamily::FontFamily(uint32_t localeListId, Variant variant, std::vector<Font>&& fonts)
+ : mLocaleListId(localeListId),
+ mVariant(variant),
+ mFonts(std::move(fonts)),
+ mIsColorEmoji(LocaleListCache::getById(localeListId).getEmojiStyle() ==
+ EmojiStyle::EMOJI) {
+ MINIKIN_ASSERT(!mFonts.empty(), "FontFamily must contain at least one font.");
computeCoverage();
}
-bool FontFamily::analyzeStyle(const std::shared_ptr<MinikinFont>& typeface, int* weight,
- bool* italic) {
- android::AutoMutex _l(gMinikinLock);
- const uint32_t os2Tag = MinikinFont::MakeTag('O', 'S', '/', '2');
- HbBlob os2Table(getFontTable(typeface.get(), os2Tag));
- if (os2Table.get() == nullptr) return false;
- return ::minikin::analyzeStyle(os2Table.get(), os2Table.size(), weight, italic);
-}
-
// Compute a matching metric between two styles - 0 is an exact match
static int computeMatch(FontStyle style1, FontStyle style2) {
if (style1 == style2) return 0;
- int score = abs(style1.getWeight() - style2.getWeight());
- if (style1.getItalic() != style2.getItalic()) {
+ int score = abs(style1.weight() / 100 - style2.weight() / 100);
+ if (style1.slant() != style2.slant()) {
score += 2;
}
return score;
@@ -132,53 +134,38 @@
// If desired weight is semibold or darker, and 2 or more grades
// higher than actual (for example, medium 500 -> bold 700), then
// select fake bold.
- int wantedWeight = wanted.getWeight();
- bool isFakeBold = wantedWeight >= 6 && (wantedWeight - actual.getWeight()) >= 2;
- bool isFakeItalic = wanted.getItalic() && !actual.getItalic();
+ bool isFakeBold = wanted.weight() >= 600 && (wanted.weight() - actual.weight()) >= 200;
+ bool isFakeItalic = wanted.slant() == FontStyle::Slant::ITALIC &&
+ actual.slant() == FontStyle::Slant::UPRIGHT;
return FontFakery(isFakeBold, isFakeItalic);
}
FakedFont FontFamily::getClosestMatch(FontStyle style) const {
- const Font* bestFont = nullptr;
- int bestMatch = 0;
- for (size_t i = 0; i < mFonts.size(); i++) {
+ const Font* bestFont = &mFonts[0];
+ int bestMatch = computeMatch(bestFont->style(), style);
+ for (size_t i = 1; i < mFonts.size(); i++) {
const Font& font = mFonts[i];
- int match = computeMatch(font.style, style);
+ int match = computeMatch(font.style(), style);
if (i == 0 || match < bestMatch) {
bestFont = &font;
bestMatch = match;
}
}
- if (bestFont != nullptr) {
- return FakedFont{ bestFont->typeface.get(), computeFakery(style, bestFont->style) };
- }
- return FakedFont{ nullptr, FontFakery() };
-}
-
-bool FontFamily::isColorEmojiFamily() const {
- const FontLanguages& languageList = FontLanguageListCache::getById(mLangId);
- for (size_t i = 0; i < languageList.size(); ++i) {
- if (languageList[i].getEmojiStyle() == FontLanguage::EMSTYLE_EMOJI) {
- return true;
- }
- }
- return false;
+ return FakedFont{bestFont, computeFakery(style, bestFont->style())};
}
void FontFamily::computeCoverage() {
- android::AutoMutex _l(gMinikinLock);
- const FontStyle defaultStyle;
- const MinikinFont* typeface = getClosestMatch(defaultStyle).font;
- const uint32_t cmapTag = MinikinFont::MakeTag('c', 'm', 'a', 'p');
- HbBlob cmapTable(getFontTable(typeface, cmapTag));
+ const Font* font = getClosestMatch(FontStyle()).font;
+ HbBlob cmapTable(font->baseFont(), MinikinFont::MakeTag('c', 'm', 'a', 'p'));
if (cmapTable.get() == nullptr) {
ALOGE("Could not get cmap table size!\n");
return;
}
+
mCoverage = CmapCoverage::getCoverage(cmapTable.get(), cmapTable.size(), &mCmapFmt14Coverage);
for (size_t i = 0; i < mFonts.size(); ++i) {
- std::unordered_set<AxisTag> supportedAxes = mFonts[i].getSupportedAxesLocked();
+ std::unordered_set<AxisTag> supportedAxes = mFonts[i].getSupportedAxes();
mSupportedAxes.insert(supportedAxes.begin(), supportedAxes.end());
}
}
@@ -229,8 +216,7 @@
std::vector<Font> fonts;
for (const Font& font : mFonts) {
bool supportedVariations = false;
- android::AutoMutex _l(gMinikinLock);
- std::unordered_set<AxisTag> supportedAxes = font.getSupportedAxesLocked();
+ std::unordered_set<AxisTag> supportedAxes = font.getSupportedAxes();
if (!supportedAxes.empty()) {
for (const FontVariation& variation : variations) {
if (supportedAxes.find(variation.axisTag) != supportedAxes.end()) {
@@ -241,15 +227,15 @@
}
std::shared_ptr<MinikinFont> minikinFont;
if (supportedVariations) {
- minikinFont = font.typeface->createFontWithVariation(variations);
+ minikinFont = font.typeface()->createFontWithVariation(variations);
}
if (minikinFont == nullptr) {
- minikinFont = font.typeface;
+ minikinFont = font.typeface();
}
- fonts.push_back(Font(std::move(minikinFont), font.style));
+ fonts.push_back(Font::Builder(minikinFont).setStyle(font.style()).build());
}
- return std::shared_ptr<FontFamily>(new FontFamily(mLangId, mVariant, std::move(fonts)));
+ return std::shared_ptr<FontFamily>(new FontFamily(mLocaleListId, mVariant, std::move(fonts)));
}
} // namespace minikin
diff --git a/libs/minikin/FontLanguage.cpp b/libs/minikin/FontLanguage.cpp
deleted file mode 100644
index 0897c06..0000000
--- a/libs/minikin/FontLanguage.cpp
+++ /dev/null
@@ -1,345 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "Minikin"
-
-#include "FontLanguage.h"
-
-#include <algorithm>
-#include <hb.h>
-#include <string.h>
-#include <unicode/uloc.h>
-
-namespace minikin {
-
-#define SCRIPT_TAG(c1, c2, c3, c4) \
- (((uint32_t)(c1)) << 24 | ((uint32_t)(c2)) << 16 | ((uint32_t)(c3)) << 8 | \
- ((uint32_t)(c4)))
-
-// Check if a language code supports emoji according to its subtag
-static bool isEmojiSubtag(const char* buf, size_t bufLen, const char* subtag, size_t subtagLen) {
- if (bufLen < subtagLen) {
- return false;
- }
- if (strncmp(buf, subtag, subtagLen) != 0) {
- return false; // no match between two strings
- }
- return (bufLen == subtagLen || buf[subtagLen] == '\0' ||
- buf[subtagLen] == '-' || buf[subtagLen] == '_');
-}
-
-// Pack the three letter code into 15 bits and stored to 16 bit integer. The highest bit is 0.
-// For the region code, the letters must be all digits in three letter case, so the number of
-// possible values are 10. For the language code, the letters must be all small alphabets, so the
-// number of possible values are 26. Thus, 5 bits are sufficient for each case and we can pack the
-// three letter language code or region code to 15 bits.
-//
-// In case of two letter code, use fullbit(0x1f) for the first letter instead.
-static uint16_t packLanguageOrRegion(const char* c, size_t length, uint8_t twoLetterBase,
- uint8_t threeLetterBase) {
- if (length == 2) {
- return 0x7c00u | // 0x1fu << 10
- (uint16_t)(c[0] - twoLetterBase) << 5 |
- (uint16_t)(c[1] - twoLetterBase);
- } else {
- return ((uint16_t)(c[0] - threeLetterBase) << 10) |
- (uint16_t)(c[1] - threeLetterBase) << 5 |
- (uint16_t)(c[2] - threeLetterBase);
- }
-}
-
-static size_t unpackLanguageOrRegion(uint16_t in, char* out, uint8_t twoLetterBase,
- uint8_t threeLetterBase) {
- uint8_t first = (in >> 10) & 0x1f;
- uint8_t second = (in >> 5) & 0x1f;
- uint8_t third = in & 0x1f;
-
- if (first == 0x1f) {
- out[0] = second + twoLetterBase;
- out[1] = third + twoLetterBase;
- return 2;
- } else {
- out[0] = first + threeLetterBase;
- out[1] = second + threeLetterBase;
- out[2] = third + threeLetterBase;
- return 3;
- }
-}
-
-// Find the next '-' or '_' index from startOffset position. If not found, returns bufferLength.
-static size_t nextDelimiterIndex(const char* buffer, size_t bufferLength, size_t startOffset) {
- for (size_t i = startOffset; i < bufferLength; ++i) {
- if (buffer[i] == '-' || buffer[i] == '_') {
- return i;
- }
- }
- return bufferLength;
-}
-
-static inline bool isLowercase(char c) {
- return 'a' <= c && c <= 'z';
-}
-
-static inline bool isUppercase(char c) {
- return 'A' <= c && c <= 'Z';
-}
-
-static inline bool isDigit(char c) {
- return '0' <= c && c <= '9';
-}
-
-// Returns true if the buffer is valid for language code.
-static inline bool isValidLanguageCode(const char* buffer, size_t length) {
- if (length != 2 && length != 3) return false;
- if (!isLowercase(buffer[0])) return false;
- if (!isLowercase(buffer[1])) return false;
- if (length == 3 && !isLowercase(buffer[2])) return false;
- return true;
-}
-
-// Returns true if buffer is valid for script code. The length of buffer must be 4.
-static inline bool isValidScriptCode(const char* buffer) {
- return isUppercase(buffer[0]) && isLowercase(buffer[1]) && isLowercase(buffer[2]) &&
- isLowercase(buffer[3]);
-}
-
-// Returns true if the buffer is valid for region code.
-static inline bool isValidRegionCode(const char* buffer, size_t length) {
- return (length == 2 && isUppercase(buffer[0]) && isUppercase(buffer[1])) ||
- (length == 3 && isDigit(buffer[0]) && isDigit(buffer[1]) && isDigit(buffer[2]));
-}
-
-// Parse BCP 47 language identifier into internal structure
-FontLanguage::FontLanguage(const char* buf, size_t length) : FontLanguage() {
- size_t firstDelimiterPos = nextDelimiterIndex(buf, length, 0);
- if (isValidLanguageCode(buf, firstDelimiterPos)) {
- mLanguage = packLanguageOrRegion(buf, firstDelimiterPos, 'a', 'a');
- } else {
- // We don't understand anything other than two-letter or three-letter
- // language codes, so we skip parsing the rest of the string.
- return;
- }
-
- if (firstDelimiterPos == length) {
- mHbLanguage = hb_language_from_string(getString().c_str(), -1);
- return; // Language code only.
- }
-
- size_t nextComponentStartPos = firstDelimiterPos + 1;
- size_t nextDelimiterPos = nextDelimiterIndex(buf, length, nextComponentStartPos);
- size_t componentLength = nextDelimiterPos - nextComponentStartPos;
-
- if (componentLength == 4) {
- // Possibly script code.
- const char* p = buf + nextComponentStartPos;
- if (isValidScriptCode(p)) {
- mScript = SCRIPT_TAG(p[0], p[1], p[2], p[3]);
- mSubScriptBits = scriptToSubScriptBits(mScript);
- }
-
- if (nextDelimiterPos == length) {
- mHbLanguage = hb_language_from_string(getString().c_str(), -1);
- mEmojiStyle = resolveEmojiStyle(buf, length, mScript);
- return; // No region code.
- }
-
- nextComponentStartPos = nextDelimiterPos + 1;
- nextDelimiterPos = nextDelimiterIndex(buf, length, nextComponentStartPos);
- componentLength = nextDelimiterPos - nextComponentStartPos;
- }
-
- if (componentLength == 2 || componentLength == 3) {
- // Possibly region code.
- const char* p = buf + nextComponentStartPos;
- if (isValidRegionCode(p, componentLength)) {
- mRegion = packLanguageOrRegion(p, componentLength, 'A', '0');
- }
- }
-
- mHbLanguage = hb_language_from_string(getString().c_str(), -1);
- mEmojiStyle = resolveEmojiStyle(buf, length, mScript);
-}
-
-// static
-FontLanguage::EmojiStyle FontLanguage::resolveEmojiStyle(const char* buf, size_t length,
- uint32_t script) {
- // First, lookup emoji subtag.
- // 10 is the length of "-u-em-text", which is the shortest emoji subtag,
- // unnecessary comparison can be avoided if total length is smaller than 10.
- const size_t kMinSubtagLength = 10;
- if (length >= kMinSubtagLength) {
- static const char kPrefix[] = "-u-em-";
- const char *pos = std::search(buf, buf + length, kPrefix, kPrefix + strlen(kPrefix));
- if (pos != buf + length) { // found
- pos += strlen(kPrefix);
- const size_t remainingLength = length - (pos - buf);
- if (isEmojiSubtag(pos, remainingLength, "emoji", 5)){
- return EMSTYLE_EMOJI;
- } else if (isEmojiSubtag(pos, remainingLength, "text", 4)){
- return EMSTYLE_TEXT;
- } else if (isEmojiSubtag(pos, remainingLength, "default", 7)){
- return EMSTYLE_DEFAULT;
- }
- }
- }
-
- // If no emoji subtag was provided, resolve the emoji style from script code.
- if (script == SCRIPT_TAG('Z', 's', 'y', 'e')) {
- return EMSTYLE_EMOJI;
- } else if (script == SCRIPT_TAG('Z', 's', 'y', 'm')) {
- return EMSTYLE_TEXT;
- }
-
- return EMSTYLE_EMPTY;
-}
-
-//static
-uint8_t FontLanguage::scriptToSubScriptBits(uint32_t script) {
- uint8_t subScriptBits = 0u;
- switch (script) {
- case SCRIPT_TAG('B', 'o', 'p', 'o'):
- subScriptBits = kBopomofoFlag;
- break;
- case SCRIPT_TAG('H', 'a', 'n', 'g'):
- subScriptBits = kHangulFlag;
- break;
- case SCRIPT_TAG('H', 'a', 'n', 'b'):
- // Bopomofo is almost exclusively used in Taiwan.
- subScriptBits = kHanFlag | kBopomofoFlag;
- break;
- case SCRIPT_TAG('H', 'a', 'n', 'i'):
- subScriptBits = kHanFlag;
- break;
- case SCRIPT_TAG('H', 'a', 'n', 's'):
- subScriptBits = kHanFlag | kSimplifiedChineseFlag;
- break;
- case SCRIPT_TAG('H', 'a', 'n', 't'):
- subScriptBits = kHanFlag | kTraditionalChineseFlag;
- break;
- case SCRIPT_TAG('H', 'i', 'r', 'a'):
- subScriptBits = kHiraganaFlag;
- break;
- case SCRIPT_TAG('H', 'r', 'k', 't'):
- subScriptBits = kKatakanaFlag | kHiraganaFlag;
- break;
- case SCRIPT_TAG('J', 'p', 'a', 'n'):
- subScriptBits = kHanFlag | kKatakanaFlag | kHiraganaFlag;
- break;
- case SCRIPT_TAG('K', 'a', 'n', 'a'):
- subScriptBits = kKatakanaFlag;
- break;
- case SCRIPT_TAG('K', 'o', 'r', 'e'):
- subScriptBits = kHanFlag | kHangulFlag;
- break;
- }
- return subScriptBits;
-}
-
-std::string FontLanguage::getString() const {
- if (isUnsupported()) {
- return "und";
- }
- char buf[16];
- size_t i = unpackLanguageOrRegion(mLanguage, buf, 'a', 'a');
- if (mScript != 0) {
- buf[i++] = '-';
- buf[i++] = (mScript >> 24) & 0xFFu;
- buf[i++] = (mScript >> 16) & 0xFFu;
- buf[i++] = (mScript >> 8) & 0xFFu;
- buf[i++] = mScript & 0xFFu;
- }
- if (mRegion != INVALID_CODE) {
- buf[i++] = '-';
- i += unpackLanguageOrRegion(mRegion, buf + i, 'A', '0');
- }
- return std::string(buf, i);
-}
-
-bool FontLanguage::isEqualScript(const FontLanguage& other) const {
- return other.mScript == mScript;
-}
-
-// static
-bool FontLanguage::supportsScript(uint8_t providedBits, uint8_t requestedBits) {
- return requestedBits != 0 && (providedBits & requestedBits) == requestedBits;
-}
-
-bool FontLanguage::supportsHbScript(hb_script_t script) const {
- static_assert(SCRIPT_TAG('J', 'p', 'a', 'n') == HB_TAG('J', 'p', 'a', 'n'),
- "The Minikin script and HarfBuzz hb_script_t have different encodings.");
- if (script == mScript) return true;
- return supportsScript(mSubScriptBits, scriptToSubScriptBits(script));
-}
-
-int FontLanguage::calcScoreFor(const FontLanguages& supported) const {
- bool languageScriptMatch = false;
- bool subtagMatch = false;
- bool scriptMatch = false;
-
- for (size_t i = 0; i < supported.size(); ++i) {
- if (mEmojiStyle != EMSTYLE_EMPTY &&
- mEmojiStyle == supported[i].mEmojiStyle) {
- subtagMatch = true;
- if (mLanguage == supported[i].mLanguage) {
- return 4;
- }
- }
- if (isEqualScript(supported[i]) ||
- supportsScript(supported[i].mSubScriptBits, mSubScriptBits)) {
- scriptMatch = true;
- if (mLanguage == supported[i].mLanguage) {
- languageScriptMatch = true;
- }
- }
- }
-
- if (supportsScript(supported.getUnionOfSubScriptBits(), mSubScriptBits)) {
- scriptMatch = true;
- if (mLanguage == supported[0].mLanguage && supported.isAllTheSameLanguage()) {
- return 3;
- }
- }
-
- if (languageScriptMatch) {
- return 3;
- } else if (subtagMatch) {
- return 2;
- } else if (scriptMatch) {
- return 1;
- }
- return 0;
-}
-
-FontLanguages::FontLanguages(std::vector<FontLanguage>&& languages)
- : mLanguages(std::move(languages)) {
- if (mLanguages.empty()) {
- return;
- }
-
- const FontLanguage& lang = mLanguages[0];
-
- mIsAllTheSameLanguage = true;
- mUnionOfSubScriptBits = lang.mSubScriptBits;
- for (size_t i = 1; i < mLanguages.size(); ++i) {
- mUnionOfSubScriptBits |= mLanguages[i].mSubScriptBits;
- if (mIsAllTheSameLanguage && lang.mLanguage != mLanguages[i].mLanguage) {
- mIsAllTheSameLanguage = false;
- }
- }
-}
-
-#undef SCRIPT_TAG
-} // namespace minikin
diff --git a/libs/minikin/FontLanguage.h b/libs/minikin/FontLanguage.h
deleted file mode 100644
index 6a50b1d..0000000
--- a/libs/minikin/FontLanguage.h
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef MINIKIN_FONT_LANGUAGE_H
-#define MINIKIN_FONT_LANGUAGE_H
-
-#include <string>
-#include <vector>
-
-#include <hb.h>
-
-namespace minikin {
-
-// Due to the limits in font fallback score calculation, we can't use anything more than 12
-// languages.
-const size_t FONT_LANGUAGES_LIMIT = 12;
-
-// The language or region code is encoded to 15 bits.
-const uint16_t INVALID_CODE = 0x7fff;
-
-class FontLanguages;
-
-// FontLanguage is a compact representation of a BCP 47 language tag. It
-// does not capture all possible information, only what directly affects
-// font rendering.
-struct FontLanguage {
-public:
- enum EmojiStyle : uint8_t {
- EMSTYLE_EMPTY = 0,
- EMSTYLE_DEFAULT = 1,
- EMSTYLE_EMOJI = 2,
- EMSTYLE_TEXT = 3,
- };
- // Default constructor creates the unsupported language.
- FontLanguage()
- : mScript(0ul),
- mLanguage(INVALID_CODE),
- mRegion(INVALID_CODE),
- mHbLanguage(HB_LANGUAGE_INVALID),
- mSubScriptBits(0ul),
- mEmojiStyle(EMSTYLE_EMPTY) {}
-
- // Parse from string
- FontLanguage(const char* buf, size_t length);
-
- bool operator==(const FontLanguage other) const {
- return !isUnsupported() && isEqualScript(other) && mLanguage == other.mLanguage &&
- mRegion == other.mRegion && mEmojiStyle == other.mEmojiStyle;
- }
-
- bool operator!=(const FontLanguage other) const {
- return !(*this == other);
- }
-
- bool isUnsupported() const { return mLanguage == INVALID_CODE; }
- EmojiStyle getEmojiStyle() const { return mEmojiStyle; }
- hb_language_t getHbLanguage() const { return mHbLanguage; }
-
-
- bool isEqualScript(const FontLanguage& other) const;
-
- // Returns true if this script supports the given script. For example, ja-Jpan supports Hira,
- // ja-Hira doesn't support Jpan.
- bool supportsHbScript(hb_script_t script) const;
-
- std::string getString() const;
-
- // Calculates a matching score. This score represents how well the input languages cover this
- // language. The maximum score in the language list is returned.
- // 0 = no match, 1 = script match, 2 = script and primary language match.
- int calcScoreFor(const FontLanguages& supported) const;
-
- uint64_t getIdentifier() const {
- return ((uint64_t)mLanguage << 49) | ((uint64_t)mScript << 17) | ((uint64_t)mRegion << 2) |
- mEmojiStyle;
- }
-
-private:
- friend class FontLanguages; // for FontLanguages constructor
-
- // ISO 15924 compliant script code. The 4 chars script code are packed into a 32 bit integer.
- uint32_t mScript;
-
- // ISO 639-1 or ISO 639-2 compliant language code.
- // The two- or three-letter language code is packed into a 15 bit integer.
- // mLanguage = 0 means the FontLanguage is unsupported.
- uint16_t mLanguage;
-
- // ISO 3166-1 or UN M.49 compliant region code. The two-letter or three-digit region code is
- // packed into a 15 bit integer.
- uint16_t mRegion;
-
- // The language to be passed HarfBuzz shaper.
- hb_language_t mHbLanguage;
-
- // For faster comparing, use 7 bits for specific scripts.
- static const uint8_t kBopomofoFlag = 1u;
- static const uint8_t kHanFlag = 1u << 1;
- static const uint8_t kHangulFlag = 1u << 2;
- static const uint8_t kHiraganaFlag = 1u << 3;
- static const uint8_t kKatakanaFlag = 1u << 4;
- static const uint8_t kSimplifiedChineseFlag = 1u << 5;
- static const uint8_t kTraditionalChineseFlag = 1u << 6;
- uint8_t mSubScriptBits;
-
- EmojiStyle mEmojiStyle;
-
- static uint8_t scriptToSubScriptBits(uint32_t script);
-
- static EmojiStyle resolveEmojiStyle(const char* buf, size_t length, uint32_t script);
-
- // Returns true if the provide subscript bits has the requested subscript bits.
- // Note that this function returns false if the requested subscript bits are empty.
- static bool supportsScript(uint8_t providedBits, uint8_t requestedBits);
-};
-
-// An immutable list of languages.
-class FontLanguages {
-public:
- explicit FontLanguages(std::vector<FontLanguage>&& languages);
- FontLanguages() : mUnionOfSubScriptBits(0), mIsAllTheSameLanguage(false) {}
- FontLanguages(FontLanguages&&) = default;
-
- size_t size() const { return mLanguages.size(); }
- bool empty() const { return mLanguages.empty(); }
- const FontLanguage& operator[] (size_t n) const { return mLanguages[n]; }
-
-private:
- friend struct FontLanguage; // for calcScoreFor
-
- std::vector<FontLanguage> mLanguages;
- uint8_t mUnionOfSubScriptBits;
- bool mIsAllTheSameLanguage;
-
- uint8_t getUnionOfSubScriptBits() const { return mUnionOfSubScriptBits; }
- bool isAllTheSameLanguage() const { return mIsAllTheSameLanguage; }
-
- // Do not copy and assign.
- FontLanguages(const FontLanguages&) = delete;
- void operator=(const FontLanguages&) = delete;
-};
-
-} // namespace minikin
-
-#endif // MINIKIN_FONT_LANGUAGE_H
diff --git a/libs/minikin/FontLanguageListCache.cpp b/libs/minikin/FontLanguageListCache.cpp
deleted file mode 100644
index f1e14f0..0000000
--- a/libs/minikin/FontLanguageListCache.cpp
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "Minikin"
-
-#include "FontLanguageListCache.h"
-
-#include <unicode/uloc.h>
-#include <unordered_set>
-
-#include <log/log.h>
-
-#include "FontLanguage.h"
-#include "MinikinInternal.h"
-
-namespace minikin {
-
-const uint32_t FontLanguageListCache::kEmptyListId;
-
-// Returns the text length of output.
-static size_t toLanguageTag(char* output, size_t outSize, const std::string& locale) {
- output[0] = '\0';
- if (locale.empty()) {
- return 0;
- }
-
- size_t outLength = 0;
- UErrorCode uErr = U_ZERO_ERROR;
- outLength = uloc_canonicalize(locale.c_str(), output, outSize, &uErr);
- if (U_FAILURE(uErr)) {
- // unable to build a proper language identifier
- ALOGD("uloc_canonicalize(\"%s\") failed: %s", locale.c_str(), u_errorName(uErr));
- output[0] = '\0';
- return 0;
- }
-
- // Preserve "und" and "und-****" since uloc_addLikelySubtags changes "und" to "en-Latn-US".
- if (strncmp(output, "und", 3) == 0 &&
- (outLength == 3 || (outLength == 8 && output[3] == '_'))) {
- return outLength;
- }
-
- char likelyChars[ULOC_FULLNAME_CAPACITY];
- uErr = U_ZERO_ERROR;
- uloc_addLikelySubtags(output, likelyChars, ULOC_FULLNAME_CAPACITY, &uErr);
- if (U_FAILURE(uErr)) {
- // unable to build a proper language identifier
- ALOGD("uloc_addLikelySubtags(\"%s\") failed: %s", output, u_errorName(uErr));
- output[0] = '\0';
- return 0;
- }
-
- uErr = U_ZERO_ERROR;
- outLength = uloc_toLanguageTag(likelyChars, output, outSize, FALSE, &uErr);
- if (U_FAILURE(uErr)) {
- // unable to build a proper language identifier
- ALOGD("uloc_toLanguageTag(\"%s\") failed: %s", likelyChars, u_errorName(uErr));
- output[0] = '\0';
- return 0;
- }
-#ifdef VERBOSE_DEBUG
- ALOGD("ICU normalized '%s' to '%s'", locale.c_str(), output);
-#endif
- return outLength;
-}
-
-static std::vector<FontLanguage> parseLanguageList(const std::string& input) {
- std::vector<FontLanguage> result;
- size_t currentIdx = 0;
- size_t commaLoc = 0;
- char langTag[ULOC_FULLNAME_CAPACITY];
- std::unordered_set<uint64_t> seen;
- std::string locale(input.size(), 0);
-
- while ((commaLoc = input.find_first_of(',', currentIdx)) != std::string::npos) {
- locale.assign(input, currentIdx, commaLoc - currentIdx);
- currentIdx = commaLoc + 1;
- size_t length = toLanguageTag(langTag, ULOC_FULLNAME_CAPACITY, locale);
- FontLanguage lang(langTag, length);
- uint64_t identifier = lang.getIdentifier();
- if (!lang.isUnsupported() && seen.count(identifier) == 0) {
- result.push_back(lang);
- if (result.size() == FONT_LANGUAGES_LIMIT) {
- break;
- }
- seen.insert(identifier);
- }
- }
- if (result.size() < FONT_LANGUAGES_LIMIT) {
- locale.assign(input, currentIdx, input.size() - currentIdx);
- size_t length = toLanguageTag(langTag, ULOC_FULLNAME_CAPACITY, locale);
- FontLanguage lang(langTag, length);
- uint64_t identifier = lang.getIdentifier();
- if (!lang.isUnsupported() && seen.count(identifier) == 0) {
- result.push_back(lang);
- }
- }
- return result;
-}
-
-// static
-uint32_t FontLanguageListCache::getId(const std::string& languages) {
- FontLanguageListCache* inst = FontLanguageListCache::getInstance();
- std::unordered_map<std::string, uint32_t>::const_iterator it =
- inst->mLanguageListLookupTable.find(languages);
- if (it != inst->mLanguageListLookupTable.end()) {
- return it->second;
- }
-
- // Given language list is not in cache. Insert it and return newly assigned ID.
- const uint32_t nextId = inst->mLanguageLists.size();
- FontLanguages fontLanguages(parseLanguageList(languages));
- if (fontLanguages.empty()) {
- return kEmptyListId;
- }
- inst->mLanguageLists.push_back(std::move(fontLanguages));
- inst->mLanguageListLookupTable.insert(std::make_pair(languages, nextId));
- return nextId;
-}
-
-// static
-const FontLanguages& FontLanguageListCache::getById(uint32_t id) {
- FontLanguageListCache* inst = FontLanguageListCache::getInstance();
- LOG_ALWAYS_FATAL_IF(id >= inst->mLanguageLists.size(), "Lookup by unknown language list ID.");
- return inst->mLanguageLists[id];
-}
-
-// static
-FontLanguageListCache* FontLanguageListCache::getInstance() {
- assertMinikinLocked();
- static FontLanguageListCache* instance = nullptr;
- if (instance == nullptr) {
- instance = new FontLanguageListCache();
-
- // Insert an empty language list for mapping default language list to kEmptyListId.
- // The default language list has only one FontLanguage and it is the unsupported language.
- instance->mLanguageLists.push_back(FontLanguages());
- instance->mLanguageListLookupTable.insert(std::make_pair("", kEmptyListId));
- }
- return instance;
-}
-
-} // namespace minikin
diff --git a/libs/minikin/FontLanguageListCache.h b/libs/minikin/FontLanguageListCache.h
deleted file mode 100644
index 9bf156f..0000000
--- a/libs/minikin/FontLanguageListCache.h
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef MINIKIN_FONT_LANGUAGE_LIST_CACHE_H
-#define MINIKIN_FONT_LANGUAGE_LIST_CACHE_H
-
-#include <unordered_map>
-
-#include <minikin/FontFamily.h>
-#include "FontLanguage.h"
-
-namespace minikin {
-
-class FontLanguageListCache {
-public:
- // A special ID for the empty language list.
- // This value must be 0 since the empty language list is inserted into mLanguageLists by
- // default.
- const static uint32_t kEmptyListId = 0;
-
- // Returns language list ID for the given string representation of FontLanguages.
- // Caller should acquire a lock before calling the method.
- static uint32_t getId(const std::string& languages);
-
- // Caller should acquire a lock before calling the method.
- static const FontLanguages& getById(uint32_t id);
-
-private:
- FontLanguageListCache() {} // Singleton
- ~FontLanguageListCache() {}
-
- // Caller should acquire a lock before calling the method.
- static FontLanguageListCache* getInstance();
-
- std::vector<FontLanguages> mLanguageLists;
-
- // A map from string representation of the font language list to the ID.
- std::unordered_map<std::string, uint32_t> mLanguageListLookupTable;
-};
-
-} // namespace minikin
-
-#endif // MINIKIN_FONT_LANGUAGE_LIST_CACHE_H
diff --git a/libs/minikin/FontUtils.cpp b/libs/minikin/FontUtils.cpp
index 60b3163..560b309 100644
--- a/libs/minikin/FontUtils.cpp
+++ b/libs/minikin/FontUtils.cpp
@@ -14,11 +14,10 @@
* limitations under the License.
*/
-#include <stdlib.h>
-#include <stdint.h>
-
#include "FontUtils.h"
+#include <cstdint>
+
#include <log/log.h>
namespace minikin {
@@ -29,7 +28,7 @@
static uint32_t readU32(const uint8_t* data, size_t offset) {
return ((uint32_t)data[offset]) << 24 | ((uint32_t)data[offset + 1]) << 16 |
- ((uint32_t)data[offset + 2]) << 8 | ((uint32_t)data[offset + 3]);
+ ((uint32_t)data[offset + 2]) << 8 | ((uint32_t)data[offset + 3]);
}
bool analyzeStyle(const uint8_t* os2_data, size_t os2_size, int* weight, bool* italic) {
@@ -40,7 +39,7 @@
return false;
}
uint16_t weightClass = readU16(os2_data, kUsWeightClassOffset);
- *weight = weightClass / 100;
+ *weight = weightClass;
uint16_t fsSelection = readU16(os2_data, kFsSelectionOffset);
*italic = (fsSelection & kItalicFlag) != 0;
return true;
diff --git a/libs/minikin/FontUtils.h b/libs/minikin/FontUtils.h
index ecb9cde..15ee4d0 100644
--- a/libs/minikin/FontUtils.h
+++ b/libs/minikin/FontUtils.h
@@ -17,6 +17,7 @@
#ifndef MINIKIN_FONT_UTILS_H
#define MINIKIN_FONT_UTILS_H
+#include <cstdint>
#include <unordered_set>
namespace minikin {
diff --git a/libs/minikin/GraphemeBreak.cpp b/libs/minikin/GraphemeBreak.cpp
index 87de421..7d06d8f 100644
--- a/libs/minikin/GraphemeBreak.cpp
+++ b/libs/minikin/GraphemeBreak.cpp
@@ -14,31 +14,32 @@
* limitations under the License.
*/
-#include <stdint.h>
+#include "minikin/GraphemeBreak.h"
+
#include <algorithm>
+#include <cstdint>
+
#include <unicode/uchar.h>
#include <unicode/utf16.h>
-#include <minikin/GraphemeBreak.h>
-#include <minikin/Emoji.h>
-#include "MinikinInternal.h"
+#include "minikin/Emoji.h"
namespace minikin {
int32_t tailoredGraphemeClusterBreak(uint32_t c) {
// Characters defined as Control that we want to treat them as Extend.
// These are curated manually.
- if (c == 0x00AD // SHY
- || c == 0x061C // ALM
- || c == 0x180E // MONGOLIAN VOWEL SEPARATOR
- || c == 0x200B // ZWSP
- || c == 0x200E // LRM
- || c == 0x200F // RLM
- || (0x202A <= c && c <= 0x202E) // LRE, RLE, PDF, LRO, RLO
- || ((c | 0xF) == 0x206F) // WJ, invisible math operators, LRI, RLI, FSI, PDI,
- // and the deprecated invisible format controls
- || c == 0xFEFF // BOM
- || ((c | 0x7F) == 0xE007F)) // recently undeprecated tag characters in Plane 14
+ if (c == 0x00AD // SHY
+ || c == 0x061C // ALM
+ || c == 0x180E // MONGOLIAN VOWEL SEPARATOR
+ || c == 0x200B // ZWSP
+ || c == 0x200E // LRM
+ || c == 0x200F // RLM
+ || (0x202A <= c && c <= 0x202E) // LRE, RLE, PDF, LRO, RLO
+ || ((c | 0xF) == 0x206F) // WJ, invisible math operators, LRI, RLI, FSI, PDI,
+ // and the deprecated invisible format controls
+ || c == 0xFEFF // BOM
+ || ((c | 0x7F) == 0xE007F)) // recently undeprecated tag characters in Plane 14
return U_GCB_EXTEND;
// THAI CHARACTER SARA AM is treated as a normal letter by most other implementations: they
// allow a grapheme break before it.
@@ -51,13 +52,14 @@
// Returns true for all characters whose IndicSyllabicCategory is Pure_Killer.
// From http://www.unicode.org/Public/9.0.0/ucd/IndicSyllabicCategory.txt
bool isPureKiller(uint32_t c) {
- return (c == 0x0E3A || c == 0x0E4E || c == 0x0F84 || c == 0x103A || c == 0x1714 || c == 0x1734
- || c == 0x17D1 || c == 0x1BAA || c == 0x1BF2 || c == 0x1BF3 || c == 0xA806
- || c == 0xA953 || c == 0xABED || c == 0x11134 || c == 0x112EA || c == 0x1172B);
+ return (c == 0x0E3A || c == 0x0E4E || c == 0x0F84 || c == 0x103A || c == 0x1714 ||
+ c == 0x1734 || c == 0x17D1 || c == 0x1BAA || c == 0x1BF2 || c == 0x1BF3 ||
+ c == 0xA806 || c == 0xA953 || c == 0xABED || c == 0x11134 || c == 0x112EA ||
+ c == 0x1172B);
}
bool GraphemeBreak::isGraphemeBreak(const float* advances, const uint16_t* buf, size_t start,
- size_t count, const size_t offset) {
+ size_t count, const size_t offset) {
// This implementation closely follows Unicode Standard Annex #29 on
// Unicode Text Segmentation (http://www.unicode.org/reports/tr29/),
// implementing a tailored version of extended grapheme clusters.
@@ -194,8 +196,8 @@
// disallow grapheme breaks (if we are here, we don't know about advances, or we already know
// that c2 has no advance).
if (u_getIntPropertyValue(c1, UCHAR_CANONICAL_COMBINING_CLASS) == 9 // virama
- && !isPureKiller(c1)
- && u_getIntPropertyValue(c2, UCHAR_GENERAL_CATEGORY) == U_OTHER_LETTER) {
+ && !isPureKiller(c1) &&
+ u_getIntPropertyValue(c2, UCHAR_GENERAL_CATEGORY) == U_OTHER_LETTER) {
return false;
}
// Rule GB999, Any ÷ Any
@@ -203,33 +205,33 @@
}
size_t GraphemeBreak::getTextRunCursor(const float* advances, const uint16_t* buf, size_t start,
- size_t count, size_t offset, MoveOpt opt) {
+ size_t count, size_t offset, MoveOpt opt) {
switch (opt) {
- case AFTER:
- if (offset < start + count) {
- offset++;
- }
+ case AFTER:
+ if (offset < start + count) {
+ offset++;
+ }
// fall through
- case AT_OR_AFTER:
- while (!isGraphemeBreak(advances, buf, start, count, offset)) {
- offset++;
- }
- break;
- case BEFORE:
- if (offset > start) {
- offset--;
- }
+ case AT_OR_AFTER:
+ while (!isGraphemeBreak(advances, buf, start, count, offset)) {
+ offset++;
+ }
+ break;
+ case BEFORE:
+ if (offset > start) {
+ offset--;
+ }
// fall through
- case AT_OR_BEFORE:
- while (!isGraphemeBreak(advances, buf, start, count, offset)) {
- offset--;
- }
- break;
- case AT:
- if (!isGraphemeBreak(advances, buf, start, count, offset)) {
- offset = (size_t)-1;
- }
- break;
+ case AT_OR_BEFORE:
+ while (!isGraphemeBreak(advances, buf, start, count, offset)) {
+ offset--;
+ }
+ break;
+ case AT:
+ if (!isGraphemeBreak(advances, buf, start, count, offset)) {
+ offset = (size_t)-1;
+ }
+ break;
}
return offset;
}
diff --git a/libs/minikin/GreedyLineBreaker.cpp b/libs/minikin/GreedyLineBreaker.cpp
new file mode 100644
index 0000000..661a42e
--- /dev/null
+++ b/libs/minikin/GreedyLineBreaker.cpp
@@ -0,0 +1,395 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "GreedyLineBreak"
+
+#include "minikin/Characters.h"
+#include "minikin/LineBreaker.h"
+#include "minikin/MeasuredText.h"
+#include "minikin/Range.h"
+#include "minikin/U16StringPiece.h"
+
+#include "HyphenatorMap.h"
+#include "LineBreakerUtil.h"
+#include "Locale.h"
+#include "LocaleListCache.h"
+#include "WordBreaker.h"
+
+namespace minikin {
+
+namespace {
+
+constexpr uint32_t NOWHERE = 0xFFFFFFFF;
+
+class GreedyLineBreaker {
+public:
+ // User of this class must keep measured, lineWidthLimit, tabStop alive until the instance is
+ // destructed.
+ GreedyLineBreaker(const U16StringPiece& textBuf, const MeasuredText& measured,
+ const LineWidth& lineWidthLimits, const TabStops& tabStops,
+ bool enableHyphenation)
+ : mLineWidthLimit(lineWidthLimits.getAt(0)),
+ mTextBuf(textBuf),
+ mMeasuredText(measured),
+ mLineWidthLimits(lineWidthLimits),
+ mTabStops(tabStops),
+ mEnableHyphenation(enableHyphenation) {}
+
+ void process();
+
+ LineBreakResult getResult() const;
+
+private:
+ struct BreakPoint {
+ BreakPoint(uint32_t offset, float lineWidth, StartHyphenEdit startHyphen,
+ EndHyphenEdit endHyphen)
+ : offset(offset),
+ lineWidth(lineWidth),
+ hyphenEdit(packHyphenEdit(startHyphen, endHyphen)) {}
+
+ uint32_t offset;
+ float lineWidth;
+ HyphenEdit hyphenEdit;
+ };
+
+ inline uint32_t getPrevLineBreakOffset() {
+ return mBreakPoints.empty() ? 0 : mBreakPoints.back().offset;
+ }
+
+ // Registers the break point and prepares for next line computation.
+ void breakLineAt(uint32_t offset, float lineWidth, float remainingNextLineWidth,
+ float remainingNextSumOfCharWidths, EndHyphenEdit thisLineEndHyphen,
+ StartHyphenEdit nextLineStartHyphen);
+
+ // Update current line width.
+ void updateLineWidth(uint16_t c, float width);
+
+ // Break line if current line exceeds the line limit.
+ void processLineBreak(uint32_t offset, WordBreaker* breaker);
+
+ // Try to break with previous word boundary.
+ // Returns false if unable to break by word boundary.
+ bool tryLineBreakWithWordBreak();
+
+ // Try to break with hyphenation.
+ // Returns false if unable to hyphenate.
+ //
+ // This method keeps hyphenation until the line width after line break meets the line width
+ // limit.
+ bool tryLineBreakWithHyphenation(const Range& range, WordBreaker* breaker);
+
+ // Do line break with each characters.
+ //
+ // This method only breaks at the first offset which has the longest width for the line width
+ // limit. This method don't keep line breaking even if the rest of the word exceeds the line
+ // width limit.
+ void doLineBreakWithGraphemeBounds(const Range& range);
+
+ // Info about the line currently processing.
+ uint32_t mLineNum = 0;
+ double mLineWidth = 0;
+ double mSumOfCharWidths = 0;
+ double mLineWidthLimit;
+ StartHyphenEdit mStartHyphenEdit = StartHyphenEdit::NO_EDIT;
+
+ // Previous word break point info.
+ uint32_t mPrevWordBoundsOffset = NOWHERE;
+ double mLineWidthAtPrevWordBoundary = 0;
+ double mSumOfCharWidthsAtPrevWordBoundary = 0;
+ bool mIsPrevWordBreakIsInEmailOrUrl = false;
+
+ // The hyphenator currently used.
+ const Hyphenator* mHyphenator = nullptr;
+
+ // Input parameters.
+ const U16StringPiece& mTextBuf;
+ const MeasuredText& mMeasuredText;
+ const LineWidth& mLineWidthLimits;
+ const TabStops& mTabStops;
+ bool mEnableHyphenation;
+
+ // The result of line breaking.
+ std::vector<BreakPoint> mBreakPoints;
+
+ MINIKIN_PREVENT_COPY_ASSIGN_AND_MOVE(GreedyLineBreaker);
+};
+
+void GreedyLineBreaker::breakLineAt(uint32_t offset, float lineWidth, float remainingNextLineWidth,
+ float remainingNextSumOfCharWidths,
+ EndHyphenEdit thisLineEndHyphen,
+ StartHyphenEdit nextLineStartHyphen) {
+ // First, push the break to result.
+ mBreakPoints.emplace_back(offset, lineWidth, mStartHyphenEdit, thisLineEndHyphen);
+
+ // Update the current line info.
+ mLineWidthLimit = mLineWidthLimits.getAt(++mLineNum);
+ mLineWidth = remainingNextLineWidth;
+ mSumOfCharWidths = remainingNextSumOfCharWidths;
+ mStartHyphenEdit = nextLineStartHyphen;
+ mPrevWordBoundsOffset = NOWHERE;
+ mLineWidthAtPrevWordBoundary = 0;
+ mSumOfCharWidthsAtPrevWordBoundary = 0;
+ mIsPrevWordBreakIsInEmailOrUrl = false;
+}
+
+bool GreedyLineBreaker::tryLineBreakWithWordBreak() {
+ if (mPrevWordBoundsOffset == NOWHERE) {
+ return false; // No word break point before..
+ }
+
+ breakLineAt(mPrevWordBoundsOffset, // break offset
+ mLineWidthAtPrevWordBoundary, // line width
+ mLineWidth - mSumOfCharWidthsAtPrevWordBoundary, // remaining next line width
+ // remaining next sum of char widths.
+ mSumOfCharWidths - mSumOfCharWidthsAtPrevWordBoundary, EndHyphenEdit::NO_EDIT,
+ StartHyphenEdit::NO_EDIT); // No hyphen modification.
+ return true;
+}
+
+bool GreedyLineBreaker::tryLineBreakWithHyphenation(const Range& range, WordBreaker* breaker) {
+ if (!mEnableHyphenation || mHyphenator == nullptr) {
+ return false;
+ }
+
+ Run* targetRun = nullptr;
+ for (const auto& run : mMeasuredText.runs) {
+ if (run->getRange().contains(range)) {
+ targetRun = run.get();
+ }
+ }
+
+ if (targetRun == nullptr) {
+ return false; // The target range may lay on multiple run. Unable to hyphenate.
+ }
+
+ const Range targetRange = breaker->wordRange();
+ if (!range.contains(targetRange)) {
+ return false;
+ }
+
+ const std::vector<HyphenationType> hyphenResult =
+ hyphenate(mTextBuf.substr(targetRange), *mHyphenator);
+ Range contextRange = range;
+ uint32_t prevOffset = NOWHERE;
+ float prevWidth = 0;
+
+ // Look up the hyphenation point from the begining.
+ for (uint32_t i = targetRange.getStart(); i < targetRange.getEnd(); ++i) {
+ const HyphenationType hyph = hyphenResult[targetRange.toRangeOffset(i)];
+ if (hyph == HyphenationType::DONT_BREAK) {
+ continue; // Not a hyphenation point.
+ }
+
+ const float width = targetRun->measureHyphenPiece(mTextBuf, contextRange.split(i).first,
+ mStartHyphenEdit, editForThisLine(hyph),
+ nullptr /* advances */, nullptr);
+
+ if (width <= mLineWidthLimit) {
+ // There are still space, remember current offset and look up next hyphenation point.
+ prevOffset = i;
+ prevWidth = width;
+ continue;
+ }
+
+ if (prevOffset == NOWHERE) {
+ // Even with hyphenation, the piece is too long for line. Give up and break in
+ // character bounds.
+ doLineBreakWithGraphemeBounds(contextRange);
+ } else {
+ // Previous offset is the longest hyphenation piece. Break with it.
+ const HyphenationType hyph = hyphenResult[targetRange.toRangeOffset(prevOffset)];
+ const StartHyphenEdit nextLineStartHyphenEdit = editForNextLine(hyph);
+ const float remainingCharWidths = targetRun->measureHyphenPiece(
+ mTextBuf, contextRange.split(prevOffset).second, nextLineStartHyphenEdit,
+ EndHyphenEdit::NO_EDIT, nullptr /* advances */, nullptr);
+ breakLineAt(prevOffset, prevWidth,
+ remainingCharWidths - (mSumOfCharWidths - mLineWidth), remainingCharWidths,
+ editForThisLine(hyph), nextLineStartHyphenEdit);
+ }
+
+ if (mLineWidth <= mLineWidthLimit) {
+ // The remaining hyphenation piece is less than line width. No more hyphenation is
+ // needed. Go to next word.
+ return true;
+ }
+
+ // Even after line break, the remaining hyphenation piece is still too long for the limit.
+ // Keep hyphenating for the rest.
+ i = getPrevLineBreakOffset();
+ contextRange.setStart(i); // Update the hyphenation start point.
+ prevOffset = NOWHERE;
+ }
+
+ // Do the same line break at the end of text.
+ // TODO: Remove code duplication. This is the same as in the for loop but extracting function
+ // may not clear.
+ if (prevOffset == NOWHERE) {
+ doLineBreakWithGraphemeBounds(contextRange);
+ } else {
+ const HyphenationType hyph = hyphenResult[targetRange.toRangeOffset(prevOffset)];
+ const StartHyphenEdit nextLineStartHyphenEdit = editForNextLine(hyph);
+ const float remainingCharWidths = targetRun->measureHyphenPiece(
+ mTextBuf, contextRange.split(prevOffset).second, nextLineStartHyphenEdit,
+ EndHyphenEdit::NO_EDIT, nullptr /* advances */, nullptr);
+
+ breakLineAt(prevOffset, prevWidth, remainingCharWidths - (mSumOfCharWidths - mLineWidth),
+ remainingCharWidths, editForThisLine(hyph), nextLineStartHyphenEdit);
+ }
+
+ return true;
+}
+
+// TODO: Respect trailing line end spaces.
+void GreedyLineBreaker::doLineBreakWithGraphemeBounds(const Range& range) {
+ double width = mMeasuredText.widths[range.getStart()];
+
+ // Starting from + 1 since at least one character needs to be assigned to a line.
+ for (uint32_t i = range.getStart() + 1; i < range.getEnd(); ++i) {
+ const float w = mMeasuredText.widths[i];
+ if (w == 0) {
+ continue; // w == 0 means here is not a grapheme bounds. Don't break here.
+ }
+ if (width + w > mLineWidthLimit) {
+ // Okay, here is the longest position.
+ breakLineAt(i, width, mLineWidth - width, mSumOfCharWidths - width,
+ EndHyphenEdit::NO_EDIT, StartHyphenEdit::NO_EDIT);
+
+ // This method only breaks at the first longest offset, since we may want to hyphenate
+ // the rest of the word.
+ return;
+ } else {
+ width += w;
+ }
+ }
+
+ // Reaching here means even one character (or cluster) doesn't fit the line.
+ // Give up and break at the end of this range.
+ breakLineAt(range.getEnd(), mLineWidth, 0, 0, EndHyphenEdit::NO_EDIT, StartHyphenEdit::NO_EDIT);
+}
+
+void GreedyLineBreaker::updateLineWidth(uint16_t c, float width) {
+ if (c == CHAR_TAB) {
+ mSumOfCharWidths = mTabStops.nextTab(mSumOfCharWidths);
+ mLineWidth = mSumOfCharWidths;
+ } else {
+ mSumOfCharWidths += width;
+ if (!isLineEndSpace(c)) {
+ mLineWidth = mSumOfCharWidths;
+ }
+ }
+}
+
+void GreedyLineBreaker::processLineBreak(uint32_t offset, WordBreaker* breaker) {
+ while (mLineWidth > mLineWidthLimit) {
+ const Range lineRange(getPrevLineBreakOffset(), offset); // The range we need to address.
+ if (tryLineBreakWithWordBreak()) {
+ continue; // The word in the new line may still be too long for the line limit.
+ } else if (tryLineBreakWithHyphenation(lineRange, breaker)) {
+ continue; // TODO: we may be able to return here.
+ } else {
+ doLineBreakWithGraphemeBounds(lineRange);
+ }
+ }
+
+ // There is still spaces, remember current word break point as a candidate and wait next word.
+ const bool isInEmailOrUrl = breaker->breakBadness() != 0;
+ if (mPrevWordBoundsOffset == NOWHERE || mIsPrevWordBreakIsInEmailOrUrl | !isInEmailOrUrl) {
+ mPrevWordBoundsOffset = offset;
+ mLineWidthAtPrevWordBoundary = mLineWidth;
+ mSumOfCharWidthsAtPrevWordBoundary = mSumOfCharWidths;
+ mIsPrevWordBreakIsInEmailOrUrl = isInEmailOrUrl;
+ }
+}
+
+void GreedyLineBreaker::process() {
+ WordBreaker wordBreaker;
+ wordBreaker.setText(mTextBuf.data(), mTextBuf.size());
+
+ // Following two will be initialized after the first iteration.
+ uint32_t localeListId = LocaleListCache::kInvalidListId;
+ uint32_t nextWordBoundaryOffset = 0;
+ for (const auto& run : mMeasuredText.runs) {
+ const Range range = run->getRange();
+
+ // Update locale if necessary.
+ uint32_t newLocaleListId = run->getLocaleListId();
+ if (localeListId != newLocaleListId) {
+ Locale locale = getEffectiveLocale(newLocaleListId);
+ nextWordBoundaryOffset = wordBreaker.followingWithLocale(locale, range.getStart());
+ mHyphenator = HyphenatorMap::lookup(locale);
+ localeListId = newLocaleListId;
+ }
+
+ for (uint32_t i = range.getStart(); i < range.getEnd(); ++i) {
+ updateLineWidth(mTextBuf[i], mMeasuredText.widths[i]);
+
+ if ((i + 1) == nextWordBoundaryOffset) {
+ // Only process line break at word boundary.
+ processLineBreak(i + 1, &wordBreaker);
+ nextWordBoundaryOffset = wordBreaker.next();
+ }
+ }
+ }
+
+ if (getPrevLineBreakOffset() != mTextBuf.size() && mPrevWordBoundsOffset != NOWHERE) {
+ // The remaining words in the last line.
+ breakLineAt(mPrevWordBoundsOffset, mLineWidth, 0, 0, EndHyphenEdit::NO_EDIT,
+ StartHyphenEdit::NO_EDIT);
+ }
+}
+
+LineBreakResult GreedyLineBreaker::getResult() const {
+ constexpr int TAB_BIT = 1 << 29; // Must be the same in StaticLayout.java
+
+ LineBreakResult out;
+ uint32_t prevBreakOffset = 0;
+ for (const auto& breakPoint : mBreakPoints) {
+ // TODO: compute these during line breaking if these takes longer time.
+ bool hasTabChar = false;
+ MinikinExtent extent = {0.0, 0.0, 0.0};
+ for (uint32_t i = prevBreakOffset; i < breakPoint.offset; ++i) {
+ const uint16_t c = mTextBuf[i];
+ hasTabChar |= c == CHAR_TAB;
+ if (!isLineSpaceExcludeChar(c)) {
+ extent.extendBy(mMeasuredText.extents[i]);
+ }
+ }
+
+ out.breakPoints.push_back(breakPoint.offset);
+ out.widths.push_back(breakPoint.lineWidth);
+ out.ascents.push_back(extent.ascent);
+ out.descents.push_back(extent.descent);
+ out.flags.push_back((hasTabChar ? TAB_BIT : 0) | static_cast<int>(breakPoint.hyphenEdit));
+
+ prevBreakOffset = breakPoint.offset;
+ }
+ return out;
+}
+
+} // namespace
+
+LineBreakResult breakLineGreedy(const U16StringPiece& textBuf, const MeasuredText& measured,
+ const LineWidth& lineWidthLimits, const TabStops& tabStops,
+ bool enableHyphenation) {
+ if (textBuf.size() == 0) {
+ return LineBreakResult();
+ }
+ GreedyLineBreaker lineBreaker(textBuf, measured, lineWidthLimits, tabStops, enableHyphenation);
+ lineBreaker.process();
+ return lineBreaker.getResult();
+}
+
+} // namespace minikin
diff --git a/libs/minikin/GreedyLineBreaker.h b/libs/minikin/GreedyLineBreaker.h
new file mode 100644
index 0000000..ead93e4
--- /dev/null
+++ b/libs/minikin/GreedyLineBreaker.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MINIKIN_GREEDY_LINE_BREAKER_H
+#define MINIKIN_GREEDY_LINE_BREAKER_H
+
+#include "minikin/LineBreaker.h"
+#include "minikin/MeasuredText.h"
+#include "minikin/U16StringPiece.h"
+
+#include "WordBreaker.h"
+
+namespace minikin {
+
+LineBreakResult breakLineGreedy(const U16StringPiece& textBuf, const MeasuredText& measured,
+ const LineWidth& lineWidthLimits, const TabStops& tabStops,
+ bool enableHyphenation);
+
+} // namespace minikin
+
+#endif // MINIKIN_GREEDY_LINE_BREAKER_H
diff --git a/libs/minikin/HbFontCache.cpp b/libs/minikin/HbFontCache.cpp
deleted file mode 100644
index af3d783..0000000
--- a/libs/minikin/HbFontCache.cpp
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "Minikin"
-
-#include "HbFontCache.h"
-
-#include <log/log.h>
-#include <utils/LruCache.h>
-
-#include <hb.h>
-#include <hb-ot.h>
-
-#include <minikin/MinikinFont.h>
-#include "MinikinInternal.h"
-
-namespace minikin {
-
-class HbFontCache : private android::OnEntryRemoved<int32_t, hb_font_t*> {
-public:
- HbFontCache() : mCache(kMaxEntries) {
- mCache.setOnEntryRemovedListener(this);
- }
-
- // callback for OnEntryRemoved
- void operator()(int32_t& /* key */, hb_font_t*& value) {
- hb_font_destroy(value);
- }
-
- hb_font_t* get(int32_t fontId) {
- return mCache.get(fontId);
- }
-
- void put(int32_t fontId, hb_font_t* font) {
- mCache.put(fontId, font);
- }
-
- void clear() {
- mCache.clear();
- }
-
- void remove(int32_t fontId) {
- mCache.remove(fontId);
- }
-
-private:
- static const size_t kMaxEntries = 100;
-
- android::LruCache<int32_t, hb_font_t*> mCache;
-};
-
-HbFontCache* getFontCacheLocked() {
- assertMinikinLocked();
- static HbFontCache* cache = nullptr;
- if (cache == nullptr) {
- cache = new HbFontCache();
- }
- return cache;
-}
-
-void purgeHbFontCacheLocked() {
- assertMinikinLocked();
- getFontCacheLocked()->clear();
-}
-
-void purgeHbFontLocked(const MinikinFont* minikinFont) {
- assertMinikinLocked();
- const int32_t fontId = minikinFont->GetUniqueId();
- getFontCacheLocked()->remove(fontId);
-}
-
-// Returns a new reference to a hb_font_t object, caller is
-// responsible for calling hb_font_destroy() on it.
-hb_font_t* getHbFontLocked(const MinikinFont* minikinFont) {
- assertMinikinLocked();
- // TODO: get rid of nullFaceFont
- static hb_font_t* nullFaceFont = nullptr;
- if (minikinFont == nullptr) {
- if (nullFaceFont == nullptr) {
- nullFaceFont = hb_font_create(nullptr);
- }
- return hb_font_reference(nullFaceFont);
- }
-
- HbFontCache* fontCache = getFontCacheLocked();
- const int32_t fontId = minikinFont->GetUniqueId();
- hb_font_t* font = fontCache->get(fontId);
- if (font != nullptr) {
- return hb_font_reference(font);
- }
-
- hb_face_t* face;
- const void* buf = minikinFont->GetFontData();
- size_t size = minikinFont->GetFontSize();
- hb_blob_t* blob = hb_blob_create(reinterpret_cast<const char*>(buf), size,
- HB_MEMORY_MODE_READONLY, nullptr, nullptr);
- face = hb_face_create(blob, minikinFont->GetFontIndex());
- hb_blob_destroy(blob);
-
- hb_font_t* parent_font = hb_font_create(face);
- hb_ot_font_set_funcs(parent_font);
-
- unsigned int upem = hb_face_get_upem(face);
- hb_font_set_scale(parent_font, upem, upem);
-
- font = hb_font_create_sub_font(parent_font);
- std::vector<hb_variation_t> variations;
- for (const FontVariation& variation : minikinFont->GetAxes()) {
- variations.push_back({variation.axisTag, variation.value});
- }
- hb_font_set_variations(font, variations.data(), variations.size());
- hb_font_destroy(parent_font);
- hb_face_destroy(face);
- fontCache->put(fontId, font);
- return hb_font_reference(font);
-}
-
-} // namespace minikin
diff --git a/libs/minikin/HbFontCache.h b/libs/minikin/HbFontCache.h
deleted file mode 100644
index 59969e2..0000000
--- a/libs/minikin/HbFontCache.h
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef MINIKIN_HBFONT_CACHE_H
-#define MINIKIN_HBFONT_CACHE_H
-
-struct hb_font_t;
-
-namespace minikin {
-class MinikinFont;
-
-void purgeHbFontCacheLocked();
-void purgeHbFontLocked(const MinikinFont* minikinFont);
-hb_font_t* getHbFontLocked(const MinikinFont* minikinFont);
-
-} // namespace minikin
-#endif // MINIKIN_HBFONT_CACHE_H
diff --git a/libs/minikin/Hyphenator.cpp b/libs/minikin/Hyphenator.cpp
index 0605b27..c755a87 100644
--- a/libs/minikin/Hyphenator.cpp
+++ b/libs/minikin/Hyphenator.cpp
@@ -14,30 +14,20 @@
* limitations under the License.
*/
-#include <vector>
-#include <memory>
+#include "minikin/Hyphenator.h"
+
#include <algorithm>
+#include <memory>
#include <string>
+#include <vector>
+
#include <unicode/uchar.h>
#include <unicode/uscript.h>
-// HACK: for reading pattern file
-#include <fcntl.h>
-
-#define LOG_TAG "Minikin"
-#include "utils/Log.h"
-
-#include "minikin/Hyphenator.h"
-
-using std::vector;
+#include "minikin/Characters.h"
namespace minikin {
-static const uint16_t CHAR_HYPHEN_MINUS = 0x002D;
-static const uint16_t CHAR_SOFT_HYPHEN = 0x00AD;
-static const uint16_t CHAR_MIDDLE_DOT = 0x00B7;
-static const uint16_t CHAR_HYPHEN = 0x2010;
-
// The following are structs that correspond to tables inside the hyb file format
struct AlphabetTable0 {
@@ -50,7 +40,7 @@
struct AlphabetTable1 {
uint32_t version;
uint32_t n_entries;
- uint32_t data[1]; // actually flexible array, size is known at runtime
+ uint32_t data[1]; // actually flexible array, size is known at runtime
static uint32_t codepoint(uint32_t entry) { return entry >> 11; }
static uint32_t value(uint32_t entry) { return entry & 0x7ff; }
@@ -100,33 +90,42 @@
const AlphabetTable1* alphabetTable1() const {
return reinterpret_cast<const AlphabetTable1*>(bytes() + alphabet_offset);
}
- const Trie* trieTable() const {
- return reinterpret_cast<const Trie*>(bytes() + trie_offset);
- }
+ const Trie* trieTable() const { return reinterpret_cast<const Trie*>(bytes() + trie_offset); }
const Pattern* patternTable() const {
return reinterpret_cast<const Pattern*>(bytes() + pattern_offset);
}
};
-Hyphenator* Hyphenator::loadBinary(const uint8_t* patternData, size_t minPrefix, size_t minSuffix) {
- Hyphenator* result = new Hyphenator;
- result->patternData = patternData;
- result->minPrefix = minPrefix;
- result->minSuffix = minSuffix;
- return result;
+// static
+Hyphenator* Hyphenator::loadBinary(const uint8_t* patternData, size_t minPrefix, size_t minSuffix,
+ const std::string& locale) {
+ HyphenationLocale hyphenLocale = HyphenationLocale::OTHER;
+ if (locale == "pl") {
+ hyphenLocale = HyphenationLocale::POLISH;
+ } else if (locale == "ca") {
+ hyphenLocale = HyphenationLocale::CATALAN;
+ } else if (locale == "sl") {
+ hyphenLocale = HyphenationLocale::SLOVENIAN;
+ }
+ return new Hyphenator(patternData, minPrefix, minSuffix, hyphenLocale);
}
-void Hyphenator::hyphenate(vector<HyphenationType>* result, const uint16_t* word, size_t len,
- const icu::Locale& locale) {
- result->clear();
- result->resize(len);
+Hyphenator::Hyphenator(const uint8_t* patternData, size_t minPrefix, size_t minSuffix,
+ HyphenationLocale hyphenLocale)
+ : mPatternData(patternData),
+ mMinPrefix(minPrefix),
+ mMinSuffix(minSuffix),
+ mHyphenationLocale(hyphenLocale) {}
+
+void Hyphenator::hyphenate(const U16StringPiece& word, HyphenationType* out) const {
+ const size_t len = word.size();
const size_t paddedLen = len + 2; // start and stop code each count for 1
- if (patternData != nullptr &&
- len >= minPrefix + minSuffix && paddedLen <= MAX_HYPHENATED_SIZE) {
+ if (mPatternData != nullptr && len >= mMinPrefix + mMinSuffix &&
+ paddedLen <= MAX_HYPHENATED_SIZE) {
uint16_t alpha_codes[MAX_HYPHENATED_SIZE];
- const HyphenationType hyphenValue = alphabetLookup(alpha_codes, word, len);
+ const HyphenationType hyphenValue = alphabetLookup(alpha_codes, word);
if (hyphenValue != HyphenationType::DONT_BREAK) {
- hyphenateFromCodes(result->data(), alpha_codes, paddedLen, hyphenValue);
+ hyphenateFromCodes(alpha_codes, paddedLen, hyphenValue, out);
return;
}
// TODO: try NFC normalization
@@ -135,7 +134,7 @@
// Note that we will always get here if the word contains a hyphen or a soft hyphen, because the
// alphabet is not expected to contain a hyphen or a soft hyphen character, so alphabetLookup
// would return DONT_BREAK.
- hyphenateWithNoPatterns(result->data(), word, len, locale);
+ hyphenateWithNoPatterns(word, out);
}
// This function determines whether a character is like U+2010 HYPHEN in
@@ -145,76 +144,46 @@
// inspecting all the characters that have the Unicode line breaking
// property of BA or HY and seeing which ones are hyphens.
bool Hyphenator::isLineBreakingHyphen(uint32_t c) {
- return (c == 0x002D || // HYPHEN-MINUS
- c == 0x058A || // ARMENIAN HYPHEN
- c == 0x05BE || // HEBREW PUNCTUATION MAQAF
- c == 0x1400 || // CANADIAN SYLLABICS HYPHEN
- c == 0x2010 || // HYPHEN
- c == 0x2013 || // EN DASH
- c == 0x2027 || // HYPHENATION POINT
- c == 0x2E17 || // DOUBLE OBLIQUE HYPHEN
- c == 0x2E40); // DOUBLE HYPHEN
+ return (c == 0x002D || // HYPHEN-MINUS
+ c == 0x058A || // ARMENIAN HYPHEN
+ c == 0x05BE || // HEBREW PUNCTUATION MAQAF
+ c == 0x1400 || // CANADIAN SYLLABICS HYPHEN
+ c == 0x2010 || // HYPHEN
+ c == 0x2013 || // EN DASH
+ c == 0x2027 || // HYPHENATION POINT
+ c == 0x2E17 || // DOUBLE OBLIQUE HYPHEN
+ c == 0x2E40); // DOUBLE HYPHEN
}
-const static uint32_t HYPHEN_STR[] = {0x2010, 0};
-const static uint32_t ARMENIAN_HYPHEN_STR[] = {0x058A, 0};
-const static uint32_t MAQAF_STR[] = {0x05BE, 0};
-const static uint32_t UCAS_HYPHEN_STR[] = {0x1400, 0};
-const static uint32_t ZWJ_STR[] = {0x200D, 0};
-const static uint32_t ZWJ_AND_HYPHEN_STR[] = {0x200D, 0x2010, 0};
-
-const uint32_t* HyphenEdit::getHyphenString(uint32_t hyph) {
- switch (hyph) {
- case INSERT_HYPHEN_AT_END:
- case REPLACE_WITH_HYPHEN_AT_END:
- case INSERT_HYPHEN_AT_START:
- return HYPHEN_STR;
- case INSERT_ARMENIAN_HYPHEN_AT_END:
- return ARMENIAN_HYPHEN_STR;
- case INSERT_MAQAF_AT_END:
- return MAQAF_STR;
- case INSERT_UCAS_HYPHEN_AT_END:
- return UCAS_HYPHEN_STR;
- case INSERT_ZWJ_AND_HYPHEN_AT_END:
- return ZWJ_AND_HYPHEN_STR;
- case INSERT_ZWJ_AT_START:
- return ZWJ_STR;
- default:
- return nullptr;
- }
-}
-
-uint32_t HyphenEdit::editForThisLine(HyphenationType type) {
+EndHyphenEdit editForThisLine(HyphenationType type) {
switch (type) {
- case HyphenationType::DONT_BREAK:
- return NO_EDIT;
case HyphenationType::BREAK_AND_INSERT_HYPHEN:
- return INSERT_HYPHEN_AT_END;
+ return EndHyphenEdit::INSERT_HYPHEN;
case HyphenationType::BREAK_AND_INSERT_ARMENIAN_HYPHEN:
- return INSERT_ARMENIAN_HYPHEN_AT_END;
+ return EndHyphenEdit::INSERT_ARMENIAN_HYPHEN;
case HyphenationType::BREAK_AND_INSERT_MAQAF:
- return INSERT_MAQAF_AT_END;
+ return EndHyphenEdit::INSERT_MAQAF;
case HyphenationType::BREAK_AND_INSERT_UCAS_HYPHEN:
- return INSERT_UCAS_HYPHEN_AT_END;
+ return EndHyphenEdit::INSERT_UCAS_HYPHEN;
case HyphenationType::BREAK_AND_REPLACE_WITH_HYPHEN:
- return REPLACE_WITH_HYPHEN_AT_END;
+ return EndHyphenEdit::REPLACE_WITH_HYPHEN;
case HyphenationType::BREAK_AND_INSERT_HYPHEN_AND_ZWJ:
- return INSERT_ZWJ_AND_HYPHEN_AT_END;
+ return EndHyphenEdit::INSERT_ZWJ_AND_HYPHEN;
+ case HyphenationType::DONT_BREAK: // Hyphen edit for non breaking case doesn't make sense.
default:
- return BREAK_AT_END;
+ return EndHyphenEdit::NO_EDIT;
}
}
-uint32_t HyphenEdit::editForNextLine(HyphenationType type) {
+StartHyphenEdit editForNextLine(HyphenationType type) {
switch (type) {
- case HyphenationType::DONT_BREAK:
- return NO_EDIT;
case HyphenationType::BREAK_AND_INSERT_HYPHEN_AT_NEXT_LINE:
- return INSERT_HYPHEN_AT_START;
+ return StartHyphenEdit::INSERT_HYPHEN;
case HyphenationType::BREAK_AND_INSERT_HYPHEN_AND_ZWJ:
- return INSERT_ZWJ_AT_START;
+ return StartHyphenEdit::INSERT_ZWJ;
+ case HyphenationType::DONT_BREAK: // Hyphen edit for non breaking case doesn't make sense.
default:
- return BREAK_AT_START;
+ return StartHyphenEdit::NO_EDIT;
}
}
@@ -234,10 +203,8 @@
// for now to be safe. BREAK_AND_INSERT_MAQAF is already implemented, so if we want to switch
// to maqaf for Hebrew, we can simply add a condition here.
const UScriptCode script = getScript(codePoint);
- if (script == USCRIPT_KANNADA
- || script == USCRIPT_MALAYALAM
- || script == USCRIPT_TAMIL
- || script == USCRIPT_TELUGU) {
+ if (script == USCRIPT_KANNADA || script == USCRIPT_MALAYALAM || script == USCRIPT_TAMIL ||
+ script == USCRIPT_TELUGU) {
// Grantha is not included, since we don't support non-BMP hyphenation yet.
return HyphenationType::BREAK_AND_DONT_INSERT_HYPHEN;
} else if (script == USCRIPT_ARMENIAN) {
@@ -255,17 +222,17 @@
// Assumption for caller: location must be >= 2 and word[location] == CHAR_SOFT_HYPHEN.
// This function decides if the letters before and after the hyphen should appear as joining.
-static inline HyphenationType getHyphTypeForArabic(const uint16_t* word, size_t len,
- size_t location) {
+static inline HyphenationType getHyphTypeForArabic(const U16StringPiece& word, size_t location) {
ssize_t i = location;
int32_t type = U_JT_NON_JOINING;
- while (static_cast<size_t>(i) < len && (type = getJoiningType(word[i])) == U_JT_TRANSPARENT) {
+ while (static_cast<size_t>(i) < word.size() &&
+ (type = getJoiningType(word[i])) == U_JT_TRANSPARENT) {
i++;
}
if (type == U_JT_DUAL_JOINING || type == U_JT_RIGHT_JOINING || type == U_JT_JOIN_CAUSING) {
// The next character is of the type that may join the last character. See if the last
// character is also of the right type.
- i = location - 2; // Skip the soft hyphen
+ i = location - 2; // Skip the soft hyphen
type = U_JT_NON_JOINING;
while (i >= 0 && (type = getJoiningType(word[i])) == U_JT_TRANSPARENT) {
i--;
@@ -280,22 +247,22 @@
// Use various recommendations of UAX #14 Unicode Line Breaking Algorithm for hyphenating words
// that didn't match patterns, especially words that contain hyphens or soft hyphens (See sections
// 5.3, Use of Hyphen, and 5.4, Use of Soft Hyphen).
-void Hyphenator::hyphenateWithNoPatterns(HyphenationType* result, const uint16_t* word, size_t len,
- const icu::Locale& locale) {
- result[0] = HyphenationType::DONT_BREAK;
- for (size_t i = 1; i < len; i++) {
+void Hyphenator::hyphenateWithNoPatterns(const U16StringPiece& word, HyphenationType* out) const {
+ out[0] = HyphenationType::DONT_BREAK;
+ for (size_t i = 1; i < word.size(); i++) {
const uint16_t prevChar = word[i - 1];
if (i > 1 && isLineBreakingHyphen(prevChar)) {
// Break after hyphens, but only if they don't start the word.
- if ((prevChar == CHAR_HYPHEN_MINUS || prevChar == CHAR_HYPHEN)
- && strcmp(locale.getLanguage(), "pl") == 0
- && getScript(word[i]) == USCRIPT_LATIN ) {
- // In Polish, hyphens get repeated at the next line. To be safe,
+ if ((prevChar == CHAR_HYPHEN_MINUS || prevChar == CHAR_HYPHEN) &&
+ (mHyphenationLocale == HyphenationLocale::POLISH ||
+ mHyphenationLocale == HyphenationLocale::SLOVENIAN) &&
+ getScript(word[i]) == USCRIPT_LATIN) {
+ // In Polish and Slovenian, hyphens get repeated at the next line. To be safe,
// we will do this only if the next character is Latin.
- result[i] = HyphenationType::BREAK_AND_INSERT_HYPHEN_AT_NEXT_LINE;
+ out[i] = HyphenationType::BREAK_AND_INSERT_HYPHEN_AT_NEXT_LINE;
} else {
- result[i] = HyphenationType::BREAK_AND_DONT_INSERT_HYPHEN;
+ out[i] = HyphenationType::BREAK_AND_DONT_INSERT_HYPHEN;
}
} else if (i > 1 && prevChar == CHAR_SOFT_HYPHEN) {
// Break after soft hyphens, but only if they don't start the word (a soft hyphen
@@ -304,26 +271,25 @@
if (getScript(word[i]) == USCRIPT_ARABIC) {
// For Arabic, we need to look and see if the characters around the soft hyphen
// actually join. If they don't, we'll just insert a normal hyphen.
- result[i] = getHyphTypeForArabic(word, len, i);
+ out[i] = getHyphTypeForArabic(word, i);
} else {
- result[i] = hyphenationTypeBasedOnScript(word[i]);
+ out[i] = hyphenationTypeBasedOnScript(word[i]);
}
- } else if (prevChar == CHAR_MIDDLE_DOT
- && minPrefix < i && i <= len - minSuffix
- && ((word[i - 2] == 'l' && word[i] == 'l')
- || (word[i - 2] == 'L' && word[i] == 'L'))
- && strcmp(locale.getLanguage(), "ca") == 0) {
+ } else if (prevChar == CHAR_MIDDLE_DOT && mMinPrefix < i && i <= word.size() - mMinSuffix &&
+ ((word[i - 2] == 'l' && word[i] == 'l') ||
+ (word[i - 2] == 'L' && word[i] == 'L')) &&
+ mHyphenationLocale == HyphenationLocale::CATALAN) {
// In Catalan, "l·l" should break as "l-" on the first line
// and "l" on the next line.
- result[i] = HyphenationType::BREAK_AND_REPLACE_WITH_HYPHEN;
+ out[i] = HyphenationType::BREAK_AND_REPLACE_WITH_HYPHEN;
} else {
- result[i] = HyphenationType::DONT_BREAK;
+ out[i] = HyphenationType::DONT_BREAK;
}
- }
+ }
}
-HyphenationType Hyphenator::alphabetLookup(uint16_t* alpha_codes, const uint16_t* word,
- size_t len) {
+HyphenationType Hyphenator::alphabetLookup(uint16_t* alpha_codes,
+ const U16StringPiece& word) const {
const Header* header = getHeader();
HyphenationType result = HyphenationType::BREAK_AND_INSERT_HYPHEN;
// TODO: check header magic
@@ -333,7 +299,7 @@
uint32_t min_codepoint = alphabet->min_codepoint;
uint32_t max_codepoint = alphabet->max_codepoint;
alpha_codes[0] = 0; // word start
- for (size_t i = 0; i < len; i++) {
+ for (size_t i = 0; i < word.size(); i++) {
uint16_t c = word[i];
if (c < min_codepoint || c >= max_codepoint) {
return HyphenationType::DONT_BREAK;
@@ -347,7 +313,7 @@
}
alpha_codes[i + 1] = code;
}
- alpha_codes[len + 1] = 0; // word termination
+ alpha_codes[word.size() + 1] = 0; // word termination
return result;
} else if (alphabetVersion == 1) {
const AlphabetTable1* alphabet = header->alphabetTable1();
@@ -355,7 +321,7 @@
const uint32_t* begin = alphabet->data;
const uint32_t* end = begin + n_entries;
alpha_codes[0] = 0;
- for (size_t i = 0; i < len; i++) {
+ for (size_t i = 0; i < word.size(); i++) {
uint16_t c = word[i];
auto p = std::lower_bound(begin, end, c << 11);
if (p == end) {
@@ -370,7 +336,7 @@
}
alpha_codes[i + 1] = AlphabetTable1::value(entry);
}
- alpha_codes[len + 1] = 0;
+ alpha_codes[word.size() + 1] = 0;
return result;
}
return HyphenationType::DONT_BREAK;
@@ -381,11 +347,11 @@
* has been done by now, and all characters have been found in the alphabet.
* Note: len here is the padded length including 0 codes at start and end.
**/
-void Hyphenator::hyphenateFromCodes(HyphenationType* result, const uint16_t* codes, size_t len,
- HyphenationType hyphenValue) {
+void Hyphenator::hyphenateFromCodes(const uint16_t* codes, size_t len, HyphenationType hyphenValue,
+ HyphenationType* out) const {
static_assert(sizeof(HyphenationType) == sizeof(uint8_t), "HyphnationType must be uint8_t.");
// Reuse the result array as a buffer for calculating intermediate hyphenation numbers.
- uint8_t* buffer = reinterpret_cast<uint8_t*>(result);
+ uint8_t* buffer = reinterpret_cast<uint8_t*>(out);
const Header* header = getHeader();
const Trie* trie = header->trieTable();
@@ -394,7 +360,7 @@
uint32_t link_shift = trie->link_shift;
uint32_t link_mask = trie->link_mask;
uint32_t pattern_shift = trie->pattern_shift;
- size_t maxOffset = len - minSuffix - 1;
+ size_t maxOffset = len - mMinSuffix - 1;
for (size_t i = 0; i < len - 1; i++) {
uint32_t node = 0; // index into Trie table
for (size_t j = i; j < len; j++) {
@@ -416,7 +382,7 @@
const uint8_t* pat_buf = pattern->buf(pat_entry);
int offset = j + 1 - (pat_len + pat_shift);
// offset is the index within buffer that lines up with the start of pat_buf
- int start = std::max((int)minPrefix - offset, 0);
+ int start = std::max((int)mMinPrefix - offset, 0);
int end = std::min(pat_len, (int)maxOffset - offset);
for (int k = start; k < end; k++) {
buffer[offset + k] = std::max(buffer[offset + k], pat_buf[k]);
@@ -425,10 +391,10 @@
}
}
// Since the above calculation does not modify values outside
- // [minPrefix, len - minSuffix], they are left as 0 = DONT_BREAK.
- for (size_t i = minPrefix; i < maxOffset; i++) {
+ // [mMinPrefix, len - mMinSuffix], they are left as 0 = DONT_BREAK.
+ for (size_t i = mMinPrefix; i < maxOffset; i++) {
// Hyphenation opportunities happen when the hyphenation numbers are odd.
- result[i] = (buffer[i] & 1u) ? hyphenValue : HyphenationType::DONT_BREAK;
+ out[i] = (buffer[i] & 1u) ? hyphenValue : HyphenationType::DONT_BREAK;
}
}
diff --git a/libs/minikin/HyphenatorMap.cpp b/libs/minikin/HyphenatorMap.cpp
new file mode 100644
index 0000000..40c785a
--- /dev/null
+++ b/libs/minikin/HyphenatorMap.cpp
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "HyphenatorMap.h"
+
+#include "LocaleListCache.h"
+#include "MinikinInternal.h"
+
+namespace minikin {
+
+namespace {
+constexpr SubtagBits LANGUAGE = SubtagBits::LANGUAGE;
+constexpr SubtagBits SCRIPT = SubtagBits::SCRIPT;
+constexpr SubtagBits REGION = SubtagBits::REGION;
+constexpr SubtagBits VARIANT = SubtagBits::VARIANT;
+
+constexpr int DEFAULT_MIN_PREFIX = 2;
+constexpr int DEFAULT_MAX_PREFIX = 2;
+} // namespace
+
+// Following two function's implementations are here since Hyphenator.cpp can't include
+// HyphenatorMap.h due to harfbuzz dependency on the host binary.
+void addHyphenator(const std::string& localeStr, const Hyphenator* hyphenator) {
+ HyphenatorMap::add(localeStr, hyphenator);
+}
+
+void addHyphenatorAlias(const std::string& fromLocaleStr, const std::string& toLocaleStr) {
+ HyphenatorMap::addAlias(fromLocaleStr, toLocaleStr);
+}
+
+HyphenatorMap::HyphenatorMap()
+ : mSoftHyphenOnlyHyphenator(
+ Hyphenator::loadBinary(nullptr, DEFAULT_MIN_PREFIX, DEFAULT_MAX_PREFIX, "")) {}
+
+void HyphenatorMap::addInternal(const std::string& localeStr, const Hyphenator* hyphenator) {
+ const Locale locale(localeStr);
+ std::lock_guard<std::mutex> lock(mMutex);
+ // Overwrite even if there is already a fallback entry.
+ mMap[locale.getIdentifier()] = hyphenator;
+}
+
+void HyphenatorMap::clearInternal() {
+ std::lock_guard<std::mutex> lock(mMutex);
+ mMap.clear();
+}
+void HyphenatorMap::addAliasInternal(const std::string& fromLocaleStr,
+ const std::string& toLocaleStr) {
+ const Locale fromLocale(fromLocaleStr);
+ const Locale toLocale(toLocaleStr);
+ std::lock_guard<std::mutex> lock(mMutex);
+ auto it = mMap.find(toLocale.getIdentifier());
+ if (it == mMap.end()) {
+ ALOGE("Target Hyphenator not found.");
+ return;
+ }
+ // Overwrite even if there is already a fallback entry.
+ mMap[fromLocale.getIdentifier()] = it->second;
+}
+
+const Hyphenator* HyphenatorMap::lookupInternal(const Locale& locale) {
+ const uint64_t id = locale.getIdentifier();
+ std::lock_guard<std::mutex> lock(mMutex);
+ const Hyphenator* result = lookupByIdentifier(id);
+ if (result != nullptr) {
+ return result; // Found with exact match.
+ }
+
+ // First, try with dropping emoji extensions.
+ result = lookupBySubtag(locale, LANGUAGE | REGION | SCRIPT | VARIANT);
+ if (result != nullptr) {
+ goto insert_result_and_return;
+ }
+ // If not found, try with dropping script.
+ result = lookupBySubtag(locale, LANGUAGE | REGION | VARIANT);
+ if (result != nullptr) {
+ goto insert_result_and_return;
+ }
+ // If not found, try with dropping script and region code.
+ result = lookupBySubtag(locale, LANGUAGE | VARIANT);
+ if (result != nullptr) {
+ goto insert_result_and_return;
+ }
+ // If not found, try only with language code.
+ result = lookupBySubtag(locale, LANGUAGE);
+ if (result != nullptr) {
+ goto insert_result_and_return;
+ }
+ // Still not found, try only with script.
+ result = lookupBySubtag(locale, SCRIPT);
+ if (result != nullptr) {
+ goto insert_result_and_return;
+ }
+
+ // If not found, use soft hyphen only hyphenator.
+ result = mSoftHyphenOnlyHyphenator;
+
+insert_result_and_return:
+ mMap.insert(std::make_pair(id, result));
+ return result;
+}
+
+const Hyphenator* HyphenatorMap::lookupByIdentifier(uint64_t id) const {
+ auto it = mMap.find(id);
+ return it == mMap.end() ? nullptr : it->second;
+}
+
+const Hyphenator* HyphenatorMap::lookupBySubtag(const Locale& locale, SubtagBits bits) const {
+ const Locale partialLocale = locale.getPartialLocale(bits);
+ if (!partialLocale.isSupported() || partialLocale == locale) {
+ return nullptr; // Skip the partial locale result in the same locale or not supported.
+ }
+ return lookupByIdentifier(partialLocale.getIdentifier());
+}
+
+} // namespace minikin
diff --git a/libs/minikin/HyphenatorMap.h b/libs/minikin/HyphenatorMap.h
new file mode 100644
index 0000000..26b5e61
--- /dev/null
+++ b/libs/minikin/HyphenatorMap.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MINIKIN_HYPHENATOR_MAP_H
+#define MINIKIN_HYPHENATOR_MAP_H
+
+#include <map>
+#include <mutex>
+
+#include "minikin/Hyphenator.h"
+#include "minikin/Macros.h"
+
+#include "Locale.h"
+
+namespace minikin {
+
+class HyphenatorMap {
+public:
+ // This map doesn't take ownership of the hyphenator but we don't need to care about the
+ // ownership. In Android, the Hyphenator is allocated in Zygote and never gets released.
+ static void add(const std::string& localeStr, const Hyphenator* hyphenator) {
+ getInstance().addInternal(localeStr, hyphenator);
+ }
+
+ static void addAlias(const std::string& fromLocaleStr, const std::string& toLocaleStr) {
+ getInstance().addAliasInternal(fromLocaleStr, toLocaleStr);
+ }
+
+ // Remove all hyphenators from the map. This is test only method.
+ static void clear() { getInstance().clearInternal(); }
+
+ // The returned pointer is never a dangling pointer. If nothing found for a given locale,
+ // returns a hyphenator which only processes soft hyphens.
+ //
+ // The Hyphenator lookup works with the following rules:
+ // 1. Search for the Hyphenator with the given locale.
+ // 2. If not found, try again with language + script + region + variant.
+ // 3. If not found, try again with language + region + variant.
+ // 4. If not found, try again with language + variant.
+ // 5. If not found, try again with language.
+ // 6. If not found, try again with script.
+ static const Hyphenator* lookup(const Locale& locale) {
+ return getInstance().lookupInternal(locale);
+ }
+
+protected:
+ // The following five methods are protected for testing purposes.
+ HyphenatorMap(); // Use getInstance() instead.
+ void addInternal(const std::string& localeStr, const Hyphenator* hyphenator);
+ void addAliasInternal(const std::string& fromLocaleStr, const std::string& toLocaleStr);
+ const Hyphenator* lookupInternal(const Locale& locale);
+
+private:
+ static HyphenatorMap& getInstance() { // Singleton.
+ static HyphenatorMap map;
+ return map;
+ }
+
+ void clearInternal();
+
+ const Hyphenator* lookupByIdentifier(uint64_t id) const EXCLUSIVE_LOCKS_REQUIRED(mMutex);
+ const Hyphenator* lookupBySubtag(const Locale& locale, SubtagBits bits) const
+ EXCLUSIVE_LOCKS_REQUIRED(mMutex);
+
+ const Hyphenator* mSoftHyphenOnlyHyphenator;
+ std::map<uint64_t, const Hyphenator*> mMap GUARDED_BY(mMutex);
+
+ std::mutex mMutex;
+};
+
+} // namespace minikin
+
+#endif // MINIKIN_HYPHENATOR_MAP_H
diff --git a/libs/minikin/Layout.cpp b/libs/minikin/Layout.cpp
index cbfb430..1b05274 100644
--- a/libs/minikin/Layout.cpp
+++ b/libs/minikin/Layout.cpp
@@ -16,206 +16,109 @@
#define LOG_TAG "Minikin"
-#include <algorithm>
-#include <fstream>
-#include <iostream> // for debugging
-#include <math.h>
-#include <string>
-#include <unicode/ubidi.h>
-#include <unicode/utf16.h>
-#include <vector>
+#include "minikin/Layout.h"
-#include <log/log.h>
-#include <utils/JenkinsHash.h>
-#include <utils/LruCache.h>
-#include <utils/Singleton.h>
-#include <utils/String16.h>
+#include <cmath>
+#include <iostream>
+#include <mutex>
+#include <string>
+#include <vector>
#include <hb-icu.h>
#include <hb-ot.h>
+#include <log/log.h>
+#include <unicode/ubidi.h>
+#include <unicode/utf16.h>
+#include <utils/JenkinsHash.h>
+#include <utils/LruCache.h>
-#include "FontLanguage.h"
-#include "FontLanguageListCache.h"
-#include "HbFontCache.h"
+#include "minikin/Emoji.h"
+#include "minikin/HbUtils.h"
+#include "minikin/LayoutCache.h"
+#include "minikin/LayoutPieces.h"
+#include "minikin/Macros.h"
+
+#include "BidiUtils.h"
#include "LayoutUtils.h"
+#include "LocaleListCache.h"
#include "MinikinInternal.h"
-#include <minikin/Emoji.h>
-#include <minikin/Layout.h>
-
-using std::string;
-using std::vector;
namespace minikin {
-const int kDirection_Mask = 0x1;
+namespace {
-struct LayoutContext {
- MinikinPaint paint;
- FontStyle style;
- std::vector<hb_font_t*> hbFonts; // parallel to mFaces
-
- void clearHbFonts() {
- for (size_t i = 0; i < hbFonts.size(); i++) {
- hb_font_set_funcs(hbFonts[i], nullptr, nullptr, nullptr);
- hb_font_destroy(hbFonts[i]);
- }
- hbFonts.clear();
- }
+struct SkiaArguments {
+ const MinikinFont* font;
+ const MinikinPaint* paint;
+ FontFakery fakery;
};
-// Layout cache datatypes
-
-class LayoutCacheKey {
-public:
- LayoutCacheKey(const std::shared_ptr<FontCollection>& collection, const MinikinPaint& paint,
- FontStyle style, const uint16_t* chars, size_t start, size_t count, size_t nchars,
- bool dir)
- : mChars(chars), mNchars(nchars),
- mStart(start), mCount(count), mId(collection->getId()), mStyle(style),
- mSize(paint.size), mScaleX(paint.scaleX), mSkewX(paint.skewX),
- mLetterSpacing(paint.letterSpacing),
- mPaintFlags(paint.paintFlags), mHyphenEdit(paint.hyphenEdit), mIsRtl(dir),
- mHash(computeHash()) {
- }
- bool operator==(const LayoutCacheKey &other) const;
-
- android::hash_t hash() const {
- return mHash;
- }
-
- void copyText() {
- uint16_t* charsCopy = new uint16_t[mNchars];
- memcpy(charsCopy, mChars, mNchars * sizeof(uint16_t));
- mChars = charsCopy;
- }
- void freeText() {
- delete[] mChars;
- mChars = NULL;
- }
-
- void doLayout(Layout* layout, LayoutContext* ctx,
- const std::shared_ptr<FontCollection>& collection) const {
- layout->mAdvances.resize(mCount, 0);
- ctx->clearHbFonts();
- layout->doLayoutRun(mChars, mStart, mCount, mNchars, mIsRtl, ctx, collection);
- }
-
-private:
- const uint16_t* mChars;
- size_t mNchars;
- size_t mStart;
- size_t mCount;
- uint32_t mId; // for the font collection
- FontStyle mStyle;
- float mSize;
- float mScaleX;
- float mSkewX;
- float mLetterSpacing;
- int32_t mPaintFlags;
- HyphenEdit mHyphenEdit;
- bool mIsRtl;
- // Note: any fields added to MinikinPaint must also be reflected here.
- // TODO: language matching (possibly integrate into style)
- android::hash_t mHash;
-
- android::hash_t computeHash() const;
-};
-
-class LayoutCache : private android::OnEntryRemoved<LayoutCacheKey, Layout*> {
-public:
- LayoutCache() : mCache(kMaxEntries) {
- mCache.setOnEntryRemovedListener(this);
- }
-
- void clear() {
- mCache.clear();
- }
-
- Layout* get(LayoutCacheKey& key, LayoutContext* ctx,
- const std::shared_ptr<FontCollection>& collection) {
- Layout* layout = mCache.get(key);
- if (layout == NULL) {
- key.copyText();
- layout = new Layout();
- key.doLayout(layout, ctx, collection);
- mCache.put(key, layout);
- }
- return layout;
- }
-
-private:
- // callback for OnEntryRemoved
- void operator()(LayoutCacheKey& key, Layout*& value) {
- key.freeText();
- delete value;
- }
-
- android::LruCache<LayoutCacheKey, Layout*> mCache;
-
- //static const size_t kMaxEntries = LruCache<LayoutCacheKey, Layout*>::kUnlimitedCapacity;
-
- // TODO: eviction based on memory footprint; for now, we just use a constant
- // number of strings
- static const size_t kMaxEntries = 5000;
-};
-
-static unsigned int disabledDecomposeCompatibility(hb_unicode_funcs_t*, hb_codepoint_t,
- hb_codepoint_t*, void*) {
- return 0;
+static hb_position_t harfbuzzGetGlyphHorizontalAdvance(hb_font_t* /* hbFont */, void* fontData,
+ hb_codepoint_t glyph, void* /* userData */) {
+ SkiaArguments* args = reinterpret_cast<SkiaArguments*>(fontData);
+ float advance = args->font->GetHorizontalAdvance(glyph, *args->paint, args->fakery);
+ return 256 * advance + 0.5;
}
-class LayoutEngine : public ::android::Singleton<LayoutEngine> {
-public:
- LayoutEngine() {
+static hb_bool_t harfbuzzGetGlyphHorizontalOrigin(hb_font_t* /* hbFont */, void* /* fontData */,
+ hb_codepoint_t /* glyph */,
+ hb_position_t* /* x */, hb_position_t* /* y */,
+ void* /* userData */) {
+ // Just return true, following the way that Harfbuzz-FreeType implementation does.
+ return true;
+}
+
+// TODO: Initialize in Zygote if it helps.
+hb_unicode_funcs_t* getUnicodeFunctions() {
+ static hb_unicode_funcs_t* unicodeFunctions = nullptr;
+ static std::once_flag once;
+ std::call_once(once, [&]() {
unicodeFunctions = hb_unicode_funcs_create(hb_icu_get_unicode_funcs());
/* Disable the function used for compatibility decomposition */
hb_unicode_funcs_set_decompose_compatibility_func(
- unicodeFunctions, disabledDecomposeCompatibility, NULL, NULL);
- hbBuffer = hb_buffer_create();
- hb_buffer_set_unicode_funcs(hbBuffer, unicodeFunctions);
- }
-
- hb_buffer_t* hbBuffer;
- hb_unicode_funcs_t* unicodeFunctions;
- LayoutCache layoutCache;
-};
-
-bool LayoutCacheKey::operator==(const LayoutCacheKey& other) const {
- return mId == other.mId
- && mStart == other.mStart
- && mCount == other.mCount
- && mStyle == other.mStyle
- && mSize == other.mSize
- && mScaleX == other.mScaleX
- && mSkewX == other.mSkewX
- && mLetterSpacing == other.mLetterSpacing
- && mPaintFlags == other.mPaintFlags
- && mHyphenEdit == other.mHyphenEdit
- && mIsRtl == other.mIsRtl
- && mNchars == other.mNchars
- && !memcmp(mChars, other.mChars, mNchars * sizeof(uint16_t));
+ unicodeFunctions,
+ [](hb_unicode_funcs_t*, hb_codepoint_t, hb_codepoint_t*, void*) -> unsigned int {
+ return 0;
+ },
+ nullptr, nullptr);
+ });
+ return unicodeFunctions;
}
-android::hash_t LayoutCacheKey::computeHash() const {
- uint32_t hash = android::JenkinsHashMix(0, mId);
- hash = android::JenkinsHashMix(hash, mStart);
- hash = android::JenkinsHashMix(hash, mCount);
- hash = android::JenkinsHashMix(hash, hash_type(mStyle));
- hash = android::JenkinsHashMix(hash, hash_type(mSize));
- hash = android::JenkinsHashMix(hash, hash_type(mScaleX));
- hash = android::JenkinsHashMix(hash, hash_type(mSkewX));
- hash = android::JenkinsHashMix(hash, hash_type(mLetterSpacing));
- hash = android::JenkinsHashMix(hash, hash_type(mPaintFlags));
- hash = android::JenkinsHashMix(hash, hash_type(mHyphenEdit.getHyphen()));
- hash = android::JenkinsHashMix(hash, hash_type(mIsRtl));
- hash = android::JenkinsHashMixShorts(hash, mChars, mNchars);
- return android::JenkinsHashWhiten(hash);
+// TODO: Initialize in Zygote if it helps.
+hb_font_funcs_t* getFontFuncs() {
+ static hb_font_funcs_t* fontFuncs = nullptr;
+ static std::once_flag once;
+ std::call_once(once, [&]() {
+ fontFuncs = hb_font_funcs_create();
+ // Override the h_advance function since we can't use HarfBuzz's implemenation. It may
+ // return the wrong value if the font uses hinting aggressively.
+ hb_font_funcs_set_glyph_h_advance_func(fontFuncs, harfbuzzGetGlyphHorizontalAdvance, 0, 0);
+ hb_font_funcs_set_glyph_h_origin_func(fontFuncs, harfbuzzGetGlyphHorizontalOrigin, 0, 0);
+ hb_font_funcs_make_immutable(fontFuncs);
+ });
+ return fontFuncs;
}
-android::hash_t hash_type(const LayoutCacheKey& key) {
- return key.hash();
+// TODO: Initialize in Zygote if it helps.
+hb_font_funcs_t* getFontFuncsForEmoji() {
+ static hb_font_funcs_t* fontFuncs = nullptr;
+ static std::once_flag once;
+ std::call_once(once, [&]() {
+ fontFuncs = hb_font_funcs_create();
+ // Don't override the h_advance function since we use HarfBuzz's implementation for emoji
+ // for performance reasons.
+ // Note that it is technically possible for a TrueType font to have outline and embedded
+ // bitmap at the same time. We ignore modified advances of hinted outline glyphs in that
+ // case.
+ hb_font_funcs_set_glyph_h_origin_func(fontFuncs, harfbuzzGetGlyphHorizontalOrigin, 0, 0);
+ hb_font_funcs_make_immutable(fontFuncs);
+ });
+ return fontFuncs;
}
+} // namespace
+
void MinikinRect::join(const MinikinRect& r) {
if (isEmpty()) {
set(r);
@@ -232,64 +135,21 @@
mFaces.clear();
mBounds.setEmpty();
mAdvances.clear();
+ mExtents.clear();
mAdvance = 0;
}
-static hb_position_t harfbuzzGetGlyphHorizontalAdvance(hb_font_t* /* hbFont */, void* fontData,
- hb_codepoint_t glyph, void* /* userData */) {
- MinikinPaint* paint = reinterpret_cast<MinikinPaint*>(fontData);
- float advance = paint->font->GetHorizontalAdvance(glyph, *paint);
- return 256 * advance + 0.5;
+static bool isColorBitmapFont(const HbFontUniquePtr& font) {
+ HbBlob cbdt(font, HB_TAG('C', 'B', 'D', 'T'));
+ return cbdt;
}
-static hb_bool_t harfbuzzGetGlyphHorizontalOrigin(hb_font_t* /* hbFont */, void* /* fontData */,
- hb_codepoint_t /* glyph */, hb_position_t* /* x */, hb_position_t* /* y */,
- void* /* userData */) {
- // Just return true, following the way that Harfbuzz-FreeType
- // implementation does.
- return true;
+static float HBFixedToFloat(hb_position_t v) {
+ return scalbnf(v, -8);
}
-hb_font_funcs_t* getHbFontFuncs(bool forColorBitmapFont) {
- assertMinikinLocked();
-
- static hb_font_funcs_t* hbFuncs = nullptr;
- static hb_font_funcs_t* hbFuncsForColorBitmap = nullptr;
-
- hb_font_funcs_t** funcs = forColorBitmapFont ? &hbFuncs : &hbFuncsForColorBitmap;
- if (*funcs == nullptr) {
- *funcs = hb_font_funcs_create();
- if (forColorBitmapFont) {
- // Don't override the h_advance function since we use HarfBuzz's implementation for
- // emoji for performance reasons.
- // Note that it is technically possible for a TrueType font to have outline and embedded
- // bitmap at the same time. We ignore modified advances of hinted outline glyphs in that
- // case.
- } else {
- // Override the h_advance function since we can't use HarfBuzz's implemenation. It may
- // return the wrong value if the font uses hinting aggressively.
- hb_font_funcs_set_glyph_h_advance_func(*funcs, harfbuzzGetGlyphHorizontalAdvance, 0, 0);
- }
- hb_font_funcs_set_glyph_h_origin_func(*funcs, harfbuzzGetGlyphHorizontalOrigin, 0, 0);
- hb_font_funcs_make_immutable(*funcs);
- }
- return *funcs;
-}
-
-static bool isColorBitmapFont(hb_font_t* font) {
- hb_face_t* face = hb_font_get_face(font);
- HbBlob cbdt(hb_face_reference_table(face, HB_TAG('C', 'B', 'D', 'T')));
- return cbdt.size() > 0;
-}
-
-static float HBFixedToFloat(hb_position_t v)
-{
- return scalbnf (v, -8);
-}
-
-static hb_position_t HBFloatToFixed(float v)
-{
- return scalbnf (v, +8);
+static hb_position_t HBFloatToFixed(float v) {
+ return scalbnf(v, +8);
}
void Layout::dump() const {
@@ -299,39 +159,27 @@
}
}
-int Layout::findFace(const FakedFont& face, LayoutContext* ctx) {
- unsigned int ix;
- for (ix = 0; ix < mFaces.size(); ix++) {
+uint8_t Layout::findOrPushBackFace(const FakedFont& face) {
+ MINIKIN_ASSERT(mFaces.size() < MAX_FAMILY_COUNT, "mFaces must not exceeds %d",
+ MAX_FAMILY_COUNT);
+ uint8_t ix = 0;
+ for (; ix < mFaces.size(); ix++) {
if (mFaces[ix].font == face.font) {
return ix;
}
}
+ ix = mFaces.size();
mFaces.push_back(face);
- // Note: ctx == NULL means we're copying from the cache, no need to create
- // corresponding hb_font object.
- if (ctx != NULL) {
- hb_font_t* font = getHbFontLocked(face.font);
- hb_font_set_funcs(font, getHbFontFuncs(isColorBitmapFont(font)), &ctx->paint, 0);
- ctx->hbFonts.push_back(font);
- }
return ix;
}
-static hb_script_t codePointToScript(hb_codepoint_t codepoint) {
- static hb_unicode_funcs_t* u = 0;
- if (!u) {
- u = LayoutEngine::getInstance().unicodeFunctions;
- }
- return hb_unicode_script(u, codepoint);
-}
-
static hb_codepoint_t decodeUtf16(const uint16_t* chars, size_t len, ssize_t* iter) {
UChar32 result;
- U16_NEXT(chars, *iter, (ssize_t) len, result);
- if (U_IS_SURROGATE(result)) { // isolated surrogate
- result = 0xFFFDu; // U+FFFD REPLACEMENT CHARACTER
+ U16_NEXT(chars, *iter, (ssize_t)len, result);
+ if (U_IS_SURROGATE(result)) { // isolated surrogate
+ result = 0xFFFDu; // U+FFFD REPLACEMENT CHARACTER
}
- return (hb_codepoint_t) result;
+ return (hb_codepoint_t)result;
}
static hb_script_t getScriptRun(const uint16_t* chars, size_t len, ssize_t* iter) {
@@ -339,19 +187,16 @@
return HB_SCRIPT_UNKNOWN;
}
uint32_t cp = decodeUtf16(chars, len, iter);
- hb_script_t current_script = codePointToScript(cp);
+ hb_script_t current_script = hb_unicode_script(getUnicodeFunctions(), cp);
for (;;) {
- if (size_t(*iter) == len)
- break;
+ if (size_t(*iter) == len) break;
const ssize_t prev_iter = *iter;
cp = decodeUtf16(chars, len, iter);
- const hb_script_t script = codePointToScript(cp);
+ const hb_script_t script = hb_unicode_script(getUnicodeFunctions(), cp);
if (script != current_script) {
- if (current_script == HB_SCRIPT_INHERITED ||
- current_script == HB_SCRIPT_COMMON) {
+ if (current_script == HB_SCRIPT_INHERITED || current_script == HB_SCRIPT_COMMON) {
current_script = script;
- } else if (script == HB_SCRIPT_INHERITED ||
- script == HB_SCRIPT_COMMON) {
+ } else if (script == HB_SCRIPT_INHERITED || script == HB_SCRIPT_COMMON) {
continue;
} else {
*iter = prev_iter;
@@ -371,338 +216,236 @@
* applied. See https://github.com/behdad/harfbuzz/issues/64 for more details.
*/
static bool isScriptOkForLetterspacing(hb_script_t script) {
- return !(
- script == HB_SCRIPT_ARABIC ||
- script == HB_SCRIPT_NKO ||
- script == HB_SCRIPT_PSALTER_PAHLAVI ||
- script == HB_SCRIPT_MANDAIC ||
- script == HB_SCRIPT_MONGOLIAN ||
- script == HB_SCRIPT_PHAGS_PA ||
- script == HB_SCRIPT_DEVANAGARI ||
- script == HB_SCRIPT_BENGALI ||
- script == HB_SCRIPT_GURMUKHI ||
- script == HB_SCRIPT_MODI ||
- script == HB_SCRIPT_SHARADA ||
- script == HB_SCRIPT_SYLOTI_NAGRI ||
- script == HB_SCRIPT_TIRHUTA ||
- script == HB_SCRIPT_OGHAM
- );
+ return !(script == HB_SCRIPT_ARABIC || script == HB_SCRIPT_NKO ||
+ script == HB_SCRIPT_PSALTER_PAHLAVI || script == HB_SCRIPT_MANDAIC ||
+ script == HB_SCRIPT_MONGOLIAN || script == HB_SCRIPT_PHAGS_PA ||
+ script == HB_SCRIPT_DEVANAGARI || script == HB_SCRIPT_BENGALI ||
+ script == HB_SCRIPT_GURMUKHI || script == HB_SCRIPT_MODI ||
+ script == HB_SCRIPT_SHARADA || script == HB_SCRIPT_SYLOTI_NAGRI ||
+ script == HB_SCRIPT_TIRHUTA || script == HB_SCRIPT_OGHAM);
}
-class BidiText {
-public:
- class Iter {
- public:
- struct RunInfo {
- int32_t mRunStart;
- int32_t mRunLength;
- bool mIsRtl;
- };
-
- Iter(UBiDi* bidi, size_t start, size_t end, size_t runIndex, size_t runCount, bool isRtl);
-
- bool operator!= (const Iter& other) const {
- return mIsEnd != other.mIsEnd || mNextRunIndex != other.mNextRunIndex
- || mBidi != other.mBidi;
- }
-
- const RunInfo& operator* () const {
- return mRunInfo;
- }
-
- const Iter& operator++ () {
- updateRunInfo();
- return *this;
- }
-
- private:
- UBiDi* const mBidi;
- bool mIsEnd;
- size_t mNextRunIndex;
- const size_t mRunCount;
- const int32_t mStart;
- const int32_t mEnd;
- RunInfo mRunInfo;
-
- void updateRunInfo();
- };
-
- BidiText(const uint16_t* buf, size_t start, size_t count, size_t bufSize, int bidiFlags);
-
- ~BidiText() {
- if (mBidi) {
- ubidi_close(mBidi);
- }
- }
-
- Iter begin () const {
- return Iter(mBidi, mStart, mEnd, 0, mRunCount, mIsRtl);
- }
-
- Iter end() const {
- return Iter(mBidi, mStart, mEnd, mRunCount, mRunCount, mIsRtl);
- }
-
-private:
- const size_t mStart;
- const size_t mEnd;
- const size_t mBufSize;
- UBiDi* mBidi;
- size_t mRunCount;
- bool mIsRtl;
-
- BidiText(const BidiText&) = delete;
- void operator=(const BidiText&) = delete;
-};
-
-BidiText::Iter::Iter(UBiDi* bidi, size_t start, size_t end, size_t runIndex, size_t runCount,
- bool isRtl)
- : mBidi(bidi), mIsEnd(runIndex == runCount), mNextRunIndex(runIndex), mRunCount(runCount),
- mStart(start), mEnd(end), mRunInfo() {
- if (mRunCount == 1) {
- mRunInfo.mRunStart = start;
- mRunInfo.mRunLength = end - start;
- mRunInfo.mIsRtl = isRtl;
- mNextRunIndex = mRunCount;
- return;
- }
- updateRunInfo();
-}
-
-void BidiText::Iter::updateRunInfo() {
- if (mNextRunIndex == mRunCount) {
- // All runs have been iterated.
- mIsEnd = true;
- return;
- }
- int32_t startRun = -1;
- int32_t lengthRun = -1;
- const UBiDiDirection runDir = ubidi_getVisualRun(mBidi, mNextRunIndex, &startRun, &lengthRun);
- mNextRunIndex++;
- if (startRun == -1 || lengthRun == -1) {
- ALOGE("invalid visual run");
- // skip the invalid run.
- updateRunInfo();
- return;
- }
- const int32_t runEnd = std::min(startRun + lengthRun, mEnd);
- mRunInfo.mRunStart = std::max(startRun, mStart);
- mRunInfo.mRunLength = runEnd - mRunInfo.mRunStart;
- if (mRunInfo.mRunLength <= 0) {
- // skip the empty run.
- updateRunInfo();
- return;
- }
- mRunInfo.mIsRtl = (runDir == UBIDI_RTL);
-}
-
-BidiText::BidiText(const uint16_t* buf, size_t start, size_t count, size_t bufSize, int bidiFlags)
- : mStart(start), mEnd(start + count), mBufSize(bufSize), mBidi(NULL), mRunCount(1),
- mIsRtl((bidiFlags & kDirection_Mask) != 0) {
- if (bidiFlags == kBidi_Force_LTR || bidiFlags == kBidi_Force_RTL) {
- // force single run.
- return;
- }
- mBidi = ubidi_open();
- if (!mBidi) {
- ALOGE("error creating bidi object");
- return;
- }
- UErrorCode status = U_ZERO_ERROR;
- // Set callbacks to override bidi classes of new emoji
- ubidi_setClassCallback(mBidi, emojiBidiOverride, nullptr, nullptr, nullptr, &status);
- if (!U_SUCCESS(status)) {
- ALOGE("error setting bidi callback function, status = %d", status);
- return;
- }
-
- UBiDiLevel bidiReq = bidiFlags;
- if (bidiFlags == kBidi_Default_LTR) {
- bidiReq = UBIDI_DEFAULT_LTR;
- } else if (bidiFlags == kBidi_Default_RTL) {
- bidiReq = UBIDI_DEFAULT_RTL;
- }
- ubidi_setPara(mBidi, reinterpret_cast<const UChar*>(buf), mBufSize, bidiReq, NULL, &status);
- if (!U_SUCCESS(status)) {
- ALOGE("error calling ubidi_setPara, status = %d", status);
- return;
- }
- const int paraDir = ubidi_getParaLevel(mBidi) & kDirection_Mask;
- const ssize_t rc = ubidi_countRuns(mBidi, &status);
- if (!U_SUCCESS(status) || rc < 0) {
- ALOGW("error counting bidi runs, status = %d", status);
- }
- if (!U_SUCCESS(status) || rc <= 0) {
- mIsRtl = (paraDir == kBidi_RTL);
- return;
- }
- if (rc == 1) {
- const UBiDiDirection runDir = ubidi_getVisualRun(mBidi, 0, nullptr, nullptr);
- mIsRtl = (runDir == UBIDI_RTL);
- return;
- }
- mRunCount = rc;
-}
-
-void Layout::doLayout(const uint16_t* buf, size_t start, size_t count, size_t bufSize,
- int bidiFlags, const FontStyle &style, const MinikinPaint &paint,
- const std::shared_ptr<FontCollection>& collection) {
- android::AutoMutex _l(gMinikinLock);
-
- LayoutContext ctx;
- ctx.style = style;
- ctx.paint = paint;
-
+void Layout::doLayout(const U16StringPiece& textBuf, const Range& range, Bidi bidiFlags,
+ const MinikinPaint& paint, StartHyphenEdit startHyphen,
+ EndHyphenEdit endHyphen) {
+ const uint32_t count = range.getLength();
reset();
mAdvances.resize(count, 0);
+ mExtents.resize(count);
- for (const BidiText::Iter::RunInfo& runInfo : BidiText(buf, start, count, bufSize, bidiFlags)) {
- doLayoutRunCached(buf, runInfo.mRunStart, runInfo.mRunLength, bufSize, runInfo.mIsRtl, &ctx,
- start, collection, this, NULL);
+ for (const BidiText::RunInfo& runInfo : BidiText(textBuf, range, bidiFlags)) {
+ doLayoutRunCached(textBuf, runInfo.range, runInfo.isRtl, paint, range.getStart(),
+ startHyphen, endHyphen, nullptr, this, nullptr, nullptr, nullptr,
+ nullptr);
}
- ctx.clearHbFonts();
}
-float Layout::measureText(const uint16_t* buf, size_t start, size_t count, size_t bufSize,
- int bidiFlags, const FontStyle &style, const MinikinPaint &paint,
- const std::shared_ptr<FontCollection>& collection, float* advances) {
- android::AutoMutex _l(gMinikinLock);
+void Layout::doLayoutWithPrecomputedPieces(const U16StringPiece& textBuf, const Range& range,
+ Bidi bidiFlags, const MinikinPaint& paint,
+ StartHyphenEdit startHyphen, EndHyphenEdit endHyphen,
+ const LayoutPieces& lpIn) {
+ const uint32_t count = range.getLength();
+ reset();
+ mAdvances.resize(count, 0);
+ mExtents.resize(count);
- LayoutContext ctx;
- ctx.style = style;
- ctx.paint = paint;
-
- float advance = 0;
- for (const BidiText::Iter::RunInfo& runInfo : BidiText(buf, start, count, bufSize, bidiFlags)) {
- float* advancesForRun = advances ? advances + (runInfo.mRunStart - start) : advances;
- advance += doLayoutRunCached(buf, runInfo.mRunStart, runInfo.mRunLength, bufSize,
- runInfo.mIsRtl, &ctx, 0, collection, NULL, advancesForRun);
+ for (const BidiText::RunInfo& runInfo : BidiText(textBuf, range, bidiFlags)) {
+ doLayoutRunCached(textBuf, runInfo.range, runInfo.isRtl, paint, range.getStart(),
+ startHyphen, endHyphen, &lpIn, this, nullptr, nullptr, nullptr, nullptr);
}
+}
- ctx.clearHbFonts();
+std::pair<float, MinikinRect> Layout::getBoundsWithPrecomputedPieces(const U16StringPiece& textBuf,
+ const Range& range,
+ Bidi bidiFlags,
+ const MinikinPaint& paint,
+ const LayoutPieces& pieces) {
+ MinikinRect rect;
+ float advance = 0;
+ for (const BidiText::RunInfo& runInfo : BidiText(textBuf, range, bidiFlags)) {
+ advance += doLayoutRunCached(textBuf, runInfo.range, runInfo.isRtl, paint, 0,
+ StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, &pieces,
+ nullptr, nullptr, nullptr, &rect, nullptr);
+ }
+ return std::make_pair(advance, rect);
+}
+
+float Layout::measureText(const U16StringPiece& textBuf, const Range& range, Bidi bidiFlags,
+ const MinikinPaint& paint, StartHyphenEdit startHyphen,
+ EndHyphenEdit endHyphen, float* advances, MinikinExtent* extents,
+ LayoutPieces* pieces) {
+ float advance = 0;
+ for (const BidiText::RunInfo& runInfo : BidiText(textBuf, range, bidiFlags)) {
+ const size_t offset = range.toRangeOffset(runInfo.range.getStart());
+ float* advancesForRun = advances ? advances + offset : nullptr;
+ MinikinExtent* extentsForRun = extents ? extents + offset : nullptr;
+ advance += doLayoutRunCached(textBuf, runInfo.range, runInfo.isRtl, paint, 0, startHyphen,
+ endHyphen, nullptr, nullptr, advancesForRun, extentsForRun,
+ nullptr, pieces);
+ }
return advance;
}
-float Layout::doLayoutRunCached(const uint16_t* buf, size_t start, size_t count, size_t bufSize,
- bool isRtl, LayoutContext* ctx, size_t dstStart,
- const std::shared_ptr<FontCollection>& collection, Layout* layout, float* advances) {
- const uint32_t originalHyphen = ctx->paint.hyphenEdit.getHyphen();
+float Layout::doLayoutRunCached(const U16StringPiece& textBuf, const Range& range, bool isRtl,
+ const MinikinPaint& paint, size_t dstStart,
+ StartHyphenEdit startHyphen, EndHyphenEdit endHyphen,
+ const LayoutPieces* lpIn, Layout* layout, float* advances,
+ MinikinExtent* extents, MinikinRect* bounds, LayoutPieces* lpOut) {
+ if (!range.isValid()) {
+ return 0.0f; // ICU failed to retrieve the bidi run?
+ }
+ const uint16_t* buf = textBuf.data();
+ const uint32_t bufSize = textBuf.size();
+ const uint32_t start = range.getStart();
+ const uint32_t end = range.getEnd();
float advance = 0;
if (!isRtl) {
// left to right
- size_t wordstart =
+ uint32_t wordstart =
start == bufSize ? start : getPrevWordBreakForCache(buf, start + 1, bufSize);
- size_t wordend;
- for (size_t iter = start; iter < start + count; iter = wordend) {
+ uint32_t wordend;
+ for (size_t iter = start; iter < end; iter = wordend) {
wordend = getNextWordBreakForCache(buf, iter, bufSize);
- // Only apply hyphen to the first or last word in the string.
- uint32_t hyphen = originalHyphen;
- if (iter != start) { // Not the first word
- hyphen &= ~HyphenEdit::MASK_START_OF_LINE;
- }
- if (wordend < start + count) { // Not the last word
- hyphen &= ~HyphenEdit::MASK_END_OF_LINE;
- }
- ctx->paint.hyphenEdit = hyphen;
- size_t wordcount = std::min(start + count, wordend) - iter;
+ const uint32_t wordcount = std::min(end, wordend) - iter;
+ const uint32_t offset = iter - start;
advance += doLayoutWord(buf + wordstart, iter - wordstart, wordcount,
- wordend - wordstart, isRtl, ctx, iter - dstStart, collection, layout,
- advances ? advances + (iter - start) : advances);
+ wordend - wordstart, isRtl, paint, iter - dstStart,
+ // Only apply hyphen to the first or last word in the string.
+ iter == start ? startHyphen : StartHyphenEdit::NO_EDIT,
+ wordend >= end ? endHyphen : EndHyphenEdit::NO_EDIT, lpIn,
+ layout, advances ? advances + offset : nullptr,
+ extents ? extents + offset : nullptr, bounds, lpOut);
wordstart = wordend;
}
} else {
// right to left
- size_t wordstart;
- size_t end = start + count;
- size_t wordend = end == 0 ? 0 : getNextWordBreakForCache(buf, end - 1, bufSize);
+ uint32_t wordstart;
+ uint32_t wordend = end == 0 ? 0 : getNextWordBreakForCache(buf, end - 1, bufSize);
for (size_t iter = end; iter > start; iter = wordstart) {
wordstart = getPrevWordBreakForCache(buf, iter, bufSize);
- // Only apply hyphen to the first (rightmost) or last (leftmost) word in the string.
- uint32_t hyphen = originalHyphen;
- if (wordstart > start) { // Not the first word
- hyphen &= ~HyphenEdit::MASK_START_OF_LINE;
- }
- if (iter != end) { // Not the last word
- hyphen &= ~HyphenEdit::MASK_END_OF_LINE;
- }
- ctx->paint.hyphenEdit = hyphen;
- size_t bufStart = std::max(start, wordstart);
+ uint32_t bufStart = std::max(start, wordstart);
+ const uint32_t offset = bufStart - start;
advance += doLayoutWord(buf + wordstart, bufStart - wordstart, iter - bufStart,
- wordend - wordstart, isRtl, ctx, bufStart - dstStart, collection, layout,
- advances ? advances + (bufStart - start) : advances);
+ wordend - wordstart, isRtl, paint, bufStart - dstStart,
+ // Only apply hyphen to the first (rightmost) or last (leftmost)
+ // word in the string.
+ wordstart <= start ? startHyphen : StartHyphenEdit::NO_EDIT,
+ iter == end ? endHyphen : EndHyphenEdit::NO_EDIT, lpIn, layout,
+ advances ? advances + offset : nullptr,
+ extents ? extents + offset : nullptr, bounds, lpOut);
wordend = wordstart;
}
}
return advance;
}
+class LayoutAppendFunctor {
+public:
+ LayoutAppendFunctor(const U16StringPiece& textBuf, const Range& range,
+ const MinikinPaint& paint, bool dir, StartHyphenEdit startEdit,
+ EndHyphenEdit endEdit, Layout* layout, float* advances,
+ MinikinExtent* extents, LayoutPieces* pieces, float* totalAdvance,
+ MinikinRect* bounds, uint32_t outOffset, float wordSpacing)
+ : mTextBuf(textBuf),
+ mRange(range),
+ mPaint(paint),
+ mDir(dir),
+ mStartEdit(startEdit),
+ mEndEdit(endEdit),
+ mLayout(layout),
+ mAdvances(advances),
+ mExtents(extents),
+ mPieces(pieces),
+ mTotalAdvance(totalAdvance),
+ mBounds(bounds),
+ mOutOffset(outOffset),
+ mWordSpacing(wordSpacing) {}
+
+ void operator()(const Layout& layout) {
+ if (mLayout) {
+ mLayout->appendLayout(layout, mOutOffset, mWordSpacing);
+ }
+ if (mAdvances) {
+ layout.getAdvances(mAdvances);
+ }
+ if (mTotalAdvance) {
+ *mTotalAdvance = layout.getAdvance();
+ }
+ if (mExtents) {
+ layout.getExtents(mExtents);
+ }
+ if (mBounds) {
+ mBounds->join(layout.getBounds());
+ }
+ if (mPieces) {
+ mPieces->insert(mTextBuf, mRange, mPaint, mDir, mStartEdit, mEndEdit, layout);
+ }
+ }
+
+private:
+ const U16StringPiece& mTextBuf;
+ const Range& mRange;
+ const MinikinPaint& mPaint;
+ bool mDir;
+ StartHyphenEdit mStartEdit;
+ EndHyphenEdit mEndEdit;
+ Layout* mLayout;
+ float* mAdvances;
+ MinikinExtent* mExtents;
+ LayoutPieces* mPieces;
+ float* mTotalAdvance;
+ MinikinRect* mBounds;
+ const uint32_t mOutOffset;
+ const float mWordSpacing;
+};
+
float Layout::doLayoutWord(const uint16_t* buf, size_t start, size_t count, size_t bufSize,
- bool isRtl, LayoutContext* ctx, size_t bufStart,
- const std::shared_ptr<FontCollection>& collection, Layout* layout, float* advances) {
- LayoutCache& cache = LayoutEngine::getInstance().layoutCache;
- LayoutCacheKey key(collection, ctx->paint, ctx->style, buf, start, count, bufSize, isRtl);
+ bool isRtl, const MinikinPaint& paint, size_t bufStart,
+ StartHyphenEdit startHyphen, EndHyphenEdit endHyphen,
+ const LayoutPieces* lpIn, Layout* layout, float* advances,
+ MinikinExtent* extents, MinikinRect* bounds, LayoutPieces* lpOut) {
+ float wordSpacing = count == 1 && isWordSpace(buf[start]) ? paint.wordSpacing : 0;
+ float totalAdvance;
- float wordSpacing = count == 1 && isWordSpace(buf[start]) ? ctx->paint.wordSpacing : 0;
-
- float advance;
- if (ctx->paint.skipCache()) {
- Layout layoutForWord;
- key.doLayout(&layoutForWord, ctx, collection);
- if (layout) {
- layout->appendLayout(&layoutForWord, bufStart, wordSpacing);
- }
- if (advances) {
- layoutForWord.getAdvances(advances);
- }
- advance = layoutForWord.getAdvance();
+ const U16StringPiece textBuf(buf, bufSize);
+ const Range range(start, start + count);
+ LayoutAppendFunctor f(textBuf, range, paint, isRtl, startHyphen, endHyphen, layout, advances,
+ extents, lpOut, &totalAdvance, bounds, bufStart, wordSpacing);
+ if (lpIn != nullptr) {
+ lpIn->getOrCreate(textBuf, range, paint, isRtl, startHyphen, endHyphen, f);
} else {
- Layout* layoutForWord = cache.get(key, ctx, collection);
- if (layout) {
- layout->appendLayout(layoutForWord, bufStart, wordSpacing);
- }
- if (advances) {
- layoutForWord->getAdvances(advances);
- }
- advance = layoutForWord->getAdvance();
+ LayoutCache::getInstance().getOrCreate(textBuf, range, paint, isRtl, startHyphen, endHyphen,
+ f);
}
if (wordSpacing != 0) {
- advance += wordSpacing;
+ totalAdvance += wordSpacing;
if (advances) {
advances[0] += wordSpacing;
}
}
- return advance;
+ return totalAdvance;
}
-static void addFeatures(const string &str, vector<hb_feature_t>* features) {
- if (!str.size())
- return;
-
- const char* start = str.c_str();
- const char* end = start + str.size();
-
- while (start < end) {
+static void addFeatures(const std::string& str, std::vector<hb_feature_t>* features) {
+ SplitIterator it(str, ',');
+ while (it.hasNext()) {
+ StringPiece featureStr = it.next();
static hb_feature_t feature;
- const char* p = strchr(start, ',');
- if (!p)
- p = end;
/* We do not allow setting features on ranges. As such, reject any
* setting that has non-universal range. */
- if (hb_feature_from_string (start, p - start, &feature)
- && feature.start == 0 && feature.end == (unsigned int) -1)
+ if (hb_feature_from_string(featureStr.data(), featureStr.size(), &feature) &&
+ feature.start == 0 && feature.end == (unsigned int)-1) {
features->push_back(feature);
- start = p + 1;
+ }
}
}
-static const hb_codepoint_t CHAR_HYPHEN = 0x2010; /* HYPHEN */
-
static inline hb_codepoint_t determineHyphenChar(hb_codepoint_t preferredHyphen, hb_font_t* font) {
hb_codepoint_t glyph;
- if (preferredHyphen == 0x058A /* ARMENIAN_HYPHEN */
- || preferredHyphen == 0x05BE /* HEBREW PUNCTUATION MAQAF */
- || preferredHyphen == 0x1400 /* CANADIAN SYLLABIC HYPHEN */) {
+ if (preferredHyphen == 0x058A /* ARMENIAN_HYPHEN */
+ || preferredHyphen == 0x05BE /* HEBREW PUNCTUATION MAQAF */
+ || preferredHyphen == 0x1400 /* CANADIAN SYLLABIC HYPHEN */) {
if (hb_font_get_nominal_glyph(font, preferredHyphen, &glyph)) {
return preferredHyphen;
} else {
@@ -716,37 +459,36 @@
// Note that we intentionally don't do anything special if the font doesn't have a
// HYPHEN-MINUS either, so a tofu could be shown, hinting towards something missing.
if (!hb_font_get_nominal_glyph(font, preferredHyphen, &glyph)) {
- return 0x002D; // HYPHEN-MINUS
+ return 0x002D; // HYPHEN-MINUS
}
}
return preferredHyphen;
}
-static inline void addHyphenToHbBuffer(hb_buffer_t* buffer, hb_font_t* font, uint32_t hyphen,
- uint32_t cluster) {
- const uint32_t* hyphenStr = HyphenEdit::getHyphenString(hyphen);
- while (*hyphenStr != 0) {
- hb_codepoint_t hyphenChar = determineHyphenChar(*hyphenStr, font);
- hb_buffer_add(buffer, hyphenChar, cluster);
- hyphenStr++;
+template <typename HyphenEdit>
+static inline void addHyphenToHbBuffer(const HbBufferUniquePtr& buffer, const HbFontUniquePtr& font,
+ HyphenEdit hyphen, uint32_t cluster) {
+ const uint32_t* chars;
+ size_t size;
+ std::tie(chars, size) = getHyphenString(hyphen);
+ for (size_t i = 0; i < size; i++) {
+ hb_buffer_add(buffer.get(), determineHyphenChar(chars[i], font.get()), cluster);
}
}
// Returns the cluster value assigned to the first codepoint added to the buffer, which can be used
// to translate cluster values returned by HarfBuzz to input indices.
-static inline uint32_t addToHbBuffer(hb_buffer_t* buffer,
- const uint16_t* buf, size_t start, size_t count, size_t bufSize,
- ssize_t scriptRunStart, ssize_t scriptRunEnd,
- HyphenEdit hyphenEdit, hb_font_t* hbFont) {
-
+static inline uint32_t addToHbBuffer(const HbBufferUniquePtr& buffer, const uint16_t* buf,
+ size_t start, size_t count, size_t bufSize,
+ ssize_t scriptRunStart, ssize_t scriptRunEnd,
+ StartHyphenEdit inStartHyphen, EndHyphenEdit inEndHyphen,
+ const HbFontUniquePtr& hbFont) {
// Only hyphenate the very first script run for starting hyphens.
- const uint32_t startHyphen = (scriptRunStart == 0)
- ? hyphenEdit.getStart()
- : HyphenEdit::NO_EDIT;
+ const StartHyphenEdit startHyphen =
+ (scriptRunStart == 0) ? inStartHyphen : StartHyphenEdit::NO_EDIT;
// Only hyphenate the very last script run for ending hyphens.
- const uint32_t endHyphen = (static_cast<size_t>(scriptRunEnd) == count)
- ? hyphenEdit.getEnd()
- : HyphenEdit::NO_EDIT;
+ const EndHyphenEdit endHyphen =
+ (static_cast<size_t>(scriptRunEnd) == count) ? inEndHyphen : EndHyphenEdit::NO_EDIT;
// In the following code, we drop the pre-context and/or post-context if there is a
// hyphen edit at that end. This is not absolutely necessary, since HarfBuzz uses
@@ -762,19 +504,19 @@
// We don't have any start-of-line replacement edit yet, so we don't need to check for
// those.
- if (HyphenEdit::isInsertion(startHyphen)) {
+ if (isInsertion(startHyphen)) {
// A cluster value of zero guarantees that the inserted hyphen will be in the same
// cluster with the next codepoint, since there is no pre-context.
- addHyphenToHbBuffer(buffer, hbFont, startHyphen, 0 /* cluster value */);
+ addHyphenToHbBuffer(buffer, hbFont, startHyphen, 0 /* cluster */);
}
const uint16_t* hbText;
int hbTextLength;
unsigned int hbItemOffset;
- unsigned int hbItemLength = scriptRunEnd - scriptRunStart; // This is >= 1.
+ unsigned int hbItemLength = scriptRunEnd - scriptRunStart; // This is >= 1.
- const bool hasEndInsertion = HyphenEdit::isInsertion(endHyphen);
- const bool hasEndReplacement = HyphenEdit::isReplacement(endHyphen);
+ const bool hasEndInsertion = isInsertion(endHyphen);
+ const bool hasEndReplacement = isReplacement(endHyphen);
if (hasEndReplacement) {
// Skip the last code unit while copying the buffer for HarfBuzz if it's a replacement. We
// don't need to worry about non-BMP characters yet since replacements are only done for
@@ -782,7 +524,7 @@
hbItemLength -= 1;
}
- if (startHyphen == HyphenEdit::NO_EDIT) {
+ if (startHyphen == StartHyphenEdit::NO_EDIT) {
// No edit at the beginning. Use the whole pre-context.
hbText = buf;
hbItemOffset = start + scriptRunStart;
@@ -793,7 +535,7 @@
hbItemOffset = 0;
}
- if (endHyphen == HyphenEdit::NO_EDIT) {
+ if (endHyphen == EndHyphenEdit::NO_EDIT) {
// No edit at the end, use the whole post-context.
hbTextLength = (buf + bufSize) - hbText;
} else {
@@ -801,10 +543,10 @@
hbTextLength = hbItemOffset + hbItemLength;
}
- hb_buffer_add_utf16(buffer, hbText, hbTextLength, hbItemOffset, hbItemLength);
+ hb_buffer_add_utf16(buffer.get(), hbText, hbTextLength, hbItemOffset, hbItemLength);
unsigned int numCodepoints;
- hb_glyph_info_t* cpInfo = hb_buffer_get_glyph_infos(buffer, &numCodepoints);
+ hb_glyph_info_t* cpInfo = hb_buffer_get_glyph_infos(buffer.get(), &numCodepoints);
// Add the hyphen at the end, if there's any.
if (hasEndInsertion || hasEndReplacement) {
@@ -823,61 +565,67 @@
// we have a replacement that is replacing a one-code unit script run.
hyphenCluster = 0;
} else {
- hyphenCluster = cpInfo[numCodepoints - 1].cluster + (uint32_t) hasEndReplacement;
+ hyphenCluster = cpInfo[numCodepoints - 1].cluster + (uint32_t)hasEndReplacement;
}
addHyphenToHbBuffer(buffer, hbFont, endHyphen, hyphenCluster);
// Since we have just added to the buffer, cpInfo no longer necessarily points to
// the right place. Refresh it.
- cpInfo = hb_buffer_get_glyph_infos(buffer, nullptr /* we don't need the size */);
+ cpInfo = hb_buffer_get_glyph_infos(buffer.get(), nullptr /* we don't need the size */);
}
return cpInfo[0].cluster;
}
-
void Layout::doLayoutRun(const uint16_t* buf, size_t start, size_t count, size_t bufSize,
- bool isRtl, LayoutContext* ctx, const std::shared_ptr<FontCollection>& collection) {
- hb_buffer_t* buffer = LayoutEngine::getInstance().hbBuffer;
- vector<FontCollection::Run> items;
- collection->itemize(buf + start, count, ctx->style, &items);
+ bool isRtl, const MinikinPaint& paint, StartHyphenEdit startHyphen,
+ EndHyphenEdit endHyphen) {
+ HbBufferUniquePtr buffer(hb_buffer_create());
+ hb_buffer_set_unicode_funcs(buffer.get(), getUnicodeFunctions());
+ std::vector<FontCollection::Run> items;
+ paint.font->itemize(buf + start, count, paint, &items);
- vector<hb_feature_t> features;
+ std::vector<hb_feature_t> features;
// Disable default-on non-required ligature features if letter-spacing
// See http://dev.w3.org/csswg/css-text-3/#letter-spacing-property
// "When the effective spacing between two characters is not zero (due to
// either justification or a non-zero value of letter-spacing), user agents
// should not apply optional ligatures."
- if (fabs(ctx->paint.letterSpacing) > 0.03)
- {
- static const hb_feature_t no_liga = { HB_TAG('l', 'i', 'g', 'a'), 0, 0, ~0u };
- static const hb_feature_t no_clig = { HB_TAG('c', 'l', 'i', 'g'), 0, 0, ~0u };
+ if (fabs(paint.letterSpacing) > 0.03) {
+ static const hb_feature_t no_liga = {HB_TAG('l', 'i', 'g', 'a'), 0, 0, ~0u};
+ static const hb_feature_t no_clig = {HB_TAG('c', 'l', 'i', 'g'), 0, 0, ~0u};
features.push_back(no_liga);
features.push_back(no_clig);
}
- addFeatures(ctx->paint.fontFeatureSettings, &features);
+ addFeatures(paint.fontFeatureSettings, &features);
- double size = ctx->paint.size;
- double scaleX = ctx->paint.scaleX;
+ std::vector<HbFontUniquePtr> hbFonts;
+ double size = paint.size;
+ double scaleX = paint.scaleX;
float x = mAdvance;
float y = 0;
for (int run_ix = isRtl ? items.size() - 1 : 0;
- isRtl ? run_ix >= 0 : run_ix < static_cast<int>(items.size());
- isRtl ? --run_ix : ++run_ix) {
- FontCollection::Run &run = items[run_ix];
- if (run.fakedFont.font == NULL) {
- ALOGE("no font for run starting u+%04x length %d", buf[run.start], run.end - run.start);
- continue;
+ isRtl ? run_ix >= 0 : run_ix < static_cast<int>(items.size());
+ isRtl ? --run_ix : ++run_ix) {
+ FontCollection::Run& run = items[run_ix];
+ const FakedFont& fakedFont = run.fakedFont;
+ const uint8_t font_ix = findOrPushBackFace(fakedFont);
+ if (hbFonts.size() == font_ix) { // findOrPushBackFace push backed the new face.
+ // We override some functions which are not thread safe.
+ HbFontUniquePtr font(hb_font_create_sub_font(fakedFont.font->baseFont().get()));
+ hb_font_set_funcs(
+ font.get(), isColorBitmapFont(font) ? getFontFuncsForEmoji() : getFontFuncs(),
+ new SkiaArguments({fakedFont.font->typeface().get(), &paint, fakedFont.fakery}),
+ [](void* data) { delete reinterpret_cast<SkiaArguments*>(data); });
+ hbFonts.push_back(std::move(font));
}
- int font_ix = findFace(run.fakedFont, ctx);
- ctx->paint.font = mFaces[font_ix].font;
- ctx->paint.fakery = mFaces[font_ix].fakery;
- hb_font_t* hbFont = ctx->hbFonts[font_ix];
-#ifdef VERBOSE_DEBUG
- ALOGD("Run %zu, font %d [%d:%d]", run_ix, font_ix, run.start, run.end);
-#endif
+ const HbFontUniquePtr& hbFont = hbFonts[font_ix];
- hb_font_set_ppem(hbFont, size * scaleX, size);
- hb_font_set_scale(hbFont, HBFloatToFixed(size * scaleX), HBFloatToFixed(size));
+ MinikinExtent verticalExtent;
+ fakedFont.font->typeface()->GetFontExtent(&verticalExtent, paint, fakedFont.fakery);
+ std::fill(&mExtents[run.start], &mExtents[run.end], verticalExtent);
+
+ hb_font_set_ppem(hbFont.get(), size * scaleX, size);
+ hb_font_set_scale(hbFont.get(), HBFloatToFixed(size * scaleX), HBFloatToFixed(size));
const bool is_color_bitmap_font = isColorBitmapFont(hbFont);
@@ -888,9 +636,8 @@
// Note: scriptRunStart and scriptRunEnd, as well as run.start and run.end, run between 0
// and count.
ssize_t scriptRunEnd;
- for (ssize_t scriptRunStart = run.start;
- scriptRunStart < run.end;
- scriptRunStart = scriptRunEnd) {
+ for (ssize_t scriptRunStart = run.start; scriptRunStart < run.end;
+ scriptRunStart = scriptRunEnd) {
scriptRunEnd = scriptRunStart;
hb_script_t script = getScriptRun(buf + start, run.end, &scriptRunEnd /* iterator */);
// After the last line, scriptRunEnd is guaranteed to have increased, since the only
@@ -904,9 +651,9 @@
double letterSpaceHalfLeft = 0.0;
double letterSpaceHalfRight = 0.0;
- if (ctx->paint.letterSpacing != 0.0 && isScriptOkForLetterspacing(script)) {
- letterSpace = ctx->paint.letterSpacing * size * scaleX;
- if ((ctx->paint.paintFlags & LinearTextFlag) == 0) {
+ if (paint.letterSpacing != 0.0 && isScriptOkForLetterspacing(script)) {
+ letterSpace = paint.letterSpacing * size * scaleX;
+ if ((paint.paintFlags & LinearTextFlag) == 0) {
letterSpace = round(letterSpace);
letterSpaceHalfLeft = floor(letterSpace * 0.5);
} else {
@@ -915,32 +662,30 @@
letterSpaceHalfRight = letterSpace - letterSpaceHalfLeft;
}
- hb_buffer_clear_contents(buffer);
- hb_buffer_set_script(buffer, script);
- hb_buffer_set_direction(buffer, isRtl? HB_DIRECTION_RTL : HB_DIRECTION_LTR);
- const FontLanguages& langList =
- FontLanguageListCache::getById(ctx->style.getLanguageListId());
- if (langList.size() != 0) {
- const FontLanguage* hbLanguage = &langList[0];
- for (size_t i = 0; i < langList.size(); ++i) {
- if (langList[i].supportsHbScript(script)) {
- hbLanguage = &langList[i];
+ hb_buffer_clear_contents(buffer.get());
+ hb_buffer_set_script(buffer.get(), script);
+ hb_buffer_set_direction(buffer.get(), isRtl ? HB_DIRECTION_RTL : HB_DIRECTION_LTR);
+ const LocaleList& localeList = LocaleListCache::getById(paint.localeListId);
+ if (localeList.size() != 0) {
+ hb_language_t hbLanguage = localeList.getHbLanguage(0);
+ for (size_t i = 0; i < localeList.size(); ++i) {
+ if (localeList[i].supportsHbScript(script)) {
+ hbLanguage = localeList.getHbLanguage(i);
break;
}
}
- hb_buffer_set_language(buffer, hbLanguage->getHbLanguage());
+ hb_buffer_set_language(buffer.get(), hbLanguage);
}
- const uint32_t clusterStart = addToHbBuffer(
- buffer,
- buf, start, count, bufSize,
- scriptRunStart, scriptRunEnd,
- ctx->paint.hyphenEdit, hbFont);
+ const uint32_t clusterStart =
+ addToHbBuffer(buffer, buf, start, count, bufSize, scriptRunStart, scriptRunEnd,
+ startHyphen, endHyphen, hbFont);
- hb_shape(hbFont, buffer, features.empty() ? NULL : &features[0], features.size());
+ hb_shape(hbFont.get(), buffer.get(), features.empty() ? NULL : &features[0],
+ features.size());
unsigned int numGlyphs;
- hb_glyph_info_t* info = hb_buffer_get_glyph_infos(buffer, &numGlyphs);
- hb_glyph_position_t* positions = hb_buffer_get_glyph_positions(buffer, NULL);
+ hb_glyph_info_t* info = hb_buffer_get_glyph_infos(buffer.get(), &numGlyphs);
+ hb_glyph_position_t* positions = hb_buffer_get_glyph_positions(buffer.get(), NULL);
// At this point in the code, the cluster values in the info buffer correspond to the
// input characters with some shift. The cluster value clusterStart corresponds to the
@@ -950,39 +695,32 @@
// mAdvances.
const ssize_t clusterOffset = clusterStart - scriptRunStart;
- if (numGlyphs)
- {
+ if (numGlyphs) {
mAdvances[info[0].cluster - clusterOffset] += letterSpaceHalfLeft;
x += letterSpaceHalfLeft;
}
for (unsigned int i = 0; i < numGlyphs; i++) {
-#ifdef VERBOSE_DEBUG
- ALOGD("%d %d %d %d",
- positions[i].x_advance, positions[i].y_advance,
- positions[i].x_offset, positions[i].y_offset);
- ALOGD("DoLayout %u: %f; %d, %d",
- info[i].codepoint, HBFixedToFloat(positions[i].x_advance),
- positions[i].x_offset, positions[i].y_offset);
-#endif
+ const size_t clusterBaseIndex = info[i].cluster - clusterOffset;
if (i > 0 && info[i - 1].cluster != info[i].cluster) {
mAdvances[info[i - 1].cluster - clusterOffset] += letterSpaceHalfRight;
- mAdvances[info[i].cluster - clusterOffset] += letterSpaceHalfLeft;
+ mAdvances[clusterBaseIndex] += letterSpaceHalfLeft;
x += letterSpace;
}
hb_codepoint_t glyph_ix = info[i].codepoint;
float xoff = HBFixedToFloat(positions[i].x_offset);
float yoff = -HBFixedToFloat(positions[i].y_offset);
- xoff += yoff * ctx->paint.skewX;
+ xoff += yoff * paint.skewX;
LayoutGlyph glyph = {font_ix, glyph_ix, x + xoff, y + yoff};
mGlyphs.push_back(glyph);
float xAdvance = HBFixedToFloat(positions[i].x_advance);
- if ((ctx->paint.paintFlags & LinearTextFlag) == 0) {
+ if ((paint.paintFlags & LinearTextFlag) == 0) {
xAdvance = roundf(xAdvance);
}
MinikinRect glyphBounds;
hb_glyph_extents_t extents = {};
- if (is_color_bitmap_font && hb_font_get_glyph_extents(hbFont, glyph_ix, &extents)) {
+ if (is_color_bitmap_font &&
+ hb_font_get_glyph_extents(hbFont.get(), glyph_ix, &extents)) {
// Note that it is technically possible for a TrueType font to have outline and
// embedded bitmap at the same time. We ignore modified bbox of hinted outline
// glyphs in that case.
@@ -992,20 +730,22 @@
glyphBounds.mBottom =
roundf(HBFixedToFloat(-extents.y_bearing - extents.height));
} else {
- ctx->paint.font->GetBounds(&glyphBounds, glyph_ix, ctx->paint);
+ fakedFont.font->typeface()->GetBounds(&glyphBounds, glyph_ix, paint,
+ fakedFont.fakery);
}
- glyphBounds.offset(x + xoff, y + yoff);
- mBounds.join(glyphBounds);
- if (static_cast<size_t>(info[i].cluster - clusterOffset) < count) {
- mAdvances[info[i].cluster - clusterOffset] += xAdvance;
+ glyphBounds.offset(xoff, yoff);
+
+ if (clusterBaseIndex < count) {
+ mAdvances[clusterBaseIndex] += xAdvance;
} else {
- ALOGE("cluster %zu (start %zu) out of bounds of count %zu",
- info[i].cluster - clusterOffset, start, count);
+ ALOGE("cluster %zu (start %zu) out of bounds of count %zu", clusterBaseIndex,
+ start, count);
}
+ glyphBounds.offset(x, y);
+ mBounds.join(glyphBounds);
x += xAdvance;
}
- if (numGlyphs)
- {
+ if (numGlyphs) {
mAdvances[info[numGlyphs - 1].cluster - clusterOffset] += letterSpaceHalfRight;
x += letterSpaceHalfRight;
}
@@ -1014,21 +754,21 @@
mAdvance = x;
}
-void Layout::appendLayout(Layout* src, size_t start, float extraAdvance) {
+void Layout::appendLayout(const Layout& src, size_t start, float extraAdvance) {
int fontMapStack[16];
int* fontMap;
- if (src->mFaces.size() < sizeof(fontMapStack) / sizeof(fontMapStack[0])) {
+ if (src.mFaces.size() < sizeof(fontMapStack) / sizeof(fontMapStack[0])) {
fontMap = fontMapStack;
} else {
- fontMap = new int[src->mFaces.size()];
+ fontMap = new int[src.mFaces.size()];
}
- for (size_t i = 0; i < src->mFaces.size(); i++) {
- int font_ix = findFace(src->mFaces[i], NULL);
+ for (size_t i = 0; i < src.mFaces.size(); i++) {
+ uint8_t font_ix = findOrPushBackFace(src.mFaces[i]);
fontMap[i] = font_ix;
}
int x0 = mAdvance;
- for (size_t i = 0; i < src->mGlyphs.size(); i++) {
- LayoutGlyph& srcGlyph = src->mGlyphs[i];
+ for (size_t i = 0; i < src.mGlyphs.size(); i++) {
+ const LayoutGlyph& srcGlyph = src.mGlyphs[i];
int font_ix = fontMap[srcGlyph.font_ix];
unsigned int glyph_id = srcGlyph.glyph_id;
float x = x0 + srcGlyph.x;
@@ -1036,15 +776,17 @@
LayoutGlyph glyph = {font_ix, glyph_id, x, y};
mGlyphs.push_back(glyph);
}
- for (size_t i = 0; i < src->mAdvances.size(); i++) {
- mAdvances[i + start] = src->mAdvances[i];
- if (i == 0)
- mAdvances[i + start] += extraAdvance;
+ for (size_t i = 0; i < src.mAdvances.size(); i++) {
+ mAdvances[i + start] = src.mAdvances[i];
+ if (i == 0) {
+ mAdvances[start] += extraAdvance;
+ }
+ mExtents[i + start] = src.mExtents[i];
}
- MinikinRect srcBounds(src->mBounds);
+ MinikinRect srcBounds(src.mBounds);
srcBounds.offset(x0, 0);
mBounds.join(srcBounds);
- mAdvance += src->mAdvance + extraAdvance;
+ mAdvance += src.mAdvance + extraAdvance;
if (fontMap != fontMapStack) {
delete[] fontMap;
@@ -1057,7 +799,7 @@
const MinikinFont* Layout::getFont(int i) const {
const LayoutGlyph& glyph = mGlyphs[i];
- return mFaces[glyph.font_ix].font;
+ return mFaces[glyph.font_ix].font->typeface().get();
}
FontFakery Layout::getFakery(int i) const {
@@ -1084,26 +826,24 @@
return mAdvance;
}
-void Layout::getAdvances(float* advances) {
+void Layout::getAdvances(float* advances) const {
memcpy(advances, &mAdvances[0], mAdvances.size() * sizeof(float));
}
+void Layout::getExtents(MinikinExtent* extents) const {
+ memcpy(extents, &mExtents[0], mExtents.size() * sizeof(MinikinExtent));
+}
+
void Layout::getBounds(MinikinRect* bounds) const {
bounds->set(mBounds);
}
void Layout::purgeCaches() {
- android::AutoMutex _l(gMinikinLock);
- LayoutCache& layoutCache = LayoutEngine::getInstance().layoutCache;
- layoutCache.clear();
- purgeHbFontCacheLocked();
+ LayoutCache::getInstance().clear();
+}
+
+void Layout::dumpMinikinStats(int fd) {
+ LayoutCache::getInstance().dumpStats(fd);
}
} // namespace minikin
-
-// Unable to define the static data member outside of android.
-// TODO: introduce our own Singleton to drop android namespace.
-namespace android {
-ANDROID_SINGLETON_STATIC_INSTANCE(minikin::LayoutEngine);
-} // namespace android
-
diff --git a/libs/minikin/LayoutUtils.cpp b/libs/minikin/LayoutUtils.cpp
index a3238d4..e79ea8c 100644
--- a/libs/minikin/LayoutUtils.cpp
+++ b/libs/minikin/LayoutUtils.cpp
@@ -14,19 +14,16 @@
* limitations under the License.
*/
-#define LOG_TAG "Minikin"
-
#include "LayoutUtils.h"
namespace minikin {
-const uint16_t CHAR_NBSP = 0x00A0;
-
/*
* Determine whether the code unit is a word space for the purposes of justification.
+ * TODO: Support NBSP and other stretchable whitespace (b/34013491 and b/68204709).
*/
bool isWordSpace(uint16_t code_unit) {
- return code_unit == ' ' || code_unit == CHAR_NBSP;
+ return code_unit == ' ';
}
/**
@@ -35,7 +32,7 @@
* heuristic, but should be accurate most of the time.
*/
static bool isWordBreakAfter(uint16_t c) {
- if (isWordSpace(c) || (c >= 0x2000 && c <= 0x200a) || c == 0x3000) {
+ if (c == ' ' || (0x2000 <= c && c <= 0x200A) || c == 0x3000) {
// spaces
return true;
}
@@ -45,14 +42,13 @@
static bool isWordBreakBefore(uint16_t c) {
// CJK ideographs (and yijing hexagram symbols)
- return isWordBreakAfter(c) || (c >= 0x3400 && c <= 0x9fff);
+ return isWordBreakAfter(c) || (0x3400 <= c && c <= 0x9FFF);
}
/**
* Return offset of previous word break. It is either < offset or == 0.
*/
-size_t getPrevWordBreakForCache(
- const uint16_t* chars, size_t offset, size_t len) {
+size_t getPrevWordBreakForCache(const uint16_t* chars, size_t offset, size_t len) {
if (offset == 0) return 0;
if (offset > len) offset = len;
if (isWordBreakBefore(chars[offset - 1])) {
@@ -69,8 +65,7 @@
/**
* Return offset of next word break. It is either > offset or == len.
*/
-size_t getNextWordBreakForCache(
- const uint16_t* chars, size_t offset, size_t len) {
+size_t getNextWordBreakForCache(const uint16_t* chars, size_t offset, size_t len) {
if (offset >= len) return len;
if (isWordBreakAfter(chars[offset])) {
return offset + 1;
diff --git a/libs/minikin/LayoutUtils.h b/libs/minikin/LayoutUtils.h
index b89004c..3128148 100644
--- a/libs/minikin/LayoutUtils.h
+++ b/libs/minikin/LayoutUtils.h
@@ -17,7 +17,7 @@
#ifndef MINIKIN_LAYOUT_UTILS_H
#define MINIKIN_LAYOUT_UTILS_H
-#include <stdint.h>
+#include <cstdint>
namespace minikin {
@@ -33,8 +33,7 @@
* kerning or complex script processing. This is necessarily a
* heuristic, but should be accurate most of the time.
*/
-size_t getPrevWordBreakForCache(
- const uint16_t* chars, size_t offset, size_t len);
+size_t getPrevWordBreakForCache(const uint16_t* chars, size_t offset, size_t len);
/**
* Return offset of next word break. It is either > offset or == len.
@@ -43,8 +42,7 @@
* kerning or complex script processing. This is necessarily a
* heuristic, but should be accurate most of the time.
*/
-size_t getNextWordBreakForCache(
- const uint16_t* chars, size_t offset, size_t len);
+size_t getNextWordBreakForCache(const uint16_t* chars, size_t offset, size_t len);
} // namespace minikin
#endif // MINIKIN_LAYOUT_UTILS_H
diff --git a/libs/minikin/LineBreaker.cpp b/libs/minikin/LineBreaker.cpp
index bccc299..12cf9bd 100644
--- a/libs/minikin/LineBreaker.cpp
+++ b/libs/minikin/LineBreaker.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,533 +14,24 @@
* limitations under the License.
*/
-#define VERBOSE_DEBUG 0
+#include "minikin/LineBreaker.h"
-#define LOG_TAG "Minikin"
-
-#include <limits>
-
-#include <log/log.h>
-
-#include "LayoutUtils.h"
-#include <minikin/Layout.h>
-#include <minikin/LineBreaker.h>
-
-using std::vector;
+#include "GreedyLineBreaker.h"
+#include "OptimalLineBreaker.h"
namespace minikin {
-const int CHAR_TAB = 0x0009;
-
-// Large scores in a hierarchy; we prefer desperate breaks to an overfull line. All these
-// constants are larger than any reasonable actual width score.
-const float SCORE_INFTY = std::numeric_limits<float>::max();
-const float SCORE_OVERFULL = 1e12f;
-const float SCORE_DESPERATE = 1e10f;
-
-// Multiplier for hyphen penalty on last line.
-const float LAST_LINE_PENALTY_MULTIPLIER = 4.0f;
-// Penalty assigned to each line break (to try to minimize number of lines)
-// TODO: when we implement full justification (so spaces can shrink and stretch), this is
-// probably not the most appropriate method.
-const float LINE_PENALTY_MULTIPLIER = 2.0f;
-
-// Penalty assigned to shrinking the whitepsace.
-const float SHRINK_PENALTY_MULTIPLIER = 4.0f;
-
-// Very long words trigger O(n^2) behavior in hyphenation, so we disable hyphenation for
-// unreasonably long words. This is somewhat of a heuristic because extremely long words
-// are possible in some languages. This does mean that very long real words can get
-// broken by desperate breaks, with no hyphens.
-const size_t LONGEST_HYPHENATED_WORD = 45;
-
-// When the text buffer is within this limit, capacity of vectors is retained at finish(),
-// to avoid allocation.
-const size_t MAX_TEXT_BUF_RETAIN = 32678;
-
-// Maximum amount that spaces can shrink, in justified text.
-const float SHRINKABILITY = 1.0 / 3.0;
-
-void LineBreaker::setLocales(const char* locales, const std::vector<Hyphenator*>& hyphenators) {
- bool goodLocaleFound = false;
- const ssize_t numLocales = hyphenators.size();
- // For now, we ignore all locales except the first valid one.
- // TODO: Support selecting the locale based on the script of the text.
- const char* localeStart = locales;
- for (ssize_t i = 0; i < numLocales - 1; i++) { // Loop over all locales, except the last one.
- const char* localeEnd = strchr(localeStart, ',');
- const size_t localeNameLength = localeEnd - localeStart;
- char localeName[localeNameLength + 1];
- strncpy(localeName, localeStart, localeNameLength);
- localeName[localeNameLength] = '\0';
- mLocale = icu::Locale::createFromName(localeName);
- goodLocaleFound = !mLocale.isBogus();
- if (goodLocaleFound) {
- mHyphenator = hyphenators[i];
- break;
- } else {
- localeStart = localeEnd + 1;
- }
- }
- if (!goodLocaleFound) { // Try the last locale.
- mLocale = icu::Locale::createFromName(localeStart);
- if (mLocale.isBogus()) {
- // No good locale.
- mLocale = icu::Locale::getRoot();
- mHyphenator = nullptr;
- } else {
- mHyphenator = numLocales == 0 ? nullptr : hyphenators[numLocales - 1];
- }
- }
- mWordBreaker.setLocale(mLocale);
-}
-
-void LineBreaker::setText() {
- mWordBreaker.setText(mTextBuf.data(), mTextBuf.size());
-
- // handle initial break here because addStyleRun may never be called
- mWordBreaker.next();
- mCandidates.clear();
- Candidate cand = {0, 0, 0.0, 0.0, 0.0, 0.0, 0, 0, 0, HyphenationType::DONT_BREAK};
- mCandidates.push_back(cand);
-
- // reset greedy breaker state
- mBreaks.clear();
- mWidths.clear();
- mFlags.clear();
- mLastBreak = 0;
- mBestBreak = 0;
- mBestScore = SCORE_INFTY;
- mPreBreak = 0;
- mLastHyphenation = HyphenEdit::NO_EDIT;
- mFirstTabIndex = INT_MAX;
- mSpaceCount = 0;
-}
-
-void LineBreaker::setLineWidths(float firstWidth, int firstWidthLineCount, float restWidth) {
- mLineWidths.setWidths(firstWidth, firstWidthLineCount, restWidth);
-}
-
-
-void LineBreaker::setIndents(const std::vector<float>& indents) {
- mLineWidths.setIndents(indents);
-}
-
-// This function determines whether a character is a space that disappears at end of line.
-// It is the Unicode set: [[:General_Category=Space_Separator:]-[:Line_Break=Glue:]],
-// plus '\n'.
-// Note: all such characters are in the BMP, so it's ok to use code units for this.
-static bool isLineEndSpace(uint16_t c) {
- return c == '\n' || c == ' ' || c == 0x1680 || (0x2000 <= c && c <= 0x200A && c != 0x2007) ||
- c == 0x205F || c == 0x3000;
-}
-
-// Ordinarily, this method measures the text in the range given. However, when paint
-// is nullptr, it assumes the widths have already been calculated and stored in the
-// width buffer.
-// This method finds the candidate word breaks (using the ICU break iterator) and sends them
-// to addCandidate.
-float LineBreaker::addStyleRun(MinikinPaint* paint, const std::shared_ptr<FontCollection>& typeface,
- FontStyle style, size_t start, size_t end, bool isRtl) {
- float width = 0.0f;
- int bidiFlags = isRtl ? kBidi_Force_RTL : kBidi_Force_LTR;
-
- float hyphenPenalty = 0.0;
- if (paint != nullptr) {
- width = Layout::measureText(mTextBuf.data(), start, end - start, mTextBuf.size(), bidiFlags,
- style, *paint, typeface, mCharWidths.data() + start);
-
- // a heuristic that seems to perform well
- hyphenPenalty = 0.5 * paint->size * paint->scaleX * mLineWidths.getLineWidth(0);
- if (mHyphenationFrequency == kHyphenationFrequency_Normal) {
- hyphenPenalty *= 4.0; // TODO: Replace with a better value after some testing
- }
-
- if (mJustified) {
- // Make hyphenation more aggressive for fully justified text (so that "normal" in
- // justified mode is the same as "full" in ragged-right).
- hyphenPenalty *= 0.25;
- } else {
- // Line penalty is zero for justified text.
- mLinePenalty = std::max(mLinePenalty, hyphenPenalty * LINE_PENALTY_MULTIPLIER);
- }
- }
-
- size_t current = (size_t)mWordBreaker.current();
- size_t afterWord = start;
- size_t lastBreak = start;
- ParaWidth lastBreakWidth = mWidth;
- ParaWidth postBreak = mWidth;
- size_t postSpaceCount = mSpaceCount;
- for (size_t i = start; i < end; i++) {
- uint16_t c = mTextBuf[i];
- if (c == CHAR_TAB) {
- mWidth = mPreBreak + mTabStops.nextTab(mWidth - mPreBreak);
- if (mFirstTabIndex == INT_MAX) {
- mFirstTabIndex = (int)i;
- }
- // fall back to greedy; other modes don't know how to deal with tabs
- mStrategy = kBreakStrategy_Greedy;
- } else {
- if (isWordSpace(c)) mSpaceCount += 1;
- mWidth += mCharWidths[i];
- if (!isLineEndSpace(c)) {
- postBreak = mWidth;
- postSpaceCount = mSpaceCount;
- afterWord = i + 1;
- }
- }
- if (i + 1 == current) {
- size_t wordStart = mWordBreaker.wordStart();
- size_t wordEnd = mWordBreaker.wordEnd();
- if (paint != nullptr && mHyphenator != nullptr &&
- mHyphenationFrequency != kHyphenationFrequency_None &&
- wordStart >= start && wordEnd > wordStart &&
- wordEnd - wordStart <= LONGEST_HYPHENATED_WORD) {
- mHyphenator->hyphenate(&mHyphBuf,
- &mTextBuf[wordStart],
- wordEnd - wordStart,
- mLocale);
-#if VERBOSE_DEBUG
- std::string hyphenatedString;
- for (size_t j = wordStart; j < wordEnd; j++) {
- if (mHyphBuf[j - wordStart] == HyphenationType::BREAK_AND_INSERT_HYPHEN) {
- hyphenatedString.push_back('-');
- }
- // Note: only works with ASCII, should do UTF-8 conversion here
- hyphenatedString.push_back(buffer()[j]);
- }
- ALOGD("hyphenated string: %s", hyphenatedString.c_str());
-#endif
-
- // measure hyphenated substrings
- for (size_t j = wordStart; j < wordEnd; j++) {
- HyphenationType hyph = mHyphBuf[j - wordStart];
- if (hyph != HyphenationType::DONT_BREAK) {
- paint->hyphenEdit = HyphenEdit::editForThisLine(hyph);
- const float firstPartWidth = Layout::measureText(mTextBuf.data(),
- lastBreak, j - lastBreak, mTextBuf.size(), bidiFlags, style,
- *paint, typeface, nullptr);
- ParaWidth hyphPostBreak = lastBreakWidth + firstPartWidth;
-
- paint->hyphenEdit = HyphenEdit::editForNextLine(hyph);
- const float secondPartWidth = Layout::measureText(mTextBuf.data(), j,
- afterWord - j, mTextBuf.size(), bidiFlags, style, *paint,
- typeface, nullptr);
- ParaWidth hyphPreBreak = postBreak - secondPartWidth;
-
- addWordBreak(j, hyphPreBreak, hyphPostBreak, postSpaceCount, postSpaceCount,
- hyphenPenalty, hyph);
-
- paint->hyphenEdit = HyphenEdit::NO_EDIT;
- }
- }
- }
-
- // Skip break for zero-width characters inside replacement span
- if (paint != nullptr || current == end || mCharWidths[current] > 0) {
- float penalty = hyphenPenalty * mWordBreaker.breakBadness();
- addWordBreak(current, mWidth, postBreak, mSpaceCount, postSpaceCount, penalty,
- HyphenationType::DONT_BREAK);
- }
- lastBreak = current;
- lastBreakWidth = mWidth;
- current = (size_t)mWordBreaker.next();
- }
- }
-
- return width;
-}
-
-// add a word break (possibly for a hyphenated fragment), and add desperate breaks if
-// needed (ie when word exceeds current line width)
-void LineBreaker::addWordBreak(size_t offset, ParaWidth preBreak, ParaWidth postBreak,
- size_t preSpaceCount, size_t postSpaceCount, float penalty, HyphenationType hyph) {
- Candidate cand;
- ParaWidth width = mCandidates.back().preBreak;
- if (postBreak - width > currentLineWidth()) {
- // Add desperate breaks.
- // Note: these breaks are based on the shaping of the (non-broken) original text; they
- // are imprecise especially in the presence of kerning, ligatures, and Arabic shaping.
- size_t i = mCandidates.back().offset;
- width += mCharWidths[i++];
- for (; i < offset; i++) {
- float w = mCharWidths[i];
- if (w > 0) {
- cand.offset = i;
- cand.preBreak = width;
- cand.postBreak = width;
- // postSpaceCount doesn't include trailing spaces
- cand.preSpaceCount = postSpaceCount;
- cand.postSpaceCount = postSpaceCount;
- cand.penalty = SCORE_DESPERATE;
- cand.hyphenType = HyphenationType::BREAK_AND_DONT_INSERT_HYPHEN;
-#if VERBOSE_DEBUG
- ALOGD("desperate cand: %zd %g:%g",
- mCandidates.size(), cand.postBreak, cand.preBreak);
-#endif
- addCandidate(cand);
- width += w;
- }
- }
- }
-
- cand.offset = offset;
- cand.preBreak = preBreak;
- cand.postBreak = postBreak;
- cand.penalty = penalty;
- cand.preSpaceCount = preSpaceCount;
- cand.postSpaceCount = postSpaceCount;
- cand.hyphenType = hyph;
-#if VERBOSE_DEBUG
- ALOGD("cand: %zd %g:%g", mCandidates.size(), cand.postBreak, cand.preBreak);
-#endif
- addCandidate(cand);
-}
-
-// Helper method for addCandidate()
-void LineBreaker::pushGreedyBreak() {
- const Candidate& bestCandidate = mCandidates[mBestBreak];
- pushBreak(bestCandidate.offset, bestCandidate.postBreak - mPreBreak,
- mLastHyphenation | HyphenEdit::editForThisLine(bestCandidate.hyphenType));
- mBestScore = SCORE_INFTY;
-#if VERBOSE_DEBUG
- ALOGD("break: %d %g", mBreaks.back(), mWidths.back());
-#endif
- mLastBreak = mBestBreak;
- mPreBreak = bestCandidate.preBreak;
- mLastHyphenation = HyphenEdit::editForNextLine(bestCandidate.hyphenType);
-}
-
-// TODO performance: could avoid populating mCandidates if greedy only
-void LineBreaker::addCandidate(Candidate cand) {
- const size_t candIndex = mCandidates.size();
- mCandidates.push_back(cand);
-
- // mLastBreak is the index of the last line break we decided to do in mCandidates,
- // and mPreBreak is its preBreak value. mBestBreak is the index of the best line breaking candidate
- // we have found since then, and mBestScore is its penalty.
- if (cand.postBreak - mPreBreak > currentLineWidth()) {
- // This break would create an overfull line, pick the best break and break there (greedy)
- if (mBestBreak == mLastBreak) {
- // No good break has been found since last break. Break here.
- mBestBreak = candIndex;
- }
- pushGreedyBreak();
- }
-
- while (mLastBreak != candIndex && cand.postBreak - mPreBreak > currentLineWidth()) {
- // We should rarely come here. But if we are here, we have broken the line, but the
- // remaining part still doesn't fit. We now need to break at the second best place after the
- // last break, but we have not kept that information, so we need to go back and find it.
- //
- // In some really rare cases, postBreak - preBreak of a candidate itself may be over the
- // current line width. We protect ourselves against an infinite loop in that case by
- // checking that we have not broken the line at this candidate already.
- for (size_t i = mLastBreak + 1; i < candIndex; i++) {
- const float penalty = mCandidates[i].penalty;
- if (penalty <= mBestScore) {
- mBestBreak = i;
- mBestScore = penalty;
- }
- }
- if (mBestBreak == mLastBreak) {
- // We didn't find anything good. Break here.
- mBestBreak = candIndex;
- }
- pushGreedyBreak();
- }
-
- if (cand.penalty <= mBestScore) {
- mBestBreak = candIndex;
- mBestScore = cand.penalty;
- }
-}
-
-void LineBreaker::pushBreak(int offset, float width, uint8_t hyphenEdit) {
- mBreaks.push_back(offset);
- mWidths.push_back(width);
- int flags = (mFirstTabIndex < mBreaks.back()) << kTab_Shift;
- flags |= hyphenEdit;
- mFlags.push_back(flags);
- mFirstTabIndex = INT_MAX;
-}
-
-void LineBreaker::addReplacement(size_t start, size_t end, float width) {
- mCharWidths[start] = width;
- std::fill(&mCharWidths[start + 1], &mCharWidths[end], 0.0f);
- addStyleRun(nullptr, nullptr, FontStyle(), start, end, false);
-}
-
-// Get the width of a space. May return 0 if there are no spaces.
-// Note: if there are multiple different widths for spaces (for example, because of mixing of
-// fonts), it's only guaranteed to pick one.
-float LineBreaker::getSpaceWidth() const {
- for (size_t i = 0; i < mTextBuf.size(); i++) {
- if (isWordSpace(mTextBuf[i])) {
- return mCharWidths[i];
- }
- }
- return 0.0f;
-}
-
-float LineBreaker::currentLineWidth() const {
- return mLineWidths.getLineWidth(mBreaks.size());
-}
-
-void LineBreaker::computeBreaksGreedy() {
- // All breaks but the last have been added in addCandidate already.
- size_t nCand = mCandidates.size();
- if (nCand == 1 || mLastBreak != nCand - 1) {
- pushBreak(mCandidates[nCand - 1].offset, mCandidates[nCand - 1].postBreak - mPreBreak,
- mLastHyphenation);
- // don't need to update mBestScore, because we're done
-#if VERBOSE_DEBUG
- ALOGD("final break: %d %g", mBreaks.back(), mWidths.back());
-#endif
- }
-}
-
-// Follow "prev" links in mCandidates array, and copy to result arrays.
-void LineBreaker::finishBreaksOptimal() {
- // clear existing greedy break result
- mBreaks.clear();
- mWidths.clear();
- mFlags.clear();
- size_t nCand = mCandidates.size();
- size_t prev;
- for (size_t i = nCand - 1; i > 0; i = prev) {
- prev = mCandidates[i].prev;
- mBreaks.push_back(mCandidates[i].offset);
- mWidths.push_back(mCandidates[i].postBreak - mCandidates[prev].preBreak);
- int flags = HyphenEdit::editForThisLine(mCandidates[i].hyphenType);
- if (prev > 0) {
- flags |= HyphenEdit::editForNextLine(mCandidates[prev].hyphenType);
- }
- mFlags.push_back(flags);
- }
- std::reverse(mBreaks.begin(), mBreaks.end());
- std::reverse(mWidths.begin(), mWidths.end());
- std::reverse(mFlags.begin(), mFlags.end());
-}
-
-void LineBreaker::computeBreaksOptimal(bool isRectangle) {
- size_t active = 0;
- size_t nCand = mCandidates.size();
- float width = mLineWidths.getLineWidth(0);
- float maxShrink = mJustified ? SHRINKABILITY * getSpaceWidth() : 0.0f;
-
- // "i" iterates through candidates for the end of the line.
- for (size_t i = 1; i < nCand; i++) {
- bool atEnd = i == nCand - 1;
- float best = SCORE_INFTY;
- size_t bestPrev = 0;
- size_t lineNumberLast = 0;
-
- if (!isRectangle) {
- size_t lineNumberLast = mCandidates[active].lineNumber;
- width = mLineWidths.getLineWidth(lineNumberLast);
- }
- ParaWidth leftEdge = mCandidates[i].postBreak - width;
- float bestHope = 0;
-
- // "j" iterates through candidates for the beginning of the line.
- for (size_t j = active; j < i; j++) {
- if (!isRectangle) {
- size_t lineNumber = mCandidates[j].lineNumber;
- if (lineNumber != lineNumberLast) {
- float widthNew = mLineWidths.getLineWidth(lineNumber);
- if (widthNew != width) {
- leftEdge = mCandidates[i].postBreak - width;
- bestHope = 0;
- width = widthNew;
- }
- lineNumberLast = lineNumber;
- }
- }
- float jScore = mCandidates[j].score;
- if (jScore + bestHope >= best) continue;
- float delta = mCandidates[j].preBreak - leftEdge;
-
- // compute width score for line
-
- // Note: the "bestHope" optimization makes the assumption that, when delta is
- // non-negative, widthScore will increase monotonically as successive candidate
- // breaks are considered.
- float widthScore = 0.0f;
- float additionalPenalty = 0.0f;
- if ((atEnd || !mJustified) && delta < 0) {
- widthScore = SCORE_OVERFULL;
- } else if (atEnd && mStrategy != kBreakStrategy_Balanced) {
- // increase penalty for hyphen on last line
- additionalPenalty = LAST_LINE_PENALTY_MULTIPLIER * mCandidates[j].penalty;
- } else {
- widthScore = delta * delta;
- if (delta < 0) {
- if (-delta < maxShrink *
- (mCandidates[i].postSpaceCount - mCandidates[j].preSpaceCount)) {
- widthScore *= SHRINK_PENALTY_MULTIPLIER;
- } else {
- widthScore = SCORE_OVERFULL;
- }
- }
- }
-
- if (delta < 0) {
- active = j + 1;
- } else {
- bestHope = widthScore;
- }
-
- float score = jScore + widthScore + additionalPenalty;
- if (score <= best) {
- best = score;
- bestPrev = j;
- }
- }
- mCandidates[i].score = best + mCandidates[i].penalty + mLinePenalty;
- mCandidates[i].prev = bestPrev;
- mCandidates[i].lineNumber = mCandidates[bestPrev].lineNumber + 1;
-#if VERBOSE_DEBUG
- ALOGD("break %zd: score=%g, prev=%zd", i, mCandidates[i].score, mCandidates[i].prev);
-#endif
- }
- finishBreaksOptimal();
-}
-
-size_t LineBreaker::computeBreaks() {
- if (mStrategy == kBreakStrategy_Greedy) {
- computeBreaksGreedy();
+LineBreakResult breakIntoLines(const U16StringPiece& textBuffer, BreakStrategy strategy,
+ HyphenationFrequency frequency, bool justified,
+ const MeasuredText& measuredText, const LineWidth& lineWidth,
+ const TabStops& tabStops) {
+ if (strategy == BreakStrategy::Greedy || textBuffer.hasChar(CHAR_TAB)) {
+ return breakLineGreedy(textBuffer, measuredText, lineWidth, tabStops,
+ frequency != HyphenationFrequency::None);
} else {
- computeBreaksOptimal(mLineWidths.isConstant());
+ return breakLineOptimal(textBuffer, measuredText, lineWidth, strategy, frequency,
+ justified);
}
- return mBreaks.size();
-}
-
-void LineBreaker::finish() {
- mWordBreaker.finish();
- mWidth = 0;
- mLineWidths.clear();
- mCandidates.clear();
- mBreaks.clear();
- mWidths.clear();
- mFlags.clear();
- if (mTextBuf.size() > MAX_TEXT_BUF_RETAIN) {
- mTextBuf.clear();
- mTextBuf.shrink_to_fit();
- mCharWidths.clear();
- mCharWidths.shrink_to_fit();
- mHyphBuf.clear();
- mHyphBuf.shrink_to_fit();
- mCandidates.shrink_to_fit();
- mBreaks.shrink_to_fit();
- mWidths.shrink_to_fit();
- mFlags.shrink_to_fit();
- }
- mStrategy = kBreakStrategy_Greedy;
- mHyphenationFrequency = kHyphenationFrequency_Normal;
- mLinePenalty = 0.0f;
- mJustified = false;
}
} // namespace minikin
diff --git a/libs/minikin/LineBreakerUtil.cpp b/libs/minikin/LineBreakerUtil.cpp
new file mode 100644
index 0000000..920a8d0
--- /dev/null
+++ b/libs/minikin/LineBreakerUtil.cpp
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "LineBreakerUtil.h"
+
+namespace minikin {
+
+// Very long words trigger O(n^2) behavior in hyphenation, so we disable hyphenation for
+// unreasonably long words. This is somewhat of a heuristic because extremely long words are
+// possible in some languages. This does mean that very long real words can get broken by
+// desperate breaks, with no hyphens.
+constexpr size_t LONGEST_HYPHENATED_WORD = 45;
+
+// Hyphenates a string potentially containing non-breaking spaces.
+std::vector<HyphenationType> hyphenate(const U16StringPiece& str, const Hyphenator& hyphenator) {
+ std::vector<HyphenationType> out;
+ const size_t len = str.size();
+ out.resize(len);
+
+ // A word here is any consecutive string of non-NBSP characters.
+ bool inWord = false;
+ size_t wordStart = 0; // The initial value will never be accessed, but just in case.
+ for (size_t i = 0; i <= len; i++) {
+ if (i == len || str[i] == CHAR_NBSP) {
+ if (inWord) {
+ // A word just ended. Hyphenate it.
+ const U16StringPiece word = str.substr(Range(wordStart, i));
+ if (word.size() <= LONGEST_HYPHENATED_WORD) {
+ hyphenator.hyphenate(word, out.data() + wordStart);
+ } else { // Word is too long. Inefficient to hyphenate.
+ out.insert(out.end(), word.size(), HyphenationType::DONT_BREAK);
+ }
+ inWord = false;
+ }
+ if (i < len) {
+ // Insert one DONT_BREAK for the NBSP.
+ out.push_back(HyphenationType::DONT_BREAK);
+ }
+ } else if (!inWord) {
+ inWord = true;
+ wordStart = i;
+ }
+ }
+ return out;
+}
+
+} // namespace minikin
diff --git a/libs/minikin/LineBreakerUtil.h b/libs/minikin/LineBreakerUtil.h
new file mode 100644
index 0000000..64f4371
--- /dev/null
+++ b/libs/minikin/LineBreakerUtil.h
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MINIKIN_LINE_BREAKER_UTIL_H
+#define MINIKIN_LINE_BREAKER_UTIL_H
+
+#include <vector>
+
+#include "minikin/Hyphenator.h"
+#include "minikin/MeasuredText.h"
+#include "minikin/U16StringPiece.h"
+
+#include "HyphenatorMap.h"
+#include "LayoutUtils.h"
+#include "Locale.h"
+#include "LocaleListCache.h"
+#include "MinikinInternal.h"
+#include "WordBreaker.h"
+
+namespace minikin {
+
+// ParaWidth is used to hold cumulative width from beginning of paragraph. Note that for very large
+// paragraphs, accuracy could degrade using only 32-bit float. Note however that float is used
+// extensively on the Java side for this. This is a typedef so that we can easily change it based
+// on performance/accuracy tradeoff.
+typedef double ParaWidth;
+
+// Hyphenates a string potentially containing non-breaking spaces.
+std::vector<HyphenationType> hyphenate(const U16StringPiece& string, const Hyphenator& hypenator);
+
+// This function determines whether a character is a space that disappears at end of line.
+// It is the Unicode set: [[:General_Category=Space_Separator:]-[:Line_Break=Glue:]], plus '\n'.
+// Note: all such characters are in the BMP, so it's ok to use code units for this.
+inline bool isLineEndSpace(uint16_t c) {
+ return c == '\n' || c == ' ' // SPACE
+ || c == 0x1680 // OGHAM SPACE MARK
+ || (0x2000 <= c && c <= 0x200A && c != 0x2007) // EN QUAD, EM QUAD, EN SPACE, EM SPACE,
+ // THREE-PER-EM SPACE, FOUR-PER-EM SPACE,
+ // SIX-PER-EM SPACE, PUNCTUATION SPACE,
+ // THIN SPACE, HAIR SPACE
+ || c == 0x205F // MEDIUM MATHEMATICAL SPACE
+ || c == 0x3000;
+}
+
+// Returns true if the character needs to be excluded for the line spacing.
+inline bool isLineSpaceExcludeChar(uint16_t c) {
+ return c == CHAR_LINE_FEED || c == CHAR_CARRIAGE_RETURN;
+}
+
+inline Locale getEffectiveLocale(uint32_t localeListId) {
+ const LocaleList& localeList = LocaleListCache::getById(localeListId);
+ return localeList.empty() ? Locale() : localeList[0];
+}
+
+// Retrieves hyphenation break points from a word.
+inline void populateHyphenationPoints(
+ const U16StringPiece& textBuf, // A text buffer.
+ const Run& run, // A run of this region.
+ const Hyphenator& hyphenator, // A hyphenator to be used for hyphenation.
+ const Range& contextRange, // A context range for measuring hyphenated piece.
+ const Range& hyphenationTargetRange, // An actual range for the hyphenation target.
+ std::vector<HyphenBreak>* out, // An output to be appended.
+ LayoutPieces* pieces) { // An output of layout pieces. Maybe null.
+ if (!run.getRange().contains(contextRange) || !contextRange.contains(hyphenationTargetRange)) {
+ return;
+ }
+
+ const std::vector<HyphenationType> hyphenResult =
+ hyphenate(textBuf.substr(hyphenationTargetRange), hyphenator);
+ for (uint32_t i = hyphenationTargetRange.getStart(); i < hyphenationTargetRange.getEnd(); ++i) {
+ const HyphenationType hyph = hyphenResult[hyphenationTargetRange.toRangeOffset(i)];
+ if (hyph == HyphenationType::DONT_BREAK) {
+ continue; // Not a hyphenation point.
+ }
+
+ auto hyphenPart = contextRange.split(i);
+ U16StringPiece firstText = textBuf.substr(hyphenPart.first);
+ U16StringPiece secondText = textBuf.substr(hyphenPart.second);
+ const float first = run.measureHyphenPiece(firstText, Range(0, firstText.size()),
+ StartHyphenEdit::NO_EDIT /* start hyphen edit */,
+ editForThisLine(hyph) /* end hyphen edit */,
+ nullptr /* advances */, pieces);
+ const float second = run.measureHyphenPiece(secondText, Range(0, secondText.size()),
+ editForNextLine(hyph) /* start hyphen edit */,
+ EndHyphenEdit::NO_EDIT /* end hyphen edit */,
+ nullptr /* advances */, pieces);
+
+ out->emplace_back(i, hyph, first, second);
+ }
+}
+
+// Processes and retrieve informations from characters in the paragraph.
+struct CharProcessor {
+ // The number of spaces.
+ uint32_t rawSpaceCount = 0;
+
+ // The number of spaces minus trailing spaces.
+ uint32_t effectiveSpaceCount = 0;
+
+ // The sum of character width from the paragraph start.
+ ParaWidth sumOfCharWidths = 0.0;
+
+ // The sum of character width from the paragraph start minus trailing line end spaces.
+ // This means that the line width from the paragraph start if we decided break now.
+ ParaWidth effectiveWidth = 0.0;
+
+ // The total amount of character widths at the previous word break point.
+ ParaWidth sumOfCharWidthsAtPrevWordBreak = 0.0;
+
+ // The next word break offset.
+ uint32_t nextWordBreak = 0;
+
+ // The previous word break offset.
+ uint32_t prevWordBreak = 0;
+
+ // The width of a space. May be 0 if there are no spaces.
+ // Note: if there are multiple different widths for spaces (for example, because of mixing of
+ // fonts), it's only guaranteed to pick one.
+ float spaceWidth = 0.0f;
+
+ // The current hyphenator.
+ const Hyphenator* hyphenator = nullptr;
+
+ // Retrieve the current word range.
+ inline Range wordRange() const { return breaker.wordRange(); }
+
+ // Retrieve the current context range.
+ inline Range contextRange() const { return Range(prevWordBreak, nextWordBreak); }
+
+ // Returns the width from the last word break point.
+ inline ParaWidth widthFromLastWordBreak() const {
+ return effectiveWidth - sumOfCharWidthsAtPrevWordBreak;
+ }
+
+ // Returns the break penalty for the current word break point.
+ inline int wordBreakPenalty() const { return breaker.breakBadness(); }
+
+ CharProcessor(const U16StringPiece& text) { breaker.setText(text.data(), text.size()); }
+
+ // The user of CharProcessor must call updateLocaleIfNecessary with valid locale at least one
+ // time before feeding characters.
+ void updateLocaleIfNecessary(const Run& run) {
+ // Update locale if necessary.
+ uint32_t newLocaleListId = run.getLocaleListId();
+ if (localeListId != newLocaleListId) {
+ Locale locale = getEffectiveLocale(newLocaleListId);
+ nextWordBreak = breaker.followingWithLocale(locale, run.getRange().getStart());
+ hyphenator = HyphenatorMap::lookup(locale);
+ localeListId = newLocaleListId;
+ }
+ }
+
+ // Process one character.
+ void feedChar(uint32_t idx, uint16_t c, float w) {
+ if (idx == nextWordBreak) {
+ prevWordBreak = nextWordBreak;
+ nextWordBreak = breaker.next();
+ sumOfCharWidthsAtPrevWordBreak = sumOfCharWidths;
+ }
+ if (isWordSpace(c)) {
+ rawSpaceCount += 1;
+ spaceWidth = w;
+ }
+ sumOfCharWidths += w;
+ if (isLineEndSpace(c)) {
+ // If we break a line on a line-ending space, that space goes away. So postBreak
+ // and postSpaceCount, which keep the width and number of spaces if we decide to
+ // break at this point, don't need to get adjusted.
+ } else {
+ effectiveSpaceCount = rawSpaceCount;
+ effectiveWidth = sumOfCharWidths;
+ }
+ }
+
+private:
+ // The current locale list id.
+ uint32_t localeListId = LocaleListCache::kInvalidListId;
+
+ WordBreaker breaker;
+};
+} // namespace minikin
+
+#endif // MINIKIN_LINE_BREAKER_UTIL_H
diff --git a/libs/minikin/Locale.cpp b/libs/minikin/Locale.cpp
new file mode 100644
index 0000000..4209413
--- /dev/null
+++ b/libs/minikin/Locale.cpp
@@ -0,0 +1,437 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Locale.h"
+
+#include <algorithm>
+
+#include <hb.h>
+
+#include "minikin/LocaleList.h"
+
+#include "LocaleListCache.h"
+#include "MinikinInternal.h"
+#include "StringPiece.h"
+
+namespace minikin {
+
+constexpr uint32_t FIVE_BITS = 0x1f;
+
+uint32_t registerLocaleList(const std::string& locales) {
+ return LocaleListCache::getId(locales);
+}
+
+// Check if a language code supports emoji according to its subtag
+static bool isEmojiSubtag(const char* buf, size_t bufLen, const char* subtag, size_t subtagLen) {
+ if (bufLen < subtagLen) {
+ return false;
+ }
+ if (strncmp(buf, subtag, subtagLen) != 0) {
+ return false; // no match between two strings
+ }
+ return (bufLen == subtagLen || buf[subtagLen] == '\0' || buf[subtagLen] == '-' ||
+ buf[subtagLen] == '_');
+}
+
+// Pack the three letter code into 15 bits and stored to 16 bit integer. The highest bit is 0.
+// For the region code, the letters must be all digits in three letter case, so the number of
+// possible values are 10. For the language code, the letters must be all small alphabets, so the
+// number of possible values are 26. Thus, 5 bits are sufficient for each case and we can pack the
+// three letter language code or region code to 15 bits.
+//
+// In case of two letter code, use fullbit(0x1f) for the first letter instead.
+static uint16_t packLanguageOrRegion(const StringPiece& in, uint8_t twoLetterBase,
+ uint8_t threeLetterBase) {
+ if (in.length() == 2) {
+ return 0x7c00u | // 0x1fu << 10
+ (uint16_t)(in[0] - twoLetterBase) << 5 | (uint16_t)(in[1] - twoLetterBase);
+ } else {
+ return ((uint16_t)(in[0] - threeLetterBase) << 10) |
+ (uint16_t)(in[1] - threeLetterBase) << 5 | (uint16_t)(in[2] - threeLetterBase);
+ }
+}
+
+static size_t unpackLanguageOrRegion(uint16_t in, char* out, uint8_t twoLetterBase,
+ uint8_t threeLetterBase) {
+ uint8_t first = (in >> 10) & FIVE_BITS;
+ uint8_t second = (in >> 5) & FIVE_BITS;
+ uint8_t third = in & FIVE_BITS;
+
+ if (first == 0x1f) {
+ out[0] = second + twoLetterBase;
+ out[1] = third + twoLetterBase;
+ return 2;
+ } else {
+ out[0] = first + threeLetterBase;
+ out[1] = second + threeLetterBase;
+ out[2] = third + threeLetterBase;
+ return 3;
+ }
+}
+
+static uint16_t packLanguage(const StringPiece& in) {
+ return packLanguageOrRegion(in, 'a', 'a');
+}
+
+static size_t unpackLanguage(uint16_t in, char* out) {
+ return unpackLanguageOrRegion(in, out, 'a', 'a');
+}
+
+constexpr uint32_t packScript(char c1, char c2, char c3, char c4) {
+ constexpr char FIRST_LETTER_BASE = 'A';
+ constexpr char REST_LETTER_BASE = 'a';
+ return ((uint32_t)(c1 - FIRST_LETTER_BASE) << 15) | (uint32_t)(c2 - REST_LETTER_BASE) << 10 |
+ ((uint32_t)(c3 - REST_LETTER_BASE) << 5) | (uint32_t)(c4 - REST_LETTER_BASE);
+}
+
+constexpr uint32_t packScript(uint32_t script) {
+ return packScript(script >> 24, (script >> 16) & 0xff, (script >> 8) & 0xff, script & 0xff);
+}
+
+constexpr uint32_t unpackScript(uint32_t packedScript) {
+ constexpr char FIRST_LETTER_BASE = 'A';
+ constexpr char REST_LETTER_BASE = 'a';
+ const uint32_t first = (packedScript >> 15) + FIRST_LETTER_BASE;
+ const uint32_t second = ((packedScript >> 10) & FIVE_BITS) + REST_LETTER_BASE;
+ const uint32_t third = ((packedScript >> 5) & FIVE_BITS) + REST_LETTER_BASE;
+ const uint32_t fourth = (packedScript & FIVE_BITS) + REST_LETTER_BASE;
+
+ return first << 24 | second << 16 | third << 8 | fourth;
+}
+
+static uint16_t packRegion(const StringPiece& in) {
+ return packLanguageOrRegion(in, 'A', '0');
+}
+
+static size_t unpackRegion(uint16_t in, char* out) {
+ return unpackLanguageOrRegion(in, out, 'A', '0');
+}
+
+static inline bool isLowercase(char c) {
+ return 'a' <= c && c <= 'z';
+}
+
+static inline bool isUppercase(char c) {
+ return 'A' <= c && c <= 'Z';
+}
+
+static inline bool isDigit(char c) {
+ return '0' <= c && c <= '9';
+}
+
+// Returns true if the buffer is valid for language code.
+static inline bool isValidLanguageCode(const StringPiece& buffer) {
+ if (buffer.length() != 2 && buffer.length() != 3) return false;
+ if (!isLowercase(buffer[0])) return false;
+ if (!isLowercase(buffer[1])) return false;
+ if (buffer.length() == 3 && !isLowercase(buffer[2])) return false;
+ return true;
+}
+
+// Returns true if buffer is valid for script code. The length of buffer must be 4.
+static inline bool isValidScriptCode(const StringPiece& buffer) {
+ return buffer.size() == 4 && isUppercase(buffer[0]) && isLowercase(buffer[1]) &&
+ isLowercase(buffer[2]) && isLowercase(buffer[3]);
+}
+
+// Returns true if the buffer is valid for region code.
+static inline bool isValidRegionCode(const StringPiece& buffer) {
+ return (buffer.size() == 2 && isUppercase(buffer[0]) && isUppercase(buffer[1])) ||
+ (buffer.size() == 3 && isDigit(buffer[0]) && isDigit(buffer[1]) && isDigit(buffer[2]));
+}
+
+// Parse BCP 47 language identifier into internal structure
+Locale::Locale(const StringPiece& input) : Locale() {
+ SplitIterator it(input, '-');
+
+ StringPiece language = it.next();
+ if (isValidLanguageCode(language)) {
+ mLanguage = packLanguage(language);
+ } else {
+ // We don't understand anything other than two-letter or three-letter
+ // language codes, so we skip parsing the rest of the string.
+ return;
+ }
+
+ if (!it.hasNext()) {
+ return; // Language code only.
+ }
+ StringPiece token = it.next();
+
+ if (isValidScriptCode(token)) {
+ mScript = packScript(token[0], token[1], token[2], token[3]);
+ mSubScriptBits = scriptToSubScriptBits(mScript);
+
+ if (!it.hasNext()) {
+ goto finalize; // No variant, emoji subtag and region code.
+ }
+ token = it.next();
+ }
+
+ if (isValidRegionCode(token)) {
+ mRegion = packRegion(token);
+
+ if (!it.hasNext()) {
+ goto finalize; // No variant or emoji subtag.
+ }
+ token = it.next();
+ }
+
+ if (language == "de") { // We are only interested in German variants.
+ if (token == "1901") {
+ mVariant = Variant::GERMAN_1901_ORTHOGRAPHY;
+ } else if (token == "1996") {
+ mVariant = Variant::GERMAN_1996_ORTHOGRAPHY;
+ }
+
+ if (mVariant != Variant::NO_VARIANT) {
+ if (!it.hasNext()) {
+ goto finalize; // No emoji subtag.
+ }
+
+ token = it.next();
+ }
+ }
+
+ mEmojiStyle = resolveEmojiStyle(input.data(), input.length());
+
+finalize:
+ if (mEmojiStyle == EmojiStyle::EMPTY) {
+ mEmojiStyle = scriptToEmojiStyle(mScript);
+ }
+}
+
+// static
+EmojiStyle Locale::resolveEmojiStyle(const char* buf, size_t length) {
+ // First, lookup emoji subtag.
+ // 10 is the length of "-u-em-text", which is the shortest emoji subtag,
+ // unnecessary comparison can be avoided if total length is smaller than 10.
+ const size_t kMinSubtagLength = 10;
+ if (length >= kMinSubtagLength) {
+ static const char kPrefix[] = "-u-em-";
+ const char* pos = std::search(buf, buf + length, kPrefix, kPrefix + strlen(kPrefix));
+ if (pos != buf + length) { // found
+ pos += strlen(kPrefix);
+ const size_t remainingLength = length - (pos - buf);
+ if (isEmojiSubtag(pos, remainingLength, "emoji", 5)) {
+ return EmojiStyle::EMOJI;
+ } else if (isEmojiSubtag(pos, remainingLength, "text", 4)) {
+ return EmojiStyle::TEXT;
+ } else if (isEmojiSubtag(pos, remainingLength, "default", 7)) {
+ return EmojiStyle::DEFAULT;
+ }
+ }
+ }
+ return EmojiStyle::EMPTY;
+}
+
+EmojiStyle Locale::scriptToEmojiStyle(uint32_t script) {
+ // If no emoji subtag was provided, resolve the emoji style from script code.
+ if (script == packScript('Z', 's', 'y', 'e')) {
+ return EmojiStyle::EMOJI;
+ } else if (script == packScript('Z', 's', 'y', 'm')) {
+ return EmojiStyle::TEXT;
+ }
+ return EmojiStyle::EMPTY;
+}
+
+// static
+uint8_t Locale::scriptToSubScriptBits(uint32_t script) {
+ uint8_t subScriptBits = 0u;
+ switch (script) {
+ case packScript('B', 'o', 'p', 'o'):
+ subScriptBits = kBopomofoFlag;
+ break;
+ case packScript('H', 'a', 'n', 'g'):
+ subScriptBits = kHangulFlag;
+ break;
+ case packScript('H', 'a', 'n', 'b'):
+ // Bopomofo is almost exclusively used in Taiwan.
+ subScriptBits = kHanFlag | kBopomofoFlag;
+ break;
+ case packScript('H', 'a', 'n', 'i'):
+ subScriptBits = kHanFlag;
+ break;
+ case packScript('H', 'a', 'n', 's'):
+ subScriptBits = kHanFlag | kSimplifiedChineseFlag;
+ break;
+ case packScript('H', 'a', 'n', 't'):
+ subScriptBits = kHanFlag | kTraditionalChineseFlag;
+ break;
+ case packScript('H', 'i', 'r', 'a'):
+ subScriptBits = kHiraganaFlag;
+ break;
+ case packScript('H', 'r', 'k', 't'):
+ subScriptBits = kKatakanaFlag | kHiraganaFlag;
+ break;
+ case packScript('J', 'p', 'a', 'n'):
+ subScriptBits = kHanFlag | kKatakanaFlag | kHiraganaFlag;
+ break;
+ case packScript('K', 'a', 'n', 'a'):
+ subScriptBits = kKatakanaFlag;
+ break;
+ case packScript('K', 'o', 'r', 'e'):
+ subScriptBits = kHanFlag | kHangulFlag;
+ break;
+ }
+ return subScriptBits;
+}
+
+std::string Locale::getString() const {
+ char buf[24];
+ size_t i;
+ if (mLanguage == NO_LANGUAGE) {
+ buf[0] = 'u';
+ buf[1] = 'n';
+ buf[2] = 'd';
+ i = 3;
+ } else {
+ i = unpackLanguage(mLanguage, buf);
+ }
+ if (mScript != NO_SCRIPT) {
+ uint32_t rawScript = unpackScript(mScript);
+ buf[i++] = '-';
+ buf[i++] = (rawScript >> 24) & 0xFFu;
+ buf[i++] = (rawScript >> 16) & 0xFFu;
+ buf[i++] = (rawScript >> 8) & 0xFFu;
+ buf[i++] = rawScript & 0xFFu;
+ }
+ if (mRegion != NO_REGION) {
+ buf[i++] = '-';
+ i += unpackRegion(mRegion, buf + i);
+ }
+ if (mVariant != Variant::NO_VARIANT) {
+ buf[i++] = '-';
+ buf[i++] = '1';
+ buf[i++] = '9';
+ switch (mVariant) {
+ case Variant::GERMAN_1901_ORTHOGRAPHY:
+ buf[i++] = '0';
+ buf[i++] = '1';
+ break;
+ case Variant::GERMAN_1996_ORTHOGRAPHY:
+ buf[i++] = '9';
+ buf[i++] = '6';
+ break;
+ default:
+ MINIKIN_ASSERT(false, "Must not reached.");
+ }
+ }
+ return std::string(buf, i);
+}
+
+Locale Locale::getPartialLocale(SubtagBits bits) const {
+ Locale subLocale;
+ if ((bits & SubtagBits::LANGUAGE) != SubtagBits::EMPTY) {
+ subLocale.mLanguage = mLanguage;
+ } else {
+ subLocale.mLanguage = packLanguage("und");
+ }
+ if ((bits & SubtagBits::SCRIPT) != SubtagBits::EMPTY) {
+ subLocale.mScript = mScript;
+ subLocale.mSubScriptBits = mSubScriptBits;
+ }
+ if ((bits & SubtagBits::REGION) != SubtagBits::EMPTY) {
+ subLocale.mRegion = mRegion;
+ }
+ if ((bits & SubtagBits::VARIANT) != SubtagBits::EMPTY) {
+ subLocale.mVariant = mVariant;
+ }
+ if ((bits & SubtagBits::EMOJI) != SubtagBits::EMPTY) {
+ subLocale.mEmojiStyle = mEmojiStyle;
+ }
+ return subLocale;
+}
+
+bool Locale::isEqualScript(const Locale& other) const {
+ return other.mScript == mScript;
+}
+
+// static
+bool Locale::supportsScript(uint8_t providedBits, uint8_t requestedBits) {
+ return requestedBits != 0 && (providedBits & requestedBits) == requestedBits;
+}
+
+bool Locale::supportsHbScript(hb_script_t script) const {
+ static_assert(unpackScript(packScript('J', 'p', 'a', 'n')) == HB_TAG('J', 'p', 'a', 'n'),
+ "The Minikin script and HarfBuzz hb_script_t have different encodings.");
+ uint32_t packedScript = packScript(script);
+ if (packedScript == mScript) return true;
+ return supportsScript(mSubScriptBits, scriptToSubScriptBits(packedScript));
+}
+
+int Locale::calcScoreFor(const LocaleList& supported) const {
+ bool languageScriptMatch = false;
+ bool subtagMatch = false;
+ bool scriptMatch = false;
+
+ for (size_t i = 0; i < supported.size(); ++i) {
+ if (mEmojiStyle != EmojiStyle::EMPTY && mEmojiStyle == supported[i].mEmojiStyle) {
+ subtagMatch = true;
+ if (mLanguage == supported[i].mLanguage) {
+ return 4;
+ }
+ }
+ if (isEqualScript(supported[i]) ||
+ supportsScript(supported[i].mSubScriptBits, mSubScriptBits)) {
+ scriptMatch = true;
+ if (mLanguage == supported[i].mLanguage) {
+ languageScriptMatch = true;
+ }
+ }
+ }
+
+ if (supportsScript(supported.getUnionOfSubScriptBits(), mSubScriptBits)) {
+ scriptMatch = true;
+ if (mLanguage == supported[0].mLanguage && supported.isAllTheSameLocale()) {
+ return 3;
+ }
+ }
+
+ if (languageScriptMatch) {
+ return 3;
+ } else if (subtagMatch) {
+ return 2;
+ } else if (scriptMatch) {
+ return 1;
+ }
+ return 0;
+}
+
+static hb_language_t buildHbLanguage(const Locale& locale) {
+ return locale.isSupported() ? hb_language_from_string(locale.getString().c_str(), -1)
+ : HB_LANGUAGE_INVALID;
+}
+
+LocaleList::LocaleList(std::vector<Locale>&& locales) : mLocales(std::move(locales)) {
+ mIsAllTheSameLocale = true;
+ mUnionOfSubScriptBits = 0u;
+ mHbLangs.reserve(mLocales.size());
+ mEmojiStyle = EmojiStyle::EMPTY;
+ const auto firstLanguage = mLocales.empty() ? NO_LANGUAGE : mLocales[0].mLanguage;
+ for (const Locale& locale : mLocales) {
+ mUnionOfSubScriptBits |= locale.mSubScriptBits;
+ if (mIsAllTheSameLocale && firstLanguage != locale.mLanguage) {
+ mIsAllTheSameLocale = false;
+ }
+ mHbLangs.push_back(buildHbLanguage(locale));
+ if (mEmojiStyle == EmojiStyle::EMPTY) {
+ mEmojiStyle = locale.getEmojiStyle();
+ }
+ }
+}
+
+} // namespace minikin
diff --git a/libs/minikin/Locale.h b/libs/minikin/Locale.h
new file mode 100644
index 0000000..f030f92
--- /dev/null
+++ b/libs/minikin/Locale.h
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MINIKIN_LOCALE_LIST_H
+#define MINIKIN_LOCALE_LIST_H
+
+#include <string>
+#include <vector>
+
+#include <hb.h>
+
+#include "StringPiece.h"
+
+namespace minikin {
+
+// Due to the limits in font fallback score calculation, we can't use anything more than 12 locales.
+const size_t FONT_LOCALE_LIMIT = 12;
+
+// The language or region code is encoded to 15 bits.
+constexpr uint16_t NO_LANGUAGE = 0x7fff;
+constexpr uint16_t NO_REGION = 0x7fff;
+// The script code is encoded to 20 bits.
+constexpr uint32_t NO_SCRIPT = 0xfffff;
+
+class LocaleList;
+
+// Enum for making sub-locale from FontLangauge.
+enum class SubtagBits : uint8_t {
+ EMPTY = 0b00000000,
+ LANGUAGE = 0b00000001,
+ SCRIPT = 0b00000010,
+ REGION = 0b00000100,
+ VARIANT = 0b00001000,
+ EMOJI = 0b00010000,
+ ALL = 0b00011111,
+};
+
+inline constexpr SubtagBits operator&(SubtagBits l, SubtagBits r) {
+ return static_cast<SubtagBits>(static_cast<uint8_t>(l) & static_cast<uint8_t>(r));
+}
+inline constexpr SubtagBits operator|(SubtagBits l, SubtagBits r) {
+ return static_cast<SubtagBits>(static_cast<uint8_t>(l) | static_cast<uint8_t>(r));
+}
+
+// Enum for emoji style.
+enum class EmojiStyle : uint8_t {
+ EMPTY = 0, // No emoji style is specified.
+ DEFAULT = 1, // Default emoji style is specified.
+ EMOJI = 2, // Emoji (color) emoji style is specified.
+ TEXT = 3, // Text (black/white) emoji style is specified.
+};
+
+// Locale is a compact representation of a BCP 47 language tag.
+// It does not capture all possible information, only what directly affects text layout:
+// font rendering, hyphenation, word breaking, etc.
+struct Locale {
+public:
+ enum class Variant : uint16_t { // Up to 12 bits
+ NO_VARIANT = 0x0000,
+ GERMAN_1901_ORTHOGRAPHY = 0x0001,
+ GERMAN_1996_ORTHOGRAPHY = 0x0002,
+ };
+
+ // Default constructor creates the unsupported locale.
+ Locale()
+ : mScript(NO_SCRIPT),
+ mLanguage(NO_LANGUAGE),
+ mRegion(NO_REGION),
+ mSubScriptBits(0ul),
+ mVariant(Variant::NO_VARIANT),
+ mEmojiStyle(EmojiStyle::EMPTY) {}
+
+ // Parse from string
+ Locale(const StringPiece& buf);
+
+ bool operator==(const Locale other) const {
+ return !isUnsupported() && isEqualScript(other) && mLanguage == other.mLanguage &&
+ mRegion == other.mRegion && mVariant == other.mVariant &&
+ mEmojiStyle == other.mEmojiStyle;
+ }
+
+ bool operator!=(const Locale other) const { return !(*this == other); }
+
+ inline bool hasLanguage() const { return mLanguage != NO_LANGUAGE; }
+ inline bool hasScript() const { return mScript != NO_SCRIPT; }
+ inline bool hasRegion() const { return mRegion != NO_REGION; }
+ inline bool hasVariant() const { return mVariant != Variant::NO_VARIANT; }
+ inline bool hasEmojiStyle() const { return mEmojiStyle != EmojiStyle::EMPTY; }
+
+ inline bool isSupported() const {
+ return hasLanguage() || hasScript() || hasRegion() || hasVariant() || hasEmojiStyle();
+ }
+
+ inline bool isUnsupported() const { return !isSupported(); }
+
+ EmojiStyle getEmojiStyle() const { return mEmojiStyle; }
+
+ bool isEqualScript(const Locale& other) const;
+
+ // Returns true if this script supports the given script. For example, ja-Jpan supports Hira,
+ // ja-Hira doesn't support Jpan.
+ bool supportsHbScript(hb_script_t script) const;
+
+ std::string getString() const;
+
+ // Calculates a matching score. This score represents how well the input locales cover this
+ // locale. The maximum score in the locale list is returned.
+ // 0 = no match, 1 = script match, 2 = script and primary language match.
+ int calcScoreFor(const LocaleList& supported) const;
+
+ uint64_t getIdentifier() const {
+ return ((uint64_t)mLanguage << 49) | ((uint64_t)mScript << 29) | ((uint64_t)mRegion << 14) |
+ ((uint64_t)mEmojiStyle << 12) | (uint64_t)mVariant;
+ }
+
+ Locale getPartialLocale(SubtagBits bits) const;
+
+private:
+ friend class LocaleList; // for LocaleList constructor
+
+ // ISO 15924 compliant script code. The 4 chars script code are packed into a 20 bit integer.
+ // If not specified, this is kInvalidScript.
+ uint32_t mScript;
+
+ // ISO 639-1 or ISO 639-2 compliant language code.
+ // The two- or three-letter language code is packed into a 15 bit integer.
+ // mLanguage = 0 means the Locale is unsupported.
+ uint16_t mLanguage;
+
+ // ISO 3166-1 or UN M.49 compliant region code. The two-letter or three-digit region code is
+ // packed into a 15 bit integer.
+ uint16_t mRegion;
+
+ // For faster comparing, use 7 bits for specific scripts.
+ static const uint8_t kBopomofoFlag = 1u;
+ static const uint8_t kHanFlag = 1u << 1;
+ static const uint8_t kHangulFlag = 1u << 2;
+ static const uint8_t kHiraganaFlag = 1u << 3;
+ static const uint8_t kKatakanaFlag = 1u << 4;
+ static const uint8_t kSimplifiedChineseFlag = 1u << 5;
+ static const uint8_t kTraditionalChineseFlag = 1u << 6;
+ uint8_t mSubScriptBits;
+
+ Variant mVariant;
+
+ EmojiStyle mEmojiStyle;
+
+ static uint8_t scriptToSubScriptBits(uint32_t rawScript);
+
+ static EmojiStyle resolveEmojiStyle(const char* buf, size_t length);
+ static EmojiStyle scriptToEmojiStyle(uint32_t script);
+
+ // Returns true if the provide subscript bits has the requested subscript bits.
+ // Note that this function returns false if the requested subscript bits are empty.
+ static bool supportsScript(uint8_t providedBits, uint8_t requestedBits);
+};
+
+// An immutable list of locale.
+class LocaleList {
+public:
+ explicit LocaleList(std::vector<Locale>&& locales);
+ LocaleList()
+ : mUnionOfSubScriptBits(0),
+ mIsAllTheSameLocale(false),
+ mEmojiStyle(EmojiStyle::EMPTY) {}
+ LocaleList(LocaleList&&) = default;
+
+ size_t size() const { return mLocales.size(); }
+ bool empty() const { return mLocales.empty(); }
+ const Locale& operator[](size_t n) const { return mLocales[n]; }
+
+ hb_language_t getHbLanguage(size_t n) const { return mHbLangs[n]; }
+
+ // Returns an effective emoji style of this locale list.
+ // The effective means the first non empty emoji style in the list.
+ EmojiStyle getEmojiStyle() const { return mEmojiStyle; }
+
+private:
+ friend struct Locale; // for calcScoreFor
+
+ std::vector<Locale> mLocales;
+
+ // The languages to be passed to HarfBuzz shaper.
+ std::vector<hb_language_t> mHbLangs;
+ uint8_t mUnionOfSubScriptBits;
+ bool mIsAllTheSameLocale;
+ EmojiStyle mEmojiStyle;
+
+ uint8_t getUnionOfSubScriptBits() const { return mUnionOfSubScriptBits; }
+ bool isAllTheSameLocale() const { return mIsAllTheSameLocale; }
+
+ // Do not copy and assign.
+ LocaleList(const LocaleList&) = delete;
+ void operator=(const LocaleList&) = delete;
+};
+
+} // namespace minikin
+
+#endif // MINIKIN_LOCALE_LIST_H
diff --git a/libs/minikin/LocaleListCache.cpp b/libs/minikin/LocaleListCache.cpp
new file mode 100644
index 0000000..c191ea6
--- /dev/null
+++ b/libs/minikin/LocaleListCache.cpp
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "Minikin"
+
+#include "LocaleListCache.h"
+
+#include <unordered_set>
+
+#include <log/log.h>
+#include <unicode/uloc.h>
+
+#include "Locale.h"
+#include "MinikinInternal.h"
+
+namespace minikin {
+
+const uint32_t LocaleListCache::kEmptyListId;
+
+// Returns the text length of output.
+static size_t toLanguageTag(char* output, size_t outSize, const StringPiece& locale) {
+ output[0] = '\0';
+ if (locale.empty()) {
+ return 0;
+ }
+
+ std::string localeString = locale.toString(); // ICU only understands C-style string.
+
+ size_t outLength = 0;
+ UErrorCode uErr = U_ZERO_ERROR;
+ outLength = uloc_canonicalize(localeString.c_str(), output, outSize, &uErr);
+ if (U_FAILURE(uErr)) {
+ // unable to build a proper locale identifier
+ ALOGD("uloc_canonicalize(\"%s\") failed: %s", localeString.c_str(), u_errorName(uErr));
+ output[0] = '\0';
+ return 0;
+ }
+
+ // Preserve "und" and "und-****" since uloc_addLikelySubtags changes "und" to "en-Latn-US".
+ if (strncmp(output, "und", 3) == 0 &&
+ (outLength == 3 || (outLength == 8 && output[3] == '_'))) {
+ output[3] = '-'; // to be language tag.
+ return outLength;
+ }
+
+ char likelyChars[ULOC_FULLNAME_CAPACITY];
+ uErr = U_ZERO_ERROR;
+ uloc_addLikelySubtags(output, likelyChars, ULOC_FULLNAME_CAPACITY, &uErr);
+ if (U_FAILURE(uErr)) {
+ // unable to build a proper locale identifier
+ ALOGD("uloc_addLikelySubtags(\"%s\") failed: %s", output, u_errorName(uErr));
+ output[0] = '\0';
+ return 0;
+ }
+
+ uErr = U_ZERO_ERROR;
+ outLength = uloc_toLanguageTag(likelyChars, output, outSize, FALSE, &uErr);
+ if (U_FAILURE(uErr)) {
+ // unable to build a proper locale identifier
+ ALOGD("uloc_toLanguageTag(\"%s\") failed: %s", likelyChars, u_errorName(uErr));
+ output[0] = '\0';
+ return 0;
+ }
+ return outLength;
+}
+
+static std::vector<Locale> parseLocaleList(const std::string& input) {
+ std::vector<Locale> result;
+ char langTag[ULOC_FULLNAME_CAPACITY];
+ std::unordered_set<uint64_t> seen;
+
+ SplitIterator it(input, ',');
+ while (it.hasNext()) {
+ StringPiece localeStr = it.next();
+ size_t length = toLanguageTag(langTag, ULOC_FULLNAME_CAPACITY, localeStr);
+ Locale locale(StringPiece(langTag, length));
+ if (locale.isUnsupported()) {
+ continue;
+ }
+ const bool isNewLocale = seen.insert(locale.getIdentifier()).second;
+ if (!isNewLocale) {
+ continue;
+ }
+
+ result.push_back(locale);
+ if (result.size() >= FONT_LOCALE_LIMIT) {
+ break;
+ }
+ }
+ return result;
+}
+
+LocaleListCache::LocaleListCache() {
+ // Insert an empty locale list for mapping default locale list to kEmptyListId.
+ // The default locale list has only one Locale and it is the unsupported locale.
+ mLocaleLists.emplace_back();
+ mLocaleListLookupTable.insert(std::make_pair("", kEmptyListId));
+}
+
+uint32_t LocaleListCache::getIdInternal(const std::string& locales) {
+ std::lock_guard<std::mutex> lock(mMutex);
+ const auto& it = mLocaleListLookupTable.find(locales);
+ if (it != mLocaleListLookupTable.end()) {
+ return it->second;
+ }
+
+ // Given locale list is not in cache. Insert it and return newly assigned ID.
+ const uint32_t nextId = mLocaleLists.size();
+ LocaleList fontLocales(parseLocaleList(locales));
+ if (fontLocales.empty()) {
+ return kEmptyListId;
+ }
+ mLocaleLists.push_back(std::move(fontLocales));
+ mLocaleListLookupTable.insert(std::make_pair(locales, nextId));
+ return nextId;
+}
+
+const LocaleList& LocaleListCache::getByIdInternal(uint32_t id) {
+ std::lock_guard<std::mutex> lock(mMutex);
+ MINIKIN_ASSERT(id < mLocaleLists.size(), "Lookup by unknown locale list ID.");
+ return mLocaleLists[id];
+}
+
+} // namespace minikin
diff --git a/libs/minikin/LocaleListCache.h b/libs/minikin/LocaleListCache.h
new file mode 100644
index 0000000..61e3f81
--- /dev/null
+++ b/libs/minikin/LocaleListCache.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MINIKIN_LOCALE_LIST_CACHE_H
+#define MINIKIN_LOCALE_LIST_CACHE_H
+
+#include <mutex>
+#include <unordered_map>
+
+#include "minikin/Macros.h"
+
+#include "Locale.h"
+
+namespace minikin {
+
+class LocaleListCache {
+public:
+ // A special ID for the empty locale list.
+ // This value must be 0 since the empty locale list is inserted into mLocaleLists by
+ // default.
+ const static uint32_t kEmptyListId = 0;
+
+ // A special ID for the invalid locale list.
+ const static uint32_t kInvalidListId = (uint32_t)(-1);
+
+ // Returns the locale list ID for the given string representation of LocaleList.
+ // Caller should acquire a lock before calling the method.
+ static inline uint32_t getId(const std::string& locales) {
+ return getInstance().getIdInternal(locales);
+ }
+
+ // Caller should acquire a lock before calling the method.
+ static inline const LocaleList& getById(uint32_t id) {
+ return getInstance().getByIdInternal(id);
+ }
+
+private:
+ LocaleListCache(); // Singleton
+ ~LocaleListCache() {}
+
+ uint32_t getIdInternal(const std::string& locales);
+ const LocaleList& getByIdInternal(uint32_t id);
+
+ // Caller should acquire a lock before calling the method.
+ static LocaleListCache& getInstance() {
+ static LocaleListCache instance;
+ return instance;
+ }
+
+ std::vector<LocaleList> mLocaleLists GUARDED_BY(mMutex);
+
+ // A map from the string representation of the font locale list to the ID.
+ std::unordered_map<std::string, uint32_t> mLocaleListLookupTable GUARDED_BY(mMutex);
+
+ std::mutex mMutex;
+};
+
+} // namespace minikin
+
+#endif // MINIKIN_LOCALE_LIST_CACHE_H
diff --git a/libs/minikin/MeasuredText.cpp b/libs/minikin/MeasuredText.cpp
new file mode 100644
index 0000000..bbc6091
--- /dev/null
+++ b/libs/minikin/MeasuredText.cpp
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "Minikin"
+#include "minikin/MeasuredText.h"
+
+#include "minikin/Layout.h"
+
+#include "LayoutUtils.h"
+#include "LineBreakerUtil.h"
+
+namespace minikin {
+
+void MeasuredText::measure(const U16StringPiece& textBuf, bool computeHyphenation,
+ bool computeLayout) {
+ if (textBuf.size() == 0) {
+ return;
+ }
+ LayoutPieces* piecesOut = computeLayout ? &layoutPieces : nullptr;
+ CharProcessor proc(textBuf);
+ for (const auto& run : runs) {
+ const Range& range = run->getRange();
+ const uint32_t runOffset = range.getStart();
+ run->getMetrics(textBuf, widths.data() + runOffset, extents.data() + runOffset, piecesOut);
+
+ if (!computeHyphenation || !run->canHyphenate()) {
+ continue;
+ }
+
+ proc.updateLocaleIfNecessary(*run);
+ for (uint32_t i = range.getStart(); i < range.getEnd(); ++i) {
+ proc.feedChar(i, textBuf[i], widths[i]);
+
+ const uint32_t nextCharOffset = i + 1;
+ if (nextCharOffset != proc.nextWordBreak) {
+ continue; // Wait until word break point.
+ }
+
+ populateHyphenationPoints(textBuf, *run, *proc.hyphenator, proc.contextRange(),
+ proc.wordRange(), &hyphenBreaks, piecesOut);
+ }
+ }
+}
+
+void MeasuredText::buildLayout(const U16StringPiece& textBuf, const Range& range,
+ const MinikinPaint& paint, Bidi bidiFlags,
+ StartHyphenEdit startHyphen, EndHyphenEdit endHyphen,
+ Layout* layout) {
+ layout->doLayoutWithPrecomputedPieces(textBuf, range, bidiFlags, paint, startHyphen, endHyphen,
+ layoutPieces);
+}
+
+MinikinRect MeasuredText::getBounds(const U16StringPiece& textBuf, const Range& range) {
+ MinikinRect rect;
+ float advance = 0.0f;
+ for (const auto& run : runs) {
+ const Range& runRange = run->getRange();
+ if (!Range::intersects(range, runRange)) {
+ continue;
+ }
+ std::pair<float, MinikinRect> next =
+ run->getBounds(textBuf, Range::intersection(runRange, range), layoutPieces);
+ MinikinRect nextRect = next.second;
+ nextRect.offset(advance, 0);
+ rect.join(nextRect);
+ advance += next.first;
+ }
+ return rect;
+}
+
+} // namespace minikin
diff --git a/libs/minikin/Measurement.cpp b/libs/minikin/Measurement.cpp
index f0d15f2..5d110c4 100644
--- a/libs/minikin/Measurement.cpp
+++ b/libs/minikin/Measurement.cpp
@@ -14,15 +14,12 @@
* limitations under the License.
*/
-#define LOG_TAG "Minikin"
+#include "minikin/Measurement.h"
+#include <cfloat>
#include <cmath>
-#include <unicode/uchar.h>
-#include <android/log.h>
-
-#include <minikin/GraphemeBreak.h>
-#include <minikin/Measurement.h>
+#include "minikin/GraphemeBreak.h"
namespace minikin {
@@ -30,7 +27,7 @@
// are separate.
static float getRunAdvance(const float* advances, const uint16_t* buf, size_t layoutStart,
- size_t start, size_t count, size_t offset) {
+ size_t start, size_t count, size_t offset) {
float advance = 0.0f;
size_t lastCluster = start;
float clusterWidth = 0.0f;
@@ -54,8 +51,8 @@
int numGraphemeClustersAfter = 0;
for (size_t i = lastCluster; i < nextCluster; i++) {
bool isAfter = i >= offset;
- if (GraphemeBreak::isGraphemeBreak(
- advances + (start - layoutStart), buf, start, count, i)) {
+ if (GraphemeBreak::isGraphemeBreak(advances + (start - layoutStart), buf, start, count,
+ i)) {
numGraphemeClusters++;
if (isAfter) {
numGraphemeClustersAfter++;
@@ -70,7 +67,7 @@
}
float getRunAdvance(const float* advances, const uint16_t* buf, size_t start, size_t count,
- size_t offset) {
+ size_t offset) {
return getRunAdvance(advances, buf, start, start, count, offset);
}
@@ -83,7 +80,7 @@
* search within the cluster and grapheme breaks.
*/
size_t getOffsetForAdvance(const float* advances, const uint16_t* buf, size_t start, size_t count,
- float advance) {
+ float advance) {
float x = 0.0f, xLastClusterStart = 0.0f, xSearchStart = 0.0f;
size_t lastClusterStart = start, searchStart = start;
for (size_t i = start; i < start + count; i++) {
@@ -108,7 +105,7 @@
// "getRunAdvance(layout, buf, start, count, i) - advance" but more efficient
float delta = getRunAdvance(advances, buf, start, searchStart, count - searchStart, i)
- + xSearchStart - advance;
+ + xSearchStart - advance;
if (std::abs(delta) < bestDist) {
bestDist = std::abs(delta);
best = i;
diff --git a/libs/minikin/MinikinInternal.cpp b/libs/minikin/MinikinInternal.cpp
index cfa43bc..d02f71f 100644
--- a/libs/minikin/MinikinInternal.cpp
+++ b/libs/minikin/MinikinInternal.cpp
@@ -13,34 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
// Definitions internal to Minikin
-#define LOG_TAG "Minikin"
-#include "MinikinInternal.h"
-#include "HbFontCache.h"
+#define LOG_TAG "Minikin"
#include <log/log.h>
+#include "MinikinInternal.h"
+
namespace minikin {
-android::Mutex gMinikinLock;
-
-void assertMinikinLocked() {
-#ifdef ENABLE_RACE_DETECTION
- LOG_ALWAYS_FATAL_IF(gMinikinLock.tryLock() == 0);
-#endif
-}
-
-hb_blob_t* getFontTable(const MinikinFont* minikinFont, uint32_t tag) {
- assertMinikinLocked();
- hb_font_t* font = getHbFontLocked(minikinFont);
- hb_face_t* face = hb_font_get_face(font);
- hb_blob_t* blob = hb_face_reference_table(face, tag);
- hb_font_destroy(font);
- return blob;
-}
-
inline static bool isBMPVariationSelector(uint32_t codePoint) {
return VS1 <= codePoint && codePoint <= VS16;
}
diff --git a/libs/minikin/MinikinInternal.h b/libs/minikin/MinikinInternal.h
index a59e55d..d90f099 100644
--- a/libs/minikin/MinikinInternal.h
+++ b/libs/minikin/MinikinInternal.h
@@ -20,23 +20,21 @@
#define MINIKIN_INTERNAL_H
#include <hb.h>
-
+#include <utils/Log.h>
#include <utils/Mutex.h>
-#include <minikin/MinikinFont.h>
+#include "minikin/HbUtils.h"
+#include "minikin/MinikinFont.h"
namespace minikin {
-// All external Minikin interfaces are designed to be thread-safe.
-// Presently, that's implemented by through a global lock, and having
-// all external interfaces take that lock.
+#ifdef ENABLE_ASSERTION
+#define MINIKIN_ASSERT(cond, ...) LOG_ALWAYS_FATAL_IF(!(cond), __VA_ARGS__)
+#else
+#define MINIKIN_ASSERT(cond, ...) ((void)0)
+#endif
-extern android::Mutex gMinikinLock;
-
-// Aborts if gMinikinLock is not acquired. Do nothing on the release build.
-void assertMinikinLocked();
-
-hb_blob_t* getFontTable(const MinikinFont* minikinFont, uint32_t tag);
+#define MINIKIN_NOT_REACHED(...) MINIKIN_ASSERT(false, __VA_ARGS__);
constexpr uint32_t MAX_UNICODE_CODE_POINT = 0x10FFFF;
@@ -57,29 +55,24 @@
// Note that this function returns false for Mongolian free variation selectors.
bool isVariationSelector(uint32_t codePoint);
-// An RAII wrapper for hb_blob_t
+// An RAII accessor for hb_blob_t
class HbBlob {
public:
- // Takes ownership of hb_blob_t object, caller is no longer
- // responsible for calling hb_blob_destroy().
- explicit HbBlob(hb_blob_t* blob) : mBlob(blob) {
+ HbBlob(const HbFaceUniquePtr& face, uint32_t tag)
+ : mBlob(hb_face_reference_table(face.get(), tag)) {}
+ HbBlob(const HbFontUniquePtr& font, uint32_t tag)
+ : mBlob(hb_face_reference_table(hb_font_get_face(font.get()), tag)) {}
+
+ inline const uint8_t* get() const {
+ return reinterpret_cast<const uint8_t*>(hb_blob_get_data(mBlob.get(), nullptr));
}
- ~HbBlob() {
- hb_blob_destroy(mBlob);
- }
+ inline size_t size() const { return (size_t)hb_blob_get_length(mBlob.get()); }
- const uint8_t* get() const {
- const char* data = hb_blob_get_data(mBlob, nullptr);
- return reinterpret_cast<const uint8_t*>(data);
- }
-
- size_t size() const {
- return (size_t)hb_blob_get_length(mBlob);
- }
+ inline operator bool() const { return size() > 0; }
private:
- hb_blob_t* mBlob;
+ HbBlobUniquePtr mBlob;
};
} // namespace minikin
diff --git a/libs/minikin/OptimalLineBreaker.cpp b/libs/minikin/OptimalLineBreaker.cpp
new file mode 100644
index 0000000..5b3a6fc
--- /dev/null
+++ b/libs/minikin/OptimalLineBreaker.cpp
@@ -0,0 +1,432 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "OptimalLineBreaker.h"
+
+#include <algorithm>
+#include <limits>
+
+#include "minikin/Characters.h"
+#include "minikin/Layout.h"
+#include "minikin/Range.h"
+#include "minikin/U16StringPiece.h"
+
+#include "HyphenatorMap.h"
+#include "LayoutUtils.h"
+#include "LineBreakerUtil.h"
+#include "Locale.h"
+#include "LocaleListCache.h"
+#include "MinikinInternal.h"
+#include "WordBreaker.h"
+
+namespace minikin {
+
+namespace {
+
+// Large scores in a hierarchy; we prefer desperate breaks to an overfull line. All these
+// constants are larger than any reasonable actual width score.
+constexpr float SCORE_INFTY = std::numeric_limits<float>::max();
+constexpr float SCORE_OVERFULL = 1e12f;
+constexpr float SCORE_DESPERATE = 1e10f;
+
+// Multiplier for hyphen penalty on last line.
+constexpr float LAST_LINE_PENALTY_MULTIPLIER = 4.0f;
+// Penalty assigned to each line break (to try to minimize number of lines)
+// TODO: when we implement full justification (so spaces can shrink and stretch), this is
+// probably not the most appropriate method.
+constexpr float LINE_PENALTY_MULTIPLIER = 2.0f;
+
+// Penalty assigned to shrinking the whitepsace.
+constexpr float SHRINK_PENALTY_MULTIPLIER = 4.0f;
+
+// Maximum amount that spaces can shrink, in justified text.
+constexpr float SHRINKABILITY = 1.0 / 3.0;
+
+// A single candidate break
+struct Candidate {
+ uint32_t offset; // offset to text buffer, in code units
+
+ ParaWidth preBreak; // width of text until this point, if we decide to not break here:
+ // preBreak is used as an optimized way to calculate the width
+ // between two candidates. The line width between two line break
+ // candidates i and j is calculated as postBreak(j) - preBreak(i).
+ ParaWidth postBreak; // width of text until this point, if we decide to break here
+ float penalty; // penalty of this break (for example, hyphen penalty)
+ uint32_t preSpaceCount; // preceding space count before breaking
+ uint32_t postSpaceCount; // preceding space count after breaking
+ HyphenationType hyphenType;
+ bool isRtl; // The direction of the bidi run containing or ending in this candidate
+
+ Candidate(uint32_t offset, ParaWidth preBreak, ParaWidth postBreak, float penalty,
+ uint32_t preSpaceCount, uint32_t postSpaceCount, HyphenationType hyphenType,
+ bool isRtl)
+ : offset(offset),
+ preBreak(preBreak),
+ postBreak(postBreak),
+ penalty(penalty),
+ preSpaceCount(preSpaceCount),
+ postSpaceCount(postSpaceCount),
+ hyphenType(hyphenType),
+ isRtl(isRtl) {}
+};
+
+// A context of line break optimization.
+struct OptimizeContext {
+ // The break candidates.
+ std::vector<Candidate> candidates;
+
+ // The penalty for the number of lines.
+ float linePenalty = 0.0f;
+
+ // The width of a space. May be 0 if there are no spaces.
+ // Note: if there are multiple different widths for spaces (for example, because of mixing of
+ // fonts), it's only guaranteed to pick one.
+ float spaceWidth = 0.0f;
+
+ // Append desperate break point to the candidates.
+ inline void pushDesperate(uint32_t offset, ParaWidth sumOfCharWidths, uint32_t spaceCount,
+ bool isRtl) {
+ candidates.emplace_back(offset, sumOfCharWidths, sumOfCharWidths, SCORE_DESPERATE,
+ spaceCount, spaceCount,
+ HyphenationType::BREAK_AND_DONT_INSERT_HYPHEN, isRtl);
+ }
+
+ // Append hyphenation break point to the candidates.
+ inline void pushHyphenation(uint32_t offset, ParaWidth preBreak, ParaWidth postBreak,
+ float penalty, uint32_t spaceCount, HyphenationType type,
+ bool isRtl) {
+ candidates.emplace_back(offset, preBreak, postBreak, penalty, spaceCount, spaceCount, type,
+ isRtl);
+ }
+
+ // Append word break point to the candidates.
+ inline void pushWordBreak(uint32_t offset, ParaWidth preBreak, ParaWidth postBreak,
+ float penalty, uint32_t preSpaceCount, uint32_t postSpaceCount,
+ bool isRtl) {
+ candidates.emplace_back(offset, preBreak, postBreak, penalty, preSpaceCount, postSpaceCount,
+ HyphenationType::DONT_BREAK, isRtl);
+ }
+
+ OptimizeContext() {
+ candidates.emplace_back(0, 0.0f, 0.0f, 0.0f, 0, 0, HyphenationType::DONT_BREAK, false);
+ }
+};
+
+// Compute the penalty for the run and returns penalty for hyphenation and number of lines.
+std::pair<float, float> computePenalties(const Run& run, const LineWidth& lineWidth,
+ HyphenationFrequency frequency, bool justified) {
+ float linePenalty = 0.0;
+ const MinikinPaint* paint = run.getPaint();
+ // a heuristic that seems to perform well
+ float hyphenPenalty = 0.5 * paint->size * paint->scaleX * lineWidth.getAt(0);
+ if (frequency == HyphenationFrequency::Normal) {
+ hyphenPenalty *= 4.0; // TODO: Replace with a better value after some testing
+ }
+
+ if (justified) {
+ // Make hyphenation more aggressive for fully justified text (so that "normal" in
+ // justified mode is the same as "full" in ragged-right).
+ hyphenPenalty *= 0.25;
+ } else {
+ // Line penalty is zero for justified text.
+ linePenalty = hyphenPenalty * LINE_PENALTY_MULTIPLIER;
+ }
+
+ return std::make_pair(hyphenPenalty, linePenalty);
+}
+
+// Represents a desperate break point.
+struct DesperateBreak {
+ // The break offset.
+ uint32_t offset;
+
+ // The sum of the character width from the beginning of the word.
+ ParaWidth sumOfChars;
+
+ DesperateBreak(uint32_t offset, ParaWidth sumOfChars)
+ : offset(offset), sumOfChars(sumOfChars){};
+};
+
+// Retrieves desperate break points from a word.
+std::vector<DesperateBreak> populateDesperatePoints(const MeasuredText& measured,
+ const Range& range) {
+ std::vector<DesperateBreak> out;
+ ParaWidth width = measured.widths[range.getStart()];
+ for (uint32_t i = range.getStart() + 1; i < range.getEnd(); ++i) {
+ const float w = measured.widths[i];
+ if (w == 0) {
+ continue; // w == 0 means here is not a grapheme bounds. Don't break here.
+ }
+ out.emplace_back(i, width);
+ width += w;
+ }
+ return out;
+}
+
+// Append hyphenation break points and desperate break points.
+// If an offset is a both candidate for hyphenation and desperate break points, place desperate
+// break candidate first and hyphenation break points second since the result width of the desperate
+// break is shorter than hyphenation break.
+// This is important since DP in computeBreaksOptimal assumes that the result line width is
+// increased by break offset.
+void appendWithMerging(std::vector<HyphenBreak>::const_iterator hyIter,
+ std::vector<HyphenBreak>::const_iterator endHyIter,
+ const std::vector<DesperateBreak>& desperates, const CharProcessor& proc,
+ float hyphenPenalty, bool isRtl, OptimizeContext* out) {
+ auto d = desperates.begin();
+ while (hyIter != endHyIter || d != desperates.end()) {
+ // If both hyphen breaks and desperate breaks point to the same offset, push desperate
+ // breaks first.
+ if (d != desperates.end() && (hyIter == endHyIter || d->offset <= hyIter->offset)) {
+ out->pushDesperate(d->offset, proc.sumOfCharWidthsAtPrevWordBreak + d->sumOfChars,
+ proc.effectiveSpaceCount, isRtl);
+ d++;
+ } else {
+ out->pushHyphenation(hyIter->offset, proc.sumOfCharWidths - hyIter->second,
+ proc.sumOfCharWidthsAtPrevWordBreak + hyIter->first, hyphenPenalty,
+ proc.effectiveSpaceCount, hyIter->type, isRtl);
+ hyIter++;
+ }
+ }
+}
+
+// Enumerate all line break candidates.
+OptimizeContext populateCandidates(const U16StringPiece& textBuf, const MeasuredText& measured,
+ const LineWidth& lineWidth, HyphenationFrequency frequency,
+ bool isJustified) {
+ const ParaWidth minLineWidth = lineWidth.getMin();
+ CharProcessor proc(textBuf);
+
+ OptimizeContext result;
+
+ const bool doHyphenation = frequency != HyphenationFrequency::None;
+ auto hyIter = std::begin(measured.hyphenBreaks);
+
+ for (const auto& run : measured.runs) {
+ const bool isRtl = run->isRtl();
+ const Range& range = run->getRange();
+
+ // Compute penalty parameters.
+ float hyphenPenalty = 0.0f;
+ if (run->canHyphenate()) {
+ auto penalties = computePenalties(*run, lineWidth, frequency, isJustified);
+ hyphenPenalty = penalties.first;
+ result.linePenalty = std::max(penalties.second, result.linePenalty);
+ }
+
+ proc.updateLocaleIfNecessary(*run);
+
+ for (uint32_t i = range.getStart(); i < range.getEnd(); ++i) {
+ MINIKIN_ASSERT(textBuf[i] != CHAR_TAB, "TAB is not supported in optimal line breaker");
+ proc.feedChar(i, textBuf[i], measured.widths[i]);
+
+ const uint32_t nextCharOffset = i + 1;
+ if (nextCharOffset != proc.nextWordBreak) {
+ continue; // Wait until word break point.
+ }
+
+ // Add hyphenation and desperate break points.
+ std::vector<HyphenBreak> hyphenedBreaks;
+ std::vector<DesperateBreak> desperateBreaks;
+ const Range contextRange = proc.contextRange();
+
+ auto beginHyIter = hyIter;
+ while (hyIter != std::end(measured.hyphenBreaks) &&
+ hyIter->offset < contextRange.getEnd()) {
+ hyIter++;
+ }
+ if (proc.widthFromLastWordBreak() > minLineWidth) {
+ desperateBreaks = populateDesperatePoints(measured, contextRange);
+ }
+ appendWithMerging(beginHyIter, doHyphenation ? hyIter : beginHyIter, desperateBreaks,
+ proc, hyphenPenalty, isRtl, &result);
+
+ // We skip breaks for zero-width characters inside replacement spans.
+ if (nextCharOffset == range.getEnd() || measured.widths[nextCharOffset] > 0) {
+ const float penalty = hyphenPenalty * proc.wordBreakPenalty();
+ result.pushWordBreak(nextCharOffset, proc.sumOfCharWidths, proc.effectiveWidth,
+ penalty, proc.rawSpaceCount, proc.effectiveSpaceCount, isRtl);
+ }
+ }
+ }
+ result.spaceWidth = proc.spaceWidth;
+ return result;
+}
+
+class LineBreakOptimizer {
+public:
+ LineBreakOptimizer() {}
+
+ LineBreakResult computeBreaks(const OptimizeContext& context, const U16StringPiece& textBuf,
+ const MeasuredText& measuredText, const LineWidth& lineWidth,
+ BreakStrategy strategy, bool justified);
+
+private:
+ // Data used to compute optimal line breaks
+ struct OptimalBreaksData {
+ float score; // best score found for this break
+ uint32_t prev; // index to previous break
+ uint32_t lineNumber; // the computed line number of the candidate
+ };
+ LineBreakResult finishBreaksOptimal(const U16StringPiece& textBuf, const MeasuredText& measured,
+ const std::vector<OptimalBreaksData>& breaksData,
+ const std::vector<Candidate>& candidates);
+
+ MinikinExtent computeMaxExtent(const U16StringPiece& textBuf, const MeasuredText& measured,
+ uint32_t start, uint32_t end) const;
+};
+
+// Find the needed extent between the start and end ranges. start is inclusive and end is exclusive.
+// Both are indices of the source string.
+MinikinExtent LineBreakOptimizer::computeMaxExtent(const U16StringPiece& textBuf,
+ const MeasuredText& measured, uint32_t start,
+ uint32_t end) const {
+ MinikinExtent res = {0.0, 0.0, 0.0};
+ for (uint32_t j = start; j < end; j++) {
+ if (!isLineSpaceExcludeChar(textBuf[j])) {
+ res.extendBy(measured.extents[j]);
+ }
+ }
+ return res;
+}
+
+// Follow "prev" links in candidates array, and copy to result arrays.
+LineBreakResult LineBreakOptimizer::finishBreaksOptimal(
+ const U16StringPiece& textBuf, const MeasuredText& measured,
+ const std::vector<OptimalBreaksData>& breaksData,
+ const std::vector<Candidate>& candidates) {
+ LineBreakResult result;
+ const uint32_t nCand = candidates.size();
+ uint32_t prevIndex;
+ for (uint32_t i = nCand - 1; i > 0; i = prevIndex) {
+ prevIndex = breaksData[i].prev;
+ const Candidate& cand = candidates[i];
+ const Candidate& prev = candidates[prevIndex];
+
+ result.breakPoints.push_back(cand.offset);
+ result.widths.push_back(cand.postBreak - prev.preBreak);
+ MinikinExtent extent = computeMaxExtent(textBuf, measured, prev.offset, cand.offset);
+ result.ascents.push_back(extent.ascent);
+ result.descents.push_back(extent.descent);
+
+ const HyphenEdit edit =
+ packHyphenEdit(editForNextLine(prev.hyphenType), editForThisLine(cand.hyphenType));
+ result.flags.push_back(static_cast<int>(edit));
+ }
+ result.reverse();
+ return result;
+}
+
+LineBreakResult LineBreakOptimizer::computeBreaks(const OptimizeContext& context,
+ const U16StringPiece& textBuf,
+ const MeasuredText& measured,
+ const LineWidth& lineWidth,
+ BreakStrategy strategy, bool justified) {
+ const std::vector<Candidate>& candidates = context.candidates;
+ uint32_t active = 0;
+ const uint32_t nCand = candidates.size();
+ const float maxShrink = justified ? SHRINKABILITY * context.spaceWidth : 0.0f;
+
+ std::vector<OptimalBreaksData> breaksData;
+ breaksData.reserve(nCand);
+ breaksData.push_back({0.0, 0, 0}); // The first candidate is always at the first line.
+
+ // "i" iterates through candidates for the end of the line.
+ for (uint32_t i = 1; i < nCand; i++) {
+ const bool atEnd = i == nCand - 1;
+ float best = SCORE_INFTY;
+ uint32_t bestPrev = 0;
+
+ uint32_t lineNumberLast = breaksData[active].lineNumber;
+ float width = lineWidth.getAt(lineNumberLast);
+
+ ParaWidth leftEdge = candidates[i].postBreak - width;
+ float bestHope = 0;
+
+ // "j" iterates through candidates for the beginning of the line.
+ for (uint32_t j = active; j < i; j++) {
+ const uint32_t lineNumber = breaksData[j].lineNumber;
+ if (lineNumber != lineNumberLast) {
+ const float widthNew = lineWidth.getAt(lineNumber);
+ if (widthNew != width) {
+ leftEdge = candidates[i].postBreak - width;
+ bestHope = 0;
+ width = widthNew;
+ }
+ lineNumberLast = lineNumber;
+ }
+ const float jScore = breaksData[j].score;
+ if (jScore + bestHope >= best) continue;
+ const float delta = candidates[j].preBreak - leftEdge;
+
+ // compute width score for line
+
+ // Note: the "bestHope" optimization makes the assumption that, when delta is
+ // non-negative, widthScore will increase monotonically as successive candidate
+ // breaks are considered.
+ float widthScore = 0.0f;
+ float additionalPenalty = 0.0f;
+ if ((atEnd || !justified) && delta < 0) {
+ widthScore = SCORE_OVERFULL;
+ } else if (atEnd && strategy != BreakStrategy::Balanced) {
+ // increase penalty for hyphen on last line
+ additionalPenalty = LAST_LINE_PENALTY_MULTIPLIER * candidates[j].penalty;
+ } else {
+ widthScore = delta * delta;
+ if (delta < 0) {
+ if (-delta <
+ maxShrink * (candidates[i].postSpaceCount - candidates[j].preSpaceCount)) {
+ widthScore *= SHRINK_PENALTY_MULTIPLIER;
+ } else {
+ widthScore = SCORE_OVERFULL;
+ }
+ }
+ }
+
+ if (delta < 0) {
+ active = j + 1;
+ } else {
+ bestHope = widthScore;
+ }
+
+ const float score = jScore + widthScore + additionalPenalty;
+ if (score <= best) {
+ best = score;
+ bestPrev = j;
+ }
+ }
+ breaksData.push_back({best + candidates[i].penalty + context.linePenalty, // score
+ bestPrev, // prev
+ breaksData[bestPrev].lineNumber + 1}); // lineNumber
+ }
+ return finishBreaksOptimal(textBuf, measured, breaksData, candidates);
+}
+
+} // namespace
+
+LineBreakResult breakLineOptimal(const U16StringPiece& textBuf, const MeasuredText& measured,
+ const LineWidth& lineWidth, BreakStrategy strategy,
+ HyphenationFrequency frequency, bool justified) {
+ if (textBuf.size() == 0) {
+ return LineBreakResult();
+ }
+ const OptimizeContext context =
+ populateCandidates(textBuf, measured, lineWidth, frequency, justified);
+ LineBreakOptimizer optimizer;
+ return optimizer.computeBreaks(context, textBuf, measured, lineWidth, strategy, justified);
+}
+
+} // namespace minikin
diff --git a/libs/minikin/OptimalLineBreaker.h b/libs/minikin/OptimalLineBreaker.h
new file mode 100644
index 0000000..f13e2a8
--- /dev/null
+++ b/libs/minikin/OptimalLineBreaker.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MINIKIN_OPTIMAL_LINE_BREAKER_H
+#define MINIKIN_OPTIMAL_LINE_BREAKER_H
+
+#include "minikin/LineBreaker.h"
+#include "minikin/MeasuredText.h"
+#include "minikin/U16StringPiece.h"
+
+namespace minikin {
+
+LineBreakResult breakLineOptimal(const U16StringPiece& textBuf, const MeasuredText& measured,
+ const LineWidth& lineWidthLimits, BreakStrategy strategy,
+ HyphenationFrequency frequency, bool justified);
+
+} // namespace minikin
+
+#endif // MINIKIN_OPTIMAL_LINE_BREAKER_H
diff --git a/libs/minikin/SparseBitSet.cpp b/libs/minikin/SparseBitSet.cpp
index 9fad6a0..66d6c02 100644
--- a/libs/minikin/SparseBitSet.cpp
+++ b/libs/minikin/SparseBitSet.cpp
@@ -14,14 +14,9 @@
* limitations under the License.
*/
-#define LOG_TAG "SparseBitSet"
+#include "minikin/SparseBitSet.h"
-#include <stddef.h>
-#include <string.h>
-
-#include <log/log.h>
-
-#include <minikin/SparseBitSet.h>
+#include "MinikinInternal.h"
namespace minikin {
@@ -69,7 +64,7 @@
for (size_t i = 0; i < nRanges; i++) {
uint32_t start = ranges[i * 2];
uint32_t end = ranges[i * 2 + 1];
- LOG_ALWAYS_FATAL_IF(end < start); // make sure range size is nonnegative
+ MINIKIN_ASSERT(start <= end, "Range size must be nonnegative");
uint32_t startPage = start >> kLogValuesPerPage;
uint32_t endPage = (end - 1) >> kLogValuesPerPage;
if (startPage >= nonzeroPageEnd) {
@@ -85,11 +80,11 @@
}
size_t index = ((currentPage - 1) << (kLogValuesPerPage - kLogBitsPerEl)) +
- ((start & kPageMask) >> kLogBitsPerEl);
+ ((start & kPageMask) >> kLogBitsPerEl);
size_t nElements = (end - (start & ~kElMask) + kElMask) >> kLogBitsPerEl;
if (nElements == 1) {
- mBitmaps[index] |= (kElAllOnes >> (start & kElMask)) &
- (kElAllOnes << ((~end + 1) & kElMask));
+ mBitmaps[index] |=
+ (kElAllOnes >> (start & kElMask)) & (kElAllOnes << ((~end + 1) & kElMask));
} else {
mBitmaps[index] |= kElAllOnes >> (start & kElMask);
for (size_t j = 1; j < nElements - 1; j++) {
diff --git a/libs/minikin/StringPiece.h b/libs/minikin/StringPiece.h
new file mode 100644
index 0000000..befb312
--- /dev/null
+++ b/libs/minikin/StringPiece.h
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MINIKIN_STRING_PIECE_H
+#define MINIKIN_STRING_PIECE_H
+
+#include <cstdint>
+#include <string>
+#include <vector>
+
+namespace minikin {
+
+class StringPiece {
+public:
+ StringPiece() : mData(nullptr), mLength(0) {}
+ StringPiece(const char* data) : mData(data), mLength(data == nullptr ? 0 : strlen(data)) {}
+ StringPiece(const char* data, size_t length) : mData(data), mLength(length) {}
+ StringPiece(const std::string& str) : mData(str.data()), mLength(str.size()) {}
+
+ inline const char* data() const { return mData; }
+ inline size_t length() const { return mLength; }
+ inline size_t size() const { return mLength; }
+ inline bool empty() const { return mLength == 0; }
+
+ inline char operator[](size_t i) const { return mData[i]; }
+
+ inline StringPiece substr(size_t from, size_t length) const {
+ return StringPiece(mData + from, length);
+ }
+
+ inline size_t find(size_t from, char c) const {
+ if (from >= mLength) {
+ return mLength;
+ }
+ const char* p = static_cast<const char*>(memchr(mData + from, c, mLength - from));
+ return p == nullptr ? mLength : p - mData;
+ }
+
+ std::string toString() const { return std::string(mData, mData + mLength); }
+
+private:
+ const char* mData;
+ size_t mLength;
+};
+
+inline bool operator==(const StringPiece& l, const StringPiece& r) {
+ const size_t len = l.size();
+ if (len != r.size()) {
+ return false;
+ }
+ const char* lData = l.data();
+ const char* rData = r.data();
+ if (lData == rData) {
+ return true;
+ }
+ return memcmp(lData, rData, len) == 0;
+}
+
+inline bool operator==(const StringPiece& l, const char* s) {
+ const size_t len = l.size();
+ if (len != strlen(s)) {
+ return false;
+ }
+ return memcmp(l.data(), s, len) == 0;
+}
+
+inline bool operator!=(const StringPiece& l, const StringPiece& r) {
+ return !(l == r);
+}
+
+inline bool operator!=(const StringPiece& l, const char* s) {
+ return !(l == s);
+}
+
+class SplitIterator {
+public:
+ SplitIterator(const StringPiece& string, char delimiter)
+ : mStarted(false), mCurrent(0), mString(string), mDelimiter(delimiter) {}
+
+ inline StringPiece next() {
+ if (!hasNext()) {
+ return StringPiece();
+ }
+ const size_t searchFrom = mStarted ? mCurrent + 1 : 0;
+ mStarted = true;
+ mCurrent = mString.find(searchFrom, mDelimiter);
+ return mString.substr(searchFrom, mCurrent - searchFrom);
+ }
+ inline bool hasNext() const { return mCurrent < mString.size(); }
+
+private:
+ bool mStarted;
+ size_t mCurrent;
+ StringPiece mString;
+ char mDelimiter;
+};
+
+} // namespace minikin
+
+#endif // MINIKIN_STRING_PIECE_H
diff --git a/libs/minikin/WordBreaker.cpp b/libs/minikin/WordBreaker.cpp
index 74c1138..dae5cab 100644
--- a/libs/minikin/WordBreaker.cpp
+++ b/libs/minikin/WordBreaker.cpp
@@ -14,45 +14,95 @@
* limitations under the License.
*/
-#define LOG_TAG "Minikin"
+#include "WordBreaker.h"
-#include <android/log.h>
-
-#include <minikin/Emoji.h>
-#include <minikin/Hyphenator.h>
-#include <minikin/WordBreaker.h>
-#include "MinikinInternal.h"
+#include <list>
+#include <map>
#include <unicode/uchar.h>
#include <unicode/utf16.h>
+#include "minikin/Emoji.h"
+#include "minikin/Hyphenator.h"
+
+#include "Locale.h"
+#include "MinikinInternal.h"
+
namespace minikin {
-const uint32_t CHAR_SOFT_HYPHEN = 0x00AD;
-const uint32_t CHAR_ZWJ = 0x200D;
-
-void WordBreaker::setLocale(const icu::Locale& locale) {
- UErrorCode status = U_ZERO_ERROR;
- mBreakIterator.reset(icu::BreakIterator::createLineInstance(locale, status));
+namespace {
+static icu::BreakIterator* createNewIterator(const Locale& locale) {
// TODO: handle failure status
- if (mText != nullptr) {
- mBreakIterator->setText(&mUText, status);
+ UErrorCode status = U_ZERO_ERROR;
+ return icu::BreakIterator::createLineInstance(
+ locale.isUnsupported() ? icu::Locale::getRoot()
+ : icu::Locale::createFromName(locale.getString().c_str()),
+ status);
+}
+} // namespace
+
+ICULineBreakerPool::Slot ICULineBreakerPoolImpl::acquire(const Locale& locale) {
+ const uint64_t id = locale.getIdentifier();
+ std::lock_guard<std::mutex> lock(mMutex);
+ for (auto i = mPool.begin(); i != mPool.end(); i++) {
+ if (i->localeId == id) {
+ Slot slot = std::move(*i);
+ mPool.erase(i);
+ return slot;
+ }
}
- mIteratorWasReset = true;
+
+ // Not found in pool. Create new one.
+ return {id, std::unique_ptr<icu::BreakIterator>(createNewIterator(locale))};
+}
+
+void ICULineBreakerPoolImpl::release(ICULineBreakerPool::Slot&& slot) {
+ if (slot.breaker.get() == nullptr) {
+ return; // Already released slot. Do nothing.
+ }
+ std::lock_guard<std::mutex> lock(mMutex);
+ if (mPool.size() >= MAX_POOL_SIZE) {
+ // Pool is full. Move to local variable, so that the given slot will be released when the
+ // variable leaves the scope.
+ Slot localSlot = std::move(slot);
+ return;
+ }
+ mPool.push_front(std::move(slot));
+}
+
+WordBreaker::WordBreaker() : mPool(&ICULineBreakerPoolImpl::getInstance()) {}
+
+WordBreaker::WordBreaker(ICULineBreakerPool* pool) : mPool(pool) {}
+
+ssize_t WordBreaker::followingWithLocale(const Locale& locale, size_t from) {
+ mIcuBreaker = mPool->acquire(locale);
+ UErrorCode status = U_ZERO_ERROR;
+ MINIKIN_ASSERT(mText != nullptr, "setText must be called first");
+ // TODO: handle failure status
+ mIcuBreaker.breaker->setText(&mUText, status);
+ if (mInEmailOrUrl) {
+ // Note:
+ // Don't reset mCurrent, mLast, or mScanOffset for keeping email/URL context.
+ // The email/URL detection doesn't support following() functionality, so that we can't
+ // restart from the specific position. This means following() can not be supported in
+ // general, but keeping old email/URL context works for LineBreaker since it just wants to
+ // re-calculate the next break point with the new locale.
+ } else {
+ mCurrent = mLast = mScanOffset = from;
+ next();
+ }
+ return mCurrent;
}
void WordBreaker::setText(const uint16_t* data, size_t size) {
mText = data;
mTextSize = size;
- mIteratorWasReset = false;
mLast = 0;
mCurrent = 0;
mScanOffset = 0;
mInEmailOrUrl = false;
UErrorCode status = U_ZERO_ERROR;
utext_openUChars(&mUText, reinterpret_cast<const UChar*>(data), size, &status);
- mBreakIterator->setText(&mUText, status);
- mBreakIterator->first();
}
ssize_t WordBreaker::current() const {
@@ -64,9 +114,14 @@
* represents customization beyond the ICU behavior, because plain ICU provides some
* line break opportunities that we don't want.
**/
-static bool isBreakValid(const uint16_t* buf, size_t bufEnd, size_t i) {
+static bool isValidBreak(const uint16_t* buf, size_t bufEnd, int32_t i) {
+ const size_t position = static_cast<size_t>(i);
+ if (i == icu::BreakIterator::DONE || position == bufEnd) {
+ // If the iterator reaches the end, treat as break.
+ return true;
+ }
uint32_t codePoint;
- size_t prev_offset = i;
+ size_t prev_offset = position;
U16_PREV(buf, 0, prev_offset, codePoint);
// Do not break on hard or soft hyphens. These are handled by automatic hyphenation.
if (Hyphenator::isLineBreakingHyphen(codePoint) || codePoint == CHAR_SOFT_HYPHEN) {
@@ -81,7 +136,7 @@
}
uint32_t next_codepoint;
- size_t next_offset = i;
+ size_t next_offset = position;
U16_NEXT(buf, next_offset, bufEnd, next_codepoint);
// Rule LB8 for Emoji ZWJ sequences. We need to do this ourselves since we may have fresher
@@ -106,16 +161,10 @@
// Customized iteratorNext that takes care of both resets and our modifications
// to ICU's behavior.
int32_t WordBreaker::iteratorNext() {
- int32_t result;
- do {
- if (mIteratorWasReset) {
- result = mBreakIterator->following(mCurrent);
- mIteratorWasReset = false;
- } else {
- result = mBreakIterator->next();
- }
- } while (!(result == icu::BreakIterator::DONE || (size_t)result == mTextSize
- || isBreakValid(mText, mTextSize, result)));
+ int32_t result = mIcuBreaker.breaker->following(mCurrent);
+ while (!isValidBreak(mText, mTextSize, result)) {
+ result = mIcuBreaker.breaker->next();
+ }
return result;
}
@@ -126,8 +175,8 @@
// Chicago Manual of Style recommends breaking before these characters in URLs and email addresses
static bool breakBefore(uint16_t c) {
- return c == '~' || c == '.' || c == ',' || c == '-' || c == '_' || c == '?' || c == '#'
- || c == '%' || c == '=' || c == '&';
+ return c == '~' || c == '.' || c == ',' || c == '-' || c == '_' || c == '?' || c == '#' ||
+ c == '%' || c == '=' || c == '&';
}
enum ScanState {
@@ -162,14 +211,13 @@
}
}
if (state == SAW_AT || state == SAW_COLON_SLASH_SLASH) {
- if (!mBreakIterator->isBoundary(i)) {
+ if (!mIcuBreaker.breaker->isBoundary(i)) {
// If there are combining marks or such at the end of the URL or the email address,
// consider them a part of the URL or the email, and skip to the next actual
// boundary.
- i = mBreakIterator->following(i);
+ i = mIcuBreaker.breaker->following(i);
}
mInEmailOrUrl = true;
- mIteratorWasReset = true;
} else {
mInEmailOrUrl = false;
}
@@ -197,7 +245,7 @@
}
// break before single slash
if (thisChar == '/' && lastChar != '/' &&
- !(i + 1 < mScanOffset && mText[i + 1] == '/')) {
+ !(i + 1 < mScanOffset && mText[i + 1] == '/')) {
break;
}
}
@@ -213,7 +261,7 @@
if (mInEmailOrUrl) {
mCurrent = findNextBreakInEmailOrUrl();
} else { // Business as usual
- mCurrent = (ssize_t) iteratorNext();
+ mCurrent = (ssize_t)iteratorNext();
}
return mCurrent;
}
@@ -248,8 +296,8 @@
ssize_t ix = result;
U16_PREV(mText, mLast, ix, c);
const int32_t gc_mask = U_GET_GC_MASK(c);
- // strip trailing space and punctuation
- if ((gc_mask & (U_GC_ZS_MASK | U_GC_P_MASK)) == 0) {
+ // strip trailing spaces, punctuation and control characters
+ if ((gc_mask & (U_GC_ZS_MASK | U_GC_P_MASK | U_GC_CC_MASK)) == 0) {
break;
}
result = ix;
@@ -265,6 +313,7 @@
mText = nullptr;
// Note: calling utext_close multiply is safe
utext_close(&mUText);
+ mPool->release(std::move(mIcuBreaker));
}
} // namespace minikin
diff --git a/libs/minikin/WordBreaker.h b/libs/minikin/WordBreaker.h
new file mode 100644
index 0000000..487c1fa
--- /dev/null
+++ b/libs/minikin/WordBreaker.h
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * A wrapper around ICU's line break iterator, that gives customized line
+ * break opportunities, as well as identifying words for the purpose of
+ * hyphenation.
+ */
+
+#ifndef MINIKIN_WORD_BREAKER_H
+#define MINIKIN_WORD_BREAKER_H
+
+#include <list>
+#include <mutex>
+
+#include <unicode/brkiter.h>
+
+#include "minikin/Macros.h"
+#include "minikin/Range.h"
+
+#include "Locale.h"
+
+namespace minikin {
+
+// A class interface for providing pooling implementation of ICU's line breaker.
+// The implementation can be customized for testing purposes.
+class ICULineBreakerPool {
+public:
+ struct Slot {
+ Slot() : localeId(0), breaker(nullptr) {}
+ Slot(uint64_t localeId, std::unique_ptr<icu::BreakIterator>&& breaker)
+ : localeId(localeId), breaker(std::move(breaker)) {}
+
+ Slot(Slot&& other) = default;
+ Slot& operator=(Slot&& other) = default;
+
+ // Forbid copy and assignment.
+ Slot(const Slot&) = delete;
+ Slot& operator=(const Slot&) = delete;
+
+ uint64_t localeId;
+ std::unique_ptr<icu::BreakIterator> breaker;
+ };
+ virtual ~ICULineBreakerPool() {}
+ virtual Slot acquire(const Locale& locale) = 0;
+ virtual void release(Slot&& slot) = 0;
+};
+
+// An singleton implementation of the ICU line breaker pool.
+// Since creating ICU line breaker instance takes some time. Pool it for later use.
+class ICULineBreakerPoolImpl : public ICULineBreakerPool {
+public:
+ Slot acquire(const Locale& locale) override;
+ void release(Slot&& slot) override;
+
+ static ICULineBreakerPoolImpl& getInstance() {
+ static ICULineBreakerPoolImpl pool;
+ return pool;
+ }
+
+protected:
+ // protected for testing purposes.
+ static constexpr size_t MAX_POOL_SIZE = 4;
+ ICULineBreakerPoolImpl(){}; // singleton.
+ size_t getPoolSize() const {
+ std::lock_guard<std::mutex> lock(mMutex);
+ return mPool.size();
+ }
+
+private:
+ std::list<Slot> mPool GUARDED_BY(mMutex);
+ mutable std::mutex mMutex;
+};
+
+class WordBreaker {
+public:
+ virtual ~WordBreaker() { finish(); }
+
+ WordBreaker();
+
+ void setText(const uint16_t* data, size_t size);
+
+ // Advance iterator to next word break with current locale. Return offset, or -1 if EOT
+ ssize_t next();
+
+ // Advance iterator to the break just after "from" with using the new provided locale.
+ // Return offset, or -1 if EOT
+ ssize_t followingWithLocale(const Locale& locale, size_t from);
+
+ // Current offset of iterator, equal to 0 at BOT or last return from next()
+ ssize_t current() const;
+
+ // After calling next(), wordStart() and wordEnd() are offsets defining the previous
+ // word. If wordEnd <= wordStart, it's not a word for the purpose of hyphenation.
+ ssize_t wordStart() const;
+
+ ssize_t wordEnd() const;
+
+ // Returns the range from wordStart() to wordEnd().
+ // If wordEnd() <= wordStart(), returns empty range.
+ inline Range wordRange() const {
+ const uint32_t start = wordStart();
+ const uint32_t end = wordEnd();
+ return start < end ? Range(start, end) : Range(end, end);
+ }
+
+ int breakBadness() const;
+
+ void finish();
+
+protected:
+ // protected virtual for testing purpose.
+ // Caller must release the pool.
+ WordBreaker(ICULineBreakerPool* pool);
+
+private:
+ int32_t iteratorNext();
+ void detectEmailOrUrl();
+ ssize_t findNextBreakInEmailOrUrl();
+
+ // Doesn't take ownership. Must not be nullptr. Must be set in constructor.
+ ICULineBreakerPool* mPool;
+
+ ICULineBreakerPool::Slot mIcuBreaker;
+
+ UText mUText = UTEXT_INITIALIZER;
+ const uint16_t* mText = nullptr;
+ size_t mTextSize;
+ ssize_t mLast;
+ ssize_t mCurrent;
+
+ // state for the email address / url detector
+ ssize_t mScanOffset;
+ bool mInEmailOrUrl;
+};
+
+} // namespace minikin
+
+#endif // MINIKIN_WORD_BREAKER_H
diff --git a/tests/Android.bp b/tests/Android.bp
index 887f057..9f6d534 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -2,6 +2,7 @@
name: "minikin-test-data",
srcs: [
"data/Arabic.ttf",
+ "data/Ascii.ttf",
"data/Bold.ttf",
"data/BoldItalic.ttf",
"data/Cherokee.ttf",
@@ -11,6 +12,7 @@
"data/Italic.ttf",
"data/Ja.ttf",
"data/Ko.ttf",
+ "data/LayoutTestFont.ttf",
"data/MultiAxis.ttf",
"data/NoCmapFormat14.ttf",
"data/NoGlyphFont.ttf",
diff --git a/tests/data/Arabic.ttf b/tests/data/Arabic.ttf
index faa1f3d..0c13030 100644
--- a/tests/data/Arabic.ttf
+++ b/tests/data/Arabic.ttf
Binary files differ
diff --git a/tests/data/Arabic.ttx b/tests/data/Arabic.ttx
index 778af33..1b8e966 100644
--- a/tests/data/Arabic.ttx
+++ b/tests/data/Arabic.ttx
@@ -18,8 +18,10 @@
<GlyphOrder>
<!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
<GlyphID id="0" name=".notdef"/>
- <GlyphID id="1" name="uni061C"/>
- <GlyphID id="2" name="uni200D"/>
+ <GlyphID id="1" name="uni000A"/>
+ <GlyphID id="2" name="uni000D"/>
+ <GlyphID id="3" name="uni061C"/>
+ <GlyphID id="4" name="uni200D"/>
</GlyphOrder>
<head>
@@ -66,7 +68,7 @@
<maxp>
<!-- Most of this table will be recalculated by the compiler -->
<tableVersion value="0x10000"/>
- <numGlyphs value="3"/>
+ <numGlyphs value="5"/>
<maxPoints value="0"/>
<maxContours value="0"/>
<maxCompositePoints value="0"/>
@@ -137,6 +139,8 @@
<hmtx>
<mtx name=".notdef" width="500" lsb="93"/>
+ <mtx name="uni000A" width="0" lsb="0"/>
+ <mtx name="uni000D" width="0" lsb="0"/>
<mtx name="uni061C" width="0" lsb="0"/>
<mtx name="uni200D" width="0" lsb="0"/>
</hmtx>
@@ -144,8 +148,10 @@
<cmap>
<tableVersion version="0"/>
<cmap_format_4 platformID="3" platEncID="10" language="0">
- <map code="0x61c" name="uni061C"/><!-- ARABIC LETTER MARK -->
- <map code="0x200d" name="uni200D"/><!-- ZERO WIDTH JOINER -->
+ <map code="0x000A" name="uni000A"/><!-- LINE FEED -->
+ <map code="0x000D" name="uni000D"/><!-- CARRIAGE RETURN -->
+ <map code="0x061C" name="uni061C"/><!-- ARABIC LETTER MARK -->
+ <map code="0x200D" name="uni200D"/><!-- ZERO WIDTH JOINER -->
</cmap_format_4>
</cmap>
@@ -160,6 +166,10 @@
<TTGlyph name=".notdef"/><!-- contains no outline data -->
+ <TTGlyph name="uni000A"/><!-- contains no outline data -->
+
+ <TTGlyph name="uni000D"/><!-- contains no outline data -->
+
<TTGlyph name="uni061C"/><!-- contains no outline data -->
<TTGlyph name="uni200D"/><!-- contains no outline data -->
diff --git a/tests/data/Ascii.ttf b/tests/data/Ascii.ttf
new file mode 100644
index 0000000..be6d5fe
--- /dev/null
+++ b/tests/data/Ascii.ttf
Binary files differ
diff --git a/tests/data/Ascii.ttx b/tests/data/Ascii.ttx
new file mode 100644
index 0000000..74eba96
--- /dev/null
+++ b/tests/data/Ascii.ttx
@@ -0,0 +1,279 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
+
+ <GlyphOrder>
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name="1em"/>
+ </GlyphOrder>
+
+ <head>
+ <tableVersion value="1.0"/>
+ <fontRevision value="1.0"/>
+ <checkSumAdjustment value="0x640cdb2f"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="100"/>
+ <created value="Fri Mar 17 07:26:00 2017"/>
+ <macStyle value="00000000 00000000"/>
+ <lowestRecPPEM value="7"/>
+ <fontDirectionHint value="2"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="0x00010000"/>
+ <ascent value="0"/>
+ <descent value="0"/>
+ <lineGap value="0"/>
+ <caretSlopeRise value="1"/>
+ <caretSlopeRun value="0"/>
+ <caretOffset value="0"/>
+ <reserved0 value="0"/>
+ <reserved1 value="0"/>
+ <reserved2 value="0"/>
+ <reserved3 value="0"/>
+ <metricDataFormat value="0"/>
+ </hhea>
+
+ <maxp>
+ <tableVersion value="0x10000"/>
+ <maxZones value="0"/>
+ <maxTwilightPoints value="0"/>
+ <maxStorage value="0"/>
+ <maxFunctionDefs value="0"/>
+ <maxInstructionDefs value="0"/>
+ <maxStackElements value="0"/>
+ <maxSizeOfInstructions value="0"/>
+ <maxComponentElements value="0"/>
+ </maxp>
+
+ <OS_2>
+ <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+ will be recalculated by the compiler -->
+ <version value="3"/>
+ <xAvgCharWidth value="594"/>
+ <usWeightClass value="400"/>
+ <usWidthClass value="5"/>
+ <fsType value="00000000 00001000"/>
+ <ySubscriptXSize value="650"/>
+ <ySubscriptYSize value="600"/>
+ <ySubscriptXOffset value="0"/>
+ <ySubscriptYOffset value="75"/>
+ <ySuperscriptXSize value="650"/>
+ <ySuperscriptYSize value="600"/>
+ <ySuperscriptXOffset value="0"/>
+ <ySuperscriptYOffset value="350"/>
+ <yStrikeoutSize value="50"/>
+ <yStrikeoutPosition value="300"/>
+ <sFamilyClass value="0"/>
+ <panose>
+ <bFamilyType value="0"/>
+ <bSerifStyle value="0"/>
+ <bWeight value="5"/>
+ <bProportion value="0"/>
+ <bContrast value="0"/>
+ <bStrokeVariation value="0"/>
+ <bArmStyle value="0"/>
+ <bLetterForm value="0"/>
+ <bMidline value="0"/>
+ <bXHeight value="0"/>
+ </panose>
+ <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+ <achVendID value="UKWN"/>
+ <fsSelection value="00000000 01000000"/>
+ <usFirstCharIndex value="32"/>
+ <usLastCharIndex value="122"/>
+ <sTypoAscender value="800"/>
+ <sTypoDescender value="-200"/>
+ <sTypoLineGap value="200"/>
+ <usWinAscent value="1000"/>
+ <usWinDescent value="200"/>
+ <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="500"/>
+ <sCapHeight value="700"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="0"/>
+ </OS_2>
+
+ <hmtx>
+ <mtx name=".notdef" width="50" lsb="0"/>
+ <mtx name="1em" width="100" lsb="0"/>
+ </hmtx>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_12 format="12" reserved="0" length="6" nGroups="1" platformID="3" platEncID="10" language="0">
+ <map code="0x0020" name="1em" /> <!-- ' ' -->
+ <map code="0x0021" name="1em" /> <!-- '!' -->
+ <map code="0x0022" name="1em" /> <!-- '"' -->
+ <map code="0x0023" name="1em" /> <!-- '#' -->
+ <map code="0x0024" name="1em" /> <!-- '$' -->
+ <map code="0x0025" name="1em" /> <!-- '%' -->
+ <map code="0x0026" name="1em" /> <!-- '&' -->
+ <map code="0x0027" name="1em" /> <!-- ''' -->
+ <map code="0x0028" name="1em" /> <!-- '(' -->
+ <map code="0x0029" name="1em" /> <!-- ')' -->
+ <map code="0x002A" name="1em" /> <!-- '*' -->
+ <map code="0x002B" name="1em" /> <!-- '+' -->
+ <map code="0x002C" name="1em" /> <!-- ''' -->
+ <map code="0x002D" name="1em" /> <!-- '-' -->
+ <map code="0x002E" name="1em" /> <!-- '.' -->
+ <map code="0x002F" name="1em" /> <!-- '/' -->
+ <map code="0x0030" name="1em" /> <!-- '0' -->
+ <map code="0x0031" name="1em" /> <!-- '1' -->
+ <map code="0x0032" name="1em" /> <!-- '2' -->
+ <map code="0x0033" name="1em" /> <!-- '3' -->
+ <map code="0x0034" name="1em" /> <!-- '4' -->
+ <map code="0x0035" name="1em" /> <!-- '5' -->
+ <map code="0x0036" name="1em" /> <!-- '6' -->
+ <map code="0x0037" name="1em" /> <!-- '7' -->
+ <map code="0x0038" name="1em" /> <!-- '8' -->
+ <map code="0x0039" name="1em" /> <!-- '9' -->
+ <map code="0x003A" name="1em" /> <!-- ':' -->
+ <map code="0x003B" name="1em" /> <!-- ';' -->
+ <map code="0x003C" name="1em" /> <!-- '<' -->
+ <map code="0x003D" name="1em" /> <!-- '=' -->
+ <map code="0x003E" name="1em" /> <!-- '>' -->
+ <map code="0x003F" name="1em" /> <!-- '?' -->
+ <map code="0x0040" name="1em" /> <!-- '@' -->
+ <map code="0x0041" name="1em" /> <!-- 'A' -->
+ <map code="0x0042" name="1em" /> <!-- 'B' -->
+ <map code="0x0043" name="1em" /> <!-- 'C' -->
+ <map code="0x0044" name="1em" /> <!-- 'D' -->
+ <map code="0x0045" name="1em" /> <!-- 'E' -->
+ <map code="0x0046" name="1em" /> <!-- 'F' -->
+ <map code="0x0047" name="1em" /> <!-- 'G' -->
+ <map code="0x0048" name="1em" /> <!-- 'H' -->
+ <map code="0x0049" name="1em" /> <!-- 'I' -->
+ <map code="0x004A" name="1em" /> <!-- 'J' -->
+ <map code="0x004B" name="1em" /> <!-- 'K' -->
+ <map code="0x004C" name="1em" /> <!-- 'L' -->
+ <map code="0x004D" name="1em" /> <!-- 'M' -->
+ <map code="0x004E" name="1em" /> <!-- 'N' -->
+ <map code="0x004F" name="1em" /> <!-- 'O' -->
+ <map code="0x0050" name="1em" /> <!-- 'P' -->
+ <map code="0x0051" name="1em" /> <!-- 'Q' -->
+ <map code="0x0052" name="1em" /> <!-- 'R' -->
+ <map code="0x0053" name="1em" /> <!-- 'S' -->
+ <map code="0x0054" name="1em" /> <!-- 'T' -->
+ <map code="0x0055" name="1em" /> <!-- 'U' -->
+ <map code="0x0056" name="1em" /> <!-- 'V' -->
+ <map code="0x0057" name="1em" /> <!-- 'W' -->
+ <map code="0x0058" name="1em" /> <!-- 'X' -->
+ <map code="0x0059" name="1em" /> <!-- 'Y' -->
+ <map code="0x005A" name="1em" /> <!-- 'Z' -->
+ <map code="0x005B" name="1em" /> <!-- '[' -->
+ <map code="0x005C" name="1em" /> <!-- '\' -->
+ <map code="0x005D" name="1em" /> <!-- ']' -->
+ <map code="0x005E" name="1em" /> <!-- '^' -->
+ <map code="0x005F" name="1em" /> <!-- '_' -->
+ <map code="0x0060" name="1em" /> <!-- '`' -->
+ <map code="0x0061" name="1em" /> <!-- 'a' -->
+ <map code="0x0062" name="1em" /> <!-- 'b' -->
+ <map code="0x0063" name="1em" /> <!-- 'c' -->
+ <map code="0x0064" name="1em" /> <!-- 'd' -->
+ <map code="0x0065" name="1em" /> <!-- 'e' -->
+ <map code="0x0066" name="1em" /> <!-- 'f' -->
+ <map code="0x0067" name="1em" /> <!-- 'g' -->
+ <map code="0x0068" name="1em" /> <!-- 'h' -->
+ <map code="0x0069" name="1em" /> <!-- 'i' -->
+ <map code="0x006A" name="1em" /> <!-- 'j' -->
+ <map code="0x006B" name="1em" /> <!-- 'k' -->
+ <map code="0x006C" name="1em" /> <!-- 'l' -->
+ <map code="0x006D" name="1em" /> <!-- 'm' -->
+ <map code="0x006E" name="1em" /> <!-- 'n' -->
+ <map code="0x006F" name="1em" /> <!-- 'o' -->
+ <map code="0x0070" name="1em" /> <!-- 'p' -->
+ <map code="0x0071" name="1em" /> <!-- 'q' -->
+ <map code="0x0072" name="1em" /> <!-- 'r' -->
+ <map code="0x0073" name="1em" /> <!-- 's' -->
+ <map code="0x0074" name="1em" /> <!-- 't' -->
+ <map code="0x0075" name="1em" /> <!-- 'u' -->
+ <map code="0x0076" name="1em" /> <!-- 'v' -->
+ <map code="0x0077" name="1em" /> <!-- 'w' -->
+ <map code="0x0078" name="1em" /> <!-- 'x' -->
+ <map code="0x0079" name="1em" /> <!-- 'y' -->
+ <map code="0x007A" name="1em" /> <!-- 'z' -->
+ <map code="0x007B" name="1em" /> <!-- '{' -->
+ <map code="0x007C" name="1em" /> <!-- '|' -->
+ <map code="0x007D" name="1em" /> <!-- '}' -->
+ <map code="0x007E" name="1em" /> <!-- '~' -->
+ </cmap_format_12>
+ </cmap>
+
+ <loca>
+ <!-- The 'loca' table will be calculated by the compiler -->
+ </loca>
+
+ <glyf>
+ <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" />
+ <TTGlyph name="1em" xMin="0" yMin="0" xMax="100" yMax="100">
+ <contour>
+ <pt x="0" y="0" on="1" />
+ <pt x="100" y="0" on="1" />
+ <pt x="100" y="100" on="1" />
+ <pt x="0" y="100" on="1" />
+ </contour>
+ <instructions><assembly></assembly></instructions>
+ </TTGlyph>
+ </glyf>
+
+ <name>
+ <namerecord nameID="1" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Font for ASCII
+ </namerecord>
+ <namerecord nameID="2" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Regular
+ </namerecord>
+ <namerecord nameID="4" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Font for ASCII
+ </namerecord>
+ <namerecord nameID="6" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ SampleFont-Regular
+ </namerecord>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ Sample Font
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ Sample Font
+ </namerecord>
+ <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+ SampleFont-Regular
+ </namerecord>
+ </name>
+
+ <post>
+ <formatType value="3.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="-75"/>
+ <underlineThickness value="50"/>
+ <isFixedPitch value="0"/>
+ <minMemType42 value="0"/>
+ <maxMemType42 value="0"/>
+ <minMemType1 value="0"/>
+ <maxMemType1 value="0"/>
+ </post>
+
+</ttFont>
diff --git a/tests/data/LayoutTestFont.ttf b/tests/data/LayoutTestFont.ttf
new file mode 100644
index 0000000..7076023
--- /dev/null
+++ b/tests/data/LayoutTestFont.ttf
Binary files differ
diff --git a/tests/data/LayoutTestFont.ttx b/tests/data/LayoutTestFont.ttx
new file mode 100644
index 0000000..7971a03
--- /dev/null
+++ b/tests/data/LayoutTestFont.ttx
@@ -0,0 +1,207 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
+
+ <GlyphOrder>
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name="0em"/>
+ <GlyphID id="2" name="1em"/>
+ <GlyphID id="3" name="3em"/>
+ <GlyphID id="4" name="5em"/>
+ <GlyphID id="5" name="7em"/>
+ <GlyphID id="6" name="10em"/>
+ <GlyphID id="7" name="50em"/>
+ <GlyphID id="8" name="100em"/>
+ </GlyphOrder>
+
+ <head>
+ <tableVersion value="1.0"/>
+ <fontRevision value="1.0"/>
+ <checkSumAdjustment value="0x640cdb2f"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="100"/>
+ <created value="Fri Mar 17 07:26:00 2017"/>
+ <macStyle value="00000000 00000000"/>
+ <lowestRecPPEM value="7"/>
+ <fontDirectionHint value="2"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="0x00010000"/>
+ <ascent value="1000"/>
+ <descent value="-200"/>
+ <lineGap value="0"/>
+ <caretSlopeRise value="1"/>
+ <caretSlopeRun value="0"/>
+ <caretOffset value="0"/>
+ <reserved0 value="0"/>
+ <reserved1 value="0"/>
+ <reserved2 value="0"/>
+ <reserved3 value="0"/>
+ <metricDataFormat value="0"/>
+ </hhea>
+
+ <maxp>
+ <tableVersion value="0x10000"/>
+ <maxZones value="0"/>
+ <maxTwilightPoints value="0"/>
+ <maxStorage value="0"/>
+ <maxFunctionDefs value="0"/>
+ <maxInstructionDefs value="0"/>
+ <maxStackElements value="0"/>
+ <maxSizeOfInstructions value="0"/>
+ <maxComponentElements value="0"/>
+ </maxp>
+
+ <OS_2>
+ <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+ will be recalculated by the compiler -->
+ <version value="3"/>
+ <xAvgCharWidth value="594"/>
+ <usWeightClass value="400"/>
+ <usWidthClass value="5"/>
+ <fsType value="00000000 00001000"/>
+ <ySubscriptXSize value="650"/>
+ <ySubscriptYSize value="600"/>
+ <ySubscriptXOffset value="0"/>
+ <ySubscriptYOffset value="75"/>
+ <ySuperscriptXSize value="650"/>
+ <ySuperscriptYSize value="600"/>
+ <ySuperscriptXOffset value="0"/>
+ <ySuperscriptYOffset value="350"/>
+ <yStrikeoutSize value="50"/>
+ <yStrikeoutPosition value="300"/>
+ <sFamilyClass value="0"/>
+ <panose>
+ <bFamilyType value="0"/>
+ <bSerifStyle value="0"/>
+ <bWeight value="5"/>
+ <bProportion value="0"/>
+ <bContrast value="0"/>
+ <bStrokeVariation value="0"/>
+ <bArmStyle value="0"/>
+ <bLetterForm value="0"/>
+ <bMidline value="0"/>
+ <bXHeight value="0"/>
+ </panose>
+ <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+ <achVendID value="UKWN"/>
+ <fsSelection value="00000000 01000000"/>
+ <usFirstCharIndex value="32"/>
+ <usLastCharIndex value="122"/>
+ <sTypoAscender value="800"/>
+ <sTypoDescender value="-200"/>
+ <sTypoLineGap value="200"/>
+ <usWinAscent value="1000"/>
+ <usWinDescent value="200"/>
+ <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="500"/>
+ <sCapHeight value="700"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="0"/>
+ </OS_2>
+
+ <hmtx>
+ <mtx name=".notdef" width="50" lsb="0"/>
+ <mtx name="0em" width="0" lsb="0"/>
+ <mtx name="1em" width="100" lsb="0"/>
+ <mtx name="3em" width="300" lsb="0"/>
+ <mtx name="5em" width="500" lsb="0"/>
+ <mtx name="7em" width="700" lsb="0"/>
+ <mtx name="10em" width="1000" lsb="0"/>
+ <mtx name="50em" width="5000" lsb="0"/>
+ <mtx name="100em" width="10000" lsb="0"/>
+ </hmtx>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_12 format="12" reserved="0" length="6" nGroups="1" platformID="3" platEncID="10" language="0">
+ <map code="0x0020" name="10em" />
+ <map code="0x002e" name="10em" /> <!-- . -->
+ <map code="0x0043" name="100em" /> <!-- C -->
+ <map code="0x0049" name="1em" /> <!-- I -->
+ <map code="0x004c" name="50em" /> <!-- L -->
+ <map code="0x0056" name="5em" /> <!-- V -->
+ <map code="0x0058" name="10em" /> <!-- X -->
+ <map code="0x005f" name="0em" /> <!-- _ -->
+ <map code="0xfffd" name="7em" /> <!-- REPLACEMENT CHAR -->
+ <map code="0x10331" name="10em" />
+ </cmap_format_12>
+ </cmap>
+
+ <loca>
+ <!-- The 'loca' table will be calculated by the compiler -->
+ </loca>
+
+ <glyf>
+ <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" />
+ <TTGlyph name="0em" xMin="0" yMin="0" xMax="0" yMax="0" />
+ <TTGlyph name="1em" xMin="0" yMin="0" xMax="0" yMax="0" />
+ <TTGlyph name="3em" xMin="0" yMin="0" xMax="0" yMax="0" />
+ <TTGlyph name="5em" xMin="0" yMin="0" xMax="0" yMax="0" />
+ <TTGlyph name="7em" xMin="0" yMin="0" xMax="0" yMax="0" />
+ <TTGlyph name="10em" xMin="0" yMin="0" xMax="0" yMax="0" />
+ <TTGlyph name="50em" xMin="0" yMin="0" xMax="0" yMax="0" />
+ <TTGlyph name="100em" xMin="0" yMin="0" xMax="0" yMax="0" />
+ </glyf>
+
+ <name>
+ <namerecord nameID="1" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Font for LayoutTestFont
+ </namerecord>
+ <namerecord nameID="2" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Regular
+ </namerecord>
+ <namerecord nameID="4" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Font for LayoutTestFont
+ </namerecord>
+ <namerecord nameID="6" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ SampleFont-Regular
+ </namerecord>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ Sample Font
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ Sample Font
+ </namerecord>
+ <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+ SampleFont-Regular
+ </namerecord>
+ </name>
+
+ <post>
+ <formatType value="3.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="-75"/>
+ <underlineThickness value="50"/>
+ <isFixedPitch value="0"/>
+ <minMemType42 value="0"/>
+ <maxMemType42 value="0"/>
+ <minMemType1 value="0"/>
+ <maxMemType1 value="0"/>
+ </post>
+
+</ttFont>
diff --git a/tests/perftests/Android.bp b/tests/perftests/Android.bp
index f8847ac..617560d 100644
--- a/tests/perftests/Android.bp
+++ b/tests/perftests/Android.bp
@@ -24,7 +24,6 @@
],
srcs: [
"FontCollection.cpp",
- "FontFamily.cpp",
"FontLanguage.cpp",
"GraphemeBreak.cpp",
"Hyphenator.cpp",
@@ -41,9 +40,10 @@
],
shared_libs: [
+ "libft2",
"libharfbuzz_ng",
"libicuuc",
"liblog",
- "libskia",
+
],
}
diff --git a/tests/perftests/FontCollection.cpp b/tests/perftests/FontCollection.cpp
index 79f2563..819f125 100644
--- a/tests/perftests/FontCollection.cpp
+++ b/tests/perftests/FontCollection.cpp
@@ -13,14 +13,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-#include <benchmark/benchmark.h>
+
+#include "minikin/FontCollection.h"
#include <memory>
-#include <minikin/FontCollection.h>
-#include <FontTestUtils.h>
-#include <UnicodeUtils.h>
-#include <MinikinInternal.h>
+#include <benchmark/benchmark.h>
+
+#include "minikin/LocaleList.h"
+
+#include "FontTestUtils.h"
+#include "MinikinInternal.h"
+#include "UnicodeUtils.h"
namespace minikin {
@@ -38,8 +42,8 @@
BENCHMARK(BM_FontCollection_construct);
static void BM_FontCollection_hasVariationSelector(benchmark::State& state) {
- std::shared_ptr<FontCollection> collection(
- getFontCollection(SYSTEM_FONT_PATH, SYSTEM_FONT_XML));
+ auto collection =
+ std::make_shared<FontCollection>(getFontFamilies(SYSTEM_FONT_PATH, SYSTEM_FONT_XML));
uint32_t baseCp = state.range(0);
uint32_t vsCp = state.range(1);
@@ -55,47 +59,47 @@
// TODO: Rewrite with BENCHMARK_CAPTURE for better test name.
BENCHMARK(BM_FontCollection_hasVariationSelector)
- ->ArgPair(0x2708, 0xFE0F)
- ->ArgPair(0x2708, 0xFE0E)
- ->ArgPair(0x3402, 0xE0100);
+ ->ArgPair(0x2708, 0xFE0F)
+ ->ArgPair(0x2708, 0xFE0E)
+ ->ArgPair(0x3402, 0xE0100);
struct ItemizeTestCases {
std::string itemizeText;
std::string languageTag;
std::string labelText;
} ITEMIZE_TEST_CASES[] = {
- { "'A' 'n' 'd' 'r' 'o' 'i' 'd'", "en", "English" },
- { "U+4E16", "zh-Hans", "CJK Ideograph" },
- { "U+4E16", "zh-Hans,zh-Hant,ja,en,es,pt,fr,de", "CJK Ideograph with many language fallback" },
- { "U+3402 U+E0100", "ja", "CJK Ideograph with variation selector" },
- { "'A' 'n' U+0E1A U+0E31 U+0645 U+062D U+0648", "en", "Mixture of English, Thai and Arabic" },
- { "U+2708 U+FE0E", "en", "Emoji with variation selector" },
- { "U+0031 U+FE0F U+20E3", "en", "KEYCAP" },
+ {"'A' 'n' 'd' 'r' 'o' 'i' 'd'", "en", "English"},
+ {"U+4E16", "zh-Hans", "CJK Ideograph"},
+ {"U+4E16", "zh-Hans,zh-Hant,ja,en,es,pt,fr,de",
+ "CJK Ideograph with many language fallback"},
+ {"U+3402 U+E0100", "ja", "CJK Ideograph with variation selector"},
+ {"'A' 'n' U+0E1A U+0E31 U+0645 U+062D U+0648", "en", "Mixture of English, Thai and Arabic"},
+ {"U+2708 U+FE0E", "en", "Emoji with variation selector"},
+ {"U+0031 U+FE0F U+20E3", "en", "KEYCAP"},
};
static void BM_FontCollection_itemize(benchmark::State& state) {
- std::shared_ptr<FontCollection> collection(
- getFontCollection(SYSTEM_FONT_PATH, SYSTEM_FONT_XML));
+ auto collection =
+ std::make_shared<FontCollection>(getFontFamilies(SYSTEM_FONT_PATH, SYSTEM_FONT_XML));
size_t testIndex = state.range(0);
state.SetLabel("Itemize: " + ITEMIZE_TEST_CASES[testIndex].labelText);
uint16_t buffer[64];
size_t utf16_length = 0;
- ParseUnicode(
- buffer, 64, ITEMIZE_TEST_CASES[testIndex].itemizeText.c_str(), &utf16_length, nullptr);
+ ParseUnicode(buffer, 64, ITEMIZE_TEST_CASES[testIndex].itemizeText.c_str(), &utf16_length,
+ nullptr);
std::vector<FontCollection::Run> result;
- FontStyle style(FontStyle::registerLanguageList(ITEMIZE_TEST_CASES[testIndex].languageTag));
+ MinikinPaint paint(collection);
+ paint.localeListId = registerLocaleList(ITEMIZE_TEST_CASES[testIndex].languageTag);
- android::AutoMutex _l(gMinikinLock);
while (state.KeepRunning()) {
result.clear();
- collection->itemize(buffer, utf16_length, style, &result);
+ collection->itemize(buffer, utf16_length, paint, &result);
}
}
// TODO: Rewrite with BENCHMARK_CAPTURE once it is available in Android.
-BENCHMARK(BM_FontCollection_itemize)
- ->Arg(0)->Arg(1)->Arg(2)->Arg(3)->Arg(4)->Arg(5)->Arg(6);
+BENCHMARK(BM_FontCollection_itemize)->Arg(0)->Arg(1)->Arg(2)->Arg(3)->Arg(4)->Arg(5)->Arg(6);
} // namespace minikin
diff --git a/tests/perftests/FontFamily.cpp b/tests/perftests/FontFamily.cpp
deleted file mode 100644
index 9ab61e1..0000000
--- a/tests/perftests/FontFamily.cpp
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#include <benchmark/benchmark.h>
-
-#include <minikin/FontFamily.h>
-#include "../util/MinikinFontForTest.h"
-
-namespace minikin {
-
-static void BM_FontFamily_create(benchmark::State& state) {
- std::shared_ptr<MinikinFontForTest> minikinFont =
- std::make_shared<MinikinFontForTest>("/system/fonts/NotoSansCJK-Regular.ttc", 0);
-
- while (state.KeepRunning()) {
- std::shared_ptr<FontFamily> family = std::make_shared<FontFamily>(
- std::vector<Font>({Font(minikinFont, FontStyle())}));
- }
-}
-
-BENCHMARK(BM_FontFamily_create);
-
-} // namespace minikin
diff --git a/tests/perftests/FontLanguage.cpp b/tests/perftests/FontLanguage.cpp
index 6c9c84d..20d8698 100644
--- a/tests/perftests/FontLanguage.cpp
+++ b/tests/perftests/FontLanguage.cpp
@@ -13,31 +13,31 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-#include <benchmark/benchmark.h>
+#include "Locale.h"
-#include "FontLanguage.h"
+#include <benchmark/benchmark.h>
namespace minikin {
-static void BM_FontLanguage_en_US(benchmark::State& state) {
+static void BM_Locale_en_US(benchmark::State& state) {
while (state.KeepRunning()) {
- FontLanguage language("en-US", 5);
+ Locale language(StringPiece("en-US", 5));
}
}
-BENCHMARK(BM_FontLanguage_en_US);
+BENCHMARK(BM_Locale_en_US);
-static void BM_FontLanguage_en_Latn_US(benchmark::State& state) {
+static void BM_Locale_en_Latn_US(benchmark::State& state) {
while (state.KeepRunning()) {
- FontLanguage language("en-Latn-US", 10);
+ Locale language(StringPiece("en-Latn-US", 10));
}
}
-BENCHMARK(BM_FontLanguage_en_Latn_US);
+BENCHMARK(BM_Locale_en_Latn_US);
-static void BM_FontLanguage_en_Latn_US_u_em_emoji(benchmark::State& state) {
+static void BM_Locale_en_Latn_US_u_em_emoji(benchmark::State& state) {
while (state.KeepRunning()) {
- FontLanguage language("en-Latn-US-u-em-emoji", 21);
+ Locale language(StringPiece("en-Latn-US-u-em-emoji", 21));
}
}
-BENCHMARK(BM_FontLanguage_en_Latn_US_u_em_emoji);
+BENCHMARK(BM_Locale_en_Latn_US_u_em_emoji);
} // namespace minikin
diff --git a/tests/perftests/GraphemeBreak.cpp b/tests/perftests/GraphemeBreak.cpp
index 830586f..af156c8 100644
--- a/tests/perftests/GraphemeBreak.cpp
+++ b/tests/perftests/GraphemeBreak.cpp
@@ -13,11 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-#include <benchmark/benchmark.h>
-
-#include <cutils/log.h>
#include "minikin/GraphemeBreak.h"
+
+#include <benchmark/benchmark.h>
+#include <cutils/log.h>
+
#include "UnicodeUtils.h"
namespace minikin {
@@ -42,9 +43,9 @@
}
}
BENCHMARK(BM_GraphemeBreak_Ascii)
- ->Arg(0) // Begining of the text.
- ->Arg(1) // Middle of the text.
- ->Arg(12); // End of the text.
+ ->Arg(0) // Begining of the text.
+ ->Arg(1) // Middle of the text.
+ ->Arg(12); // End of the text.
static void BM_GraphemeBreak_Emoji(benchmark::State& state) {
size_t result_size;
@@ -57,9 +58,9 @@
}
}
BENCHMARK(BM_GraphemeBreak_Emoji)
- ->Arg(1) // Middle of emoji modifier sequence.
- ->Arg(2) // Middle of the surrogate pairs.
- ->Arg(3); // After emoji modifier sequence. Here is boundary of grapheme cluster.
+ ->Arg(1) // Middle of emoji modifier sequence.
+ ->Arg(2) // Middle of the surrogate pairs.
+ ->Arg(3); // After emoji modifier sequence. Here is boundary of grapheme cluster.
static void BM_GraphemeBreak_Emoji_Flags(benchmark::State& state) {
size_t result_size;
@@ -72,8 +73,8 @@
}
}
BENCHMARK(BM_GraphemeBreak_Emoji_Flags)
- ->Arg(2) // Middle of flag sequence.
- ->Arg(4) // After flag sequence. Here is boundary of grapheme cluster.
- ->Arg(10); // Middle of 3rd flag sequence.
+ ->Arg(2) // Middle of flag sequence.
+ ->Arg(4) // After flag sequence. Here is boundary of grapheme cluster.
+ ->Arg(10); // Middle of 3rd flag sequence.
} // namespace minikin
diff --git a/tests/perftests/Hyphenator.cpp b/tests/perftests/Hyphenator.cpp
index ae62498..ae95644 100644
--- a/tests/perftests/Hyphenator.cpp
+++ b/tests/perftests/Hyphenator.cpp
@@ -13,43 +13,40 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+#include "minikin/Hyphenator.h"
+
#include <benchmark/benchmark.h>
-#include <minikin/Hyphenator.h>
-#include <FileUtils.h>
-#include <UnicodeUtils.h>
+#include "FileUtils.h"
+#include "UnicodeUtils.h"
namespace minikin {
const char* enUsHyph = "/system/usr/hyphen-data/hyph-en-us.hyb";
const int enUsMinPrefix = 2;
const int enUsMinSuffix = 3;
-const icu::Locale& usLocale = icu::Locale::getUS();
static void BM_Hyphenator_short_word(benchmark::State& state) {
- Hyphenator* hyphenator = Hyphenator::loadBinary(
- readWholeFile(enUsHyph).data(), enUsMinPrefix, enUsMinSuffix);
+ Hyphenator* hyphenator = Hyphenator::loadBinary(readWholeFile(enUsHyph).data(), enUsMinPrefix,
+ enUsMinSuffix, "en");
std::vector<uint16_t> word = utf8ToUtf16("hyphen");
std::vector<HyphenationType> result;
while (state.KeepRunning()) {
- hyphenator->hyphenate(&result, word.data(), word.size(), usLocale);
+ hyphenator->hyphenate(word, &result);
}
- Hyphenator::loadBinary(nullptr, 2, 2);
}
// TODO: Use BENCHMARK_CAPTURE for parametrise.
BENCHMARK(BM_Hyphenator_short_word);
static void BM_Hyphenator_long_word(benchmark::State& state) {
- Hyphenator* hyphenator = Hyphenator::loadBinary(
- readWholeFile(enUsHyph).data(), enUsMinPrefix, enUsMinSuffix);
- std::vector<uint16_t> word = utf8ToUtf16(
- "Pneumonoultramicroscopicsilicovolcanoconiosis");
+ Hyphenator* hyphenator = Hyphenator::loadBinary(readWholeFile(enUsHyph).data(), enUsMinPrefix,
+ enUsMinSuffix, "en");
+ std::vector<uint16_t> word = utf8ToUtf16("Pneumonoultramicroscopicsilicovolcanoconiosis");
std::vector<HyphenationType> result;
while (state.KeepRunning()) {
- hyphenator->hyphenate(&result, word.data(), word.size(), usLocale);
+ hyphenator->hyphenate(word, &result);
}
- Hyphenator::loadBinary(nullptr, 2, 2);
}
// TODO: Use BENCHMARK_CAPTURE for parametrise.
diff --git a/tests/perftests/WordBreaker.cpp b/tests/perftests/WordBreaker.cpp
index f9ef214..6a8b1c4 100644
--- a/tests/perftests/WordBreaker.cpp
+++ b/tests/perftests/WordBreaker.cpp
@@ -13,23 +13,27 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+#include "WordBreaker.h"
+
#include <benchmark/benchmark.h>
-#include "minikin/WordBreaker.h"
+#include "Locale.h"
#include "UnicodeUtils.h"
namespace minikin {
static void BM_WordBreaker_English(benchmark::State& state) {
- const char* kLoremIpsum = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do "
- "eiusmod tempor incididunt ut labore et dolore magna aliqua.";
+ const char* kLoremIpsum =
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do "
+ "eiusmod tempor incididunt ut labore et dolore magna aliqua.";
WordBreaker wb;
- wb.setLocale(icu::Locale::getEnglish());
+ wb.followingWithLocale(Locale("en-US"), 0);
std::vector<uint16_t> text = utf8ToUtf16(kLoremIpsum);
while (state.KeepRunning()) {
wb.setText(text.data(), text.size());
- while (wb.next() != -1) {}
+ while (wb.next() != -1) {
+ }
}
}
BENCHMARK(BM_WordBreaker_English);
diff --git a/tests/perftests/main.cpp b/tests/perftests/main.cpp
index e6f9d14..142b396 100644
--- a/tests/perftests/main.cpp
+++ b/tests/perftests/main.cpp
@@ -13,17 +13,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+
#include <benchmark/benchmark.h>
-
#include <cutils/log.h>
-
#include <unicode/uclean.h>
#include <unicode/udata.h>
-#include <fcntl.h>
-#include <sys/stat.h>
-#include <sys/mman.h>
-
int main(int argc, char** argv) {
const char* fn = "/system/usr/icu/" U_ICUDATA_NAME ".dat";
int fd = open(fn, O_RDONLY);
diff --git a/tests/stresstest/Android.bp b/tests/stresstest/Android.bp
index d91007a..3b132ed 100644
--- a/tests/stresstest/Android.bp
+++ b/tests/stresstest/Android.bp
@@ -18,6 +18,7 @@
name: "minikin_stress_tests",
header_libs: ["libminikin-headers-for-tests"],
+ data: [":minikin-test-data"],
static_libs: [
"libminikin-tests-util",
@@ -28,7 +29,7 @@
// Shared libraries which are dependencies of minikin; these are not automatically
// pulled in by the build system (and thus sadly must be repeated).
shared_libs: [
- "libskia",
+
"libft2",
"libharfbuzz_ng",
"libicuuc",
@@ -42,7 +43,7 @@
"MultithreadTest.cpp",
],
- cppflags: [
+ cflags: [
"-Werror",
"-Wall",
"-Wextra",
diff --git a/tests/stresstest/FontFamilyTest.cpp b/tests/stresstest/FontFamilyTest.cpp
index 9d289e5..7554314 100644
--- a/tests/stresstest/FontFamilyTest.cpp
+++ b/tests/stresstest/FontFamilyTest.cpp
@@ -14,14 +14,16 @@
* limitations under the License.
*/
-#include <gtest/gtest.h>
+#include "minikin/FontFamily.h"
-#include "../util/FontTestUtils.h"
-#include "../util/MinikinFontForTest.h"
-#include "HbFontCache.h"
-#include "MinikinInternal.h"
+#include <gtest/gtest.h>
+#include <hb.h>
+
#include "minikin/FontCollection.h"
-#include "minikin/Layout.h"
+
+#include "FontTestUtils.h"
+#include "FreeTypeMinikinFontForTest.h"
+#include "MinikinInternal.h"
namespace minikin {
@@ -33,18 +35,18 @@
const std::string& fontPath = GetParam().first;
int ttcIndex = GetParam().second;
- std::shared_ptr<MinikinFont> font(new MinikinFontForTest(fontPath, ttcIndex));
- std::shared_ptr<FontFamily> family =
- std::make_shared<FontFamily>(std::vector<Font>({Font(font, FontStyle())}));
+ auto font = std::make_shared<FreeTypeMinikinFontForTest>(fontPath);
+ std::vector<Font> fonts;
+ fonts.push_back(Font::Builder(font).build());
+ std::shared_ptr<FontFamily> family = std::make_shared<FontFamily>(std::move(fonts));
- android::AutoMutex _l(gMinikinLock);
- hb_font_t* hbFont = getHbFontLocked(font.get());
+ hb_font_t* hbFont = family->getFont(0)->baseFont().get();
for (uint32_t codePoint = 0; codePoint < MAX_UNICODE_CODE_POINT; ++codePoint) {
uint32_t unusedGlyph;
EXPECT_EQ(family->hasGlyph(codePoint, 0 /* variation selector */),
- static_cast<bool>(hb_font_get_glyph(hbFont, codePoint, 0 /* variation selector */,
- &unusedGlyph)));
+ static_cast<bool>(hb_font_get_glyph(hbFont, codePoint, 0 /* variation selector */,
+ &unusedGlyph)));
}
for (uint32_t vs = VS1; vs < VS256; ++vs) {
@@ -55,21 +57,17 @@
for (uint32_t codePoint = 0; codePoint < MAX_UNICODE_CODE_POINT; ++codePoint) {
uint32_t unusedGlyph;
ASSERT_EQ(family->hasGlyph(codePoint, vs),
- static_cast<bool>(hb_font_get_glyph(hbFont, codePoint, vs, &unusedGlyph)))
- << "Inconsistent Result: " << fontPath << "#" << ttcIndex
- << ": U+" << std::hex << codePoint << " U+" << std::hex << vs
- << " Minikin: " << family->hasGlyph(codePoint, vs)
- << " HarfBuzz: "
- << static_cast<bool>(hb_font_get_glyph(hbFont, codePoint, vs, &unusedGlyph));
-
+ static_cast<bool>(hb_font_get_glyph(hbFont, codePoint, vs, &unusedGlyph)))
+ << "Inconsistent Result: " << fontPath << "#" << ttcIndex << ": U+" << std::hex
+ << codePoint << " U+" << std::hex << vs
+ << " Minikin: " << family->hasGlyph(codePoint, vs) << " HarfBuzz: "
+ << static_cast<bool>(hb_font_get_glyph(hbFont, codePoint, vs, &unusedGlyph));
}
}
hb_font_destroy(hbFont);
}
-INSTANTIATE_TEST_CASE_P(FontFamilyTest,
- FontFamilyHarfBuzzCompatibilityTest,
- ::testing::Values(
- TestParam("/system/fonts/NotoSansCJK-Regular.ttc", 0),
- TestParam("/system/fonts/NotoColorEmoji.ttf", 0)));
+INSTANTIATE_TEST_CASE_P(FontFamilyTest, FontFamilyHarfBuzzCompatibilityTest,
+ ::testing::Values(TestParam("/system/fonts/NotoSansCJK-Regular.ttc", 0),
+ TestParam("/system/fonts/NotoColorEmoji.ttf", 0)));
} // namespace minikin
diff --git a/tests/stresstest/MultithreadTest.cpp b/tests/stresstest/MultithreadTest.cpp
index 08c94b9..5d5ae03 100644
--- a/tests/stresstest/MultithreadTest.cpp
+++ b/tests/stresstest/MultithreadTest.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#include <gtest/gtest.h>
+#include "minikin/Layout.h"
#include <condition_variable>
#include <mutex>
@@ -22,27 +22,27 @@
#include <thread>
#include <cutils/log.h>
+#include <gtest/gtest.h>
-#include "MinikinInternal.h"
#include "minikin/FontCollection.h"
-#include "minikin/Layout.h"
-#include "../util/FontTestUtils.h"
+#include "minikin/Macros.h"
+
+#include "FontTestUtils.h"
+#include "MinikinInternal.h"
+#include "PathUtils.h"
namespace minikin {
-const char* SYSTEM_FONT_PATH = "/system/fonts/";
-const char* SYSTEM_FONT_XML = "/system/etc/fonts.xml";
-
constexpr int LAYOUT_COUNT_PER_COLLECTION = 500;
constexpr int COLLECTION_COUNT_PER_THREAD = 15;
constexpr int NUM_THREADS = 10;
std::mutex gMutex;
std::condition_variable gCv;
-bool gReady = false;
+bool gReady GUARDED_BY(gMutex) = false;
-static std::vector<uint16_t> generateTestText(
- std::mt19937* mt, int lettersInWord, int wordsInText) {
+static std::vector<uint16_t> generateTestText(std::mt19937* mt, int lettersInWord,
+ int wordsInText) {
std::uniform_int_distribution<uint16_t> dist('A', 'Z');
std::vector<uint16_t> text;
@@ -66,22 +66,21 @@
}
std::mt19937 mt(tid);
- MinikinPaint paint;
for (int i = 0; i < COLLECTION_COUNT_PER_THREAD; ++i) {
- std::shared_ptr<FontCollection> collection(
- getFontCollection(SYSTEM_FONT_PATH, SYSTEM_FONT_XML));
+ MinikinPaint paint(buildFontCollection("Ascii.ttf"));
+ paint.size = 10.0f; // Make 1em = 10px
for (int j = 0; j < LAYOUT_COUNT_PER_COLLECTION; ++j) {
// Generates 10 of 3-letter words so that the word sometimes hit the cache.
Layout layout;
std::vector<uint16_t> text = generateTestText(&mt, 3, 10);
- layout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_LTR, FontStyle(),
- paint, collection);
+ layout.doLayout(text, Range(0, text.size()), Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
+ EndHyphenEdit::NO_EDIT);
std::vector<float> advances(text.size());
layout.getAdvances(advances.data());
for (size_t k = 0; k < advances.size(); ++k) {
- // MinikinFontForTest always returns 10.0f for horizontal advance.
+ // All characters in Ascii.ttf has 1.0em horizontal advance.
LOG_ALWAYS_FATAL_IF(advances[k] != 10.0f, "Memory corruption detected.");
}
}
diff --git a/tests/unittest/Android.bp b/tests/unittest/Android.bp
index 770d7be..02bec1e 100644
--- a/tests/unittest/Android.bp
+++ b/tests/unittest/Android.bp
@@ -30,7 +30,6 @@
// Shared libraries which are dependencies of minikin; these are not automatically
// pulled in by the build system (and thus sadly must be repeated).
shared_libs: [
- "libskia",
"libft2",
"libharfbuzz_ng",
"libicuuc",
@@ -40,6 +39,8 @@
],
srcs: [
+ "AndroidLineBreakerHelperTest.cpp",
+ "BidiUtilsTest.cpp",
"CmapCoverageTest.cpp",
"EmojiTest.cpp",
"FontCollectionTest.cpp",
@@ -47,30 +48,27 @@
"FontFamilyTest.cpp",
"FontLanguageListCacheTest.cpp",
"FontUtilsTest.cpp",
- "HbFontCacheTest.cpp",
+ "HyphenatorMapTest.cpp",
"HyphenatorTest.cpp",
"GraphemeBreakTests.cpp",
+ "GreedyLineBreakerTest.cpp",
+ "LayoutCacheTest.cpp",
"LayoutTest.cpp",
"LayoutUtilsTest.cpp",
- "LineBreakerTest.cpp",
+ "LocaleListTest.cpp",
+ "MeasuredTextTest.cpp",
"MeasurementTests.cpp",
+ "OptimalLineBreakerTest.cpp",
"SparseBitSetTest.cpp",
+ "StringPieceTest.cpp",
+ "TestMain.cpp",
"UnicodeUtilsTest.cpp",
"WordBreakerTests.cpp",
],
- cppflags: [
+ cflags: [
"-Werror",
"-Wall",
"-Wextra",
],
-
- multilib: {
- lib32: {
- cppflags: ["-DkTestFontDir=\"/data/nativetest/minikin_tests/data/\""],
- },
- lib64: {
- cppflags: ["-DkTestFontDir=\"/data/nativetest64/minikin_tests/data/\""],
- },
- },
}
diff --git a/tests/unittest/AndroidLineBreakerHelperTest.cpp b/tests/unittest/AndroidLineBreakerHelperTest.cpp
new file mode 100644
index 0000000..ea977c0
--- /dev/null
+++ b/tests/unittest/AndroidLineBreakerHelperTest.cpp
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "minikin/AndroidLineBreakerHelper.h"
+
+#include <gtest/gtest.h>
+
+namespace minikin {
+namespace android {
+
+TEST(AndroidLineWidth, negativeWidthTest) {
+ const int LINE_COUNT = 10;
+ const std::vector<float> EMPTY;
+ {
+ AndroidLineWidth lineWidth(-10 /* first width */, 1 /* first count */, 0 /* rest width */,
+ EMPTY, EMPTY, EMPTY, 0);
+
+ EXPECT_LE(0.0f, lineWidth.getMin());
+ for (int i = 0; i < LINE_COUNT; ++i) {
+ EXPECT_LE(0.0f, lineWidth.getAt(i));
+ }
+ }
+ {
+ AndroidLineWidth lineWidth(0 /* first width */, 0 /* first count */, -10 /* rest width */,
+ EMPTY, EMPTY, EMPTY, 0);
+
+ EXPECT_LE(0.0f, lineWidth.getMin());
+ for (int i = 0; i < LINE_COUNT; ++i) {
+ EXPECT_LE(0.0f, lineWidth.getAt(i));
+ }
+ }
+ {
+ std::vector<float> indents = {10};
+ AndroidLineWidth lineWidth(0 /* first width */, 0 /* first count */, 0 /* rest width */,
+ indents, EMPTY, EMPTY, 0);
+
+ EXPECT_LE(0.0f, lineWidth.getMin());
+ for (int i = 0; i < LINE_COUNT; ++i) {
+ EXPECT_LE(0.0f, lineWidth.getAt(i));
+ }
+ }
+}
+
+} // namespace android
+} // namespace minikin
diff --git a/tests/unittest/BidiUtilsTest.cpp b/tests/unittest/BidiUtilsTest.cpp
new file mode 100644
index 0000000..f743a66
--- /dev/null
+++ b/tests/unittest/BidiUtilsTest.cpp
@@ -0,0 +1,536 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "BidiUtils.h"
+
+#include <gtest/gtest.h>
+
+#include "minikin/Range.h"
+
+#include "UnicodeUtils.h"
+
+namespace minikin {
+
+const char LTR_1[] = "Hello, World";
+const char RTL_1[] = "\u0627\u0644\u0633\u0644\u0627\u0645\u0020\u0639\u0644\u064A\u0643\u0645";
+const char LTR_2[] = "Hello, Android";
+const char RTL_2[] = "\u0639\u0644\u064A\u0643\u0645\u0020\u0627\u0644\u0633\u0644\u0627\u0645";
+
+TEST(BidiUtilsTest, AllLTRCharText) {
+ auto text = utf8ToUtf16(LTR_1);
+ uint32_t ltrLength = text.size();
+ {
+ BidiText bidiText(text, Range(0, ltrLength), Bidi::LTR);
+ auto it = bidiText.begin();
+ EXPECT_NE(bidiText.end(), it);
+ EXPECT_EQ(Range(0, ltrLength), (*it).range);
+ EXPECT_FALSE((*it).isRtl);
+ ++it;
+ EXPECT_EQ(bidiText.end(), it);
+ }
+ {
+ BidiText bidiText(text, Range(0, ltrLength), Bidi::RTL);
+ auto it = bidiText.begin();
+ EXPECT_NE(bidiText.end(), it);
+ EXPECT_EQ(Range(0, ltrLength), (*it).range);
+ EXPECT_FALSE((*it).isRtl);
+ ++it;
+ EXPECT_EQ(bidiText.end(), it);
+ }
+ {
+ BidiText bidiText(text, Range(0, ltrLength), Bidi::DEFAULT_LTR);
+ auto it = bidiText.begin();
+ EXPECT_NE(bidiText.end(), it);
+ EXPECT_EQ(Range(0, ltrLength), (*it).range);
+ EXPECT_FALSE((*it).isRtl);
+ ++it;
+ EXPECT_EQ(bidiText.end(), it);
+ }
+ {
+ BidiText bidiText(text, Range(0, ltrLength), Bidi::DEFAULT_RTL);
+ auto it = bidiText.begin();
+ EXPECT_NE(bidiText.end(), it);
+ EXPECT_EQ(Range(0, ltrLength), (*it).range);
+ EXPECT_FALSE((*it).isRtl);
+ ++it;
+ EXPECT_EQ(bidiText.end(), it);
+ }
+ {
+ BidiText bidiText(text, Range(0, ltrLength), Bidi::FORCE_LTR);
+ auto it = bidiText.begin();
+ EXPECT_NE(bidiText.end(), it);
+ EXPECT_EQ(Range(0, ltrLength), (*it).range);
+ EXPECT_FALSE((*it).isRtl);
+ ++it;
+ EXPECT_EQ(bidiText.end(), it);
+ }
+ {
+ BidiText bidiText(text, Range(0, ltrLength), Bidi::FORCE_RTL);
+ auto it = bidiText.begin();
+ EXPECT_NE(bidiText.end(), it);
+ EXPECT_EQ(Range(0, ltrLength), (*it).range);
+ EXPECT_TRUE((*it).isRtl);
+ ++it;
+ EXPECT_EQ(bidiText.end(), it);
+ }
+}
+
+TEST(BidiUtilsTest, AllRTLCharText) {
+ auto text = utf8ToUtf16(RTL_1);
+ uint32_t rtlLength = text.size();
+ {
+ BidiText bidiText(text, Range(0, rtlLength), Bidi::LTR);
+ auto it = bidiText.begin();
+ EXPECT_NE(bidiText.end(), it);
+ EXPECT_EQ(Range(0, rtlLength), (*it).range);
+ EXPECT_TRUE((*it).isRtl);
+ ++it;
+ EXPECT_EQ(bidiText.end(), it);
+ }
+ {
+ BidiText bidiText(text, Range(0, rtlLength), Bidi::RTL);
+ auto it = bidiText.begin();
+ EXPECT_NE(bidiText.end(), it);
+ EXPECT_EQ(Range(0, rtlLength), (*it).range);
+ EXPECT_TRUE((*it).isRtl);
+ ++it;
+ EXPECT_EQ(bidiText.end(), it);
+ }
+ {
+ BidiText bidiText(text, Range(0, rtlLength), Bidi::DEFAULT_LTR);
+ auto it = bidiText.begin();
+ EXPECT_NE(bidiText.end(), it);
+ EXPECT_EQ(Range(0, rtlLength), (*it).range);
+ EXPECT_TRUE((*it).isRtl);
+ ++it;
+ EXPECT_EQ(bidiText.end(), it);
+ }
+ {
+ BidiText bidiText(text, Range(0, rtlLength), Bidi::DEFAULT_RTL);
+ auto it = bidiText.begin();
+ EXPECT_NE(bidiText.end(), it);
+ EXPECT_EQ(Range(0, rtlLength), (*it).range);
+ EXPECT_TRUE((*it).isRtl);
+ ++it;
+ EXPECT_EQ(bidiText.end(), it);
+ }
+ {
+ BidiText bidiText(text, Range(0, rtlLength), Bidi::FORCE_LTR);
+ auto it = bidiText.begin();
+ EXPECT_NE(bidiText.end(), it);
+ EXPECT_EQ(Range(0, rtlLength), (*it).range);
+ EXPECT_FALSE((*it).isRtl);
+ ++it;
+ EXPECT_EQ(bidiText.end(), it);
+ }
+ {
+ BidiText bidiText(text, Range(0, rtlLength), Bidi::FORCE_RTL);
+ auto it = bidiText.begin();
+ EXPECT_NE(bidiText.end(), it);
+ EXPECT_EQ(Range(0, rtlLength), (*it).range);
+ EXPECT_TRUE((*it).isRtl);
+ ++it;
+ EXPECT_EQ(bidiText.end(), it);
+ }
+}
+
+TEST(BidiUtilsTest, LTR_RTL_CharText) {
+ auto text = utf8ToUtf16(std::string(LTR_1) + RTL_1);
+ uint32_t ltrLength = utf8ToUtf16(LTR_1).size();
+ uint32_t rtlLength = utf8ToUtf16(RTL_1).size();
+ {
+ // Logical Run: L1 L2 R1 R2
+ // Visual Run : L1 L2 R2 R1
+ BidiText bidiText(text, Range(0, text.size()), Bidi::LTR);
+ auto it = bidiText.begin();
+ EXPECT_NE(bidiText.end(), it);
+ EXPECT_EQ(Range(0, ltrLength), (*it).range);
+ EXPECT_FALSE((*it).isRtl);
+ ++it;
+ EXPECT_NE(bidiText.end(), it);
+ EXPECT_EQ(Range(ltrLength, ltrLength + rtlLength), (*it).range);
+ EXPECT_TRUE((*it).isRtl);
+ ++it;
+ EXPECT_EQ(bidiText.end(), it);
+ }
+ {
+ // Logical Run: L1 L2 R1 R2
+ // Visual Run : R2 R1 L1 L2
+ BidiText bidiText(text, Range(0, text.size()), Bidi::RTL);
+ auto it = bidiText.begin();
+ EXPECT_NE(bidiText.end(), it);
+ EXPECT_EQ(Range(ltrLength, ltrLength + rtlLength), (*it).range);
+ EXPECT_TRUE((*it).isRtl);
+ ++it;
+ EXPECT_NE(bidiText.end(), it);
+ EXPECT_EQ(Range(0, ltrLength), (*it).range);
+ EXPECT_FALSE((*it).isRtl);
+ ++it;
+ EXPECT_EQ(bidiText.end(), it);
+ }
+ {
+ // Logical Run: L1 L2 R1 R2
+ // Visual Run : L1 L2 R2 R1
+ BidiText bidiText(text, Range(0, text.size()), Bidi::DEFAULT_LTR);
+ auto it = bidiText.begin();
+ EXPECT_NE(bidiText.end(), it);
+ EXPECT_EQ(Range(0, ltrLength), (*it).range);
+ EXPECT_FALSE((*it).isRtl);
+ ++it;
+ EXPECT_NE(bidiText.end(), it);
+ EXPECT_EQ(Range(ltrLength, ltrLength + rtlLength), (*it).range);
+ EXPECT_TRUE((*it).isRtl);
+ ++it;
+ EXPECT_EQ(bidiText.end(), it);
+ }
+ {
+ // Logical Run: L1 L2 R1 R2
+ // Visual Run : L1 L2 R2 R1
+ BidiText bidiText(text, Range(0, text.size()), Bidi::DEFAULT_RTL);
+ auto it = bidiText.begin();
+ EXPECT_NE(bidiText.end(), it);
+ EXPECT_EQ(Range(0, ltrLength), (*it).range);
+ EXPECT_FALSE((*it).isRtl);
+ ++it;
+ EXPECT_NE(bidiText.end(), it);
+ EXPECT_EQ(Range(ltrLength, ltrLength + rtlLength), (*it).range);
+ EXPECT_TRUE((*it).isRtl);
+ ++it;
+ EXPECT_EQ(bidiText.end(), it);
+ }
+ {
+ // Logical Run: L1 L2 R1 R2
+ // Visual Run : L1 L2 R1 R2
+ BidiText bidiText(text, Range(0, text.size()), Bidi::FORCE_LTR);
+ auto it = bidiText.begin();
+ EXPECT_NE(bidiText.end(), it);
+ EXPECT_EQ(Range(0, ltrLength + rtlLength), (*it).range);
+ EXPECT_FALSE((*it).isRtl);
+ ++it;
+ EXPECT_EQ(bidiText.end(), it);
+ }
+ {
+ // Logical Run: L1 L2 R1 R2
+ // Visual Run : R2 R1 L2 L1
+ BidiText bidiText(text, Range(0, text.size()), Bidi::FORCE_RTL);
+ auto it = bidiText.begin();
+ EXPECT_NE(bidiText.end(), it);
+ EXPECT_EQ(Range(0, ltrLength + rtlLength), (*it).range);
+ EXPECT_TRUE((*it).isRtl);
+ ++it;
+ EXPECT_EQ(bidiText.end(), it);
+ }
+}
+
+TEST(BidiUtilsTest, RTL_LTR_CharText) {
+ auto text = utf8ToUtf16(std::string(RTL_1) + LTR_1);
+ uint32_t ltrLength = utf8ToUtf16(LTR_1).size();
+ uint32_t rtlLength = utf8ToUtf16(RTL_1).size();
+ {
+ // Logical Run: R1 R2 L1 L2
+ // Visual Run : R2 R1 L1 L2
+ BidiText bidiText(text, Range(0, text.size()), Bidi::LTR);
+ auto it = bidiText.begin();
+ EXPECT_NE(bidiText.end(), it);
+ EXPECT_EQ(Range(0, rtlLength), (*it).range);
+ EXPECT_TRUE((*it).isRtl);
+ ++it;
+ EXPECT_NE(bidiText.end(), it);
+ EXPECT_EQ(Range(rtlLength, ltrLength + rtlLength), (*it).range);
+ EXPECT_FALSE((*it).isRtl);
+ ++it;
+ EXPECT_EQ(bidiText.end(), it);
+ }
+ {
+ // Logical Run: R1 R2 L1 L2
+ // Visual Run : L1 L2 R2 R1
+ BidiText bidiText(text, Range(0, text.size()), Bidi::RTL);
+ auto it = bidiText.begin();
+ EXPECT_NE(bidiText.end(), it);
+ EXPECT_EQ(Range(rtlLength, ltrLength + rtlLength), (*it).range);
+ EXPECT_FALSE((*it).isRtl);
+ ++it;
+ EXPECT_NE(bidiText.end(), it);
+ EXPECT_EQ(Range(0, rtlLength), (*it).range);
+ EXPECT_TRUE((*it).isRtl);
+ ++it;
+ EXPECT_EQ(bidiText.end(), it);
+ }
+ {
+ // Logical Run: R1 R2 L1 L2
+ // Visual Run : L1 L2 R2 R1
+ BidiText bidiText(text, Range(0, text.size()), Bidi::DEFAULT_LTR);
+ auto it = bidiText.begin();
+ EXPECT_NE(bidiText.end(), it);
+ EXPECT_EQ(Range(rtlLength, ltrLength + rtlLength), (*it).range);
+ EXPECT_FALSE((*it).isRtl);
+ ++it;
+ EXPECT_NE(bidiText.end(), it);
+ EXPECT_EQ(Range(0, rtlLength), (*it).range);
+ EXPECT_TRUE((*it).isRtl);
+ ++it;
+ EXPECT_EQ(bidiText.end(), it);
+ }
+ {
+ // Logical Run: R1 R2 L1 L2
+ // Visual Run : L1 L2 R2 R1
+ BidiText bidiText(text, Range(0, text.size()), Bidi::DEFAULT_RTL);
+ auto it = bidiText.begin();
+ EXPECT_NE(bidiText.end(), it);
+ EXPECT_EQ(Range(rtlLength, ltrLength + rtlLength), (*it).range);
+ EXPECT_FALSE((*it).isRtl);
+ ++it;
+ EXPECT_NE(bidiText.end(), it);
+ EXPECT_EQ(Range(0, rtlLength), (*it).range);
+ EXPECT_TRUE((*it).isRtl);
+ ++it;
+ EXPECT_EQ(bidiText.end(), it);
+ }
+ {
+ // Logical Run: R1 R2 L1 L2
+ // Visual Run : R1 R2 L1 L2
+ BidiText bidiText(text, Range(0, text.size()), Bidi::FORCE_LTR);
+ auto it = bidiText.begin();
+ EXPECT_NE(bidiText.end(), it);
+ EXPECT_EQ(Range(0, ltrLength + rtlLength), (*it).range);
+ EXPECT_FALSE((*it).isRtl);
+ ++it;
+ EXPECT_EQ(bidiText.end(), it);
+ }
+ {
+ // Logical Run: R1 R2 L1 L2
+ // Visual Run : L2 L1 R2 R1
+ BidiText bidiText(text, Range(0, text.size()), Bidi::FORCE_RTL);
+ auto it = bidiText.begin();
+ EXPECT_NE(bidiText.end(), it);
+ EXPECT_EQ(Range(0, ltrLength + rtlLength), (*it).range);
+ EXPECT_TRUE((*it).isRtl);
+ ++it;
+ EXPECT_EQ(bidiText.end(), it);
+ }
+}
+
+TEST(BidiUtilsTest, LTR_RTL_LTR_CharText) {
+ auto text = utf8ToUtf16(std::string(LTR_1) + RTL_1 + LTR_2);
+ uint32_t ltr1Length = utf8ToUtf16(LTR_1).size();
+ uint32_t ltr2Length = utf8ToUtf16(LTR_2).size();
+ uint32_t rtlLength = utf8ToUtf16(RTL_1).size();
+ {
+ // Logical Run: L1 L2 R1 R2 L3 L4
+ // Visual Run : L1 L2 R2 R1 L3 L4
+ BidiText bidiText(text, Range(0, text.size()), Bidi::LTR);
+ auto it = bidiText.begin();
+ EXPECT_NE(bidiText.end(), it);
+ EXPECT_EQ(Range(0, ltr1Length), (*it).range);
+ EXPECT_FALSE((*it).isRtl);
+ ++it;
+ EXPECT_NE(bidiText.end(), it);
+ EXPECT_EQ(Range(ltr1Length, ltr1Length + rtlLength), (*it).range);
+ EXPECT_TRUE((*it).isRtl);
+ ++it;
+ EXPECT_NE(bidiText.end(), it);
+ EXPECT_EQ(Range(ltr1Length + rtlLength, ltr1Length + rtlLength + ltr2Length), (*it).range);
+ EXPECT_FALSE((*it).isRtl);
+ ++it;
+ EXPECT_EQ(bidiText.end(), it);
+ }
+ {
+ // Logical Run: L1 L2 R1 R2 L3 L4
+ // Visual Run : L3 L4 R2 R1 L1 2L
+ BidiText bidiText(text, Range(0, text.size()), Bidi::RTL);
+ auto it = bidiText.begin();
+ EXPECT_NE(bidiText.end(), it);
+ EXPECT_EQ(Range(ltr1Length + rtlLength, text.size()), (*it).range);
+ EXPECT_FALSE((*it).isRtl);
+ ++it;
+ EXPECT_NE(bidiText.end(), it);
+ EXPECT_EQ(Range(ltr1Length, ltr1Length + rtlLength), (*it).range);
+ EXPECT_TRUE((*it).isRtl);
+ ++it;
+ EXPECT_NE(bidiText.end(), it);
+ EXPECT_EQ(Range(0, ltr1Length), (*it).range);
+ EXPECT_FALSE((*it).isRtl);
+ ++it;
+ EXPECT_EQ(bidiText.end(), it);
+ }
+ {
+ // Logical Run: L1 L2 R1 R2 L3 L4
+ // Visual Run : L1 L2 R2 R1 L3 L4
+ BidiText bidiText(text, Range(0, text.size()), Bidi::DEFAULT_LTR);
+ auto it = bidiText.begin();
+ EXPECT_NE(bidiText.end(), it);
+ EXPECT_EQ(Range(0, ltr1Length), (*it).range);
+ EXPECT_FALSE((*it).isRtl);
+ ++it;
+ EXPECT_NE(bidiText.end(), it);
+ EXPECT_EQ(Range(ltr1Length, ltr1Length + rtlLength), (*it).range);
+ EXPECT_TRUE((*it).isRtl);
+ ++it;
+ EXPECT_NE(bidiText.end(), it);
+ EXPECT_EQ(Range(ltr1Length + rtlLength, ltr1Length + rtlLength + ltr2Length), (*it).range);
+ EXPECT_FALSE((*it).isRtl);
+ ++it;
+ EXPECT_EQ(bidiText.end(), it);
+ }
+ {
+ // Logical Run: L1 L2 R1 R2 L3 L4
+ // Visual Run : L1 L2 R2 R1 L3 L4
+ BidiText bidiText(text, Range(0, text.size()), Bidi::DEFAULT_RTL);
+ auto it = bidiText.begin();
+ EXPECT_NE(bidiText.end(), it);
+ EXPECT_EQ(Range(0, ltr1Length), (*it).range);
+ EXPECT_FALSE((*it).isRtl);
+ ++it;
+ EXPECT_NE(bidiText.end(), it);
+ EXPECT_EQ(Range(ltr1Length, ltr1Length + rtlLength), (*it).range);
+ EXPECT_TRUE((*it).isRtl);
+ ++it;
+ EXPECT_NE(bidiText.end(), it);
+ EXPECT_EQ(Range(ltr1Length + rtlLength, ltr1Length + rtlLength + ltr2Length), (*it).range);
+ EXPECT_FALSE((*it).isRtl);
+ ++it;
+ EXPECT_EQ(bidiText.end(), it);
+ }
+ {
+ // Logical Run: L1 L2 R1 R2 L3 L4
+ // Visual Run : L1 L2 R2 R1 L3 L4
+ BidiText bidiText(text, Range(0, text.size()), Bidi::FORCE_LTR);
+ auto it = bidiText.begin();
+ EXPECT_NE(bidiText.end(), it);
+ EXPECT_EQ(Range(0, ltr1Length + rtlLength + ltr2Length), (*it).range);
+ EXPECT_FALSE((*it).isRtl);
+ ++it;
+ EXPECT_EQ(bidiText.end(), it);
+ }
+ {
+ // Logical Run: L1 L2 R1 R2 L3 L4
+ // Visual Run : L1 L2 R2 R1 L3 L4
+ BidiText bidiText(text, Range(0, text.size()), Bidi::FORCE_RTL);
+ auto it = bidiText.begin();
+ EXPECT_NE(bidiText.end(), it);
+ EXPECT_EQ(Range(0, ltr1Length + rtlLength + ltr2Length), (*it).range);
+ EXPECT_TRUE((*it).isRtl);
+ ++it;
+ EXPECT_EQ(bidiText.end(), it);
+ }
+}
+
+TEST(BidiUtilsTest, RTL_LTR_RTL_CharText) {
+ auto text = utf8ToUtf16(std::string(RTL_1) + LTR_1 + RTL_2);
+ uint32_t ltrLength = utf8ToUtf16(LTR_1).size();
+ uint32_t rtl1Length = utf8ToUtf16(RTL_1).size();
+ uint32_t rtl2Length = utf8ToUtf16(RTL_2).size();
+ {
+ // Logical Run: R1 R2 L1 L2 R3 R4
+ // Visual Run : R2 R1 L1 L2 R4 R3
+ BidiText bidiText(text, Range(0, text.size()), Bidi::LTR);
+ auto it = bidiText.begin();
+ EXPECT_NE(bidiText.end(), it);
+ EXPECT_EQ(Range(0, rtl1Length), (*it).range);
+ EXPECT_TRUE((*it).isRtl);
+ ++it;
+ EXPECT_NE(bidiText.end(), it);
+ EXPECT_EQ(Range(rtl1Length, ltrLength + rtl1Length), (*it).range);
+ EXPECT_FALSE((*it).isRtl);
+ ++it;
+ EXPECT_NE(bidiText.end(), it);
+ EXPECT_EQ(Range(rtl1Length + ltrLength, text.size()), (*it).range);
+ EXPECT_TRUE((*it).isRtl);
+ ++it;
+ EXPECT_EQ(bidiText.end(), it);
+ }
+ {
+ // Logical Run: R1 R2 L1 L2 R3 R4
+ // Visual Run : R4 R3 L1 L2 R2 R1
+ BidiText bidiText(text, Range(0, text.size()), Bidi::RTL);
+ auto it = bidiText.begin();
+ EXPECT_NE(bidiText.end(), it);
+ EXPECT_EQ(Range(rtl1Length + ltrLength, text.size()), (*it).range);
+ EXPECT_TRUE((*it).isRtl);
+ ++it;
+ EXPECT_NE(bidiText.end(), it);
+ EXPECT_EQ(Range(rtl1Length, ltrLength + rtl1Length), (*it).range);
+ EXPECT_FALSE((*it).isRtl);
+ ++it;
+ EXPECT_NE(bidiText.end(), it);
+ EXPECT_EQ(Range(0, rtl1Length), (*it).range);
+ EXPECT_TRUE((*it).isRtl);
+ ++it;
+ EXPECT_EQ(bidiText.end(), it);
+ }
+ {
+ // Logical Run: R1 R2 L1 L2 R3 R4
+ // Visual Run : R4 R3 L1 L2 R2 R1
+ BidiText bidiText(text, Range(0, text.size()), Bidi::DEFAULT_LTR);
+ auto it = bidiText.begin();
+ EXPECT_NE(bidiText.end(), it);
+ EXPECT_EQ(Range(rtl1Length + ltrLength, text.size()), (*it).range);
+ EXPECT_TRUE((*it).isRtl);
+ ++it;
+ EXPECT_NE(bidiText.end(), it);
+ EXPECT_EQ(Range(rtl1Length, ltrLength + rtl1Length), (*it).range);
+ EXPECT_FALSE((*it).isRtl);
+ ++it;
+ EXPECT_NE(bidiText.end(), it);
+ EXPECT_EQ(Range(0, rtl1Length), (*it).range);
+ EXPECT_TRUE((*it).isRtl);
+ ++it;
+ EXPECT_EQ(bidiText.end(), it);
+ }
+ {
+ // Logical Run: R1 R2 L1 L2 R3 R4
+ // Visual Run : R4 R3 L1 L2 R2 R1
+ BidiText bidiText(text, Range(0, text.size()), Bidi::DEFAULT_RTL);
+ auto it = bidiText.begin();
+ EXPECT_NE(bidiText.end(), it);
+ EXPECT_EQ(Range(rtl1Length + ltrLength, text.size()), (*it).range);
+ EXPECT_TRUE((*it).isRtl);
+ ++it;
+ EXPECT_NE(bidiText.end(), it);
+ EXPECT_EQ(Range(rtl1Length, ltrLength + rtl1Length), (*it).range);
+ EXPECT_FALSE((*it).isRtl);
+ ++it;
+ EXPECT_NE(bidiText.end(), it);
+ EXPECT_EQ(Range(0, rtl1Length), (*it).range);
+ EXPECT_TRUE((*it).isRtl);
+ ++it;
+ EXPECT_EQ(bidiText.end(), it);
+ }
+ {
+ // Logical Run: R1 R2 L1 L2 R3 R4
+ // Visual Run : R1 R2 L1 L2 R3 R4
+ BidiText bidiText(text, Range(0, text.size()), Bidi::FORCE_LTR);
+ auto it = bidiText.begin();
+ EXPECT_NE(bidiText.end(), it);
+ EXPECT_EQ(Range(0, rtl1Length + ltrLength + rtl2Length), (*it).range);
+ EXPECT_FALSE((*it).isRtl);
+ ++it;
+ EXPECT_EQ(bidiText.end(), it);
+ }
+ {
+ // Logical Run: R1 R2 L1 L2 R3 R4
+ // Visual Run : R4 R3 L2 L1 R2 R1
+ BidiText bidiText(text, Range(0, text.size()), Bidi::FORCE_RTL);
+ auto it = bidiText.begin();
+ EXPECT_NE(bidiText.end(), it);
+ EXPECT_EQ(Range(0, rtl1Length + ltrLength + rtl2Length), (*it).range);
+ EXPECT_TRUE((*it).isRtl);
+ ++it;
+ EXPECT_EQ(bidiText.end(), it);
+ }
+}
+
+} // namespace minikin
diff --git a/tests/unittest/CmapCoverageTest.cpp b/tests/unittest/CmapCoverageTest.cpp
index 590a363..9dba583 100644
--- a/tests/unittest/CmapCoverageTest.cpp
+++ b/tests/unittest/CmapCoverageTest.cpp
@@ -14,12 +14,14 @@
* limitations under the License.
*/
+#include "minikin/CmapCoverage.h"
+
#include <random>
-#include <log/log.h>
#include <gtest/gtest.h>
-#include <minikin/CmapCoverage.h>
-#include <minikin/SparseBitSet.h>
+#include <log/log.h>
+
+#include "minikin/SparseBitSet.h"
#include "MinikinInternal.h"
@@ -64,22 +66,23 @@
static std::vector<uint8_t> buildCmapFormat4Table(const std::vector<uint16_t>& ranges) {
uint16_t segmentCount = ranges.size() / 2 + 1 /* +1 for end marker */;
- const size_t numOfUint16 =
- 8 /* format, length, languages, segCountX2, searchRange, entrySelector, rangeShift, pad */ +
- segmentCount * 4 /* endCount, startCount, idRange, idRangeOffset */;
+ const size_t numOfUint16 = 8 /* format, length, languages, segCountX2, searchRange,
+ entrySelector, rangeShift, pad */
+ +
+ segmentCount * 4 /* endCount, startCount, idRange, idRangeOffset */;
const size_t finalLength = sizeof(uint16_t) * numOfUint16;
std::vector<uint8_t> out(finalLength);
size_t head = 0;
- head = writeU16(4, out.data(), head); // format
+ head = writeU16(4, out.data(), head); // format
head = writeU16(finalLength, out.data(), head); // length
- head = writeU16(0, out.data(), head); // langauge
+ head = writeU16(0, out.data(), head); // langauge
const uint16_t searchRange = 2 * (1 << static_cast<int>(floor(log2(segmentCount))));
- head = writeU16(segmentCount * 2, out.data(), head); // segCountX2
- head = writeU16(searchRange, out.data(), head); // searchRange
- head = writeU16(__builtin_ctz(searchRange) - 1, out.data(), head); // entrySelector
+ head = writeU16(segmentCount * 2, out.data(), head); // segCountX2
+ head = writeU16(searchRange, out.data(), head); // searchRange
+ head = writeU16(__builtin_ctz(searchRange) - 1, out.data(), head); // entrySelector
head = writeU16(segmentCount * 2 - searchRange, out.data(), head); // rangeShift
size_t endCountHead = head;
@@ -113,18 +116,19 @@
// 'a' (U+0061) is mapped to Glyph ID = 0x0061).
// 'range' should be specified with inclusive-inclusive values.
static std::vector<uint8_t> buildCmapFormat12Table(const std::vector<uint32_t>& ranges) {
- uint32_t numGroups = ranges.size() / 2;
+ uint32_t numGroups = ranges.size() / 2;
const size_t finalLength = 2 /* format */ + 2 /* reserved */ + 4 /* length */ +
- 4 /* languages */ + 4 /* numGroups */ + 12 /* size of a group */ * numGroups;
+ 4 /* languages */ + 4 /* numGroups */ +
+ 12 /* size of a group */ * numGroups;
std::vector<uint8_t> out(finalLength);
size_t head = 0;
- head = writeU16(12, out.data(), head); // format
- head = writeU16(0, out.data(), head); // reserved
+ head = writeU16(12, out.data(), head); // format
+ head = writeU16(0, out.data(), head); // reserved
head = writeU32(finalLength, out.data(), head); // length
- head = writeU32(0, out.data(), head); // langauge
- head = writeU32(numGroups, out.data(), head); // numGroups
+ head = writeU32(0, out.data(), head); // langauge
+ head = writeU32(numGroups, out.data(), head); // numGroups
for (uint32_t i = 0; i < numGroups; ++i) {
const uint32_t start = ranges[2 * i];
@@ -153,7 +157,7 @@
}
const size_t numOfRanges = defaultUVSRanges.size() / 2;
const size_t length = sizeof(uint32_t) /* numUnicodeValueRanges */ +
- numOfRanges * 4 /* size of Unicode Range Table */;
+ numOfRanges * 4 /* size of Unicode Range Table */;
std::vector<uint8_t> out(length);
size_t head = 0;
@@ -173,7 +177,7 @@
return std::vector<uint8_t>();
}
const size_t length = sizeof(uint32_t) /* numUnicodeValueRanges */ +
- nonDefaultUVS.size() * 5 /* size of UVS Mapping Record */;
+ nonDefaultUVS.size() * 5 /* size of UVS Mapping Record */;
std::vector<uint8_t> out(length);
size_t head = 0;
@@ -189,15 +193,14 @@
static std::vector<uint8_t> buildCmapFormat14Table(
const std::vector<VariationSelectorRecord>& vsRecords) {
-
const size_t headerLength = sizeof(uint16_t) /* format */ + sizeof(uint32_t) /* length */ +
- sizeof(uint32_t) /* numVarSelectorRecords */ +
- 11 /* size of variation selector record */ * vsRecords.size();
+ sizeof(uint32_t) /* numVarSelectorRecords */ +
+ 11 /* size of variation selector record */ * vsRecords.size();
std::vector<uint8_t> out(headerLength);
size_t head = 0;
- head = writeU16(14, out.data(), head); // format
- head += sizeof(uint32_t); // length will be filled later
+ head = writeU16(14, out.data(), head); // format
+ head += sizeof(uint32_t); // length will be filled later
head = writeU32(vsRecords.size(), out.data(), head); // numVarSelectorRecords;
for (const auto& record : vsRecords) {
@@ -232,14 +235,13 @@
CmapBuilder(int numTables) : mNumTables(numTables), mCurrentTableIndex(0) {
const size_t headerSize =
- 2 /* version */ + 2 /* numTables */ + kEncodingTableSize * numTables;
+ 2 /* version */ + 2 /* numTables */ + kEncodingTableSize * numTables;
out.resize(headerSize);
writeU16(0, out.data(), 0);
writeU16(numTables, out.data(), 2);
}
- void appendTable(uint16_t platformId, uint16_t encodingId,
- const std::vector<uint8_t>& table) {
+ void appendTable(uint16_t platformId, uint16_t encodingId, const std::vector<uint8_t>& table) {
appendEncodingTable(platformId, encodingId, out.size());
out.insert(out.end(), table.begin(), table.end());
}
@@ -251,14 +253,14 @@
// Helper functions.
static std::vector<uint8_t> buildSingleFormat4Cmap(uint16_t platformId, uint16_t encodingId,
- const std::vector<uint16_t>& ranges) {
+ const std::vector<uint16_t>& ranges) {
CmapBuilder builder(1);
builder.appendTable(platformId, encodingId, buildCmapFormat4Table(ranges));
return builder.build();
}
static std::vector<uint8_t> buildSingleFormat12Cmap(uint16_t platformId, uint16_t encodingId,
- const std::vector<uint32_t>& ranges) {
+ const std::vector<uint32_t>& ranges) {
CmapBuilder builder(1);
builder.appendTable(platformId, encodingId, buildCmapFormat12Table(ranges));
return builder.build();
@@ -316,8 +318,8 @@
}
{
SCOPED_TRACE("Reversed range");
- std::vector<uint8_t> cmap = CmapBuilder::buildSingleFormat4Cmap(0, 0, std::vector<uint16_t>(
- {'b', 'b', 'a', 'a'}));
+ std::vector<uint8_t> cmap = CmapBuilder::buildSingleFormat4Cmap(
+ 0, 0, std::vector<uint16_t>({'b', 'b', 'a', 'a'}));
SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables);
EXPECT_EQ(0U, coverage.length());
@@ -325,8 +327,8 @@
}
{
SCOPED_TRACE("Reversed range - partially readable");
- std::vector<uint8_t> cmap = CmapBuilder::buildSingleFormat4Cmap(0, 0, std::vector<uint16_t>(
- { 'a', 'a', 'c', 'c', 'b', 'b'}));
+ std::vector<uint8_t> cmap = CmapBuilder::buildSingleFormat4Cmap(
+ 0, 0, std::vector<uint16_t>({'a', 'a', 'c', 'c', 'b', 'b'}));
SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables);
EXPECT_EQ(0U, coverage.length());
@@ -341,11 +343,9 @@
uint16_t platformId;
uint16_t encodingId;
} TEST_CASES[] = {
- { "Platform 0, Encoding 0", 0, 0 },
- { "Platform 0, Encoding 1", 0, 1 },
- { "Platform 0, Encoding 2", 0, 2 },
- { "Platform 0, Encoding 3", 0, 3 },
- { "Platform 3, Encoding 1", 3, 1 },
+ {"Platform 0, Encoding 0", 0, 0}, {"Platform 0, Encoding 1", 0, 1},
+ {"Platform 0, Encoding 2", 0, 2}, {"Platform 0, Encoding 3", 0, 3},
+ {"Platform 3, Encoding 1", 3, 1},
};
for (const auto& testCase : TEST_CASES) {
@@ -367,9 +367,9 @@
uint16_t platformId;
uint16_t encodingId;
} TEST_CASES[] = {
- { "Platform 0, Encoding 4", 0, 4 },
- { "Platform 0, Encoding 6", 0, 6 },
- { "Platform 3, Encoding 10", 3, 10 },
+ {"Platform 0, Encoding 4", 0, 4},
+ {"Platform 0, Encoding 6", 0, 6},
+ {"Platform 3, Encoding 10", 3, 10},
};
for (const auto& testCase : TEST_CASES) {
@@ -417,26 +417,26 @@
uint16_t platformId;
uint16_t encodingId;
} TEST_CASES[] = {
- // Any encodings with platform 2 is not supported.
- { "Platform 2, Encoding 0", 2, 0 },
- { "Platform 2, Encoding 1", 2, 1 },
- { "Platform 2, Encoding 2", 2, 2 },
- { "Platform 2, Encoding 3", 2, 3 },
- // UCS-2 or UCS-4 are supported on Platform == 3. Others are not supported.
- { "Platform 3, Encoding 0", 3, 0 }, // Symbol
- { "Platform 3, Encoding 2", 3, 2 }, // ShiftJIS
- { "Platform 3, Encoding 3", 3, 3 }, // RPC
- { "Platform 3, Encoding 4", 3, 4 }, // Big5
- { "Platform 3, Encoding 5", 3, 5 }, // Wansung
- { "Platform 3, Encoding 6", 3, 6 }, // Johab
- { "Platform 3, Encoding 7", 3, 7 }, // Reserved
- { "Platform 3, Encoding 8", 3, 8 }, // Reserved
- { "Platform 3, Encoding 9", 3, 9 }, // Reserved
- // Uknown platforms
- { "Platform 4, Encoding 0", 4, 0 },
- { "Platform 5, Encoding 1", 5, 1 },
- { "Platform 6, Encoding 0", 6, 0 },
- { "Platform 7, Encoding 1", 7, 1 },
+ // Any encodings with platform 2 is not supported.
+ {"Platform 2, Encoding 0", 2, 0},
+ {"Platform 2, Encoding 1", 2, 1},
+ {"Platform 2, Encoding 2", 2, 2},
+ {"Platform 2, Encoding 3", 2, 3},
+ // UCS-2 or UCS-4 are supported on Platform == 3. Others are not supported.
+ {"Platform 3, Encoding 0", 3, 0}, // Symbol
+ {"Platform 3, Encoding 2", 3, 2}, // ShiftJIS
+ {"Platform 3, Encoding 3", 3, 3}, // RPC
+ {"Platform 3, Encoding 4", 3, 4}, // Big5
+ {"Platform 3, Encoding 5", 3, 5}, // Wansung
+ {"Platform 3, Encoding 6", 3, 6}, // Johab
+ {"Platform 3, Encoding 7", 3, 7}, // Reserved
+ {"Platform 3, Encoding 8", 3, 8}, // Reserved
+ {"Platform 3, Encoding 9", 3, 9}, // Reserved
+ // Uknown platforms
+ {"Platform 4, Encoding 0", 4, 0},
+ {"Platform 5, Encoding 1", 5, 1},
+ {"Platform 6, Encoding 0", 6, 0},
+ {"Platform 7, Encoding 1", 7, 1},
};
for (const auto& testCase : TEST_CASES) {
@@ -490,6 +490,31 @@
EXPECT_EQ(0U, coverage.length());
EXPECT_TRUE(vsTables.empty());
}
+ {
+ SCOPED_TRACE("Reversed end code points");
+ std::vector<uint8_t> table =
+ buildCmapFormat4Table(std::vector<uint16_t>({'b', 'b', 'a', 'a'}));
+ CmapBuilder builder(1);
+ builder.appendTable(0, 0, table);
+ std::vector<uint8_t> cmap = builder.build();
+
+ SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables);
+ EXPECT_EQ(0U, coverage.length());
+ EXPECT_TRUE(vsTables.empty());
+ }
+}
+
+TEST(CmapCoverageTest, duplicatedCmap4EntryTest) {
+ std::vector<std::unique_ptr<SparseBitSet>> vsTables;
+ std::vector<uint8_t> table = buildCmapFormat4Table(std::vector<uint16_t>({'a', 'b', 'b', 'b'}));
+ CmapBuilder builder(1);
+ builder.appendTable(0, 0, table);
+ std::vector<uint8_t> cmap = builder.build();
+
+ SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables);
+ EXPECT_TRUE(coverage.get('a'));
+ EXPECT_TRUE(coverage.get('b'));
+ EXPECT_TRUE(vsTables.empty());
}
TEST(CmapCoverageTest, brokenFormat12Table) {
@@ -578,13 +603,8 @@
uint16_t encodingId;
const std::vector<uint8_t>& table;
} LOWER_PRIORITY_TABLES[] = {
- { 0, 0, format4 },
- { 0, 1, format4 },
- { 0, 2, format4 },
- { 0, 3, format4 },
- { 0, 4, format12 },
- { 0, 6, format12 },
- { 3, 1, format4 },
+ {0, 0, format4}, {0, 1, format4}, {0, 2, format4}, {0, 3, format4},
+ {0, 4, format12}, {0, 6, format12}, {3, 1, format4},
};
for (const auto& table : LOWER_PRIORITY_TABLES) {
@@ -594,7 +614,7 @@
std::vector<uint8_t> cmap = builder.build();
SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables);
- EXPECT_TRUE(coverage.get('a')); // comes from highest table
+ EXPECT_TRUE(coverage.get('a')); // comes from highest table
EXPECT_FALSE(coverage.get('b')); // should not use other table.
EXPECT_TRUE(vsTables.empty());
}
@@ -607,10 +627,7 @@
uint16_t encodingId;
const std::vector<uint8_t>& table;
} LOWER_PRIORITY_TABLES[] = {
- { 0, 0, format4 },
- { 0, 1, format4 },
- { 0, 2, format4 },
- { 0, 3, format4 },
+ {0, 0, format4}, {0, 1, format4}, {0, 2, format4}, {0, 3, format4},
};
for (const auto& table : LOWER_PRIORITY_TABLES) {
@@ -620,7 +637,7 @@
std::vector<uint8_t> cmap = builder.build();
SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables);
- EXPECT_TRUE(coverage.get('a')); // comes from highest table
+ EXPECT_TRUE(coverage.get('a')); // comes from highest table
EXPECT_FALSE(coverage.get('b')); // should not use other table.
EXPECT_TRUE(vsTables.empty());
}
@@ -640,7 +657,7 @@
std::vector<uint8_t> cmap = builder.build();
SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables);
- EXPECT_TRUE(coverage.get('a')); // comes from valid table
+ EXPECT_TRUE(coverage.get('a')); // comes from valid table
EXPECT_FALSE(coverage.get('b')); // should not use invalid table.
EXPECT_TRUE(vsTables.empty());
}
@@ -654,7 +671,7 @@
std::vector<uint8_t> cmap = builder.build();
SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables);
- EXPECT_TRUE(coverage.get('a')); // comes from valid table
+ EXPECT_TRUE(coverage.get('a')); // comes from valid table
EXPECT_FALSE(coverage.get('b')); // should not use invalid table.
EXPECT_TRUE(vsTables.empty());
}
@@ -668,7 +685,7 @@
std::vector<uint8_t> cmap = builder.build();
SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables);
- EXPECT_TRUE(coverage.get('a')); // comes from valid table
+ EXPECT_TRUE(coverage.get('a')); // comes from valid table
EXPECT_FALSE(coverage.get('b')); // should not use invalid table.
EXPECT_TRUE(vsTables.empty());
}
@@ -676,8 +693,7 @@
TEST(CmapCoverageTest, TableSelection_SkipBrokenFormat12Table) {
std::vector<std::unique_ptr<SparseBitSet>> vsTables;
- std::vector<uint8_t> validTable =
- buildCmapFormat12Table(std::vector<uint32_t>({'a', 'a'}));
+ std::vector<uint8_t> validTable = buildCmapFormat12Table(std::vector<uint32_t>({'a', 'a'}));
{
SCOPED_TRACE("Unsupported format");
CmapBuilder builder(2);
@@ -688,7 +704,7 @@
std::vector<uint8_t> cmap = builder.build();
SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables);
- EXPECT_TRUE(coverage.get('a')); // comes from valid table
+ EXPECT_TRUE(coverage.get('a')); // comes from valid table
EXPECT_FALSE(coverage.get('b')); // should not use invalid table.
EXPECT_TRUE(vsTables.empty());
}
@@ -702,7 +718,7 @@
std::vector<uint8_t> cmap = builder.build();
SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables);
- EXPECT_TRUE(coverage.get('a')); // comes from valid table
+ EXPECT_TRUE(coverage.get('a')); // comes from valid table
EXPECT_FALSE(coverage.get('b')); // should not use invalid table.
EXPECT_TRUE(vsTables.empty());
}
@@ -716,7 +732,7 @@
std::vector<uint8_t> cmap = builder.build();
SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables);
- EXPECT_TRUE(coverage.get('a')); // comes from valid table
+ EXPECT_TRUE(coverage.get('a')); // comes from valid table
EXPECT_FALSE(coverage.get('b')); // should not use invalid table.
EXPECT_TRUE(vsTables.empty());
}
@@ -726,9 +742,9 @@
std::vector<uint8_t> smallLetterTable =
buildCmapFormat12Table(std::vector<uint32_t>({'a', 'z'}));
std::vector<uint8_t> vsTable = buildCmapFormat14Table(std::vector<VariationSelectorRecord>({
- { 0xFE0E, { 'a', 'b' }, {} /* no non-default UVS table */ },
- { 0xFE0F, {} /* no default UVS table */, { 'a', 'b'} },
- { 0xE0100, { 'a', 'a' }, { 'b'} },
+ {0xFE0E, {'a', 'b'}, {} /* no non-default UVS table */},
+ {0xFE0F, {} /* no default UVS table */, {'a', 'b'}},
+ {0xE0100, {'a', 'a'}, {'b'}},
}));
CmapBuilder builder(2);
builder.appendTable(3, 1, smallLetterTable);
@@ -763,12 +779,20 @@
std::vector<uint8_t> smallLetterTable =
buildCmapFormat12Table(std::vector<uint32_t>({'a', 'z'}));
std::vector<uint8_t> vsTable = buildCmapFormat14Table(std::vector<VariationSelectorRecord>({
- { 0xFE0E, { 'a', 'e' }, { 'c', 'd', } },
- { 0xFE0F, { 'c', 'e'} , { 'a', 'b', 'c', 'd', 'e'} },
- { 0xE0100, { 'a', 'c' }, { 'b', 'c', 'd' } },
- { 0xE0101, { 'b', 'd'} , { 'a', 'b', 'c', 'd'} },
- { 0xE0102, { 'a', 'c', 'd', 'g'} , { 'b', 'c', 'd', 'e', 'f', 'g', 'h'} },
- { 0xE0103, { 'a', 'f'} , { 'b', 'd', } },
+ {0xFE0E,
+ {'a', 'e'},
+ {
+ 'c', 'd',
+ }},
+ {0xFE0F, {'c', 'e'}, {'a', 'b', 'c', 'd', 'e'}},
+ {0xE0100, {'a', 'c'}, {'b', 'c', 'd'}},
+ {0xE0101, {'b', 'd'}, {'a', 'b', 'c', 'd'}},
+ {0xE0102, {'a', 'c', 'd', 'g'}, {'b', 'c', 'd', 'e', 'f', 'g', 'h'}},
+ {0xE0103,
+ {'a', 'f'},
+ {
+ 'b', 'd',
+ }},
}));
CmapBuilder builder(2);
builder.appendTable(3, 1, smallLetterTable);
@@ -841,25 +865,23 @@
std::vector<uint8_t> cmap12Table = buildCmapFormat12Table(std::vector<uint32_t>({'a', 'z'}));
{
SCOPED_TRACE("Too small cmap size");
- std::vector<uint8_t> vsTable = buildCmapFormat14Table(std::vector<VariationSelectorRecord>({
- { 0xFE0E, { 'a', 'a' }, { 'b' } }
- }));
+ std::vector<uint8_t> vsTable = buildCmapFormat14Table(
+ std::vector<VariationSelectorRecord>({{0xFE0E, {'a', 'a'}, {'b'}}}));
CmapBuilder builder(2);
builder.appendTable(3, 1, cmap12Table);
builder.appendTable(VS_PLATFORM_ID, VS_ENCODING_ID, vsTable);
std::vector<uint8_t> cmap = builder.build();
std::vector<std::unique_ptr<SparseBitSet>> vsTables;
- SparseBitSet coverage = CmapCoverage::getCoverage(
- cmap.data(), 3 /* too small size */, &vsTables);
+ SparseBitSet coverage =
+ CmapCoverage::getCoverage(cmap.data(), 3 /* too small size */, &vsTables);
EXPECT_FALSE(coverage.get('a'));
ASSERT_TRUE(vsTables.empty());
}
{
SCOPED_TRACE("Too many variation records");
- std::vector<uint8_t> vsTable = buildCmapFormat14Table(std::vector<VariationSelectorRecord>({
- { 0xFE0F, { 'a', 'a' }, { 'b' } }
- }));
+ std::vector<uint8_t> vsTable = buildCmapFormat14Table(
+ std::vector<VariationSelectorRecord>({{0xFE0F, {'a', 'a'}, {'b'}}}));
writeU32(5000, vsTable.data(), 6 /* numVarSelectorRecord offset */);
CmapBuilder builder(2);
builder.appendTable(3, 1, cmap12Table);
@@ -872,9 +894,8 @@
}
{
SCOPED_TRACE("Invalid default UVS offset in variation records");
- std::vector<uint8_t> vsTable = buildCmapFormat14Table(std::vector<VariationSelectorRecord>({
- { 0xFE0F, { 'a', 'a' }, { 'b' } }
- }));
+ std::vector<uint8_t> vsTable = buildCmapFormat14Table(
+ std::vector<VariationSelectorRecord>({{0xFE0F, {'a', 'a'}, {'b'}}}));
writeU32(5000, vsTable.data(), 13 /* defaultUVSffset offset in the first record */);
CmapBuilder builder(2);
builder.appendTable(3, 1, cmap12Table);
@@ -887,9 +908,8 @@
}
{
SCOPED_TRACE("Invalid non default UVS offset in variation records");
- std::vector<uint8_t> vsTable = buildCmapFormat14Table(std::vector<VariationSelectorRecord>({
- { 0xFE0F, { 'a', 'a' }, { 'b' } }
- }));
+ std::vector<uint8_t> vsTable = buildCmapFormat14Table(
+ std::vector<VariationSelectorRecord>({{0xFE0F, {'a', 'a'}, {'b'}}}));
writeU32(5000, vsTable.data(), 17 /* nonDefaultUVSffset offset in the first record */);
CmapBuilder builder(2);
builder.appendTable(3, 1, cmap12Table);
@@ -902,9 +922,8 @@
}
{
SCOPED_TRACE("Too many ranges entry in default UVS table");
- std::vector<uint8_t> vsTable = buildCmapFormat14Table(std::vector<VariationSelectorRecord>({
- { 0xFE0F, { 'a', 'a' }, { 'b' } }
- }));
+ std::vector<uint8_t> vsTable = buildCmapFormat14Table(
+ std::vector<VariationSelectorRecord>({{0xFE0F, {'a', 'a'}, {'b'}}}));
// 21 is the offset of the numUnicodeValueRanges in the fist defulat UVS table.
writeU32(5000, vsTable.data(), 21);
CmapBuilder builder(2);
@@ -918,9 +937,8 @@
}
{
SCOPED_TRACE("Too many ranges entry in non default UVS table");
- std::vector<uint8_t> vsTable = buildCmapFormat14Table(std::vector<VariationSelectorRecord>({
- { 0xFE0F, { 'a', 'a' }, { 'b' } }
- }));
+ std::vector<uint8_t> vsTable = buildCmapFormat14Table(
+ std::vector<VariationSelectorRecord>({{0xFE0F, {'a', 'a'}, {'b'}}}));
// 29 is the offset of the numUnicodeValueRanges in the fist defulat UVS table.
writeU32(5000, vsTable.data(), 29);
CmapBuilder builder(2);
@@ -934,9 +952,8 @@
}
{
SCOPED_TRACE("Reversed range in default UVS table");
- std::vector<uint8_t> vsTable = buildCmapFormat14Table(std::vector<VariationSelectorRecord>({
- { 0xFE0F, { 'b', 'b', 'a', 'a' }, { } }
- }));
+ std::vector<uint8_t> vsTable = buildCmapFormat14Table(
+ std::vector<VariationSelectorRecord>({{0xFE0F, {'b', 'b', 'a', 'a'}, {}}}));
CmapBuilder builder(2);
builder.appendTable(3, 1, cmap12Table);
builder.appendTable(VS_PLATFORM_ID, VS_ENCODING_ID, vsTable);
@@ -948,9 +965,8 @@
}
{
SCOPED_TRACE("Reversed range in default UVS table - partially readable");
- std::vector<uint8_t> vsTable = buildCmapFormat14Table(std::vector<VariationSelectorRecord>({
- { 0xFE0F, { 'a', 'a', 'c', 'c', 'b', 'b' }, { } }
- }));
+ std::vector<uint8_t> vsTable = buildCmapFormat14Table(std::vector<VariationSelectorRecord>(
+ {{0xFE0F, {'a', 'a', 'c', 'c', 'b', 'b'}, {}}}));
CmapBuilder builder(2);
builder.appendTable(3, 1, cmap12Table);
builder.appendTable(VS_PLATFORM_ID, VS_ENCODING_ID, vsTable);
@@ -962,9 +978,8 @@
}
{
SCOPED_TRACE("Reversed mapping entries in non default UVS table");
- std::vector<uint8_t> vsTable = buildCmapFormat14Table(std::vector<VariationSelectorRecord>({
- { 0xFE0F, { }, { 'b', 'a' } }
- }));
+ std::vector<uint8_t> vsTable = buildCmapFormat14Table(
+ std::vector<VariationSelectorRecord>({{0xFE0F, {}, {'b', 'a'}}}));
CmapBuilder builder(2);
builder.appendTable(3, 1, cmap12Table);
builder.appendTable(VS_PLATFORM_ID, VS_ENCODING_ID, vsTable);
@@ -976,9 +991,56 @@
}
{
SCOPED_TRACE("Reversed mapping entries in non default UVS table");
- std::vector<uint8_t> vsTable = buildCmapFormat14Table(std::vector<VariationSelectorRecord>({
- { 0xFE0F, { }, { 'a', 'c', 'b' } }
- }));
+ std::vector<uint8_t> vsTable = buildCmapFormat14Table(
+ std::vector<VariationSelectorRecord>({{0xFE0F, {}, {'a', 'c', 'b'}}}));
+ CmapBuilder builder(2);
+ builder.appendTable(3, 1, cmap12Table);
+ builder.appendTable(VS_PLATFORM_ID, VS_ENCODING_ID, vsTable);
+ std::vector<uint8_t> cmap = builder.build();
+
+ std::vector<std::unique_ptr<SparseBitSet>> vsTables;
+ SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables);
+ ASSERT_TRUE(vsTables.empty());
+ }
+ {
+ // http://b/70808908
+ SCOPED_TRACE("OOB access due to integer overflow in non default UVS table");
+ std::vector<uint8_t> vsTable = buildCmapFormat14Table(
+ std::vector<VariationSelectorRecord>({{0xFE0F, {'a', 'a'}, {'b'}}}));
+ // 6 is the offset of the numRecords in the Cmap format14 subtable header.
+ writeU32(0x1745d174 /* 2^32 / kRecordSize(=11) */, vsTable.data(), 6);
+ CmapBuilder builder(2);
+ builder.appendTable(3, 1, cmap12Table);
+ builder.appendTable(VS_PLATFORM_ID, VS_ENCODING_ID, vsTable);
+ std::vector<uint8_t> cmap = builder.build();
+
+ std::vector<std::unique_ptr<SparseBitSet>> vsTables;
+ SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables);
+ ASSERT_TRUE(vsTables.empty());
+ }
+ {
+ // http://b/70808908
+ SCOPED_TRACE("OOB access due to integer overflow in non default UVS table");
+ std::vector<uint8_t> vsTable = buildCmapFormat14Table(
+ std::vector<VariationSelectorRecord>({{0xFE0F, {'a', 'a'}, {'b'}}}));
+ // 29 is the offset of the numUVSMappings in the fist non defulat UVS table.
+ writeU32(0x33333333 /* 2^32 / kUVSMappingRecordSize(=5) */, vsTable.data(), 29);
+ CmapBuilder builder(2);
+ builder.appendTable(3, 1, cmap12Table);
+ builder.appendTable(VS_PLATFORM_ID, VS_ENCODING_ID, vsTable);
+ std::vector<uint8_t> cmap = builder.build();
+
+ std::vector<std::unique_ptr<SparseBitSet>> vsTables;
+ SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables);
+ ASSERT_TRUE(vsTables.empty());
+ }
+ {
+ // http://b/70808908
+ SCOPED_TRACE("OOB access due to integer overflow in default UVS table");
+ std::vector<uint8_t> vsTable = buildCmapFormat14Table(
+ std::vector<VariationSelectorRecord>({{0xFE0F, {'a', 'a'}, {'b'}}}));
+ // 21 is the offset of the numUnicodeValueRanges in the fist defulat UVS table.
+ writeU32(0x40000000 /* 2^32 / kUnicodeRangeRecordSize(=4) */, vsTable.data(), 21);
CmapBuilder builder(2);
builder.appendTable(3, 1, cmap12Table);
builder.appendTable(VS_PLATFORM_ID, VS_ENCODING_ID, vsTable);
@@ -1043,8 +1105,7 @@
{
SCOPED_TRACE("Invalid default UVS offset in variation records");
std::vector<uint8_t> vsTable = buildCmapFormat14Table(std::vector<VariationSelectorRecord>({
- { 0xFE0E, { 'a', 'a' }, { 'b' } },
- { 0xFE0F, { 'a', 'a' }, { 'b' } },
+ {0xFE0E, {'a', 'a'}, {'b'}}, {0xFE0F, {'a', 'a'}, {'b'}},
}));
writeU32(5000, vsTable.data(), 13 /* defaultUVSffset offset in the record for 0xFE0E */);
CmapBuilder builder(2);
@@ -1067,8 +1128,7 @@
{
SCOPED_TRACE("Invalid non default UVS offset in variation records");
std::vector<uint8_t> vsTable = buildCmapFormat14Table(std::vector<VariationSelectorRecord>({
- { 0xFE0E, { 'a', 'a' }, { 'b' } },
- { 0xFE0F, { 'a', 'a' }, { 'b' } },
+ {0xFE0E, {'a', 'a'}, {'b'}}, {0xFE0F, {'a', 'a'}, {'b'}},
}));
writeU32(5000, vsTable.data(), 17 /* nonDefaultUVSffset offset in the first record */);
CmapBuilder builder(2);
@@ -1091,8 +1151,7 @@
{
SCOPED_TRACE("Unknown variation selectors.");
std::vector<uint8_t> vsTable = buildCmapFormat14Table(std::vector<VariationSelectorRecord>({
- { 0xFE0F, { 'a', 'a' }, { 'b' } },
- { 0xEFFFF, { 'a', 'a' }, { 'b' } },
+ {0xFE0F, {'a', 'a'}, {'b'}}, {0xEFFFF, {'a', 'a'}, {'b'}},
}));
CmapBuilder builder(2);
builder.appendTable(3, 1, cmap12Table);
@@ -1116,9 +1175,8 @@
TEST(CmapCoverageTest, TableSelection_defaultUVSPointMissingGlyph) {
std::vector<uint8_t> baseTable = buildCmapFormat12Table(std::vector<uint32_t>(
{RANGE('a', 'e'), RANGE('g', 'h'), RANGE('j', 'j'), RANGE('m', 'z')}));
- std::vector<uint8_t> vsTable = buildCmapFormat14Table(std::vector<VariationSelectorRecord>({
- { 0xFE0F, { 'a', 'z' }, { } }
- }));
+ std::vector<uint8_t> vsTable = buildCmapFormat14Table(
+ std::vector<VariationSelectorRecord>({{0xFE0F, {'a', 'z'}, {}}}));
CmapBuilder builder(2);
builder.appendTable(3, 1, baseTable);
@@ -1131,7 +1189,7 @@
ASSERT_LT(vsIndex, vsTables.size());
ASSERT_TRUE(vsTables[vsIndex]);
- for (char c = 'a'; c <= 'z'; ++c) {
+ for (char c = 'a'; c <= 'z'; ++c) {
// Default UVS table points the variation sequence to the glyph of the base code point.
// Thus, if the base code point is not supported, we should exclude them.
EXPECT_EQ(coverage.get(c), vsTables[vsIndex]->get(c)) << c;
@@ -1141,9 +1199,8 @@
#undef RANGE
TEST(CmapCoverageTest, TableSelection_vsTableOnly) {
- std::vector<uint8_t> vsTable = buildCmapFormat14Table(std::vector<VariationSelectorRecord>({
- { 0xFE0F, { }, { 'a' } }
- }));
+ std::vector<uint8_t> vsTable =
+ buildCmapFormat14Table(std::vector<VariationSelectorRecord>({{0xFE0F, {}, {'a'}}}));
CmapBuilder builder(1);
builder.appendTable(VS_PLATFORM_ID, VS_ENCODING_ID, vsTable);
diff --git a/tests/unittest/EmojiTest.cpp b/tests/unittest/EmojiTest.cpp
index e7d0f56..32ffde7 100644
--- a/tests/unittest/EmojiTest.cpp
+++ b/tests/unittest/EmojiTest.cpp
@@ -14,27 +14,26 @@
* limitations under the License.
*/
+#include "minikin/Emoji.h"
+
#include <gtest/gtest.h>
-
#include <unicode/uchar.h>
-#include <minikin/Emoji.h>
-
namespace minikin {
TEST(EmojiTest, isEmojiTest) {
- EXPECT_TRUE(isEmoji(0x0023)); // NUMBER SIGN
- EXPECT_TRUE(isEmoji(0x0035)); // DIGIT FIVE
- EXPECT_TRUE(isEmoji(0x2640)); // FEMALE SIGN
- EXPECT_TRUE(isEmoji(0x2642)); // MALE SIGN
- EXPECT_TRUE(isEmoji(0x2695)); // STAFF OF AESCULAPIUS
+ EXPECT_TRUE(isEmoji(0x0023)); // NUMBER SIGN
+ EXPECT_TRUE(isEmoji(0x0035)); // DIGIT FIVE
+ EXPECT_TRUE(isEmoji(0x2640)); // FEMALE SIGN
+ EXPECT_TRUE(isEmoji(0x2642)); // MALE SIGN
+ EXPECT_TRUE(isEmoji(0x2695)); // STAFF OF AESCULAPIUS
EXPECT_TRUE(isEmoji(0x1F0CF)); // PLAYING CARD BLACK JOKER
EXPECT_TRUE(isEmoji(0x1F1E9)); // REGIONAL INDICATOR SYMBOL LETTER D
EXPECT_TRUE(isEmoji(0x1F6F7)); // SLED
EXPECT_TRUE(isEmoji(0x1F9E6)); // SOCKS
- EXPECT_FALSE(isEmoji(0x0000)); // <control>
- EXPECT_FALSE(isEmoji(0x0061)); // LATIN SMALL LETTER A
+ EXPECT_FALSE(isEmoji(0x0000)); // <control>
+ EXPECT_FALSE(isEmoji(0x0061)); // LATIN SMALL LETTER A
EXPECT_FALSE(isEmoji(0x1F93B)); // MODERN PENTATHLON
EXPECT_FALSE(isEmoji(0x1F946)); // RIFLE
EXPECT_FALSE(isEmoji(0x29E3D)); // A han character.
@@ -47,15 +46,15 @@
EXPECT_TRUE(isEmojiModifier(0x1F3FE)); // EMOJI MODIFIER FITZPATRICK TYPE-5
EXPECT_TRUE(isEmojiModifier(0x1F3FF)); // EMOJI MODIFIER FITZPATRICK TYPE-6
- EXPECT_FALSE(isEmojiModifier(0x0000)); // <control>
+ EXPECT_FALSE(isEmojiModifier(0x0000)); // <control>
EXPECT_FALSE(isEmojiModifier(0x1F3FA)); // AMPHORA
EXPECT_FALSE(isEmojiModifier(0x1F400)); // RAT
EXPECT_FALSE(isEmojiModifier(0x29E3D)); // A han character.
}
TEST(EmojiTest, isEmojiBaseTest) {
- EXPECT_TRUE(isEmojiBase(0x261D)); // WHITE UP POINTING INDEX
- EXPECT_TRUE(isEmojiBase(0x270D)); // WRITING HAND
+ EXPECT_TRUE(isEmojiBase(0x261D)); // WHITE UP POINTING INDEX
+ EXPECT_TRUE(isEmojiBase(0x270D)); // WRITING HAND
EXPECT_TRUE(isEmojiBase(0x1F385)); // FATHER CHRISTMAS
EXPECT_TRUE(isEmojiBase(0x1F3C2)); // SNOWBOARDER
EXPECT_TRUE(isEmojiBase(0x1F3C7)); // HORSE RACING
@@ -70,8 +69,8 @@
EXPECT_TRUE(isEmojiBase(0x1F9D1)); // ADULT
EXPECT_TRUE(isEmojiBase(0x1F9DD)); // ELF
- EXPECT_FALSE(isEmojiBase(0x0000)); // <control>
- EXPECT_FALSE(isEmojiBase(0x261C)); // WHITE LEFT POINTING INDEX
+ EXPECT_FALSE(isEmojiBase(0x0000)); // <control>
+ EXPECT_FALSE(isEmojiBase(0x261C)); // WHITE LEFT POINTING INDEX
EXPECT_FALSE(isEmojiBase(0x1F384)); // CHRISTMAS TREE
EXPECT_FALSE(isEmojiBase(0x1F9DE)); // GENIE
EXPECT_FALSE(isEmojiBase(0x29E3D)); // A han character.
@@ -80,7 +79,7 @@
TEST(EmojiTest, emojiBidiOverrideTest) {
EXPECT_EQ(U_RIGHT_TO_LEFT, emojiBidiOverride(nullptr, 0x05D0)); // HEBREW LETTER ALEF
EXPECT_EQ(U_LEFT_TO_RIGHT,
- emojiBidiOverride(nullptr, 0x1F170)); // NEGATIVE SQUARED LATIN CAPITAL LETTER A
+ emojiBidiOverride(nullptr, 0x1F170)); // NEGATIVE SQUARED LATIN CAPITAL LETTER A
EXPECT_EQ(U_OTHER_NEUTRAL, emojiBidiOverride(nullptr, 0x1F6F7)); // SLED
EXPECT_EQ(U_OTHER_NEUTRAL, emojiBidiOverride(nullptr, 0x1F9E6)); // SOCKS
}
diff --git a/tests/unittest/FontCollectionItemizeTest.cpp b/tests/unittest/FontCollectionItemizeTest.cpp
index a031908..c9abc10 100644
--- a/tests/unittest/FontCollectionItemizeTest.cpp
+++ b/tests/unittest/FontCollectionItemizeTest.cpp
@@ -14,83 +14,105 @@
* limitations under the License.
*/
-#include <gtest/gtest.h>
+#include "minikin/FontCollection.h"
#include <memory>
-#include "FontLanguageListCache.h"
-#include "FontLanguage.h"
+#include <gtest/gtest.h>
+
+#include "minikin/FontFamily.h"
+#include "minikin/LocaleList.h"
+
#include "FontTestUtils.h"
-#include "ICUTestBase.h"
-#include "MinikinFontForTest.h"
+#include "FreeTypeMinikinFontForTest.h"
+#include "Locale.h"
+#include "LocaleListCache.h"
#include "MinikinInternal.h"
#include "UnicodeUtils.h"
-#include "minikin/FontFamily.h"
namespace minikin {
-const char kItemizeFontXml[] = kTestFontDir "itemize.xml";
-const char kCherokeeFont[] = kTestFontDir "Cherokee.ttf";
-const char kEmojiFont[] = kTestFontDir "Emoji.ttf";
-const char kJAFont[] = kTestFontDir "Ja.ttf";
-const char kKOFont[] = kTestFontDir "Ko.ttf";
-const char kLatinBoldFont[] = kTestFontDir "Bold.ttf";
-const char kLatinBoldItalicFont[] = kTestFontDir "BoldItalic.ttf";
-const char kLatinFont[] = kTestFontDir "Regular.ttf";
-const char kLatinItalicFont[] = kTestFontDir "Italic.ttf";
-const char kZH_HansFont[] = kTestFontDir "ZhHans.ttf";
-const char kZH_HantFont[] = kTestFontDir "ZhHant.ttf";
+const char kItemizeFontXml[] = "itemize.xml";
+const char kCherokeeFont[] = "Cherokee.ttf";
+const char kEmojiFont[] = "Emoji.ttf";
+const char kJAFont[] = "Ja.ttf";
+const char kKOFont[] = "Ko.ttf";
+const char kLatinBoldFont[] = "Bold.ttf";
+const char kLatinBoldItalicFont[] = "BoldItalic.ttf";
+const char kLatinFont[] = "Regular.ttf";
+const char kLatinItalicFont[] = "Italic.ttf";
+const char kZH_HansFont[] = "ZhHans.ttf";
+const char kZH_HantFont[] = "ZhHant.ttf";
-const char kEmojiXmlFile[] = kTestFontDir "emoji.xml";
-const char kNoGlyphFont[] = kTestFontDir "NoGlyphFont.ttf";
-const char kColorEmojiFont[] = kTestFontDir "ColorEmojiFont.ttf";
-const char kTextEmojiFont[] = kTestFontDir "TextEmojiFont.ttf";
-const char kMixedEmojiFont[] = kTestFontDir "ColorTextMixedEmojiFont.ttf";
+const char kEmojiXmlFile[] = "emoji.xml";
+const char kNoGlyphFont[] = "NoGlyphFont.ttf";
+const char kColorEmojiFont[] = "ColorEmojiFont.ttf";
+const char kTextEmojiFont[] = "TextEmojiFont.ttf";
+const char kMixedEmojiFont[] = "ColorTextMixedEmojiFont.ttf";
-const char kHasCmapFormat14Font[] = kTestFontDir "NoCmapFormat14.ttf";
-const char kNoCmapFormat14Font[] = kTestFontDir "VariationSelectorTest-Regular.ttf";
+const char kHasCmapFormat14Font[] = "NoCmapFormat14.ttf";
+const char kNoCmapFormat14Font[] = "VariationSelectorTest-Regular.ttf";
-typedef ICUTestBase FontCollectionItemizeTest;
-
-// Utility function for calling itemize function.
+// Utility functions for calling itemize function.
void itemize(const std::shared_ptr<FontCollection>& collection, const char* str, FontStyle style,
- std::vector<FontCollection::Run>* result) {
+ const std::string& localeList, std::vector<FontCollection::Run>* result) {
const size_t BUF_SIZE = 256;
uint16_t buf[BUF_SIZE];
size_t len;
result->clear();
ParseUnicode(buf, BUF_SIZE, str, &len, NULL);
- android::AutoMutex _l(gMinikinLock);
- collection->itemize(buf, len, style, result);
+ const uint32_t localeListId = registerLocaleList(localeList);
+ MinikinPaint paint(collection);
+ paint.fontStyle = style;
+ paint.localeListId = localeListId;
+ collection->itemize(buf, len, paint, result);
+}
+
+// Overloaded version for default font style.
+void itemize(const std::shared_ptr<FontCollection>& collection, const char* str,
+ const std::string& localeList, std::vector<FontCollection::Run>* result) {
+ itemize(collection, str, FontStyle(), localeList, result);
+}
+
+// Overloaded version for empty locale list id.
+void itemize(const std::shared_ptr<FontCollection>& collection, const char* str, FontStyle style,
+ std::vector<FontCollection::Run>* result) {
+ itemize(collection, str, style, "", result);
+}
+
+// Overloaded version for default font style and empty locale list id.
+void itemize(const std::shared_ptr<FontCollection>& collection, const char* str,
+ std::vector<FontCollection::Run>* result) {
+ itemize(collection, str, FontStyle(), "", result);
}
// Utility function to obtain font path associated with run.
-const std::string& getFontPath(const FontCollection::Run& run) {
+std::string getFontName(const FontCollection::Run& run) {
EXPECT_NE(nullptr, run.fakedFont.font);
- return ((MinikinFontForTest*)run.fakedFont.font)->fontPath();
+ return getBasename(
+ ((FreeTypeMinikinFontForTest*)run.fakedFont.font->typeface().get())->fontPath());
}
-// Utility function to obtain FontLanguages from string.
-const FontLanguages& registerAndGetFontLanguages(const std::string& lang_string) {
- android::AutoMutex _l(gMinikinLock);
- return FontLanguageListCache::getById(FontLanguageListCache::getId(lang_string));
+// Utility function to obtain LocaleList from string.
+const LocaleList& registerAndGetLocaleList(const std::string& locale_string) {
+ return LocaleListCache::getById(LocaleListCache::getId(locale_string));
}
-TEST_F(FontCollectionItemizeTest, itemize_latin) {
- std::shared_ptr<FontCollection> collection(getFontCollection(kTestFontDir, kItemizeFontXml));
+TEST(FontCollectionItemizeTest, itemize_latin) {
+ auto collection = buildFontCollectionFromXml(kItemizeFontXml);
std::vector<FontCollection::Run> runs;
const FontStyle kRegularStyle = FontStyle();
- const FontStyle kItalicStyle = FontStyle(4, true);
- const FontStyle kBoldStyle = FontStyle(7, false);
- const FontStyle kBoldItalicStyle = FontStyle(7, true);
+ const FontStyle kItalicStyle = FontStyle(FontStyle::Slant::ITALIC);
+ const FontStyle kBoldStyle = FontStyle(FontStyle::Weight::BOLD);
+ const FontStyle kBoldItalicStyle = FontStyle(FontStyle::Weight::BOLD, FontStyle::Slant::ITALIC);
itemize(collection, "'a' 'b' 'c' 'd' 'e'", kRegularStyle, &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(5, runs[0].end);
- EXPECT_EQ(kLatinFont, getFontPath(runs[0]));
+ EXPECT_EQ(kLatinFont, getFontName(runs[0]));
EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());
EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());
@@ -98,7 +120,7 @@
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(5, runs[0].end);
- EXPECT_EQ(kLatinItalicFont, getFontPath(runs[0]));
+ EXPECT_EQ(kLatinItalicFont, getFontName(runs[0]));
EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());
EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());
@@ -106,7 +128,7 @@
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(5, runs[0].end);
- EXPECT_EQ(kLatinBoldFont, getFontPath(runs[0]));
+ EXPECT_EQ(kLatinBoldFont, getFontName(runs[0]));
EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());
EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());
@@ -114,7 +136,7 @@
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(5, runs[0].end);
- EXPECT_EQ(kLatinBoldItalicFont, getFontPath(runs[0]));
+ EXPECT_EQ(kLatinBoldItalicFont, getFontName(runs[0]));
EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());
EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());
@@ -124,7 +146,7 @@
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(5, runs[0].end);
- EXPECT_EQ(kLatinFont, getFontPath(runs[0]));
+ EXPECT_EQ(kLatinFont, getFontName(runs[0]));
EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());
EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());
@@ -132,7 +154,7 @@
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(5, runs[0].end);
- EXPECT_EQ(kLatinFont, getFontPath(runs[0]));
+ EXPECT_EQ(kLatinFont, getFontName(runs[0]));
EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());
EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());
@@ -142,1400 +164,1361 @@
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
- EXPECT_EQ(kLatinFont, getFontPath(runs[0]));
+ EXPECT_EQ(kLatinFont, getFontName(runs[0]));
EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());
EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());
}
-TEST_F(FontCollectionItemizeTest, itemize_combining) {
+TEST(FontCollectionItemizeTest, itemize_combining) {
// The regular font and the Cherokee font both support U+0301 (COMBINING ACUTE ACCENT). Since
// it's a combining mark, it should come from whatever font the base character comes from.
- std::shared_ptr<FontCollection> collection(getFontCollection(kTestFontDir, kItemizeFontXml));
+ auto collection = buildFontCollectionFromXml(kItemizeFontXml);
std::vector<FontCollection::Run> runs;
- const FontStyle kRegularStyle = FontStyle();
-
- itemize(collection, "'a' U+0301", kRegularStyle, &runs);
+ itemize(collection, "'a' U+0301", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
- EXPECT_EQ(kLatinFont, getFontPath(runs[0]));
+ EXPECT_EQ(kLatinFont, getFontName(runs[0]));
// CHEROKEE LETTER A, COMBINING ACUTE ACCENT
- itemize(collection, "U+13A0 U+0301", kRegularStyle, &runs);
+ itemize(collection, "U+13A0 U+0301", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
- EXPECT_EQ(kCherokeeFont, getFontPath(runs[0]));
+ EXPECT_EQ(kCherokeeFont, getFontName(runs[0]));
// CHEROKEE LETTER A, COMBINING ACUTE ACCENT, COMBINING ACUTE ACCENT
- itemize(collection, "U+13A0 U+0301 U+0301", kRegularStyle, &runs);
+ itemize(collection, "U+13A0 U+0301 U+0301", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(3, runs[0].end);
- EXPECT_EQ(kCherokeeFont, getFontPath(runs[0]));
+ EXPECT_EQ(kCherokeeFont, getFontName(runs[0]));
- itemize(collection, "U+0301", kRegularStyle, &runs);
+ itemize(collection, "U+0301", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(1, runs[0].end);
- EXPECT_EQ(kLatinFont, getFontPath(runs[0]));
+ EXPECT_EQ(kLatinFont, getFontName(runs[0]));
// COMBINING ACUTE ACCENT, CHEROKEE LETTER A, COMBINING ACUTE ACCENT
- itemize(collection, "U+0301 U+13A0 U+0301", kRegularStyle, &runs);
+ itemize(collection, "U+0301 U+13A0 U+0301", &runs);
ASSERT_EQ(2U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(1, runs[0].end);
- EXPECT_EQ(kLatinFont, getFontPath(runs[0]));
+ EXPECT_EQ(kLatinFont, getFontName(runs[0]));
EXPECT_EQ(1, runs[1].start);
EXPECT_EQ(3, runs[1].end);
- EXPECT_EQ(kCherokeeFont, getFontPath(runs[1]));
+ EXPECT_EQ(kCherokeeFont, getFontName(runs[1]));
}
-TEST_F(FontCollectionItemizeTest, itemize_emoji) {
- std::shared_ptr<FontCollection> collection(getFontCollection(kTestFontDir, kItemizeFontXml));
+TEST(FontCollectionItemizeTest, itemize_emoji) {
+ auto collection = buildFontCollectionFromXml(kItemizeFontXml);
std::vector<FontCollection::Run> runs;
- itemize(collection, "U+1F469 U+1F467", FontStyle(), &runs);
+ itemize(collection, "U+1F469 U+1F467", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(4, runs[0].end);
- EXPECT_EQ(kEmojiFont, getFontPath(runs[0]));
+ EXPECT_EQ(kEmojiFont, getFontName(runs[0]));
EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());
EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());
// U+20E3(COMBINING ENCLOSING KEYCAP) must be in the same run with preceding
// character if the font supports.
- itemize(collection, "'0' U+20E3", FontStyle(), &runs);
+ itemize(collection, "'0' U+20E3", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
- EXPECT_EQ(kEmojiFont, getFontPath(runs[0]));
+ EXPECT_EQ(kEmojiFont, getFontName(runs[0]));
EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());
EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());
- itemize(collection, "U+1F470 U+20E3", FontStyle(), &runs);
+ itemize(collection, "U+1F470 U+20E3", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(3, runs[0].end);
- EXPECT_EQ(kEmojiFont, getFontPath(runs[0]));
+ EXPECT_EQ(kEmojiFont, getFontName(runs[0]));
EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());
EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());
- itemize(collection, "U+242EE U+1F470 U+20E3", FontStyle(), &runs);
+ itemize(collection, "U+242EE U+1F470 U+20E3", &runs);
ASSERT_EQ(2U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
- EXPECT_EQ(kJAFont, getFontPath(runs[0]));
+ EXPECT_EQ(kJAFont, getFontName(runs[0]));
EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());
EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());
EXPECT_EQ(2, runs[1].start);
EXPECT_EQ(5, runs[1].end);
- EXPECT_EQ(kEmojiFont, getFontPath(runs[1]));
+ EXPECT_EQ(kEmojiFont, getFontName(runs[1]));
EXPECT_FALSE(runs[1].fakedFont.fakery.isFakeBold());
EXPECT_FALSE(runs[1].fakedFont.fakery.isFakeItalic());
// Currently there is no fonts which has a glyph for 'a' + U+20E3, so they
// are splitted into two.
- itemize(collection, "'a' U+20E3", FontStyle(), &runs);
+ itemize(collection, "'a' U+20E3", &runs);
ASSERT_EQ(2U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(1, runs[0].end);
- EXPECT_EQ(kLatinFont, getFontPath(runs[0]));
+ EXPECT_EQ(kLatinFont, getFontName(runs[0]));
EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());
EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());
EXPECT_EQ(1, runs[1].start);
EXPECT_EQ(2, runs[1].end);
- EXPECT_EQ(kEmojiFont, getFontPath(runs[1]));
+ EXPECT_EQ(kEmojiFont, getFontName(runs[1]));
EXPECT_FALSE(runs[1].fakedFont.fakery.isFakeBold());
EXPECT_FALSE(runs[1].fakedFont.fakery.isFakeItalic());
}
-TEST_F(FontCollectionItemizeTest, itemize_non_latin) {
- std::shared_ptr<FontCollection> collection(getFontCollection(kTestFontDir, kItemizeFontXml));
+TEST(FontCollectionItemizeTest, itemize_non_latin) {
+ auto collection = buildFontCollectionFromXml(kItemizeFontXml);
std::vector<FontCollection::Run> runs;
- FontStyle kJAStyle = FontStyle(FontStyle::registerLanguageList("ja_JP"));
- FontStyle kUSStyle = FontStyle(FontStyle::registerLanguageList("en_US"));
- FontStyle kZH_HansStyle = FontStyle(FontStyle::registerLanguageList("zh_Hans"));
-
// All Japanese Hiragana characters.
- itemize(collection, "U+3042 U+3044 U+3046 U+3048 U+304A", kUSStyle, &runs);
+ itemize(collection, "U+3042 U+3044 U+3046 U+3048 U+304A", "ja-JP", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(5, runs[0].end);
- EXPECT_EQ(kJAFont, getFontPath(runs[0]));
+ EXPECT_EQ(kJAFont, getFontName(runs[0]));
EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());
EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());
// All Korean Hangul characters.
- itemize(collection, "U+B300 U+D55C U+BBFC U+AD6D", kUSStyle, &runs);
+ itemize(collection, "U+B300 U+D55C U+BBFC U+AD6D", "en-US", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(4, runs[0].end);
- EXPECT_EQ(kKOFont, getFontPath(runs[0]));
+ EXPECT_EQ(kKOFont, getFontName(runs[0]));
EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());
EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());
// All Han characters ja, zh-Hans font having.
// Japanese font should be selected if the specified language is Japanese.
- itemize(collection, "U+81ED U+82B1 U+5FCD", kJAStyle, &runs);
+ itemize(collection, "U+81ED U+82B1 U+5FCD", "ja-JP", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(3, runs[0].end);
- EXPECT_EQ(kJAFont, getFontPath(runs[0]));
+ EXPECT_EQ(kJAFont, getFontName(runs[0]));
EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());
EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());
// Simplified Chinese font should be selected if the specified language is Simplified
// Chinese.
- itemize(collection, "U+81ED U+82B1 U+5FCD", kZH_HansStyle, &runs);
+ itemize(collection, "U+81ED U+82B1 U+5FCD", "zh-Hans", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(3, runs[0].end);
- EXPECT_EQ(kZH_HansFont, getFontPath(runs[0]));
+ EXPECT_EQ(kZH_HansFont, getFontName(runs[0]));
EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());
EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());
// Fallbacks to other fonts if there is no glyph in the specified language's
// font. There is no character U+4F60 in Japanese.
- itemize(collection, "U+81ED U+4F60 U+5FCD", kJAStyle, &runs);
+ itemize(collection, "U+81ED U+4F60 U+5FCD", "ja-JP", &runs);
ASSERT_EQ(3U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(1, runs[0].end);
- EXPECT_EQ(kJAFont, getFontPath(runs[0]));
+ EXPECT_EQ(kJAFont, getFontName(runs[0]));
EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());
EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());
EXPECT_EQ(1, runs[1].start);
EXPECT_EQ(2, runs[1].end);
- EXPECT_EQ(kZH_HansFont, getFontPath(runs[1]));
+ EXPECT_EQ(kZH_HansFont, getFontName(runs[1]));
EXPECT_FALSE(runs[1].fakedFont.fakery.isFakeBold());
EXPECT_FALSE(runs[1].fakedFont.fakery.isFakeItalic());
EXPECT_EQ(2, runs[2].start);
EXPECT_EQ(3, runs[2].end);
- EXPECT_EQ(kJAFont, getFontPath(runs[2]));
+ EXPECT_EQ(kJAFont, getFontName(runs[2]));
EXPECT_FALSE(runs[2].fakedFont.fakery.isFakeBold());
EXPECT_FALSE(runs[2].fakedFont.fakery.isFakeItalic());
// Tone mark.
- itemize(collection, "U+4444 U+302D", FontStyle(), &runs);
+ itemize(collection, "U+4444 U+302D", "", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
- EXPECT_EQ(kZH_HansFont, getFontPath(runs[0]));
+ EXPECT_EQ(kZH_HansFont, getFontName(runs[0]));
EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());
EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());
// Both zh-Hant and ja fonts support U+242EE, but zh-Hans doesn't.
// Here, ja and zh-Hant font should have the same score but ja should be selected since it is
// listed before zh-Hant.
- itemize(collection, "U+242EE", kZH_HansStyle, &runs);
+ itemize(collection, "U+242EE", "zh-Hans", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
- EXPECT_EQ(kJAFont, getFontPath(runs[0]));
+ EXPECT_EQ(kJAFont, getFontName(runs[0]));
EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());
EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());
}
-TEST_F(FontCollectionItemizeTest, itemize_mixed) {
- std::shared_ptr<FontCollection> collection(getFontCollection(kTestFontDir, kItemizeFontXml));
+TEST(FontCollectionItemizeTest, itemize_mixed) {
+ auto collection = buildFontCollectionFromXml(kItemizeFontXml);
std::vector<FontCollection::Run> runs;
- FontStyle kUSStyle = FontStyle(FontStyle::registerLanguageList("en_US"));
-
- itemize(collection, "'a' U+4F60 'b' U+4F60 'c'", kUSStyle, &runs);
+ itemize(collection, "'a' U+4F60 'b' U+4F60 'c'", "en-US", &runs);
ASSERT_EQ(5U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(1, runs[0].end);
- EXPECT_EQ(kLatinFont, getFontPath(runs[0]));
+ EXPECT_EQ(kLatinFont, getFontName(runs[0]));
EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());
EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());
EXPECT_EQ(1, runs[1].start);
EXPECT_EQ(2, runs[1].end);
- EXPECT_EQ(kZH_HansFont, getFontPath(runs[1]));
+ EXPECT_EQ(kZH_HansFont, getFontName(runs[1]));
EXPECT_FALSE(runs[1].fakedFont.fakery.isFakeBold());
EXPECT_FALSE(runs[1].fakedFont.fakery.isFakeItalic());
EXPECT_EQ(2, runs[2].start);
EXPECT_EQ(3, runs[2].end);
- EXPECT_EQ(kLatinFont, getFontPath(runs[2]));
+ EXPECT_EQ(kLatinFont, getFontName(runs[2]));
EXPECT_FALSE(runs[2].fakedFont.fakery.isFakeBold());
EXPECT_FALSE(runs[2].fakedFont.fakery.isFakeItalic());
EXPECT_EQ(3, runs[3].start);
EXPECT_EQ(4, runs[3].end);
- EXPECT_EQ(kZH_HansFont, getFontPath(runs[3]));
+ EXPECT_EQ(kZH_HansFont, getFontName(runs[3]));
EXPECT_FALSE(runs[3].fakedFont.fakery.isFakeBold());
EXPECT_FALSE(runs[3].fakedFont.fakery.isFakeItalic());
EXPECT_EQ(4, runs[4].start);
EXPECT_EQ(5, runs[4].end);
- EXPECT_EQ(kLatinFont, getFontPath(runs[4]));
+ EXPECT_EQ(kLatinFont, getFontName(runs[4]));
EXPECT_FALSE(runs[4].fakedFont.fakery.isFakeBold());
EXPECT_FALSE(runs[4].fakedFont.fakery.isFakeItalic());
}
-TEST_F(FontCollectionItemizeTest, itemize_variationSelector) {
- std::shared_ptr<FontCollection> collection(getFontCollection(kTestFontDir, kItemizeFontXml));
+TEST(FontCollectionItemizeTest, itemize_variationSelector) {
+ auto collection = buildFontCollectionFromXml(kItemizeFontXml);
std::vector<FontCollection::Run> runs;
// A glyph for U+4FAE is provided by both Japanese font and Simplified
// Chinese font. Also a glyph for U+242EE is provided by both Japanese and
// Traditional Chinese font. To avoid effects of device default locale,
// explicitly specify the locale.
- FontStyle kZH_HansStyle = FontStyle(FontStyle::registerLanguageList("zh_Hans"));
- FontStyle kZH_HantStyle = FontStyle(FontStyle::registerLanguageList("zh_Hant"));
// U+4FAE is available in both zh_Hans and ja font, but U+4FAE,U+FE00 is
// only available in ja font.
- itemize(collection, "U+4FAE", kZH_HansStyle, &runs);
+ itemize(collection, "U+4FAE", "zh-Hans", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(1, runs[0].end);
- EXPECT_EQ(kZH_HansFont, getFontPath(runs[0]));
+ EXPECT_EQ(kZH_HansFont, getFontName(runs[0]));
- itemize(collection, "U+4FAE U+FE00", kZH_HansStyle, &runs);
+ itemize(collection, "U+4FAE U+FE00", "zh-Hans", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
- EXPECT_EQ(kJAFont, getFontPath(runs[0]));
+ EXPECT_EQ(kJAFont, getFontName(runs[0]));
- itemize(collection, "U+4FAE U+4FAE U+FE00", kZH_HansStyle, &runs);
+ itemize(collection, "U+4FAE U+4FAE U+FE00", "zh-Hans", &runs);
ASSERT_EQ(2U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(1, runs[0].end);
- EXPECT_EQ(kZH_HansFont, getFontPath(runs[0]));
+ EXPECT_EQ(kZH_HansFont, getFontName(runs[0]));
EXPECT_EQ(1, runs[1].start);
EXPECT_EQ(3, runs[1].end);
- EXPECT_EQ(kJAFont, getFontPath(runs[1]));
+ EXPECT_EQ(kJAFont, getFontName(runs[1]));
- itemize(collection, "U+4FAE U+4FAE U+FE00 U+4FAE", kZH_HansStyle, &runs);
+ itemize(collection, "U+4FAE U+4FAE U+FE00 U+4FAE", "zh-Hans", &runs);
ASSERT_EQ(3U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(1, runs[0].end);
- EXPECT_EQ(kZH_HansFont, getFontPath(runs[0]));
+ EXPECT_EQ(kZH_HansFont, getFontName(runs[0]));
EXPECT_EQ(1, runs[1].start);
EXPECT_EQ(3, runs[1].end);
- EXPECT_EQ(kJAFont, getFontPath(runs[1]));
+ EXPECT_EQ(kJAFont, getFontName(runs[1]));
EXPECT_EQ(3, runs[2].start);
EXPECT_EQ(4, runs[2].end);
- EXPECT_EQ(kZH_HansFont, getFontPath(runs[2]));
+ EXPECT_EQ(kZH_HansFont, getFontName(runs[2]));
// Validation selector after validation selector.
- itemize(collection, "U+4FAE U+FE00 U+FE00", kZH_HansStyle, &runs);
+ itemize(collection, "U+4FAE U+FE00 U+FE00", "zh-Hans", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(3, runs[0].end);
- EXPECT_EQ(kJAFont, getFontPath(runs[1]));
+ EXPECT_EQ(kJAFont, getFontName(runs[0]));
// No font supports U+242EE U+FE0E.
- itemize(collection, "U+4FAE U+FE0E", kZH_HansStyle, &runs);
+ itemize(collection, "U+4FAE U+FE0E", "zh-Hans", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
- EXPECT_EQ(kZH_HansFont, getFontPath(runs[0]));
+ EXPECT_EQ(kZH_HansFont, getFontName(runs[0]));
// Surrogate pairs handling.
// U+242EE is available in ja font and zh_Hant font.
// U+242EE U+FE00 is available only in ja font.
- itemize(collection, "U+242EE", kZH_HantStyle, &runs);
+ itemize(collection, "U+242EE", "zh-Hant", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
- EXPECT_EQ(kZH_HantFont, getFontPath(runs[0]));
+ EXPECT_EQ(kZH_HantFont, getFontName(runs[0]));
- itemize(collection, "U+242EE U+FE00", kZH_HantStyle, &runs);
+ itemize(collection, "U+242EE U+FE00", "zh-Hant", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(3, runs[0].end);
- EXPECT_EQ(kJAFont, getFontPath(runs[0]));
+ EXPECT_EQ(kJAFont, getFontName(runs[0]));
- itemize(collection, "U+242EE U+242EE U+FE00", kZH_HantStyle, &runs);
+ itemize(collection, "U+242EE U+242EE U+FE00", "zh-Hant", &runs);
ASSERT_EQ(2U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
- EXPECT_EQ(kZH_HantFont, getFontPath(runs[0]));
+ EXPECT_EQ(kZH_HantFont, getFontName(runs[0]));
EXPECT_EQ(2, runs[1].start);
EXPECT_EQ(5, runs[1].end);
- EXPECT_EQ(kJAFont, getFontPath(runs[1]));
+ EXPECT_EQ(kJAFont, getFontName(runs[1]));
- itemize(collection, "U+242EE U+242EE U+FE00 U+242EE", kZH_HantStyle, &runs);
+ itemize(collection, "U+242EE U+242EE U+FE00 U+242EE", "zh-Hant", &runs);
ASSERT_EQ(3U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
- EXPECT_EQ(kZH_HantFont, getFontPath(runs[0]));
+ EXPECT_EQ(kZH_HantFont, getFontName(runs[0]));
EXPECT_EQ(2, runs[1].start);
EXPECT_EQ(5, runs[1].end);
- EXPECT_EQ(kJAFont, getFontPath(runs[1]));
+ EXPECT_EQ(kJAFont, getFontName(runs[1]));
EXPECT_EQ(5, runs[2].start);
EXPECT_EQ(7, runs[2].end);
- EXPECT_EQ(kZH_HantFont, getFontPath(runs[2]));
+ EXPECT_EQ(kZH_HantFont, getFontName(runs[2]));
// Validation selector after validation selector.
- itemize(collection, "U+242EE U+FE00 U+FE00", kZH_HansStyle, &runs);
+ itemize(collection, "U+242EE U+FE00 U+FE00", "zh-Hans", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(4, runs[0].end);
- EXPECT_EQ(kJAFont, getFontPath(runs[0]));
+ EXPECT_EQ(kJAFont, getFontName(runs[0]));
// No font supports U+242EE U+FE0E
- itemize(collection, "U+242EE U+FE0E", kZH_HantStyle, &runs);
+ itemize(collection, "U+242EE U+FE0E", "zh-Hant", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(3, runs[0].end);
- EXPECT_EQ(kZH_HantFont, getFontPath(runs[0]));
+ EXPECT_EQ(kZH_HantFont, getFontName(runs[0]));
// Isolated variation selector supplement.
- itemize(collection, "U+FE00", FontStyle(), &runs);
+ itemize(collection, "U+FE00", "", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(1, runs[0].end);
- EXPECT_TRUE(runs[0].fakedFont.font == nullptr || kLatinFont == getFontPath(runs[0]));
+ EXPECT_TRUE(runs[0].fakedFont.font == nullptr || kLatinFont == getFontName(runs[0]));
- itemize(collection, "U+FE00", kZH_HantStyle, &runs);
+ itemize(collection, "U+FE00", "zh-Hant", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(1, runs[0].end);
- EXPECT_TRUE(runs[0].fakedFont.font == nullptr || kLatinFont == getFontPath(runs[0]));
+ EXPECT_TRUE(runs[0].fakedFont.font == nullptr || kLatinFont == getFontName(runs[0]));
// First font family (Regular.ttf) supports U+203C but doesn't support U+203C U+FE0F.
// Emoji.ttf font supports U+203C U+FE0F. Emoji.ttf should be selected.
- itemize(collection, "U+203C U+FE0F", kZH_HantStyle, &runs);
+ itemize(collection, "U+203C U+FE0F", "zh-Hant", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
- EXPECT_EQ(kEmojiFont, getFontPath(runs[0]));
+ EXPECT_EQ(kEmojiFont, getFontName(runs[0]));
// First font family (Regular.ttf) supports U+203C U+FE0E.
- itemize(collection, "U+203C U+FE0E", kZH_HantStyle, &runs);
+ itemize(collection, "U+203C U+FE0E", "zh-Hant", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
- EXPECT_EQ(kLatinFont, getFontPath(runs[0]));
+ EXPECT_EQ(kLatinFont, getFontName(runs[0]));
}
-TEST_F(FontCollectionItemizeTest, itemize_variationSelectorSupplement) {
- std::shared_ptr<FontCollection> collection(getFontCollection(kTestFontDir, kItemizeFontXml));
+TEST(FontCollectionItemizeTest, itemize_variationSelectorSupplement) {
+ auto collection = buildFontCollectionFromXml(kItemizeFontXml);
std::vector<FontCollection::Run> runs;
// A glyph for U+845B is provided by both Japanese font and Simplified
// Chinese font. Also a glyph for U+242EE is provided by both Japanese and
// Traditional Chinese font. To avoid effects of device default locale,
// explicitly specify the locale.
- FontStyle kZH_HansStyle = FontStyle(FontStyle::registerLanguageList("zh_Hans"));
- FontStyle kZH_HantStyle = FontStyle(FontStyle::registerLanguageList("zh_Hant"));
// U+845B is available in both zh_Hans and ja font, but U+845B,U+E0100 is
// only available in ja font.
- itemize(collection, "U+845B", kZH_HansStyle, &runs);
+ itemize(collection, "U+845B", "zh-Hans", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(1, runs[0].end);
- EXPECT_EQ(kZH_HansFont, getFontPath(runs[0]));
+ EXPECT_EQ(kZH_HansFont, getFontName(runs[0]));
- itemize(collection, "U+845B U+E0100", kZH_HansStyle, &runs);
+ itemize(collection, "U+845B U+E0100", "zh-Hans", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(3, runs[0].end);
- EXPECT_EQ(kJAFont, getFontPath(runs[0]));
+ EXPECT_EQ(kJAFont, getFontName(runs[0]));
- itemize(collection, "U+845B U+845B U+E0100", kZH_HansStyle, &runs);
+ itemize(collection, "U+845B U+845B U+E0100", "zh-Hans", &runs);
ASSERT_EQ(2U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(1, runs[0].end);
- EXPECT_EQ(kZH_HansFont, getFontPath(runs[0]));
+ EXPECT_EQ(kZH_HansFont, getFontName(runs[0]));
EXPECT_EQ(1, runs[1].start);
EXPECT_EQ(4, runs[1].end);
- EXPECT_EQ(kJAFont, getFontPath(runs[1]));
+ EXPECT_EQ(kJAFont, getFontName(runs[1]));
- itemize(collection, "U+845B U+845B U+E0100 U+845B", kZH_HansStyle, &runs);
+ itemize(collection, "U+845B U+845B U+E0100 U+845B", "zh-Hans", &runs);
ASSERT_EQ(3U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(1, runs[0].end);
- EXPECT_EQ(kZH_HansFont, getFontPath(runs[0]));
+ EXPECT_EQ(kZH_HansFont, getFontName(runs[0]));
EXPECT_EQ(1, runs[1].start);
EXPECT_EQ(4, runs[1].end);
- EXPECT_EQ(kJAFont, getFontPath(runs[1]));
+ EXPECT_EQ(kJAFont, getFontName(runs[1]));
EXPECT_EQ(4, runs[2].start);
EXPECT_EQ(5, runs[2].end);
- EXPECT_EQ(kZH_HansFont, getFontPath(runs[2]));
+ EXPECT_EQ(kZH_HansFont, getFontName(runs[2]));
// Validation selector after validation selector.
- itemize(collection, "U+845B U+E0100 U+E0100", kZH_HansStyle, &runs);
+ itemize(collection, "U+845B U+E0100 U+E0100", "zh-Hans", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(5, runs[0].end);
- EXPECT_EQ(kJAFont, getFontPath(runs[0]));
+ EXPECT_EQ(kJAFont, getFontName(runs[0]));
// No font supports U+845B U+E01E0.
- itemize(collection, "U+845B U+E01E0", kZH_HansStyle, &runs);
+ itemize(collection, "U+845B U+E01E0", "zh-Hans", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(3, runs[0].end);
- EXPECT_EQ(kZH_HansFont, getFontPath(runs[0]));
+ EXPECT_EQ(kZH_HansFont, getFontName(runs[0]));
// Isolated variation selector supplement
// Surrogate pairs handling.
// U+242EE is available in ja font and zh_Hant font.
// U+242EE U+E0100 is available only in ja font.
- itemize(collection, "U+242EE", kZH_HantStyle, &runs);
+ itemize(collection, "U+242EE", "zh-Hant", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
- EXPECT_EQ(kZH_HantFont, getFontPath(runs[0]));
+ EXPECT_EQ(kZH_HantFont, getFontName(runs[0]));
- itemize(collection, "U+242EE U+E0101", kZH_HantStyle, &runs);
+ itemize(collection, "U+242EE U+E0101", "zh-Hant", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(4, runs[0].end);
- EXPECT_EQ(kJAFont, getFontPath(runs[0]));
+ EXPECT_EQ(kJAFont, getFontName(runs[0]));
- itemize(collection, "U+242EE U+242EE U+E0101", kZH_HantStyle, &runs);
+ itemize(collection, "U+242EE U+242EE U+E0101", "zh-Hant", &runs);
ASSERT_EQ(2U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
- EXPECT_EQ(kZH_HantFont, getFontPath(runs[0]));
+ EXPECT_EQ(kZH_HantFont, getFontName(runs[0]));
EXPECT_EQ(2, runs[1].start);
EXPECT_EQ(6, runs[1].end);
- EXPECT_EQ(kJAFont, getFontPath(runs[1]));
+ EXPECT_EQ(kJAFont, getFontName(runs[1]));
- itemize(collection, "U+242EE U+242EE U+E0101 U+242EE", kZH_HantStyle, &runs);
+ itemize(collection, "U+242EE U+242EE U+E0101 U+242EE", "zh-Hant", &runs);
ASSERT_EQ(3U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
- EXPECT_EQ(kZH_HantFont, getFontPath(runs[0]));
+ EXPECT_EQ(kZH_HantFont, getFontName(runs[0]));
EXPECT_EQ(2, runs[1].start);
EXPECT_EQ(6, runs[1].end);
- EXPECT_EQ(kJAFont, getFontPath(runs[1]));
+ EXPECT_EQ(kJAFont, getFontName(runs[1]));
EXPECT_EQ(6, runs[2].start);
EXPECT_EQ(8, runs[2].end);
- EXPECT_EQ(kZH_HantFont, getFontPath(runs[2]));
+ EXPECT_EQ(kZH_HantFont, getFontName(runs[2]));
// Validation selector after validation selector.
- itemize(collection, "U+242EE U+E0100 U+E0100", kZH_HantStyle, &runs);
+ itemize(collection, "U+242EE U+E0100 U+E0100", "zh-Hant", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(6, runs[0].end);
- EXPECT_EQ(kJAFont, getFontPath(runs[0]));
+ EXPECT_EQ(kJAFont, getFontName(runs[0]));
// No font supports U+242EE U+E01E0.
- itemize(collection, "U+242EE U+E01E0", kZH_HantStyle, &runs);
+ itemize(collection, "U+242EE U+E01E0", "zh-Hant", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(4, runs[0].end);
- EXPECT_EQ(kZH_HantFont, getFontPath(runs[0]));
+ EXPECT_EQ(kZH_HantFont, getFontName(runs[0]));
// Isolated variation selector supplement.
- itemize(collection, "U+E0100", FontStyle(), &runs);
+ itemize(collection, "U+E0100", "", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
- EXPECT_TRUE(runs[0].fakedFont.font == nullptr || kLatinFont == getFontPath(runs[0]));
+ EXPECT_TRUE(runs[0].fakedFont.font == nullptr || kLatinFont == getFontName(runs[0]));
- itemize(collection, "U+E0100", kZH_HantStyle, &runs);
+ itemize(collection, "U+E0100", "zh-Hant", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
- EXPECT_TRUE(runs[0].fakedFont.font == nullptr || kLatinFont == getFontPath(runs[0]));
+ EXPECT_TRUE(runs[0].fakedFont.font == nullptr || kLatinFont == getFontName(runs[0]));
}
-TEST_F(FontCollectionItemizeTest, itemize_no_crash) {
- std::shared_ptr<FontCollection> collection(getFontCollection(kTestFontDir, kItemizeFontXml));
+TEST(FontCollectionItemizeTest, itemize_no_crash) {
+ auto collection = buildFontCollectionFromXml(kItemizeFontXml);
std::vector<FontCollection::Run> runs;
// Broken Surrogate pairs. Check only not crashing.
- itemize(collection, "'a' U+D83D 'a'", FontStyle(), &runs);
- itemize(collection, "'a' U+DC69 'a'", FontStyle(), &runs);
- itemize(collection, "'a' U+D83D U+D83D 'a'", FontStyle(), &runs);
- itemize(collection, "'a' U+DC69 U+DC69 'a'", FontStyle(), &runs);
+ itemize(collection, "'a' U+D83D 'a'", &runs);
+ itemize(collection, "'a' U+DC69 'a'", &runs);
+ itemize(collection, "'a' U+D83D U+D83D 'a'", &runs);
+ itemize(collection, "'a' U+DC69 U+DC69 'a'", &runs);
// Isolated variation selector. Check only not crashing.
- itemize(collection, "U+FE00 U+FE00", FontStyle(), &runs);
- itemize(collection, "U+E0100 U+E0100", FontStyle(), &runs);
- itemize(collection, "U+FE00 U+E0100", FontStyle(), &runs);
- itemize(collection, "U+E0100 U+FE00", FontStyle(), &runs);
+ itemize(collection, "U+FE00 U+FE00", &runs);
+ itemize(collection, "U+E0100 U+E0100", &runs);
+ itemize(collection, "U+FE00 U+E0100", &runs);
+ itemize(collection, "U+E0100 U+FE00", &runs);
// Tone mark only. Check only not crashing.
- itemize(collection, "U+302D", FontStyle(), &runs);
- itemize(collection, "U+302D U+302D", FontStyle(), &runs);
+ itemize(collection, "U+302D", &runs);
+ itemize(collection, "U+302D U+302D", &runs);
// Tone mark and variation selector mixed. Check only not crashing.
- itemize(collection, "U+FE00 U+302D U+E0100", FontStyle(), &runs);
+ itemize(collection, "U+FE00 U+302D U+E0100", &runs);
}
-TEST_F(FontCollectionItemizeTest, itemize_fakery) {
- std::shared_ptr<FontCollection> collection(getFontCollection(kTestFontDir, kItemizeFontXml));
+TEST(FontCollectionItemizeTest, itemize_fakery) {
+ auto collection = buildFontCollectionFromXml(kItemizeFontXml);
std::vector<FontCollection::Run> runs;
- FontStyle kJABoldStyle = FontStyle(FontStyle::registerLanguageList("ja_JP"), 0, 7, false);
- FontStyle kJAItalicStyle = FontStyle(FontStyle::registerLanguageList("ja_JP"), 0, 5, true);
- FontStyle kJABoldItalicStyle =
- FontStyle(FontStyle::registerLanguageList("ja_JP"), 0, 7, true);
+ FontStyle kBoldStyle(FontStyle::Weight::BOLD);
+ FontStyle kItalicStyle(FontStyle::Slant::ITALIC);
+ FontStyle kBoldItalicStyle(FontStyle::Weight::BOLD, FontStyle::Slant::ITALIC);
// Currently there is no italic or bold font for Japanese. FontFakery has
// the differences between desired and actual font style.
// All Japanese Hiragana characters.
- itemize(collection, "U+3042 U+3044 U+3046 U+3048 U+304A", kJABoldStyle, &runs);
+ itemize(collection, "U+3042 U+3044 U+3046 U+3048 U+304A", kBoldStyle, "ja-JP", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(5, runs[0].end);
- EXPECT_EQ(kJAFont, getFontPath(runs[0]));
+ EXPECT_EQ(kJAFont, getFontName(runs[0]));
EXPECT_TRUE(runs[0].fakedFont.fakery.isFakeBold());
EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());
// All Japanese Hiragana characters.
- itemize(collection, "U+3042 U+3044 U+3046 U+3048 U+304A", kJAItalicStyle, &runs);
+ itemize(collection, "U+3042 U+3044 U+3046 U+3048 U+304A", kItalicStyle, "ja-JP", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(5, runs[0].end);
- EXPECT_EQ(kJAFont, getFontPath(runs[0]));
+ EXPECT_EQ(kJAFont, getFontName(runs[0]));
EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());
EXPECT_TRUE(runs[0].fakedFont.fakery.isFakeItalic());
// All Japanese Hiragana characters.
- itemize(collection, "U+3042 U+3044 U+3046 U+3048 U+304A", kJABoldItalicStyle, &runs);
+ itemize(collection, "U+3042 U+3044 U+3046 U+3048 U+304A", kBoldItalicStyle, "ja-JP", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(5, runs[0].end);
- EXPECT_EQ(kJAFont, getFontPath(runs[0]));
+ EXPECT_EQ(kJAFont, getFontName(runs[0]));
EXPECT_TRUE(runs[0].fakedFont.fakery.isFakeBold());
EXPECT_TRUE(runs[0].fakedFont.fakery.isFakeItalic());
}
-TEST_F(FontCollectionItemizeTest, itemize_vs_sequence_but_no_base_char) {
+TEST(FontCollectionItemizeTest, itemize_vs_sequence_but_no_base_char) {
// kVSTestFont supports U+717D U+FE02 but doesn't support U+717D.
// kVSTestFont should be selected for U+717D U+FE02 even if it does not support the base code
// point.
- const std::string kVSTestFont = kTestFontDir "VariationSelectorTest-Regular.ttf";
+ const std::string kVSTestFont = "VariationSelectorTest-Regular.ttf";
std::vector<std::shared_ptr<FontFamily>> families;
- std::shared_ptr<MinikinFont> font(new MinikinFontForTest(kLatinFont));
- std::shared_ptr<FontFamily> family1(new FontFamily(VARIANT_DEFAULT,
- std::vector<Font>{ Font(font, FontStyle()) }));
- families.push_back(family1);
-
- std::shared_ptr<MinikinFont> font2(new MinikinFontForTest(kVSTestFont));
- std::shared_ptr<FontFamily> family2(new FontFamily(VARIANT_DEFAULT,
- std::vector<Font>{ Font(font2, FontStyle()) }));
- families.push_back(family2);
+ families.push_back(buildFontFamily(kLatinFont));
+ families.push_back(buildFontFamily(kVSTestFont));
std::shared_ptr<FontCollection> collection(new FontCollection(families));
std::vector<FontCollection::Run> runs;
- itemize(collection, "U+717D U+FE02", FontStyle(), &runs);
+ itemize(collection, "U+717D U+FE02", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
- EXPECT_EQ(kVSTestFont, getFontPath(runs[0]));
+ EXPECT_EQ(kVSTestFont, getFontName(runs[0]));
}
-TEST_F(FontCollectionItemizeTest, itemize_format_chars) {
- std::shared_ptr<FontCollection> collection(getFontCollection(kTestFontDir, kItemizeFontXml));
+TEST(FontCollectionItemizeTest, itemize_format_chars) {
+ auto collection = buildFontCollectionFromXml(kItemizeFontXml);
std::vector<FontCollection::Run> runs;
- const FontStyle kDefaultFontStyle;
-
- itemize(collection, "'a' U+061C 'b'", kDefaultFontStyle, &runs);
+ itemize(collection, "'a' U+061C 'b'", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(3, runs[0].end);
- EXPECT_EQ(kLatinFont, getFontPath(runs[0]));
+ EXPECT_EQ(kLatinFont, getFontName(runs[0]));
- itemize(collection, "'a' U+200D 'b'", kDefaultFontStyle, &runs);
+ itemize(collection, "'a' U+200D 'b'", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(3, runs[0].end);
- EXPECT_EQ(kLatinFont, getFontPath(runs[0]));
+ EXPECT_EQ(kLatinFont, getFontName(runs[0]));
- itemize(collection, "U+3042 U+061C U+3042", kDefaultFontStyle, &runs);
+ itemize(collection, "U+3042 U+061C U+3042", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(3, runs[0].end);
- EXPECT_EQ(kJAFont, getFontPath(runs[0]));
+ EXPECT_EQ(kJAFont, getFontName(runs[0]));
- itemize(collection, "U+061C 'b'", kDefaultFontStyle, &runs);
+ itemize(collection, "U+061C 'b'", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
- EXPECT_EQ(kLatinFont, getFontPath(runs[0]));
+ EXPECT_EQ(kLatinFont, getFontName(runs[0]));
- itemize(collection, "U+061C U+3042", kDefaultFontStyle, &runs);
+ itemize(collection, "U+061C U+3042", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
- EXPECT_EQ(kJAFont, getFontPath(runs[0]));
+ EXPECT_EQ(kJAFont, getFontName(runs[0]));
- itemize(collection, "U+061C", kDefaultFontStyle, &runs);
+ itemize(collection, "U+061C", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(1, runs[0].end);
- EXPECT_EQ(kLatinFont, getFontPath(runs[0]));
+ EXPECT_EQ(kLatinFont, getFontName(runs[0]));
- itemize(collection, "U+061C U+061C U+061C", kDefaultFontStyle, &runs);
+ itemize(collection, "U+061C U+061C U+061C", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(3, runs[0].end);
- EXPECT_EQ(kLatinFont, getFontPath(runs[0]));
+ EXPECT_EQ(kLatinFont, getFontName(runs[0]));
- itemize(collection, "U+200D U+20E3", kDefaultFontStyle, &runs);
+ itemize(collection, "U+200D U+20E3", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
- EXPECT_EQ(kEmojiFont, getFontPath(runs[0]));
+ EXPECT_EQ(kEmojiFont, getFontName(runs[0]));
- itemize(collection, "U+200D", kDefaultFontStyle, &runs);
+ itemize(collection, "U+200D", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(1, runs[0].end);
- EXPECT_EQ(kLatinFont, getFontPath(runs[0]));
+ EXPECT_EQ(kLatinFont, getFontName(runs[0]));
- itemize(collection, "U+20E3", kDefaultFontStyle, &runs);
+ itemize(collection, "U+20E3", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(1, runs[0].end);
- EXPECT_EQ(kEmojiFont, getFontPath(runs[0]));
+ EXPECT_EQ(kEmojiFont, getFontName(runs[0]));
}
-TEST_F(FontCollectionItemizeTest, itemize_LanguageScore) {
+TEST(FontCollectionItemizeTest, itemize_LocaleScore) {
struct TestCase {
- std::string userPreferredLanguages;
- std::vector<std::string> fontLanguages;
+ std::string userPreferredLocale;
+ std::vector<std::string> fontLocales;
int selectedFontIndex;
} testCases[] = {
- // Font can specify empty language.
- { "und", { "", "" }, 0 },
- { "und", { "", "en-Latn" }, 0 },
- { "en-Latn", { "", "" }, 0 },
- { "en-Latn", { "", "en-Latn" }, 1 },
+ // Font can specify empty locale.
+ {"und", {"", ""}, 0},
+ {"und", {"", "en-Latn"}, 0},
+ {"en-Latn", {"", ""}, 0},
+ {"en-Latn", {"", "en-Latn"}, 1},
- // Single user preferred language.
- // Exact match case
- { "en-Latn", { "en-Latn", "ja-Jpan" }, 0 },
- { "ja-Jpan", { "en-Latn", "ja-Jpan" }, 1 },
- { "en-Latn", { "en-Latn", "nl-Latn", "es-Latn" }, 0 },
- { "nl-Latn", { "en-Latn", "nl-Latn", "es-Latn" }, 1 },
- { "es-Latn", { "en-Latn", "nl-Latn", "es-Latn" }, 2 },
- { "es-Latn", { "en-Latn", "en-Latn", "nl-Latn" }, 0 },
+ // Single user preferred locale.
+ // Exact match case
+ {"en-Latn", {"en-Latn", "ja-Jpan"}, 0},
+ {"ja-Jpan", {"en-Latn", "ja-Jpan"}, 1},
+ {"en-Latn", {"en-Latn", "nl-Latn", "es-Latn"}, 0},
+ {"nl-Latn", {"en-Latn", "nl-Latn", "es-Latn"}, 1},
+ {"es-Latn", {"en-Latn", "nl-Latn", "es-Latn"}, 2},
+ {"es-Latn", {"en-Latn", "en-Latn", "nl-Latn"}, 0},
- // Exact script match case
- { "en-Latn", { "nl-Latn", "e-Latn" }, 0 },
- { "en-Arab", { "nl-Latn", "ar-Arab" }, 1 },
- { "en-Latn", { "be-Latn", "ar-Arab", "d-Beng" }, 0 },
- { "en-Arab", { "be-Latn", "ar-Arab", "d-Beng" }, 1 },
- { "en-Beng", { "be-Latn", "ar-Arab", "d-Beng" }, 2 },
- { "en-Beng", { "be-Latn", "ar-Beng", "d-Beng" }, 1 },
- { "zh-Hant", { "zh-Hant", "zh-Hans" }, 0 },
- { "zh-Hans", { "zh-Hant", "zh-Hans" }, 1 },
+ // Exact script match case
+ {"en-Latn", {"nl-Latn", "e-Latn"}, 0},
+ {"en-Arab", {"nl-Latn", "ar-Arab"}, 1},
+ {"en-Latn", {"be-Latn", "ar-Arab", "d-Beng"}, 0},
+ {"en-Arab", {"be-Latn", "ar-Arab", "d-Beng"}, 1},
+ {"en-Beng", {"be-Latn", "ar-Arab", "d-Beng"}, 2},
+ {"en-Beng", {"be-Latn", "ar-Beng", "d-Beng"}, 1},
+ {"zh-Hant", {"zh-Hant", "zh-Hans"}, 0},
+ {"zh-Hans", {"zh-Hant", "zh-Hans"}, 1},
- // Subscript match case, e.g. Jpan supports Hira.
- { "en-Hira", { "ja-Jpan" }, 0 },
- { "zh-Hani", { "zh-Hans", "zh-Hant" }, 0 },
- { "zh-Hani", { "zh-Hant", "zh-Hans" }, 0 },
- { "en-Hira", { "zh-Hant", "ja-Jpan", "ja-Jpan" }, 1 },
+ // Subscript match case, e.g. Jpan supports Hira.
+ {"en-Hira", {"ja-Jpan"}, 0},
+ {"zh-Hani", {"zh-Hans", "zh-Hant"}, 0},
+ {"zh-Hani", {"zh-Hant", "zh-Hans"}, 0},
+ {"en-Hira", {"zh-Hant", "ja-Jpan", "ja-Jpan"}, 1},
- // Language match case
- { "ja-Latn", { "zh-Latn", "ja-Latn" }, 1 },
- { "zh-Latn", { "zh-Latn", "ja-Latn" }, 0 },
- { "ja-Latn", { "zh-Latn", "ja-Latn" }, 1 },
- { "ja-Latn", { "zh-Latn", "ja-Latn", "ja-Latn" }, 1 },
+ // Language match case
+ {"ja-Latn", {"zh-Latn", "ja-Latn"}, 1},
+ {"zh-Latn", {"zh-Latn", "ja-Latn"}, 0},
+ {"ja-Latn", {"zh-Latn", "ja-Latn"}, 1},
+ {"ja-Latn", {"zh-Latn", "ja-Latn", "ja-Latn"}, 1},
- // Mixed case
- // Script/subscript match is strongest.
- { "ja-Jpan", { "en-Latn", "ja-Latn", "en-Jpan" }, 2 },
- { "ja-Hira", { "en-Latn", "ja-Latn", "en-Jpan" }, 2 },
- { "ja-Hira", { "en-Latn", "ja-Latn", "en-Jpan", "en-Jpan" }, 2 },
+ // Mixed case
+ // Script/subscript match is strongest.
+ {"ja-Jpan", {"en-Latn", "ja-Latn", "en-Jpan"}, 2},
+ {"ja-Hira", {"en-Latn", "ja-Latn", "en-Jpan"}, 2},
+ {"ja-Hira", {"en-Latn", "ja-Latn", "en-Jpan", "en-Jpan"}, 2},
- // Language match only happens if the script matches.
- { "ja-Hira", { "en-Latn", "ja-Latn" }, 0 },
- { "ja-Hira", { "en-Jpan", "ja-Jpan" }, 1 },
+ // Language match only happens if the script matches.
+ {"ja-Hira", {"en-Latn", "ja-Latn"}, 0},
+ {"ja-Hira", {"en-Jpan", "ja-Jpan"}, 1},
- // Multiple languages.
- // Even if all fonts have the same score, use the 2nd language for better selection.
- { "en-Latn,ja-Jpan", { "zh-Hant", "zh-Hans", "ja-Jpan" }, 2 },
- { "en-Latn,nl-Latn", { "es-Latn", "be-Latn", "nl-Latn" }, 2 },
- { "en-Latn,br-Latn,nl-Latn", { "es-Latn", "be-Latn", "nl-Latn" }, 2 },
- { "en-Latn,br-Latn,nl-Latn", { "es-Latn", "be-Latn", "nl-Latn", "nl-Latn" }, 2 },
+ // Multiple locales.
+ // Even if all fonts have the same score, use the 2nd locale for better selection.
+ {"en-Latn,ja-Jpan", {"zh-Hant", "zh-Hans", "ja-Jpan"}, 2},
+ {"en-Latn,nl-Latn", {"es-Latn", "be-Latn", "nl-Latn"}, 2},
+ {"en-Latn,br-Latn,nl-Latn", {"es-Latn", "be-Latn", "nl-Latn"}, 2},
+ {"en-Latn,br-Latn,nl-Latn", {"es-Latn", "be-Latn", "nl-Latn", "nl-Latn"}, 2},
- // Script score.
- { "en-Latn,ja-Jpan", { "en-Arab", "en-Jpan" }, 1 },
- { "en-Latn,ja-Jpan", { "en-Arab", "en-Jpan", "en-Jpan" }, 1 },
+ // Script score.
+ {"en-Latn,ja-Jpan", {"en-Arab", "en-Jpan"}, 1},
+ {"en-Latn,ja-Jpan", {"en-Arab", "en-Jpan", "en-Jpan"}, 1},
- // Language match case
- { "en-Latn,ja-Latn", { "bd-Latn", "ja-Latn" }, 1 },
- { "en-Latn,ja-Latn", { "bd-Latn", "ja-Latn", "ja-Latn" }, 1 },
+ // Language match case
+ {"en-Latn,ja-Latn", {"bd-Latn", "ja-Latn"}, 1},
+ {"en-Latn,ja-Latn", {"bd-Latn", "ja-Latn", "ja-Latn"}, 1},
- // Language match only happens if the script matches.
- { "en-Latn,ar-Arab", { "en-Beng", "ar-Arab" }, 1 },
+ // Language match only happens if the script matches.
+ {"en-Latn,ar-Arab", {"en-Beng", "ar-Arab"}, 1},
- // Multiple languages in the font settings.
- { "ko-Jamo", { "ja-Jpan", "ko-Kore", "ko-Kore,ko-Jamo"}, 2 },
- { "en-Latn", { "ja-Jpan", "en-Latn,ja-Jpan"}, 1 },
- { "en-Latn", { "ja-Jpan", "ja-Jpan,en-Latn"}, 1 },
- { "en-Latn", { "ja-Jpan,zh-Hant", "en-Latn,ja-Jpan", "en-Latn"}, 1 },
- { "en-Latn", { "zh-Hant,ja-Jpan", "ja-Jpan,en-Latn", "en-Latn"}, 1 },
+ // Multiple locales in the font settings.
+ {"ko-Jamo", {"ja-Jpan", "ko-Kore", "ko-Kore,ko-Jamo"}, 2},
+ {"en-Latn", {"ja-Jpan", "en-Latn,ja-Jpan"}, 1},
+ {"en-Latn", {"ja-Jpan", "ja-Jpan,en-Latn"}, 1},
+ {"en-Latn", {"ja-Jpan,zh-Hant", "en-Latn,ja-Jpan", "en-Latn"}, 1},
+ {"en-Latn", {"zh-Hant,ja-Jpan", "ja-Jpan,en-Latn", "en-Latn"}, 1},
- // Kore = Hang + Hani, etc.
- { "ko-Kore", { "ko-Hang", "ko-Jamo,ko-Hani", "ko-Hang,ko-Hani"}, 2 },
- { "ja-Hrkt", { "ja-Hira", "ja-Kana", "ja-Hira,ja-Kana"}, 2 },
- { "ja-Jpan", { "ja-Hira", "ja-Kana", "ja-Hani", "ja-Hira,ja-Kana,ja-Hani"}, 3 },
- { "zh-Hanb", { "zh-Hant", "zh-Bopo", "zh-Hant,zh-Bopo"}, 2 },
- { "zh-Hanb", { "ja-Hanb", "zh-Hant,zh-Bopo"}, 1 },
+ // Kore = Hang + Hani, etc.
+ {"ko-Kore", {"ko-Hang", "ko-Jamo,ko-Hani", "ko-Hang,ko-Hani"}, 2},
+ {"ja-Hrkt", {"ja-Hira", "ja-Kana", "ja-Hira,ja-Kana"}, 2},
+ {"ja-Jpan", {"ja-Hira", "ja-Kana", "ja-Hani", "ja-Hira,ja-Kana,ja-Hani"}, 3},
+ {"zh-Hanb", {"zh-Hant", "zh-Bopo", "zh-Hant,zh-Bopo"}, 2},
+ {"zh-Hanb", {"ja-Hanb", "zh-Hant,zh-Bopo"}, 1},
- // Language match with unified subscript bits.
- { "zh-Hanb", { "zh-Hant", "zh-Bopo", "ja-Hant,ja-Bopo", "zh-Hant,zh-Bopo"}, 3 },
- { "zh-Hanb", { "zh-Hant", "zh-Bopo", "ja-Hant,zh-Bopo", "zh-Hant,zh-Bopo"}, 3 },
+ // Language match with unified subscript bits.
+ {"zh-Hanb", {"zh-Hant", "zh-Bopo", "ja-Hant,ja-Bopo", "zh-Hant,zh-Bopo"}, 3},
+ {"zh-Hanb", {"zh-Hant", "zh-Bopo", "ja-Hant,zh-Bopo", "zh-Hant,zh-Bopo"}, 3},
- // Two elements subtag matching: language and subtag or language or script.
- { "ja-Kana-u-em-emoji", { "zh-Hant", "ja-Kana"}, 1 },
- { "ja-Kana-u-em-emoji", { "zh-Hant", "ja-Kana", "ja-Zsye"}, 2 },
- { "ja-Zsym-u-em-emoji", { "ja-Kana", "ja-Zsym", "ja-Zsye"}, 2 },
+ // Two elements subtag matching: language and subtag or language or script.
+ {"ja-Kana-u-em-emoji", {"zh-Hant", "ja-Kana"}, 1},
+ {"ja-Kana-u-em-emoji", {"zh-Hant", "ja-Kana", "ja-Zsye"}, 2},
+ {"ja-Zsym-u-em-emoji", {"ja-Kana", "ja-Zsym", "ja-Zsye"}, 2},
- // One element subtag matching: subtag only or script only.
- { "en-Latn-u-em-emoji", { "ja-Latn", "ja-Zsye"}, 1 },
- { "en-Zsym-u-em-emoji", { "ja-Zsym", "ja-Zsye"}, 1 },
- { "en-Zsye-u-em-text", { "ja-Zsym", "ja-Zsye"}, 0 },
+ // One element subtag matching: subtag only or script only.
+ {"en-Latn-u-em-emoji", {"ja-Latn", "ja-Zsye"}, 1},
+ {"en-Zsym-u-em-emoji", {"ja-Zsym", "ja-Zsye"}, 1},
+ {"en-Zsye-u-em-text", {"ja-Zsym", "ja-Zsye"}, 0},
- // Multiple languages list with subtags.
- { "en-Latn,ja-Jpan-u-em-text", { "en-Latn", "en-Zsye", "en-Zsym"}, 0 },
- { "en-Latn,en-Zsye,ja-Jpan-u-em-text", { "zh", "en-Zsye", "en-Zsym"}, 1 },
+ // Multiple locale list with subtags.
+ {"en-Latn,ja-Jpan-u-em-text", {"en-Latn", "en-Zsye", "en-Zsym"}, 0},
+ {"en-Latn,en-Zsye,ja-Jpan-u-em-text", {"zh", "en-Zsye", "en-Zsym"}, 1},
};
for (auto testCase : testCases) {
- std::string fontLanguagesStr = "{";
- for (size_t i = 0; i < testCase.fontLanguages.size(); ++i) {
+ std::string fontLocaleStr = "{";
+ for (size_t i = 0; i < testCase.fontLocales.size(); ++i) {
if (i != 0) {
- fontLanguagesStr += ", ";
+ fontLocaleStr += ", ";
}
- fontLanguagesStr += "\"" + testCase.fontLanguages[i] + "\"";
+ fontLocaleStr += "\"" + testCase.fontLocales[i] + "\"";
}
- fontLanguagesStr += "}";
- SCOPED_TRACE("Test of user preferred languages: \"" + testCase.userPreferredLanguages +
- "\" with font languages: " + fontLanguagesStr);
+ fontLocaleStr += "}";
+ SCOPED_TRACE("Test of user preferred locale: \"" + testCase.userPreferredLocale +
+ "\" with font locale: " + fontLocaleStr);
std::vector<std::shared_ptr<FontFamily>> families;
// Prepare first font which doesn't supports U+9AA8
- std::shared_ptr<MinikinFont> firstFamilyMinikinFont(
- new MinikinFontForTest(kNoGlyphFont));
- std::shared_ptr<FontFamily> firstFamily(new FontFamily(
- FontStyle::registerLanguageList("und"), 0 /* variant */,
- std::vector<Font>({ Font(firstFamilyMinikinFont, FontStyle()) })));
+ auto firstFamilyMinikinFont =
+ std::make_shared<FreeTypeMinikinFontForTest>(getTestFontPath(kNoGlyphFont));
+ std::vector<Font> fonts;
+ fonts.push_back(Font::Builder(firstFamilyMinikinFont).build());
+ auto firstFamily = std::make_shared<FontFamily>(
+ registerLocaleList("und"), FontFamily::Variant::DEFAULT, std::move(fonts));
families.push_back(firstFamily);
// Prepare font families
- // Each font family is associated with a specified language. All font families except for
+ // Each font family is associated with a specified locale. All font families except for
// the first font support U+9AA8.
- std::unordered_map<MinikinFont*, int> fontLangIdxMap;
+ std::unordered_map<MinikinFont*, int> fontLocaleIdxMap;
- for (size_t i = 0; i < testCase.fontLanguages.size(); ++i) {
- std::shared_ptr<MinikinFont> minikin_font(new MinikinFontForTest(kJAFont));
- std::shared_ptr<FontFamily> family(new FontFamily(
- FontStyle::registerLanguageList(testCase.fontLanguages[i]), 0 /* variant */,
- std::vector<Font>({ Font(minikin_font, FontStyle()) })));
+ for (size_t i = 0; i < testCase.fontLocales.size(); ++i) {
+ auto minikinFont =
+ std::make_shared<FreeTypeMinikinFontForTest>(getTestFontPath(kJAFont));
+ std::vector<Font> fonts;
+ fonts.push_back(Font::Builder(minikinFont).build());
+ auto family =
+ std::make_shared<FontFamily>(registerLocaleList(testCase.fontLocales[i]),
+ FontFamily::Variant::DEFAULT, std::move(fonts));
families.push_back(family);
- fontLangIdxMap.insert(std::make_pair(minikin_font.get(), i));
+ fontLocaleIdxMap.insert(std::make_pair(minikinFont.get(), i));
}
std::shared_ptr<FontCollection> collection(new FontCollection(families));
// Do itemize
- const FontStyle style = FontStyle(
- FontStyle::registerLanguageList(testCase.userPreferredLanguages));
std::vector<FontCollection::Run> runs;
- itemize(collection, "U+9AA8", style, &runs);
+ itemize(collection, "U+9AA8", testCase.userPreferredLocale, &runs);
ASSERT_EQ(1U, runs.size());
ASSERT_NE(nullptr, runs[0].fakedFont.font);
// First family doesn't support U+9AA8 and others support it, so the first font should not
// be selected.
- EXPECT_NE(firstFamilyMinikinFont.get(), runs[0].fakedFont.font);
+ EXPECT_NE(firstFamilyMinikinFont.get(), runs[0].fakedFont.font->typeface().get());
// Lookup used font family by MinikinFont*.
- const int usedLangIndex = fontLangIdxMap[runs[0].fakedFont.font];
- EXPECT_EQ(testCase.selectedFontIndex, usedLangIndex);
+ const int usedLocaleIndex = fontLocaleIdxMap[runs[0].fakedFont.font->typeface().get()];
+ EXPECT_EQ(testCase.selectedFontIndex, usedLocaleIndex);
}
}
-TEST_F(FontCollectionItemizeTest, itemize_LanguageAndCoverage) {
+TEST(FontCollectionItemizeTest, itemize_LocaleAndCoverage) {
struct TestCase {
std::string testString;
- std::string requestedLanguages;
+ std::string requestedLocales;
std::string expectedFont;
} testCases[] = {
- // Following test cases verify that following rules in font fallback chain.
- // - If the first font in the collection supports the given character or variation sequence,
- // it should be selected.
- // - If the font doesn't support the given character, variation sequence or its base
- // character, it should not be selected.
- // - If two or more fonts match the requested languages, the font matches with the highest
- // priority language should be selected.
- // - If two or more fonts get the same score, the font listed earlier in the XML file
- // (here, kItemizeFontXml) should be selected.
+ // Following test cases verify that following rules in font fallback chain.
+ // - If the first font in the collection supports the given character or variation
+ // sequence,
+ // it should be selected.
+ // - If the font doesn't support the given character, variation sequence or its base
+ // character, it should not be selected.
+ // - If two or more fonts match the requested locales, the font matches with the highest
+ // priority locale should be selected.
+ // - If two or more fonts get the same score, the font listed earlier in the XML file
+ // (here, kItemizeFontXml) should be selected.
- // Regardless of language, the first font is always selected if it covers the code point.
- { "'a'", "", kLatinFont},
- { "'a'", "en-Latn", kLatinFont},
- { "'a'", "ja-Jpan", kLatinFont},
- { "'a'", "ja-Jpan,en-Latn", kLatinFont},
- { "'a'", "zh-Hans,zh-Hant,en-Latn,ja-Jpan,fr-Latn", kLatinFont},
+ // Regardless of locale, the first font is always selected if it covers the code point.
+ {"'a'", "", kLatinFont},
+ {"'a'", "en-Latn", kLatinFont},
+ {"'a'", "ja-Jpan", kLatinFont},
+ {"'a'", "ja-Jpan,en-Latn", kLatinFont},
+ {"'a'", "zh-Hans,zh-Hant,en-Latn,ja-Jpan,fr-Latn", kLatinFont},
- // U+81ED is supported by both the ja font and zh-Hans font.
- { "U+81ED", "", kZH_HansFont }, // zh-Hans font is listed before ja font.
- { "U+81ED", "en-Latn", kZH_HansFont }, // zh-Hans font is listed before ja font.
- { "U+81ED", "ja-Jpan", kJAFont },
- { "U+81ED", "zh-Hans", kZH_HansFont },
+ // U+81ED is supported by both the ja font and zh-Hans font.
+ {"U+81ED", "", kZH_HansFont}, // zh-Hans font is listed before ja font.
+ {"U+81ED", "en-Latn", kZH_HansFont}, // zh-Hans font is listed before ja font.
+ {"U+81ED", "ja-Jpan", kJAFont},
+ {"U+81ED", "zh-Hans", kZH_HansFont},
- { "U+81ED", "ja-Jpan,en-Latn", kJAFont },
- { "U+81ED", "en-Latn,ja-Jpan", kJAFont },
- { "U+81ED", "en-Latn,zh-Hans", kZH_HansFont },
- { "U+81ED", "zh-Hans,en-Latn", kZH_HansFont },
- { "U+81ED", "ja-Jpan,zh-Hans", kJAFont },
- { "U+81ED", "zh-Hans,ja-Jpan", kZH_HansFont },
+ {"U+81ED", "ja-Jpan,en-Latn", kJAFont},
+ {"U+81ED", "en-Latn,ja-Jpan", kJAFont},
+ {"U+81ED", "en-Latn,zh-Hans", kZH_HansFont},
+ {"U+81ED", "zh-Hans,en-Latn", kZH_HansFont},
+ {"U+81ED", "ja-Jpan,zh-Hans", kJAFont},
+ {"U+81ED", "zh-Hans,ja-Jpan", kZH_HansFont},
- { "U+81ED", "en-Latn,zh-Hans,ja-Jpan", kZH_HansFont },
- { "U+81ED", "en-Latn,ja-Jpan,zh-Hans", kJAFont },
- { "U+81ED", "en-Latn,zh-Hans,ja-Jpan", kZH_HansFont },
- { "U+81ED", "ja-Jpan,en-Latn,zh-Hans", kJAFont },
- { "U+81ED", "ja-Jpan,zh-Hans,en-Latn", kJAFont },
- { "U+81ED", "zh-Hans,en-Latn,ja-Jpan", kZH_HansFont },
- { "U+81ED", "zh-Hans,ja-Jpan,en-Latn", kZH_HansFont },
+ {"U+81ED", "en-Latn,zh-Hans,ja-Jpan", kZH_HansFont},
+ {"U+81ED", "en-Latn,ja-Jpan,zh-Hans", kJAFont},
+ {"U+81ED", "en-Latn,zh-Hans,ja-Jpan", kZH_HansFont},
+ {"U+81ED", "ja-Jpan,en-Latn,zh-Hans", kJAFont},
+ {"U+81ED", "ja-Jpan,zh-Hans,en-Latn", kJAFont},
+ {"U+81ED", "zh-Hans,en-Latn,ja-Jpan", kZH_HansFont},
+ {"U+81ED", "zh-Hans,ja-Jpan,en-Latn", kZH_HansFont},
- // U+304A is only supported by ja font.
- { "U+304A", "", kJAFont },
- { "U+304A", "ja-Jpan", kJAFont },
- { "U+304A", "zh-Hant", kJAFont },
- { "U+304A", "zh-Hans", kJAFont },
+ // U+304A is only supported by ja font.
+ {"U+304A", "", kJAFont},
+ {"U+304A", "ja-Jpan", kJAFont},
+ {"U+304A", "zh-Hant", kJAFont},
+ {"U+304A", "zh-Hans", kJAFont},
- { "U+304A", "ja-Jpan,zh-Hant", kJAFont },
- { "U+304A", "zh-Hant,ja-Jpan", kJAFont },
- { "U+304A", "zh-Hans,zh-Hant", kJAFont },
- { "U+304A", "zh-Hant,zh-Hans", kJAFont },
- { "U+304A", "zh-Hans,ja-Jpan", kJAFont },
- { "U+304A", "ja-Jpan,zh-Hans", kJAFont },
+ {"U+304A", "ja-Jpan,zh-Hant", kJAFont},
+ {"U+304A", "zh-Hant,ja-Jpan", kJAFont},
+ {"U+304A", "zh-Hans,zh-Hant", kJAFont},
+ {"U+304A", "zh-Hant,zh-Hans", kJAFont},
+ {"U+304A", "zh-Hans,ja-Jpan", kJAFont},
+ {"U+304A", "ja-Jpan,zh-Hans", kJAFont},
- { "U+304A", "zh-Hans,ja-Jpan,zh-Hant", kJAFont },
- { "U+304A", "zh-Hans,zh-Hant,ja-Jpan", kJAFont },
- { "U+304A", "ja-Jpan,zh-Hans,zh-Hant", kJAFont },
- { "U+304A", "ja-Jpan,zh-Hant,zh-Hans", kJAFont },
- { "U+304A", "zh-Hant,zh-Hans,ja-Jpan", kJAFont },
- { "U+304A", "zh-Hant,ja-Jpan,zh-Hans", kJAFont },
+ {"U+304A", "zh-Hans,ja-Jpan,zh-Hant", kJAFont},
+ {"U+304A", "zh-Hans,zh-Hant,ja-Jpan", kJAFont},
+ {"U+304A", "ja-Jpan,zh-Hans,zh-Hant", kJAFont},
+ {"U+304A", "ja-Jpan,zh-Hant,zh-Hans", kJAFont},
+ {"U+304A", "zh-Hant,zh-Hans,ja-Jpan", kJAFont},
+ {"U+304A", "zh-Hant,ja-Jpan,zh-Hans", kJAFont},
- // U+242EE is supported by both ja font and zh-Hant fonts but not by zh-Hans font.
- { "U+242EE", "", kJAFont }, // ja font is listed before zh-Hant font.
- { "U+242EE", "ja-Jpan", kJAFont },
- { "U+242EE", "zh-Hans", kJAFont },
- { "U+242EE", "zh-Hant", kZH_HantFont },
+ // U+242EE is supported by both ja font and zh-Hant fonts but not by zh-Hans font.
+ {"U+242EE", "", kJAFont}, // ja font is listed before zh-Hant font.
+ {"U+242EE", "ja-Jpan", kJAFont},
+ {"U+242EE", "zh-Hans", kJAFont},
+ {"U+242EE", "zh-Hant", kZH_HantFont},
- { "U+242EE", "ja-Jpan,zh-Hant", kJAFont },
- { "U+242EE", "zh-Hant,ja-Jpan", kZH_HantFont },
- { "U+242EE", "zh-Hans,zh-Hant", kZH_HantFont },
- { "U+242EE", "zh-Hant,zh-Hans", kZH_HantFont },
- { "U+242EE", "zh-Hans,ja-Jpan", kJAFont },
- { "U+242EE", "ja-Jpan,zh-Hans", kJAFont },
+ {"U+242EE", "ja-Jpan,zh-Hant", kJAFont},
+ {"U+242EE", "zh-Hant,ja-Jpan", kZH_HantFont},
+ {"U+242EE", "zh-Hans,zh-Hant", kZH_HantFont},
+ {"U+242EE", "zh-Hant,zh-Hans", kZH_HantFont},
+ {"U+242EE", "zh-Hans,ja-Jpan", kJAFont},
+ {"U+242EE", "ja-Jpan,zh-Hans", kJAFont},
- { "U+242EE", "zh-Hans,ja-Jpan,zh-Hant", kJAFont },
- { "U+242EE", "zh-Hans,zh-Hant,ja-Jpan", kZH_HantFont },
- { "U+242EE", "ja-Jpan,zh-Hans,zh-Hant", kJAFont },
- { "U+242EE", "ja-Jpan,zh-Hant,zh-Hans", kJAFont },
- { "U+242EE", "zh-Hant,zh-Hans,ja-Jpan", kZH_HantFont },
- { "U+242EE", "zh-Hant,ja-Jpan,zh-Hans", kZH_HantFont },
+ {"U+242EE", "zh-Hans,ja-Jpan,zh-Hant", kJAFont},
+ {"U+242EE", "zh-Hans,zh-Hant,ja-Jpan", kZH_HantFont},
+ {"U+242EE", "ja-Jpan,zh-Hans,zh-Hant", kJAFont},
+ {"U+242EE", "ja-Jpan,zh-Hant,zh-Hans", kJAFont},
+ {"U+242EE", "zh-Hant,zh-Hans,ja-Jpan", kZH_HantFont},
+ {"U+242EE", "zh-Hant,ja-Jpan,zh-Hans", kZH_HantFont},
- // U+9AA8 is supported by all ja-Jpan, zh-Hans, zh-Hant fonts.
- { "U+9AA8", "", kZH_HansFont }, // zh-Hans font is listed before ja and zh-Hant fonts.
- { "U+9AA8", "ja-Jpan", kJAFont },
- { "U+9AA8", "zh-Hans", kZH_HansFont },
- { "U+9AA8", "zh-Hant", kZH_HantFont },
+ // U+9AA8 is supported by all ja-Jpan, zh-Hans, zh-Hant fonts.
+ {"U+9AA8", "", kZH_HansFont}, // zh-Hans font is listed before ja and zh-Hant fonts.
+ {"U+9AA8", "ja-Jpan", kJAFont},
+ {"U+9AA8", "zh-Hans", kZH_HansFont},
+ {"U+9AA8", "zh-Hant", kZH_HantFont},
- { "U+9AA8", "ja-Jpan,zh-Hant", kJAFont },
- { "U+9AA8", "zh-Hant,ja-Jpan", kZH_HantFont },
- { "U+9AA8", "zh-Hans,zh-Hant", kZH_HansFont },
- { "U+9AA8", "zh-Hant,zh-Hans", kZH_HantFont },
- { "U+9AA8", "zh-Hans,ja-Jpan", kZH_HansFont },
- { "U+9AA8", "ja-Jpan,zh-Hans", kJAFont },
+ {"U+9AA8", "ja-Jpan,zh-Hant", kJAFont},
+ {"U+9AA8", "zh-Hant,ja-Jpan", kZH_HantFont},
+ {"U+9AA8", "zh-Hans,zh-Hant", kZH_HansFont},
+ {"U+9AA8", "zh-Hant,zh-Hans", kZH_HantFont},
+ {"U+9AA8", "zh-Hans,ja-Jpan", kZH_HansFont},
+ {"U+9AA8", "ja-Jpan,zh-Hans", kJAFont},
- { "U+9AA8", "zh-Hans,ja-Jpan,zh-Hant", kZH_HansFont },
- { "U+9AA8", "zh-Hans,zh-Hant,ja-Jpan", kZH_HansFont },
- { "U+9AA8", "ja-Jpan,zh-Hans,zh-Hant", kJAFont },
- { "U+9AA8", "ja-Jpan,zh-Hant,zh-Hans", kJAFont },
- { "U+9AA8", "zh-Hant,zh-Hans,ja-Jpan", kZH_HantFont },
- { "U+9AA8", "zh-Hant,ja-Jpan,zh-Hans", kZH_HantFont },
+ {"U+9AA8", "zh-Hans,ja-Jpan,zh-Hant", kZH_HansFont},
+ {"U+9AA8", "zh-Hans,zh-Hant,ja-Jpan", kZH_HansFont},
+ {"U+9AA8", "ja-Jpan,zh-Hans,zh-Hant", kJAFont},
+ {"U+9AA8", "ja-Jpan,zh-Hant,zh-Hans", kJAFont},
+ {"U+9AA8", "zh-Hant,zh-Hans,ja-Jpan", kZH_HantFont},
+ {"U+9AA8", "zh-Hant,ja-Jpan,zh-Hans", kZH_HantFont},
- // U+242EE U+FE00 is supported by ja font but not by zh-Hans or zh-Hant fonts.
- { "U+242EE U+FE00", "", kJAFont },
- { "U+242EE U+FE00", "ja-Jpan", kJAFont },
- { "U+242EE U+FE00", "zh-Hant", kJAFont },
- { "U+242EE U+FE00", "zh-Hans", kJAFont },
+ // U+242EE U+FE00 is supported by ja font but not by zh-Hans or zh-Hant fonts.
+ {"U+242EE U+FE00", "", kJAFont},
+ {"U+242EE U+FE00", "ja-Jpan", kJAFont},
+ {"U+242EE U+FE00", "zh-Hant", kJAFont},
+ {"U+242EE U+FE00", "zh-Hans", kJAFont},
- { "U+242EE U+FE00", "ja-Jpan,zh-Hant", kJAFont },
- { "U+242EE U+FE00", "zh-Hant,ja-Jpan", kJAFont },
- { "U+242EE U+FE00", "zh-Hans,zh-Hant", kJAFont },
- { "U+242EE U+FE00", "zh-Hant,zh-Hans", kJAFont },
- { "U+242EE U+FE00", "zh-Hans,ja-Jpan", kJAFont },
- { "U+242EE U+FE00", "ja-Jpan,zh-Hans", kJAFont },
+ {"U+242EE U+FE00", "ja-Jpan,zh-Hant", kJAFont},
+ {"U+242EE U+FE00", "zh-Hant,ja-Jpan", kJAFont},
+ {"U+242EE U+FE00", "zh-Hans,zh-Hant", kJAFont},
+ {"U+242EE U+FE00", "zh-Hant,zh-Hans", kJAFont},
+ {"U+242EE U+FE00", "zh-Hans,ja-Jpan", kJAFont},
+ {"U+242EE U+FE00", "ja-Jpan,zh-Hans", kJAFont},
- { "U+242EE U+FE00", "zh-Hans,ja-Jpan,zh-Hant", kJAFont },
- { "U+242EE U+FE00", "zh-Hans,zh-Hant,ja-Jpan", kJAFont },
- { "U+242EE U+FE00", "ja-Jpan,zh-Hans,zh-Hant", kJAFont },
- { "U+242EE U+FE00", "ja-Jpan,zh-Hant,zh-Hans", kJAFont },
- { "U+242EE U+FE00", "zh-Hant,zh-Hans,ja-Jpan", kJAFont },
- { "U+242EE U+FE00", "zh-Hant,ja-Jpan,zh-Hans", kJAFont },
+ {"U+242EE U+FE00", "zh-Hans,ja-Jpan,zh-Hant", kJAFont},
+ {"U+242EE U+FE00", "zh-Hans,zh-Hant,ja-Jpan", kJAFont},
+ {"U+242EE U+FE00", "ja-Jpan,zh-Hans,zh-Hant", kJAFont},
+ {"U+242EE U+FE00", "ja-Jpan,zh-Hant,zh-Hans", kJAFont},
+ {"U+242EE U+FE00", "zh-Hant,zh-Hans,ja-Jpan", kJAFont},
+ {"U+242EE U+FE00", "zh-Hant,ja-Jpan,zh-Hans", kJAFont},
- // U+3402 U+E0100 is supported by both zh-Hans and zh-Hant but not by ja font.
- { "U+3402 U+E0100", "", kZH_HansFont }, // zh-Hans font is listed before zh-Hant font.
- { "U+3402 U+E0100", "ja-Jpan", kZH_HansFont }, // zh-Hans font is listed before zh-Hant font.
- { "U+3402 U+E0100", "zh-Hant", kZH_HantFont },
- { "U+3402 U+E0100", "zh-Hans", kZH_HansFont },
+ // U+3402 U+E0100 is supported by both zh-Hans and zh-Hant but not by ja font.
+ {"U+3402 U+E0100", "", kZH_HansFont}, // zh-Hans font is listed before zh-Hant font.
+ {"U+3402 U+E0100", "ja-Jpan",
+ kZH_HansFont}, // zh-Hans font is listed before zh-Hant font.
+ {"U+3402 U+E0100", "zh-Hant", kZH_HantFont},
+ {"U+3402 U+E0100", "zh-Hans", kZH_HansFont},
- { "U+3402 U+E0100", "ja-Jpan,zh-Hant", kZH_HantFont },
- { "U+3402 U+E0100", "zh-Hant,ja-Jpan", kZH_HantFont },
- { "U+3402 U+E0100", "zh-Hans,zh-Hant", kZH_HansFont },
- { "U+3402 U+E0100", "zh-Hant,zh-Hans", kZH_HantFont },
- { "U+3402 U+E0100", "zh-Hans,ja-Jpan", kZH_HansFont },
- { "U+3402 U+E0100", "ja-Jpan,zh-Hans", kZH_HansFont },
+ {"U+3402 U+E0100", "ja-Jpan,zh-Hant", kZH_HantFont},
+ {"U+3402 U+E0100", "zh-Hant,ja-Jpan", kZH_HantFont},
+ {"U+3402 U+E0100", "zh-Hans,zh-Hant", kZH_HansFont},
+ {"U+3402 U+E0100", "zh-Hant,zh-Hans", kZH_HantFont},
+ {"U+3402 U+E0100", "zh-Hans,ja-Jpan", kZH_HansFont},
+ {"U+3402 U+E0100", "ja-Jpan,zh-Hans", kZH_HansFont},
- { "U+3402 U+E0100", "zh-Hans,ja-Jpan,zh-Hant", kZH_HansFont },
- { "U+3402 U+E0100", "zh-Hans,zh-Hant,ja-Jpan", kZH_HansFont },
- { "U+3402 U+E0100", "ja-Jpan,zh-Hans,zh-Hant", kZH_HansFont },
- { "U+3402 U+E0100", "ja-Jpan,zh-Hant,zh-Hans", kZH_HantFont },
- { "U+3402 U+E0100", "zh-Hant,zh-Hans,ja-Jpan", kZH_HantFont },
- { "U+3402 U+E0100", "zh-Hant,ja-Jpan,zh-Hans", kZH_HantFont },
+ {"U+3402 U+E0100", "zh-Hans,ja-Jpan,zh-Hant", kZH_HansFont},
+ {"U+3402 U+E0100", "zh-Hans,zh-Hant,ja-Jpan", kZH_HansFont},
+ {"U+3402 U+E0100", "ja-Jpan,zh-Hans,zh-Hant", kZH_HansFont},
+ {"U+3402 U+E0100", "ja-Jpan,zh-Hant,zh-Hans", kZH_HantFont},
+ {"U+3402 U+E0100", "zh-Hant,zh-Hans,ja-Jpan", kZH_HantFont},
+ {"U+3402 U+E0100", "zh-Hant,ja-Jpan,zh-Hans", kZH_HantFont},
- // No font supports U+4444 U+FE00 but only zh-Hans supports its base character U+4444.
- { "U+4444 U+FE00", "", kZH_HansFont },
- { "U+4444 U+FE00", "ja-Jpan", kZH_HansFont },
- { "U+4444 U+FE00", "zh-Hant", kZH_HansFont },
- { "U+4444 U+FE00", "zh-Hans", kZH_HansFont },
+ // No font supports U+4444 U+FE00 but only zh-Hans supports its base character U+4444.
+ {"U+4444 U+FE00", "", kZH_HansFont},
+ {"U+4444 U+FE00", "ja-Jpan", kZH_HansFont},
+ {"U+4444 U+FE00", "zh-Hant", kZH_HansFont},
+ {"U+4444 U+FE00", "zh-Hans", kZH_HansFont},
- { "U+4444 U+FE00", "ja-Jpan,zh-Hant", kZH_HansFont },
- { "U+4444 U+FE00", "zh-Hant,ja-Jpan", kZH_HansFont },
- { "U+4444 U+FE00", "zh-Hans,zh-Hant", kZH_HansFont },
- { "U+4444 U+FE00", "zh-Hant,zh-Hans", kZH_HansFont },
- { "U+4444 U+FE00", "zh-Hans,ja-Jpan", kZH_HansFont },
- { "U+4444 U+FE00", "ja-Jpan,zh-Hans", kZH_HansFont },
+ {"U+4444 U+FE00", "ja-Jpan,zh-Hant", kZH_HansFont},
+ {"U+4444 U+FE00", "zh-Hant,ja-Jpan", kZH_HansFont},
+ {"U+4444 U+FE00", "zh-Hans,zh-Hant", kZH_HansFont},
+ {"U+4444 U+FE00", "zh-Hant,zh-Hans", kZH_HansFont},
+ {"U+4444 U+FE00", "zh-Hans,ja-Jpan", kZH_HansFont},
+ {"U+4444 U+FE00", "ja-Jpan,zh-Hans", kZH_HansFont},
- { "U+4444 U+FE00", "zh-Hans,ja-Jpan,zh-Hant", kZH_HansFont },
- { "U+4444 U+FE00", "zh-Hans,zh-Hant,ja-Jpan", kZH_HansFont },
- { "U+4444 U+FE00", "ja-Jpan,zh-Hans,zh-Hant", kZH_HansFont },
- { "U+4444 U+FE00", "ja-Jpan,zh-Hant,zh-Hans", kZH_HansFont },
- { "U+4444 U+FE00", "zh-Hant,zh-Hans,ja-Jpan", kZH_HansFont },
- { "U+4444 U+FE00", "zh-Hant,ja-Jpan,zh-Hans", kZH_HansFont },
+ {"U+4444 U+FE00", "zh-Hans,ja-Jpan,zh-Hant", kZH_HansFont},
+ {"U+4444 U+FE00", "zh-Hans,zh-Hant,ja-Jpan", kZH_HansFont},
+ {"U+4444 U+FE00", "ja-Jpan,zh-Hans,zh-Hant", kZH_HansFont},
+ {"U+4444 U+FE00", "ja-Jpan,zh-Hant,zh-Hans", kZH_HansFont},
+ {"U+4444 U+FE00", "zh-Hant,zh-Hans,ja-Jpan", kZH_HansFont},
+ {"U+4444 U+FE00", "zh-Hant,ja-Jpan,zh-Hans", kZH_HansFont},
- // No font supports U+81ED U+E0100 but ja and zh-Hans support its base character U+81ED.
- // zh-Hans font is listed before ja font.
- { "U+81ED U+E0100", "", kZH_HansFont },
- { "U+81ED U+E0100", "ja-Jpan", kJAFont },
- { "U+81ED U+E0100", "zh-Hant", kZH_HansFont },
- { "U+81ED U+E0100", "zh-Hans", kZH_HansFont },
+ // No font supports U+81ED U+E0100 but ja and zh-Hans support its base character U+81ED.
+ // zh-Hans font is listed before ja font.
+ {"U+81ED U+E0100", "", kZH_HansFont},
+ {"U+81ED U+E0100", "ja-Jpan", kJAFont},
+ {"U+81ED U+E0100", "zh-Hant", kZH_HansFont},
+ {"U+81ED U+E0100", "zh-Hans", kZH_HansFont},
- { "U+81ED U+E0100", "ja-Jpan,zh-Hant", kJAFont },
- { "U+81ED U+E0100", "zh-Hant,ja-Jpan", kJAFont },
- { "U+81ED U+E0100", "zh-Hans,zh-Hant", kZH_HansFont },
- { "U+81ED U+E0100", "zh-Hant,zh-Hans", kZH_HansFont },
- { "U+81ED U+E0100", "zh-Hans,ja-Jpan", kZH_HansFont },
- { "U+81ED U+E0100", "ja-Jpan,zh-Hans", kJAFont },
+ {"U+81ED U+E0100", "ja-Jpan,zh-Hant", kJAFont},
+ {"U+81ED U+E0100", "zh-Hant,ja-Jpan", kJAFont},
+ {"U+81ED U+E0100", "zh-Hans,zh-Hant", kZH_HansFont},
+ {"U+81ED U+E0100", "zh-Hant,zh-Hans", kZH_HansFont},
+ {"U+81ED U+E0100", "zh-Hans,ja-Jpan", kZH_HansFont},
+ {"U+81ED U+E0100", "ja-Jpan,zh-Hans", kJAFont},
- { "U+81ED U+E0100", "zh-Hans,ja-Jpan,zh-Hant", kZH_HansFont },
- { "U+81ED U+E0100", "zh-Hans,zh-Hant,ja-Jpan", kZH_HansFont },
- { "U+81ED U+E0100", "ja-Jpan,zh-Hans,zh-Hant", kJAFont },
- { "U+81ED U+E0100", "ja-Jpan,zh-Hant,zh-Hans", kJAFont },
- { "U+81ED U+E0100", "zh-Hant,zh-Hans,ja-Jpan", kZH_HansFont },
- { "U+81ED U+E0100", "zh-Hant,ja-Jpan,zh-Hans", kJAFont },
+ {"U+81ED U+E0100", "zh-Hans,ja-Jpan,zh-Hant", kZH_HansFont},
+ {"U+81ED U+E0100", "zh-Hans,zh-Hant,ja-Jpan", kZH_HansFont},
+ {"U+81ED U+E0100", "ja-Jpan,zh-Hans,zh-Hant", kJAFont},
+ {"U+81ED U+E0100", "ja-Jpan,zh-Hant,zh-Hans", kJAFont},
+ {"U+81ED U+E0100", "zh-Hant,zh-Hans,ja-Jpan", kZH_HansFont},
+ {"U+81ED U+E0100", "zh-Hant,ja-Jpan,zh-Hans", kJAFont},
- // No font supports U+9AA8 U+E0100 but all zh-Hans zh-hant ja fonts support its base
- // character U+9AA8.
- // zh-Hans font is listed before ja and zh-Hant fonts.
- { "U+9AA8 U+E0100", "", kZH_HansFont },
- { "U+9AA8 U+E0100", "ja-Jpan", kJAFont },
- { "U+9AA8 U+E0100", "zh-Hans", kZH_HansFont },
- { "U+9AA8 U+E0100", "zh-Hant", kZH_HantFont },
+ // No font supports U+9AA8 U+E0100 but all zh-Hans zh-hant ja fonts support its base
+ // character U+9AA8.
+ // zh-Hans font is listed before ja and zh-Hant fonts.
+ {"U+9AA8 U+E0100", "", kZH_HansFont},
+ {"U+9AA8 U+E0100", "ja-Jpan", kJAFont},
+ {"U+9AA8 U+E0100", "zh-Hans", kZH_HansFont},
+ {"U+9AA8 U+E0100", "zh-Hant", kZH_HantFont},
- { "U+9AA8 U+E0100", "ja-Jpan,zh-Hant", kJAFont },
- { "U+9AA8 U+E0100", "zh-Hant,ja-Jpan", kZH_HantFont },
- { "U+9AA8 U+E0100", "zh-Hans,zh-Hant", kZH_HansFont },
- { "U+9AA8 U+E0100", "zh-Hant,zh-Hans", kZH_HantFont },
- { "U+9AA8 U+E0100", "zh-Hans,ja-Jpan", kZH_HansFont },
- { "U+9AA8 U+E0100", "ja-Jpan,zh-Hans", kJAFont },
+ {"U+9AA8 U+E0100", "ja-Jpan,zh-Hant", kJAFont},
+ {"U+9AA8 U+E0100", "zh-Hant,ja-Jpan", kZH_HantFont},
+ {"U+9AA8 U+E0100", "zh-Hans,zh-Hant", kZH_HansFont},
+ {"U+9AA8 U+E0100", "zh-Hant,zh-Hans", kZH_HantFont},
+ {"U+9AA8 U+E0100", "zh-Hans,ja-Jpan", kZH_HansFont},
+ {"U+9AA8 U+E0100", "ja-Jpan,zh-Hans", kJAFont},
- { "U+9AA8 U+E0100", "zh-Hans,ja-Jpan,zh-Hant", kZH_HansFont },
- { "U+9AA8 U+E0100", "zh-Hans,zh-Hant,ja-Jpan", kZH_HansFont },
- { "U+9AA8 U+E0100", "ja-Jpan,zh-Hans,zh-Hant", kJAFont },
- { "U+9AA8 U+E0100", "ja-Jpan,zh-Hant,zh-Hans", kJAFont },
- { "U+9AA8 U+E0100", "zh-Hant,zh-Hans,ja-Jpan", kZH_HantFont },
- { "U+9AA8 U+E0100", "zh-Hant,ja-Jpan,zh-Hans", kZH_HantFont },
+ {"U+9AA8 U+E0100", "zh-Hans,ja-Jpan,zh-Hant", kZH_HansFont},
+ {"U+9AA8 U+E0100", "zh-Hans,zh-Hant,ja-Jpan", kZH_HansFont},
+ {"U+9AA8 U+E0100", "ja-Jpan,zh-Hans,zh-Hant", kJAFont},
+ {"U+9AA8 U+E0100", "ja-Jpan,zh-Hant,zh-Hans", kJAFont},
+ {"U+9AA8 U+E0100", "zh-Hant,zh-Hans,ja-Jpan", kZH_HantFont},
+ {"U+9AA8 U+E0100", "zh-Hant,ja-Jpan,zh-Hans", kZH_HantFont},
- // All zh-Hans,zh-Hant,ja fonts support U+35A8 U+E0100 and its base character U+35A8.
- // zh-Hans font is listed before ja and zh-Hant fonts.
- { "U+35A8", "", kZH_HansFont },
- { "U+35A8", "ja-Jpan", kJAFont },
- { "U+35A8", "zh-Hans", kZH_HansFont },
- { "U+35A8", "zh-Hant", kZH_HantFont },
+ // All zh-Hans,zh-Hant,ja fonts support U+35A8 U+E0100 and its base character U+35A8.
+ // zh-Hans font is listed before ja and zh-Hant fonts.
+ {"U+35A8", "", kZH_HansFont},
+ {"U+35A8", "ja-Jpan", kJAFont},
+ {"U+35A8", "zh-Hans", kZH_HansFont},
+ {"U+35A8", "zh-Hant", kZH_HantFont},
- { "U+35A8", "ja-Jpan,zh-Hant", kJAFont },
- { "U+35A8", "zh-Hant,ja-Jpan", kZH_HantFont },
- { "U+35A8", "zh-Hans,zh-Hant", kZH_HansFont },
- { "U+35A8", "zh-Hant,zh-Hans", kZH_HantFont },
- { "U+35A8", "zh-Hans,ja-Jpan", kZH_HansFont },
- { "U+35A8", "ja-Jpan,zh-Hans", kJAFont },
+ {"U+35A8", "ja-Jpan,zh-Hant", kJAFont},
+ {"U+35A8", "zh-Hant,ja-Jpan", kZH_HantFont},
+ {"U+35A8", "zh-Hans,zh-Hant", kZH_HansFont},
+ {"U+35A8", "zh-Hant,zh-Hans", kZH_HantFont},
+ {"U+35A8", "zh-Hans,ja-Jpan", kZH_HansFont},
+ {"U+35A8", "ja-Jpan,zh-Hans", kJAFont},
- { "U+35A8", "zh-Hans,ja-Jpan,zh-Hant", kZH_HansFont },
- { "U+35A8", "zh-Hans,zh-Hant,ja-Jpan", kZH_HansFont },
- { "U+35A8", "ja-Jpan,zh-Hans,zh-Hant", kJAFont },
- { "U+35A8", "ja-Jpan,zh-Hant,zh-Hans", kJAFont },
- { "U+35A8", "zh-Hant,zh-Hans,ja-Jpan", kZH_HantFont },
- { "U+35A8", "zh-Hant,ja-Jpan,zh-Hans", kZH_HantFont },
+ {"U+35A8", "zh-Hans,ja-Jpan,zh-Hant", kZH_HansFont},
+ {"U+35A8", "zh-Hans,zh-Hant,ja-Jpan", kZH_HansFont},
+ {"U+35A8", "ja-Jpan,zh-Hans,zh-Hant", kJAFont},
+ {"U+35A8", "ja-Jpan,zh-Hant,zh-Hans", kJAFont},
+ {"U+35A8", "zh-Hant,zh-Hans,ja-Jpan", kZH_HantFont},
+ {"U+35A8", "zh-Hant,ja-Jpan,zh-Hans", kZH_HantFont},
- // All zh-Hans,zh-Hant,ja fonts support U+35B6 U+E0100, but zh-Hant and ja fonts support its
- // base character U+35B6.
- // ja font is listed before zh-Hant font.
- { "U+35B6", "", kJAFont },
- { "U+35B6", "ja-Jpan", kJAFont },
- { "U+35B6", "zh-Hant", kZH_HantFont },
- { "U+35B6", "zh-Hans", kJAFont },
+ // All zh-Hans,zh-Hant,ja fonts support U+35B6 U+E0100, but zh-Hant and ja fonts support
+ // its
+ // base character U+35B6.
+ // ja font is listed before zh-Hant font.
+ {"U+35B6", "", kJAFont},
+ {"U+35B6", "ja-Jpan", kJAFont},
+ {"U+35B6", "zh-Hant", kZH_HantFont},
+ {"U+35B6", "zh-Hans", kJAFont},
- { "U+35B6", "ja-Jpan,zh-Hant", kJAFont },
- { "U+35B6", "zh-Hant,ja-Jpan", kZH_HantFont },
- { "U+35B6", "zh-Hans,zh-Hant", kZH_HantFont },
- { "U+35B6", "zh-Hant,zh-Hans", kZH_HantFont },
- { "U+35B6", "zh-Hans,ja-Jpan", kJAFont },
- { "U+35B6", "ja-Jpan,zh-Hans", kJAFont },
+ {"U+35B6", "ja-Jpan,zh-Hant", kJAFont},
+ {"U+35B6", "zh-Hant,ja-Jpan", kZH_HantFont},
+ {"U+35B6", "zh-Hans,zh-Hant", kZH_HantFont},
+ {"U+35B6", "zh-Hant,zh-Hans", kZH_HantFont},
+ {"U+35B6", "zh-Hans,ja-Jpan", kJAFont},
+ {"U+35B6", "ja-Jpan,zh-Hans", kJAFont},
- { "U+35B6", "zh-Hans,ja-Jpan,zh-Hant", kJAFont },
- { "U+35B6", "zh-Hans,zh-Hant,ja-Jpan", kZH_HantFont },
- { "U+35B6", "ja-Jpan,zh-Hans,zh-Hant", kJAFont },
- { "U+35B6", "ja-Jpan,zh-Hant,zh-Hans", kJAFont },
- { "U+35B6", "zh-Hant,zh-Hans,ja-Jpan", kZH_HantFont },
- { "U+35B6", "zh-Hant,ja-Jpan,zh-Hans", kZH_HantFont },
+ {"U+35B6", "zh-Hans,ja-Jpan,zh-Hant", kJAFont},
+ {"U+35B6", "zh-Hans,zh-Hant,ja-Jpan", kZH_HantFont},
+ {"U+35B6", "ja-Jpan,zh-Hans,zh-Hant", kJAFont},
+ {"U+35B6", "ja-Jpan,zh-Hant,zh-Hans", kJAFont},
+ {"U+35B6", "zh-Hant,zh-Hans,ja-Jpan", kZH_HantFont},
+ {"U+35B6", "zh-Hant,ja-Jpan,zh-Hans", kZH_HantFont},
- // All zh-Hans,zh-Hant,ja fonts support U+35C5 U+E0100, but only ja font supports its base
- // character U+35C5.
- { "U+35C5", "", kJAFont },
- { "U+35C5", "ja-Jpan", kJAFont },
- { "U+35C5", "zh-Hant", kJAFont },
- { "U+35C5", "zh-Hans", kJAFont },
+ // All zh-Hans,zh-Hant,ja fonts support U+35C5 U+E0100, but only ja font supports its
+ // base
+ // character U+35C5.
+ {"U+35C5", "", kJAFont},
+ {"U+35C5", "ja-Jpan", kJAFont},
+ {"U+35C5", "zh-Hant", kJAFont},
+ {"U+35C5", "zh-Hans", kJAFont},
- { "U+35C5", "ja-Jpan,zh-Hant", kJAFont },
- { "U+35C5", "zh-Hant,ja-Jpan", kJAFont },
- { "U+35C5", "zh-Hans,zh-Hant", kJAFont },
- { "U+35C5", "zh-Hant,zh-Hans", kJAFont },
- { "U+35C5", "zh-Hans,ja-Jpan", kJAFont },
- { "U+35C5", "ja-Jpan,zh-Hans", kJAFont },
+ {"U+35C5", "ja-Jpan,zh-Hant", kJAFont},
+ {"U+35C5", "zh-Hant,ja-Jpan", kJAFont},
+ {"U+35C5", "zh-Hans,zh-Hant", kJAFont},
+ {"U+35C5", "zh-Hant,zh-Hans", kJAFont},
+ {"U+35C5", "zh-Hans,ja-Jpan", kJAFont},
+ {"U+35C5", "ja-Jpan,zh-Hans", kJAFont},
- { "U+35C5", "zh-Hans,ja-Jpan,zh-Hant", kJAFont },
- { "U+35C5", "zh-Hans,zh-Hant,ja-Jpan", kJAFont },
- { "U+35C5", "ja-Jpan,zh-Hans,zh-Hant", kJAFont },
- { "U+35C5", "ja-Jpan,zh-Hant,zh-Hans", kJAFont },
- { "U+35C5", "zh-Hant,zh-Hans,ja-Jpan", kJAFont },
- { "U+35C5", "zh-Hant,ja-Jpan,zh-Hans", kJAFont },
+ {"U+35C5", "zh-Hans,ja-Jpan,zh-Hant", kJAFont},
+ {"U+35C5", "zh-Hans,zh-Hant,ja-Jpan", kJAFont},
+ {"U+35C5", "ja-Jpan,zh-Hans,zh-Hant", kJAFont},
+ {"U+35C5", "ja-Jpan,zh-Hant,zh-Hans", kJAFont},
+ {"U+35C5", "zh-Hant,zh-Hans,ja-Jpan", kJAFont},
+ {"U+35C5", "zh-Hant,ja-Jpan,zh-Hans", kJAFont},
- // None of ja-Jpan, zh-Hant, zh-Hans font supports U+1F469. Emoji font supports it.
- { "U+1F469", "", kEmojiFont },
- { "U+1F469", "ja-Jpan", kEmojiFont },
- { "U+1F469", "zh-Hant", kEmojiFont },
- { "U+1F469", "zh-Hans", kEmojiFont },
+ // None of ja-Jpan, zh-Hant, zh-Hans font supports U+1F469. Emoji font supports it.
+ {"U+1F469", "", kEmojiFont},
+ {"U+1F469", "ja-Jpan", kEmojiFont},
+ {"U+1F469", "zh-Hant", kEmojiFont},
+ {"U+1F469", "zh-Hans", kEmojiFont},
- { "U+1F469", "ja-Jpan,zh-Hant", kEmojiFont },
- { "U+1F469", "zh-Hant,ja-Jpan", kEmojiFont },
- { "U+1F469", "zh-Hans,zh-Hant", kEmojiFont },
- { "U+1F469", "zh-Hant,zh-Hans", kEmojiFont },
- { "U+1F469", "zh-Hans,ja-Jpan", kEmojiFont },
- { "U+1F469", "ja-Jpan,zh-Hans", kEmojiFont },
+ {"U+1F469", "ja-Jpan,zh-Hant", kEmojiFont},
+ {"U+1F469", "zh-Hant,ja-Jpan", kEmojiFont},
+ {"U+1F469", "zh-Hans,zh-Hant", kEmojiFont},
+ {"U+1F469", "zh-Hant,zh-Hans", kEmojiFont},
+ {"U+1F469", "zh-Hans,ja-Jpan", kEmojiFont},
+ {"U+1F469", "ja-Jpan,zh-Hans", kEmojiFont},
- { "U+1F469", "zh-Hans,ja-Jpan,zh-Hant", kEmojiFont },
- { "U+1F469", "zh-Hans,zh-Hant,ja-Jpan", kEmojiFont },
- { "U+1F469", "ja-Jpan,zh-Hans,zh-Hant", kEmojiFont },
- { "U+1F469", "ja-Jpan,zh-Hant,zh-Hans", kEmojiFont },
- { "U+1F469", "zh-Hant,zh-Hans,ja-Jpan", kEmojiFont },
- { "U+1F469", "zh-Hant,ja-Jpan,zh-Hans", kEmojiFont },
+ {"U+1F469", "zh-Hans,ja-Jpan,zh-Hant", kEmojiFont},
+ {"U+1F469", "zh-Hans,zh-Hant,ja-Jpan", kEmojiFont},
+ {"U+1F469", "ja-Jpan,zh-Hans,zh-Hant", kEmojiFont},
+ {"U+1F469", "ja-Jpan,zh-Hant,zh-Hans", kEmojiFont},
+ {"U+1F469", "zh-Hant,zh-Hans,ja-Jpan", kEmojiFont},
+ {"U+1F469", "zh-Hant,ja-Jpan,zh-Hans", kEmojiFont},
};
- std::shared_ptr<FontCollection> collection(getFontCollection(kTestFontDir, kItemizeFontXml));
+ auto collection = buildFontCollectionFromXml(kItemizeFontXml);
for (auto testCase : testCases) {
- SCOPED_TRACE("Test for \"" + testCase.testString + "\" with languages " +
- testCase.requestedLanguages);
+ SCOPED_TRACE("Test for \"" + testCase.testString + "\" with locales " +
+ testCase.requestedLocales);
std::vector<FontCollection::Run> runs;
- const FontStyle style =
- FontStyle(FontStyle::registerLanguageList(testCase.requestedLanguages));
- itemize(collection, testCase.testString.c_str(), style, &runs);
+ itemize(collection, testCase.testString.c_str(), testCase.requestedLocales, &runs);
ASSERT_EQ(1U, runs.size());
- EXPECT_EQ(testCase.expectedFont, getFontPath(runs[0]));
+ EXPECT_EQ(testCase.expectedFont, getFontName(runs[0]));
}
}
-TEST_F(FontCollectionItemizeTest, itemize_emojiSelection_withFE0E) {
- std::shared_ptr<FontCollection> collection(getFontCollection(kTestFontDir, kEmojiXmlFile));
+TEST(FontCollectionItemizeTest, itemize_emojiSelection_withFE0E) {
+ auto collection = buildFontCollectionFromXml(kEmojiXmlFile);
std::vector<FontCollection::Run> runs;
- const FontStyle kDefaultFontStyle;
-
// U+00A9 is a text default emoji which is only available in TextEmojiFont.ttf.
// TextEmojiFont.ttf should be selected.
- itemize(collection, "U+00A9 U+FE0E", kDefaultFontStyle, &runs);
+ itemize(collection, "U+00A9 U+FE0E", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
- EXPECT_EQ(kTextEmojiFont, getFontPath(runs[0]));
+ EXPECT_EQ(kTextEmojiFont, getFontName(runs[0]));
// U+00A9 is a text default emoji which is only available in ColorEmojiFont.ttf.
// ColorEmojiFont.ttf should be selected.
- itemize(collection, "U+00AE U+FE0E", kDefaultFontStyle, &runs);
+ itemize(collection, "U+00AE U+FE0E", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
// Text emoji is specified but it is not available. Use color emoji instead.
- EXPECT_EQ(kColorEmojiFont, getFontPath(runs[0]));
+ EXPECT_EQ(kColorEmojiFont, getFontName(runs[0]));
// U+203C is a text default emoji which is available in both TextEmojiFont.ttf and
// ColorEmojiFont.ttf. TextEmojiFont.ttf should be selected.
- itemize(collection, "U+203C U+FE0E", kDefaultFontStyle, &runs);
+ itemize(collection, "U+203C U+FE0E", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
- EXPECT_EQ(kTextEmojiFont, getFontPath(runs[0]));
+ EXPECT_EQ(kTextEmojiFont, getFontName(runs[0]));
// U+2049 is a text default emoji which is not available either TextEmojiFont.ttf or
// ColorEmojiFont.ttf. No font should be selected.
- itemize(collection, "U+2049 U+FE0E", kDefaultFontStyle, &runs);
+ itemize(collection, "U+2049 U+FE0E", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
- EXPECT_EQ(kNoGlyphFont, getFontPath(runs[0]));
+ EXPECT_EQ(kNoGlyphFont, getFontName(runs[0]));
// U+231A is a emoji default emoji which is available only in TextEmojifFont.
// TextEmojiFont.ttf sohuld be selected.
- itemize(collection, "U+231A U+FE0E", kDefaultFontStyle, &runs);
+ itemize(collection, "U+231A U+FE0E", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
- EXPECT_EQ(kTextEmojiFont, getFontPath(runs[0]));
+ EXPECT_EQ(kTextEmojiFont, getFontName(runs[0]));
// U+231B is a emoji default emoji which is available only in ColorEmojiFont.ttf.
// ColorEmojiFont.ttf should be selected.
- itemize(collection, "U+231B U+FE0E", kDefaultFontStyle, &runs);
+ itemize(collection, "U+231B U+FE0E", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
// Text emoji is specified but it is not available. Use color emoji instead.
- EXPECT_EQ(kColorEmojiFont, getFontPath(runs[0]));
+ EXPECT_EQ(kColorEmojiFont, getFontName(runs[0]));
// U+23E9 is a emoji default emoji which is available in both TextEmojiFont.ttf and
// ColorEmojiFont.ttf. TextEmojiFont.ttf should be selected even if U+23E9 is emoji default
// emoji since U+FE0E is appended.
- itemize(collection, "U+23E9 U+FE0E", kDefaultFontStyle, &runs);
+ itemize(collection, "U+23E9 U+FE0E", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
- EXPECT_EQ(kTextEmojiFont, getFontPath(runs[0]));
+ EXPECT_EQ(kTextEmojiFont, getFontName(runs[0]));
// U+23EA is a emoji default emoji but which is not available in either TextEmojiFont.ttf or
// ColorEmojiFont.ttf. No font should be selected.
- itemize(collection, "U+23EA U+FE0E", kDefaultFontStyle, &runs);
+ itemize(collection, "U+23EA U+FE0E", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
- EXPECT_EQ(kNoGlyphFont, getFontPath(runs[0]));
+ EXPECT_EQ(kNoGlyphFont, getFontName(runs[0]));
// U+26FA U+FE0E is specified but ColorTextMixedEmojiFont has a variation sequence U+26F9 U+FE0F
// in its cmap, so ColorTextMixedEmojiFont should be selected instaed of ColorEmojiFont.
- itemize(collection, "U+26FA U+FE0E", kDefaultFontStyle, &runs);
+ itemize(collection, "U+26FA U+FE0E", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
- EXPECT_EQ(kMixedEmojiFont, getFontPath(runs[0]));
+ EXPECT_EQ(kMixedEmojiFont, getFontName(runs[0]));
}
-TEST_F(FontCollectionItemizeTest, itemize_emojiSelection_withFE0F) {
- std::shared_ptr<FontCollection> collection(getFontCollection(kTestFontDir, kEmojiXmlFile));
+TEST(FontCollectionItemizeTest, itemize_emojiSelection_withFE0F) {
+ auto collection = buildFontCollectionFromXml(kEmojiXmlFile);
std::vector<FontCollection::Run> runs;
- const FontStyle kDefaultFontStyle;
-
// U+00A9 is a text default emoji which is available only in TextEmojiFont.ttf.
// TextEmojiFont.ttf shoudl be selected.
- itemize(collection, "U+00A9 U+FE0F", kDefaultFontStyle, &runs);
+ itemize(collection, "U+00A9 U+FE0F", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
// Color emoji is specified but it is not available. Use text representaion instead.
- EXPECT_EQ(kTextEmojiFont, getFontPath(runs[0]));
+ EXPECT_EQ(kTextEmojiFont, getFontName(runs[0]));
// U+00AE is a text default emoji which is available only in ColorEmojiFont.ttf.
// ColorEmojiFont.ttf should be selected.
- itemize(collection, "U+00AE U+FE0F", kDefaultFontStyle, &runs);
+ itemize(collection, "U+00AE U+FE0F", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
- EXPECT_EQ(kColorEmojiFont, getFontPath(runs[0]));
+ EXPECT_EQ(kColorEmojiFont, getFontName(runs[0]));
// U+203C is a text default emoji which is available in both TextEmojiFont.ttf and
// ColorEmojiFont.ttf. ColorEmojiFont.ttf should be selected even if U+203C is a text default
// emoji since U+FF0F is appended.
- itemize(collection, "U+203C U+FE0F", kDefaultFontStyle, &runs);
+ itemize(collection, "U+203C U+FE0F", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
- EXPECT_EQ(kColorEmojiFont, getFontPath(runs[0]));
+ EXPECT_EQ(kColorEmojiFont, getFontName(runs[0]));
// U+2049 is a text default emoji which is not available in either TextEmojiFont.ttf or
// ColorEmojiFont.ttf. No font should be selected.
- itemize(collection, "U+2049 U+FE0F", kDefaultFontStyle, &runs);
+ itemize(collection, "U+2049 U+FE0F", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
- EXPECT_EQ(kNoGlyphFont, getFontPath(runs[0]));
+ EXPECT_EQ(kNoGlyphFont, getFontName(runs[0]));
// U+231A is a emoji default emoji which is available only in TextEmojiFont.ttf.
// TextEmojiFont.ttf should be selected.
- itemize(collection, "U+231A U+FE0F", kDefaultFontStyle, &runs);
+ itemize(collection, "U+231A U+FE0F", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
// Color emoji is specified but it is not available. Use text representation instead.
- EXPECT_EQ(kTextEmojiFont, getFontPath(runs[0]));
+ EXPECT_EQ(kTextEmojiFont, getFontName(runs[0]));
// U+231B is a emoji default emoji which is available only in ColorEmojiFont.ttf.
// ColorEmojiFont.ttf should be selected.
- itemize(collection, "U+231B U+FE0F", kDefaultFontStyle, &runs);
+ itemize(collection, "U+231B U+FE0F", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
- EXPECT_EQ(kColorEmojiFont, getFontPath(runs[0]));
+ EXPECT_EQ(kColorEmojiFont, getFontName(runs[0]));
// U+23E9 is a emoji default emoji which is available in both TextEmojiFont.ttf and
// ColorEmojiFont.ttf. ColorEmojiFont.ttf should be selected.
- itemize(collection, "U+23E9 U+FE0F", kDefaultFontStyle, &runs);
+ itemize(collection, "U+23E9 U+FE0F", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
- EXPECT_EQ(kColorEmojiFont, getFontPath(runs[0]));
+ EXPECT_EQ(kColorEmojiFont, getFontName(runs[0]));
// U+23EA is a emoji default emoji which is not available in either TextEmojiFont.ttf or
// ColorEmojiFont.ttf. No font should be selected.
- itemize(collection, "U+23EA U+FE0F", kDefaultFontStyle, &runs);
+ itemize(collection, "U+23EA U+FE0F", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
- EXPECT_EQ(kNoGlyphFont, getFontPath(runs[0]));
+ EXPECT_EQ(kNoGlyphFont, getFontName(runs[0]));
// U+26F9 U+FE0F is specified but ColorTextMixedEmojiFont has a variation sequence U+26F9 U+FE0F
// in its cmap, so ColorTextMixedEmojiFont should be selected instaed of ColorEmojiFont.
- itemize(collection, "U+26F9 U+FE0F", kDefaultFontStyle, &runs);
+ itemize(collection, "U+26F9 U+FE0F", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
- EXPECT_EQ(kMixedEmojiFont, getFontPath(runs[0]));
+ EXPECT_EQ(kMixedEmojiFont, getFontName(runs[0]));
}
-TEST_F(FontCollectionItemizeTest, itemize_emojiSelection_with_skinTone) {
- std::shared_ptr<FontCollection> collection(getFontCollection(kTestFontDir, kEmojiXmlFile));
+TEST(FontCollectionItemizeTest, itemize_emojiSelection_with_skinTone) {
+ auto collection = buildFontCollectionFromXml(kEmojiXmlFile);
std::vector<FontCollection::Run> runs;
- const FontStyle kDefaultFontStyle;
-
// TextEmoji font is selected since it is listed before ColorEmoji font.
- itemize(collection, "U+261D", kDefaultFontStyle, &runs);
+ itemize(collection, "U+261D", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(1, runs[0].end);
- EXPECT_EQ(kTextEmojiFont, getFontPath(runs[0]));
+ EXPECT_EQ(kTextEmojiFont, getFontName(runs[0]));
// If skin tone is specified, it should be colored.
- itemize(collection, "U+261D U+1F3FD", kDefaultFontStyle, &runs);
+ itemize(collection, "U+261D U+1F3FD", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(3, runs[0].end);
- EXPECT_EQ(kColorEmojiFont, getFontPath(runs[0]));
+ EXPECT_EQ(kColorEmojiFont, getFontName(runs[0]));
// Still color font is selected if an emoji variation selector is specified.
- itemize(collection, "U+261D U+FE0F U+1F3FD", kDefaultFontStyle, &runs);
+ itemize(collection, "U+261D U+FE0F U+1F3FD", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(4, runs[0].end);
- EXPECT_EQ(kColorEmojiFont, getFontPath(runs[0]));
+ EXPECT_EQ(kColorEmojiFont, getFontName(runs[0]));
// Text font should be selected if a text variation selector is specified and skin tone is
// rendered by itself.
- itemize(collection, "U+261D U+FE0E U+1F3FD", kDefaultFontStyle, &runs);
+ itemize(collection, "U+261D U+FE0E U+1F3FD", &runs);
ASSERT_EQ(2U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
- EXPECT_EQ(kTextEmojiFont, getFontPath(runs[0]));
+ EXPECT_EQ(kTextEmojiFont, getFontName(runs[0]));
EXPECT_EQ(2, runs[1].start);
EXPECT_EQ(4, runs[1].end);
- EXPECT_EQ(kColorEmojiFont, getFontPath(runs[1]));
+ EXPECT_EQ(kColorEmojiFont, getFontName(runs[1]));
}
-TEST_F(FontCollectionItemizeTest, itemize_PrivateUseArea) {
- std::shared_ptr<FontCollection> collection(getFontCollection(kTestFontDir, kEmojiXmlFile));
+TEST(FontCollectionItemizeTest, itemize_PrivateUseArea) {
+ auto collection = buildFontCollectionFromXml(kEmojiXmlFile);
std::vector<FontCollection::Run> runs;
- const FontStyle kDefaultFontStyle;
-
// Should not set nullptr to the result run. (Issue 26808815)
- itemize(collection, "U+FEE10", kDefaultFontStyle, &runs);
+ itemize(collection, "U+FEE10", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
- EXPECT_EQ(kNoGlyphFont, getFontPath(runs[0]));
+ EXPECT_EQ(kNoGlyphFont, getFontName(runs[0]));
- itemize(collection, "U+FEE40 U+FE4C5", kDefaultFontStyle, &runs);
+ itemize(collection, "U+FEE40 U+FE4C5", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(4, runs[0].end);
- EXPECT_EQ(kNoGlyphFont, getFontPath(runs[0]));
+ EXPECT_EQ(kNoGlyphFont, getFontName(runs[0]));
}
-TEST_F(FontCollectionItemizeTest, itemize_genderBalancedEmoji) {
- std::shared_ptr<FontCollection> collection(getFontCollection(kTestFontDir, kEmojiXmlFile));
+TEST(FontCollectionItemizeTest, itemize_genderBalancedEmoji) {
+ auto collection = buildFontCollectionFromXml(kEmojiXmlFile);
std::vector<FontCollection::Run> runs;
- const FontStyle kDefaultFontStyle;
-
- itemize(collection, "U+1F469 U+200D U+1F373", kDefaultFontStyle, &runs);
+ itemize(collection, "U+1F469 U+200D U+1F373", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(5, runs[0].end);
- EXPECT_EQ(kColorEmojiFont, getFontPath(runs[0]));
+ EXPECT_EQ(kColorEmojiFont, getFontName(runs[0]));
- itemize(collection, "U+1F469 U+200D U+2695 U+FE0F", kDefaultFontStyle, &runs);
+ itemize(collection, "U+1F469 U+200D U+2695 U+FE0F", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(5, runs[0].end);
- EXPECT_EQ(kColorEmojiFont, getFontPath(runs[0]));
+ EXPECT_EQ(kColorEmojiFont, getFontName(runs[0]));
- itemize(collection, "U+1F469 U+200D U+2695", kDefaultFontStyle, &runs);
+ itemize(collection, "U+1F469 U+200D U+2695", &runs);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(4, runs[0].end);
- EXPECT_EQ(kColorEmojiFont, getFontPath(runs[0]));
+ EXPECT_EQ(kColorEmojiFont, getFontName(runs[0]));
}
// For b/29585939
-TEST_F(FontCollectionItemizeTest, itemizeShouldKeepOrderForVS) {
- const FontStyle kDefaultFontStyle;
+TEST(FontCollectionItemizeTest, itemizeShouldKeepOrderForVS) {
+ std::shared_ptr<FontFamily> dummyFamily = buildFontFamily(kNoGlyphFont);
+ std::shared_ptr<FontFamily> familyA = buildFontFamily(kZH_HansFont);
+ std::shared_ptr<FontFamily> familyB = buildFontFamily(kZH_HansFont);
- std::shared_ptr<MinikinFont> dummyFont(new MinikinFontForTest(kNoGlyphFont));
- std::shared_ptr<MinikinFont> fontA(new MinikinFontForTest(kZH_HansFont));
- std::shared_ptr<MinikinFont> fontB(new MinikinFontForTest(kZH_HansFont));
-
- std::shared_ptr<FontFamily> dummyFamily(new FontFamily(
- std::vector<Font>({ Font(dummyFont, FontStyle()) })));
- std::shared_ptr<FontFamily> familyA(new FontFamily(
- std::vector<Font>({ Font(fontA, FontStyle()) })));
- std::shared_ptr<FontFamily> familyB(new FontFamily(
- std::vector<Font>({ Font(fontB, FontStyle()) })));
-
- std::vector<std::shared_ptr<FontFamily>> families =
- { dummyFamily, familyA, familyB };
- std::vector<std::shared_ptr<FontFamily>> reversedFamilies =
- { dummyFamily, familyB, familyA };
+ std::vector<std::shared_ptr<FontFamily>> families = {dummyFamily, familyA, familyB};
+ std::vector<std::shared_ptr<FontFamily>> reversedFamilies = {dummyFamily, familyB, familyA};
std::shared_ptr<FontCollection> collection(new FontCollection(families));
std::shared_ptr<FontCollection> reversedCollection(new FontCollection(reversedFamilies));
@@ -1543,34 +1526,23 @@
// Both fontA/fontB support U+35A8 but don't support U+35A8 U+E0100. The first font should be
// selected.
std::vector<FontCollection::Run> runs;
- itemize(collection, "U+35A8 U+E0100", kDefaultFontStyle, &runs);
- EXPECT_EQ(fontA.get(), runs[0].fakedFont.font);
+ itemize(collection, "U+35A8 U+E0100", &runs);
+ EXPECT_EQ(familyA->getFont(0), runs[0].fakedFont.font);
- itemize(reversedCollection, "U+35A8 U+E0100", kDefaultFontStyle, &runs);
- EXPECT_EQ(fontB.get(), runs[0].fakedFont.font);
+ itemize(reversedCollection, "U+35A8 U+E0100", &runs);
+ EXPECT_EQ(familyB->getFont(0), runs[0].fakedFont.font);
}
// For b/29585939
-TEST_F(FontCollectionItemizeTest, itemizeShouldKeepOrderForVS2) {
- const FontStyle kDefaultFontStyle;
+TEST(FontCollectionItemizeTest, itemizeShouldKeepOrderForVS2) {
+ std::shared_ptr<FontFamily> dummyFamily = buildFontFamily(kNoGlyphFont);
+ std::shared_ptr<FontFamily> hasCmapFormat14Family = buildFontFamily(kHasCmapFormat14Font);
+ std::shared_ptr<FontFamily> noCmapFormat14Family = buildFontFamily(kNoCmapFormat14Font);
- std::shared_ptr<MinikinFont> dummyFont(new MinikinFontForTest(kNoGlyphFont));
- std::shared_ptr<MinikinFont> hasCmapFormat14Font(
- new MinikinFontForTest(kHasCmapFormat14Font));
- std::shared_ptr<MinikinFont> noCmapFormat14Font(
- new MinikinFontForTest(kNoCmapFormat14Font));
-
- std::shared_ptr<FontFamily> dummyFamily(new FontFamily(
- std::vector<Font>({ Font(dummyFont, FontStyle()) })));
- std::shared_ptr<FontFamily> hasCmapFormat14Family(new FontFamily(
- std::vector<Font>({ Font(hasCmapFormat14Font, FontStyle()) })));
- std::shared_ptr<FontFamily> noCmapFormat14Family(new FontFamily(
- std::vector<Font>({ Font(noCmapFormat14Font, FontStyle()) })));
-
- std::vector<std::shared_ptr<FontFamily>> families =
- { dummyFamily, hasCmapFormat14Family, noCmapFormat14Family };
- std::vector<std::shared_ptr<FontFamily>> reversedFamilies =
- { dummyFamily, noCmapFormat14Family, hasCmapFormat14Family };
+ std::vector<std::shared_ptr<FontFamily>> families = {dummyFamily, hasCmapFormat14Family,
+ noCmapFormat14Family};
+ std::vector<std::shared_ptr<FontFamily>> reversedFamilies = {dummyFamily, noCmapFormat14Family,
+ hasCmapFormat14Family};
std::shared_ptr<FontCollection> collection(new FontCollection(families));
std::shared_ptr<FontCollection> reversedCollection(new FontCollection(reversedFamilies));
@@ -1578,11 +1550,63 @@
// Both hasCmapFormat14Font/noCmapFormat14Font support U+5380 but don't support U+5380 U+E0100.
// The first font should be selected.
std::vector<FontCollection::Run> runs;
- itemize(collection, "U+5380 U+E0100", kDefaultFontStyle, &runs);
- EXPECT_EQ(hasCmapFormat14Font.get(), runs[0].fakedFont.font);
+ itemize(collection, "U+5380 U+E0100", &runs);
+ EXPECT_EQ(hasCmapFormat14Family->getFont(0), runs[0].fakedFont.font);
- itemize(reversedCollection, "U+5380 U+E0100", kDefaultFontStyle, &runs);
- EXPECT_EQ(noCmapFormat14Font.get(), runs[0].fakedFont.font);
+ itemize(reversedCollection, "U+5380 U+E0100", &runs);
+ EXPECT_EQ(noCmapFormat14Family->getFont(0), runs[0].fakedFont.font);
+}
+
+TEST(FontCollectionItemizeTest, colorEmojiSelectionTest) {
+ auto dummyFamily = buildFontFamily(kNoGlyphFont);
+ auto textEmojiFamily = buildFontFamily(kTextEmojiFont, "ja-JP");
+ auto colorEmojiFamily = buildFontFamily(kColorEmojiFont, "und-Zsye");
+
+ std::vector<std::shared_ptr<FontFamily>> families = {dummyFamily, textEmojiFamily,
+ colorEmojiFamily};
+ auto collection = std::make_shared<FontCollection>(families);
+ std::vector<FontCollection::Run> runs;
+ // Both textEmojiFamily and colorEmojiFamily supports U+203C and U+23E9.
+ // U+203C is text default emoji, and U+23E9 is color default emoji.
+ itemize(collection, "U+203C", "en-US,en-Zsym", &runs);
+ EXPECT_EQ(textEmojiFamily->getFont(0), runs[0].fakedFont.font);
+ itemize(collection, "U+23E9", "en-US,en-Zsym", &runs);
+ EXPECT_EQ(textEmojiFamily->getFont(0), runs[0].fakedFont.font);
+
+ itemize(collection, "U+203C", "en-US,en-Zsye", &runs);
+ EXPECT_EQ(colorEmojiFamily->getFont(0), runs[0].fakedFont.font);
+ itemize(collection, "U+23E9", "en-US,en-Zsye", &runs);
+ EXPECT_EQ(colorEmojiFamily->getFont(0), runs[0].fakedFont.font);
+
+ itemize(collection, "U+203C", "ja-Zsym-JP", &runs);
+ EXPECT_EQ(textEmojiFamily->getFont(0), runs[0].fakedFont.font);
+ itemize(collection, "U+23E9", "ja-Zsym-JP", &runs);
+ EXPECT_EQ(textEmojiFamily->getFont(0), runs[0].fakedFont.font);
+
+ itemize(collection, "U+203C", "ja-Zsye-JP", &runs);
+ EXPECT_EQ(colorEmojiFamily->getFont(0), runs[0].fakedFont.font);
+ itemize(collection, "U+23E9", "ja-Zsye-JP", &runs);
+ EXPECT_EQ(colorEmojiFamily->getFont(0), runs[0].fakedFont.font);
+
+ itemize(collection, "U+203C", "ja-JP-u-em-text", &runs);
+ EXPECT_EQ(textEmojiFamily->getFont(0), runs[0].fakedFont.font);
+ itemize(collection, "U+23E9", "ja-JP-u-em-text", &runs);
+ EXPECT_EQ(textEmojiFamily->getFont(0), runs[0].fakedFont.font);
+
+ itemize(collection, "U+203C", "ja-JP-u-em-emoji", &runs);
+ EXPECT_EQ(colorEmojiFamily->getFont(0), runs[0].fakedFont.font);
+ itemize(collection, "U+23E9", "ja-JP-u-em-emoji", &runs);
+ EXPECT_EQ(colorEmojiFamily->getFont(0), runs[0].fakedFont.font);
+
+ itemize(collection, "U+203C", "ja-JP,und-Zsym", &runs);
+ EXPECT_EQ(textEmojiFamily->getFont(0), runs[0].fakedFont.font);
+ itemize(collection, "U+23E9", "ja-JP,und-Zsym", &runs);
+ EXPECT_EQ(textEmojiFamily->getFont(0), runs[0].fakedFont.font);
+
+ itemize(collection, "U+203C", "ja-JP,und-Zsye", &runs);
+ EXPECT_EQ(colorEmojiFamily->getFont(0), runs[0].fakedFont.font);
+ itemize(collection, "U+23E9", "ja-JP,und-Zsye", &runs);
+ EXPECT_EQ(colorEmojiFamily->getFont(0), runs[0].fakedFont.font);
}
} // namespace minikin
diff --git a/tests/unittest/FontCollectionTest.cpp b/tests/unittest/FontCollectionTest.cpp
index bef1c63..6b39508 100644
--- a/tests/unittest/FontCollectionTest.cpp
+++ b/tests/unittest/FontCollectionTest.cpp
@@ -14,11 +14,11 @@
* limitations under the License.
*/
+#include "minikin/FontCollection.h"
+
#include <gtest/gtest.h>
-#include <minikin/FontCollection.h>
#include "FontTestUtils.h"
-#include "MinikinFontForTest.h"
#include "MinikinInternal.h"
namespace minikin {
@@ -38,7 +38,7 @@
// U+717D U+FE02 (VS3)
// U+717D U+E0102 (VS19)
// U+717D U+E0103 (VS20)
-const char kVsTestFont[] = kTestFontDir "/VariationSelectorTest-Regular.ttf";
+const char kVsTestFont[] = "VariationSelectorTest-Regular.ttf";
void expectVSGlyphs(const FontCollection* fc, uint32_t codepoint, const std::set<uint32_t>& vsSet) {
for (uint32_t vs = 0xFE00; vs <= 0xE01EF; ++vs) {
@@ -48,38 +48,36 @@
}
if (vsSet.find(vs) == vsSet.end()) {
EXPECT_FALSE(fc->hasVariationSelector(codepoint, vs))
- << "Glyph for U+" << std::hex << codepoint << " U+" << vs;
+ << "Glyph for U+" << std::hex << codepoint << " U+" << vs;
} else {
EXPECT_TRUE(fc->hasVariationSelector(codepoint, vs))
- << "Glyph for U+" << std::hex << codepoint << " U+" << vs;
+ << "Glyph for U+" << std::hex << codepoint << " U+" << vs;
}
}
}
TEST(FontCollectionTest, hasVariationSelectorTest) {
- std::shared_ptr<MinikinFont> font(new MinikinFontForTest(kVsTestFont));
- std::shared_ptr<FontFamily> family(new FontFamily(
- std::vector<Font>({ Font(font, FontStyle()) })));
- std::vector<std::shared_ptr<FontFamily>> families({ family });
- std::shared_ptr<FontCollection> fc(new FontCollection(families));
+ auto fc = buildFontCollection(kVsTestFont);
- EXPECT_FALSE(fc->hasVariationSelector(0x82A6, 0));
- expectVSGlyphs(fc.get(), 0x82A6, std::set<uint32_t>({0xFE00, 0xFE0E, 0xE0100, 0xE0101, 0xE0102}));
+ EXPECT_FALSE(fc->hasVariationSelector(0x82A6, 0));
+ expectVSGlyphs(fc.get(), 0x82A6,
+ std::set<uint32_t>({0xFE00, 0xFE0E, 0xE0100, 0xE0101, 0xE0102}));
- EXPECT_FALSE(fc->hasVariationSelector(0x845B, 0));
- expectVSGlyphs(fc.get(), 0x845B, std::set<uint32_t>({0xFE01, 0xFE0E, 0xE0101, 0xE0102, 0xE0103}));
+ EXPECT_FALSE(fc->hasVariationSelector(0x845B, 0));
+ expectVSGlyphs(fc.get(), 0x845B,
+ std::set<uint32_t>({0xFE01, 0xFE0E, 0xE0101, 0xE0102, 0xE0103}));
- EXPECT_FALSE(fc->hasVariationSelector(0x537F, 0));
- expectVSGlyphs(fc.get(), 0x537F, std::set<uint32_t>({0xFE0E}));
+ EXPECT_FALSE(fc->hasVariationSelector(0x537F, 0));
+ expectVSGlyphs(fc.get(), 0x537F, std::set<uint32_t>({0xFE0E}));
- EXPECT_FALSE(fc->hasVariationSelector(0x717D, 0));
- expectVSGlyphs(fc.get(), 0x717D, std::set<uint32_t>({0xFE02, 0xE0102, 0xE0103}));
+ EXPECT_FALSE(fc->hasVariationSelector(0x717D, 0));
+ expectVSGlyphs(fc.get(), 0x717D, std::set<uint32_t>({0xFE02, 0xE0102, 0xE0103}));
}
-const char kEmojiXmlFile[] = kTestFontDir "emoji.xml";
+const char kEmojiXmlFile[] = "emoji.xml";
TEST(FontCollectionTest, hasVariationSelectorTest_emoji) {
- std::shared_ptr<FontCollection> collection(getFontCollection(kTestFontDir, kEmojiXmlFile));
+ auto collection = buildFontCollectionFromXml(kEmojiXmlFile);
// Both text/color font have cmap format 14 subtable entry for VS15/VS16 respectively.
EXPECT_TRUE(collection->hasVariationSelector(0x2623, 0xFE0E));
@@ -108,11 +106,10 @@
// None of the fonts support U+2229.
EXPECT_FALSE(collection->hasVariationSelector(0x2229, 0xFE0E));
EXPECT_FALSE(collection->hasVariationSelector(0x2229, 0xFE0F));
-
}
TEST(FontCollectionTest, newEmojiTest) {
- std::shared_ptr<FontCollection> collection(getFontCollection(kTestFontDir, kEmojiXmlFile));
+ auto collection = buildFontCollectionFromXml(kEmojiXmlFile);
// U+2695, U+2640, U+2642 are not in emoji catrgory in Unicode 9 but they are now in emoji
// category. Should return true even if U+FE0E was appended.
@@ -126,33 +123,21 @@
TEST(FontCollectionTest, createWithVariations) {
// This font has 'wdth' and 'wght' axes.
- const char kMultiAxisFont[] = kTestFontDir "/MultiAxis.ttf";
- const char kNoAxisFont[] = kTestFontDir "/Regular.ttf";
+ const char kMultiAxisFont[] = "MultiAxis.ttf";
+ const char kNoAxisFont[] = "Regular.ttf";
- std::shared_ptr<MinikinFont> multiAxisFont(new MinikinFontForTest(kMultiAxisFont));
- std::shared_ptr<FontFamily> multiAxisFamily(new FontFamily(
- std::vector<Font>({ Font(multiAxisFont, FontStyle()) })));
- std::vector<std::shared_ptr<FontFamily>> multiAxisFamilies({multiAxisFamily});
- std::shared_ptr<FontCollection> multiAxisFc(new FontCollection(multiAxisFamilies));
-
- std::shared_ptr<MinikinFont> noAxisFont(new MinikinFontForTest(kNoAxisFont));
- std::shared_ptr<FontFamily> noAxisFamily(new FontFamily(
- std::vector<Font>({ Font(noAxisFont, FontStyle()) })));
- std::vector<std::shared_ptr<FontFamily>> noAxisFamilies({noAxisFamily});
- std::shared_ptr<FontCollection> noAxisFc(new FontCollection(noAxisFamilies));
+ std::shared_ptr<FontCollection> multiAxisFc = buildFontCollection(kMultiAxisFont);
+ std::shared_ptr<FontCollection> noAxisFc = buildFontCollection(kNoAxisFont);
{
// Do not ceate new instance if none of variations are specified.
EXPECT_EQ(nullptr,
- multiAxisFc->createCollectionWithVariation(std::vector<FontVariation>()));
- EXPECT_EQ(nullptr,
- noAxisFc->createCollectionWithVariation(std::vector<FontVariation>()));
+ multiAxisFc->createCollectionWithVariation(std::vector<FontVariation>()));
+ EXPECT_EQ(nullptr, noAxisFc->createCollectionWithVariation(std::vector<FontVariation>()));
}
{
// New instance should be used for supported variation.
- std::vector<FontVariation> variations = {
- { MinikinFont::MakeTag('w', 'd', 't', 'h'), 1.0f }
- };
+ std::vector<FontVariation> variations = {{MinikinFont::MakeTag('w', 'd', 't', 'h'), 1.0f}};
std::shared_ptr<FontCollection> newFc(
multiAxisFc->createCollectionWithVariation(variations));
EXPECT_NE(nullptr, newFc.get());
@@ -162,10 +147,8 @@
}
{
// New instance should be used for supported variation (multiple variations case).
- std::vector<FontVariation> variations = {
- { MinikinFont::MakeTag('w', 'd', 't', 'h'), 1.0f },
- { MinikinFont::MakeTag('w', 'g', 'h', 't'), 1.0f }
- };
+ std::vector<FontVariation> variations = {{MinikinFont::MakeTag('w', 'd', 't', 'h'), 1.0f},
+ {MinikinFont::MakeTag('w', 'g', 'h', 't'), 1.0f}};
std::shared_ptr<FontCollection> newFc(
multiAxisFc->createCollectionWithVariation(variations));
EXPECT_NE(nullptr, newFc.get());
@@ -175,18 +158,14 @@
}
{
// Do not ceate new instance if none of variations are supported.
- std::vector<FontVariation> variations = {
- { MinikinFont::MakeTag('Z', 'Z', 'Z', 'Z'), 1.0f }
- };
+ std::vector<FontVariation> variations = {{MinikinFont::MakeTag('Z', 'Z', 'Z', 'Z'), 1.0f}};
EXPECT_EQ(nullptr, multiAxisFc->createCollectionWithVariation(variations));
EXPECT_EQ(nullptr, noAxisFc->createCollectionWithVariation(variations));
}
{
// At least one axis is supported, should create new instance.
- std::vector<FontVariation> variations = {
- { MinikinFont::MakeTag('w', 'd', 't', 'h'), 1.0f },
- { MinikinFont::MakeTag('Z', 'Z', 'Z', 'Z'), 1.0f }
- };
+ std::vector<FontVariation> variations = {{MinikinFont::MakeTag('w', 'd', 't', 'h'), 1.0f},
+ {MinikinFont::MakeTag('Z', 'Z', 'Z', 'Z'), 1.0f}};
std::shared_ptr<FontCollection> newFc(
multiAxisFc->createCollectionWithVariation(variations));
EXPECT_NE(nullptr, newFc.get());
diff --git a/tests/unittest/FontFamilyTest.cpp b/tests/unittest/FontFamilyTest.cpp
index 90e2a64..28662c1 100644
--- a/tests/unittest/FontFamilyTest.cpp
+++ b/tests/unittest/FontFamilyTest.cpp
@@ -14,68 +14,59 @@
* limitations under the License.
*/
-#include <minikin/FontFamily.h>
+#include "minikin/FontFamily.h"
-#include <android/log.h>
#include <gtest/gtest.h>
-#include "FontLanguageListCache.h"
-#include "ICUTestBase.h"
-#include "MinikinFontForTest.h"
+#include "minikin/LocaleList.h"
+
+#include "FontTestUtils.h"
+#include "FreeTypeMinikinFontForTest.h"
+#include "LocaleListCache.h"
#include "MinikinInternal.h"
namespace minikin {
-typedef ICUTestBase FontLanguagesTest;
-typedef ICUTestBase FontLanguageTest;
-
-static const FontLanguages& createFontLanguages(const std::string& input) {
- android::AutoMutex _l(gMinikinLock);
- uint32_t langId = FontLanguageListCache::getId(input);
- return FontLanguageListCache::getById(langId);
+static const LocaleList& createLocaleList(const std::string& input) {
+ uint32_t localeListId = LocaleListCache::getId(input);
+ return LocaleListCache::getById(localeListId);
}
-static FontLanguage createFontLanguage(const std::string& input) {
- android::AutoMutex _l(gMinikinLock);
- uint32_t langId = FontLanguageListCache::getId(input);
- return FontLanguageListCache::getById(langId)[0];
+static Locale createLocale(const std::string& input) {
+ uint32_t localeListId = LocaleListCache::getId(input);
+ return LocaleListCache::getById(localeListId)[0];
}
-static FontLanguage createFontLanguageWithoutICUSanitization(const std::string& input) {
- return FontLanguage(input.c_str(), input.size());
+static Locale createLocaleWithoutICUSanitization(const std::string& input) {
+ return Locale(input);
}
-std::shared_ptr<FontFamily> makeFamily(const std::string& fontPath) {
- std::shared_ptr<MinikinFont> font(new MinikinFontForTest(fontPath));
- return std::make_shared<FontFamily>(
- std::vector<Font>({Font(font, FontStyle())}));
-}
-
-TEST_F(FontLanguageTest, basicTests) {
- FontLanguage defaultLang;
- FontLanguage emptyLang("", 0);
- FontLanguage english = createFontLanguage("en");
- FontLanguage french = createFontLanguage("fr");
- FontLanguage und = createFontLanguage("und");
- FontLanguage undZsye = createFontLanguage("und-Zsye");
+TEST(LocaleTest, basicTests) {
+ Locale defaultLocale;
+ Locale emptyLocale("");
+ Locale english = createLocale("en");
+ Locale french = createLocale("fr");
+ Locale und = createLocale("und");
+ Locale undZsye = createLocale("und-Zsye");
EXPECT_EQ(english, english);
EXPECT_EQ(french, french);
- EXPECT_TRUE(defaultLang != defaultLang);
- EXPECT_TRUE(emptyLang != emptyLang);
- EXPECT_TRUE(defaultLang != emptyLang);
- EXPECT_TRUE(defaultLang != und);
- EXPECT_TRUE(emptyLang != und);
- EXPECT_TRUE(english != defaultLang);
- EXPECT_TRUE(english != emptyLang);
+ EXPECT_TRUE(defaultLocale != defaultLocale);
+ EXPECT_TRUE(emptyLocale != emptyLocale);
+ EXPECT_TRUE(defaultLocale != emptyLocale);
+ EXPECT_TRUE(defaultLocale != und);
+ EXPECT_TRUE(emptyLocale != und);
+ EXPECT_TRUE(english != defaultLocale);
+ EXPECT_TRUE(english != emptyLocale);
EXPECT_TRUE(english != french);
EXPECT_TRUE(english != undZsye);
EXPECT_TRUE(und != undZsye);
EXPECT_TRUE(english != und);
+ EXPECT_TRUE(createLocale("de-1901") != createLocale("de-1996"));
- EXPECT_TRUE(defaultLang.isUnsupported());
- EXPECT_TRUE(emptyLang.isUnsupported());
+ EXPECT_TRUE(defaultLocale.isUnsupported());
+ EXPECT_TRUE(emptyLocale.isUnsupported());
EXPECT_FALSE(english.isUnsupported());
EXPECT_FALSE(french.isUnsupported());
@@ -83,84 +74,87 @@
EXPECT_FALSE(undZsye.isUnsupported());
}
-TEST_F(FontLanguageTest, getStringTest) {
- EXPECT_EQ("en-Latn-US", createFontLanguage("en").getString());
- EXPECT_EQ("en-Latn-US", createFontLanguage("en-Latn").getString());
+TEST(LocaleTest, getStringTest) {
+ EXPECT_EQ("en-Latn-US", createLocale("en").getString());
+ EXPECT_EQ("en-Latn-US", createLocale("en-Latn").getString());
// Capitalized language code or lowercased script should be normalized.
- EXPECT_EQ("en-Latn-US", createFontLanguage("EN-LATN").getString());
- EXPECT_EQ("en-Latn-US", createFontLanguage("EN-latn").getString());
- EXPECT_EQ("en-Latn-US", createFontLanguage("en-latn").getString());
+ EXPECT_EQ("en-Latn-US", createLocale("EN-LATN").getString());
+ EXPECT_EQ("en-Latn-US", createLocale("EN-latn").getString());
+ EXPECT_EQ("en-Latn-US", createLocale("en-latn").getString());
// Invalid script should be kept.
- EXPECT_EQ("en-Xyzt-US", createFontLanguage("en-xyzt").getString());
+ EXPECT_EQ("en-Xyzt-US", createLocale("en-xyzt").getString());
- EXPECT_EQ("en-Latn-US", createFontLanguage("en-Latn-US").getString());
- EXPECT_EQ("ja-Jpan-JP", createFontLanguage("ja").getString());
- EXPECT_EQ("zh-Hant-TW", createFontLanguage("zh-TW").getString());
- EXPECT_EQ("zh-Hant-HK", createFontLanguage("zh-HK").getString());
- EXPECT_EQ("zh-Hant-MO", createFontLanguage("zh-MO").getString());
- EXPECT_EQ("zh-Hans-CN", createFontLanguage("zh").getString());
- EXPECT_EQ("zh-Hans-CN", createFontLanguage("zh-CN").getString());
- EXPECT_EQ("zh-Hans-SG", createFontLanguage("zh-SG").getString());
- EXPECT_EQ("und", createFontLanguage("und").getString());
- EXPECT_EQ("und", createFontLanguage("UND").getString());
- EXPECT_EQ("und", createFontLanguage("Und").getString());
- EXPECT_EQ("und-Zsye", createFontLanguage("und-Zsye").getString());
- EXPECT_EQ("und-Zsye", createFontLanguage("Und-ZSYE").getString());
- EXPECT_EQ("und-Zsye", createFontLanguage("Und-zsye").getString());
+ EXPECT_EQ("en-Latn-US", createLocale("en-Latn-US").getString());
+ EXPECT_EQ("ja-Jpan-JP", createLocale("ja").getString());
+ EXPECT_EQ("zh-Hant-TW", createLocale("zh-TW").getString());
+ EXPECT_EQ("zh-Hant-HK", createLocale("zh-HK").getString());
+ EXPECT_EQ("zh-Hant-MO", createLocale("zh-MO").getString());
+ EXPECT_EQ("zh-Hans-CN", createLocale("zh").getString());
+ EXPECT_EQ("zh-Hans-CN", createLocale("zh-CN").getString());
+ EXPECT_EQ("zh-Hans-SG", createLocale("zh-SG").getString());
+ EXPECT_EQ("und", createLocale("und").getString());
+ EXPECT_EQ("und", createLocale("UND").getString());
+ EXPECT_EQ("und", createLocale("Und").getString());
+ EXPECT_EQ("und-Zsye", createLocale("und-Zsye").getString());
+ EXPECT_EQ("und-Zsye", createLocale("Und-ZSYE").getString());
+ EXPECT_EQ("und-Zsye", createLocale("Und-zsye").getString());
- EXPECT_EQ("de-Latn-DE", createFontLanguage("de-1901").getString());
+ EXPECT_EQ("es-Latn-419", createLocale("es-Latn-419").getString());
- EXPECT_EQ("es-Latn-419", createFontLanguage("es-Latn-419").getString());
+ // Variant
+ EXPECT_EQ("de-Latn-DE", createLocale("de").getString());
+ EXPECT_EQ("de-Latn-DE-1901", createLocale("de-1901").getString());
+ EXPECT_EQ("de-Latn-DE-1996", createLocale("de-DE-1996").getString());
// Emoji subtag is dropped from getString().
- EXPECT_EQ("es-Latn-419", createFontLanguage("es-419-u-em-emoji").getString());
- EXPECT_EQ("es-Latn-419", createFontLanguage("es-Latn-419-u-em-emoji").getString());
+ EXPECT_EQ("es-Latn-419", createLocale("es-419-u-em-emoji").getString());
+ EXPECT_EQ("es-Latn-419", createLocale("es-Latn-419-u-em-emoji").getString());
// This is not a necessary desired behavior, just known behavior.
- EXPECT_EQ("en-Latn-US", createFontLanguage("und-Abcdefgh").getString());
+ EXPECT_EQ("en-Latn-US", createLocale("und-Abcdefgh").getString());
}
-TEST_F(FontLanguageTest, testReconstruction) {
- EXPECT_EQ("en", createFontLanguageWithoutICUSanitization("en").getString());
- EXPECT_EQ("fil", createFontLanguageWithoutICUSanitization("fil").getString());
- EXPECT_EQ("und", createFontLanguageWithoutICUSanitization("und").getString());
+TEST(LocaleTest, testReconstruction) {
+ EXPECT_EQ("en", createLocaleWithoutICUSanitization("en").getString());
+ EXPECT_EQ("fil", createLocaleWithoutICUSanitization("fil").getString());
+ EXPECT_EQ("und", createLocaleWithoutICUSanitization("und").getString());
- EXPECT_EQ("en-Latn", createFontLanguageWithoutICUSanitization("en-Latn").getString());
- EXPECT_EQ("fil-Taga", createFontLanguageWithoutICUSanitization("fil-Taga").getString());
- EXPECT_EQ("und-Zsye", createFontLanguageWithoutICUSanitization("und-Zsye").getString());
+ EXPECT_EQ("en-Latn", createLocaleWithoutICUSanitization("en-Latn").getString());
+ EXPECT_EQ("fil-Taga", createLocaleWithoutICUSanitization("fil-Taga").getString());
+ EXPECT_EQ("und-Zsye", createLocaleWithoutICUSanitization("und-Zsye").getString());
- EXPECT_EQ("en-US", createFontLanguageWithoutICUSanitization("en-US").getString());
- EXPECT_EQ("fil-PH", createFontLanguageWithoutICUSanitization("fil-PH").getString());
- EXPECT_EQ("es-419", createFontLanguageWithoutICUSanitization("es-419").getString());
+ EXPECT_EQ("en-US", createLocaleWithoutICUSanitization("en-US").getString());
+ EXPECT_EQ("fil-PH", createLocaleWithoutICUSanitization("fil-PH").getString());
+ EXPECT_EQ("es-419", createLocaleWithoutICUSanitization("es-419").getString());
- EXPECT_EQ("en-Latn-US", createFontLanguageWithoutICUSanitization("en-Latn-US").getString());
- EXPECT_EQ("fil-Taga-PH", createFontLanguageWithoutICUSanitization("fil-Taga-PH").getString());
- EXPECT_EQ("es-Latn-419", createFontLanguageWithoutICUSanitization("es-Latn-419").getString());
+ EXPECT_EQ("en-Latn-US", createLocaleWithoutICUSanitization("en-Latn-US").getString());
+ EXPECT_EQ("fil-Taga-PH", createLocaleWithoutICUSanitization("fil-Taga-PH").getString());
+ EXPECT_EQ("es-Latn-419", createLocaleWithoutICUSanitization("es-Latn-419").getString());
// Possible minimum/maximum values.
- EXPECT_EQ("aa", createFontLanguageWithoutICUSanitization("aa").getString());
- EXPECT_EQ("zz", createFontLanguageWithoutICUSanitization("zz").getString());
- EXPECT_EQ("aa-Aaaa", createFontLanguageWithoutICUSanitization("aa-Aaaa").getString());
- EXPECT_EQ("zz-Zzzz", createFontLanguageWithoutICUSanitization("zz-Zzzz").getString());
- EXPECT_EQ("aaa-Aaaa-AA", createFontLanguageWithoutICUSanitization("aaa-Aaaa-AA").getString());
- EXPECT_EQ("zzz-Zzzz-ZZ", createFontLanguageWithoutICUSanitization("zzz-Zzzz-ZZ").getString());
- EXPECT_EQ("aaa-Aaaa-000", createFontLanguageWithoutICUSanitization("aaa-Aaaa-000").getString());
- EXPECT_EQ("zzz-Zzzz-999", createFontLanguageWithoutICUSanitization("zzz-Zzzz-999").getString());
+ EXPECT_EQ("aa", createLocaleWithoutICUSanitization("aa").getString());
+ EXPECT_EQ("zz", createLocaleWithoutICUSanitization("zz").getString());
+ EXPECT_EQ("aa-Aaaa", createLocaleWithoutICUSanitization("aa-Aaaa").getString());
+ EXPECT_EQ("zz-Zzzz", createLocaleWithoutICUSanitization("zz-Zzzz").getString());
+ EXPECT_EQ("aaa-Aaaa-AA", createLocaleWithoutICUSanitization("aaa-Aaaa-AA").getString());
+ EXPECT_EQ("zzz-Zzzz-ZZ", createLocaleWithoutICUSanitization("zzz-Zzzz-ZZ").getString());
+ EXPECT_EQ("aaa-Aaaa-000", createLocaleWithoutICUSanitization("aaa-Aaaa-000").getString());
+ EXPECT_EQ("zzz-Zzzz-999", createLocaleWithoutICUSanitization("zzz-Zzzz-999").getString());
}
-TEST_F(FontLanguageTest, ScriptEqualTest) {
- EXPECT_TRUE(createFontLanguage("en").isEqualScript(createFontLanguage("en")));
- EXPECT_TRUE(createFontLanguage("en-Latn").isEqualScript(createFontLanguage("en")));
- EXPECT_TRUE(createFontLanguage("jp-Latn").isEqualScript(createFontLanguage("en-Latn")));
- EXPECT_TRUE(createFontLanguage("en-Jpan").isEqualScript(createFontLanguage("en-Jpan")));
+TEST(LocaleTest, ScriptEqualTest) {
+ EXPECT_TRUE(createLocale("en").isEqualScript(createLocale("en")));
+ EXPECT_TRUE(createLocale("en-Latn").isEqualScript(createLocale("en")));
+ EXPECT_TRUE(createLocale("jp-Latn").isEqualScript(createLocale("en-Latn")));
+ EXPECT_TRUE(createLocale("en-Jpan").isEqualScript(createLocale("en-Jpan")));
- EXPECT_FALSE(createFontLanguage("en-Jpan").isEqualScript(createFontLanguage("en-Hira")));
- EXPECT_FALSE(createFontLanguage("en-Jpan").isEqualScript(createFontLanguage("en-Hani")));
+ EXPECT_FALSE(createLocale("en-Jpan").isEqualScript(createLocale("en-Hira")));
+ EXPECT_FALSE(createLocale("en-Jpan").isEqualScript(createLocale("en-Hani")));
}
-TEST_F(FontLanguageTest, ScriptMatchTest) {
+TEST(LocaleTest, ScriptMatchTest) {
const bool SUPPORTED = true;
const bool NOT_SUPPORTED = false;
@@ -169,80 +163,80 @@
const std::string requestedScript;
bool isSupported;
} testCases[] = {
- // Same scripts
- { "en-Latn", "Latn", SUPPORTED },
- { "ja-Jpan", "Jpan", SUPPORTED },
- { "ja-Hira", "Hira", SUPPORTED },
- { "ja-Kana", "Kana", SUPPORTED },
- { "ja-Hrkt", "Hrkt", SUPPORTED },
- { "zh-Hans", "Hans", SUPPORTED },
- { "zh-Hant", "Hant", SUPPORTED },
- { "zh-Hani", "Hani", SUPPORTED },
- { "ko-Kore", "Kore", SUPPORTED },
- { "ko-Hang", "Hang", SUPPORTED },
- { "zh-Hanb", "Hanb", SUPPORTED },
+ // Same scripts
+ {"en-Latn", "Latn", SUPPORTED},
+ {"ja-Jpan", "Jpan", SUPPORTED},
+ {"ja-Hira", "Hira", SUPPORTED},
+ {"ja-Kana", "Kana", SUPPORTED},
+ {"ja-Hrkt", "Hrkt", SUPPORTED},
+ {"zh-Hans", "Hans", SUPPORTED},
+ {"zh-Hant", "Hant", SUPPORTED},
+ {"zh-Hani", "Hani", SUPPORTED},
+ {"ko-Kore", "Kore", SUPPORTED},
+ {"ko-Hang", "Hang", SUPPORTED},
+ {"zh-Hanb", "Hanb", SUPPORTED},
- // Japanese supports Hiragana, Katakanara, etc.
- { "ja-Jpan", "Hira", SUPPORTED },
- { "ja-Jpan", "Kana", SUPPORTED },
- { "ja-Jpan", "Hrkt", SUPPORTED },
- { "ja-Hrkt", "Hira", SUPPORTED },
- { "ja-Hrkt", "Kana", SUPPORTED },
+ // Japanese supports Hiragana, Katakanara, etc.
+ {"ja-Jpan", "Hira", SUPPORTED},
+ {"ja-Jpan", "Kana", SUPPORTED},
+ {"ja-Jpan", "Hrkt", SUPPORTED},
+ {"ja-Hrkt", "Hira", SUPPORTED},
+ {"ja-Hrkt", "Kana", SUPPORTED},
- // Chinese supports Han.
- { "zh-Hans", "Hani", SUPPORTED },
- { "zh-Hant", "Hani", SUPPORTED },
- { "zh-Hanb", "Hani", SUPPORTED },
+ // Chinese supports Han.
+ {"zh-Hans", "Hani", SUPPORTED},
+ {"zh-Hant", "Hani", SUPPORTED},
+ {"zh-Hanb", "Hani", SUPPORTED},
- // Hanb supports Bopomofo.
- { "zh-Hanb", "Bopo", SUPPORTED },
+ // Hanb supports Bopomofo.
+ {"zh-Hanb", "Bopo", SUPPORTED},
- // Korean supports Hangul.
- { "ko-Kore", "Hang", SUPPORTED },
+ // Korean supports Hangul.
+ {"ko-Kore", "Hang", SUPPORTED},
- // Different scripts
- { "ja-Jpan", "Latn", NOT_SUPPORTED },
- { "en-Latn", "Jpan", NOT_SUPPORTED },
- { "ja-Jpan", "Hant", NOT_SUPPORTED },
- { "zh-Hant", "Jpan", NOT_SUPPORTED },
- { "ja-Jpan", "Hans", NOT_SUPPORTED },
- { "zh-Hans", "Jpan", NOT_SUPPORTED },
- { "ja-Jpan", "Kore", NOT_SUPPORTED },
- { "ko-Kore", "Jpan", NOT_SUPPORTED },
- { "zh-Hans", "Hant", NOT_SUPPORTED },
- { "zh-Hant", "Hans", NOT_SUPPORTED },
- { "zh-Hans", "Kore", NOT_SUPPORTED },
- { "ko-Kore", "Hans", NOT_SUPPORTED },
- { "zh-Hant", "Kore", NOT_SUPPORTED },
- { "ko-Kore", "Hant", NOT_SUPPORTED },
+ // Different scripts
+ {"ja-Jpan", "Latn", NOT_SUPPORTED},
+ {"en-Latn", "Jpan", NOT_SUPPORTED},
+ {"ja-Jpan", "Hant", NOT_SUPPORTED},
+ {"zh-Hant", "Jpan", NOT_SUPPORTED},
+ {"ja-Jpan", "Hans", NOT_SUPPORTED},
+ {"zh-Hans", "Jpan", NOT_SUPPORTED},
+ {"ja-Jpan", "Kore", NOT_SUPPORTED},
+ {"ko-Kore", "Jpan", NOT_SUPPORTED},
+ {"zh-Hans", "Hant", NOT_SUPPORTED},
+ {"zh-Hant", "Hans", NOT_SUPPORTED},
+ {"zh-Hans", "Kore", NOT_SUPPORTED},
+ {"ko-Kore", "Hans", NOT_SUPPORTED},
+ {"zh-Hant", "Kore", NOT_SUPPORTED},
+ {"ko-Kore", "Hant", NOT_SUPPORTED},
- // Hiragana doesn't support Japanese, etc.
- { "ja-Hira", "Jpan", NOT_SUPPORTED },
- { "ja-Kana", "Jpan", NOT_SUPPORTED },
- { "ja-Hrkt", "Jpan", NOT_SUPPORTED },
- { "ja-Hani", "Jpan", NOT_SUPPORTED },
- { "ja-Hira", "Hrkt", NOT_SUPPORTED },
- { "ja-Kana", "Hrkt", NOT_SUPPORTED },
- { "ja-Hani", "Hrkt", NOT_SUPPORTED },
- { "ja-Hani", "Hira", NOT_SUPPORTED },
- { "ja-Hani", "Kana", NOT_SUPPORTED },
+ // Hiragana doesn't support Japanese, etc.
+ {"ja-Hira", "Jpan", NOT_SUPPORTED},
+ {"ja-Kana", "Jpan", NOT_SUPPORTED},
+ {"ja-Hrkt", "Jpan", NOT_SUPPORTED},
+ {"ja-Hani", "Jpan", NOT_SUPPORTED},
+ {"ja-Hira", "Hrkt", NOT_SUPPORTED},
+ {"ja-Kana", "Hrkt", NOT_SUPPORTED},
+ {"ja-Hani", "Hrkt", NOT_SUPPORTED},
+ {"ja-Hani", "Hira", NOT_SUPPORTED},
+ {"ja-Hani", "Kana", NOT_SUPPORTED},
- // Kanji doesn't support Chinese, etc.
- { "zh-Hani", "Hant", NOT_SUPPORTED },
- { "zh-Hani", "Hans", NOT_SUPPORTED },
- { "zh-Hani", "Hanb", NOT_SUPPORTED },
+ // Kanji doesn't support Chinese, etc.
+ {"zh-Hani", "Hant", NOT_SUPPORTED},
+ {"zh-Hani", "Hans", NOT_SUPPORTED},
+ {"zh-Hani", "Hanb", NOT_SUPPORTED},
- // Hangul doesn't support Korean, etc.
- { "ko-Hang", "Kore", NOT_SUPPORTED },
- { "ko-Hani", "Kore", NOT_SUPPORTED },
- { "ko-Hani", "Hang", NOT_SUPPORTED },
- { "ko-Hang", "Hani", NOT_SUPPORTED },
+ // Hangul doesn't support Korean, etc.
+ {"ko-Hang", "Kore", NOT_SUPPORTED},
+ {"ko-Hani", "Kore", NOT_SUPPORTED},
+ {"ko-Hani", "Hang", NOT_SUPPORTED},
+ {"ko-Hang", "Hani", NOT_SUPPORTED},
- // Han with botomofo doesn't support simplified Chinese, etc.
- { "zh-Hanb", "Hant", NOT_SUPPORTED },
- { "zh-Hanb", "Hans", NOT_SUPPORTED },
- { "zh-Hanb", "Jpan", NOT_SUPPORTED },
- { "zh-Hanb", "Kore", NOT_SUPPORTED },
+ // Han with botomofo doesn't support simplified Chinese, etc.
+ {"zh-Hanb", "Hant", NOT_SUPPORTED},
+ {"zh-Hanb", "Hans", NOT_SUPPORTED},
+ {"zh-Hanb", "Jpan", NOT_SUPPORTED},
+ {"zh-Hanb", "Kore", NOT_SUPPORTED},
};
for (auto testCase : testCases) {
@@ -250,240 +244,217 @@
HB_TAG(testCase.requestedScript[0], testCase.requestedScript[1],
testCase.requestedScript[2], testCase.requestedScript[3]));
if (testCase.isSupported) {
- EXPECT_TRUE(
- createFontLanguage(testCase.baseScript).supportsHbScript(script))
+ EXPECT_TRUE(createLocale(testCase.baseScript).supportsHbScript(script))
<< testCase.baseScript << " should support " << testCase.requestedScript;
} else {
- EXPECT_FALSE(
- createFontLanguage(testCase.baseScript).supportsHbScript(script))
+ EXPECT_FALSE(createLocale(testCase.baseScript).supportsHbScript(script))
<< testCase.baseScript << " shouldn't support " << testCase.requestedScript;
}
}
}
-TEST_F(FontLanguagesTest, basicTests) {
- FontLanguages emptyLangs;
- EXPECT_EQ(0u, emptyLangs.size());
+TEST(LocaleListTest, basicTests) {
+ LocaleList emptyLocales;
+ EXPECT_EQ(0u, emptyLocales.size());
- FontLanguage english = createFontLanguage("en");
- const FontLanguages& singletonLangs = createFontLanguages("en");
- EXPECT_EQ(1u, singletonLangs.size());
- EXPECT_EQ(english, singletonLangs[0]);
+ Locale english = createLocale("en");
+ const LocaleList& singletonLocales = createLocaleList("en");
+ EXPECT_EQ(1u, singletonLocales.size());
+ EXPECT_EQ(english, singletonLocales[0]);
- FontLanguage french = createFontLanguage("fr");
- const FontLanguages& twoLangs = createFontLanguages("en,fr");
- EXPECT_EQ(2u, twoLangs.size());
- EXPECT_EQ(english, twoLangs[0]);
- EXPECT_EQ(french, twoLangs[1]);
+ Locale french = createLocale("fr");
+ const LocaleList& twoLocales = createLocaleList("en,fr");
+ EXPECT_EQ(2u, twoLocales.size());
+ EXPECT_EQ(english, twoLocales[0]);
+ EXPECT_EQ(french, twoLocales[1]);
}
-TEST_F(FontLanguagesTest, unsupportedLanguageTests) {
- const FontLanguages& oneUnsupported = createFontLanguages("abcd-example");
+TEST(LocaleListTest, unsupportedLocaleuageTests) {
+ const LocaleList& oneUnsupported = createLocaleList("abcd-example");
EXPECT_TRUE(oneUnsupported.empty());
- const FontLanguages& twoUnsupporteds = createFontLanguages("abcd-example,abcd-example");
+ const LocaleList& twoUnsupporteds = createLocaleList("abcd-example,abcd-example");
EXPECT_TRUE(twoUnsupporteds.empty());
- FontLanguage english = createFontLanguage("en");
- const FontLanguages& firstUnsupported = createFontLanguages("abcd-example,en");
+ Locale english = createLocale("en");
+ const LocaleList& firstUnsupported = createLocaleList("abcd-example,en");
EXPECT_EQ(1u, firstUnsupported.size());
EXPECT_EQ(english, firstUnsupported[0]);
- const FontLanguages& lastUnsupported = createFontLanguages("en,abcd-example");
+ const LocaleList& lastUnsupported = createLocaleList("en,abcd-example");
EXPECT_EQ(1u, lastUnsupported.size());
EXPECT_EQ(english, lastUnsupported[0]);
}
-TEST_F(FontLanguagesTest, repeatedLanguageTests) {
- FontLanguage english = createFontLanguage("en");
- FontLanguage french = createFontLanguage("fr");
- FontLanguage canadianFrench = createFontLanguage("fr-CA");
- FontLanguage englishInLatn = createFontLanguage("en-Latn");
+TEST(LocaleListTest, repeatedLocaleuageTests) {
+ Locale english = createLocale("en");
+ Locale french = createLocale("fr");
+ Locale canadianFrench = createLocale("fr-CA");
+ Locale englishInLatn = createLocale("en-Latn");
ASSERT_TRUE(english == englishInLatn);
- const FontLanguages& langs = createFontLanguages("en,en-Latn");
- EXPECT_EQ(1u, langs.size());
- EXPECT_EQ(english, langs[0]);
+ const LocaleList& locales = createLocaleList("en,en-Latn");
+ EXPECT_EQ(1u, locales.size());
+ EXPECT_EQ(english, locales[0]);
- const FontLanguages& fr = createFontLanguages("fr,fr-FR,fr-Latn-FR");
+ const LocaleList& fr = createLocaleList("fr,fr-FR,fr-Latn-FR");
EXPECT_EQ(1u, fr.size());
EXPECT_EQ(french, fr[0]);
// ICU appends FR to fr. The third language is dropped which is same as the first language.
- const FontLanguages& fr2 = createFontLanguages("fr,fr-CA,fr-FR");
+ const LocaleList& fr2 = createLocaleList("fr,fr-CA,fr-FR");
EXPECT_EQ(2u, fr2.size());
EXPECT_EQ(french, fr2[0]);
EXPECT_EQ(canadianFrench, fr2[1]);
// The order should be kept.
- const FontLanguages& langs2 = createFontLanguages("en,fr,en-Latn");
- EXPECT_EQ(2u, langs2.size());
- EXPECT_EQ(english, langs2[0]);
- EXPECT_EQ(french, langs2[1]);
+ const LocaleList& locales2 = createLocaleList("en,fr,en-Latn");
+ EXPECT_EQ(2u, locales2.size());
+ EXPECT_EQ(english, locales2[0]);
+ EXPECT_EQ(french, locales2[1]);
}
-TEST_F(FontLanguagesTest, identifierTest) {
- EXPECT_EQ(createFontLanguage("en-Latn-US"), createFontLanguage("en-Latn-US"));
- EXPECT_EQ(createFontLanguage("zh-Hans-CN"), createFontLanguage("zh-Hans-CN"));
- EXPECT_EQ(createFontLanguage("en-Zsye-US"), createFontLanguage("en-Zsye-US"));
+TEST(LocaleListTest, identifierTest) {
+ EXPECT_EQ(createLocale("en-Latn-US"), createLocale("en-Latn-US"));
+ EXPECT_EQ(createLocale("zh-Hans-CN"), createLocale("zh-Hans-CN"));
+ EXPECT_EQ(createLocale("en-Zsye-US"), createLocale("en-Zsye-US"));
- EXPECT_NE(createFontLanguage("en-Latn-US"), createFontLanguage("en-Latn-GB"));
- EXPECT_NE(createFontLanguage("en-Latn-US"), createFontLanguage("en-Zsye-US"));
- EXPECT_NE(createFontLanguage("es-Latn-US"), createFontLanguage("en-Latn-US"));
- EXPECT_NE(createFontLanguage("zh-Hant-HK"), createFontLanguage("zh-Hant-TW"));
+ EXPECT_NE(createLocale("en-Latn-US"), createLocale("en-Latn-GB"));
+ EXPECT_NE(createLocale("en-Latn-US"), createLocale("en-Zsye-US"));
+ EXPECT_NE(createLocale("es-Latn-US"), createLocale("en-Latn-US"));
+ EXPECT_NE(createLocale("zh-Hant-HK"), createLocale("zh-Hant-TW"));
}
-TEST_F(FontLanguagesTest, undEmojiTests) {
- FontLanguage emoji = createFontLanguage("und-Zsye");
- EXPECT_EQ(FontLanguage::EMSTYLE_EMOJI, emoji.getEmojiStyle());
+TEST(LocaleListTest, undEmojiTests) {
+ Locale emoji = createLocale("und-Zsye");
+ EXPECT_EQ(EmojiStyle::EMOJI, emoji.getEmojiStyle());
- FontLanguage und = createFontLanguage("und");
- EXPECT_EQ(FontLanguage::EMSTYLE_EMPTY, und.getEmojiStyle());
+ Locale und = createLocale("und");
+ EXPECT_EQ(EmojiStyle::EMPTY, und.getEmojiStyle());
EXPECT_FALSE(emoji == und);
- FontLanguage undExample = createFontLanguage("und-example");
- EXPECT_EQ(FontLanguage::EMSTYLE_EMPTY, undExample.getEmojiStyle());
+ Locale undExample = createLocale("und-example");
+ EXPECT_EQ(EmojiStyle::EMPTY, undExample.getEmojiStyle());
EXPECT_FALSE(emoji == undExample);
}
-TEST_F(FontLanguagesTest, subtagEmojiTest) {
+TEST(LocaleListTest, subtagEmojiTest) {
std::string subtagEmojiStrings[] = {
- // Duplicate subtag case.
- "und-Latn-u-em-emoji-u-em-text",
+ // Duplicate subtag case.
+ "und-Latn-u-em-emoji-u-em-text",
- // Strings that contain language.
- "und-u-em-emoji",
- "en-u-em-emoji",
+ // Strings that contain language.
+ "und-u-em-emoji", "en-u-em-emoji",
- // Strings that contain the script.
- "und-Jpan-u-em-emoji",
- "en-Latn-u-em-emoji",
- "und-Zsym-u-em-emoji",
- "und-Zsye-u-em-emoji",
- "en-Zsym-u-em-emoji",
- "en-Zsye-u-em-emoji",
+ // Strings that contain the script.
+ "und-Jpan-u-em-emoji", "en-Latn-u-em-emoji", "und-Zsym-u-em-emoji",
+ "und-Zsye-u-em-emoji", "en-Zsym-u-em-emoji", "en-Zsye-u-em-emoji",
- // Strings that contain the county.
- "und-US-u-em-emoji",
- "en-US-u-em-emoji",
- "es-419-u-em-emoji",
- "und-Latn-US-u-em-emoji",
- "en-Zsym-US-u-em-emoji",
- "en-Zsye-US-u-em-emoji",
- "es-Zsye-419-u-em-emoji",
+ // Strings that contain the country.
+ "und-US-u-em-emoji", "en-US-u-em-emoji", "es-419-u-em-emoji", "und-Latn-US-u-em-emoji",
+ "en-Zsym-US-u-em-emoji", "en-Zsye-US-u-em-emoji", "es-Zsye-419-u-em-emoji",
+
+ // Strings that contain the variant.
+ "de-Latn-DE-1901-u-em-emoji",
};
for (auto subtagEmojiString : subtagEmojiStrings) {
SCOPED_TRACE("Test for \"" + subtagEmojiString + "\"");
- FontLanguage subtagEmoji = createFontLanguage(subtagEmojiString);
- EXPECT_EQ(FontLanguage::EMSTYLE_EMOJI, subtagEmoji.getEmojiStyle());
+ Locale subtagEmoji = createLocale(subtagEmojiString);
+ EXPECT_EQ(EmojiStyle::EMOJI, subtagEmoji.getEmojiStyle());
}
}
-TEST_F(FontLanguagesTest, subtagTextTest) {
+TEST(LocaleListTest, subtagTextTest) {
std::string subtagTextStrings[] = {
- // Duplicate subtag case.
- "und-Latn-u-em-text-u-em-emoji",
+ // Duplicate subtag case.
+ "und-Latn-u-em-text-u-em-emoji",
- // Strings that contain language.
- "und-u-em-text",
- "en-u-em-text",
+ // Strings that contain language.
+ "und-u-em-text", "en-u-em-text",
- // Strings that contain the script.
- "und-Latn-u-em-text",
- "en-Jpan-u-em-text",
- "und-Zsym-u-em-text",
- "und-Zsye-u-em-text",
- "en-Zsym-u-em-text",
- "en-Zsye-u-em-text",
+ // Strings that contain the script.
+ "und-Latn-u-em-text", "en-Jpan-u-em-text", "und-Zsym-u-em-text", "und-Zsye-u-em-text",
+ "en-Zsym-u-em-text", "en-Zsye-u-em-text",
- // Strings that contain the county.
- "und-US-u-em-text",
- "en-US-u-em-text",
- "es-419-u-em-text",
- "und-Latn-US-u-em-text",
- "en-Zsym-US-u-em-text",
- "en-Zsye-US-u-em-text",
- "es-Zsye-419-u-em-text",
+ // Strings that contain the country.
+ "und-US-u-em-text", "en-US-u-em-text", "es-419-u-em-text", "und-Latn-US-u-em-text",
+ "en-Zsym-US-u-em-text", "en-Zsye-US-u-em-text", "es-Zsye-419-u-em-text",
+
+ // Strings that contain the variant.
+ "de-Latn-DE-1901-u-em-text",
};
for (auto subtagTextString : subtagTextStrings) {
SCOPED_TRACE("Test for \"" + subtagTextString + "\"");
- FontLanguage subtagText = createFontLanguage(subtagTextString);
- EXPECT_EQ(FontLanguage::EMSTYLE_TEXT, subtagText.getEmojiStyle());
+ Locale subtagText = createLocale(subtagTextString);
+ EXPECT_EQ(EmojiStyle::TEXT, subtagText.getEmojiStyle());
}
}
// TODO: add more "und" language cases whose language and script are
// unexpectedly translated to en-Latn by ICU.
-TEST_F(FontLanguagesTest, subtagDefaultTest) {
+TEST(LocaleListTest, subtagDefaultTest) {
std::string subtagDefaultStrings[] = {
- // Duplicate subtag case.
- "en-Latn-u-em-default-u-em-emoji",
- "en-Latn-u-em-default-u-em-text",
+ // Duplicate subtag case.
+ "en-Latn-u-em-default-u-em-emoji", "en-Latn-u-em-default-u-em-text",
- // Strings that contain language.
- "und-u-em-default",
- "en-u-em-default",
+ // Strings that contain language.
+ "und-u-em-default", "en-u-em-default",
- // Strings that contain the script.
- "en-Latn-u-em-default",
- "en-Zsym-u-em-default",
- "en-Zsye-u-em-default",
+ // Strings that contain the script.
+ "en-Latn-u-em-default", "en-Zsym-u-em-default", "en-Zsye-u-em-default",
- // Strings that contain the county.
- "en-US-u-em-default",
- "en-Latn-US-u-em-default",
- "es-Latn-419-u-em-default",
- "en-Zsym-US-u-em-default",
- "en-Zsye-US-u-em-default",
- "es-Zsye-419-u-em-default",
+ // Strings that contain the country.
+ "en-US-u-em-default", "en-Latn-US-u-em-default", "es-Latn-419-u-em-default",
+ "en-Zsym-US-u-em-default", "en-Zsye-US-u-em-default", "es-Zsye-419-u-em-default",
+
+ // Strings that contain the variant.
+ "de-Latn-DE-1901-u-em-default",
};
for (auto subtagDefaultString : subtagDefaultStrings) {
SCOPED_TRACE("Test for \"" + subtagDefaultString + "\"");
- FontLanguage subtagDefault = createFontLanguage(subtagDefaultString);
- EXPECT_EQ(FontLanguage::EMSTYLE_DEFAULT, subtagDefault.getEmojiStyle());
+ Locale subtagDefault = createLocale(subtagDefaultString);
+ EXPECT_EQ(EmojiStyle::DEFAULT, subtagDefault.getEmojiStyle());
}
}
-TEST_F(FontLanguagesTest, subtagEmptyTest) {
+TEST(LocaleListTest, subtagEmptyTest) {
std::string subtagEmptyStrings[] = {
- "und",
- "jp",
- "en-US",
- "en-Latn",
- "en-Latn-US",
- "en-Latn-US-u-em",
- "en-Latn-US-u-em-defaultemoji",
+ "und",
+ "jp",
+ "en-US",
+ "en-Latn",
+ "en-Latn-US",
+ "en-Latn-US-u-em",
+ "en-Latn-US-u-em-defaultemoji",
+ "de-Latn-DE-1901",
};
for (auto subtagEmptyString : subtagEmptyStrings) {
SCOPED_TRACE("Test for \"" + subtagEmptyString + "\"");
- FontLanguage subtagEmpty = createFontLanguage(subtagEmptyString);
- EXPECT_EQ(FontLanguage::EMSTYLE_EMPTY, subtagEmpty.getEmojiStyle());
+ Locale subtagEmpty = createLocale(subtagEmptyString);
+ EXPECT_EQ(EmojiStyle::EMPTY, subtagEmpty.getEmojiStyle());
}
}
-TEST_F(FontLanguagesTest, registerLanguageListTest) {
- EXPECT_EQ(0UL, FontStyle::registerLanguageList(""));
- EXPECT_NE(0UL, FontStyle::registerLanguageList("en"));
- EXPECT_NE(0UL, FontStyle::registerLanguageList("jp"));
- EXPECT_NE(0UL, FontStyle::registerLanguageList("en,zh-Hans"));
+TEST(LocaleListTest, registerLocaleListTest) {
+ EXPECT_EQ(0UL, registerLocaleList(""));
+ EXPECT_NE(0UL, registerLocaleList("en"));
+ EXPECT_NE(0UL, registerLocaleList("jp"));
+ EXPECT_NE(0UL, registerLocaleList("en,zh-Hans"));
- EXPECT_EQ(FontStyle::registerLanguageList("en"), FontStyle::registerLanguageList("en"));
- EXPECT_NE(FontStyle::registerLanguageList("en"), FontStyle::registerLanguageList("jp"));
+ EXPECT_EQ(registerLocaleList("en"), registerLocaleList("en"));
+ EXPECT_NE(registerLocaleList("en"), registerLocaleList("jp"));
+ EXPECT_NE(registerLocaleList("de"), registerLocaleList("de-1901"));
- EXPECT_EQ(FontStyle::registerLanguageList("en,zh-Hans"),
- FontStyle::registerLanguageList("en,zh-Hans"));
- EXPECT_NE(FontStyle::registerLanguageList("en,zh-Hans"),
- FontStyle::registerLanguageList("zh-Hans,en"));
- EXPECT_NE(FontStyle::registerLanguageList("en,zh-Hans"),
- FontStyle::registerLanguageList("jp"));
- EXPECT_NE(FontStyle::registerLanguageList("en,zh-Hans"),
- FontStyle::registerLanguageList("en"));
- EXPECT_NE(FontStyle::registerLanguageList("en,zh-Hans"),
- FontStyle::registerLanguageList("en,zh-Hant"));
+ EXPECT_EQ(registerLocaleList("en,zh-Hans"), registerLocaleList("en,zh-Hans"));
+ EXPECT_NE(registerLocaleList("en,zh-Hans"), registerLocaleList("zh-Hans,en"));
+ EXPECT_NE(registerLocaleList("en,zh-Hans"), registerLocaleList("jp"));
+ EXPECT_NE(registerLocaleList("en,zh-Hans"), registerLocaleList("en"));
+ EXPECT_NE(registerLocaleList("en,zh-Hans"), registerLocaleList("en,zh-Hant"));
+ EXPECT_NE(registerLocaleList("de,de-1901"), registerLocaleList("de-1901,de"));
}
// The test font has following glyphs.
@@ -501,13 +472,12 @@
// U+717D U+FE02 (VS3)
// U+717D U+E0102 (VS19)
// U+717D U+E0103 (VS20)
-const char kVsTestFont[] = kTestFontDir "VariationSelectorTest-Regular.ttf";
+const char kVsTestFont[] = "VariationSelectorTest-Regular.ttf";
-class FontFamilyTest : public ICUTestBase {
+class FontFamilyTest : public testing::Test {
public:
virtual void SetUp() override {
- ICUTestBase::SetUp();
- if (access(kVsTestFont, R_OK) != 0) {
+ if (access(getTestFontPath(kVsTestFont).c_str(), R_OK) != 0) {
FAIL() << "Unable to read " << kVsTestFont << ". "
<< "Please prepare the test data directory. "
<< "For more details, please see how_to_run.txt.";
@@ -530,14 +500,11 @@
EXPECT_TRUE(family->hasGlyph(codepoint, i))
<< "Glyph for U+" << std::hex << codepoint << " U+" << i;
}
-
}
}
TEST_F(FontFamilyTest, hasVariationSelectorTest) {
- std::shared_ptr<MinikinFont> minikinFont(new MinikinFontForTest(kVsTestFont));
- std::shared_ptr<FontFamily> family(
- new FontFamily(std::vector<Font>{ Font(minikinFont, FontStyle()) }));
+ std::shared_ptr<FontFamily> family = buildFontFamily(kVsTestFont);
const uint32_t kVS1 = 0xFE00;
const uint32_t kVS2 = 0xFE01;
@@ -573,41 +540,34 @@
const std::string fontPath;
bool hasVSTable;
} testCases[] = {
- { kTestFontDir "Ja.ttf", true },
- { kTestFontDir "ZhHant.ttf", true },
- { kTestFontDir "ZhHans.ttf", true },
- { kTestFontDir "Italic.ttf", false },
- { kTestFontDir "Bold.ttf", false },
- { kTestFontDir "BoldItalic.ttf", false },
+ {"Ja.ttf", true}, {"ZhHant.ttf", true}, {"ZhHans.ttf", true},
+ {"Italic.ttf", false}, {"Bold.ttf", false}, {"BoldItalic.ttf", false},
};
for (auto testCase : testCases) {
- SCOPED_TRACE(testCase.hasVSTable ?
- "Font " + testCase.fontPath + " should have a variation sequence table." :
- "Font " + testCase.fontPath + " shouldn't have a variation sequence table.");
+ SCOPED_TRACE(testCase.hasVSTable ? "Font " + testCase.fontPath +
+ " should have a variation sequence table."
+ : "Font " + testCase.fontPath +
+ " shouldn't have a variation sequence table.");
- std::shared_ptr<MinikinFont> minikinFont(
- new MinikinFontForTest(testCase.fontPath));
- std::shared_ptr<FontFamily> family(new FontFamily(
- std::vector<Font>{ Font(minikinFont, FontStyle()) }));
+ std::shared_ptr<FontFamily> family = buildFontFamily(testCase.fontPath);
EXPECT_EQ(testCase.hasVSTable, family->hasVSTable());
}
}
TEST_F(FontFamilyTest, createFamilyWithVariationTest) {
// This font has 'wdth' and 'wght' axes.
- const char kMultiAxisFont[] = kTestFontDir "/MultiAxis.ttf";
- const char kNoAxisFont[] = kTestFontDir "/Regular.ttf";
+ const char kMultiAxisFont[] = "MultiAxis.ttf";
+ const char kNoAxisFont[] = "Regular.ttf";
- std::shared_ptr<FontFamily> multiAxisFamily = makeFamily(kMultiAxisFont);
- std::shared_ptr<FontFamily> noAxisFamily = makeFamily(kNoAxisFont);
+ std::shared_ptr<FontFamily> multiAxisFamily = buildFontFamily(kMultiAxisFont);
+ std::shared_ptr<FontFamily> noAxisFamily = buildFontFamily(kNoAxisFont);
{
// Do not ceate new instance if none of variations are specified.
EXPECT_EQ(nullptr,
- multiAxisFamily->createFamilyWithVariation(std::vector<FontVariation>()));
- EXPECT_EQ(nullptr,
- noAxisFamily->createFamilyWithVariation(std::vector<FontVariation>()));
+ multiAxisFamily->createFamilyWithVariation(std::vector<FontVariation>()));
+ EXPECT_EQ(nullptr, noAxisFamily->createFamilyWithVariation(std::vector<FontVariation>()));
}
{
// New instance should be used for supported variation.
@@ -620,10 +580,8 @@
}
{
// New instance should be used for supported variation. (multiple variations case)
- std::vector<FontVariation> variations = {
- { MinikinFont::MakeTag('w', 'd', 't', 'h'), 1.0f },
- { MinikinFont::MakeTag('w', 'g', 'h', 't'), 1.0f }
- };
+ std::vector<FontVariation> variations = {{MinikinFont::MakeTag('w', 'd', 't', 'h'), 1.0f},
+ {MinikinFont::MakeTag('w', 'g', 'h', 't'), 1.0f}};
std::shared_ptr<FontFamily> newFamily(
multiAxisFamily->createFamilyWithVariation(variations));
EXPECT_NE(nullptr, newFamily.get());
@@ -632,18 +590,14 @@
}
{
// Do not ceate new instance if none of variations are supported.
- std::vector<FontVariation> variations = {
- { MinikinFont::MakeTag('Z', 'Z', 'Z', 'Z'), 1.0f }
- };
+ std::vector<FontVariation> variations = {{MinikinFont::MakeTag('Z', 'Z', 'Z', 'Z'), 1.0f}};
EXPECT_EQ(nullptr, multiAxisFamily->createFamilyWithVariation(variations));
EXPECT_EQ(nullptr, noAxisFamily->createFamilyWithVariation(variations));
}
{
// At least one axis is supported, should create new instance.
- std::vector<FontVariation> variations = {
- { MinikinFont::MakeTag('w', 'd', 't', 'h'), 1.0f },
- { MinikinFont::MakeTag('Z', 'Z', 'Z', 'Z'), 1.0f }
- };
+ std::vector<FontVariation> variations = {{MinikinFont::MakeTag('w', 'd', 't', 'h'), 1.0f},
+ {MinikinFont::MakeTag('Z', 'Z', 'Z', 'Z'), 1.0f}};
std::shared_ptr<FontFamily> newFamily(
multiAxisFamily->createFamilyWithVariation(variations));
EXPECT_NE(nullptr, newFamily.get());
@@ -655,22 +609,20 @@
TEST_F(FontFamilyTest, coverageTableSelectionTest) {
// This font supports U+0061. The cmap subtable is format 4 and its platform ID is 0 and
// encoding ID is 1.
- const char kUnicodeEncoding1Font[] = kTestFontDir "UnicodeBMPOnly.ttf";
+ const char kUnicodeEncoding1Font[] = "UnicodeBMPOnly.ttf";
// This font supports U+0061. The cmap subtable is format 4 and its platform ID is 0 and
// encoding ID is 3.
- const char kUnicodeEncoding3Font[] = kTestFontDir "UnicodeBMPOnly2.ttf";
+ const char kUnicodeEncoding3Font[] = "UnicodeBMPOnly2.ttf";
// This font has both cmap format 4 subtable which platform ID is 0 and encoding ID is 1
// and cmap format 14 subtable which platform ID is 0 and encoding ID is 10.
// U+0061 is listed in both subtable but U+1F926 is only listed in latter.
- const char kUnicodeEncoding4Font[] = kTestFontDir "UnicodeUCS4.ttf";
+ const char kUnicodeEncoding4Font[] = "UnicodeUCS4.ttf";
- std::shared_ptr<FontFamily> unicodeEnc1Font = makeFamily(kUnicodeEncoding1Font);
- std::shared_ptr<FontFamily> unicodeEnc3Font = makeFamily(kUnicodeEncoding3Font);
- std::shared_ptr<FontFamily> unicodeEnc4Font = makeFamily(kUnicodeEncoding4Font);
-
- android::AutoMutex _l(gMinikinLock);
+ std::shared_ptr<FontFamily> unicodeEnc1Font = buildFontFamily(kUnicodeEncoding1Font);
+ std::shared_ptr<FontFamily> unicodeEnc3Font = buildFontFamily(kUnicodeEncoding3Font);
+ std::shared_ptr<FontFamily> unicodeEnc4Font = buildFontFamily(kUnicodeEncoding4Font);
EXPECT_TRUE(unicodeEnc1Font->hasGlyph(0x0061, 0));
EXPECT_TRUE(unicodeEnc3Font->hasGlyph(0x0061, 0));
@@ -679,4 +631,117 @@
EXPECT_TRUE(unicodeEnc4Font->hasGlyph(0x1F926, 0));
}
+const char* slantToString(FontStyle::Slant slant) {
+ if (slant == FontStyle::Slant::ITALIC) {
+ return "ITALIC";
+ } else {
+ return "UPRIGHT";
+ }
+}
+
+std::string fontStyleToString(const FontStyle& style) {
+ char buf[64] = {};
+ snprintf(buf, sizeof(buf), "FontStyle(weight=%d, slant=%s)", style.weight(),
+ slantToString(style.slant()));
+ return buf;
+}
+
+TEST_F(FontFamilyTest, closestMatch) {
+ constexpr char kTestFont[] = "Ascii.ttf";
+
+ constexpr FontStyle::Weight THIN = FontStyle::Weight::THIN;
+ constexpr FontStyle::Weight LIGHT = FontStyle::Weight::LIGHT;
+ constexpr FontStyle::Weight NORMAL = FontStyle::Weight::NORMAL;
+ constexpr FontStyle::Weight MEDIUM = FontStyle::Weight::MEDIUM;
+ constexpr FontStyle::Weight BOLD = FontStyle::Weight::BOLD;
+ constexpr FontStyle::Weight BLACK = FontStyle::Weight::BLACK;
+
+ constexpr FontStyle::Slant UPRIGHT = FontStyle::Slant::UPRIGHT;
+ constexpr FontStyle::Slant ITALIC = FontStyle::Slant::ITALIC;
+
+ const std::vector<FontStyle> STANDARD_SET = {
+ FontStyle(NORMAL, UPRIGHT), // 0
+ FontStyle(BOLD, UPRIGHT), // 1
+ FontStyle(NORMAL, ITALIC), // 2
+ FontStyle(BOLD, ITALIC), // 3
+ };
+
+ const std::vector<FontStyle> FULL_SET = {
+ FontStyle(THIN, UPRIGHT), // 0
+ FontStyle(LIGHT, UPRIGHT), // 1
+ FontStyle(NORMAL, UPRIGHT), // 2
+ FontStyle(MEDIUM, UPRIGHT), // 3
+ FontStyle(BOLD, UPRIGHT), // 4
+ FontStyle(BLACK, UPRIGHT), // 5
+ FontStyle(THIN, ITALIC), // 6
+ FontStyle(LIGHT, ITALIC), // 7
+ FontStyle(NORMAL, ITALIC), // 8
+ FontStyle(MEDIUM, ITALIC), // 9
+ FontStyle(BOLD, ITALIC), // 10
+ FontStyle(BLACK, ITALIC), // 11
+ };
+ struct TestCase {
+ FontStyle wantedStyle;
+ std::vector<FontStyle> familyStyles;
+ size_t expectedIndex;
+ } testCases[] = {
+ {FontStyle(), {FontStyle()}, 0},
+
+ // Exact matches
+ {FontStyle(BOLD), {FontStyle(NORMAL), FontStyle(BOLD)}, 1},
+ {FontStyle(BOLD), {FontStyle(LIGHT), FontStyle(BOLD)}, 1},
+ {FontStyle(LIGHT), {FontStyle(NORMAL), FontStyle(LIGHT)}, 1},
+ {FontStyle(LIGHT), {FontStyle(BOLD), FontStyle(LIGHT)}, 1},
+ {FontStyle(NORMAL), {FontStyle(NORMAL), FontStyle(LIGHT)}, 0},
+ {FontStyle(NORMAL), {FontStyle(NORMAL), FontStyle(BOLD)}, 0},
+ {FontStyle(LIGHT), {FontStyle(LIGHT), FontStyle(NORMAL), FontStyle(BOLD)}, 0},
+ {FontStyle(NORMAL), {FontStyle(LIGHT), FontStyle(NORMAL), FontStyle(BOLD)}, 1},
+ {FontStyle(BOLD), {FontStyle(LIGHT), FontStyle(NORMAL), FontStyle(BOLD)}, 2},
+
+ {FontStyle(UPRIGHT), {FontStyle(UPRIGHT), FontStyle(ITALIC)}, 0},
+ {FontStyle(ITALIC), {FontStyle(UPRIGHT), FontStyle(ITALIC)}, 1},
+
+ {FontStyle(NORMAL, UPRIGHT), STANDARD_SET, 0},
+ {FontStyle(BOLD, UPRIGHT), STANDARD_SET, 1},
+ {FontStyle(NORMAL, ITALIC), STANDARD_SET, 2},
+ {FontStyle(BOLD, ITALIC), STANDARD_SET, 3},
+
+ {FontStyle(NORMAL, UPRIGHT), FULL_SET, 2},
+ {FontStyle(BOLD, UPRIGHT), FULL_SET, 4},
+ {FontStyle(NORMAL, ITALIC), FULL_SET, 8},
+ {FontStyle(BOLD, ITALIC), FULL_SET, 10},
+
+ // TODO: Add fallback expectations. (b/68814338)
+ };
+
+ for (const TestCase& testCase : testCases) {
+ std::vector<std::shared_ptr<MinikinFont>> dummyFonts;
+ std::vector<Font> fonts;
+ for (auto familyStyle : testCase.familyStyles) {
+ std::shared_ptr<MinikinFont> dummyFont(
+ new FreeTypeMinikinFontForTest(getTestFontPath(kTestFont)));
+ dummyFonts.push_back(dummyFont);
+ fonts.push_back(Font::Builder(dummyFont).setStyle(familyStyle).build());
+ }
+
+ FontFamily family(std::move(fonts));
+ FakedFont closest = family.getClosestMatch(testCase.wantedStyle);
+
+ size_t idx = dummyFonts.size();
+ for (size_t i = 0; i < dummyFonts.size(); i++) {
+ if (dummyFonts[i].get() == closest.font->typeface().get()) {
+ idx = i;
+ break;
+ }
+ }
+ ASSERT_NE(idx, dummyFonts.size()) << "The selected font is unknown.";
+ EXPECT_EQ(testCase.expectedIndex, idx)
+ << "Input Style: " << fontStyleToString(testCase.wantedStyle) << std::endl
+ << "Actual Families' Style: " << fontStyleToString(testCase.familyStyles[idx])
+ << std::endl
+ << "Expected Families' Style: "
+ << fontStyleToString(testCase.familyStyles[testCase.expectedIndex]) << std::endl;
+ }
+}
+
} // namespace minikin
diff --git a/tests/unittest/FontLanguageListCacheTest.cpp b/tests/unittest/FontLanguageListCacheTest.cpp
index 81d84a8..e957cfc 100644
--- a/tests/unittest/FontLanguageListCacheTest.cpp
+++ b/tests/unittest/FontLanguageListCacheTest.cpp
@@ -14,60 +14,51 @@
* limitations under the License.
*/
+#include "minikin/FontFamily.h"
+
#include <gtest/gtest.h>
-#include <minikin/FontFamily.h>
+#include "minikin/LocaleList.h"
-#include "FontLanguageListCache.h"
-#include "ICUTestBase.h"
+#include "LocaleListCache.h"
#include "MinikinInternal.h"
namespace minikin {
-typedef ICUTestBase FontLanguageListCacheTest;
+TEST(LocaleListCacheTest, getId) {
+ EXPECT_NE(0UL, registerLocaleList("en"));
+ EXPECT_NE(0UL, registerLocaleList("jp"));
+ EXPECT_NE(0UL, registerLocaleList("en,zh-Hans"));
-TEST_F(FontLanguageListCacheTest, getId) {
- EXPECT_NE(0UL, FontStyle::registerLanguageList("en"));
- EXPECT_NE(0UL, FontStyle::registerLanguageList("jp"));
- EXPECT_NE(0UL, FontStyle::registerLanguageList("en,zh-Hans"));
+ EXPECT_EQ(0UL, LocaleListCache::getId(""));
- android::AutoMutex _l(gMinikinLock);
- EXPECT_EQ(0UL, FontLanguageListCache::getId(""));
+ EXPECT_EQ(LocaleListCache::getId("en"), LocaleListCache::getId("en"));
+ EXPECT_NE(LocaleListCache::getId("en"), LocaleListCache::getId("jp"));
- EXPECT_EQ(FontLanguageListCache::getId("en"), FontLanguageListCache::getId("en"));
- EXPECT_NE(FontLanguageListCache::getId("en"), FontLanguageListCache::getId("jp"));
-
- EXPECT_EQ(FontLanguageListCache::getId("en,zh-Hans"),
- FontLanguageListCache::getId("en,zh-Hans"));
- EXPECT_NE(FontLanguageListCache::getId("en,zh-Hans"),
- FontLanguageListCache::getId("zh-Hans,en"));
- EXPECT_NE(FontLanguageListCache::getId("en,zh-Hans"),
- FontLanguageListCache::getId("jp"));
- EXPECT_NE(FontLanguageListCache::getId("en,zh-Hans"),
- FontLanguageListCache::getId("en"));
- EXPECT_NE(FontLanguageListCache::getId("en,zh-Hans"),
- FontLanguageListCache::getId("en,zh-Hant"));
+ EXPECT_EQ(LocaleListCache::getId("en,zh-Hans"), LocaleListCache::getId("en,zh-Hans"));
+ EXPECT_NE(LocaleListCache::getId("en,zh-Hans"), LocaleListCache::getId("zh-Hans,en"));
+ EXPECT_NE(LocaleListCache::getId("en,zh-Hans"), LocaleListCache::getId("jp"));
+ EXPECT_NE(LocaleListCache::getId("en,zh-Hans"), LocaleListCache::getId("en"));
+ EXPECT_NE(LocaleListCache::getId("en,zh-Hans"), LocaleListCache::getId("en,zh-Hant"));
}
-TEST_F(FontLanguageListCacheTest, getById) {
- android::AutoMutex _l(gMinikinLock);
- uint32_t enLangId = FontLanguageListCache::getId("en");
- uint32_t jpLangId = FontLanguageListCache::getId("jp");
- FontLanguage english = FontLanguageListCache::getById(enLangId)[0];
- FontLanguage japanese = FontLanguageListCache::getById(jpLangId)[0];
+TEST(LocaleListCacheTest, getById) {
+ uint32_t enLangId = LocaleListCache::getId("en");
+ uint32_t jpLangId = LocaleListCache::getId("jp");
+ Locale english = LocaleListCache::getById(enLangId)[0];
+ Locale japanese = LocaleListCache::getById(jpLangId)[0];
- const FontLanguages& defLangs = FontLanguageListCache::getById(0);
+ const LocaleList& defLangs = LocaleListCache::getById(0);
EXPECT_TRUE(defLangs.empty());
- const FontLanguages& langs = FontLanguageListCache::getById(FontLanguageListCache::getId("en"));
- ASSERT_EQ(1UL, langs.size());
- EXPECT_EQ(english, langs[0]);
+ const LocaleList& locales = LocaleListCache::getById(LocaleListCache::getId("en"));
+ ASSERT_EQ(1UL, locales.size());
+ EXPECT_EQ(english, locales[0]);
- const FontLanguages& langs2 =
- FontLanguageListCache::getById(FontLanguageListCache::getId("en,jp"));
- ASSERT_EQ(2UL, langs2.size());
- EXPECT_EQ(english, langs2[0]);
- EXPECT_EQ(japanese, langs2[1]);
+ const LocaleList& locales2 = LocaleListCache::getById(LocaleListCache::getId("en,jp"));
+ ASSERT_EQ(2UL, locales2.size());
+ EXPECT_EQ(english, locales2[0]);
+ EXPECT_EQ(japanese, locales2[1]);
}
} // namespace minikin
diff --git a/tests/unittest/GraphemeBreakTests.cpp b/tests/unittest/GraphemeBreakTests.cpp
index 6720df6..362ae10 100644
--- a/tests/unittest/GraphemeBreakTests.cpp
+++ b/tests/unittest/GraphemeBreakTests.cpp
@@ -14,9 +14,13 @@
* limitations under the License.
*/
+#include "minikin/GraphemeBreak.h"
+
+#include <vector>
+
#include <gtest/gtest.h>
-#include <UnicodeUtils.h>
-#include <minikin/GraphemeBreak.h>
+
+#include "UnicodeUtils.h"
namespace minikin {
@@ -44,10 +48,10 @@
// tests for invalid UTF-16
EXPECT_TRUE(IsBreak("U+D800 | U+D800")); // two leading surrogates
EXPECT_TRUE(IsBreak("U+DC00 | U+DC00")); // two trailing surrogates
- EXPECT_TRUE(IsBreak("'a' | U+D800")); // lonely leading surrogate
- EXPECT_TRUE(IsBreak("U+DC00 | 'a'")); // lonely trailing surrogate
- EXPECT_TRUE(IsBreak("U+D800 | 'a'")); // leading surrogate followed by non-surrogate
- EXPECT_TRUE(IsBreak("'a' | U+DC00")); // non-surrogate followed by trailing surrogate
+ EXPECT_TRUE(IsBreak("'a' | U+D800")); // lonely leading surrogate
+ EXPECT_TRUE(IsBreak("U+DC00 | 'a'")); // lonely trailing surrogate
+ EXPECT_TRUE(IsBreak("U+D800 | 'a'")); // leading surrogate followed by non-surrogate
+ EXPECT_TRUE(IsBreak("'a' | U+DC00")); // non-surrogate followed by trailing surrogate
}
TEST(GraphemeBreak, rules) {
@@ -93,15 +97,15 @@
// Rule GB12 and Rule GB13, Regional_Indicator x Regional_Indicator
EXPECT_FALSE(IsBreak("U+1F1FA | U+1F1F8"));
- EXPECT_TRUE(IsBreak("U+1F1FA U+1F1F8 | U+1F1FA U+1F1F8")); // Regional indicator pair (flag)
- EXPECT_FALSE(IsBreak("U+1F1FA | U+1F1F8 U+1F1FA U+1F1F8")); // Regional indicator pair (flag)
- EXPECT_FALSE(IsBreak("U+1F1FA U+1F1F8 U+1F1FA | U+1F1F8")); // Regional indicator pair (flag)
+ EXPECT_TRUE(IsBreak("U+1F1FA U+1F1F8 | U+1F1FA U+1F1F8")); // Regional indicator pair (flag)
+ EXPECT_FALSE(IsBreak("U+1F1FA | U+1F1F8 U+1F1FA U+1F1F8")); // Regional indicator pair (flag)
+ EXPECT_FALSE(IsBreak("U+1F1FA U+1F1F8 U+1F1FA | U+1F1F8")); // Regional indicator pair (flag)
- EXPECT_TRUE(IsBreak("U+1F1FA U+1F1F8 | U+1F1FA")); // Regional indicator pair (flag)
+ EXPECT_TRUE(IsBreak("U+1F1FA U+1F1F8 | U+1F1FA")); // Regional indicator pair (flag)
EXPECT_FALSE(IsBreak("U+1F1FA | U+1F1F8 U+1F1FA")); // Regional indicator pair (flag)
// Same case as the two above, knowing that the first two characters ligate, which is what
// would typically happen.
- const float firstPairLigated[] = {1.0, 0.0, 0.0, 0.0, 1.0, 0.0}; // Two entries per codepoint
+ const float firstPairLigated[] = {1.0, 0.0, 0.0, 0.0, 1.0, 0.0}; // Two entries per codepoint
EXPECT_TRUE(IsBreakWithAdvances(firstPairLigated, "U+1F1FA U+1F1F8 | U+1F1FA"));
EXPECT_FALSE(IsBreakWithAdvances(firstPairLigated, "U+1F1FA | U+1F1F8 U+1F1FA"));
// Repeat the tests, But now the font doesn't have a ligature for the first two characters,
@@ -111,7 +115,7 @@
EXPECT_FALSE(IsBreakWithAdvances(secondPairLigated, "U+1F1FA U+1F1F8 | U+1F1FA"));
EXPECT_TRUE(IsBreakWithAdvances(secondPairLigated, "U+1F1FA | U+1F1F8 U+1F1FA"));
- EXPECT_TRUE(IsBreak("'a' U+1F1FA U+1F1F8 | U+1F1FA")); // Regional indicator pair (flag)
+ EXPECT_TRUE(IsBreak("'a' U+1F1FA U+1F1F8 | U+1F1FA")); // Regional indicator pair (flag)
EXPECT_FALSE(IsBreak("'a' U+1F1FA | U+1F1F8 U+1F1FA")); // Regional indicator pair (flag)
EXPECT_TRUE(
@@ -131,9 +135,9 @@
// Rule GB999, Any ÷ Any
EXPECT_TRUE(IsBreak("'a' | 'b'"));
- EXPECT_TRUE(IsBreak("'f' | 'i'")); // probable ligature
- EXPECT_TRUE(IsBreak("U+0644 | U+0627")); // probable ligature, lam + alef
- EXPECT_TRUE(IsBreak("U+4E00 | U+4E00")); // CJK ideographs
+ EXPECT_TRUE(IsBreak("'f' | 'i'")); // probable ligature
+ EXPECT_TRUE(IsBreak("U+0644 | U+0627")); // probable ligature, lam + alef
+ EXPECT_TRUE(IsBreak("U+4E00 | U+4E00")); // CJK ideographs
EXPECT_TRUE(IsBreak("'a' | U+1F1FA U+1F1F8")); // Regional indicator pair (flag)
EXPECT_TRUE(IsBreak("U+1F1FA U+1F1F8 | 'a'")); // Regional indicator pair (flag)
@@ -168,10 +172,10 @@
TEST(GraphemeBreak, tailoring) {
// control characters that we interpret as "extend"
- EXPECT_FALSE(IsBreak("'a' | U+00AD")); // soft hyphen
- EXPECT_FALSE(IsBreak("'a' | U+200B")); // zwsp
- EXPECT_FALSE(IsBreak("'a' | U+200E")); // lrm
- EXPECT_FALSE(IsBreak("'a' | U+202A")); // lre
+ EXPECT_FALSE(IsBreak("'a' | U+00AD")); // soft hyphen
+ EXPECT_FALSE(IsBreak("'a' | U+200B")); // zwsp
+ EXPECT_FALSE(IsBreak("'a' | U+200E")); // lrm
+ EXPECT_FALSE(IsBreak("'a' | U+202A")); // lre
EXPECT_FALSE(IsBreak("'a' | U+E0041")); // tag character
// UTC-approved characters for the Prepend class
@@ -183,32 +187,32 @@
EXPECT_FALSE(IsBreak("U+0915 | U+094D U+0915")); // Devanagari ka+virama+ka
EXPECT_FALSE(IsBreak("U+0915 U+094D | U+0915")); // Devanagari ka+virama+ka
EXPECT_FALSE(IsBreak("U+0E01 | U+0E3A U+0E01")); // thai phinthu = pure killer
- EXPECT_TRUE(IsBreak("U+0E01 U+0E3A | U+0E01")); // thai phinthu = pure killer
+ EXPECT_TRUE(IsBreak("U+0E01 U+0E3A | U+0E01")); // thai phinthu = pure killer
// Repetition of above tests, but with a given advances array that implies everything
// became just one cluster.
const float conjoined[] = {1.0, 0.0, 0.0};
EXPECT_FALSE(IsBreakWithAdvances(conjoined,
- "U+0915 | U+094D U+0915")); // Devanagari ka+virama+ka
+ "U+0915 | U+094D U+0915")); // Devanagari ka+virama+ka
EXPECT_FALSE(IsBreakWithAdvances(conjoined,
- "U+0915 U+094D | U+0915")); // Devanagari ka+virama+ka
+ "U+0915 U+094D | U+0915")); // Devanagari ka+virama+ka
EXPECT_FALSE(IsBreakWithAdvances(conjoined,
- "U+0E01 | U+0E3A U+0E01")); // thai phinthu = pure killer
+ "U+0E01 | U+0E3A U+0E01")); // thai phinthu = pure killer
EXPECT_TRUE(IsBreakWithAdvances(conjoined,
- "U+0E01 U+0E3A | U+0E01")); // thai phinthu = pure killer
+ "U+0E01 U+0E3A | U+0E01")); // thai phinthu = pure killer
// Repetition of above tests, but with a given advances array that the virama did not
// form a cluster with the following consonant. The difference is that there is now
// a grapheme break after the virama in ka+virama+ka.
const float separate[] = {1.0, 0.0, 1.0};
EXPECT_FALSE(IsBreakWithAdvances(separate,
- "U+0915 | U+094D U+0915")); // Devanagari ka+virama+ka
+ "U+0915 | U+094D U+0915")); // Devanagari ka+virama+ka
EXPECT_TRUE(IsBreakWithAdvances(separate,
- "U+0915 U+094D | U+0915")); // Devanagari ka+virama+ka
+ "U+0915 U+094D | U+0915")); // Devanagari ka+virama+ka
EXPECT_FALSE(IsBreakWithAdvances(separate,
- "U+0E01 | U+0E3A U+0E01")); // thai phinthu = pure killer
+ "U+0E01 | U+0E3A U+0E01")); // thai phinthu = pure killer
EXPECT_TRUE(IsBreakWithAdvances(separate,
- "U+0E01 U+0E3A | U+0E01")); // thai phinthu = pure killer
+ "U+0E01 U+0E3A | U+0E01")); // thai phinthu = pure killer
// suppress grapheme breaks in zwj emoji sequences
EXPECT_FALSE(IsBreak("U+1F469 U+200D | U+2764 U+FE0F U+200D U+1F48B U+200D U+1F468"));
@@ -230,8 +234,8 @@
}
TEST(GraphemeBreak, emojiModifiers) {
- EXPECT_FALSE(IsBreak("U+261D | U+1F3FB")); // white up pointing index + modifier
- EXPECT_FALSE(IsBreak("U+270C | U+1F3FB")); // victory hand + modifier
+ EXPECT_FALSE(IsBreak("U+261D | U+1F3FB")); // white up pointing index + modifier
+ EXPECT_FALSE(IsBreak("U+270C | U+1F3FB")); // victory hand + modifier
EXPECT_FALSE(IsBreak("U+1F466 | U+1F3FB")); // boy + modifier
EXPECT_FALSE(IsBreak("U+1F466 | U+1F3FC")); // boy + modifier
EXPECT_FALSE(IsBreak("U+1F466 | U+1F3FD")); // boy + modifier
@@ -277,10 +281,10 @@
EXPECT_TRUE(IsBreakWithAdvances(unligated1_1_2, "U+270C U+FE0F | U+1F3FB"));
// heart is not an emoji base
- EXPECT_TRUE(IsBreak("U+2764 | U+1F3FB")); // heart + modifier
+ EXPECT_TRUE(IsBreak("U+2764 | U+1F3FB")); // heart + modifier
EXPECT_TRUE(IsBreak("U+2764 U+FE0E | U+1F3FB")); // heart + emoji style + modifier
EXPECT_TRUE(IsBreak("U+2764 U+FE0F | U+1F3FB")); // heart + emoji style + modifier
- EXPECT_TRUE(IsBreak("U+1F3FB | U+1F3FB")); // modifier + modifier
+ EXPECT_TRUE(IsBreak("U+1F3FB | U+1F3FB")); // modifier + modifier
// rat is not an emoji modifer
EXPECT_TRUE(IsBreak("U+1F466 | U+1F400")); // boy + rat
@@ -307,7 +311,7 @@
}
TEST(GraphemeBreak, offsets) {
- uint16_t string[] = { 0x0041, 0x06DD, 0x0045, 0x0301, 0x0049, 0x0301 };
+ uint16_t string[] = {0x0041, 0x06DD, 0x0045, 0x0301, 0x0049, 0x0301};
EXPECT_TRUE(GraphemeBreak::isGraphemeBreak(nullptr, string, 2, 3, 2));
EXPECT_FALSE(GraphemeBreak::isGraphemeBreak(nullptr, string, 2, 3, 3));
EXPECT_TRUE(GraphemeBreak::isGraphemeBreak(nullptr, string, 2, 3, 4));
diff --git a/tests/unittest/GreedyLineBreakerTest.cpp b/tests/unittest/GreedyLineBreakerTest.cpp
new file mode 100644
index 0000000..ad5824d
--- /dev/null
+++ b/tests/unittest/GreedyLineBreakerTest.cpp
@@ -0,0 +1,842 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <memory>
+
+#include <gtest/gtest.h>
+
+#include "minikin/Hyphenator.h"
+
+#include "FileUtils.h"
+#include "FontTestUtils.h"
+#include "GreedyLineBreaker.h"
+#include "HyphenatorMap.h"
+#include "LineBreakerTestHelper.h"
+#include "LocaleListCache.h"
+#include "MinikinInternal.h"
+#include "UnicodeUtils.h"
+#include "WordBreaker.h"
+
+namespace minikin {
+namespace {
+
+using line_breaker_test_helper::ConstantRun;
+using line_breaker_test_helper::LineBreakExpectation;
+using line_breaker_test_helper::RectangleLineWidth;
+using line_breaker_test_helper::sameLineBreak;
+using line_breaker_test_helper::toString;
+
+class GreedyLineBreakerTest : public testing::Test {
+public:
+ GreedyLineBreakerTest() {}
+
+ virtual ~GreedyLineBreakerTest() {}
+
+ virtual void SetUp() override {
+ mHyphenationPattern = readWholeFile("/system/usr/hyphen-data/hyph-en-us.hyb");
+ Hyphenator* hyphenator = Hyphenator::loadBinary(
+ mHyphenationPattern.data(), 2 /* min prefix */, 2 /* min suffix */, "en-US");
+ HyphenatorMap::add("en-US", hyphenator);
+ HyphenatorMap::add("pl", Hyphenator::loadBinary(nullptr, 0, 0, "pl"));
+ }
+
+ virtual void TearDown() override { HyphenatorMap::clear(); }
+
+protected:
+ LineBreakResult doLineBreak(const U16StringPiece& textBuffer, bool doHyphenation,
+ float charWidth, float lineWidth) {
+ return doLineBreak(textBuffer, doHyphenation, charWidth, "en-US", lineWidth);
+ }
+
+ LineBreakResult doLineBreak(const U16StringPiece& textBuffer, bool doHyphenation,
+ float charWidth, const std::string& lang, float lineWidth) {
+ MeasuredTextBuilder builder;
+ builder.addCustomRun<ConstantRun>(Range(0, textBuffer.size()), lang, charWidth);
+ std::unique_ptr<MeasuredText> measuredText = builder.build(
+ textBuffer, false /* compute hyphenation */, false /* compute full layout */);
+ RectangleLineWidth rectangleLineWidth(lineWidth);
+ TabStops tabStops(nullptr, 0, 10);
+ return breakLineGreedy(textBuffer, *measuredText, rectangleLineWidth, tabStops,
+ doHyphenation);
+ }
+
+private:
+ std::vector<uint8_t> mHyphenationPattern;
+};
+
+TEST_F(GreedyLineBreakerTest, testBreakWithoutHyphenation) {
+ constexpr float CHAR_WIDTH = 10.0;
+ constexpr bool NO_HYPHEN = false; // No hyphenation in this test case.
+ const std::vector<uint16_t> textBuf = utf8ToUtf16("This is an example text.");
+
+ constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
+ constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
+ // Note that disable clang-format everywhere since aligned expectation is more readable.
+ {
+ constexpr float LINE_WIDTH = 1000 * CHAR_WIDTH;
+ std::vector<LineBreakExpectation> expect = {
+ {"This is an example text.", 24 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ };
+
+ const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 24 * CHAR_WIDTH;
+ std::vector<LineBreakExpectation> expect = {
+ {"This is an example text.", 24 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ };
+
+ const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 23 * CHAR_WIDTH;
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ { "This is an example ", 18 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "text." , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ };
+ // clang-format on
+
+ const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 8 * CHAR_WIDTH;
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ { "This is ", 7 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "an " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "example ", 7 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "text." , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ };
+ // clang-format on
+
+ const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 7 * CHAR_WIDTH;
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ { "This is ", 7 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "an " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "example ", 7 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "text." , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ };
+ // clang-format on
+
+ const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 6 * CHAR_WIDTH;
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ { "This " , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "is an ", 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "exampl", 6 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "e " , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "text." , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ };
+ // clang-format on
+
+ const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 5 * CHAR_WIDTH;
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ { "This " , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "is an ", 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "examp" , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "le " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "text." , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ };
+ // clang-format on
+
+ const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 4 * CHAR_WIDTH;
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ { "This " , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "is " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "an " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "exam" , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "ple " , 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "text" , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "." , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ };
+ // clang-format on
+
+ const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 3 * CHAR_WIDTH;
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ { "Thi" , 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "s " , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "is " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "an " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "exa" , 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "mpl" , 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "e " , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "tex" , 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "t." , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ };
+ // clang-format on
+
+ const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 2 * CHAR_WIDTH;
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ { "Th" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "is ", 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "is ", 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "an ", 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "ex" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "am" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "pl" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "e " , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "te" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "xt" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "." , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ };
+ // clang-format on
+
+ const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 1 * CHAR_WIDTH;
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ { "T" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "h" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "i" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "s ", 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "i" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "s ", 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "a" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "n ", 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "e" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "x" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "a" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "m" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "p" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "l" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "e ", 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "t" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "e" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "x" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "t" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "." , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ };
+ // clang-format on
+
+ const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+}
+
+TEST_F(GreedyLineBreakerTest, testBreakWithHyphenation) {
+ constexpr float CHAR_WIDTH = 10.0;
+ constexpr bool NO_HYPHEN = true; // Do hyphenation in this test case.
+ // "hyphenation" is hyphnated to "hy-phen-a-tion".
+ const std::vector<uint16_t> textBuf = utf8ToUtf16("Hyphenation is hyphenation.");
+
+ constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
+ constexpr EndHyphenEdit END_HYPHEN = EndHyphenEdit::INSERT_HYPHEN;
+ constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
+
+ // Note that disable clang-format everywhere since aligned expectation is more readable.
+ {
+ constexpr float LINE_WIDTH = 1000 * CHAR_WIDTH;
+ std::vector<LineBreakExpectation> expect = {
+ {"Hyphenation is hyphenation.", 27 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ };
+
+ const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 27 * CHAR_WIDTH;
+ std::vector<LineBreakExpectation> expect = {
+ {"Hyphenation is hyphenation.", 27 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ };
+
+ const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 26 * CHAR_WIDTH;
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ { "Hyphenation is " , 14 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "hyphenation." , 12 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ };
+ // clang-format on
+
+ const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 17 * CHAR_WIDTH;
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ { "Hyphenation is " , 14 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "hyphenation." , 12 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ };
+ // clang-format on
+
+ const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 12 * CHAR_WIDTH;
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ { "Hyphenation " , 11 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "is " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "hyphenation." , 12 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ };
+ // clang-format on
+
+ const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 10 * CHAR_WIDTH;
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ { "Hyphena-", 8 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
+ { "tion is ", 7 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "hyphena-", 8 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
+ { "tion." , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ };
+ // clang-format on
+
+ const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 8 * CHAR_WIDTH;
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ { "Hyphena-", 8 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
+ { "tion is ", 7 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "hyphena-", 8 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
+ { "tion." , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ };
+ // clang-format on
+
+ const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 7 * CHAR_WIDTH;
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ { "Hyphen-", 7 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
+ { "ation " , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "is " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "hyphen-", 7 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
+ { "ation." , 6 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ };
+ // clang-format on
+
+ const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 6 * CHAR_WIDTH;
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ { "Hy-" , 3 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
+ { "phena-", 6 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
+ { "tion " , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "is " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "hy-" , 3 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
+ { "phena-", 6 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
+ { "tion." , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ };
+ // clang-format on
+
+ const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 5 * CHAR_WIDTH;
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ { "Hy-" , 3 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
+ { "phen-" , 5 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
+ { "ation ", 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "is " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "hy-" , 3 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
+ { "phen-" , 5 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
+ { "a-" , 2 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
+ { "tion." , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ };
+ // clang-format on
+
+ const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 4 * CHAR_WIDTH;
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ { "Hy-" , 3 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
+ { "phen" , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "a-" , 2 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
+ { "tion ", 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "is " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "hy-" , 3 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
+ { "phen" , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "a-" , 2 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
+ { "tion" , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "." , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ };
+ // clang-format on
+
+ const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 3 * CHAR_WIDTH;
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ { "Hy-", 3 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
+ { "phe", 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "na-", 3 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
+ { "tio", 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "n " , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "is ", 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "hy-", 3 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
+ { "phe", 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "na-", 3 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
+ { "tio", 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "n." , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ };
+ // clang-format on
+
+ const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 2 * CHAR_WIDTH;
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ { "Hy" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "ph" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "en" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "a-" , 2 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
+ { "ti" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "on ", 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "is ", 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "hy" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "ph" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "en" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "a-" , 2 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
+ { "ti" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "on" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "." , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ };
+ // clang-format on
+
+ const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 1 * CHAR_WIDTH;
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ { "H" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "y" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "p" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "h" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "e" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "n" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "a" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "t" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "i" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "o" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "n ", 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "i" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "s ", 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "h" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "y" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "p" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "h" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "e" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "n" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "a" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "t" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "i" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "o" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "n" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "." , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ };
+ // clang-format on
+
+ const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+}
+
+TEST_F(GreedyLineBreakerTest, testHyphenationStartLineChange) {
+ constexpr float CHAR_WIDTH = 10.0;
+ constexpr bool DO_HYPHEN = true; // Do hyphenation in this test case.
+ // "hyphenation" is hyphnated to "hy-phen-a-tion".
+ const std::vector<uint16_t> textBuf = utf8ToUtf16("czerwono-niebieska");
+
+ constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
+ constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
+ constexpr StartHyphenEdit START_HYPHEN = StartHyphenEdit::INSERT_HYPHEN;
+
+ // Note that disable clang-format everywhere since aligned expectation is more readable.
+ {
+ constexpr float LINE_WIDTH = 1000 * CHAR_WIDTH;
+ std::vector<LineBreakExpectation> expect = {
+ {"czerwono-niebieska", 18 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ };
+
+ const auto actual = doLineBreak(textBuf, DO_HYPHEN, CHAR_WIDTH, "pl", LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 18 * CHAR_WIDTH;
+ std::vector<LineBreakExpectation> expect = {
+ {"czerwono-niebieska", 18 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ };
+
+ const auto actual = doLineBreak(textBuf, DO_HYPHEN, CHAR_WIDTH, "pl", LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 13 * CHAR_WIDTH;
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ {"czerwono-" , 9 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ {"-niebieska", 10 * CHAR_WIDTH, START_HYPHEN, NO_END_HYPHEN},
+ };
+ // clang-format on
+
+ const auto actual = doLineBreak(textBuf, DO_HYPHEN, CHAR_WIDTH, "pl", LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+}
+
+TEST_F(GreedyLineBreakerTest, testZeroWidthLine) {
+ constexpr float CHAR_WIDTH = 10.0;
+ constexpr bool DO_HYPHEN = true; // Do hyphenation in this test case.
+ constexpr float LINE_WIDTH = 0 * CHAR_WIDTH;
+
+ constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
+ constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
+
+ {
+ const auto textBuf = utf8ToUtf16("");
+ std::vector<LineBreakExpectation> expect = {};
+ const auto actual = doLineBreak(textBuf, DO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ const auto textBuf = utf8ToUtf16("A");
+ std::vector<LineBreakExpectation> expect = {
+ {"A", 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ };
+ const auto actual = doLineBreak(textBuf, DO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ const auto textBuf = utf8ToUtf16("AB");
+ std::vector<LineBreakExpectation> expect = {
+ {"A", 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ {"B", 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ };
+ const auto actual = doLineBreak(textBuf, DO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+}
+
+TEST_F(GreedyLineBreakerTest, testZeroWidthCharacter) {
+ constexpr float CHAR_WIDTH = 0.0;
+ constexpr bool DO_HYPHEN = true; // Do hyphenation in this test case.
+
+ constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
+ constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
+ {
+ constexpr float LINE_WIDTH = 1.0;
+ const auto textBuf = utf8ToUtf16("This is an example text.");
+ std::vector<LineBreakExpectation> expect = {
+ {"This is an example text.", 0, NO_START_HYPHEN, NO_END_HYPHEN},
+ };
+ const auto actual = doLineBreak(textBuf, DO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 0.0;
+ const auto textBuf = utf8ToUtf16("This is an example text.");
+ std::vector<LineBreakExpectation> expect = {
+ {"This is an example text.", 0, NO_START_HYPHEN, NO_END_HYPHEN},
+ };
+ const auto actual = doLineBreak(textBuf, DO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+}
+
+TEST_F(GreedyLineBreakerTest, testLocaleSwitchTest) {
+ constexpr float CHAR_WIDTH = 10.0;
+ constexpr bool DO_HYPHEN = true; // Do hyphenation in this test case.
+
+ constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
+ constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
+
+ constexpr float LINE_WIDTH = 24 * CHAR_WIDTH;
+ const auto textBuf = utf8ToUtf16("This is an example text.");
+ {
+ std::vector<LineBreakExpectation> expect = {
+ {"This is an example text.", 24 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ };
+
+ MeasuredTextBuilder builder;
+ builder.addCustomRun<ConstantRun>(Range(0, 18), "en-US", CHAR_WIDTH);
+ builder.addCustomRun<ConstantRun>(Range(18, textBuf.size()), "en-US", CHAR_WIDTH);
+ std::unique_ptr<MeasuredText> measuredText = builder.build(
+ textBuf, false /* compute hyphenation */, false /* compute full layout */);
+ RectangleLineWidth rectangleLineWidth(LINE_WIDTH);
+ TabStops tabStops(nullptr, 0, 0);
+
+ const auto actual =
+ breakLineGreedy(textBuf, *measuredText, rectangleLineWidth, tabStops, DO_HYPHEN);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ std::vector<LineBreakExpectation> expect = {
+ {"This is an example text.", 24 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ };
+
+ MeasuredTextBuilder builder;
+ builder.addCustomRun<ConstantRun>(Range(0, 18), "en-US", CHAR_WIDTH);
+ builder.addCustomRun<ConstantRun>(Range(18, textBuf.size()), "fr-FR", CHAR_WIDTH);
+ std::unique_ptr<MeasuredText> measuredText = builder.build(
+ textBuf, false /* compute hyphenation */, false /* compute full layout */);
+ RectangleLineWidth rectangleLineWidth(LINE_WIDTH);
+ TabStops tabStops(nullptr, 0, 0);
+
+ const auto actual =
+ breakLineGreedy(textBuf, *measuredText, rectangleLineWidth, tabStops, DO_HYPHEN);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+}
+
+TEST_F(GreedyLineBreakerTest, testEmailOrUrl) {
+ constexpr float CHAR_WIDTH = 10.0;
+ constexpr bool DO_HYPHEN = true; // Do hyphenation in this test case.
+
+ constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
+ constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
+ {
+ constexpr float LINE_WIDTH = 24 * CHAR_WIDTH;
+ const auto textBuf = utf8ToUtf16("This is an url: http://a.b");
+ std::vector<LineBreakExpectation> expect = {
+ {"This is an url: ", 15 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ {"http://a.b", 10 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ };
+ const auto actual = doLineBreak(textBuf, DO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 24 * CHAR_WIDTH;
+ const auto textBuf = utf8ToUtf16("This is an email: a@example.com");
+ std::vector<LineBreakExpectation> expect = {
+ {"This is an email: ", 17 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ {"a@example.com", 13 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ };
+ const auto actual = doLineBreak(textBuf, DO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+}
+
+TEST_F(GreedyLineBreakerTest, testLocaleSwitch_InEmailOrUrl) {
+ constexpr float CHAR_WIDTH = 10.0;
+ constexpr bool DO_HYPHEN = true; // Do hyphenation in this test case.
+
+ constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
+ constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
+
+ constexpr float LINE_WIDTH = 24 * CHAR_WIDTH;
+ {
+ const auto textBuf = utf8ToUtf16("This is an url: http://a.b");
+ std::vector<LineBreakExpectation> expect = {
+ {"This is an url: ", 15 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ {"http://a.b", 10 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ };
+
+ MeasuredTextBuilder builder;
+ builder.addCustomRun<ConstantRun>(Range(0, 18), "en-US", CHAR_WIDTH);
+ builder.addCustomRun<ConstantRun>(Range(18, textBuf.size()), "fr-FR", CHAR_WIDTH);
+ std::unique_ptr<MeasuredText> measuredText = builder.build(
+ textBuf, false /* compute hyphenation */, false /* compute full layout */);
+ RectangleLineWidth rectangleLineWidth(LINE_WIDTH);
+ TabStops tabStops(nullptr, 0, 0);
+
+ const auto actual =
+ breakLineGreedy(textBuf, *measuredText, rectangleLineWidth, tabStops, DO_HYPHEN);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ const auto textBuf = utf8ToUtf16("This is an email: a@example.com");
+ std::vector<LineBreakExpectation> expect = {
+ {"This is an email: ", 17 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ {"a@example.com", 13 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ };
+
+ MeasuredTextBuilder builder;
+ builder.addCustomRun<ConstantRun>(Range(0, 18), "en-US", CHAR_WIDTH);
+ builder.addCustomRun<ConstantRun>(Range(18, textBuf.size()), "fr-FR", CHAR_WIDTH);
+ std::unique_ptr<MeasuredText> measuredText = builder.build(
+ textBuf, false /* compute hyphenation */, false /* compute full layout */);
+ RectangleLineWidth rectangleLineWidth(LINE_WIDTH);
+ TabStops tabStops(nullptr, 0, 0);
+
+ const auto actual =
+ breakLineGreedy(textBuf, *measuredText, rectangleLineWidth, tabStops, DO_HYPHEN);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+}
+
+// b/68669534
+TEST_F(GreedyLineBreakerTest, CrashFix_Space_Tab) {
+ constexpr float CHAR_WIDTH = 10.0;
+ constexpr bool DO_HYPHEN = true; // Do hyphenation in this test case.
+
+ constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
+ constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
+ {
+ constexpr float LINE_WIDTH = 5 * CHAR_WIDTH;
+ const auto textBuf = utf8ToUtf16("a \tb");
+ std::vector<LineBreakExpectation> expect = {
+ {"a \tb", 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ };
+
+ MeasuredTextBuilder builder;
+ builder.addCustomRun<ConstantRun>(Range(0, textBuf.size()), "en-US", CHAR_WIDTH);
+ std::unique_ptr<MeasuredText> measuredText = builder.build(
+ textBuf, false /* compute hyphenation */, false /* compute full layout */);
+ RectangleLineWidth rectangleLineWidth(LINE_WIDTH);
+ TabStops tabStops(nullptr, 0, CHAR_WIDTH);
+
+ const auto actual =
+ breakLineGreedy(textBuf, *measuredText, rectangleLineWidth, tabStops, DO_HYPHEN);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+}
+
+} // namespace
+} // namespace minikin
diff --git a/tests/unittest/HbFontCacheTest.cpp b/tests/unittest/HbFontCacheTest.cpp
deleted file mode 100644
index a5581a2..0000000
--- a/tests/unittest/HbFontCacheTest.cpp
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "HbFontCache.h"
-
-#include <android/log.h>
-#include <gtest/gtest.h>
-#include <utils/Mutex.h>
-
-#include <memory>
-
-#include <hb.h>
-
-#include "MinikinInternal.h"
-#include "MinikinFontForTest.h"
-#include <minikin/MinikinFont.h>
-
-namespace minikin {
-
-class HbFontCacheTest : public testing::Test {
-public:
- virtual void TearDown() {
- android::AutoMutex _l(gMinikinLock);
- purgeHbFontCacheLocked();
- }
-};
-
-TEST_F(HbFontCacheTest, getHbFontLockedTest) {
- std::shared_ptr<MinikinFontForTest> fontA(
- new MinikinFontForTest(kTestFontDir "Regular.ttf"));
-
- std::shared_ptr<MinikinFontForTest> fontB(
- new MinikinFontForTest(kTestFontDir "Bold.ttf"));
-
- std::shared_ptr<MinikinFontForTest> fontC(
- new MinikinFontForTest(kTestFontDir "BoldItalic.ttf"));
-
- android::AutoMutex _l(gMinikinLock);
- // Never return NULL.
- EXPECT_NE(nullptr, getHbFontLocked(fontA.get()));
- EXPECT_NE(nullptr, getHbFontLocked(fontB.get()));
- EXPECT_NE(nullptr, getHbFontLocked(fontC.get()));
-
- EXPECT_NE(nullptr, getHbFontLocked(nullptr));
-
- // Must return same object if same font object is passed.
- EXPECT_EQ(getHbFontLocked(fontA.get()), getHbFontLocked(fontA.get()));
- EXPECT_EQ(getHbFontLocked(fontB.get()), getHbFontLocked(fontB.get()));
- EXPECT_EQ(getHbFontLocked(fontC.get()), getHbFontLocked(fontC.get()));
-
- // Different object must be returned if the passed minikinFont has different ID.
- EXPECT_NE(getHbFontLocked(fontA.get()), getHbFontLocked(fontB.get()));
- EXPECT_NE(getHbFontLocked(fontA.get()), getHbFontLocked(fontC.get()));
-}
-
-TEST_F(HbFontCacheTest, purgeCacheTest) {
- std::shared_ptr<MinikinFontForTest> minikinFont(
- new MinikinFontForTest(kTestFontDir "Regular.ttf"));
-
- android::AutoMutex _l(gMinikinLock);
- hb_font_t* font = getHbFontLocked(minikinFont.get());
- ASSERT_NE(nullptr, font);
-
- // Set user data to identify the font object.
- hb_user_data_key_t key;
- void* data = (void*)0xdeadbeef;
- hb_font_set_user_data(font, &key, data, NULL, false);
- ASSERT_EQ(data, hb_font_get_user_data(font, &key));
-
- purgeHbFontCacheLocked();
-
- // By checking user data, confirm that the object after purge is different from previously
- // created one. Do not compare the returned pointer here since memory allocator may assign
- // same region for new object.
- font = getHbFontLocked(minikinFont.get());
- EXPECT_EQ(nullptr, hb_font_get_user_data(font, &key));
-}
-
-} // namespace minikin
diff --git a/tests/unittest/HyphenatorMapTest.cpp b/tests/unittest/HyphenatorMapTest.cpp
new file mode 100644
index 0000000..b793fe0
--- /dev/null
+++ b/tests/unittest/HyphenatorMapTest.cpp
@@ -0,0 +1,307 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "HyphenatorMap.h"
+
+#include <gtest/gtest.h>
+
+#include "LocaleListCache.h"
+#include "MinikinInternal.h"
+
+namespace minikin {
+namespace {
+
+// Constants used for testing. The address does not need a valid one.
+const Hyphenator* FAKE_ADDRESS = reinterpret_cast<const Hyphenator*>(1);
+const Hyphenator* AS_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* BG_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* BN_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* CU_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* CY_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* DA_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* DE_1901_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* DE_1996_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* DE_CH_1901_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* EN_GB_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* EN_US_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* ES_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* ET_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* EU_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* FR_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* GA_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* GU_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* HI_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* HR_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* HU_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* HY_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* KN_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* ML_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* MN_CYRL_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* MR_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* NB_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* NN_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* OR_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* PA_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* PT_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* SL_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* TA_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* TE_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* TK_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* UND_ETHI_HYPHENATOR = FAKE_ADDRESS++;
+
+class TestableHyphenatorMap : public HyphenatorMap {
+public:
+ TestableHyphenatorMap() : HyphenatorMap() {}
+
+ using HyphenatorMap::addAliasInternal;
+ using HyphenatorMap::addInternal;
+ using HyphenatorMap::lookupInternal;
+};
+
+class HyphenatorMapTest : public testing::Test {
+protected:
+ virtual void SetUp() override {
+ // Following settings are copied from Hyphenator.java.
+ mMap.addInternal("as", AS_HYPHENATOR);
+ mMap.addInternal("bg", BG_HYPHENATOR);
+ mMap.addInternal("bn", BN_HYPHENATOR);
+ mMap.addInternal("cu", CU_HYPHENATOR);
+ mMap.addInternal("cy", CY_HYPHENATOR);
+ mMap.addInternal("da", DA_HYPHENATOR);
+ mMap.addInternal("de-1901", DE_1901_HYPHENATOR);
+ mMap.addInternal("de-1996", DE_1996_HYPHENATOR);
+ mMap.addInternal("de-CH-1901", DE_CH_1901_HYPHENATOR);
+ mMap.addInternal("en-GB", EN_GB_HYPHENATOR);
+ mMap.addInternal("en-US", EN_US_HYPHENATOR);
+ mMap.addInternal("es", ES_HYPHENATOR);
+ mMap.addInternal("et", ET_HYPHENATOR);
+ mMap.addInternal("eu", EU_HYPHENATOR);
+ mMap.addInternal("fr", FR_HYPHENATOR);
+ mMap.addInternal("ga", GA_HYPHENATOR);
+ mMap.addInternal("gu", GU_HYPHENATOR);
+ mMap.addInternal("hi", HI_HYPHENATOR);
+ mMap.addInternal("hr", HR_HYPHENATOR);
+ mMap.addInternal("hu", HU_HYPHENATOR);
+ mMap.addInternal("hy", HY_HYPHENATOR);
+ mMap.addInternal("kn", KN_HYPHENATOR);
+ mMap.addInternal("ml", ML_HYPHENATOR);
+ mMap.addInternal("mn-Cyrl", MN_CYRL_HYPHENATOR);
+ mMap.addInternal("mr", MR_HYPHENATOR);
+ mMap.addInternal("nb", NB_HYPHENATOR);
+ mMap.addInternal("nn", NN_HYPHENATOR);
+ mMap.addInternal("or", OR_HYPHENATOR);
+ mMap.addInternal("pa", PA_HYPHENATOR);
+ mMap.addInternal("pt", PT_HYPHENATOR);
+ mMap.addInternal("sl", SL_HYPHENATOR);
+ mMap.addInternal("ta", TA_HYPHENATOR);
+ mMap.addInternal("te", TE_HYPHENATOR);
+ mMap.addInternal("tk", TK_HYPHENATOR);
+ mMap.addInternal("und-Ethi", UND_ETHI_HYPHENATOR);
+
+ mMap.addAliasInternal("en", "en-GB");
+ mMap.addAliasInternal("en-AS", "en-US");
+ mMap.addAliasInternal("en-GU", "en-US");
+ mMap.addAliasInternal("en-MH", "en-US");
+ mMap.addAliasInternal("en-MP", "en-US");
+ mMap.addAliasInternal("en-PR", "en-US");
+ mMap.addAliasInternal("en-UM", "en-US");
+ mMap.addAliasInternal("en-VI", "en-US");
+ mMap.addAliasInternal("de-LI-1901", "de-CH-1901");
+ mMap.addAliasInternal("de", "de-1996");
+ mMap.addAliasInternal("no", "nb");
+ mMap.addAliasInternal("mn", "mn-Cyrl");
+ // am for und-Ethi is removed for testing purposes.
+ mMap.addAliasInternal("byn", "und-Ethi");
+ mMap.addAliasInternal("gez", "und-Ethi");
+ mMap.addAliasInternal("ti", "und-Ethi");
+ mMap.addAliasInternal("wal", "und-Ethi");
+ }
+
+ const Locale& getLocale(const std::string& localeStr) {
+ // In production, we reconstruct the LocaleList from the locale list ID.
+ // So, do it here too.
+ const uint32_t id = LocaleListCache::getId(localeStr);
+ const LocaleList& locales = LocaleListCache::getById(id);
+ MINIKIN_ASSERT(locales.size() == 1, "The input must be a single locale");
+ return locales[0];
+ }
+
+ const Hyphenator* lookup(const std::string& localeStr) {
+ return mMap.lookupInternal(getLocale(localeStr));
+ }
+
+private:
+ TestableHyphenatorMap mMap;
+};
+
+TEST_F(HyphenatorMapTest, exactMatch) {
+ EXPECT_EQ(AS_HYPHENATOR, lookup("as"));
+ EXPECT_EQ(BG_HYPHENATOR, lookup("bg"));
+ EXPECT_EQ(BN_HYPHENATOR, lookup("bn"));
+ EXPECT_EQ(CU_HYPHENATOR, lookup("cu"));
+ EXPECT_EQ(CY_HYPHENATOR, lookup("cy"));
+ EXPECT_EQ(DA_HYPHENATOR, lookup("da"));
+ EXPECT_EQ(DE_1901_HYPHENATOR, lookup("de-1901"));
+ EXPECT_EQ(DE_1996_HYPHENATOR, lookup("de-1996"));
+ EXPECT_EQ(DE_CH_1901_HYPHENATOR, lookup("de-CH-1901"));
+ EXPECT_EQ(EN_GB_HYPHENATOR, lookup("en-GB"));
+ EXPECT_EQ(EN_US_HYPHENATOR, lookup("en-US"));
+ EXPECT_EQ(ES_HYPHENATOR, lookup("es"));
+ EXPECT_EQ(ET_HYPHENATOR, lookup("et"));
+ EXPECT_EQ(EU_HYPHENATOR, lookup("eu"));
+ EXPECT_EQ(FR_HYPHENATOR, lookup("fr"));
+ EXPECT_EQ(GA_HYPHENATOR, lookup("ga"));
+ EXPECT_EQ(GU_HYPHENATOR, lookup("gu"));
+ EXPECT_EQ(HI_HYPHENATOR, lookup("hi"));
+ EXPECT_EQ(HR_HYPHENATOR, lookup("hr"));
+ EXPECT_EQ(HU_HYPHENATOR, lookup("hu"));
+ EXPECT_EQ(HY_HYPHENATOR, lookup("hy"));
+ EXPECT_EQ(KN_HYPHENATOR, lookup("kn"));
+ EXPECT_EQ(ML_HYPHENATOR, lookup("ml"));
+ EXPECT_EQ(MN_CYRL_HYPHENATOR, lookup("mn-Cyrl"));
+ EXPECT_EQ(MR_HYPHENATOR, lookup("mr"));
+ EXPECT_EQ(NB_HYPHENATOR, lookup("nb"));
+ EXPECT_EQ(NN_HYPHENATOR, lookup("nn"));
+ EXPECT_EQ(OR_HYPHENATOR, lookup("or"));
+ EXPECT_EQ(PA_HYPHENATOR, lookup("pa"));
+ EXPECT_EQ(PT_HYPHENATOR, lookup("pt"));
+ EXPECT_EQ(SL_HYPHENATOR, lookup("sl"));
+ EXPECT_EQ(TA_HYPHENATOR, lookup("ta"));
+ EXPECT_EQ(TE_HYPHENATOR, lookup("te"));
+ EXPECT_EQ(TK_HYPHENATOR, lookup("tk"));
+ EXPECT_EQ(UND_ETHI_HYPHENATOR, lookup("und-Ethi"));
+}
+
+TEST_F(HyphenatorMapTest, aliasMatch) {
+ EXPECT_EQ(EN_US_HYPHENATOR, lookup("en-AS"));
+ EXPECT_EQ(EN_US_HYPHENATOR, lookup("en-GU"));
+ EXPECT_EQ(EN_US_HYPHENATOR, lookup("en-MH"));
+ EXPECT_EQ(EN_US_HYPHENATOR, lookup("en-MP"));
+ EXPECT_EQ(EN_US_HYPHENATOR, lookup("en-PR"));
+ EXPECT_EQ(EN_US_HYPHENATOR, lookup("en-UM"));
+ EXPECT_EQ(EN_US_HYPHENATOR, lookup("en-VI"));
+ EXPECT_EQ(DE_1996_HYPHENATOR, lookup("de"));
+ EXPECT_EQ(DE_CH_1901_HYPHENATOR, lookup("de-LI-1901"));
+ EXPECT_EQ(NB_HYPHENATOR, lookup("no"));
+ EXPECT_EQ(MN_CYRL_HYPHENATOR, lookup("mn"));
+ EXPECT_EQ(UND_ETHI_HYPHENATOR, lookup("byn"));
+ EXPECT_EQ(UND_ETHI_HYPHENATOR, lookup("gez"));
+ EXPECT_EQ(UND_ETHI_HYPHENATOR, lookup("ti"));
+ EXPECT_EQ(UND_ETHI_HYPHENATOR, lookup("wal"));
+ // Amharic is tested in fallbackTest_scriptFallback
+}
+
+TEST_F(HyphenatorMapTest, IgnoreScript) {
+ // Script should be ignored until the final script-only matching rule.
+ EXPECT_EQ(EN_US_HYPHENATOR, lookup("en-Latn-US"));
+ EXPECT_EQ(EN_US_HYPHENATOR, lookup("en-Zsye-US"));
+ EXPECT_EQ(EN_US_HYPHENATOR, lookup("en-Zsym-US"));
+ EXPECT_EQ(EN_US_HYPHENATOR, lookup("en-Jpan-US"));
+ EXPECT_EQ(EN_US_HYPHENATOR, lookup("en-Hans-US"));
+ EXPECT_EQ(EN_US_HYPHENATOR, lookup("en-Ethi-US"));
+
+ EXPECT_EQ(EN_GB_HYPHENATOR, lookup("en-Zsye-AU"));
+ EXPECT_EQ(EN_GB_HYPHENATOR, lookup("en-Zsye-GB"));
+}
+
+TEST_F(HyphenatorMapTest, languageFallback) {
+ EXPECT_EQ(EN_GB_HYPHENATOR, lookup("en-AU"));
+ EXPECT_EQ(EN_GB_HYPHENATOR, lookup("en-NZ"));
+
+ // "en" is expanded to en-Latn-US. So this is equivalent to "en-Latn-US" test case.
+ // This expansion also happens in production.
+ EXPECT_EQ(EN_US_HYPHENATOR, lookup("en"));
+}
+
+TEST_F(HyphenatorMapTest, GermanFallback) {
+ // German in general
+ EXPECT_EQ(DE_1996_HYPHENATOR, lookup("de"));
+ EXPECT_EQ(DE_1901_HYPHENATOR, lookup("de-1901"));
+ EXPECT_EQ(DE_1996_HYPHENATOR, lookup("de-1996"));
+
+ // German in Germany
+ EXPECT_EQ(DE_1996_HYPHENATOR, lookup("de-DE"));
+
+ EXPECT_EQ(DE_1901_HYPHENATOR, lookup("de-DE-1901"));
+ EXPECT_EQ(DE_1901_HYPHENATOR, lookup("de-Latn-DE-1901"));
+ EXPECT_EQ(DE_1901_HYPHENATOR, lookup("de-Latn-DE-1901-u-em-emoji"));
+
+ EXPECT_EQ(DE_1996_HYPHENATOR, lookup("de-DE-1996"));
+ EXPECT_EQ(DE_1996_HYPHENATOR, lookup("de-Latn-DE-1996"));
+ EXPECT_EQ(DE_1996_HYPHENATOR, lookup("de-Latn-DE-1996-u-em-emoji"));
+
+ // German in Austria
+ EXPECT_EQ(DE_1996_HYPHENATOR, lookup("de-AT"));
+
+ EXPECT_EQ(DE_1901_HYPHENATOR, lookup("de-AT-1901"));
+ EXPECT_EQ(DE_1901_HYPHENATOR, lookup("de-Latn-AT-1901"));
+ EXPECT_EQ(DE_1901_HYPHENATOR, lookup("de-Latn-AT-1901-u-em-emoji"));
+
+ EXPECT_EQ(DE_1996_HYPHENATOR, lookup("de-AT-1996"));
+ EXPECT_EQ(DE_1996_HYPHENATOR, lookup("de-Latn-AT-1996"));
+ EXPECT_EQ(DE_1996_HYPHENATOR, lookup("de-Latn-AT-1996-u-em-emoji"));
+
+ // German in Switzerland
+ EXPECT_EQ(DE_1996_HYPHENATOR, lookup("de-CH"));
+
+ EXPECT_EQ(DE_CH_1901_HYPHENATOR, lookup("de-CH-1901"));
+ EXPECT_EQ(DE_CH_1901_HYPHENATOR, lookup("de-Latn-CH-1901"));
+ EXPECT_EQ(DE_CH_1901_HYPHENATOR, lookup("de-Latn-CH-1901-u-em-emoji"));
+
+ EXPECT_EQ(DE_1996_HYPHENATOR, lookup("de-CH-1996"));
+ EXPECT_EQ(DE_1996_HYPHENATOR, lookup("de-Latn-CH-1996"));
+ EXPECT_EQ(DE_1996_HYPHENATOR, lookup("de-Latn-CH-1996-u-em-emoji"));
+
+ // German in Liechtenstein
+ EXPECT_EQ(DE_1996_HYPHENATOR, lookup("de-LI"));
+
+ EXPECT_EQ(DE_CH_1901_HYPHENATOR, lookup("de-LI-1901"));
+ EXPECT_EQ(DE_CH_1901_HYPHENATOR, lookup("de-Latn-LI-1901"));
+ EXPECT_EQ(DE_CH_1901_HYPHENATOR, lookup("de-Latn-LI-1901-u-em-emoji"));
+
+ EXPECT_EQ(DE_1996_HYPHENATOR, lookup("de-LI-1996"));
+ EXPECT_EQ(DE_1996_HYPHENATOR, lookup("de-Latn-LI-1996"));
+ EXPECT_EQ(DE_1996_HYPHENATOR, lookup("de-Latn-LI-1996-u-em-emoji"));
+}
+
+TEST_F(HyphenatorMapTest, fallbackTest_LanguageFallback) {
+ EXPECT_EQ(ES_HYPHENATOR, lookup("es-ES"));
+ EXPECT_EQ(ES_HYPHENATOR, lookup("es-AR"));
+ EXPECT_EQ(ES_HYPHENATOR, lookup("es-BO"));
+ EXPECT_EQ(ES_HYPHENATOR, lookup("es-CL"));
+
+ // Spanish in Great Britain
+ EXPECT_EQ(ES_HYPHENATOR, lookup("es-GB"));
+}
+
+TEST_F(HyphenatorMapTest, fallbackTest_ScriptFallback) {
+ EXPECT_EQ(UND_ETHI_HYPHENATOR, lookup("am"));
+}
+
+TEST_F(HyphenatorMapTest, neverReturnNullptrTest) {
+ EXPECT_NE(nullptr, lookup("und"));
+ EXPECT_NE(nullptr, lookup("ja"));
+ EXPECT_NE(nullptr, lookup("ja-JP"));
+}
+
+TEST_F(HyphenatorMapTest, CyrlScriptFallback) {
+ // mn-Cryl should not match with ru-Cyrl and und-Cyrl
+ EXPECT_NE(MN_CYRL_HYPHENATOR, lookup("ru-Cyrl"));
+ EXPECT_NE(MN_CYRL_HYPHENATOR, lookup("und-Cyrl"));
+}
+
+} // namespace
+} // namespace minikin
diff --git a/tests/unittest/HyphenatorTest.cpp b/tests/unittest/HyphenatorTest.cpp
index ecd58a2..14c3ea3 100644
--- a/tests/unittest/HyphenatorTest.cpp
+++ b/tests/unittest/HyphenatorTest.cpp
@@ -14,11 +14,11 @@
* limitations under the License.
*/
+#include "minikin/Hyphenator.h"
+
#include <gtest/gtest.h>
-#include "ICUTestBase.h"
-#include <minikin/Hyphenator.h>
-#include <FileUtils.h>
+#include "FileUtils.h"
#ifndef NELEM
#define NELEM(x) ((sizeof(x) / sizeof((x)[0])))
@@ -29,12 +29,6 @@
const char* usHyph = "/system/usr/hyphen-data/hyph-en-us.hyb";
const char* malayalamHyph = "/system/usr/hyphen-data/hyph-ml.hyb";
-typedef ICUTestBase HyphenatorTest;
-
-const icu::Locale catalanLocale("ca", "ES", nullptr, nullptr);
-const icu::Locale polishLocale("pl", "PL", nullptr, nullptr);
-const icu::Locale& usLocale = icu::Locale::getUS();
-
const uint16_t HYPHEN_MINUS = 0x002D;
const uint16_t SOFT_HYPHEN = 0x00AD;
const uint16_t MIDDLE_DOT = 0x00B7;
@@ -50,12 +44,13 @@
const uint16_t EN_DASH = 0x2013;
// Simple test for US English. This tests "table", which happens to be the in the exceptions list.
-TEST_F(HyphenatorTest, usEnglishAutomaticHyphenation) {
- Hyphenator* hyphenator = Hyphenator::loadBinary(readWholeFile(usHyph).data(), 2, 3);
+TEST(HyphenatorTest, usEnglishAutomaticHyphenation) {
+ std::vector<uint8_t> patternData = readWholeFile(usHyph);
+ Hyphenator* hyphenator = Hyphenator::loadBinary(patternData.data(), 2, 3, "en");
const uint16_t word[] = {'t', 'a', 'b', 'l', 'e'};
std::vector<HyphenationType> result;
- hyphenator->hyphenate(&result, word, NELEM(word), usLocale);
- EXPECT_EQ((size_t) 5, result.size());
+ hyphenator->hyphenate(word, &result);
+ EXPECT_EQ((size_t)5, result.size());
EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);
EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);
EXPECT_EQ(HyphenationType::BREAK_AND_INSERT_HYPHEN, result[2]);
@@ -64,12 +59,12 @@
}
// Catalan l·l should break as l-/l
-TEST_F(HyphenatorTest, catalanMiddleDot) {
- Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2);
+TEST(HyphenatorTest, catalanMiddleDot) {
+ Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2, "ca");
const uint16_t word[] = {'l', 'l', MIDDLE_DOT, 'l', 'l'};
std::vector<HyphenationType> result;
- hyphenator->hyphenate(&result, word, NELEM(word), catalanLocale);
- EXPECT_EQ((size_t) 5, result.size());
+ hyphenator->hyphenate(word, &result);
+ EXPECT_EQ((size_t)5, result.size());
EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);
EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);
EXPECT_EQ(HyphenationType::DONT_BREAK, result[2]);
@@ -78,36 +73,36 @@
}
// Catalan l·l should not break if the word is too short.
-TEST_F(HyphenatorTest, catalanMiddleDotShortWord) {
- Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2);
+TEST(HyphenatorTest, catalanMiddleDotShortWord) {
+ Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2, "ca");
const uint16_t word[] = {'l', MIDDLE_DOT, 'l'};
std::vector<HyphenationType> result;
- hyphenator->hyphenate(&result, word, NELEM(word), catalanLocale);
- EXPECT_EQ((size_t) 3, result.size());
+ hyphenator->hyphenate(word, &result);
+ EXPECT_EQ((size_t)3, result.size());
EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);
EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);
EXPECT_EQ(HyphenationType::DONT_BREAK, result[2]);
}
// If we break on a hyphen in Polish, the hyphen should be repeated on the next line.
-TEST_F(HyphenatorTest, polishHyphen) {
- Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2);
+TEST(HyphenatorTest, polishHyphen) {
+ Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2, "pl");
const uint16_t word[] = {'x', HYPHEN, 'y'};
std::vector<HyphenationType> result;
- hyphenator->hyphenate(&result, word, NELEM(word), polishLocale);
- EXPECT_EQ((size_t) 3, result.size());
+ hyphenator->hyphenate(word, &result);
+ EXPECT_EQ((size_t)3, result.size());
EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);
EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);
EXPECT_EQ(HyphenationType::BREAK_AND_INSERT_HYPHEN_AT_NEXT_LINE, result[2]);
}
// If the language is Polish but the script is not Latin, don't use Polish rules for hyphenation.
-TEST_F(HyphenatorTest, polishHyphenButNonLatinWord) {
- Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2);
+TEST(HyphenatorTest, polishHyphenButNonLatinWord) {
+ Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2, "pl");
const uint16_t word[] = {GREEK_LOWER_ALPHA, HYPHEN, GREEK_LOWER_ALPHA};
std::vector<HyphenationType> result;
- hyphenator->hyphenate(&result, word, NELEM(word), polishLocale);
- EXPECT_EQ((size_t) 3, result.size());
+ hyphenator->hyphenate(word, &result);
+ EXPECT_EQ((size_t)3, result.size());
EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);
EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);
EXPECT_EQ(HyphenationType::BREAK_AND_DONT_INSERT_HYPHEN, result[2]);
@@ -115,60 +110,73 @@
// Polish en dash doesn't repeat on next line (as far as we know), but just provides a break
// opportunity.
-TEST_F(HyphenatorTest, polishEnDash) {
- Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2);
+TEST(HyphenatorTest, polishEnDash) {
+ Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2, "pl");
const uint16_t word[] = {'x', EN_DASH, 'y'};
std::vector<HyphenationType> result;
- hyphenator->hyphenate(&result, word, NELEM(word), polishLocale);
- EXPECT_EQ((size_t) 3, result.size());
+ hyphenator->hyphenate(word, &result);
+ EXPECT_EQ((size_t)3, result.size());
EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);
EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);
EXPECT_EQ(HyphenationType::BREAK_AND_DONT_INSERT_HYPHEN, result[2]);
}
+// If we break on a hyphen in Slovenian, the hyphen should be repeated on the next line. (Same as
+// Polish.)
+TEST(HyphenatorTest, slovenianHyphen) {
+ Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2, "sl");
+ const uint16_t word[] = {'x', HYPHEN, 'y'};
+ std::vector<HyphenationType> result;
+ hyphenator->hyphenate(word, &result);
+ EXPECT_EQ((size_t)3, result.size());
+ EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);
+ EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);
+ EXPECT_EQ(HyphenationType::BREAK_AND_INSERT_HYPHEN_AT_NEXT_LINE, result[2]);
+}
+
// In Latin script text, soft hyphens should insert a visible hyphen if broken at.
-TEST_F(HyphenatorTest, latinSoftHyphen) {
- Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2);
+TEST(HyphenatorTest, latinSoftHyphen) {
+ Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2, "en");
const uint16_t word[] = {'x', SOFT_HYPHEN, 'y'};
std::vector<HyphenationType> result;
- hyphenator->hyphenate(&result, word, NELEM(word), usLocale);
- EXPECT_EQ((size_t) 3, result.size());
+ hyphenator->hyphenate(word, &result);
+ EXPECT_EQ((size_t)3, result.size());
EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);
EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);
EXPECT_EQ(HyphenationType::BREAK_AND_INSERT_HYPHEN, result[2]);
}
// Soft hyphens at the beginning of a word are not useful in linebreaking.
-TEST_F(HyphenatorTest, latinSoftHyphenStartingTheWord) {
- Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2);
+TEST(HyphenatorTest, latinSoftHyphenStartingTheWord) {
+ Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2, "en");
const uint16_t word[] = {SOFT_HYPHEN, 'y'};
std::vector<HyphenationType> result;
- hyphenator->hyphenate(&result, word, NELEM(word), usLocale);
- EXPECT_EQ((size_t) 2, result.size());
+ hyphenator->hyphenate(word, &result);
+ EXPECT_EQ((size_t)2, result.size());
EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);
EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);
}
// In Malayalam script text, soft hyphens should not insert a visible hyphen if broken at.
-TEST_F(HyphenatorTest, malayalamSoftHyphen) {
- Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2);
+TEST(HyphenatorTest, malayalamSoftHyphen) {
+ Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2, "en");
const uint16_t word[] = {MALAYALAM_KA, SOFT_HYPHEN, MALAYALAM_KA};
std::vector<HyphenationType> result;
- hyphenator->hyphenate(&result, word, NELEM(word), usLocale);
- EXPECT_EQ((size_t) 3, result.size());
+ hyphenator->hyphenate(word, &result);
+ EXPECT_EQ((size_t)3, result.size());
EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);
EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);
EXPECT_EQ(HyphenationType::BREAK_AND_DONT_INSERT_HYPHEN, result[2]);
}
// In automatically hyphenated Malayalam script text, we should not insert a visible hyphen.
-TEST_F(HyphenatorTest, malayalamAutomaticHyphenation) {
- Hyphenator* hyphenator = Hyphenator::loadBinary(readWholeFile(malayalamHyph).data(), 2, 2);
- const uint16_t word[] = {
- MALAYALAM_KA, MALAYALAM_KA, MALAYALAM_KA, MALAYALAM_KA, MALAYALAM_KA};
+TEST(HyphenatorTest, malayalamAutomaticHyphenation) {
+ std::vector<uint8_t> patternData = readWholeFile(malayalamHyph);
+ Hyphenator* hyphenator = Hyphenator::loadBinary(patternData.data(), 2, 2, "en");
+ const uint16_t word[] = {MALAYALAM_KA, MALAYALAM_KA, MALAYALAM_KA, MALAYALAM_KA, MALAYALAM_KA};
std::vector<HyphenationType> result;
- hyphenator->hyphenate(&result, word, NELEM(word), usLocale);
- EXPECT_EQ((size_t) 5, result.size());
+ hyphenator->hyphenate(word, &result);
+ EXPECT_EQ((size_t)5, result.size());
EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);
EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);
EXPECT_EQ(HyphenationType::BREAK_AND_DONT_INSERT_HYPHEN, result[2]);
@@ -177,12 +185,12 @@
}
// In Armenian script text, soft hyphens should insert an Armenian hyphen if broken at.
-TEST_F(HyphenatorTest, aremenianSoftHyphen) {
- Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2);
+TEST(HyphenatorTest, aremenianSoftHyphen) {
+ Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2, "en");
const uint16_t word[] = {ARMENIAN_AYB, SOFT_HYPHEN, ARMENIAN_AYB};
std::vector<HyphenationType> result;
- hyphenator->hyphenate(&result, word, NELEM(word), usLocale);
- EXPECT_EQ((size_t) 3, result.size());
+ hyphenator->hyphenate(word, &result);
+ EXPECT_EQ((size_t)3, result.size());
EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);
EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);
EXPECT_EQ(HyphenationType::BREAK_AND_INSERT_ARMENIAN_HYPHEN, result[2]);
@@ -190,12 +198,12 @@
// In Hebrew script text, soft hyphens should insert a normal hyphen if broken at, for now.
// We may need to change this to maqaf later.
-TEST_F(HyphenatorTest, hebrewSoftHyphen) {
- Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2);
+TEST(HyphenatorTest, hebrewSoftHyphen) {
+ Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2, "en");
const uint16_t word[] = {HEBREW_ALEF, SOFT_HYPHEN, HEBREW_ALEF};
std::vector<HyphenationType> result;
- hyphenator->hyphenate(&result, word, NELEM(word), usLocale);
- EXPECT_EQ((size_t) 3, result.size());
+ hyphenator->hyphenate(word, &result);
+ EXPECT_EQ((size_t)3, result.size());
EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);
EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);
EXPECT_EQ(HyphenationType::BREAK_AND_INSERT_HYPHEN, result[2]);
@@ -203,12 +211,12 @@
// Soft hyphen between two Arabic letters that join should keep the joining
// behavior when broken across lines.
-TEST_F(HyphenatorTest, arabicSoftHyphenConnecting) {
- Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2);
+TEST(HyphenatorTest, arabicSoftHyphenConnecting) {
+ Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2, "en");
const uint16_t word[] = {ARABIC_BEH, SOFT_HYPHEN, ARABIC_BEH};
std::vector<HyphenationType> result;
- hyphenator->hyphenate(&result, word, NELEM(word), usLocale);
- EXPECT_EQ((size_t) 3, result.size());
+ hyphenator->hyphenate(word, &result);
+ EXPECT_EQ((size_t)3, result.size());
EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);
EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);
EXPECT_EQ(HyphenationType::BREAK_AND_INSERT_HYPHEN_AND_ZWJ, result[2]);
@@ -216,24 +224,24 @@
// Arabic letters may be joining on one side, but if it's the wrong side, we
// should use the normal hyphen.
-TEST_F(HyphenatorTest, arabicSoftHyphenNonConnecting) {
- Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2);
+TEST(HyphenatorTest, arabicSoftHyphenNonConnecting) {
+ Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2, "en");
const uint16_t word[] = {ARABIC_ALEF, SOFT_HYPHEN, ARABIC_BEH};
std::vector<HyphenationType> result;
- hyphenator->hyphenate(&result, word, NELEM(word), usLocale);
- EXPECT_EQ((size_t) 3, result.size());
+ hyphenator->hyphenate(word, &result);
+ EXPECT_EQ((size_t)3, result.size());
EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);
EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);
EXPECT_EQ(HyphenationType::BREAK_AND_INSERT_HYPHEN, result[2]);
}
// Skip transparent characters until you find a non-transparent one.
-TEST_F(HyphenatorTest, arabicSoftHyphenSkipTransparents) {
- Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2);
+TEST(HyphenatorTest, arabicSoftHyphenSkipTransparents) {
+ Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2, "en");
const uint16_t word[] = {ARABIC_BEH, ARABIC_ZWARAKAY, SOFT_HYPHEN, ARABIC_ZWARAKAY, ARABIC_BEH};
std::vector<HyphenationType> result;
- hyphenator->hyphenate(&result, word, NELEM(word), usLocale);
- EXPECT_EQ((size_t) 5, result.size());
+ hyphenator->hyphenate(word, &result);
+ EXPECT_EQ((size_t)5, result.size());
EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);
EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);
EXPECT_EQ(HyphenationType::DONT_BREAK, result[2]);
@@ -243,12 +251,12 @@
// Skip transparent characters until you find a non-transparent one. If we get to one end without
// finding anything, we are still non-joining.
-TEST_F(HyphenatorTest, arabicSoftHyphenTransparentsAtEnd) {
- Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2);
+TEST(HyphenatorTest, arabicSoftHyphenTransparentsAtEnd) {
+ Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2, "en");
const uint16_t word[] = {ARABIC_BEH, ARABIC_ZWARAKAY, SOFT_HYPHEN, ARABIC_ZWARAKAY};
std::vector<HyphenationType> result;
- hyphenator->hyphenate(&result, word, NELEM(word), usLocale);
- EXPECT_EQ((size_t) 4, result.size());
+ hyphenator->hyphenate(word, &result);
+ EXPECT_EQ((size_t)4, result.size());
EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);
EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);
EXPECT_EQ(HyphenationType::DONT_BREAK, result[2]);
@@ -257,12 +265,12 @@
// Skip transparent characters until you find a non-transparent one. If we get to one end without
// finding anything, we are still non-joining.
-TEST_F(HyphenatorTest, arabicSoftHyphenTransparentsAtStart) {
- Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2);
+TEST(HyphenatorTest, arabicSoftHyphenTransparentsAtStart) {
+ Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2, "en");
const uint16_t word[] = {ARABIC_ZWARAKAY, SOFT_HYPHEN, ARABIC_ZWARAKAY, ARABIC_BEH};
std::vector<HyphenationType> result;
- hyphenator->hyphenate(&result, word, NELEM(word), usLocale);
- EXPECT_EQ((size_t) 4, result.size());
+ hyphenator->hyphenate(word, &result);
+ EXPECT_EQ((size_t)4, result.size());
EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);
EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);
EXPECT_EQ(HyphenationType::BREAK_AND_INSERT_HYPHEN, result[2]);
@@ -270,12 +278,12 @@
}
// In Unified Canadian Aboriginal script (UCAS) text, soft hyphens should insert a UCAS hyphen.
-TEST_F(HyphenatorTest, ucasSoftHyphen) {
- Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2);
+TEST(HyphenatorTest, ucasSoftHyphen) {
+ Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2, "en");
const uint16_t word[] = {UCAS_E, SOFT_HYPHEN, UCAS_E};
std::vector<HyphenationType> result;
- hyphenator->hyphenate(&result, word, NELEM(word), usLocale);
- EXPECT_EQ((size_t) 3, result.size());
+ hyphenator->hyphenate(word, &result);
+ EXPECT_EQ((size_t)3, result.size());
EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);
EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);
EXPECT_EQ(HyphenationType::BREAK_AND_INSERT_UCAS_HYPHEN, result[2]);
@@ -283,36 +291,36 @@
// Presently, soft hyphen looks at the character after it to determine hyphenation type. This is a
// little arbitrary, but let's test it anyway.
-TEST_F(HyphenatorTest, mixedScriptSoftHyphen) {
- Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2);
+TEST(HyphenatorTest, mixedScriptSoftHyphen) {
+ Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2, "en");
const uint16_t word[] = {'a', SOFT_HYPHEN, UCAS_E};
std::vector<HyphenationType> result;
- hyphenator->hyphenate(&result, word, NELEM(word), usLocale);
- EXPECT_EQ((size_t) 3, result.size());
+ hyphenator->hyphenate(word, &result);
+ EXPECT_EQ((size_t)3, result.size());
EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);
EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);
EXPECT_EQ(HyphenationType::BREAK_AND_INSERT_UCAS_HYPHEN, result[2]);
}
// Hard hyphens provide a breaking opportunity with nothing extra inserted.
-TEST_F(HyphenatorTest, hardHyphen) {
- Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2);
+TEST(HyphenatorTest, hardHyphen) {
+ Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2, "en");
const uint16_t word[] = {'x', HYPHEN, 'y'};
std::vector<HyphenationType> result;
- hyphenator->hyphenate(&result, word, NELEM(word), usLocale);
- EXPECT_EQ((size_t) 3, result.size());
+ hyphenator->hyphenate(word, &result);
+ EXPECT_EQ((size_t)3, result.size());
EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);
EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);
EXPECT_EQ(HyphenationType::BREAK_AND_DONT_INSERT_HYPHEN, result[2]);
}
// Hyphen-minuses also provide a breaking opportunity with nothing extra inserted.
-TEST_F(HyphenatorTest, hyphenMinus) {
- Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2);
+TEST(HyphenatorTest, hyphenMinus) {
+ Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2, "en");
const uint16_t word[] = {'x', HYPHEN_MINUS, 'y'};
std::vector<HyphenationType> result;
- hyphenator->hyphenate(&result, word, NELEM(word), usLocale);
- EXPECT_EQ((size_t) 3, result.size());
+ hyphenator->hyphenate(word, &result);
+ EXPECT_EQ((size_t)3, result.size());
EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);
EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);
EXPECT_EQ(HyphenationType::BREAK_AND_DONT_INSERT_HYPHEN, result[2]);
@@ -320,15 +328,14 @@
// If the word starts with a hard hyphen or hyphen-minus, it doesn't make sense to break
// it at that point.
-TEST_F(HyphenatorTest, startingHyphenMinus) {
- Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2);
+TEST(HyphenatorTest, startingHyphenMinus) {
+ Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2, "en");
const uint16_t word[] = {HYPHEN_MINUS, 'y'};
std::vector<HyphenationType> result;
- hyphenator->hyphenate(&result, word, NELEM(word), usLocale);
- EXPECT_EQ((size_t) 2, result.size());
+ hyphenator->hyphenate(word, &result);
+ EXPECT_EQ((size_t)2, result.size());
EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);
EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);
}
} // namespace minikin
-
diff --git a/tests/unittest/ICUTestBase.h b/tests/unittest/ICUEnvironment.h
similarity index 61%
rename from tests/unittest/ICUTestBase.h
rename to tests/unittest/ICUEnvironment.h
index f915cf8..0dfb3f7 100644
--- a/tests/unittest/ICUTestBase.h
+++ b/tests/unittest/ICUEnvironment.h
@@ -14,41 +14,53 @@
* limitations under the License.
*/
-#ifndef MINIKIN_TEST_ICU_TEST_BASE_H
-#define MINIKIN_TEST_ICU_TEST_BASE_H
+#ifndef MINIKIN_TEST_ICU_ENVIRONMENT_H
+#define MINIKIN_TEST_ICU_ENVIRONMENT_H
+// low level file access for mapping ICU data
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+
+#include <cutils/log.h>
#include <gtest/gtest.h>
#include <unicode/uclean.h>
#include <unicode/udata.h>
-// low level file access for mapping ICU data
-#include <fcntl.h>
-#include <sys/stat.h>
-#include <sys/mman.h>
-
namespace minikin {
-class ICUTestBase : public testing::Test {
-protected:
+class ICUEnvironment : public testing::Environment {
+public:
+ ICUEnvironment() : testing::Environment(), mData(nullptr), mSize(0) {}
+
+ void* mData;
+ size_t mSize;
+
virtual void SetUp() override {
const char* fn = "/system/usr/icu/" U_ICUDATA_NAME ".dat";
int fd = open(fn, O_RDONLY);
- ASSERT_NE(-1, fd);
+ LOG_ALWAYS_FATAL_IF(fd == -1);
struct stat sb;
- ASSERT_EQ(0, fstat(fd, &sb));
- void* data = mmap(NULL, sb.st_size, PROT_READ, MAP_SHARED, fd, 0);
+ LOG_ALWAYS_FATAL_IF(fstat(fd, &sb) != 0);
+
+ mSize = sb.st_size;
+ void* mData = mmap(nullptr, mSize, PROT_READ, MAP_SHARED, fd, 0);
+ close(fd);
UErrorCode errorCode = U_ZERO_ERROR;
- udata_setCommonData(data, &errorCode);
- ASSERT_TRUE(U_SUCCESS(errorCode));
+ udata_setCommonData(mData, &errorCode);
+ LOG_ALWAYS_FATAL_IF(U_FAILURE(errorCode));
+
+ errorCode = U_ZERO_ERROR;
u_init(&errorCode);
- ASSERT_TRUE(U_SUCCESS(errorCode));
+ LOG_ALWAYS_FATAL_IF(U_FAILURE(errorCode));
}
virtual void TearDown() override {
u_cleanup();
+ munmap(mData, mSize);
}
};
} // namespace minikin
-#endif // MINIKIN_TEST_ICU_TEST_BASE_H
+#endif // MINIKIN_TEST_ICU_ENVIRONMENT_H
diff --git a/tests/unittest/LayoutCacheTest.cpp b/tests/unittest/LayoutCacheTest.cpp
new file mode 100644
index 0000000..b7ad0ba
--- /dev/null
+++ b/tests/unittest/LayoutCacheTest.cpp
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "minikin/Layout.h"
+
+#include <gtest/gtest.h>
+
+#include "minikin/LayoutCache.h"
+
+#include "FontTestUtils.h"
+#include "LocaleListCache.h"
+#include "UnicodeUtils.h"
+
+namespace minikin {
+
+class TestableLayoutCache : public LayoutCache {
+public:
+ TestableLayoutCache(uint32_t maxEntries) : LayoutCache(maxEntries) {}
+};
+
+class LayoutCapture {
+public:
+ LayoutCapture() {}
+
+ void operator()(const Layout& layout) { mLayout = &layout; }
+
+ const Layout* get() const { return mLayout; }
+
+private:
+ const Layout* mLayout;
+};
+
+TEST(LayoutCacheTest, cacheHitTest) {
+ auto text = utf8ToUtf16("android");
+ Range range(0, text.size());
+ MinikinPaint paint(buildFontCollection("Ascii.ttf"));
+
+ TestableLayoutCache layoutCache(10);
+
+ LayoutCapture layout1;
+ layoutCache.getOrCreate(text, range, paint, false /* LTR */, StartHyphenEdit::NO_EDIT,
+ EndHyphenEdit::NO_EDIT, layout1);
+
+ LayoutCapture layout2;
+ layoutCache.getOrCreate(text, range, paint, false /* LTR */, StartHyphenEdit::NO_EDIT,
+ EndHyphenEdit::NO_EDIT, layout2);
+
+ EXPECT_EQ(layout1.get(), layout2.get());
+}
+
+TEST(LayoutCacheTest, cacheMissTest) {
+ auto text1 = utf8ToUtf16("android");
+ auto text2 = utf8ToUtf16("ANDROID");
+ MinikinPaint paint(buildFontCollection("Ascii.ttf"));
+
+ TestableLayoutCache layoutCache(10);
+
+ LayoutCapture layout1;
+ LayoutCapture layout2;
+
+ {
+ SCOPED_TRACE("Different text");
+ layoutCache.getOrCreate(text1, Range(0, text1.size()), paint, false /* LTR */,
+ StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout1);
+ layoutCache.getOrCreate(text2, Range(0, text2.size()), paint, false /* LTR */,
+ StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout2);
+ EXPECT_NE(layout1.get(), layout2.get());
+ }
+ {
+ SCOPED_TRACE("Different range");
+ layoutCache.getOrCreate(text1, Range(0, text1.size()), paint, false /* LTR */,
+ StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout1);
+ layoutCache.getOrCreate(text1, Range(1, text1.size()), paint, false /* LTR */,
+ StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout2);
+ EXPECT_NE(layout1.get(), layout2.get());
+ }
+ {
+ SCOPED_TRACE("Different text");
+ layoutCache.getOrCreate(text1, Range(0, text1.size()), paint, false /* LTR */,
+ StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout1);
+ layoutCache.getOrCreate(text2, Range(0, text2.size()), paint, false /* LTR */,
+ StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout2);
+ EXPECT_NE(layout1.get(), layout2.get());
+ }
+ {
+ SCOPED_TRACE("Different direction");
+ layoutCache.getOrCreate(text1, Range(0, text1.size()), paint, false /* LTR */,
+ StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout1);
+ layoutCache.getOrCreate(text1, Range(0, text1.size()), paint, true /* RTL */,
+ StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout2);
+ EXPECT_NE(layout1.get(), layout2.get());
+ }
+ {
+ SCOPED_TRACE("Different start hyphenation");
+ layoutCache.getOrCreate(text1, Range(0, text1.size()), paint, false /* LTR */,
+ StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout1);
+ layoutCache.getOrCreate(text1, Range(0, text1.size()), paint, false /* LTR */,
+ StartHyphenEdit::INSERT_HYPHEN, EndHyphenEdit::NO_EDIT, layout2);
+ EXPECT_NE(layout1.get(), layout2.get());
+ }
+ {
+ SCOPED_TRACE("Different end hyphen");
+ layoutCache.getOrCreate(text1, Range(0, text1.size()), paint, false /* LTR */,
+ StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout1);
+ layoutCache.getOrCreate(text1, Range(0, text1.size()), paint, false /* LTR */,
+ StartHyphenEdit::NO_EDIT, EndHyphenEdit::INSERT_HYPHEN, layout2);
+ EXPECT_NE(layout1.get(), layout2.get());
+ }
+ {
+ SCOPED_TRACE("Different collection");
+ MinikinPaint paint1(buildFontCollection("Ascii.ttf"));
+ layoutCache.getOrCreate(text1, Range(0, text1.size()), paint1, false /* LTR */,
+ StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout1);
+ MinikinPaint paint2(buildFontCollection("Emoji.ttf"));
+ layoutCache.getOrCreate(text1, Range(0, text1.size()), paint2, false /* LTR */,
+ StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout2);
+ EXPECT_NE(layout1.get(), layout2.get());
+ }
+ {
+ SCOPED_TRACE("Different size");
+ auto collection = buildFontCollection("Ascii.ttf");
+ MinikinPaint paint1(collection);
+ paint1.size = 10.0f;
+ layoutCache.getOrCreate(text1, Range(0, text1.size()), paint1, false /* LTR */,
+ StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout1);
+ MinikinPaint paint2(collection);
+ paint2.size = 20.0f;
+ layoutCache.getOrCreate(text1, Range(0, text1.size()), paint2, false /* LTR */,
+ StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout2);
+ EXPECT_NE(layout1.get(), layout2.get());
+ }
+ {
+ SCOPED_TRACE("Different scale X");
+ auto collection = buildFontCollection("Ascii.ttf");
+ MinikinPaint paint1(collection);
+ paint1.scaleX = 1.0f;
+ layoutCache.getOrCreate(text1, Range(0, text1.size()), paint1, false /* LTR */,
+ StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout1);
+ MinikinPaint paint2(collection);
+ paint2.scaleX = 2.0f;
+ layoutCache.getOrCreate(text1, Range(0, text1.size()), paint2, false /* LTR */,
+ StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout2);
+ EXPECT_NE(layout1.get(), layout2.get());
+ }
+ {
+ SCOPED_TRACE("Different skew X");
+ auto collection = buildFontCollection("Ascii.ttf");
+ MinikinPaint paint1(collection);
+ paint1.skewX = 1.0f;
+ layoutCache.getOrCreate(text1, Range(0, text1.size()), paint1, false /* LTR */,
+ StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout1);
+ MinikinPaint paint2(collection);
+ paint2.skewX = 2.0f;
+ layoutCache.getOrCreate(text1, Range(0, text1.size()), paint2, false /* LTR */,
+ StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout2);
+ EXPECT_NE(layout1.get(), layout2.get());
+ }
+ {
+ SCOPED_TRACE("Different letter spacing");
+ auto collection = buildFontCollection("Ascii.ttf");
+ MinikinPaint paint1(collection);
+ paint1.letterSpacing = 0.0f;
+ layoutCache.getOrCreate(text1, Range(0, text1.size()), paint1, false /* LTR */,
+ StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout1);
+ MinikinPaint paint2(collection);
+ paint2.letterSpacing = 1.0f;
+ layoutCache.getOrCreate(text1, Range(0, text1.size()), paint2, false /* LTR */,
+ StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout2);
+ EXPECT_NE(layout1.get(), layout2.get());
+ }
+ {
+ SCOPED_TRACE("Different word spacing");
+ auto collection = buildFontCollection("Ascii.ttf");
+ MinikinPaint paint1(collection);
+ paint1.wordSpacing = 0.0f;
+ layoutCache.getOrCreate(text1, Range(0, text1.size()), paint1, false /* LTR */,
+ StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout1);
+ MinikinPaint paint2(collection);
+ paint2.wordSpacing = 1.0f;
+ layoutCache.getOrCreate(text1, Range(0, text1.size()), paint2, false /* LTR */,
+ StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout2);
+ EXPECT_NE(layout1.get(), layout2.get());
+ }
+ {
+ SCOPED_TRACE("Different paint flags");
+ auto collection = buildFontCollection("Ascii.ttf");
+ MinikinPaint paint1(collection);
+ paint1.paintFlags = 0;
+ layoutCache.getOrCreate(text1, Range(0, text1.size()), paint1, false /* LTR */,
+ StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout1);
+ MinikinPaint paint2(collection);
+ paint2.paintFlags = LinearTextFlag;
+ layoutCache.getOrCreate(text1, Range(0, text1.size()), paint2, false /* LTR */,
+ StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout2);
+ EXPECT_NE(layout1.get(), layout2.get());
+ }
+ {
+ SCOPED_TRACE("Different locale list ID");
+ auto collection = buildFontCollection("Ascii.ttf");
+ MinikinPaint paint1(collection);
+ paint1.localeListId = LocaleListCache::getId("en-US");
+ layoutCache.getOrCreate(text1, Range(0, text1.size()), paint1, false /* LTR */,
+ StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout1);
+ MinikinPaint paint2(collection);
+ paint2.localeListId = LocaleListCache::getId("ja-JP");
+ layoutCache.getOrCreate(text1, Range(0, text1.size()), paint2, false /* LTR */,
+ StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout2);
+ EXPECT_NE(layout1.get(), layout2.get());
+ }
+ {
+ SCOPED_TRACE("Different family variant");
+ auto collection = buildFontCollection("Ascii.ttf");
+ MinikinPaint paint1(collection);
+ paint1.familyVariant = FontFamily::Variant::DEFAULT;
+ layoutCache.getOrCreate(text1, Range(0, text1.size()), paint1, false /* LTR */,
+ StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout1);
+ MinikinPaint paint2(collection);
+ paint2.familyVariant = FontFamily::Variant::COMPACT;
+ layoutCache.getOrCreate(text1, Range(0, text1.size()), paint2, false /* LTR */,
+ StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout2);
+ EXPECT_NE(layout1.get(), layout2.get());
+ }
+ {
+ SCOPED_TRACE("Different font feature settings");
+ auto collection = buildFontCollection("Ascii.ttf");
+ MinikinPaint paint1(collection);
+ paint1.fontFeatureSettings = "";
+ layoutCache.getOrCreate(text1, Range(0, text1.size()), paint1, false /* LTR */,
+ StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout1);
+ MinikinPaint paint2(collection);
+ paint2.fontFeatureSettings = "'liga' on";
+ layoutCache.getOrCreate(text1, Range(0, text1.size()), paint2, false /* LTR */,
+ StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout2);
+ EXPECT_NE(layout1.get(), layout2.get());
+ }
+}
+
+TEST(LayoutCacheTest, cacheOverflowTest) {
+ auto text = utf8ToUtf16("android");
+ Range range(0, text.size());
+ MinikinPaint paint(buildFontCollection("Ascii.ttf"));
+
+ TestableLayoutCache layoutCache(5);
+
+ LayoutCapture layout1;
+ layoutCache.getOrCreate(text, range, paint, false /* LTR */, StartHyphenEdit::NO_EDIT,
+ EndHyphenEdit::NO_EDIT, layout1);
+
+ for (char c = 'a'; c <= 'z'; c++) {
+ auto text1 = utf8ToUtf16(std::string(c, 10));
+ LayoutCapture layout2;
+ layoutCache.getOrCreate(text1, Range(0, text1.size()), paint, false /* LTR */,
+ StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout2);
+ }
+
+ LayoutCapture layout3;
+ layoutCache.getOrCreate(text, range, paint, false /* LTR */, StartHyphenEdit::NO_EDIT,
+ EndHyphenEdit::NO_EDIT, layout3);
+ EXPECT_NE(layout1.get(), layout3.get());
+}
+
+} // namespace minikin
diff --git a/tests/unittest/LayoutTest.cpp b/tests/unittest/LayoutTest.cpp
index f89ac9e..21109cb 100644
--- a/tests/unittest/LayoutTest.cpp
+++ b/tests/unittest/LayoutTest.cpp
@@ -14,16 +14,15 @@
* limitations under the License.
*/
+#include "minikin/Layout.h"
+
#include <gtest/gtest.h>
-#include "ICUTestBase.h"
#include "minikin/FontCollection.h"
-#include "minikin/Layout.h"
-#include "../util/FontTestUtils.h"
-#include "../util/UnicodeUtils.h"
+#include "minikin/LayoutPieces.h"
-const char* SYSTEM_FONT_PATH = "/system/fonts/";
-const char* SYSTEM_FONT_XML = "/system/etc/fonts.xml";
+#include "FontTestUtils.h"
+#include "UnicodeUtils.h"
namespace minikin {
@@ -45,26 +44,41 @@
}
}
-class LayoutTest : public ICUTestBase {
+static Layout doLayout(const std::string& text, const MinikinPaint& paint) {
+ Layout layout;
+ auto utf16 = utf8ToUtf16(text);
+ Range range(0, utf16.size());
+ layout.doLayout(utf16, range, Bidi::FORCE_LTR, paint, StartHyphenEdit::NO_EDIT,
+ EndHyphenEdit::NO_EDIT);
+ return layout;
+}
+
+static Layout doLayoutWithPrecomputedPieces(const std::string& text, const MinikinPaint& paint,
+ const LayoutPieces& pieces) {
+ Layout layout;
+ auto utf16 = utf8ToUtf16(text);
+ Range range(0, utf16.size());
+ layout.doLayoutWithPrecomputedPieces(utf16, range, Bidi::FORCE_LTR, paint,
+ StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, pieces);
+ return layout;
+}
+
+class LayoutTest : public testing::Test {
protected:
- LayoutTest() : mCollection(nullptr) {
- }
+ LayoutTest() : mCollection(nullptr) {}
virtual ~LayoutTest() {}
- virtual void SetUp() override {
- mCollection = std::shared_ptr<FontCollection>(
- getFontCollection(SYSTEM_FONT_PATH, SYSTEM_FONT_XML));
- }
+ virtual void SetUp() override { mCollection = buildFontCollection("Ascii.ttf"); }
- virtual void TearDown() override {
- }
+ virtual void TearDown() override {}
std::shared_ptr<FontCollection> mCollection;
};
TEST_F(LayoutTest, doLayoutTest) {
- MinikinPaint paint;
+ MinikinPaint paint(mCollection);
+ paint.size = 10.0f; // make 1em = 10px
MinikinRect rect;
const size_t kMaxAdvanceLength = 32;
float advances[kMaxAdvanceLength];
@@ -77,14 +91,15 @@
{
SCOPED_TRACE("one word");
text = utf8ToUtf16("oneword");
- layout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_LTR, FontStyle(), paint,
- mCollection);
+ Range range(0, text.size());
+ layout.doLayout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
+ EndHyphenEdit::NO_EDIT);
EXPECT_EQ(70.0f, layout.getAdvance());
layout.getBounds(&rect);
EXPECT_EQ(0.0f, rect.mLeft);
- EXPECT_EQ(0.0f, rect.mTop);
+ EXPECT_EQ(10.0f, rect.mTop);
EXPECT_EQ(70.0f, rect.mRight);
- EXPECT_EQ(10.0f, rect.mBottom);
+ EXPECT_EQ(0.0f, rect.mBottom);
resetAdvances(advances, kMaxAdvanceLength);
layout.getAdvances(advances);
expectedValues.resize(text.size());
@@ -96,14 +111,15 @@
{
SCOPED_TRACE("two words");
text = utf8ToUtf16("two words");
- layout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_LTR, FontStyle(), paint,
- mCollection);
+ Range range(0, text.size());
+ layout.doLayout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
+ EndHyphenEdit::NO_EDIT);
EXPECT_EQ(90.0f, layout.getAdvance());
layout.getBounds(&rect);
EXPECT_EQ(0.0f, rect.mLeft);
- EXPECT_EQ(0.0f, rect.mTop);
+ EXPECT_EQ(10.0f, rect.mTop);
EXPECT_EQ(90.0f, rect.mRight);
- EXPECT_EQ(10.0f, rect.mBottom);
+ EXPECT_EQ(0.0f, rect.mBottom);
resetAdvances(advances, kMaxAdvanceLength);
layout.getAdvances(advances);
expectedValues.resize(text.size());
@@ -115,14 +131,15 @@
{
SCOPED_TRACE("three words");
text = utf8ToUtf16("three words test");
- layout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_LTR, FontStyle(), paint,
- mCollection);
+ Range range(0, text.size());
+ layout.doLayout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
+ EndHyphenEdit::NO_EDIT);
EXPECT_EQ(160.0f, layout.getAdvance());
layout.getBounds(&rect);
EXPECT_EQ(0.0f, rect.mLeft);
- EXPECT_EQ(0.0f, rect.mTop);
+ EXPECT_EQ(10.0f, rect.mTop);
EXPECT_EQ(160.0f, rect.mRight);
- EXPECT_EQ(10.0f, rect.mBottom);
+ EXPECT_EQ(0.0f, rect.mBottom);
resetAdvances(advances, kMaxAdvanceLength);
layout.getAdvances(advances);
expectedValues.resize(text.size());
@@ -134,14 +151,15 @@
{
SCOPED_TRACE("two spaces");
text = utf8ToUtf16("two spaces");
- layout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_LTR, FontStyle(), paint,
- mCollection);
+ Range range(0, text.size());
+ layout.doLayout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
+ EndHyphenEdit::NO_EDIT);
EXPECT_EQ(110.0f, layout.getAdvance());
layout.getBounds(&rect);
EXPECT_EQ(0.0f, rect.mLeft);
- EXPECT_EQ(0.0f, rect.mTop);
+ EXPECT_EQ(10.0f, rect.mTop);
EXPECT_EQ(110.0f, rect.mRight);
- EXPECT_EQ(10.0f, rect.mBottom);
+ EXPECT_EQ(0.0f, rect.mBottom);
resetAdvances(advances, kMaxAdvanceLength);
layout.getAdvances(advances);
expectedValues.resize(text.size());
@@ -153,7 +171,8 @@
}
TEST_F(LayoutTest, doLayoutTest_wordSpacing) {
- MinikinPaint paint;
+ MinikinPaint paint(mCollection);
+ paint.size = 10.0f; // make 1em = 10px
MinikinRect rect;
const size_t kMaxAdvanceLength = 32;
float advances[kMaxAdvanceLength];
@@ -168,14 +187,15 @@
{
SCOPED_TRACE("one word");
text = utf8ToUtf16("oneword");
- layout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_LTR, FontStyle(), paint,
- mCollection);
+ Range range(0, text.size());
+ layout.doLayout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
+ EndHyphenEdit::NO_EDIT);
EXPECT_EQ(70.0f, layout.getAdvance());
layout.getBounds(&rect);
EXPECT_EQ(0.0f, rect.mLeft);
- EXPECT_EQ(0.0f, rect.mTop);
+ EXPECT_EQ(10.0f, rect.mTop);
EXPECT_EQ(70.0f, rect.mRight);
- EXPECT_EQ(10.0f, rect.mBottom);
+ EXPECT_EQ(0.0f, rect.mBottom);
resetAdvances(advances, kMaxAdvanceLength);
layout.getAdvances(advances);
expectedValues.resize(text.size());
@@ -187,14 +207,15 @@
{
SCOPED_TRACE("two words");
text = utf8ToUtf16("two words");
- layout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_LTR, FontStyle(), paint,
- mCollection);
+ Range range(0, text.size());
+ layout.doLayout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
+ EndHyphenEdit::NO_EDIT);
EXPECT_EQ(95.0f, layout.getAdvance());
layout.getBounds(&rect);
EXPECT_EQ(0.0f, rect.mLeft);
- EXPECT_EQ(0.0f, rect.mTop);
+ EXPECT_EQ(10.0f, rect.mTop);
EXPECT_EQ(95.0f, rect.mRight);
- EXPECT_EQ(10.0f, rect.mBottom);
+ EXPECT_EQ(0.0f, rect.mBottom);
resetAdvances(advances, kMaxAdvanceLength);
layout.getAdvances(advances);
EXPECT_EQ(UNTOUCHED_MARKER, advances[text.size()]);
@@ -210,14 +231,15 @@
{
SCOPED_TRACE("three words test");
text = utf8ToUtf16("three words test");
- layout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_LTR, FontStyle(), paint,
- mCollection);
+ Range range(0, text.size());
+ layout.doLayout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
+ EndHyphenEdit::NO_EDIT);
EXPECT_EQ(170.0f, layout.getAdvance());
layout.getBounds(&rect);
EXPECT_EQ(0.0f, rect.mLeft);
- EXPECT_EQ(0.0f, rect.mTop);
+ EXPECT_EQ(10.0f, rect.mTop);
EXPECT_EQ(170.0f, rect.mRight);
- EXPECT_EQ(10.0f, rect.mBottom);
+ EXPECT_EQ(0.0f, rect.mBottom);
resetAdvances(advances, kMaxAdvanceLength);
layout.getAdvances(advances);
expectedValues.resize(text.size());
@@ -231,14 +253,15 @@
{
SCOPED_TRACE("two spaces");
text = utf8ToUtf16("two spaces");
- layout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_LTR, FontStyle(), paint,
- mCollection);
+ Range range(0, text.size());
+ layout.doLayout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
+ EndHyphenEdit::NO_EDIT);
EXPECT_EQ(120.0f, layout.getAdvance());
layout.getBounds(&rect);
EXPECT_EQ(0.0f, rect.mLeft);
- EXPECT_EQ(0.0f, rect.mTop);
+ EXPECT_EQ(10.0f, rect.mTop);
EXPECT_EQ(120.0f, rect.mRight);
- EXPECT_EQ(10.0f, rect.mBottom);
+ EXPECT_EQ(0.0f, rect.mBottom);
resetAdvances(advances, kMaxAdvanceLength);
layout.getAdvances(advances);
expectedValues.resize(text.size());
@@ -252,7 +275,8 @@
}
TEST_F(LayoutTest, doLayoutTest_negativeWordSpacing) {
- MinikinPaint paint;
+ MinikinPaint paint(mCollection);
+ paint.size = 10.0f; // make 1em = 10px
MinikinRect rect;
const size_t kMaxAdvanceLength = 32;
float advances[kMaxAdvanceLength];
@@ -267,14 +291,15 @@
{
SCOPED_TRACE("one word");
text = utf8ToUtf16("oneword");
- layout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_LTR, FontStyle(), paint,
- mCollection);
+ Range range(0, text.size());
+ layout.doLayout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
+ EndHyphenEdit::NO_EDIT);
EXPECT_EQ(70.0f, layout.getAdvance());
layout.getBounds(&rect);
EXPECT_EQ(0.0f, rect.mLeft);
- EXPECT_EQ(0.0f, rect.mTop);
+ EXPECT_EQ(10.0f, rect.mTop);
EXPECT_EQ(70.0f, rect.mRight);
- EXPECT_EQ(10.0f, rect.mBottom);
+ EXPECT_EQ(0.0f, rect.mBottom);
resetAdvances(advances, kMaxAdvanceLength);
layout.getAdvances(advances);
expectedValues.resize(text.size());
@@ -286,14 +311,15 @@
{
SCOPED_TRACE("two words");
text = utf8ToUtf16("two words");
- layout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_LTR, FontStyle(), paint,
- mCollection);
+ Range range(0, text.size());
+ layout.doLayout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
+ EndHyphenEdit::NO_EDIT);
EXPECT_EQ(85.0f, layout.getAdvance());
layout.getBounds(&rect);
EXPECT_EQ(0.0f, rect.mLeft);
- EXPECT_EQ(0.0f, rect.mTop);
+ EXPECT_EQ(10.0f, rect.mTop);
EXPECT_EQ(85.0f, rect.mRight);
- EXPECT_EQ(10.0f, rect.mBottom);
+ EXPECT_EQ(0.0f, rect.mBottom);
resetAdvances(advances, kMaxAdvanceLength);
layout.getAdvances(advances);
expectedValues.resize(text.size());
@@ -306,14 +332,15 @@
{
SCOPED_TRACE("three words");
text = utf8ToUtf16("three word test");
- layout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_LTR, FontStyle(), paint,
- mCollection);
+ Range range(0, text.size());
+ layout.doLayout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
+ EndHyphenEdit::NO_EDIT);
EXPECT_EQ(140.0f, layout.getAdvance());
layout.getBounds(&rect);
EXPECT_EQ(0.0f, rect.mLeft);
- EXPECT_EQ(0.0f, rect.mTop);
+ EXPECT_EQ(10.0f, rect.mTop);
EXPECT_EQ(140.0f, rect.mRight);
- EXPECT_EQ(10.0f, rect.mBottom);
+ EXPECT_EQ(0.0f, rect.mBottom);
resetAdvances(advances, kMaxAdvanceLength);
layout.getAdvances(advances);
expectedValues.resize(text.size());
@@ -327,14 +354,15 @@
{
SCOPED_TRACE("two spaces");
text = utf8ToUtf16("two spaces");
- layout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_LTR, FontStyle(), paint,
- mCollection);
+ Range range(0, text.size());
+ layout.doLayout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
+ EndHyphenEdit::NO_EDIT);
EXPECT_EQ(100.0f, layout.getAdvance());
layout.getBounds(&rect);
EXPECT_EQ(0.0f, rect.mLeft);
- EXPECT_EQ(0.0f, rect.mTop);
+ EXPECT_EQ(10.0f, rect.mTop);
EXPECT_EQ(100.0f, rect.mRight);
- EXPECT_EQ(10.0f, rect.mBottom);
+ EXPECT_EQ(0.0f, rect.mBottom);
resetAdvances(advances, kMaxAdvanceLength);
layout.getAdvances(advances);
expectedValues.resize(text.size());
@@ -349,17 +377,18 @@
// Test that a forced-RTL layout correctly mirros a forced-LTR layout.
TEST_F(LayoutTest, doLayoutTest_rtlTest) {
- MinikinPaint paint;
+ MinikinPaint paint(mCollection);
std::vector<uint16_t> text = parseUnicodeString("'a' 'b' U+3042 U+3043 'c' 'd'");
+ Range range(0, text.size());
Layout ltrLayout;
- ltrLayout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_Force_LTR, FontStyle(),
- paint, mCollection);
+ ltrLayout.doLayout(text, range, Bidi::FORCE_LTR, paint, StartHyphenEdit::NO_EDIT,
+ EndHyphenEdit::NO_EDIT);
Layout rtlLayout;
- rtlLayout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_Force_RTL, FontStyle(),
- paint, mCollection);
+ rtlLayout.doLayout(text, range, Bidi::FORCE_RTL, paint, StartHyphenEdit::NO_EDIT,
+ EndHyphenEdit::NO_EDIT);
ASSERT_EQ(ltrLayout.nGlyphs(), rtlLayout.nGlyphs());
ASSERT_EQ(6u, ltrLayout.nGlyphs());
@@ -373,21 +402,22 @@
// Test that single-run RTL layouts of LTR-only text is laid out identical to an LTR layout.
TEST_F(LayoutTest, singleRunBidiTest) {
- MinikinPaint paint;
+ MinikinPaint paint(mCollection);
std::vector<uint16_t> text = parseUnicodeString("'1' '2' '3'");
+ Range range(0, text.size());
Layout ltrLayout;
- ltrLayout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_LTR, FontStyle(),
- paint, mCollection);
+ ltrLayout.doLayout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
+ EndHyphenEdit::NO_EDIT);
Layout rtlLayout;
- rtlLayout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_RTL, FontStyle(),
- paint, mCollection);
+ rtlLayout.doLayout(text, range, Bidi::RTL, paint, StartHyphenEdit::NO_EDIT,
+ EndHyphenEdit::NO_EDIT);
Layout defaultRtlLayout;
- defaultRtlLayout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_Default_RTL,
- FontStyle(), paint, mCollection);
+ defaultRtlLayout.doLayout(text, range, Bidi::DEFAULT_RTL, paint, StartHyphenEdit::NO_EDIT,
+ EndHyphenEdit::NO_EDIT);
const size_t nGlyphs = ltrLayout.nGlyphs();
ASSERT_EQ(3u, nGlyphs);
@@ -404,6 +434,8 @@
}
TEST_F(LayoutTest, hyphenationTest) {
+ MinikinPaint paint(mCollection);
+ paint.size = 10.0f; // make 1em = 10px
Layout layout;
std::vector<uint16_t> text;
@@ -411,50 +443,174 @@
{
SCOPED_TRACE("one word with no hyphen edit");
text = utf8ToUtf16("oneword");
- MinikinPaint paint;
- paint.hyphenEdit = HyphenEdit::NO_EDIT;
- layout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_LTR, FontStyle(), paint,
- mCollection);
+ Range range(0, text.size());
+ layout.doLayout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
+ EndHyphenEdit::NO_EDIT);
EXPECT_EQ(70.0f, layout.getAdvance());
}
{
SCOPED_TRACE("one word with hyphen insertion at the end");
text = utf8ToUtf16("oneword");
- MinikinPaint paint;
- paint.hyphenEdit = HyphenEdit::INSERT_HYPHEN_AT_END;
- layout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_LTR, FontStyle(), paint,
- mCollection);
+ Range range(0, text.size());
+ layout.doLayout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
+ EndHyphenEdit::INSERT_HYPHEN);
EXPECT_EQ(80.0f, layout.getAdvance());
}
{
SCOPED_TRACE("one word with hyphen replacement at the end");
text = utf8ToUtf16("oneword");
- MinikinPaint paint;
- paint.hyphenEdit = HyphenEdit::REPLACE_WITH_HYPHEN_AT_END;
- layout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_LTR, FontStyle(), paint,
- mCollection);
+ Range range(0, text.size());
+ layout.doLayout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
+ EndHyphenEdit::REPLACE_WITH_HYPHEN);
EXPECT_EQ(70.0f, layout.getAdvance());
}
{
SCOPED_TRACE("one word with hyphen insertion at the start");
text = utf8ToUtf16("oneword");
- MinikinPaint paint;
- paint.hyphenEdit = HyphenEdit::INSERT_HYPHEN_AT_START;
- layout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_LTR, FontStyle(), paint,
- mCollection);
+ Range range(0, text.size());
+ layout.doLayout(text, range, Bidi::LTR, paint, StartHyphenEdit::INSERT_HYPHEN,
+ EndHyphenEdit::NO_EDIT);
EXPECT_EQ(80.0f, layout.getAdvance());
}
{
SCOPED_TRACE("one word with hyphen insertion at the both ends");
text = utf8ToUtf16("oneword");
- MinikinPaint paint;
- paint.hyphenEdit = HyphenEdit::INSERT_HYPHEN_AT_START | HyphenEdit::INSERT_HYPHEN_AT_END;
- layout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_LTR, FontStyle(), paint,
- mCollection);
+ Range range(0, text.size());
+ layout.doLayout(text, range, Bidi::LTR, paint, StartHyphenEdit::INSERT_HYPHEN,
+ EndHyphenEdit::INSERT_HYPHEN);
EXPECT_EQ(90.0f, layout.getAdvance());
}
}
+TEST_F(LayoutTest, verticalExtentTest) {
+ MinikinPaint paint(mCollection);
+
+ std::vector<uint16_t> text = utf8ToUtf16("ab");
+ Range range(0, text.size());
+
+ Layout layout;
+ layout.doLayout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
+ EndHyphenEdit::NO_EDIT);
+ MinikinExtent extents[text.size()];
+ layout.getExtents(extents);
+ for (size_t i = 0; i < text.size(); i++) {
+ EXPECT_EQ(-10.0f, extents[i].ascent);
+ EXPECT_EQ(20.0f, extents[i].descent);
+ EXPECT_EQ(0.0f, extents[i].line_gap);
+ }
+}
+
+TEST_F(LayoutTest, measuredTextTest) {
+ // The test font has following coverage and width.
+ // U+0020: 10em
+ // U+002E (.): 10em
+ // U+0043 (C): 100em
+ // U+0049 (I): 1em
+ // U+004C (L): 50em
+ // U+0056 (V): 5em
+ // U+0058 (X): 10em
+ // U+005F (_): 0em
+ // U+FFFD (invalid surrogate will be replaced to this): 7em
+ // U+10331 (\uD800\uDF31): 10em
+ auto fc = buildFontCollection("LayoutTestFont.ttf");
+ {
+ MinikinPaint paint(fc);
+ std::vector<uint16_t> text = utf8ToUtf16("I");
+ std::vector<float> advances(text.size());
+ Range range(0, text.size());
+ EXPECT_EQ(1.0f,
+ Layout::measureText(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
+ EndHyphenEdit::NO_EDIT, advances.data(), nullptr, nullptr));
+ ASSERT_EQ(1u, advances.size());
+ EXPECT_EQ(1.0f, advances[0]);
+ }
+ {
+ MinikinPaint paint(fc);
+ std::vector<uint16_t> text = utf8ToUtf16("IV");
+ std::vector<float> advances(text.size());
+ Range range(0, text.size());
+ EXPECT_EQ(6.0f,
+ Layout::measureText(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
+ EndHyphenEdit::NO_EDIT, advances.data(), nullptr, nullptr));
+ ASSERT_EQ(2u, advances.size());
+ EXPECT_EQ(1.0f, advances[0]);
+ EXPECT_EQ(5.0f, advances[1]);
+ }
+ {
+ MinikinPaint paint(fc);
+ std::vector<uint16_t> text = utf8ToUtf16("IVX");
+ std::vector<float> advances(text.size());
+ Range range(0, text.size());
+ EXPECT_EQ(16.0f,
+ Layout::measureText(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
+ EndHyphenEdit::NO_EDIT, advances.data(), nullptr, nullptr));
+ ASSERT_EQ(3u, advances.size());
+ EXPECT_EQ(1.0f, advances[0]);
+ EXPECT_EQ(5.0f, advances[1]);
+ EXPECT_EQ(10.0f, advances[2]);
+ }
+}
+
+TEST_F(LayoutTest, doLayoutWithPrecomputedPiecesTest) {
+ float MARKER1 = 1e+16;
+ float MARKER2 = 1e+17;
+ auto fc = buildFontCollection("LayoutTestFont.ttf");
+ MinikinPaint paint(fc);
+ {
+ LayoutPieces pieces;
+
+ Layout inLayout = doLayout("I", MinikinPaint(fc));
+ inLayout.mAdvances[0] = MARKER1; // Modify the advance to make sure this layout is used.
+ pieces.insert(utf8ToUtf16("I"), Range(0, 1), paint, false /* dir */,
+ StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, inLayout);
+
+ Layout outLayout = doLayoutWithPrecomputedPieces("I", MinikinPaint(fc), pieces);
+ EXPECT_EQ(MARKER1, outLayout.mAdvances[0]);
+ }
+ {
+ LayoutPieces pieces;
+
+ Layout inLayout = doLayout("I", MinikinPaint(fc));
+ inLayout.mAdvances[0] = MARKER1;
+ pieces.insert(utf8ToUtf16("I"), Range(0, 1), paint, false /* dir */,
+ StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, inLayout);
+
+ Layout outLayout = doLayoutWithPrecomputedPieces("II", MinikinPaint(fc), pieces);
+ // The layout pieces are used in word units. Should not be used "I" for "II".
+ EXPECT_NE(MARKER1, outLayout.mAdvances[0]);
+ EXPECT_NE(MARKER1, outLayout.mAdvances[1]);
+ }
+ {
+ LayoutPieces pieces;
+
+ Layout inLayout = doLayout("I", MinikinPaint(fc));
+ inLayout.mAdvances[0] = MARKER1;
+ pieces.insert(utf8ToUtf16("I"), Range(0, 1), paint, false /* dir */,
+ StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, inLayout);
+
+ Layout outLayout = doLayoutWithPrecomputedPieces("I I", MinikinPaint(fc), pieces);
+ EXPECT_EQ(MARKER1, outLayout.mAdvances[0]);
+ EXPECT_EQ(MARKER1, outLayout.mAdvances[2]);
+ }
+ {
+ LayoutPieces pieces;
+
+ Layout inLayout = doLayout("I", MinikinPaint(fc));
+ inLayout.mAdvances[0] = MARKER1; // Modify the advance to make sure this layout is used.
+ pieces.insert(utf8ToUtf16("I"), Range(0, 1), paint, false /* dir */,
+ StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, inLayout);
+
+ inLayout = doLayout("V", MinikinPaint(fc));
+ inLayout.mAdvances[0] = MARKER2; // Modify the advance to make sure this layout is used.
+ pieces.insert(utf8ToUtf16("V"), Range(0, 1), paint, false /* dir */,
+ StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, inLayout);
+
+ Layout outLayout = doLayoutWithPrecomputedPieces("I V", MinikinPaint(fc), pieces);
+ EXPECT_EQ(MARKER1, outLayout.mAdvances[0]);
+ EXPECT_EQ(MARKER2, outLayout.mAdvances[2]);
+ }
+}
+
// TODO: Add more test cases, e.g. measure text, letter spacing.
} // namespace minikin
diff --git a/tests/unittest/LayoutUtilsTest.cpp b/tests/unittest/LayoutUtilsTest.cpp
index e7e6c27..4c18cce 100644
--- a/tests/unittest/LayoutUtilsTest.cpp
+++ b/tests/unittest/LayoutUtilsTest.cpp
@@ -14,11 +14,12 @@
* limitations under the License.
*/
-#include <gtest/gtest.h>
-#include <UnicodeUtils.h>
-
#include "LayoutUtils.h"
+#include <gtest/gtest.h>
+
+#include "UnicodeUtils.h"
+
namespace minikin {
void ExpectNextWordBreakForCache(size_t offset_in, const char* query_str) {
@@ -28,9 +29,8 @@
size_t size = 0U;
ParseUnicode(buf, BUF_SIZE, query_str, &size, &expected_breakpoint);
- EXPECT_EQ(expected_breakpoint,
- getNextWordBreakForCache(buf, offset_in, size))
- << "Expected position is [" << query_str << "] from offset " << offset_in;
+ EXPECT_EQ(expected_breakpoint, getNextWordBreakForCache(buf, offset_in, size))
+ << "Expected position is [" << query_str << "] from offset " << offset_in;
}
void ExpectPrevWordBreakForCache(size_t offset_in, const char* query_str) {
@@ -40,9 +40,8 @@
size_t size = 0U;
ParseUnicode(buf, BUF_SIZE, query_str, &size, &expected_breakpoint);
- EXPECT_EQ(expected_breakpoint,
- getPrevWordBreakForCache(buf, offset_in, size))
- << "Expected position is [" << query_str << "] from offset " << offset_in;
+ EXPECT_EQ(expected_breakpoint, getPrevWordBreakForCache(buf, offset_in, size))
+ << "Expected position is [" << query_str << "] from offset " << offset_in;
}
TEST(WordBreakTest, goNextWordBreakTest) {
@@ -89,8 +88,7 @@
ExpectNextWordBreakForCache(3, "U+4E00 U+4E00 U+4E00 U+4E00 | U+4E00");
ExpectNextWordBreakForCache(4, "U+4E00 U+4E00 U+4E00 U+4E00 U+4E00 |");
ExpectNextWordBreakForCache(5, "U+4E00 U+4E00 U+4E00 U+4E00 U+4E00 |");
- ExpectNextWordBreakForCache(1000,
- "U+4E00 U+4E00 U+4E00 U+4E00 U+4E00 |");
+ ExpectNextWordBreakForCache(1000, "U+4E00 U+4E00 U+4E00 U+4E00 U+4E00 |");
ExpectNextWordBreakForCache(0, "U+4E00 | U+4E8C U+4E09 U+56DB U+4E94");
ExpectNextWordBreakForCache(1, "U+4E00 U+4E8C | U+4E09 U+56DB U+4E94");
@@ -98,8 +96,7 @@
ExpectNextWordBreakForCache(3, "U+4E00 U+4E8C U+4E09 U+56DB | U+4E94");
ExpectNextWordBreakForCache(4, "U+4E00 U+4E8C U+4E09 U+56DB U+4E94 |");
ExpectNextWordBreakForCache(5, "U+4E00 U+4E8C U+4E09 U+56DB U+4E94 |");
- ExpectNextWordBreakForCache(1000,
- "U+4E00 U+4E8C U+4E09 U+56DB U+4E94 |");
+ ExpectNextWordBreakForCache(1000, "U+4E00 U+4E8C U+4E09 U+56DB U+4E94 |");
ExpectNextWordBreakForCache(0, "U+4E00 'a' 'b' | U+2000 'c' U+4E00");
ExpectNextWordBreakForCache(1, "U+4E00 'a' 'b' | U+2000 'c' U+4E00");
@@ -246,8 +243,7 @@
ExpectNextWordBreakForCache(4, "U+845B U+E0100 U+E0100 | U+845B");
ExpectNextWordBreakForCache(5, "U+845B U+E0100 U+E0100 U+845B |");
ExpectNextWordBreakForCache(6, "U+845B U+E0100 U+E0100 U+845B |");
- ExpectNextWordBreakForCache(1000,
- "U+845B U+E0100 U+E0100 U+845B |");
+ ExpectNextWordBreakForCache(1000, "U+845B U+E0100 U+E0100 U+845B |");
// CJK Ideographic char + Variation Selector(VS1) + Variation Selector(VS17)
ExpectNextWordBreakForCache(0, "U+845B U+FE00 U+E0100 | U+845B");
@@ -477,8 +473,7 @@
ExpectPrevWordBreakForCache(4, "| U+845B U+E0100 U+E0100 U+845B");
ExpectPrevWordBreakForCache(5, "| U+845B U+E0100 U+E0100 U+845B");
ExpectPrevWordBreakForCache(6, "U+845B U+E0100 U+E0100 | U+845B");
- ExpectPrevWordBreakForCache(1000,
- "U+845B U+E0100 U+E0100 | U+845B");
+ ExpectPrevWordBreakForCache(1000, "U+845B U+E0100 U+E0100 | U+845B");
// CJK Ideographic char + Variation Selector(VS1) + Variation Selector(VS17)
ExpectPrevWordBreakForCache(0, "| U+845B U+FE00 U+E0100 U+845B");
diff --git a/tests/unittest/LineBreakerTest.cpp b/tests/unittest/LineBreakerTest.cpp
deleted file mode 100644
index 4b18c8c..0000000
--- a/tests/unittest/LineBreakerTest.cpp
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <gtest/gtest.h>
-
-#include "ICUTestBase.h"
-#include <minikin/LineBreaker.h>
-#include <unicode/locid.h>
-
-namespace minikin {
-
-typedef ICUTestBase LineBreakerTest;
-
-TEST_F(LineBreakerTest, setLocales) {
- {
- LineBreaker lineBreaker;
- Hyphenator hyphenator;
- std::vector<Hyphenator*> hyphenators;
- hyphenators.push_back(&hyphenator);
- lineBreaker.setLocales("en-US", hyphenators);
- EXPECT_EQ(icu::Locale::getUS(), lineBreaker.mLocale);
- EXPECT_EQ(&hyphenator, lineBreaker.mHyphenator);
- }
- {
- LineBreaker lineBreaker;
- Hyphenator hyphenator1, hyphenator2;
- std::vector<Hyphenator*> hyphenators;
- hyphenators.push_back(&hyphenator1);
- hyphenators.push_back(&hyphenator2);
- lineBreaker.setLocales("fr-FR,en-US", hyphenators);
- EXPECT_EQ(icu::Locale::getFrance(), lineBreaker.mLocale);
- EXPECT_EQ(&hyphenator1, lineBreaker.mHyphenator);
- }
- {
- LineBreaker lineBreaker;
- std::vector<Hyphenator*> hyphenators;
- lineBreaker.setLocales("", hyphenators);
- EXPECT_EQ(icu::Locale::getRoot(), lineBreaker.mLocale);
- EXPECT_EQ(nullptr, lineBreaker.mHyphenator);
- }
- {
- LineBreaker lineBreaker;
- std::vector<Hyphenator*> hyphenators;
- Hyphenator hyphenator;
- hyphenators.push_back(&hyphenator);
- lineBreaker.setLocales("THISISABOGUSLANGUAGE", hyphenators);
- EXPECT_EQ(icu::Locale::getRoot(), lineBreaker.mLocale);
- EXPECT_EQ(nullptr, lineBreaker.mHyphenator);
- }
- {
- LineBreaker lineBreaker;
- Hyphenator hyphenator1, hyphenator2;
- std::vector<Hyphenator*> hyphenators;
- hyphenators.push_back(&hyphenator1);
- hyphenators.push_back(&hyphenator2);
- lineBreaker.setLocales("THISISABOGUSLANGUAGE,en-US", hyphenators);
- EXPECT_EQ(icu::Locale::getUS(), lineBreaker.mLocale);
- EXPECT_EQ(&hyphenator2, lineBreaker.mHyphenator);
- }
- {
- LineBreaker lineBreaker;
- Hyphenator hyphenator1, hyphenator2;
- std::vector<Hyphenator*> hyphenators;
- hyphenators.push_back(&hyphenator1);
- hyphenators.push_back(&hyphenator2);
- lineBreaker.setLocales("THISISABOGUSLANGUAGE,ANOTHERBOGUSLANGUAGE", hyphenators);
- EXPECT_EQ(icu::Locale::getRoot(), lineBreaker.mLocale);
- EXPECT_EQ(nullptr, lineBreaker.mHyphenator);
- }
-}
-
-} // namespace minikin
diff --git a/tests/unittest/LineBreakerTestHelper.h b/tests/unittest/LineBreakerTestHelper.h
new file mode 100644
index 0000000..70d6521
--- /dev/null
+++ b/tests/unittest/LineBreakerTestHelper.h
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+
+#include "minikin/Hyphenator.h"
+#include "minikin/LineBreaker.h"
+
+#include "LocaleListCache.h"
+#include "MinikinInternal.h"
+#include "UnicodeUtils.h"
+
+namespace minikin {
+namespace line_breaker_test_helper {
+
+class RectangleLineWidth : public LineWidth {
+public:
+ RectangleLineWidth(float width) : mWidth(width) {}
+ virtual ~RectangleLineWidth() {}
+
+ float getAt(size_t) const override { return mWidth; }
+ float getMin() const override { return mWidth; }
+ float getLeftPaddingAt(size_t) const override { return 0; }
+ float getRightPaddingAt(size_t) const override { return 0; }
+
+private:
+ float mWidth;
+};
+
+// The run implemenataion for returning the same width for all characters.
+class ConstantRun : public Run {
+public:
+ ConstantRun(const Range& range, const std::string& lang, float width)
+ : Run(range), mPaint(nullptr /* font collection */), mWidth(width) {
+ mLocaleListId = LocaleListCache::getId(lang);
+ }
+
+ virtual bool isRtl() const override { return false; }
+ virtual bool canHyphenate() const override { return true; }
+ virtual uint32_t getLocaleListId() const { return mLocaleListId; }
+
+ virtual void getMetrics(const U16StringPiece&, float* advances, MinikinExtent*,
+ LayoutPieces*) const {
+ std::fill(advances, advances + mRange.getLength(), mWidth);
+ }
+
+ virtual std::pair<float, MinikinRect> getBounds(const U16StringPiece& /* text */,
+ const Range& /* range */,
+ const LayoutPieces& /* pieces */) const {
+ return std::make_pair(mWidth, MinikinRect());
+ }
+
+ virtual const MinikinPaint* getPaint() const { return &mPaint; }
+
+ virtual float measureHyphenPiece(const U16StringPiece&, const Range& range,
+ StartHyphenEdit start, EndHyphenEdit end, float*,
+ LayoutPieces*) const {
+ uint32_t extraCharForHyphen = 0;
+ if (isInsertion(start)) {
+ extraCharForHyphen++;
+ }
+ if (isInsertion(end)) {
+ extraCharForHyphen++;
+ }
+ return mWidth * (range.getLength() + extraCharForHyphen);
+ }
+
+private:
+ MinikinPaint mPaint;
+ uint32_t mLocaleListId;
+ float mWidth;
+};
+
+struct LineBreakExpectation {
+ LineBreakExpectation(const std::string& lineContent, float width, StartHyphenEdit startEdit,
+ EndHyphenEdit endEdit)
+ : mLineContent(lineContent), mWidth(width), mStartEdit(startEdit), mEndEdit(endEdit){};
+
+ std::string mLineContent;
+ float mWidth;
+ StartHyphenEdit mStartEdit;
+ EndHyphenEdit mEndEdit;
+};
+
+static bool sameLineBreak(const std::vector<LineBreakExpectation>& expected,
+ const LineBreakResult& actual) {
+ if (expected.size() != actual.breakPoints.size()) {
+ return false;
+ }
+
+ uint32_t breakOffset = 0;
+ for (uint32_t i = 0; i < expected.size(); ++i) {
+ std::vector<uint16_t> u16Str = utf8ToUtf16(expected[i].mLineContent);
+
+ // The expected string contains auto inserted hyphen. Remove it for computing offset.
+ uint32_t lineLength = u16Str.size();
+ if (isInsertion(expected[i].mStartEdit)) {
+ if (u16Str[0] != '-') {
+ return false;
+ }
+ --lineLength;
+ }
+ if (isInsertion(expected[i].mEndEdit)) {
+ if (u16Str.back() != '-') {
+ return false;
+ }
+ --lineLength;
+ }
+ breakOffset += lineLength;
+
+ if (breakOffset != static_cast<uint32_t>(actual.breakPoints[i])) {
+ return false;
+ }
+ if (expected[i].mWidth != actual.widths[i]) {
+ return false;
+ }
+ HyphenEdit edit = static_cast<HyphenEdit>(actual.flags[i] & 0xFF);
+ if (expected[i].mStartEdit != startHyphenEdit(edit)) {
+ return false;
+ }
+ if (expected[i].mEndEdit != endHyphenEdit(edit)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+// Make debug string.
+static std::string toString(const std::vector<LineBreakExpectation>& lines) {
+ std::string out;
+ for (uint32_t i = 0; i < lines.size(); ++i) {
+ const LineBreakExpectation& line = lines[i];
+
+ char lineMsg[128] = {};
+ snprintf(lineMsg, sizeof(lineMsg),
+ "Line %2d, Width: %5.1f, Hyphen(%hhu, %hhu), Text: \"%s\"\n", i, line.mWidth,
+ line.mStartEdit, line.mEndEdit, line.mLineContent.c_str());
+ out += lineMsg;
+ }
+ return out;
+}
+
+// Make debug string.
+static std::string toString(const U16StringPiece& textBuf, const LineBreakResult& lines) {
+ std::string out;
+ for (uint32_t i = 0; i < lines.breakPoints.size(); ++i) {
+ const Range textRange(i == 0 ? 0 : lines.breakPoints[i - 1], lines.breakPoints[i]);
+ const HyphenEdit edit = static_cast<HyphenEdit>(lines.flags[i] & 0xFF);
+
+ const StartHyphenEdit startEdit = startHyphenEdit(edit);
+ const EndHyphenEdit endEdit = endHyphenEdit(edit);
+ std::string hyphenatedStr = utf16ToUtf8(textBuf.substr(textRange));
+
+ if (isInsertion(startEdit)) {
+ hyphenatedStr.insert(0, "-");
+ }
+ if (isInsertion(endEdit)) {
+ hyphenatedStr.push_back('-');
+ }
+ char lineMsg[128] = {};
+ snprintf(lineMsg, sizeof(lineMsg),
+ "Line %2d, Width: %5.1f, Hyphen(%hhu, %hhu), Text: \"%s\"\n", i, lines.widths[i],
+ startEdit, endEdit, hyphenatedStr.c_str());
+ out += lineMsg;
+ }
+ return out;
+}
+
+} // namespace line_breaker_test_helper
+} // namespace minikin
diff --git a/tests/unittest/LocaleListTest.cpp b/tests/unittest/LocaleListTest.cpp
new file mode 100644
index 0000000..8056185
--- /dev/null
+++ b/tests/unittest/LocaleListTest.cpp
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+
+#include "minikin/Hyphenator.h"
+
+#include "GreedyLineBreaker.h"
+#include "LocaleListCache.h"
+#include "MinikinInternal.h"
+
+namespace minikin {
+namespace {
+
+const LocaleList& getLocaleList(const std::string& localeStr) {
+ return LocaleListCache::getById(LocaleListCache::getId(localeStr));
+}
+
+TEST(LocaleListTest, basicTest) {
+ std::string langTag = "en-US";
+ EXPECT_EQ(1U, getLocaleList(langTag).size());
+ EXPECT_EQ("en-Latn-US", getLocaleList(langTag)[0].getString());
+
+ langTag = "en-US,fr-FR";
+ EXPECT_EQ(2U, getLocaleList(langTag).size());
+ EXPECT_EQ("en-Latn-US", getLocaleList(langTag)[0].getString());
+ EXPECT_EQ("fr-Latn-FR", getLocaleList(langTag)[1].getString());
+
+ langTag = "en-US,en-US,fr-FR";
+ EXPECT_EQ(2U, getLocaleList(langTag).size());
+ EXPECT_EQ("en-Latn-US", getLocaleList(langTag)[0].getString());
+ EXPECT_EQ("fr-Latn-FR", getLocaleList(langTag)[1].getString());
+
+ EXPECT_EQ(0U, getLocaleList("").size());
+}
+
+TEST(LocaleListTest, bogusLanguageTest) {
+ std::string langTag = "en-US,THISISBOGUSLANGUAGE";
+ EXPECT_EQ(1U, getLocaleList(langTag).size());
+ EXPECT_EQ("en-Latn-US", getLocaleList(langTag)[0].getString());
+
+ langTag = "THISISBOGUSLANGUAGE,en-US";
+ EXPECT_EQ(1U, getLocaleList(langTag).size());
+ EXPECT_EQ("en-Latn-US", getLocaleList(langTag)[0].getString());
+
+ langTag = "THISISBOGUSLANGUAGE,THISISANOTHERBOGUSLANGUAGE";
+ EXPECT_EQ(0U, getLocaleList(langTag).size());
+}
+
+} // namespace
+} // namespace minikin
diff --git a/tests/unittest/MeasuredTextTest.cpp b/tests/unittest/MeasuredTextTest.cpp
new file mode 100644
index 0000000..e8ed408
--- /dev/null
+++ b/tests/unittest/MeasuredTextTest.cpp
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "minikin/MeasuredText.h"
+
+#include <gtest/gtest.h>
+
+#include "minikin/LineBreaker.h"
+
+#include "FontTestUtils.h"
+#include "UnicodeUtils.h"
+
+namespace minikin {
+
+constexpr float CHAR_WIDTH = 10.0; // Mock implementation always returns 10.0 for advance.
+
+TEST(MeasuredTextTest, RunTests) {
+ constexpr uint32_t CHAR_COUNT = 6;
+ constexpr float REPLACEMENT_WIDTH = 20.0f;
+ auto font = buildFontCollection("Ascii.ttf");
+
+ MeasuredTextBuilder builder;
+
+ MinikinPaint paint1(font);
+ paint1.size = 10.0f; // make 1em = 10px
+ builder.addStyleRun(0, 2, std::move(paint1), false /* is RTL */);
+ builder.addReplacementRun(2, 4, REPLACEMENT_WIDTH, 0 /* locale list id */);
+ MinikinPaint paint2(font);
+ paint2.size = 10.0f; // make 1em = 10px
+ builder.addStyleRun(4, 6, std::move(paint2), false /* is RTL */);
+
+ std::vector<uint16_t> text(CHAR_COUNT, 'a');
+
+ std::unique_ptr<MeasuredText> measuredText =
+ builder.build(text, true /* compute hyphenation */, false /* compute full layout */);
+
+ ASSERT_TRUE(measuredText);
+
+ // ReplacementRun assigns all width to the first character and leave zeros others.
+ std::vector<float> expectedWidths = {CHAR_WIDTH, CHAR_WIDTH, REPLACEMENT_WIDTH,
+ 0, CHAR_WIDTH, CHAR_WIDTH};
+
+ EXPECT_EQ(expectedWidths, measuredText->widths);
+}
+
+} // namespace minikin
diff --git a/tests/unittest/MeasurementTests.cpp b/tests/unittest/MeasurementTests.cpp
index 7fedecb..b5a85c7 100644
--- a/tests/unittest/MeasurementTests.cpp
+++ b/tests/unittest/MeasurementTests.cpp
@@ -14,9 +14,11 @@
* limitations under the License.
*/
+#include "minikin/Measurement.h"
+
#include <gtest/gtest.h>
-#include <UnicodeUtils.h>
-#include <minikin/Measurement.h>
+
+#include "UnicodeUtils.h"
namespace minikin {
diff --git a/tests/unittest/OptimalLineBreakerTest.cpp b/tests/unittest/OptimalLineBreakerTest.cpp
new file mode 100644
index 0000000..a4479ad
--- /dev/null
+++ b/tests/unittest/OptimalLineBreakerTest.cpp
@@ -0,0 +1,1155 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <memory>
+
+#include <gtest/gtest.h>
+
+#include "minikin/Hyphenator.h"
+
+#include "FileUtils.h"
+#include "FontTestUtils.h"
+#include "HyphenatorMap.h"
+#include "LineBreakerTestHelper.h"
+#include "LocaleListCache.h"
+#include "MinikinInternal.h"
+#include "OptimalLineBreaker.h"
+#include "UnicodeUtils.h"
+#include "WordBreaker.h"
+
+namespace minikin {
+namespace {
+
+using line_breaker_test_helper::ConstantRun;
+using line_breaker_test_helper::LineBreakExpectation;
+using line_breaker_test_helper::RectangleLineWidth;
+using line_breaker_test_helper::sameLineBreak;
+using line_breaker_test_helper::toString;
+
+class OptimalLineBreakerTest : public testing::Test {
+public:
+ OptimalLineBreakerTest() {}
+
+ virtual ~OptimalLineBreakerTest() {}
+
+ virtual void SetUp() override {
+ mHyphenationPattern = readWholeFile("/system/usr/hyphen-data/hyph-en-us.hyb");
+ Hyphenator* hyphenator = Hyphenator::loadBinary(
+ mHyphenationPattern.data(), 2 /* min prefix */, 2 /* min suffix */, "en-US");
+ HyphenatorMap::add("en-US", hyphenator);
+ HyphenatorMap::add("pl", Hyphenator::loadBinary(nullptr, 0, 0, "pl"));
+ }
+
+ virtual void TearDown() override { HyphenatorMap::clear(); }
+
+protected:
+ LineBreakResult doLineBreak(const U16StringPiece& textBuffer, BreakStrategy strategy,
+ HyphenationFrequency frequency, float charWidth, float lineWidth) {
+ return doLineBreak(textBuffer, strategy, frequency, charWidth, "en-US", lineWidth);
+ }
+
+ LineBreakResult doLineBreak(const U16StringPiece& textBuffer, BreakStrategy strategy,
+ HyphenationFrequency frequency, float charWidth,
+ const std::string& lang, float lineWidth) {
+ MeasuredTextBuilder builder;
+ builder.addCustomRun<ConstantRun>(Range(0, textBuffer.size()), lang, charWidth);
+ std::unique_ptr<MeasuredText> measuredText = builder.build(
+ textBuffer, true /* compute hyphenation */, false /* compute full layout */);
+ return doLineBreak(textBuffer, *measuredText, strategy, frequency, lineWidth);
+ }
+
+ LineBreakResult doLineBreak(const U16StringPiece& textBuffer, const MeasuredText& measuredText,
+ BreakStrategy strategy, HyphenationFrequency frequency,
+ float lineWidth) {
+ RectangleLineWidth rectangleLineWidth(lineWidth);
+ return breakLineOptimal(textBuffer, measuredText, rectangleLineWidth, strategy, frequency,
+ false /* justified */);
+ }
+
+private:
+ std::vector<uint8_t> mHyphenationPattern;
+};
+
+TEST_F(OptimalLineBreakerTest, testBreakWithoutHyphenation) {
+ constexpr float CHAR_WIDTH = 10.0;
+ constexpr BreakStrategy HIGH_QUALITY = BreakStrategy::HighQuality;
+ constexpr BreakStrategy BALANCED = BreakStrategy::Balanced;
+ constexpr HyphenationFrequency NO_HYPHENATION = HyphenationFrequency::None;
+ constexpr HyphenationFrequency NORMAL_HYPHENATION = HyphenationFrequency::Normal;
+ const std::vector<uint16_t> textBuf = utf8ToUtf16("This is an example text.");
+
+ constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
+ constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
+ constexpr EndHyphenEdit END_HYPHEN = EndHyphenEdit::INSERT_HYPHEN;
+ // Note that disable clang-format everywhere since aligned expectation is more readable.
+ {
+ constexpr float LINE_WIDTH = 1000 * CHAR_WIDTH;
+ std::vector<LineBreakExpectation> expect = {
+ {"This is an example text.", 24 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ };
+
+ auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 24 * CHAR_WIDTH;
+ std::vector<LineBreakExpectation> expect = {
+ {"This is an example text.", 24 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ };
+ auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 23 * CHAR_WIDTH;
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ { "This is an example " , 18 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "text." , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ };
+ // clang-format on
+
+ auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+
+ // clang-format off
+ expect = {
+ { "This is an " , 10 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "example text." , 13 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ };
+ // clang-format on
+ actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ // clang-format off
+ expect = {
+ { "This is an ex-" , 14 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
+ { "ample text." , 11 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ };
+ // clang-format on
+ actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 17 * CHAR_WIDTH;
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ { "This is an " , 10 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "example text." , 13 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ };
+ // clang-format on
+
+ auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ // clang-format off
+ expect = {
+ { "This is an exam-" , 16 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
+ { "ple text." , 9 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ };
+ // clang-format on
+ actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ // clang-format off
+ expect = {
+ { "This is an " , 10 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "example text." , 13 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ };
+ // clang-format on
+ actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ // clang-format off
+ expect = {
+ { "This is an ex-", 14 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
+ { "ample text." , 11 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ };
+ // clang-format on
+ actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 16 * CHAR_WIDTH;
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ { "This is an " , 10 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "example text." , 13 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ };
+ // clang-format on
+
+ auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ // clang-format off
+ expect = {
+ { "This is an exam-" , 16 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
+ { "ple text." , 9 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ };
+ // clang-format on
+ actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ // clang-format off
+ expect = {
+ { "This is an " , 10 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "example text." , 13 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ };
+ // clang-format on
+ actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ // clang-format off
+ expect = {
+ { "This is an ex-", 14 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
+ { "ample text." , 11 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ };
+ // clang-format on
+ actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 15 * CHAR_WIDTH;
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ { "This is an " , 10 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "example text." , 13 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ };
+ // clang-format on
+
+ auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ // clang-format off
+ expect = {
+ { "This is an ex-", 14 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
+ { "ample text." , 11 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ };
+ // clang-format on
+ actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ // clang-format off
+ expect = {
+ { "This is an " , 10 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "example text." , 13 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ };
+ // clang-format on
+ actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ // clang-format off
+ expect = {
+ { "This is an ex-", 14 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
+ { "ample text." , 11 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ };
+ // clang-format on
+ actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 13 * CHAR_WIDTH;
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ { "This is an " , 10 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "example text." , 13 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ };
+ // clang-format on
+
+ auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 12 * CHAR_WIDTH;
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ { "This is an ", 10 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "example " , 7 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "text." , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ };
+ // clang-format on
+
+ auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ // clang-format off
+ expect = {
+ { "This is " , 7 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "an exam-" , 8 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
+ { "ple text.", 9 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ };
+ // clang-format on
+ actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 9 * CHAR_WIDTH;
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ { "This " , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "is an " , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "example ", 7 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "text." , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ };
+ // clang-format on
+
+ auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ // clang-format off
+ expect = {
+ { "This is " , 7 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "an exam-" , 8 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
+ { "ple text.", 9 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ };
+ // clang-format on
+ actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ // clang-format off
+ expect = {
+ { "This " , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "is an " , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "example ", 7 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "text." , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ };
+ // clang-format on
+ actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ // clang-format off
+ expect = {
+ { "This is " , 7 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "an exam-" , 8 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
+ { "ple text.", 9 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ };
+ // clang-format on
+ actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 8 * CHAR_WIDTH;
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ { "This " , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "is an " , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "example ", 7 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "text." , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ };
+ // clang-format on
+
+ auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ // clang-format off
+ expect = {
+ { "This is ", 7 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "an ex-" , 6 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
+ { "ample " , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "text." , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ };
+ // clang-format on
+ actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 7 * CHAR_WIDTH;
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ { "This " , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "is an " , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "example ", 7 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "text." , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ };
+ // clang-format on
+
+ auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ // clang-format off
+ expect = {
+ { "This is ", 7 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "an ex-" , 6 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
+ { "ample " , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "text." , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ };
+ // clang-format on
+ actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 6 * CHAR_WIDTH;
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ { "This " , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "is an ", 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ // TODO: Is this desperate break working correctly?
+ { "exa" , 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "mple " , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "text." , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ };
+ // clang-format on
+
+ auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ // clang-format off
+ expect = {
+ { "This " , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "is an ", 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "exam-" , 5 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
+ { "ple " , 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "text." , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ };
+ // clang-format on
+ actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 5 * CHAR_WIDTH;
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ { "This " , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "is an ", 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ // TODO: Is this desperate break working correctly?
+ { "exa" , 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "mple " , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "text." , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ };
+ // clang-format on
+
+ auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ // clang-format off
+ expect = {
+ { "This " , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "is an ", 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "exam-" , 5 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
+ { "ple " , 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "text." , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ };
+ // clang-format on
+ actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 4 * CHAR_WIDTH;
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ { "This " , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "is " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "an " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ // TODO: Is this desperate break working correctly?
+ { "exa" , 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "mple " , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "text" , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "." , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ };
+ // clang-format on
+
+ auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ // clang-format off
+ expect = {
+ { "This " , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "is " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "an " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ // TODO: Is this desperate break working correctly?
+ { "exa" , 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "mple " , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ // TODO: Is this desperate break working correctly?
+ { "t" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "ext." , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ };
+ // clang-format on
+ actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ // clang-format off
+ expect = {
+ { "This ", 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "is " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "an " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "ex-" , 3 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
+ { "am-" , 3 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
+ { "ple " , 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "text" , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "." , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ };
+ // clang-format on
+ actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ // clang-format off
+ expect = {
+ { "This ", 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "is " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "an " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "ex-" , 3 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
+ { "am-" , 3 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
+ { "ple " , 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ // TODO: Is this desperate break working correctly?
+ { "te" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "xt." , 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ };
+ // clang-format on
+ actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 3 * CHAR_WIDTH;
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ // TODO: Is this desperate break working correctly?
+ { "T" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "his ", 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "is " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "an " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ // TODO: Is this desperate break working correctly?
+ { "e" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "xam" , 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "ple ", 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "tex" , 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "t." , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ };
+ // clang-format on
+
+ auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ // clang-format off
+ expect = {
+ // TODO: Is this desperate break working correctly?
+ { "T" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "his ", 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "is " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "an " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ // TODO: Is this desperate break working correctly?
+ { "e" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "xam" , 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "ple ", 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ // TODO: Is this desperate break working correctly?
+ { "te" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "xt." , 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ };
+ // clang-format on
+ actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ // clang-format off
+ expect = {
+ // TODO: Is this desperate break working correctly?
+ { "T" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "his ", 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "is " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "an " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "ex-" , 3 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
+ { "am-" , 3 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
+ { "ple ", 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "tex" , 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "t." , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ };
+ // clang-format on
+ actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ // clang-format off
+ expect = {
+ // TODO: Is this desperate break working correctly?
+ {"T" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ {"his ", 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ {"is " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ {"an " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ {"ex-" , 3 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN},
+ {"am-" , 3 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN},
+ {"ple ", 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ // TODO: Is this desperate break working correctly?
+ {"te" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ {"xt." , 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ };
+ // clang-format on
+ actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 2 * CHAR_WIDTH;
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ { "Th" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "is ", 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "is ", 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "an ", 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ // TODO: Is this desperate break working correctly?
+ { "e" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "xa" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "mp" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "le ", 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "te" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "xt" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "." , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ };
+ // clang-format on
+
+ auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ // clang-format off
+ expect = {
+ { "Th" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "is ", 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "is ", 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "an ", 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ // TODO: Is this desperate break working correctly?
+ { "e" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "xa" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "mp" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "le ", 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ // TODO: Is this desperate break working correctly?
+ { "t" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "ex" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "t." , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ };
+ // clang-format on
+ actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 1 * CHAR_WIDTH;
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ { "T" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "h" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "i" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "s ", 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "i" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "s ", 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "a" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "n ", 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "e" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "x" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "a" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "m" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "p" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "l" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "e ", 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "t" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "e" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "x" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "t" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "." , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ };
+ // clang-format on
+
+ auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+}
+
+TEST_F(OptimalLineBreakerTest, testHyphenationStartLineChange) {
+ constexpr float CHAR_WIDTH = 10.0;
+ constexpr BreakStrategy HIGH_QUALITY = BreakStrategy::HighQuality;
+ constexpr HyphenationFrequency NORMAL_HYPHENATION = HyphenationFrequency::Normal;
+ // "hyphenation" is hyphnated to "hy-phen-a-tion".
+ const std::vector<uint16_t> textBuf = utf8ToUtf16("czerwono-niebieska");
+
+ constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
+ constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
+ constexpr StartHyphenEdit START_HYPHEN = StartHyphenEdit::INSERT_HYPHEN;
+
+ // Note that disable clang-format everywhere since aligned expectation is more readable.
+ {
+ constexpr float LINE_WIDTH = 1000 * CHAR_WIDTH;
+ std::vector<LineBreakExpectation> expect = {
+ {"czerwono-niebieska", 18 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ };
+
+ const auto actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, "pl",
+ LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 18 * CHAR_WIDTH;
+ std::vector<LineBreakExpectation> expect = {
+ {"czerwono-niebieska", 18 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ };
+
+ const auto actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, "pl",
+ LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 13 * CHAR_WIDTH;
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ {"czerwono-" , 9 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ {"-niebieska", 10 * CHAR_WIDTH, START_HYPHEN, NO_END_HYPHEN},
+ };
+ // clang-format on
+
+ const auto actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, "pl",
+ LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+}
+
+TEST_F(OptimalLineBreakerTest, testZeroWidthLine) {
+ constexpr float CHAR_WIDTH = 10.0;
+ constexpr BreakStrategy HIGH_QUALITY = BreakStrategy::HighQuality;
+ constexpr HyphenationFrequency NORMAL_HYPHENATION = HyphenationFrequency::Normal;
+ constexpr float LINE_WIDTH = 0 * CHAR_WIDTH;
+
+ constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
+ constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
+
+ {
+ const auto textBuf = utf8ToUtf16("");
+ std::vector<LineBreakExpectation> expect = {};
+ const auto actual =
+ doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ const auto textBuf = utf8ToUtf16("A");
+ std::vector<LineBreakExpectation> expect = {
+ {"A", 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ };
+ const auto actual =
+ doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ const auto textBuf = utf8ToUtf16("AB");
+ std::vector<LineBreakExpectation> expect = {
+ {"A", 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ {"B", 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ };
+ const auto actual =
+ doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+}
+
+TEST_F(OptimalLineBreakerTest, testZeroWidthCharacter) {
+ constexpr float CHAR_WIDTH = 0.0;
+ constexpr BreakStrategy HIGH_QUALITY = BreakStrategy::HighQuality;
+ constexpr HyphenationFrequency NORMAL_HYPHENATION = HyphenationFrequency::Normal;
+
+ constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
+ constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
+ {
+ constexpr float LINE_WIDTH = 1.0;
+ const auto textBuf = utf8ToUtf16("This is an example text.");
+ std::vector<LineBreakExpectation> expect = {
+ {"This is an example text.", 0, NO_START_HYPHEN, NO_END_HYPHEN},
+ };
+ const auto actual =
+ doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 0.0;
+ const auto textBuf = utf8ToUtf16("This is an example text.");
+ std::vector<LineBreakExpectation> expect = {
+ {"This is an example text.", 0, NO_START_HYPHEN, NO_END_HYPHEN},
+ };
+ const auto actual =
+ doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+}
+
+TEST_F(OptimalLineBreakerTest, testLocaleSwitchTest) {
+ constexpr float CHAR_WIDTH = 10.0;
+ constexpr BreakStrategy HIGH_QUALITY = BreakStrategy::HighQuality;
+ constexpr HyphenationFrequency NORMAL_HYPHENATION = HyphenationFrequency::Normal;
+
+ constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
+ constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
+
+ constexpr float LINE_WIDTH = 24 * CHAR_WIDTH;
+ const auto textBuf = utf8ToUtf16("This is an example text.");
+ {
+ std::vector<LineBreakExpectation> expect = {
+ {"This is an example text.", 24 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ };
+
+ MeasuredTextBuilder builder;
+ builder.addCustomRun<ConstantRun>(Range(0, 18), "en-US", CHAR_WIDTH);
+ builder.addCustomRun<ConstantRun>(Range(18, textBuf.size()), "en-US", CHAR_WIDTH);
+ std::unique_ptr<MeasuredText> measuredText = builder.build(
+ textBuf, true /* compute hyphenation */, false /* compute full layout */);
+
+ const auto actual =
+ doLineBreak(textBuf, *measuredText, HIGH_QUALITY, NORMAL_HYPHENATION, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ std::vector<LineBreakExpectation> expect = {
+ {"This is an example text.", 24 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ };
+
+ MeasuredTextBuilder builder;
+ builder.addCustomRun<ConstantRun>(Range(0, 18), "en-US", CHAR_WIDTH);
+ builder.addCustomRun<ConstantRun>(Range(18, textBuf.size()), "fr-FR", CHAR_WIDTH);
+ std::unique_ptr<MeasuredText> measuredText = builder.build(
+ textBuf, true /* compute hyphenation */, false /* compute full layout */);
+ const auto actual =
+ doLineBreak(textBuf, *measuredText, HIGH_QUALITY, NORMAL_HYPHENATION, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+}
+
+TEST_F(OptimalLineBreakerTest, testEmailOrUrl) {
+ constexpr float CHAR_WIDTH = 10.0;
+ constexpr BreakStrategy HIGH_QUALITY = BreakStrategy::HighQuality;
+ constexpr BreakStrategy BALANCED = BreakStrategy::Balanced;
+ constexpr HyphenationFrequency NO_HYPHENATION = HyphenationFrequency::None;
+ constexpr HyphenationFrequency NORMAL_HYPHENATION = HyphenationFrequency::Normal;
+
+ constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
+ constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
+ {
+ constexpr float LINE_WIDTH = 24 * CHAR_WIDTH;
+ const auto textBuf = utf8ToUtf16("This is an url: http://a.b");
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ // TODO: Fix this. Prefer not to break inside URL.
+ {"This is an url: http://a", 24 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ {".b", 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ };
+ // clang-format on
+ auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ // clang-format off
+ expect = {
+ {"This is an url: ", 15 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ {"http://a.b", 10 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ };
+ // clang-format on
+ actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 24 * CHAR_WIDTH;
+ const auto textBuf = utf8ToUtf16("This is an email: a@example.com");
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ {"This is an email: ", 17 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ {"a@example.com" , 13 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ };
+ // clang-format on
+
+ auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+}
+
+TEST_F(OptimalLineBreakerTest, testLocaleSwitch_InEmailOrUrl) {
+ constexpr float CHAR_WIDTH = 10.0;
+ constexpr BreakStrategy HIGH_QUALITY = BreakStrategy::HighQuality;
+ constexpr BreakStrategy BALANCED = BreakStrategy::Balanced;
+ constexpr HyphenationFrequency NO_HYPHENATION = HyphenationFrequency::None;
+ constexpr HyphenationFrequency NORMAL_HYPHENATION = HyphenationFrequency::Normal;
+
+ constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
+ constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
+
+ constexpr float LINE_WIDTH = 24 * CHAR_WIDTH;
+ {
+ const auto textBuf = utf8ToUtf16("This is an url: http://a.b");
+ MeasuredTextBuilder builder;
+ builder.addCustomRun<ConstantRun>(Range(0, 18), "en-US", CHAR_WIDTH);
+ builder.addCustomRun<ConstantRun>(Range(18, textBuf.size()), "fr-FR", CHAR_WIDTH);
+ std::unique_ptr<MeasuredText> measured = builder.build(
+ textBuf, true /* compute hyphenation */, false /* compute full layout */);
+
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ // TODO: Fix this. Prefer not to break inside URL.
+ {"This is an url: http://a", 24 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ {".b", 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ };
+ // clang-format on
+
+ auto actual = doLineBreak(textBuf, *measured, HIGH_QUALITY, NO_HYPHENATION, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ actual = doLineBreak(textBuf, *measured, HIGH_QUALITY, NORMAL_HYPHENATION, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+
+ // clang-format off
+ expect = {
+ {"This is an url: ", 15 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ {"http://a.b", 10 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ };
+ // clang-format on
+
+ actual = doLineBreak(textBuf, *measured, BALANCED, NO_HYPHENATION, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ actual = doLineBreak(textBuf, *measured, BALANCED, NORMAL_HYPHENATION, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ const auto textBuf = utf8ToUtf16("This is an email: a@example.com");
+ MeasuredTextBuilder builder;
+ builder.addCustomRun<ConstantRun>(Range(0, 18), "en-US", CHAR_WIDTH);
+ builder.addCustomRun<ConstantRun>(Range(18, textBuf.size()), "fr-FR", CHAR_WIDTH);
+ std::unique_ptr<MeasuredText> measured = builder.build(
+ textBuf, true /* compute hyphenation */, false /* compute full layout */);
+
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ {"This is an email: ", 17 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ {"a@example.com", 13 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ };
+ // clang-format on
+
+ auto actual = doLineBreak(textBuf, *measured, HIGH_QUALITY, NO_HYPHENATION, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ actual = doLineBreak(textBuf, *measured, HIGH_QUALITY, NORMAL_HYPHENATION, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ actual = doLineBreak(textBuf, *measured, BALANCED, NO_HYPHENATION, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ actual = doLineBreak(textBuf, *measured, BALANCED, NORMAL_HYPHENATION, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+}
+
+} // namespace
+} // namespace minikin
diff --git a/tests/unittest/SparseBitSetTest.cpp b/tests/unittest/SparseBitSetTest.cpp
index 39c9e1b..03a14d9 100644
--- a/tests/unittest/SparseBitSetTest.cpp
+++ b/tests/unittest/SparseBitSetTest.cpp
@@ -14,10 +14,11 @@
* limitations under the License.
*/
+#include "minikin/SparseBitSet.h"
+
#include <random>
#include <gtest/gtest.h>
-#include <minikin/SparseBitSet.h>
namespace minikin {
@@ -27,7 +28,7 @@
std::mt19937 mt; // Fix seeds to be able to reproduce the result.
std::uniform_int_distribution<uint16_t> distribution(1, 512);
- std::vector<uint32_t> range { distribution(mt) };
+ std::vector<uint32_t> range{distribution(mt)};
for (size_t i = 1; i < kTestRangeNum * 2; ++i) {
range.push_back((range.back() - 1) + distribution(mt));
}
diff --git a/tests/unittest/StringPieceTest.cpp b/tests/unittest/StringPieceTest.cpp
new file mode 100644
index 0000000..f70fd3a
--- /dev/null
+++ b/tests/unittest/StringPieceTest.cpp
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "StringPiece.h"
+
+#include <gtest/gtest.h>
+
+namespace minikin {
+
+TEST(StringPieceTest, basics) {
+ {
+ StringPiece s(nullptr);
+ EXPECT_EQ(nullptr, s.data());
+ EXPECT_EQ(0u, s.size());
+ EXPECT_TRUE(s.empty());
+ }
+ {
+ StringPiece s("");
+ EXPECT_NE(nullptr, s.data());
+ EXPECT_EQ(0u, s.size());
+ EXPECT_TRUE(s.empty());
+ }
+ {
+ StringPiece s("", 0);
+ EXPECT_NE(nullptr, s.data());
+ EXPECT_EQ(0u, s.size());
+ EXPECT_TRUE(s.empty());
+ }
+ {
+ StringPiece s("abcde");
+ EXPECT_NE(nullptr, s.data());
+ EXPECT_EQ(5u, s.size());
+ EXPECT_FALSE(s.empty());
+ EXPECT_EQ("abcde", s);
+ EXPECT_NE("abc", s);
+ }
+ {
+ StringPiece s("abcde", 5);
+ EXPECT_NE(nullptr, s.data());
+ EXPECT_EQ(5u, s.size());
+ EXPECT_FALSE(s.empty());
+ EXPECT_EQ("abcde", s);
+ EXPECT_NE("abc", s);
+ }
+ {
+ StringPiece s("abcde", 3);
+ EXPECT_NE(nullptr, s.data());
+ EXPECT_EQ(3u, s.size());
+ EXPECT_FALSE(s.empty());
+ EXPECT_EQ("abc", s);
+ EXPECT_NE("abcde", s);
+ }
+ {
+ const char* kText = "abcde";
+ StringPiece s(kText + 2, 3);
+ EXPECT_NE(nullptr, s.data());
+ EXPECT_EQ(3u, s.size());
+ EXPECT_FALSE(s.empty());
+ EXPECT_EQ("cde", s);
+ EXPECT_NE("abcde", s);
+ }
+ {
+ const char* kText = "abcde";
+ StringPiece s(kText + 2);
+ EXPECT_NE(nullptr, s.data());
+ EXPECT_EQ(3u, s.size());
+ EXPECT_FALSE(s.empty());
+ EXPECT_EQ("cde", s);
+ EXPECT_NE("abcde", s);
+ }
+}
+
+TEST(StringPieceTest, substr) {
+ StringPiece s("abcde");
+ EXPECT_EQ("", s.substr(0, 0));
+ EXPECT_EQ("a", s.substr(0, 1));
+ EXPECT_EQ("abc", s.substr(0, 3));
+ EXPECT_EQ("cde", s.substr(2, 3));
+ EXPECT_EQ("", s.substr(2, 0));
+ EXPECT_EQ("", s.substr(5, 0));
+}
+
+TEST(StringPieceTest, find) {
+ StringPiece s("mississippi");
+ EXPECT_EQ(1u, s.find(0, 'i'));
+ EXPECT_EQ(1u, s.find(1, 'i'));
+ EXPECT_EQ(4u, s.find(2, 'i'));
+ EXPECT_EQ(4u, s.find(3, 'i'));
+ EXPECT_EQ(4u, s.find(4, 'i'));
+ EXPECT_EQ(7u, s.find(5, 'i'));
+ EXPECT_EQ(7u, s.find(6, 'i'));
+ EXPECT_EQ(7u, s.find(7, 'i'));
+ EXPECT_EQ(10u, s.find(8, 'i'));
+ EXPECT_EQ(10u, s.find(9, 'i'));
+ EXPECT_EQ(10u, s.find(10, 'i'));
+ EXPECT_EQ(11u, s.find(11, 'i'));
+
+ EXPECT_EQ(11u, s.find(12, 'i')); // search index is out of bounds.
+}
+
+TEST(StringPieceTest, find_empty) {
+ StringPiece s("");
+ EXPECT_EQ(0u, s.find(0, 'a'));
+}
+
+TEST(SplitIteratorTest, split) {
+ {
+ StringPiece s("");
+ SplitIterator it(s, ',');
+ EXPECT_FALSE(it.hasNext());
+ }
+ {
+ StringPiece s("abcde");
+ SplitIterator it(s, ',');
+ EXPECT_TRUE(it.hasNext());
+ EXPECT_EQ("abcde", it.next());
+ EXPECT_FALSE(it.hasNext());
+ }
+ {
+ StringPiece s("a,bb,ccc,dddd,eeeee");
+ SplitIterator it(s, ',');
+ EXPECT_TRUE(it.hasNext());
+ EXPECT_EQ("a", it.next());
+ EXPECT_TRUE(it.hasNext());
+ EXPECT_EQ("bb", it.next());
+ EXPECT_TRUE(it.hasNext());
+ EXPECT_EQ("ccc", it.next());
+ EXPECT_TRUE(it.hasNext());
+ EXPECT_EQ("dddd", it.next());
+ EXPECT_TRUE(it.hasNext());
+ EXPECT_EQ("eeeee", it.next());
+ EXPECT_FALSE(it.hasNext());
+ }
+ {
+ StringPiece s(",,,,");
+ SplitIterator it(s, ',');
+ EXPECT_TRUE(it.hasNext());
+ EXPECT_EQ("", it.next());
+ EXPECT_TRUE(it.hasNext());
+ EXPECT_EQ("", it.next());
+ EXPECT_TRUE(it.hasNext());
+ EXPECT_EQ("", it.next());
+ EXPECT_TRUE(it.hasNext());
+ EXPECT_EQ("", it.next());
+ EXPECT_TRUE(it.hasNext());
+ EXPECT_EQ("", it.next());
+ EXPECT_FALSE(it.hasNext());
+ }
+ {
+ StringPiece s(",a,,b,");
+ SplitIterator it(s, ',');
+ EXPECT_TRUE(it.hasNext());
+ EXPECT_EQ("", it.next());
+ EXPECT_TRUE(it.hasNext());
+ EXPECT_EQ("a", it.next());
+ EXPECT_TRUE(it.hasNext());
+ EXPECT_EQ("", it.next());
+ EXPECT_TRUE(it.hasNext());
+ EXPECT_EQ("b", it.next());
+ EXPECT_TRUE(it.hasNext());
+ EXPECT_EQ("", it.next());
+ EXPECT_FALSE(it.hasNext());
+ }
+}
+
+} // namespace minikin
diff --git a/libs/minikin/MinikinFont.cpp b/tests/unittest/TestMain.cpp
similarity index 66%
rename from libs/minikin/MinikinFont.cpp
rename to tests/unittest/TestMain.cpp
index 6bf6a4a..6680fbd 100644
--- a/libs/minikin/MinikinFont.cpp
+++ b/tests/unittest/TestMain.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,15 +14,12 @@
* limitations under the License.
*/
-#include <minikin/MinikinFont.h>
-#include "HbFontCache.h"
-#include "MinikinInternal.h"
+#include "ICUEnvironment.h"
-namespace minikin {
+#include <gtest/gtest.h>
-MinikinFont::~MinikinFont() {
- android::AutoMutex _l(gMinikinLock);
- purgeHbFontLocked(this);
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ ::testing::AddGlobalTestEnvironment(new minikin::ICUEnvironment);
+ return RUN_ALL_TESTS();
}
-
-} // namespace minikin
diff --git a/tests/unittest/UnicodeUtilsTest.cpp b/tests/unittest/UnicodeUtilsTest.cpp
index 9932723..9bdd07b 100644
--- a/tests/unittest/UnicodeUtilsTest.cpp
+++ b/tests/unittest/UnicodeUtilsTest.cpp
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-#include <gtest/gtest.h>
-
#include "UnicodeUtils.h"
+#include <gtest/gtest.h>
+
namespace minikin {
TEST(UnicodeUtils, parse) {
@@ -34,4 +34,4 @@
EXPECT_EQ(buf[3], 'a');
}
-} // namespace minikin
+} // namespace minikin
diff --git a/tests/unittest/WordBreakerTests.cpp b/tests/unittest/WordBreakerTests.cpp
index 13e0420..394c64e 100644
--- a/tests/unittest/WordBreakerTests.cpp
+++ b/tests/unittest/WordBreakerTests.cpp
@@ -14,18 +14,17 @@
* limitations under the License.
*/
-#define LOG_TAG "Minikin"
+#include "WordBreaker.h"
-#include <android/log.h>
+#include <cstdio>
+
#include <gtest/gtest.h>
-
-#include "ICUTestBase.h"
-#include "UnicodeUtils.h"
-#include <minikin/WordBreaker.h>
#include <unicode/locid.h>
#include <unicode/uclean.h>
#include <unicode/udata.h>
+#include "UnicodeUtils.h"
+
#ifndef NELEM
#define NELEM(x) ((sizeof(x) / sizeof((x)[0])))
#endif
@@ -34,100 +33,95 @@
namespace minikin {
-typedef ICUTestBase WordBreakerTest;
-
-TEST_F(WordBreakerTest, basic) {
- uint16_t buf[] = {'h', 'e', 'l', 'l' ,'o', ' ', 'w', 'o', 'r', 'l', 'd'};
+TEST(WordBreakerTest, basic) {
+ uint16_t buf[] = {'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd'};
WordBreaker breaker;
- breaker.setLocale(icu::Locale::getUS());
breaker.setText(buf, NELEM(buf));
EXPECT_EQ(0, breaker.current());
- EXPECT_EQ(6, breaker.next()); // after "hello "
- EXPECT_EQ(0, breaker.wordStart()); // "hello"
+ EXPECT_EQ(6, breaker.followingWithLocale(Locale("en-US"), 0)); // after "hello "
+ EXPECT_EQ(0, breaker.wordStart()); // "hello"
EXPECT_EQ(5, breaker.wordEnd());
EXPECT_EQ(0, breaker.breakBadness());
EXPECT_EQ(6, breaker.current());
EXPECT_EQ((ssize_t)NELEM(buf), breaker.next()); // end
- EXPECT_EQ(6, breaker.wordStart()); // "world"
+ EXPECT_EQ(6, breaker.wordStart()); // "world"
EXPECT_EQ(11, breaker.wordEnd());
EXPECT_EQ(0, breaker.breakBadness());
EXPECT_EQ(11, breaker.current());
}
-TEST_F(WordBreakerTest, softHyphen) {
- uint16_t buf[] = {'h', 'e', 'l', 0x00AD, 'l' ,'o', ' ', 'w', 'o', 'r', 'l', 'd'};
+TEST(WordBreakerTest, softHyphen) {
+ uint16_t buf[] = {'h', 'e', 'l', 0x00AD, 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd'};
WordBreaker breaker;
- breaker.setLocale(icu::Locale::getUS());
breaker.setText(buf, NELEM(buf));
EXPECT_EQ(0, breaker.current());
- EXPECT_EQ(7, breaker.next()); // after "hel{SOFT HYPHEN}lo "
+ // after "hel{SOFT HYPHEN}lo "
+ EXPECT_EQ(7, breaker.followingWithLocale(Locale("en-US"), 0));
EXPECT_EQ(0, breaker.wordStart()); // "hel{SOFT HYPHEN}lo"
EXPECT_EQ(6, breaker.wordEnd());
EXPECT_EQ(0, breaker.breakBadness());
EXPECT_EQ((ssize_t)NELEM(buf), breaker.next()); // end
- EXPECT_EQ(7, breaker.wordStart()); // "world"
+ EXPECT_EQ(7, breaker.wordStart()); // "world"
EXPECT_EQ(12, breaker.wordEnd());
EXPECT_EQ(0, breaker.breakBadness());
}
-TEST_F(WordBreakerTest, hardHyphen) {
+TEST(WordBreakerTest, hardHyphen) {
// Hyphens should not allow breaks anymore.
uint16_t buf[] = {'s', 'u', 'g', 'a', 'r', '-', 'f', 'r', 'e', 'e'};
WordBreaker breaker;
- breaker.setLocale(icu::Locale::getUS());
breaker.setText(buf, NELEM(buf));
EXPECT_EQ(0, breaker.current());
- EXPECT_EQ((ssize_t)NELEM(buf), breaker.next());
+ EXPECT_EQ((ssize_t)NELEM(buf), breaker.followingWithLocale(Locale("en-US"), 0));
EXPECT_EQ(0, breaker.wordStart());
EXPECT_EQ((ssize_t)NELEM(buf), breaker.wordEnd());
EXPECT_EQ(0, breaker.breakBadness());
}
-TEST_F(WordBreakerTest, postfixAndPrefix) {
- uint16_t buf[] = {'U', 'S', 0x00A2, ' ', 'J', 'P', 0x00A5}; // US¢ JP¥
+TEST(WordBreakerTest, postfixAndPrefix) {
+ uint16_t buf[] = {'U', 'S', 0x00A2, ' ', 'J', 'P', 0x00A5}; // US¢ JP¥
WordBreaker breaker;
- breaker.setLocale(icu::Locale::getUS());
breaker.setText(buf, NELEM(buf));
EXPECT_EQ(0, breaker.current());
- EXPECT_EQ(4, breaker.next()); // after CENT SIGN
- EXPECT_EQ(0, breaker.wordStart()); // "US¢"
+ EXPECT_EQ(4, breaker.followingWithLocale(Locale("en-US"), 0)); // after CENT SIGN
+ EXPECT_EQ(0, breaker.wordStart()); // "US¢"
EXPECT_EQ(3, breaker.wordEnd());
EXPECT_EQ((ssize_t)NELEM(buf), breaker.next()); // end of string
- EXPECT_EQ(4, breaker.wordStart()); // "JP¥"
+ EXPECT_EQ(4, breaker.wordStart()); // "JP¥"
EXPECT_EQ((ssize_t)NELEM(buf), breaker.wordEnd());
}
-TEST_F(WordBreakerTest, myanmarKinzi) {
+TEST(WordBreakerTest, myanmarKinzi) {
uint16_t buf[] = {0x1004, 0x103A, 0x1039, 0x1000, 0x102C}; // NGA, ASAT, VIRAMA, KA, UU
WordBreaker breaker;
icu::Locale burmese("my");
- breaker.setLocale(burmese);
breaker.setText(buf, NELEM(buf));
EXPECT_EQ(0, breaker.current());
- EXPECT_EQ((ssize_t)NELEM(buf), breaker.next()); // end of string
+ // end of string
+ EXPECT_EQ((ssize_t)NELEM(buf), breaker.followingWithLocale(Locale("en-US"), 0));
EXPECT_EQ(0, breaker.wordStart());
EXPECT_EQ((ssize_t)NELEM(buf), breaker.wordEnd());
}
-TEST_F(WordBreakerTest, zwjEmojiSequences) {
+TEST(WordBreakerTest, zwjEmojiSequences) {
uint16_t buf[] = {
- // man + zwj + heart + zwj + man
- UTF16(0x1F468), 0x200D, 0x2764, 0x200D, UTF16(0x1F468),
- // woman + zwj + heart + zwj + kiss mark + zwj + woman
- UTF16(0x1F469), 0x200D, 0x2764, 0x200D, UTF16(0x1F48B), 0x200D, UTF16(0x1F469),
- // eye + zwj + left speech bubble
- UTF16(0x1F441), 0x200D, UTF16(0x1F5E8),
- // CAT FACE + zwj + BUST IN SILHOUETTE
- UTF16(0x1F431), 0x200D, UTF16(0x1F464),
+ // man + zwj + heart + zwj + man
+ UTF16(0x1F468), 0x200D, 0x2764, 0x200D, UTF16(0x1F468),
+ // woman + zwj + heart + zwj + kiss mark + zwj + woman
+ UTF16(0x1F469), 0x200D, 0x2764, 0x200D, UTF16(0x1F48B), 0x200D, UTF16(0x1F469),
+ // eye + zwj + left speech bubble
+ UTF16(0x1F441), 0x200D, UTF16(0x1F5E8),
+ // CAT FACE + zwj + BUST IN SILHOUETTE
+ UTF16(0x1F431), 0x200D, UTF16(0x1F464),
};
WordBreaker breaker;
- breaker.setLocale(icu::Locale::getUS());
breaker.setText(buf, NELEM(buf));
EXPECT_EQ(0, breaker.current());
- EXPECT_EQ(7, breaker.next()); // after man + zwj + heart + zwj + man
+ // after man + zwj + heart + zwj + man
+ EXPECT_EQ(7, breaker.followingWithLocale(Locale("en-US"), 0));
EXPECT_EQ(0, breaker.wordStart());
EXPECT_EQ(7, breaker.wordEnd());
EXPECT_EQ(17, breaker.next()); // after woman + zwj + heart + zwj + woman
@@ -141,16 +135,17 @@
EXPECT_EQ(27, breaker.wordEnd());
}
-TEST_F(WordBreakerTest, emojiWithModifier) {
+TEST(WordBreakerTest, emojiWithModifier) {
uint16_t buf[] = {
- UTF16(0x1F466), UTF16(0x1F3FB), // boy + type 1-2 fitzpatrick modifier
- 0x270C, 0xFE0F, UTF16(0x1F3FF) // victory hand + emoji style + type 6 fitzpatrick modifier
+ UTF16(0x1F466), UTF16(0x1F3FB), // boy + type 1-2 fitzpatrick modifier
+ 0x270C, 0xFE0F,
+ UTF16(0x1F3FF) // victory hand + emoji style + type 6 fitzpatrick modifier
};
WordBreaker breaker;
- breaker.setLocale(icu::Locale::getUS());
breaker.setText(buf, NELEM(buf));
EXPECT_EQ(0, breaker.current());
- EXPECT_EQ(4, breaker.next()); // after boy + type 1-2 fitzpatrick modifier
+ // after boy + type 1-2 fitzpatrick modifier
+ EXPECT_EQ(4, breaker.followingWithLocale(Locale("en-US"), 0));
EXPECT_EQ(0, breaker.wordStart());
EXPECT_EQ(4, breaker.wordEnd());
EXPECT_EQ((ssize_t)NELEM(buf), breaker.next()); // end
@@ -158,23 +153,22 @@
EXPECT_EQ(8, breaker.wordEnd());
}
-TEST_F(WordBreakerTest, unicode10Emoji) {
+TEST(WordBreakerTest, unicode10Emoji) {
// Should break between emojis.
uint16_t buf[] = {
- // SLED + SLED
- UTF16(0x1F6F7), UTF16(0x1F6F7),
- // SLED + VS15 + SLED
- UTF16(0x1F6F7), 0xFE0E, UTF16(0x1F6F7),
- // WHITE SMILING FACE + SLED
- 0x263A, UTF16(0x1F6F7),
- // WHITE SMILING FACE + VS16 + SLED
- 0x263A, 0xFE0F, UTF16(0x1F6F7),
+ // SLED + SLED
+ UTF16(0x1F6F7), UTF16(0x1F6F7),
+ // SLED + VS15 + SLED
+ UTF16(0x1F6F7), 0xFE0E, UTF16(0x1F6F7),
+ // WHITE SMILING FACE + SLED
+ 0x263A, UTF16(0x1F6F7),
+ // WHITE SMILING FACE + VS16 + SLED
+ 0x263A, 0xFE0F, UTF16(0x1F6F7),
};
WordBreaker breaker;
- breaker.setLocale(icu::Locale::getEnglish());
breaker.setText(buf, NELEM(buf));
EXPECT_EQ(0, breaker.current());
- EXPECT_EQ(2, breaker.next());
+ EXPECT_EQ(2, breaker.followingWithLocale(Locale("en"), 0));
EXPECT_EQ(0, breaker.wordStart());
EXPECT_EQ(2, breaker.wordEnd());
@@ -207,7 +201,7 @@
EXPECT_EQ(16, breaker.wordEnd());
}
-TEST_F(WordBreakerTest, flagsSequenceSingleFlag) {
+TEST(WordBreakerTest, flagsSequenceSingleFlag) {
const std::string kFlag = "U+1F3F4";
const std::string flags = kFlag + " " + kFlag;
@@ -219,10 +213,10 @@
ParseUnicode(buf, BUF_SIZE, flags.c_str(), &size, nullptr);
WordBreaker breaker;
- breaker.setLocale(icu::Locale::getUS());
breaker.setText(buf, size);
EXPECT_EQ(0, breaker.current());
- EXPECT_EQ(kFlagLength, breaker.next()); // end of the first flag
+ // end of the first flag
+ EXPECT_EQ(kFlagLength, breaker.followingWithLocale(Locale("en-US"), 0));
EXPECT_EQ(0, breaker.wordStart());
EXPECT_EQ(kFlagLength, breaker.wordEnd());
EXPECT_EQ(static_cast<ssize_t>(size), breaker.next());
@@ -230,7 +224,7 @@
EXPECT_EQ(kFlagLength * 2, breaker.wordEnd());
}
-TEST_F(WordBreakerTest, flagsSequence) {
+TEST(WordBreakerTest, flagsSequence) {
// U+1F3F4 U+E0067 U+E0062 U+E0073 U+E0063 U+E0074 U+E007F is emoji tag sequence for the flag
// of Scotland.
const std::string kFlagSequence = "U+1F3F4 U+E0067 U+E0062 U+E0073 U+E0063 U+E0074 U+E007F";
@@ -244,10 +238,10 @@
ParseUnicode(buf, BUF_SIZE, flagSequence.c_str(), &size, nullptr);
WordBreaker breaker;
- breaker.setLocale(icu::Locale::getUS());
breaker.setText(buf, size);
EXPECT_EQ(0, breaker.current());
- EXPECT_EQ(kFlagLength, breaker.next()); // end of the first flag sequence
+ // end of the first flag sequence
+ EXPECT_EQ(kFlagLength, breaker.followingWithLocale(Locale("en-US"), 0));
EXPECT_EQ(0, breaker.wordStart());
EXPECT_EQ(kFlagLength, breaker.wordEnd());
EXPECT_EQ(static_cast<ssize_t>(size), breaker.next());
@@ -255,50 +249,47 @@
EXPECT_EQ(kFlagLength * 2, breaker.wordEnd());
}
-TEST_F(WordBreakerTest, punct) {
- uint16_t buf[] = {0x00A1, 0x00A1, 'h', 'e', 'l', 'l' ,'o', ',', ' ', 'w', 'o', 'r', 'l', 'd',
- '!', '!'};
+TEST(WordBreakerTest, punct) {
+ uint16_t buf[] = {0x00A1, 0x00A1, 'h', 'e', 'l', 'l', 'o', ',',
+ ' ', 'w', 'o', 'r', 'l', 'd', '!', '!'};
WordBreaker breaker;
- breaker.setLocale(icu::Locale::getUS());
breaker.setText(buf, NELEM(buf));
EXPECT_EQ(0, breaker.current());
- EXPECT_EQ(9, breaker.next()); // after "¡¡hello, "
- EXPECT_EQ(2, breaker.wordStart()); // "hello"
+ EXPECT_EQ(9, breaker.followingWithLocale(Locale("en-US"), 0)); // after "¡¡hello, "
+ EXPECT_EQ(2, breaker.wordStart()); // "hello"
EXPECT_EQ(7, breaker.wordEnd());
EXPECT_EQ(0, breaker.breakBadness());
EXPECT_EQ((ssize_t)NELEM(buf), breaker.next()); // end
- EXPECT_EQ(9, breaker.wordStart()); // "world"
+ EXPECT_EQ(9, breaker.wordStart()); // "world"
EXPECT_EQ(14, breaker.wordEnd());
EXPECT_EQ(0, breaker.breakBadness());
}
-TEST_F(WordBreakerTest, email) {
- uint16_t buf[] = {'f', 'o', 'o', '@', 'e', 'x', 'a', 'm', 'p', 'l', 'e', '.', 'c', 'o', 'm',
- ' ', 'x'};
+TEST(WordBreakerTest, email) {
+ uint16_t buf[] = {'f', 'o', 'o', '@', 'e', 'x', 'a', 'm', 'p',
+ 'l', 'e', '.', 'c', 'o', 'm', ' ', 'x'};
WordBreaker breaker;
- breaker.setLocale(icu::Locale::getUS());
breaker.setText(buf, NELEM(buf));
EXPECT_EQ(0, breaker.current());
- EXPECT_EQ(11, breaker.next()); // after "foo@example"
+ EXPECT_EQ(11, breaker.followingWithLocale(Locale("en-US"), 0)); // after "foo@example"
EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());
EXPECT_EQ(1, breaker.breakBadness());
EXPECT_EQ(16, breaker.next()); // after ".com "
EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());
EXPECT_EQ(0, breaker.breakBadness());
EXPECT_EQ((ssize_t)NELEM(buf), breaker.next()); // end
- EXPECT_EQ(16, breaker.wordStart()); // "x"
+ EXPECT_EQ(16, breaker.wordStart()); // "x"
EXPECT_EQ(17, breaker.wordEnd());
EXPECT_EQ(0, breaker.breakBadness());
}
-TEST_F(WordBreakerTest, mailto) {
- uint16_t buf[] = {'m', 'a', 'i', 'l', 't', 'o', ':', 'f', 'o', 'o', '@',
- 'e', 'x', 'a', 'm', 'p', 'l', 'e', '.', 'c', 'o', 'm', ' ', 'x'};
+TEST(WordBreakerTest, mailto) {
+ uint16_t buf[] = {'m', 'a', 'i', 'l', 't', 'o', ':', 'f', 'o', 'o', '@', 'e',
+ 'x', 'a', 'm', 'p', 'l', 'e', '.', 'c', 'o', 'm', ' ', 'x'};
WordBreaker breaker;
- breaker.setLocale(icu::Locale::getUS());
breaker.setText(buf, NELEM(buf));
EXPECT_EQ(0, breaker.current());
- EXPECT_EQ(7, breaker.next()); // after "mailto:"
+ EXPECT_EQ(7, breaker.followingWithLocale(Locale("en-US"), 0)); // after "mailto:"
EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());
EXPECT_EQ(1, breaker.breakBadness());
EXPECT_EQ(18, breaker.next()); // after "foo@example"
@@ -308,78 +299,74 @@
EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());
EXPECT_EQ(0, breaker.breakBadness());
EXPECT_EQ((ssize_t)NELEM(buf), breaker.next()); // end
- EXPECT_EQ(23, breaker.wordStart()); // "x"
+ EXPECT_EQ(23, breaker.wordStart()); // "x"
EXPECT_EQ(24, breaker.wordEnd());
EXPECT_EQ(0, breaker.breakBadness());
}
// The current logic always places a line break after a detected email address or URL
// and an immediately following non-ASCII character.
-TEST_F(WordBreakerTest, emailNonAscii) {
- uint16_t buf[] = {'f', 'o', 'o', '@', 'e', 'x', 'a', 'm', 'p', 'l', 'e', '.', 'c', 'o', 'm',
- 0x4E00};
+TEST(WordBreakerTest, emailNonAscii) {
+ uint16_t buf[] = {'f', 'o', 'o', '@', 'e', 'x', 'a', 'm',
+ 'p', 'l', 'e', '.', 'c', 'o', 'm', 0x4E00};
WordBreaker breaker;
- breaker.setLocale(icu::Locale::getUS());
breaker.setText(buf, NELEM(buf));
EXPECT_EQ(0, breaker.current());
- EXPECT_EQ(11, breaker.next()); // after "foo@example"
+ EXPECT_EQ(11, breaker.followingWithLocale(Locale("en-US"), 0)); // after "foo@example"
EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());
EXPECT_EQ(1, breaker.breakBadness());
EXPECT_EQ(15, breaker.next()); // after ".com"
EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());
EXPECT_EQ(0, breaker.breakBadness());
EXPECT_EQ((ssize_t)NELEM(buf), breaker.next()); // end
- EXPECT_EQ(15, breaker.wordStart()); // "一"
+ EXPECT_EQ(15, breaker.wordStart()); // "一"
EXPECT_EQ(16, breaker.wordEnd());
EXPECT_EQ(0, breaker.breakBadness());
}
-TEST_F(WordBreakerTest, emailCombining) {
- uint16_t buf[] = {'f', 'o', 'o', '@', 'e', 'x', 'a', 'm', 'p', 'l', 'e', '.', 'c', 'o', 'm',
- 0x0303, ' ', 'x'};
+TEST(WordBreakerTest, emailCombining) {
+ uint16_t buf[] = {'f', 'o', 'o', '@', 'e', 'x', 'a', 'm', 'p',
+ 'l', 'e', '.', 'c', 'o', 'm', 0x0303, ' ', 'x'};
WordBreaker breaker;
- breaker.setLocale(icu::Locale::getUS());
breaker.setText(buf, NELEM(buf));
EXPECT_EQ(0, breaker.current());
- EXPECT_EQ(11, breaker.next()); // after "foo@example"
+ EXPECT_EQ(11, breaker.followingWithLocale(Locale("en-US"), 0)); // after "foo@example"
EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());
EXPECT_EQ(1, breaker.breakBadness());
EXPECT_EQ(17, breaker.next()); // after ".com̃ "
EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());
EXPECT_EQ(0, breaker.breakBadness());
EXPECT_EQ((ssize_t)NELEM(buf), breaker.next()); // end
- EXPECT_EQ(17, breaker.wordStart()); // "x"
+ EXPECT_EQ(17, breaker.wordStart()); // "x"
EXPECT_EQ(18, breaker.wordEnd());
EXPECT_EQ(0, breaker.breakBadness());
}
-TEST_F(WordBreakerTest, lonelyAt) {
+TEST(WordBreakerTest, lonelyAt) {
uint16_t buf[] = {'a', ' ', '@', ' ', 'b'};
WordBreaker breaker;
- breaker.setLocale(icu::Locale::getUS());
breaker.setText(buf, NELEM(buf));
EXPECT_EQ(0, breaker.current());
- EXPECT_EQ(2, breaker.next()); // after "a "
- EXPECT_EQ(0, breaker.wordStart()); // "a"
+ EXPECT_EQ(2, breaker.followingWithLocale(Locale("en-US"), 0)); // after "a "
+ EXPECT_EQ(0, breaker.wordStart()); // "a"
EXPECT_EQ(1, breaker.wordEnd());
EXPECT_EQ(0, breaker.breakBadness());
EXPECT_EQ(4, breaker.next()); // after "@ "
EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());
EXPECT_EQ(0, breaker.breakBadness());
EXPECT_EQ((ssize_t)NELEM(buf), breaker.next()); // end
- EXPECT_EQ(4, breaker.wordStart()); // "b"
+ EXPECT_EQ(4, breaker.wordStart()); // "b"
EXPECT_EQ(5, breaker.wordEnd());
EXPECT_EQ(0, breaker.breakBadness());
}
-TEST_F(WordBreakerTest, url) {
- uint16_t buf[] = {'h', 't', 't', 'p', ':', '/', '/', 'e', 'x', 'a', 'm', 'p', 'l', 'e',
- '.', 'c', 'o', 'm', ' ', 'x'};
+TEST(WordBreakerTest, url) {
+ uint16_t buf[] = {'h', 't', 't', 'p', ':', '/', '/', 'e', 'x', 'a',
+ 'm', 'p', 'l', 'e', '.', 'c', 'o', 'm', ' ', 'x'};
WordBreaker breaker;
- breaker.setLocale(icu::Locale::getUS());
breaker.setText(buf, NELEM(buf));
EXPECT_EQ(0, breaker.current());
- EXPECT_EQ(5, breaker.next()); // after "http:"
+ EXPECT_EQ(5, breaker.followingWithLocale(Locale("en-US"), 0)); // after "http:"
EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());
EXPECT_EQ(1, breaker.breakBadness());
EXPECT_EQ(7, breaker.next()); // after "//"
@@ -392,20 +379,20 @@
EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());
EXPECT_EQ(0, breaker.breakBadness());
EXPECT_EQ((ssize_t)NELEM(buf), breaker.next()); // end
- EXPECT_EQ(19, breaker.wordStart()); // "x"
+ EXPECT_EQ(19, breaker.wordStart()); // "x"
EXPECT_EQ(20, breaker.wordEnd());
EXPECT_EQ(0, breaker.breakBadness());
}
// Breaks according to section 14.12 of Chicago Manual of Style, *URLs or DOIs and line breaks*
-TEST_F(WordBreakerTest, urlBreakChars) {
- uint16_t buf[] = {'h', 't', 't', 'p', ':', '/', '/', 'a', '.', 'b', '/', '~', 'c', ',', 'd',
- '-', 'e', '?', 'f', '=', 'g', '&', 'h', '#', 'i', '%', 'j', '_', 'k', '/', 'l'};
+TEST(WordBreakerTest, urlBreakChars) {
+ uint16_t buf[] = {'h', 't', 't', 'p', ':', '/', '/', 'a', '.', 'b', '/',
+ '~', 'c', ',', 'd', '-', 'e', '?', 'f', '=', 'g', '&',
+ 'h', '#', 'i', '%', 'j', '_', 'k', '/', 'l'};
WordBreaker breaker;
- breaker.setLocale(icu::Locale::getUS());
breaker.setText(buf, NELEM(buf));
EXPECT_EQ(0, breaker.current());
- EXPECT_EQ(5, breaker.next()); // after "http:"
+ EXPECT_EQ(5, breaker.followingWithLocale(Locale("en-US"), 0)); // after "http:"
EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());
EXPECT_EQ(1, breaker.breakBadness());
EXPECT_EQ(7, breaker.next()); // after "//"
@@ -458,13 +445,12 @@
EXPECT_EQ(0, breaker.breakBadness());
}
-TEST_F(WordBreakerTest, urlNoHyphenBreak) {
+TEST(WordBreakerTest, urlNoHyphenBreak) {
uint16_t buf[] = {'h', 't', 't', 'p', ':', '/', '/', 'a', '-', '/', 'b'};
WordBreaker breaker;
- breaker.setLocale(icu::Locale::getUS());
breaker.setText(buf, NELEM(buf));
EXPECT_EQ(0, breaker.current());
- EXPECT_EQ(5, breaker.next()); // after "http:"
+ EXPECT_EQ(5, breaker.followingWithLocale(Locale("en-US"), 0)); // after "http:"
EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());
EXPECT_EQ(7, breaker.next()); // after "//"
EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());
@@ -474,13 +460,12 @@
EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());
}
-TEST_F(WordBreakerTest, urlEndsWithSlash) {
+TEST(WordBreakerTest, urlEndsWithSlash) {
uint16_t buf[] = {'h', 't', 't', 'p', ':', '/', '/', 'a', '/'};
WordBreaker breaker;
- breaker.setLocale(icu::Locale::getUS());
breaker.setText(buf, NELEM(buf));
EXPECT_EQ(0, breaker.current());
- EXPECT_EQ(5, breaker.next()); // after "http:"
+ EXPECT_EQ(5, breaker.followingWithLocale(Locale("en-US"), 0)); // after "http:"
EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());
EXPECT_EQ(7, breaker.next()); // after "//"
EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());
@@ -490,14 +475,166 @@
EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());
}
-TEST_F(WordBreakerTest, emailStartsWithSlash) {
+TEST(WordBreakerTest, emailStartsWithSlash) {
uint16_t buf[] = {'/', 'a', '@', 'b'};
WordBreaker breaker;
- breaker.setLocale(icu::Locale::getUS());
breaker.setText(buf, NELEM(buf));
EXPECT_EQ(0, breaker.current());
- EXPECT_EQ((ssize_t)NELEM(buf), breaker.next()); // end
+ EXPECT_EQ((ssize_t)NELEM(buf), breaker.followingWithLocale(Locale("en-US"), 0)); // end
EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());
}
+TEST(WordBreakerTest, setLocaleInsideUrl) {
+ std::vector<uint16_t> buf = utf8ToUtf16("Hello http://abc/d.html World");
+ WordBreaker breaker;
+ breaker.setText(buf.data(), buf.size());
+ EXPECT_EQ(0, breaker.current());
+ EXPECT_EQ(6, breaker.followingWithLocale(Locale("en-US"), 0)); // after "Hello "
+ EXPECT_EQ(0, breaker.wordStart());
+ EXPECT_EQ(5, breaker.wordEnd());
+
+ EXPECT_EQ(6, breaker.current());
+ EXPECT_EQ(11, breaker.next()); // after "http:"
+
+ // Restart from middle point of the URL. It should return the same previous break point.
+ EXPECT_EQ(11, breaker.followingWithLocale(Locale("en-US"), 6)); // after "http:"
+ EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());
+
+ EXPECT_EQ(13, breaker.next()); // after "//"
+ EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());
+
+ // Restart from middle point of the URL. It should return the same previous break point.
+ EXPECT_EQ(13, breaker.followingWithLocale(Locale("en-US"), 12)); // after "//"
+ EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());
+ EXPECT_EQ(16, breaker.next()); // after "abc"
+ EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());
+ EXPECT_EQ(18, breaker.next()); // after "/d"
+ EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());
+ EXPECT_EQ(24, breaker.next()); // after ".html"
+ EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());
+
+ EXPECT_EQ(29, breaker.next()); // after "World"
+ EXPECT_EQ(24, breaker.wordStart());
+ EXPECT_EQ(29, breaker.wordEnd());
+}
+
+// b/68669534
+TEST(WordBreakerTest, spaceAfterSpace) {
+ const std::vector<uint16_t> SPACES = {
+ '\t', // TAB
+ 0x1680, // OGHAM SPACE MARK
+ 0x3000, // IDEOGRAPHIC SPACE
+ };
+
+ constexpr uint16_t CHAR_SPACE = 0x0020;
+
+ for (uint16_t sp : SPACES) {
+ char msg[64] = {};
+ snprintf(msg, sizeof(msg), "Test Space: U+%04X", sp);
+ SCOPED_TRACE(msg);
+
+ std::vector<uint16_t> buf = {'a', CHAR_SPACE, sp, 'b'};
+ WordBreaker breaker;
+ breaker.setText(buf.data(), buf.size());
+
+ EXPECT_EQ(0, breaker.current());
+ EXPECT_EQ(2, breaker.followingWithLocale(Locale("en-US"), 0)); // after "a "
+ EXPECT_EQ(0, breaker.wordStart());
+ EXPECT_EQ(1, breaker.wordEnd());
+
+ EXPECT_EQ(2, breaker.current());
+ EXPECT_EQ(3, breaker.next()); // after CHAR_SPACE character.
+ EXPECT_EQ(2, breaker.wordStart());
+ EXPECT_EQ(2, breaker.wordEnd());
+
+ EXPECT_EQ(3, breaker.current());
+ EXPECT_EQ(4, breaker.next()); // after sp character.
+ EXPECT_EQ(3, breaker.wordStart());
+ EXPECT_EQ(4, breaker.wordEnd());
+ }
+}
+
+class TestableICULineBreakerPoolImpl : public ICULineBreakerPoolImpl {
+public:
+ TestableICULineBreakerPoolImpl() : ICULineBreakerPoolImpl() {}
+
+ using ICULineBreakerPoolImpl::getPoolSize;
+ using ICULineBreakerPoolImpl::MAX_POOL_SIZE;
+};
+
+TEST(WordBreakerTest, LineBreakerPool_acquire_without_release) {
+ TestableICULineBreakerPoolImpl pool;
+
+ const Locale enUS("en-Latn-US");
+ const Locale frFR("fr-Latn-FR");
+
+ // All following three breakers must be the different instances.
+ ICULineBreakerPool::Slot enUSBreaker = pool.acquire(enUS);
+ ICULineBreakerPool::Slot enUSBreaker2 = pool.acquire(enUS);
+ ICULineBreakerPool::Slot frFRBreaker = pool.acquire(frFR);
+
+ EXPECT_NE(nullptr, enUSBreaker.breaker.get());
+ EXPECT_NE(nullptr, enUSBreaker2.breaker.get());
+ EXPECT_NE(nullptr, frFRBreaker.breaker.get());
+
+ EXPECT_NE(enUSBreaker.breaker.get(), enUSBreaker2.breaker.get());
+ EXPECT_NE(enUSBreaker.breaker.get(), frFRBreaker.breaker.get());
+ EXPECT_NE(enUSBreaker2.breaker.get(), frFRBreaker.breaker.get());
+
+ EXPECT_EQ(enUSBreaker.localeId, enUSBreaker2.localeId);
+ EXPECT_NE(enUSBreaker.localeId, frFRBreaker.localeId);
+ EXPECT_NE(enUSBreaker2.localeId, frFRBreaker.localeId);
+}
+
+TEST(WordBreakerTest, LineBreakerPool_acquire_with_release) {
+ TestableICULineBreakerPoolImpl pool;
+
+ const Locale enUS("en-Latn-US");
+ const Locale frFR("fr-Latn-FR");
+
+ // All following three breakers must be the different instances.
+ ICULineBreakerPool::Slot enUSBreaker = pool.acquire(enUS);
+
+ uint64_t enUSBreakerLocaleId = enUSBreaker.localeId;
+ icu::BreakIterator* enUSBreakerPtr = enUSBreaker.breaker.get();
+
+ pool.release(std::move(enUSBreaker));
+ EXPECT_EQ(nullptr, enUSBreaker.breaker.get());
+
+ // acquire must return a different instance if the locale is different.
+ ICULineBreakerPool::Slot frFRBreaker = pool.acquire(frFR);
+ EXPECT_NE(enUSBreakerPtr, frFRBreaker.breaker.get());
+ EXPECT_NE(enUSBreakerLocaleId, frFRBreaker.localeId);
+
+ // acquire must return the same instance as released before if the locale is the same.
+ ICULineBreakerPool::Slot enUSBreaker2 = pool.acquire(enUS);
+ EXPECT_EQ(enUSBreakerPtr, enUSBreaker2.breaker.get());
+ EXPECT_EQ(enUSBreakerLocaleId, enUSBreaker2.localeId);
+}
+
+TEST(WordBreakerTest, LineBreakerPool_exceeds_pool_size) {
+ const size_t MAX_POOL_SIZE = TestableICULineBreakerPoolImpl::MAX_POOL_SIZE;
+ TestableICULineBreakerPoolImpl pool;
+
+ const Locale enUS("en-Latn-US");
+
+ ICULineBreakerPool::Slot slots[MAX_POOL_SIZE * 2];
+
+ // Make pool full.
+ for (size_t i = 0; i < MAX_POOL_SIZE * 2; i++) {
+ slots[i] = pool.acquire(enUS);
+ EXPECT_EQ(0U, pool.getPoolSize());
+ }
+
+ for (size_t i = 0; i < MAX_POOL_SIZE; i++) {
+ pool.release(std::move(slots[i]));
+ EXPECT_EQ(i + 1, pool.getPoolSize());
+ }
+
+ for (size_t i = MAX_POOL_SIZE; i < MAX_POOL_SIZE * 2; i++) {
+ pool.release(std::move(slots[i]));
+ EXPECT_EQ(MAX_POOL_SIZE, pool.getPoolSize());
+ }
+}
+
} // namespace minikin
diff --git a/tests/util/Android.bp b/tests/util/Android.bp
index 50a2cd7..561643b 100644
--- a/tests/util/Android.bp
+++ b/tests/util/Android.bp
@@ -3,9 +3,11 @@
srcs: [
"FileUtils.cpp",
"FontTestUtils.cpp",
- "MinikinFontForTest.cpp",
+ "FreeTypeMinikinFontForTest.cpp",
+ "PathUtils.cpp",
"UnicodeUtils.cpp",
],
+ cflags: ["-Wall", "-Werror"],
export_include_dirs: ["."],
shared_libs: ["libxml2"],
static_libs: ["libminikin"],
diff --git a/tests/util/FileUtils.cpp b/tests/util/FileUtils.cpp
index 68cc45c..76a7b9a 100644
--- a/tests/util/FileUtils.cpp
+++ b/tests/util/FileUtils.cpp
@@ -14,14 +14,15 @@
* limitations under the License.
*/
-#include <cutils/log.h>
+#include "FileUtils.h"
-#include <stdio.h>
#include <sys/stat.h>
-
+#include <cstdio>
#include <string>
#include <vector>
+#include <cutils/log.h>
+
std::vector<uint8_t> readWholeFile(const std::string& filePath) {
FILE* fp = fopen(filePath.c_str(), "r");
LOG_ALWAYS_FATAL_IF(fp == nullptr);
diff --git a/tests/util/FileUtils.h b/tests/util/FileUtils.h
index 1e66d1b..3aabc0d 100644
--- a/tests/util/FileUtils.h
+++ b/tests/util/FileUtils.h
@@ -14,5 +14,7 @@
* limitations under the License.
*/
-std::vector<uint8_t> readWholeFile(const std::string& filePath);
+#include <string>
+#include <vector>
+std::vector<uint8_t> readWholeFile(const std::string& filePath);
diff --git a/tests/util/FontTestUtils.cpp b/tests/util/FontTestUtils.cpp
index 13360d4..8aaac3d 100644
--- a/tests/util/FontTestUtils.cpp
+++ b/tests/util/FontTestUtils.cpp
@@ -17,19 +17,38 @@
#define LOG_TAG "Minikin"
#include <libxml/tree.h>
+#include <log/log.h>
#include <unistd.h>
-#include <log/log.h>
+#include "minikin/FontCollection.h"
+#include "minikin/FontFamily.h"
+#include "minikin/LocaleList.h"
-#include "FontLanguage.h"
-#include "MinikinFontForTest.h"
-#include <minikin/FontCollection.h>
-#include <minikin/FontFamily.h>
+#include "FontTestUtils.h"
+#include "FreeTypeMinikinFontForTest.h"
+#include "LocaleListCache.h"
+#include "MinikinInternal.h"
namespace minikin {
-std::vector<std::shared_ptr<FontFamily>> getFontFamilies(const char* fontDir, const char* fontXml) {
- xmlDoc* doc = xmlReadFile(fontXml, NULL, 0);
+namespace {
+std::string xmlTrim(const std::string& in) {
+ if (in.empty()) {
+ return in;
+ }
+ const char XML_SPACES[] = "\u0020\u000D\u000A\u0009";
+ const size_t start = in.find_first_not_of(XML_SPACES); // inclusive
+ const size_t end = in.find_last_not_of(XML_SPACES); // inclusive
+ MINIKIN_ASSERT(start != std::string::npos, "Not a valid file name \"%s\"", in.c_str());
+ MINIKIN_ASSERT(end != std::string::npos, "Not a valid file name \"%s\"", in.c_str());
+ return in.substr(start, end - start + 1 /* +1 since end is inclusive */);
+}
+
+} // namespace
+
+std::vector<std::shared_ptr<FontFamily>> getFontFamilies(const std::string& fontDir,
+ const std::string& xmlPath) {
+ xmlDoc* doc = xmlReadFile(xmlPath.c_str(), NULL, 0);
xmlNode* familySet = xmlDocGetRootElement(doc);
std::vector<std::shared_ptr<FontFamily>> families;
@@ -39,12 +58,12 @@
}
xmlChar* variantXmlch = xmlGetProp(familyNode, (const xmlChar*)"variant");
- int variant = VARIANT_DEFAULT;
+ FontFamily::Variant variant = FontFamily::Variant::DEFAULT;
if (variantXmlch) {
if (xmlStrcmp(variantXmlch, (const xmlChar*)"elegant") == 0) {
- variant = VARIANT_ELEGANT;
+ variant = FontFamily::Variant::ELEGANT;
} else if (xmlStrcmp(variantXmlch, (const xmlChar*)"compact") == 0) {
- variant = VARIANT_COMPACT;
+ variant = FontFamily::Variant::COMPACT;
}
}
@@ -54,28 +73,33 @@
continue;
}
- int weight = atoi((const char*)(xmlGetProp(fontNode, (const xmlChar*)"weight"))) / 100;
- bool italic = xmlStrcmp(
- xmlGetProp(fontNode, (const xmlChar*)"style"), (const xmlChar*)"italic") == 0;
+ uint16_t weight = atoi((const char*)(xmlGetProp(fontNode, (const xmlChar*)"weight")));
+ FontStyle::Slant italic = static_cast<FontStyle::Slant>(
+ xmlStrcmp(xmlGetProp(fontNode, (const xmlChar*)"style"),
+ (const xmlChar*)"italic") == 0);
xmlChar* index = xmlGetProp(familyNode, (const xmlChar*)"index");
xmlChar* fontFileName = xmlNodeListGetString(doc, fontNode->xmlChildrenNode, 1);
- std::string fontPath = fontDir + std::string((const char*)fontFileName);
+ const std::string fontPath = xmlTrim(fontDir + std::string((const char*)fontFileName));
xmlFree(fontFileName);
+ // TODO: Support font variation axis.
+
if (access(fontPath.c_str(), R_OK) != 0) {
ALOGW("%s is not found.", fontPath.c_str());
continue;
}
+ FontStyle style(weight, italic);
if (index == nullptr) {
std::shared_ptr<MinikinFont> minikinFont =
- std::make_shared<MinikinFontForTest>(fontPath);
- fonts.push_back(Font(minikinFont, FontStyle(weight, italic)));
+ std::make_shared<FreeTypeMinikinFontForTest>(fontPath);
+ fonts.push_back(Font::Builder(minikinFont).setStyle(style).build());
} else {
std::shared_ptr<MinikinFont> minikinFont =
- std::make_shared<MinikinFontForTest>(fontPath, atoi((const char*)index));
- fonts.push_back(Font(minikinFont, FontStyle(weight, italic)));
+ std::make_shared<FreeTypeMinikinFontForTest>(fontPath,
+ atoi((const char*)index));
+ fonts.push_back(Font::Builder(minikinFont).setStyle(style).build());
}
}
@@ -84,8 +108,7 @@
if (lang == nullptr) {
family = std::make_shared<FontFamily>(variant, std::move(fonts));
} else {
- uint32_t langId = FontStyle::registerLanguageList(
- std::string((const char*)lang, xmlStrlen(lang)));
+ uint32_t langId = registerLocaleList(std::string((const char*)lang, xmlStrlen(lang)));
family = std::make_shared<FontFamily>(langId, variant, std::move(fonts));
}
families.push_back(family);
@@ -93,8 +116,24 @@
xmlFreeDoc(doc);
return families;
}
-std::shared_ptr<FontCollection> getFontCollection(const char* fontDir, const char* fontXml) {
- return std::make_shared<FontCollection>(getFontFamilies(fontDir, fontXml));
+
+std::shared_ptr<FontCollection> buildFontCollection(const std::string& filePath) {
+ return std::make_shared<FontCollection>(buildFontFamily(filePath));
+}
+
+std::shared_ptr<FontFamily> buildFontFamily(const std::string& filePath) {
+ auto font = std::make_shared<FreeTypeMinikinFontForTest>(getTestFontPath(filePath));
+ std::vector<Font> fonts;
+ fonts.push_back(Font::Builder(font).build());
+ return std::make_shared<FontFamily>(std::move(fonts));
+}
+
+std::shared_ptr<FontFamily> buildFontFamily(const std::string& filePath, const std::string& lang) {
+ auto font = std::make_shared<FreeTypeMinikinFontForTest>(getTestFontPath(filePath));
+ std::vector<Font> fonts;
+ fonts.push_back(Font::Builder(font).build());
+ return std::make_shared<FontFamily>(LocaleListCache::getId(lang), FontFamily::Variant::DEFAULT,
+ std::move(fonts));
}
} // namespace minikin
diff --git a/tests/util/FontTestUtils.h b/tests/util/FontTestUtils.h
index dd5e586..ba85093 100644
--- a/tests/util/FontTestUtils.h
+++ b/tests/util/FontTestUtils.h
@@ -17,30 +17,49 @@
#ifndef MINIKIN_FONT_TEST_UTILS_H
#define MINIKIN_FONT_TEST_UTILS_H
-#include <minikin/FontCollection.h>
-
#include <memory>
+#include "minikin/FontCollection.h"
+
+#include "PathUtils.h"
+
namespace minikin {
/**
* Returns list of FontFamily from installed fonts.
*
* This function reads an XML file and makes font families.
- *
- * Caller must unref the returned pointer.
*/
-std::vector<std::shared_ptr<FontFamily>> getFontFamilies(const char* fontDir, const char* fontXml);
+std::vector<std::shared_ptr<FontFamily>> getFontFamilies(const std::string& fontDir,
+ const std::string& xmlAbsPath);
/**
* Returns FontCollection from installed fonts.
*
* This function reads an XML file and makes font families and collections of them.
- * MinikinFontForTest is used for FontFamily creation.
- *
- * Caller must unref the returned pointer.
+ * The XML path and font files are needed to be in the test data directory.
*/
-std::shared_ptr<FontCollection> getFontCollection(const char* fontDir, const char* fontXml);
+inline std::shared_ptr<FontCollection> buildFontCollectionFromXml(const std::string& xmlPath) {
+ return std::make_shared<FontCollection>(
+ getFontFamilies(getTestDataDir(), getTestDataDir() + xmlPath));
+}
+
+/**
+ * Build new FontCollection from single file.
+ * The font file needs to be in the test data directory.
+ */
+std::shared_ptr<FontCollection> buildFontCollection(const std::string& filePath);
+
+/**
+ * Build new FontFamily from single file.
+ * The font file needs to be in the test data directory.
+ */
+std::shared_ptr<FontFamily> buildFontFamily(const std::string& filePath);
+
+/**
+ * Build new FontFamily from single file with locale.
+ */
+std::shared_ptr<FontFamily> buildFontFamily(const std::string& filePath, const std::string& lang);
} // namespace minikin
#endif // MINIKIN_FONT_TEST_UTILS_H
diff --git a/tests/util/FreeTypeMinikinFontForTest.cpp b/tests/util/FreeTypeMinikinFontForTest.cpp
new file mode 100644
index 0000000..7f70772
--- /dev/null
+++ b/tests/util/FreeTypeMinikinFontForTest.cpp
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "Minikin"
+
+#include "FreeTypeMinikinFontForTest.h"
+
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <string>
+
+#include <ft2build.h>
+#include <log/log.h>
+#include FT_OUTLINE_H
+
+#include "minikin/MinikinFont.h"
+
+namespace minikin {
+namespace {
+
+static int uniqueId = 0;
+
+constexpr FT_Int32 LOAD_FLAG =
+ FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP | FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH;
+
+constexpr float FTPosToFloat(FT_Pos x) {
+ return x / 64.0;
+}
+
+constexpr FT_F26Dot6 FTFloatToF26Dot6(float x) {
+ return static_cast<FT_F26Dot6>(x * 64);
+}
+
+void loadGlyphOrDie(uint32_t glyphId, float size, FT_Face face) {
+ const FT_F26Dot6 scale = FTFloatToF26Dot6(size);
+ LOG_ALWAYS_FATAL_IF(FT_Set_Char_Size(face, scale, scale, 72 /* dpi */, 72 /* dpi */),
+ "Failed to set character size.");
+ LOG_ALWAYS_FATAL_IF(FT_Load_Glyph(face, glyphId, LOAD_FLAG), "Failed to load glyph");
+ LOG_ALWAYS_FATAL_IF(face->glyph->format != FT_GLYPH_FORMAT_OUTLINE,
+ "Only outline font is supported.");
+}
+
+} // namespace
+
+FreeTypeMinikinFontForTest::FreeTypeMinikinFontForTest(const std::string& font_path, int index)
+ : MinikinFont(uniqueId++), mFontPath(font_path), mFontIndex(index) {
+ int fd = open(font_path.c_str(), O_RDONLY);
+ LOG_ALWAYS_FATAL_IF(fd == -1, "Open failed: %s", font_path.c_str());
+ struct stat st = {};
+ LOG_ALWAYS_FATAL_IF(fstat(fd, &st) != 0);
+ mFontSize = st.st_size;
+ mFontData = mmap(NULL, mFontSize, PROT_READ, MAP_SHARED, fd, 0);
+ LOG_ALWAYS_FATAL_IF(mFontData == nullptr);
+ close(fd);
+
+ LOG_ALWAYS_FATAL_IF(FT_Init_FreeType(&mFtLibrary), "Failed to initialize FreeType");
+
+ FT_Open_Args args;
+ args.flags = FT_OPEN_MEMORY;
+ args.memory_base = static_cast<const FT_Byte*>(mFontData);
+ args.memory_size = mFontSize;
+ LOG_ALWAYS_FATAL_IF(FT_Open_Face(mFtLibrary, &args, index, &mFtFace), "Failed to open FT_Face");
+}
+
+FreeTypeMinikinFontForTest::~FreeTypeMinikinFontForTest() {
+ FT_Done_Face(mFtFace);
+ FT_Done_FreeType(mFtLibrary);
+ munmap(mFontData, mFontSize);
+}
+
+float FreeTypeMinikinFontForTest::GetHorizontalAdvance(uint32_t glyphId, const MinikinPaint& paint,
+ const FontFakery& /* fakery */) const {
+ loadGlyphOrDie(glyphId, paint.size, mFtFace);
+ return FTPosToFloat(mFtFace->glyph->advance.x);
+}
+
+void FreeTypeMinikinFontForTest::GetBounds(MinikinRect* bounds, uint32_t glyphId,
+ const MinikinPaint& paint,
+ const FontFakery& /* fakery */) const {
+ loadGlyphOrDie(glyphId, paint.size, mFtFace);
+
+ FT_BBox bbox;
+ FT_Outline_Get_CBox(&mFtFace->glyph->outline, &bbox);
+
+ bounds->mLeft = FTPosToFloat(bbox.xMin);
+ bounds->mTop = FTPosToFloat(bbox.yMax);
+ bounds->mRight = FTPosToFloat(bbox.xMax);
+ bounds->mBottom = FTPosToFloat(bbox.yMin);
+}
+
+void FreeTypeMinikinFontForTest::GetFontExtent(MinikinExtent* extent,
+ const MinikinPaint& /* paint */,
+ const FontFakery& /* fakery */) const {
+ // TODO: Retrieve font metrics from FreeType.
+ extent->ascent = -10.0f;
+ extent->descent = 20.0f;
+ extent->line_gap = 0.0f;
+}
+
+} // namespace minikin
diff --git a/tests/util/FreeTypeMinikinFontForTest.h b/tests/util/FreeTypeMinikinFontForTest.h
new file mode 100644
index 0000000..d63b2bf
--- /dev/null
+++ b/tests/util/FreeTypeMinikinFontForTest.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MINIKIN_TEST_FREE_TYPE_MINIKIN_FONT_FOR_TEST_H
+#define MINIKIN_TEST_FREE_TYPE_MINIKIN_FONT_FOR_TEST_H
+
+#include "minikin/MinikinFont.h"
+
+#include <ft2build.h>
+#include FT_FREETYPE_H
+
+#include "minikin/Macros.h"
+
+namespace minikin {
+
+class FreeTypeMinikinFontForTest : public MinikinFont {
+public:
+ FreeTypeMinikinFontForTest(const std::string& font_path, int index);
+ FreeTypeMinikinFontForTest(const std::string& font_path)
+ : FreeTypeMinikinFontForTest(font_path, 0) {}
+ virtual ~FreeTypeMinikinFontForTest();
+
+ // MinikinFont overrides.
+ float GetHorizontalAdvance(uint32_t glyph_id, const MinikinPaint& paint,
+ const FontFakery& fakery) const override;
+ void GetBounds(MinikinRect* bounds, uint32_t glyph_id, const MinikinPaint& paint,
+ const FontFakery& fakery) const override;
+ void GetFontExtent(MinikinExtent* extent, const MinikinPaint& paint,
+ const FontFakery& fakery) const override;
+
+ const std::string& fontPath() const { return mFontPath; }
+
+ const void* GetFontData() const { return mFontData; }
+ size_t GetFontSize() const { return mFontSize; }
+ int GetFontIndex() const { return mFontIndex; }
+ const std::vector<minikin::FontVariation>& GetAxes() const { return mAxes; }
+
+private:
+ const std::string mFontPath;
+ const int mFontIndex;
+ void* mFontData;
+ size_t mFontSize;
+ std::vector<minikin::FontVariation> mAxes;
+
+ FT_Library mFtLibrary;
+ FT_Face mFtFace;
+
+ MINIKIN_PREVENT_COPY_AND_ASSIGN(FreeTypeMinikinFontForTest);
+};
+
+} // namespace minikin
+
+#endif // MINIKIN_TEST_FREE_TYPE_MINIKIN_FONT_FOR_TEST_H
diff --git a/tests/util/MinikinFontForTest.cpp b/tests/util/MinikinFontForTest.cpp
deleted file mode 100644
index 723e86a..0000000
--- a/tests/util/MinikinFontForTest.cpp
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "Minikin"
-
-#include "MinikinFontForTest.h"
-
-#include <minikin/MinikinFont.h>
-
-#include <fcntl.h>
-#include <sys/mman.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include <string>
-
-#include <log/log.h>
-
-namespace minikin {
-
-static int uniqueId = 0; // TODO: make thread safe if necessary.
-
-MinikinFontForTest::MinikinFontForTest(const std::string& font_path, int index,
- const std::vector<FontVariation>& variations) :
- MinikinFont(uniqueId++),
- mFontPath(font_path),
- mVariations(variations),
- mFontIndex(index) {
- int fd = open(font_path.c_str(), O_RDONLY);
- LOG_ALWAYS_FATAL_IF(fd == -1);
- struct stat st = {};
- LOG_ALWAYS_FATAL_IF(fstat(fd, &st) != 0);
- mFontSize = st.st_size;
- mFontData = mmap(NULL, mFontSize, PROT_READ, MAP_SHARED, fd, 0);
- LOG_ALWAYS_FATAL_IF(mFontData == nullptr);
- close(fd);
-}
-
-MinikinFontForTest::~MinikinFontForTest() {
- munmap(mFontData, mFontSize);
-}
-
-float MinikinFontForTest::GetHorizontalAdvance(uint32_t /* glyph_id */,
- const MinikinPaint& /* paint */) const {
- // TODO: Make mock value configurable if necessary.
- return 10.0f;
-}
-
-void MinikinFontForTest::GetBounds(MinikinRect* bounds, uint32_t /* glyph_id */,
- const MinikinPaint& /* paint */) const {
- // TODO: Make mock values configurable if necessary.
- bounds->mLeft = 0.0f;
- bounds->mTop = 0.0f;
- bounds->mRight = 10.0f;
- bounds->mBottom = 10.0f;
-}
-
-std::shared_ptr<MinikinFont> MinikinFontForTest::createFontWithVariation(
- const std::vector<FontVariation>& variations) const {
- return std::shared_ptr<MinikinFont>(new MinikinFontForTest(mFontPath, mFontIndex, variations));
-}
-
-} // namespace minikin
diff --git a/tests/util/MinikinFontForTest.h b/tests/util/MinikinFontForTest.h
deleted file mode 100644
index 6e230e1..0000000
--- a/tests/util/MinikinFontForTest.h
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef MINIKIN_TEST_MINIKIN_FONT_FOR_TEST_H
-#define MINIKIN_TEST_MINIKIN_FONT_FOR_TEST_H
-
-#include <minikin/MinikinFont.h>
-
-class SkTypeface;
-
-namespace minikin {
-
-class MinikinFontForTest : public MinikinFont {
-public:
- MinikinFontForTest(const std::string& font_path, int index,
- const std::vector<FontVariation>& variations);
- MinikinFontForTest(const std::string& font_path, int index)
- : MinikinFontForTest(font_path, index, std::vector<FontVariation>()) {}
- MinikinFontForTest(const std::string& font_path) : MinikinFontForTest(font_path, 0) {}
- virtual ~MinikinFontForTest();
-
- // MinikinFont overrides.
- float GetHorizontalAdvance(uint32_t glyph_id, const MinikinPaint &paint) const;
- void GetBounds(MinikinRect* bounds, uint32_t glyph_id,
- const MinikinPaint& paint) const;
-
- const std::string& fontPath() const { return mFontPath; }
-
- const void* GetFontData() const { return mFontData; }
- size_t GetFontSize() const { return mFontSize; }
- int GetFontIndex() const { return mFontIndex; }
- const std::vector<minikin::FontVariation>& GetAxes() const { return mVariations; }
- std::shared_ptr<MinikinFont> createFontWithVariation(
- const std::vector<FontVariation>& variations) const;
-private:
- MinikinFontForTest() = delete;
- MinikinFontForTest(const MinikinFontForTest&) = delete;
- MinikinFontForTest& operator=(MinikinFontForTest&) = delete;
-
- const std::string mFontPath;
- const std::vector<FontVariation> mVariations;
- const int mFontIndex;
- void* mFontData;
- size_t mFontSize;
-};
-
-} // namespace minikin
-
-#endif // MINIKIN_TEST_MINIKIN_FONT_FOR_TEST_H
diff --git a/tests/util/PathUtils.cpp b/tests/util/PathUtils.cpp
new file mode 100644
index 0000000..b08ff4b
--- /dev/null
+++ b/tests/util/PathUtils.cpp
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "PathUtils.h"
+
+#include <cutils/log.h>
+#include <libgen.h>
+#include <unistd.h>
+
+namespace minikin {
+
+const char* SELF_EXE_PATH = "/proc/self/exe";
+
+std::string getDirname(const std::string& path) {
+ const char* result = dirname(path.c_str());
+ LOG_ALWAYS_FATAL_IF(result == nullptr, "dirname failed.");
+ return std::string(result);
+}
+
+std::string getBasename(const std::string& path) {
+ const char* result = basename(path.c_str());
+ LOG_ALWAYS_FATAL_IF(result == nullptr, "basename failed.");
+ return std::string(result);
+}
+
+std::string getTestDataDir() {
+ char buf[PATH_MAX] = {};
+ LOG_ALWAYS_FATAL_IF(readlink(SELF_EXE_PATH, buf, PATH_MAX) == -1, "readlink failed.");
+ return getDirname(buf) + "/data/";
+}
+
+} // namespace minikin
diff --git a/tests/util/PathUtils.h b/tests/util/PathUtils.h
new file mode 100644
index 0000000..c757b84
--- /dev/null
+++ b/tests/util/PathUtils.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef TEST_UTILS_PATH_UTILS_H
+#define TEST_UTILS_PATH_UTILS_H
+
+#include <string>
+
+namespace minikin {
+
+std::string getDirname(const std::string& path);
+std::string getBasename(const std::string& path);
+
+// Returns test data directory.
+std::string getTestDataDir();
+
+inline std::string getTestFontPath(const std::string& fontFilePath) {
+ return getTestDataDir() + fontFilePath;
+}
+
+} // namespace minikin
+
+#endif // TEST_UTILS_PATH_UTILS_H
diff --git a/tests/util/UnicodeUtils.cpp b/tests/util/UnicodeUtils.cpp
index e66ff93..25716a7 100644
--- a/tests/util/UnicodeUtils.cpp
+++ b/tests/util/UnicodeUtils.cpp
@@ -14,73 +14,76 @@
* limitations under the License.
*/
+#include <cstdlib>
+#include <string>
+#include <vector>
+
+#include <cutils/log.h>
#include <unicode/utf.h>
#include <unicode/utf8.h>
-#include <cstdlib>
-#include <cutils/log.h>
-#include <vector>
-#include <string>
+
+#include "minikin/U16StringPiece.h"
namespace minikin {
// src is of the form "U+1F431 | 'h' 'i'". Position of "|" gets saved to offset if non-null.
// Size is returned in an out parameter because gtest needs a void return for ASSERT to work.
void ParseUnicode(uint16_t* buf, size_t buf_size, const char* src, size_t* result_size,
- size_t* offset) {
+ size_t* offset) {
size_t input_ix = 0;
size_t output_ix = 0;
bool seen_offset = false;
while (src[input_ix] != 0) {
switch (src[input_ix]) {
- case '\'':
- // single ASCII char
- LOG_ALWAYS_FATAL_IF(static_cast<uint8_t>(src[input_ix]) >= 0x80);
- input_ix++;
- LOG_ALWAYS_FATAL_IF(src[input_ix] == 0);
- LOG_ALWAYS_FATAL_IF(output_ix >= buf_size);
- buf[output_ix++] = (uint16_t)src[input_ix++];
- LOG_ALWAYS_FATAL_IF(src[input_ix] != '\'');
- input_ix++;
- break;
- case 'u':
- case 'U': {
- // Unicode codepoint in hex syntax
- input_ix++;
- LOG_ALWAYS_FATAL_IF(src[input_ix] != '+');
- input_ix++;
- char* endptr = (char*)src + input_ix;
- unsigned long int codepoint = strtoul(src + input_ix, &endptr, 16);
- size_t num_hex_digits = endptr - (src + input_ix);
+ case '\'':
+ // single ASCII char
+ LOG_ALWAYS_FATAL_IF(static_cast<uint8_t>(src[input_ix]) >= 0x80);
+ input_ix++;
+ LOG_ALWAYS_FATAL_IF(src[input_ix] == 0);
+ LOG_ALWAYS_FATAL_IF(output_ix >= buf_size);
+ buf[output_ix++] = (uint16_t)src[input_ix++];
+ LOG_ALWAYS_FATAL_IF(src[input_ix] != '\'');
+ input_ix++;
+ break;
+ case 'u':
+ case 'U': {
+ // Unicode codepoint in hex syntax
+ input_ix++;
+ LOG_ALWAYS_FATAL_IF(src[input_ix] != '+');
+ input_ix++;
+ char* endptr = (char*)src + input_ix;
+ unsigned long int codepoint = strtoul(src + input_ix, &endptr, 16);
+ size_t num_hex_digits = endptr - (src + input_ix);
- // also triggers on invalid number syntax, digits = 0
- LOG_ALWAYS_FATAL_IF(num_hex_digits < 4u);
- LOG_ALWAYS_FATAL_IF(num_hex_digits > 6u);
- LOG_ALWAYS_FATAL_IF(codepoint > 0x10FFFFu);
- input_ix += num_hex_digits;
- if (U16_LENGTH(codepoint) == 1) {
- LOG_ALWAYS_FATAL_IF(output_ix + 1 > buf_size);
- buf[output_ix++] = codepoint;
- } else {
- // UTF-16 encoding
- LOG_ALWAYS_FATAL_IF(output_ix + 2 > buf_size);
- buf[output_ix++] = U16_LEAD(codepoint);
- buf[output_ix++] = U16_TRAIL(codepoint);
+ // also triggers on invalid number syntax, digits = 0
+ LOG_ALWAYS_FATAL_IF(num_hex_digits < 4u);
+ LOG_ALWAYS_FATAL_IF(num_hex_digits > 6u);
+ LOG_ALWAYS_FATAL_IF(codepoint > 0x10FFFFu);
+ input_ix += num_hex_digits;
+ if (U16_LENGTH(codepoint) == 1) {
+ LOG_ALWAYS_FATAL_IF(output_ix + 1 > buf_size);
+ buf[output_ix++] = codepoint;
+ } else {
+ // UTF-16 encoding
+ LOG_ALWAYS_FATAL_IF(output_ix + 2 > buf_size);
+ buf[output_ix++] = U16_LEAD(codepoint);
+ buf[output_ix++] = U16_TRAIL(codepoint);
+ }
+ break;
}
- break;
- }
- case ' ':
- input_ix++;
- break;
- case '|':
- LOG_ALWAYS_FATAL_IF(seen_offset);
- LOG_ALWAYS_FATAL_IF(offset == nullptr);
- *offset = output_ix;
- seen_offset = true;
- input_ix++;
- break;
- default:
- LOG_ALWAYS_FATAL("Unexpected Character");
+ case ' ':
+ input_ix++;
+ break;
+ case '|':
+ LOG_ALWAYS_FATAL_IF(seen_offset);
+ LOG_ALWAYS_FATAL_IF(offset == nullptr);
+ *offset = output_ix;
+ seen_offset = true;
+ input_ix++;
+ break;
+ default:
+ LOG_ALWAYS_FATAL("Unexpected Character");
}
}
LOG_ALWAYS_FATAL_IF(result_size == nullptr);
@@ -116,4 +119,23 @@
return result;
}
+std::string utf16ToUtf8(const U16StringPiece& u16String) {
+ const uint32_t textLength = u16String.size();
+ uint32_t i = 0;
+ uint32_t c = 0;
+
+ std::string out;
+ out.reserve(textLength * 4);
+
+ while (i < textLength) {
+ U16_NEXT(u16String.data(), i, textLength, c);
+
+ char buf[U8_MAX_LENGTH] = {};
+ uint32_t outIndex = 0;
+ U8_APPEND_UNSAFE(buf, outIndex, c);
+ out.append(buf, outIndex);
+ }
+ return out;
+}
+
} // namespace minikin
diff --git a/tests/util/UnicodeUtils.h b/tests/util/UnicodeUtils.h
index 6ce2fcb..af9d056 100644
--- a/tests/util/UnicodeUtils.h
+++ b/tests/util/UnicodeUtils.h
@@ -14,15 +14,22 @@
* limitations under the License.
*/
+#include <cstdint>
+#include <string>
+#include <vector>
+
+#include "minikin/U16StringPiece.h"
+
namespace minikin {
void ParseUnicode(uint16_t* buf, size_t buf_size, const char* src, size_t* result_size,
- size_t* offset);
+ size_t* offset);
std::vector<uint16_t> parseUnicodeStringWithOffset(const std::string& in, size_t* offset);
std::vector<uint16_t> parseUnicodeString(const std::string& in);
// Converts UTF-8 to UTF-16.
std::vector<uint16_t> utf8ToUtf16(const std::string& text);
+std::string utf16ToUtf8(const U16StringPiece& u16String);
} // namespace minikin