diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2022-05-10 07:11:14 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2022-05-10 07:11:14 +0000 |
commit | 51aee8dc46b2b4fc4d06d44a1950a9ca012da5b8 (patch) | |
tree | a18e89087afaf754171443658f43a0c46d77f843 | |
parent | 33c5c2df64f144f97e659817c3c6598c5df4495c (diff) | |
parent | 63438e24011d2201e85e17089f51af37dbc50a2b (diff) | |
download | minikin-android13-mainline-adbd-release.tar.gz |
Snap for 8564071 from 63438e24011d2201e85e17089f51af37dbc50a2b to mainline-adbd-releaseaml_adb_331113120aml_adb_331011050aml_adb_331011040android13-mainline-adbd-release
Change-Id: Ibfe79a62eb9104f96e280b2ad935e4fddf84109c
36 files changed, 1537 insertions, 825 deletions
diff --git a/include/minikin/Emoji.h b/include/minikin/Emoji.h index 046a9d6..7c1d81c 100644 --- a/include/minikin/Emoji.h +++ b/include/minikin/Emoji.h @@ -27,6 +27,18 @@ bool isEmojiBase(uint32_t c); // Returns true if c is emoji modifier. bool isEmojiModifier(uint32_t c); +inline bool isRegionalIndicator(uint32_t c) { + return 0x1F1E6 <= c && c <= 0x1F1FF; +} + +inline bool isKeyCap(uint32_t c) { + return c == 0x20E3; +} + +inline bool isTagChar(uint32_t c) { + return 0xE0000 <= c && c <= 0xE007F; +} + // Bidi override for ICU that knows about new emoji. UCharDirection emojiBidiOverride(const void* context, UChar32 c); diff --git a/include/minikin/Hyphenator.h b/include/minikin/Hyphenator.h index 26c2a19..da265f0 100644 --- a/include/minikin/Hyphenator.h +++ b/include/minikin/Hyphenator.h @@ -125,16 +125,16 @@ 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); +constexpr uint16_t HYPHEN_STR_ZWJ[] = {CHAR_ZWJ}; +constexpr uint16_t HYPHEN_STR_HYPHEN[] = {CHAR_HYPHEN}; +constexpr uint16_t HYPHEN_STR_ARMENIAN_HYPHEN[] = {CHAR_ARMENIAN_HYPHEN}; +constexpr uint16_t HYPHEN_STR_MAQAF[] = {CHAR_MAQAF}; +constexpr uint16_t HYPHEN_STR_UCAS_HYPHEN[] = {CHAR_UCAS_HYPHEN}; +constexpr uint16_t HYPHEN_STR_ZWJ_AND_HYPHEN[] = {CHAR_ZWJ, CHAR_HYPHEN}; +constexpr std::pair<const uint16_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) { +inline std::pair<const uint16_t*, size_t> getHyphenString(StartHyphenEdit hyph) { if (hyph == StartHyphenEdit::INSERT_ZWJ) { return MAKE_HYPHEN_STR(HYPHEN_STR_ZWJ); } else if (hyph == StartHyphenEdit::INSERT_HYPHEN) { @@ -144,7 +144,7 @@ inline std::pair<const uint32_t*, size_t> getHyphenString(StartHyphenEdit hyph) } } -inline std::pair<const uint32_t*, size_t> getHyphenString(EndHyphenEdit hyph) { +inline std::pair<const uint16_t*, size_t> getHyphenString(EndHyphenEdit hyph) { switch (hyph) { case EndHyphenEdit::REPLACE_WITH_HYPHEN: // fall through case EndHyphenEdit::INSERT_HYPHEN: diff --git a/include/minikin/LineBreakStyle.h b/include/minikin/LineBreakStyle.h new file mode 100644 index 0000000..f474cbd --- /dev/null +++ b/include/minikin/LineBreakStyle.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2022 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_BREAK_STYLE_H +#define MINIKIN_LINE_BREAK_STYLE_H + +namespace minikin { + +// The line break style(lb) of the strings. +// See https://www.unicode.org/reports/tr35/#Key_Type_Definitions +enum class LineBreakStyle : uint8_t { + None = 0, + Loose = 1, + Normal = 2, + Strict = 3, +}; + +// The line break word style(lw) of the strings. +// See the key "lw" in https://www.unicode.org/reports/tr35/#Key_Type_Definitions +enum class LineBreakWordStyle : uint8_t { + None = 0, + Phrase = 1, +}; + +} // namespace minikin + +#endif // MINIKIN_LINE_BREAK_STYLE_H diff --git a/include/minikin/MeasuredText.h b/include/minikin/MeasuredText.h index 2c4b3f8..fa37eb6 100644 --- a/include/minikin/MeasuredText.h +++ b/include/minikin/MeasuredText.h @@ -23,6 +23,7 @@ #include "minikin/FontCollection.h" #include "minikin/Layout.h" #include "minikin/LayoutPieces.h" +#include "minikin/LineBreakStyle.h" #include "minikin/Macros.h" #include "minikin/MinikinFont.h" #include "minikin/Range.h" @@ -41,6 +42,12 @@ public: // Returns true if this run can be broken into multiple pieces for line breaking. virtual bool canBreak() const = 0; + // Return the line break style(lb) for this run. + virtual LineBreakStyle lineBreakStyle() const = 0; + + // Return the line break word style(lw) for this run. + virtual LineBreakWordStyle lineBreakWordStyle() const = 0; + // Returns the locale list ID for this run. virtual uint32_t getLocaleListId() const = 0; @@ -59,6 +66,8 @@ public: StartHyphenEdit startHyphen, EndHyphenEdit endHyphen, Layout* outLayout) const = 0; + virtual float measureText(const U16StringPiece& text) const = 0; + // Following two methods are only called when the implementation returns true for // canBreak method. @@ -83,10 +92,21 @@ protected: class StyleRun : public Run { public: - StyleRun(const Range& range, MinikinPaint&& paint, bool isRtl) - : Run(range), mPaint(std::move(paint)), mIsRtl(isRtl) {} + StyleRun(const Range& range, MinikinPaint&& paint, int lineBreakStyle, int lineBreakWordStyle, + bool isRtl) + : Run(range), + mPaint(std::move(paint)), + mLineBreakStyle(lineBreakStyle), + mLineBreakWordStyle(lineBreakWordStyle), + mIsRtl(isRtl) {} bool canBreak() const override { return true; } + LineBreakStyle lineBreakStyle() const override { + return static_cast<LineBreakStyle>(mLineBreakStyle); + } + LineBreakWordStyle lineBreakWordStyle() const override { + return static_cast<LineBreakWordStyle>(mLineBreakWordStyle); + } uint32_t getLocaleListId() const override { return mPaint.localeListId; } bool isRtl() const override { return mIsRtl; } @@ -109,9 +129,12 @@ public: float measureHyphenPiece(const U16StringPiece& text, const Range& range, StartHyphenEdit startHyphen, EndHyphenEdit endHyphen, LayoutPieces* pieces) const override; + float measureText(const U16StringPiece& text) const; private: MinikinPaint mPaint; + int mLineBreakStyle; + int mLineBreakWordStyle; const bool mIsRtl; }; @@ -122,6 +145,8 @@ public: bool isRtl() const { return false; } bool canBreak() const { return false; } + LineBreakStyle lineBreakStyle() const override { return LineBreakStyle::None; } + LineBreakWordStyle lineBreakWordStyle() const override { return LineBreakWordStyle::None; } uint32_t getLocaleListId() const { return mLocaleListId; } void getMetrics(const U16StringPiece& /* text */, std::vector<float>* advances, @@ -148,6 +173,8 @@ public: StartHyphenEdit /* startHyphen */, EndHyphenEdit /* endHyphen */, Layout* /* outLayout*/) const override {} + float measureText(const U16StringPiece&) const { return 0; } + private: const float mWidth; const uint32_t mLocaleListId; @@ -206,13 +233,14 @@ private: friend class MeasuredTextBuilder; void measure(const U16StringPiece& textBuf, bool computeHyphenation, bool computeLayout, - MeasuredText* hint); + bool ignoreHyphenKerning, MeasuredText* hint); // Use MeasuredTextBuilder instead. MeasuredText(const U16StringPiece& textBuf, std::vector<std::unique_ptr<Run>>&& runs, - bool computeHyphenation, bool computeLayout, MeasuredText* hint) + bool computeHyphenation, bool computeLayout, bool ignoreHyphenKerning, + MeasuredText* hint) : widths(textBuf.size()), runs(std::move(runs)) { - measure(textBuf, computeHyphenation, computeLayout, hint); + measure(textBuf, computeHyphenation, computeLayout, ignoreHyphenKerning, hint); } }; @@ -220,8 +248,10 @@ 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 addStyleRun(int32_t start, int32_t end, MinikinPaint&& paint, int lineBreakStyle, + int lineBreakWordStyle, bool isRtl) { + mRuns.emplace_back(std::make_unique<StyleRun>(Range(start, end), std::move(paint), + lineBreakStyle, lineBreakWordStyle, isRtl)); } void addReplacementRun(int32_t start, int32_t end, float width, uint32_t localeListId) { @@ -235,10 +265,12 @@ public: } std::unique_ptr<MeasuredText> build(const U16StringPiece& textBuf, bool computeHyphenation, - bool computeLayout, MeasuredText* hint) { + bool computeLayout, bool ignoreHyphenKerning, + MeasuredText* hint) { // 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, hint)); + return std::unique_ptr<MeasuredText>(new MeasuredText(textBuf, std::move(mRuns), + computeHyphenation, computeLayout, + ignoreHyphenKerning, hint)); } MINIKIN_PREVENT_COPY_ASSIGN_AND_MOVE(MeasuredTextBuilder); diff --git a/include/minikin/Measurement.h b/include/minikin/Measurement.h index d0aaa51..76f3701 100644 --- a/include/minikin/Measurement.h +++ b/include/minikin/Measurement.h @@ -17,11 +17,12 @@ #ifndef MINIKIN_MEASUREMENT_H #define MINIKIN_MEASUREMENT_H +#include <minikin/Layout.h> +#include <minikin/MinikinExtent.h> + #include <cstddef> #include <cstdint> -#include <minikin/Layout.h> - namespace minikin { float getRunAdvance(const float* advances, const uint16_t* buf, size_t start, size_t count, @@ -34,6 +35,9 @@ void getBounds(const U16StringPiece& str, const Range& range, Bidi bidiFlags, const MinikinPaint& paint, StartHyphenEdit startHyphen, EndHyphenEdit endHyphen, MinikinRect* out); +MinikinExtent getFontExtent(const U16StringPiece& str, const Range& range, Bidi bidiFlags, + const MinikinPaint& paint); + } // namespace minikin #endif // MINIKIN_MEASUREMENT_H diff --git a/libs/minikin/Android.bp b/libs/minikin/Android.bp index 8356ea4..c0c4035 100644 --- a/libs/minikin/Android.bp +++ b/libs/minikin/Android.bp @@ -34,6 +34,7 @@ cc_library { "Font.cpp", "FontCollection.cpp", "FontFamily.cpp", + "FontFeatureUtils.cpp", "FontFileParser.cpp", "FontUtils.cpp", "GraphemeBreak.cpp", @@ -89,8 +90,6 @@ cc_library { ], export_header_lib_headers: ["libminikin_headers"], - clang: true, - target: { android: { shared_libs: [ diff --git a/libs/minikin/FontCollection.cpp b/libs/minikin/FontCollection.cpp index 9e4dd3b..38fc022 100644 --- a/libs/minikin/FontCollection.cpp +++ b/libs/minikin/FontCollection.cpp @@ -18,17 +18,17 @@ #include "minikin/FontCollection.h" -#include <algorithm> - #include <log/log.h> #include <unicode/unorm2.h> -#include "minikin/Emoji.h" -#include "minikin/FontFileParser.h" +#include <algorithm> #include "Locale.h" #include "LocaleListCache.h" #include "MinikinInternal.h" +#include "minikin/Characters.h" +#include "minikin/Emoji.h" +#include "minikin/FontFileParser.h" using std::vector; @@ -46,7 +46,13 @@ static std::atomic<uint32_t> gNextCollectionId = {0}; namespace { -uint32_t getGlyphCount(U16StringPiece text, uint32_t start, uint32_t end, +inline bool isEmojiBreak(uint32_t prevCh, uint32_t ch) { + return !(isEmojiModifier(ch) || (isRegionalIndicator(prevCh) && isRegionalIndicator(ch)) || + isKeyCap(ch) || isTagChar(ch) || ch == CHAR_ZWJ || prevCh == CHAR_ZWJ); +} + +// Lower is better +uint32_t getGlyphScore(U16StringPiece text, uint32_t start, uint32_t end, const HbFontUniquePtr& font) { HbBufferUniquePtr buffer(hb_buffer_create()); hb_buffer_set_direction(buffer.get(), HB_DIRECTION_LTR); @@ -55,7 +61,61 @@ uint32_t getGlyphCount(U16StringPiece text, uint32_t start, uint32_t end, unsigned int numGlyphs = -1; hb_shape(font.get(), buffer.get(), nullptr, 0); - hb_buffer_get_glyph_infos(buffer.get(), &numGlyphs); + hb_glyph_info_t* info = hb_buffer_get_glyph_infos(buffer.get(), &numGlyphs); + + // HarfBuzz squashed unsupported tag sequence into first emoji glyph. So, we cannot use glyph + // count for the font selection score. Give extra score if the base score is different from the + // first glyph. + if (numGlyphs == 1) { + constexpr uint32_t TAG_SEQUENCE_FALLBACK_PENALTY = 0x10000; + + uint32_t ch = 0; + const uint16_t* string = text.data(); + const uint32_t string_size = text.size(); + uint32_t readLength = 0; + + U16_NEXT(string, readLength, string_size, ch); + if (U_IS_SURROGATE(ch)) { + return numGlyphs; // Broken surrogate pair. + } + + if (readLength >= string_size) { + return numGlyphs; // No more characters remaining. + } + + uint32_t nextCh = 0; + U16_NEXT(string, readLength, string_size, nextCh); + + if (!isTagChar(nextCh)) { + return numGlyphs; // Not a tag sequence. + } + + uint32_t composedGlyphId = info[0].codepoint; + + // Shape only the first base emoji. + hb_buffer_reset(buffer.get()); + hb_buffer_set_direction(buffer.get(), HB_DIRECTION_LTR); + hb_buffer_add_codepoints(buffer.get(), &ch, 1, 0, 1); + hb_buffer_guess_segment_properties(buffer.get()); + + unsigned int numGlyphs = -1; + hb_shape(font.get(), buffer.get(), nullptr, 0); + info = hb_buffer_get_glyph_infos(buffer.get(), &numGlyphs); + + if (numGlyphs != 1) { + // If the single code point of the first base emoji is decomposed to multiple glyphs, + // we don't support it. + return numGlyphs; + } + + uint32_t baseGlyphId = info[0].codepoint; + if (composedGlyphId == baseGlyphId) { + return numGlyphs + TAG_SEQUENCE_FALLBACK_PENALTY; + } else { + return numGlyphs; + } + } + return numGlyphs; } @@ -552,8 +612,15 @@ std::vector<FontCollection::Run> FontCollection::itemize(U16StringPiece text, Fo if (intersection.empty()) { breakRun = true; // None of last family can draw the given char. } else { - lastFamilyIndices = intersection; - breakRun = false; + breakRun = isEmojiBreak(prevCh, ch); + if (!breakRun) { + // To select sequence supported families, update family indices with the + // intersection between the supported families between prev char and + // current char. + familyIndices = intersection; + lastFamilyIndices = intersection; + run->familyMatch = intersection; + } } } else { breakRun = familyIndices[0] != lastFamilyIndices[0]; @@ -627,17 +694,18 @@ std::vector<FontCollection::Run> FontCollection::itemize(U16StringPiece text, Fo FakedFont FontCollection::getBestFont(U16StringPiece text, const Run& run, FontStyle style) { uint8_t bestIndex = 0; - uint32_t bestGlyphCount = 0xFFFFFFFF; + uint32_t bestScore = 0xFFFFFFFF; const std::shared_ptr<FontFamily>& family = mFamilies[run.familyMatch[0]]; if (family->isColorEmojiFamily() && run.familyMatch.size() > 1) { for (size_t i = 0; i < run.familyMatch.size(); ++i) { const std::shared_ptr<FontFamily>& family = mFamilies[run.familyMatch[i]]; const HbFontUniquePtr& font = family->getFont(0)->baseFont(); - uint32_t glyphCount = getGlyphCount(text, run.start, run.end, font); - if (glyphCount < bestGlyphCount) { + uint32_t score = getGlyphScore(text, run.start, run.end, font); + + if (score < bestScore) { bestIndex = run.familyMatch[i]; - bestGlyphCount = glyphCount; + bestScore = score; } } } else { diff --git a/libs/minikin/FontFeatureUtils.cpp b/libs/minikin/FontFeatureUtils.cpp new file mode 100644 index 0000000..e1ec065 --- /dev/null +++ b/libs/minikin/FontFeatureUtils.cpp @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2021 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 "FontFeatureUtils.h" + +#include "StringPiece.h" + +namespace minikin { + +std::vector<hb_feature_t> cleanAndAddDefaultFontFeatures(const MinikinPaint& paint) { + 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(paint.letterSpacing) > 0.03) { + static constexpr hb_feature_t no_liga = {HB_TAG('l', 'i', 'g', 'a'), 0, 0, ~0u}; + static constexpr hb_feature_t no_clig = {HB_TAG('c', 'l', 'i', 'g'), 0, 0, ~0u}; + features.push_back(no_liga); + features.push_back(no_clig); + } + + bool default_enable_chws = true; + + static constexpr hb_tag_t chws_tag = HB_TAG('c', 'h', 'w', 's'); + static constexpr hb_tag_t halt_tag = HB_TAG('h', 'a', 'l', 't'); + static constexpr hb_tag_t palt_tag = HB_TAG('p', 'a', 'l', 't'); + + SplitIterator it(paint.fontFeatureSettings, ','); + while (it.hasNext()) { + StringPiece featureStr = it.next(); + static hb_feature_t feature; + // We do not allow setting features on ranges. As such, reject any setting that has + // non-universal range. + if (hb_feature_from_string(featureStr.data(), featureStr.size(), &feature) && + feature.start == 0 && feature.end == (unsigned int)-1) { + // OpenType requires disabling default `chws` feature if glyph-width features. + // https://docs.microsoft.com/en-us/typography/opentype/spec/features_ae#tag-chws + // Here, we follow Chrome's impl: not enabling default `chws` feature if `palt` or + // `halt` is enabled. + // https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/platform/fonts/shaping/font_features.cc;drc=77a9a09de0688ca449f5333a305ceaf3f36b6daf;l=215 + if (default_enable_chws && + (feature.tag == chws_tag || + (feature.value && (feature.tag == halt_tag || feature.tag == palt_tag)))) { + default_enable_chws = false; + } + + features.push_back(feature); + } + } + + if (default_enable_chws) { + static constexpr hb_feature_t chws = {chws_tag, 1, 0, ~0u}; + features.push_back(chws); + } + + return features; +} + +} // namespace minikin diff --git a/libs/minikin/FontFeatureUtils.h b/libs/minikin/FontFeatureUtils.h new file mode 100644 index 0000000..1a02173 --- /dev/null +++ b/libs/minikin/FontFeatureUtils.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2021 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_FEATURE_UTILS_H +#define MINIKIN_FONT_FEATURE_UTILS_H + +#include <hb.h> + +#include "minikin/MinikinPaint.h" + +namespace minikin { + +/** + * Returns the final set of font features based on the features requested by this paint object and + * extra defaults or implied font features. + * + * Features are included from the paint object if they are: + * 1) in a supported range + * + * Default features are added based if they are: + * 1) implied due to Paint settings such as letterSpacing + * 2) default features that do not conflict with requested features + */ +std::vector<hb_feature_t> cleanAndAddDefaultFontFeatures(const MinikinPaint& paint); + +} // namespace minikin +#endif // MINIKIN_LAYOUT_UTILS_H diff --git a/libs/minikin/GreedyLineBreaker.cpp b/libs/minikin/GreedyLineBreaker.cpp index 9bcb22d..bb2f91c 100644 --- a/libs/minikin/GreedyLineBreaker.cpp +++ b/libs/minikin/GreedyLineBreaker.cpp @@ -333,7 +333,8 @@ void GreedyLineBreaker::process() { uint32_t newLocaleListId = run->getLocaleListId(); if (localeListId != newLocaleListId) { Locale locale = getEffectiveLocale(newLocaleListId); - nextWordBoundaryOffset = wordBreaker.followingWithLocale(locale, range.getStart()); + nextWordBoundaryOffset = wordBreaker.followingWithLocale( + locale, run->lineBreakStyle(), run->lineBreakWordStyle(), range.getStart()); mHyphenator = HyphenatorMap::lookup(locale); localeListId = newLocaleListId; } diff --git a/libs/minikin/LayoutCore.cpp b/libs/minikin/LayoutCore.cpp index f1d0de8..6f33ad7 100644 --- a/libs/minikin/LayoutCore.cpp +++ b/libs/minikin/LayoutCore.cpp @@ -18,12 +18,6 @@ #include "minikin/LayoutCore.h" -#include <cmath> -#include <iostream> -#include <mutex> -#include <string> -#include <vector> - #include <hb-icu.h> #include <hb-ot.h> #include <log/log.h> @@ -31,16 +25,22 @@ #include <unicode/utf16.h> #include <utils/LruCache.h> -#include "minikin/Emoji.h" -#include "minikin/HbUtils.h" -#include "minikin/LayoutCache.h" -#include "minikin/LayoutPieces.h" -#include "minikin/Macros.h" +#include <cmath> +#include <iostream> +#include <mutex> +#include <string> +#include <vector> #include "BidiUtils.h" +#include "FontFeatureUtils.h" #include "LayoutUtils.h" #include "LocaleListCache.h" #include "MinikinInternal.h" +#include "minikin/Emoji.h" +#include "minikin/HbUtils.h" +#include "minikin/LayoutCache.h" +#include "minikin/LayoutPieces.h" +#include "minikin/Macros.h" namespace minikin { @@ -189,20 +189,6 @@ static bool isScriptOkForLetterspacing(hb_script_t script) { script == HB_SCRIPT_TIRHUTA || script == HB_SCRIPT_OGHAM); } -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; - /* We do not allow setting features on ranges. As such, reject any - * setting that has non-universal range. */ - if (hb_feature_from_string(featureStr.data(), featureStr.size(), &feature) && - feature.start == 0 && feature.end == (unsigned int)-1) { - features->push_back(feature); - } - } -} - static inline hb_codepoint_t determineHyphenChar(hb_codepoint_t preferredHyphen, hb_font_t* font) { hb_codepoint_t glyph; if (preferredHyphen == 0x058A /* ARMENIAN_HYPHEN */ @@ -230,9 +216,7 @@ static inline hb_codepoint_t determineHyphenChar(hb_codepoint_t preferredHyphen, 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); + auto [chars, size] = getHyphenString(hyphen); for (size_t i = 0; i < size; i++) { hb_buffer_add(buffer.get(), determineHyphenChar(chars[i], font.get()), cluster); } @@ -359,19 +343,7 @@ LayoutPiece::LayoutPiece(const U16StringPiece& textBuf, const Range& range, bool std::vector<FontCollection::Run> items = paint.font->itemize(substr, paint.fontStyle, paint.localeListId, paint.familyVariant); - 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(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(paint.fontFeatureSettings, &features); + std::vector<hb_feature_t> features = cleanAndAddDefaultFontFeatures(paint); std::vector<HbFontUniquePtr> hbFonts; double size = paint.size; @@ -455,7 +427,7 @@ LayoutPiece::LayoutPiece(const U16StringPiece& textBuf, const Range& range, bool 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)) { + if (localeList[i].supportsScript(hb_script_to_iso15924_tag(script))) { hbLanguage = localeList.getHbLanguage(i); break; } diff --git a/libs/minikin/LayoutUtils.cpp b/libs/minikin/LayoutUtils.cpp index acf07e2..c69dbf3 100644 --- a/libs/minikin/LayoutUtils.cpp +++ b/libs/minikin/LayoutUtils.cpp @@ -16,6 +16,8 @@ #include "LayoutUtils.h" +#include "StringPiece.h" + namespace minikin { /* diff --git a/libs/minikin/LineBreakerUtil.h b/libs/minikin/LineBreakerUtil.h index 8b383a4..5764c5e 100644 --- a/libs/minikin/LineBreakerUtil.h +++ b/libs/minikin/LineBreakerUtil.h @@ -62,13 +62,15 @@ inline Locale getEffectiveLocale(uint32_t localeListId) { // 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. + 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. + const std::vector<float>& charWidths, // Char width used for hyphen piece estimation. + bool ignoreKerning, // True use full shaping for hyphenation piece. + 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; } @@ -81,19 +83,45 @@ inline void populateHyphenationPoints( 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 */, pieces); - const float second = - run.measureHyphenPiece(secondText, Range(0, secondText.size()), - editForNextLine(hyph) /* start hyphen edit */, - EndHyphenEdit::NO_EDIT /* end hyphen edit */, pieces); - - out->emplace_back(i, hyph, first, second); + if (!ignoreKerning) { + 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 */, pieces); + const float second = + run.measureHyphenPiece(secondText, Range(0, secondText.size()), + editForNextLine(hyph) /* start hyphen edit */, + EndHyphenEdit::NO_EDIT /* end hyphen edit */, pieces); + + out->emplace_back(i, hyph, first, second); + } else { + float first = 0; + float second = 0; + for (uint32_t j = contextRange.getStart(); j < i; ++j) { + first += charWidths[j]; + } + for (uint32_t j = i; j < contextRange.getEnd(); ++j) { + second += charWidths[j]; + } + + EndHyphenEdit endEdit = editForThisLine(hyph); + StartHyphenEdit startEdit = editForNextLine(hyph); + + if (endEdit != EndHyphenEdit::NO_EDIT) { + auto [str, strSize] = getHyphenString(endEdit); + first += run.measureText(U16StringPiece(str, strSize)); + } + + if (startEdit != StartHyphenEdit::NO_EDIT) { + auto [str, strSize] = getHyphenString(startEdit); + second += run.measureText(U16StringPiece(str, strSize)); + } + + out->emplace_back(i, hyph, first, second); + } } } @@ -151,7 +179,9 @@ struct CharProcessor { uint32_t newLocaleListId = run.getLocaleListId(); if (localeListId != newLocaleListId) { Locale locale = getEffectiveLocale(newLocaleListId); - nextWordBreak = breaker.followingWithLocale(locale, run.getRange().getStart()); + nextWordBreak = breaker.followingWithLocale(locale, run.lineBreakStyle(), + run.lineBreakWordStyle(), + run.getRange().getStart()); hyphenator = HyphenatorMap::lookup(locale); localeListId = newLocaleListId; } diff --git a/libs/minikin/Locale.cpp b/libs/minikin/Locale.cpp index 553f61a..d246020 100644 --- a/libs/minikin/Locale.cpp +++ b/libs/minikin/Locale.cpp @@ -233,36 +233,11 @@ void Locale::resolveUnicodeExtension(const char* buf, size_t length) { if (pos != buf + length) { pos += strlen(kPrefix); const size_t remainingLength = length - (pos - buf); - mLBStyle = resolveLineBreakStyle(pos, remainingLength); mEmojiStyle = resolveEmojiStyle(pos, remainingLength); } } // static -// Lookup line break subtag and determine the line break style. -LineBreakStyle Locale::resolveLineBreakStyle(const char* buf, size_t length) { - // 8 is the length of "-u-lb-loose", which is the shortest line break subtag, - // unnecessary comparison can be avoided if total length is smaller than 11. - const size_t kMinSubtagLength = 8; - if (length >= kMinSubtagLength) { - static const char kPrefix[] = "lb-"; - 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 (isSubtag(pos, remainingLength, "loose", 5)) { - return LineBreakStyle::LOOSE; - } else if (isSubtag(pos, remainingLength, "normal", 6)) { - return LineBreakStyle::NORMAL; - } else if (isSubtag(pos, remainingLength, "strict", 6)) { - return LineBreakStyle::STRICT; - } - } - } - return LineBreakStyle::EMPTY; -} - -// static // Lookup emoji subtag and determine the emoji style. EmojiStyle Locale::resolveEmojiStyle(const char* buf, size_t length) { // 7 is the length of "-u-em-text", which is the shortest emoji subtag, @@ -339,7 +314,78 @@ uint8_t Locale::scriptToSubScriptBits(uint32_t script) { } std::string Locale::getString() const { - char buf[32] = {}; + char buf[32]; + int i = buildLocaleString(buf); + return std::string(buf, i); +} + +std::string Locale::getStringWithLineBreakOption(LineBreakStyle lbStyle, + LineBreakWordStyle lbWordStyle) const { + char buf[48]; + int i = buildLocaleString(buf); + + // Add line break unicode extension. + if (lbStyle != LineBreakStyle::None || lbWordStyle != LineBreakWordStyle::None) { + buf[i++] = '-'; + buf[i++] = 'u'; + } + + if (lbStyle != LineBreakStyle::None) { + buf[i++] = '-'; + buf[i++] = 'l'; + buf[i++] = 'b'; + buf[i++] = '-'; + switch (lbStyle) { + case LineBreakStyle::Loose: + buf[i++] = 'l'; + buf[i++] = 'o'; + buf[i++] = 'o'; + buf[i++] = 's'; + buf[i++] = 'e'; + break; + case LineBreakStyle::Normal: + buf[i++] = 'n'; + buf[i++] = 'o'; + buf[i++] = 'r'; + buf[i++] = 'm'; + buf[i++] = 'a'; + buf[i++] = 'l'; + break; + case LineBreakStyle::Strict: + buf[i++] = 's'; + buf[i++] = 't'; + buf[i++] = 'r'; + buf[i++] = 'i'; + buf[i++] = 'c'; + buf[i++] = 't'; + break; + default: + MINIKIN_ASSERT(false, "Must not reached."); + } + } + + if (lbWordStyle != LineBreakWordStyle::None) { + buf[i++] = '-'; + buf[i++] = 'l'; + buf[i++] = 'w'; + buf[i++] = '-'; + switch (lbWordStyle) { + case LineBreakWordStyle::Phrase: + buf[i++] = 'p'; + buf[i++] = 'h'; + buf[i++] = 'r'; + buf[i++] = 'a'; + buf[i++] = 's'; + buf[i++] = 'e'; + break; + default: + MINIKIN_ASSERT(false, "Must not reached."); + } + } + return std::string(buf, i); +} + +int Locale::buildLocaleString(char* buf) const { size_t i; if (mLanguage == NO_LANGUAGE) { buf[0] = 'u'; @@ -378,43 +424,7 @@ std::string Locale::getString() const { MINIKIN_ASSERT(false, "Must not reached."); } } - // Add line break unicode extension. - if (mLBStyle != LineBreakStyle::EMPTY) { - buf[i++] = '-'; - buf[i++] = 'u'; - buf[i++] = '-'; - buf[i++] = 'l'; - buf[i++] = 'b'; - buf[i++] = '-'; - switch (mLBStyle) { - case LineBreakStyle::LOOSE: - buf[i++] = 'l'; - buf[i++] = 'o'; - buf[i++] = 'o'; - buf[i++] = 's'; - buf[i++] = 'e'; - break; - case LineBreakStyle::NORMAL: - buf[i++] = 'n'; - buf[i++] = 'o'; - buf[i++] = 'r'; - buf[i++] = 'm'; - buf[i++] = 'a'; - buf[i++] = 'l'; - break; - case LineBreakStyle::STRICT: - buf[i++] = 's'; - buf[i++] = 't'; - buf[i++] = 'r'; - buf[i++] = 'i'; - buf[i++] = 'c'; - buf[i++] = 't'; - break; - default: - MINIKIN_ASSERT(false, "Must not reached."); - } - } - return std::string(buf, i); + return i; } Locale Locale::getPartialLocale(SubtagBits bits) const { @@ -449,7 +459,7 @@ bool Locale::supportsScript(uint8_t providedBits, uint8_t requestedBits) { return requestedBits != 0 && (providedBits & requestedBits) == requestedBits; } -bool Locale::supportsHbScript(hb_script_t script) const { +bool Locale::supportsScript(uint32_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); diff --git a/libs/minikin/Locale.h b/libs/minikin/Locale.h index 7557301..285bb7b 100644 --- a/libs/minikin/Locale.h +++ b/libs/minikin/Locale.h @@ -17,12 +17,13 @@ #ifndef MINIKIN_LOCALE_LIST_H #define MINIKIN_LOCALE_LIST_H +#include <hb.h> + #include <string> #include <vector> -#include <hb.h> - #include "StringPiece.h" +#include "minikin/LineBreakStyle.h" namespace minikin { @@ -63,14 +64,6 @@ enum class EmojiStyle : uint8_t { TEXT = 3, // Text (black/white) emoji style is specified. }; -// Enum for line break style. -enum class LineBreakStyle : uint8_t { - EMPTY = 0, // No line break style is specified. - LOOSE = 1, // line break style is loose. - NORMAL = 2, // line break style is normal. - STRICT = 3, // line break style is strict. -}; - // 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. @@ -89,8 +82,7 @@ public: mRegion(NO_REGION), mSubScriptBits(0ul), mVariant(Variant::NO_VARIANT), - mEmojiStyle(EmojiStyle::EMPTY), - mLBStyle(LineBreakStyle::EMPTY) {} + mEmojiStyle(EmojiStyle::EMPTY) {} // Parse from string Locale(const StringPiece& buf); @@ -102,13 +94,12 @@ public: mRegion(extractBits(identifier, 14, 15)), mSubScriptBits(scriptToSubScriptBits(mScript)), mVariant(static_cast<Variant>(extractBits(identifier, 0, 2))), - mEmojiStyle(static_cast<EmojiStyle>(extractBits(identifier, 12, 2))), - mLBStyle(static_cast<LineBreakStyle>(extractBits(identifier, 10, 2))) {} + mEmojiStyle(static_cast<EmojiStyle>(extractBits(identifier, 12, 2))) {} bool operator==(const Locale& other) const { return !isUnsupported() && isEqualScript(other) && mLanguage == other.mLanguage && mRegion == other.mRegion && mVariant == other.mVariant && - mLBStyle == other.mLBStyle && mEmojiStyle == other.mEmojiStyle; + mEmojiStyle == other.mEmojiStyle; } bool operator!=(const Locale other) const { return !(*this == other); } @@ -117,12 +108,10 @@ public: 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 hasLBStyle() const { return mLBStyle != LineBreakStyle::EMPTY; } inline bool hasEmojiStyle() const { return mEmojiStyle != EmojiStyle::EMPTY; } inline bool isSupported() const { - return hasLanguage() || hasScript() || hasRegion() || hasVariant() || hasLBStyle() || - hasEmojiStyle(); + return hasLanguage() || hasScript() || hasRegion() || hasVariant() || hasEmojiStyle(); } inline bool isUnsupported() const { return !isSupported(); } @@ -133,10 +122,13 @@ public: // 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; + bool supportsScript(uint32_t script) const; std::string getString() const; + std::string getStringWithLineBreakOption(LineBreakStyle lbStyle, + LineBreakWordStyle lbWordStyle) 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. @@ -148,12 +140,11 @@ public: // ssssssssssssssssssss Script Code (20 bits) // rrrrrrrrrrrrrrr Region Code (15 bits) // ee Emoji Style (2 bits) - // bb Line Break Style (2 bits) - // XXXXXXXX Free (8 bits) + // XXXXXXXXXX Free (10 bits) // vv German Variant (2 bits) uint64_t getIdentifier() const { return ((uint64_t)mLanguage << 49) | ((uint64_t)mScript << 29) | ((uint64_t)mRegion << 14) | - ((uint64_t)mEmojiStyle << 12) | ((uint64_t)mLBStyle << 10) | (uint64_t)mVariant; + ((uint64_t)mEmojiStyle << 12) | (uint64_t)mVariant; } Locale getPartialLocale(SubtagBits bits) const; @@ -187,7 +178,6 @@ private: Variant mVariant; EmojiStyle mEmojiStyle; - LineBreakStyle mLBStyle; void resolveUnicodeExtension(const char* buf, size_t length); @@ -195,9 +185,10 @@ private: return (value >> shift) & ((1 << nBits) - 1); } + int buildLocaleString(char* buf) const; + static uint8_t scriptToSubScriptBits(uint32_t rawScript); - static LineBreakStyle resolveLineBreakStyle(const char* buf, size_t length); static EmojiStyle resolveEmojiStyle(const char* buf, size_t length); static EmojiStyle scriptToEmojiStyle(uint32_t script); diff --git a/libs/minikin/MeasuredText.cpp b/libs/minikin/MeasuredText.cpp index 1bf3942..33dab38 100644 --- a/libs/minikin/MeasuredText.cpp +++ b/libs/minikin/MeasuredText.cpp @@ -75,6 +75,37 @@ void StyleRun::getMetrics(const U16StringPiece& textBuf, std::vector<float>* adv } } +// Helper class for composing total advances. +class TotalAdvancesCompositor { +public: + TotalAdvancesCompositor() : mOut(0) {} + + void operator()(const LayoutPiece& layoutPiece, const MinikinPaint&) { + for (float w : layoutPiece.advances()) { + mOut += w; + } + } + + float getTotalAdvance() { return mOut; } + +private: + float mOut; +}; + +float StyleRun::measureText(const U16StringPiece& textBuf) const { + TotalAdvancesCompositor compositor; + const Bidi bidiFlag = mIsRtl ? Bidi::FORCE_RTL : Bidi::FORCE_LTR; + LayoutCache& layoutCache = LayoutCache::getInstance(); + for (const BidiText::RunInfo info : BidiText(textBuf, Range(0, textBuf.length()), bidiFlag)) { + for (const auto [context, piece] : LayoutSplitter(textBuf, info.range, info.isRtl)) { + layoutCache.getOrCreate(textBuf.substr(context), piece - context.getStart(), mPaint, + info.isRtl, StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, + compositor); + } + } + return compositor.getTotalAdvance(); +} + // Helper class for composing total amount of advance class TotalAdvanceCompositor { public: @@ -125,7 +156,7 @@ float StyleRun::measureHyphenPiece(const U16StringPiece& textBuf, const Range& r } void MeasuredText::measure(const U16StringPiece& textBuf, bool computeHyphenation, - bool computeLayout, MeasuredText* hint) { + bool computeLayout, bool ignoreHyphenKerning, MeasuredText* hint) { if (textBuf.size() == 0) { return; } @@ -153,7 +184,8 @@ void MeasuredText::measure(const U16StringPiece& textBuf, bool computeHyphenatio } populateHyphenationPoints(textBuf, *run, *proc.hyphenator, proc.contextRange(), - proc.wordRange(), &hyphenBreaks, piecesOut); + proc.wordRange(), widths, ignoreHyphenKerning, &hyphenBreaks, + piecesOut); } } } diff --git a/libs/minikin/Measurement.cpp b/libs/minikin/Measurement.cpp index 66886da..093dba8 100644 --- a/libs/minikin/Measurement.cpp +++ b/libs/minikin/Measurement.cpp @@ -157,4 +157,27 @@ void getBounds(const U16StringPiece& str, const Range& range, Bidi bidiFlag, *out = bc.mBounds; } +struct ExtentComposer { + ExtentComposer() {} + + void operator()(const LayoutPiece& layoutPiece, const MinikinPaint&) { + extent.extendBy(layoutPiece.extent()); + } + + MinikinExtent extent; +}; + +MinikinExtent getFontExtent(const U16StringPiece& textBuf, const Range& range, Bidi bidiFlag, + const MinikinPaint& paint) { + ExtentComposer composer; + for (const BidiText::RunInfo info : BidiText(textBuf, range, bidiFlag)) { + for (const auto [context, piece] : LayoutSplitter(textBuf, info.range, info.isRtl)) { + LayoutCache::getInstance().getOrCreate( + textBuf.substr(context), piece - context.getStart(), paint, info.isRtl, + StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, composer); + } + } + return composer.extent; +} + } // namespace minikin diff --git a/libs/minikin/WordBreaker.cpp b/libs/minikin/WordBreaker.cpp index 7a9ab3a..fd0dea9 100644 --- a/libs/minikin/WordBreaker.cpp +++ b/libs/minikin/WordBreaker.cpp @@ -32,21 +32,24 @@ namespace minikin { namespace { -static UBreakIterator* createNewIterator(const Locale& locale) { +static UBreakIterator* createNewIterator(const Locale& locale, LineBreakStyle lbStyle, + LineBreakWordStyle lbWordStyle) { // TODO: handle failure status UErrorCode status = U_ZERO_ERROR; char localeID[ULOC_FULLNAME_CAPACITY] = {}; - uloc_forLanguageTag(locale.getString().c_str(), localeID, ULOC_FULLNAME_CAPACITY, nullptr, - &status); + uloc_forLanguageTag(locale.getStringWithLineBreakOption(lbStyle, lbWordStyle).c_str(), localeID, + ULOC_FULLNAME_CAPACITY, nullptr, &status); return ubrk_open(UBreakIteratorType::UBRK_LINE, localeID, nullptr, 0, &status); } } // namespace -ICULineBreakerPool::Slot ICULineBreakerPoolImpl::acquire(const Locale& locale) { +ICULineBreakerPool::Slot ICULineBreakerPoolImpl::acquire(const Locale& locale, + LineBreakStyle lbStyle, + LineBreakWordStyle lbWordStyle) { 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) { + if (i->localeId == id && i->lbStyle == lbStyle && i->lbWordStyle == lbWordStyle) { Slot slot = std::move(*i); mPool.erase(i); return slot; @@ -54,7 +57,8 @@ ICULineBreakerPool::Slot ICULineBreakerPoolImpl::acquire(const Locale& locale) { } // Not found in pool. Create new one. - return {id, IcuUbrkUniquePtr(createNewIterator(locale))}; + return {id, lbStyle, lbWordStyle, + IcuUbrkUniquePtr(createNewIterator(locale, lbStyle, lbWordStyle))}; } void ICULineBreakerPoolImpl::release(ICULineBreakerPool::Slot&& slot) { @@ -75,8 +79,9 @@ 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); +ssize_t WordBreaker::followingWithLocale(const Locale& locale, LineBreakStyle lbStyle, + LineBreakWordStyle lbWordStyle, size_t from) { + mIcuBreaker = mPool->acquire(locale, lbStyle, lbWordStyle); UErrorCode status = U_ZERO_ERROR; MINIKIN_ASSERT(mText != nullptr, "setText must be called first"); // TODO: handle failure status diff --git a/libs/minikin/WordBreaker.h b/libs/minikin/WordBreaker.h index 99023a2..45bcd40 100644 --- a/libs/minikin/WordBreaker.h +++ b/libs/minikin/WordBreaker.h @@ -23,17 +23,17 @@ #ifndef MINIKIN_WORD_BREAKER_H #define MINIKIN_WORD_BREAKER_H +#include <unicode/ubrk.h> + #include <list> #include <mutex> -#include <unicode/ubrk.h> - +#include "Locale.h" #include "minikin/IcuUtils.h" +#include "minikin/LineBreakStyle.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. @@ -42,8 +42,12 @@ class ICULineBreakerPool { public: struct Slot { Slot() : localeId(0), breaker(nullptr) {} - Slot(uint64_t localeId, IcuUbrkUniquePtr&& breaker) - : localeId(localeId), breaker(std::move(breaker)) {} + Slot(uint64_t localeId, LineBreakStyle lbStyle, LineBreakWordStyle lbWordStyle, + IcuUbrkUniquePtr&& breaker) + : localeId(localeId), + lbStyle(lbStyle), + lbWordStyle(lbWordStyle), + breaker(std::move(breaker)) {} Slot(Slot&& other) = default; Slot& operator=(Slot&& other) = default; @@ -53,10 +57,13 @@ public: Slot& operator=(const Slot&) = delete; uint64_t localeId; + LineBreakStyle lbStyle; + LineBreakWordStyle lbWordStyle; IcuUbrkUniquePtr breaker; }; virtual ~ICULineBreakerPool() {} - virtual Slot acquire(const Locale& locale) = 0; + virtual Slot acquire(const Locale& locale, LineBreakStyle lbStyle, + LineBreakWordStyle lbWordStyle) = 0; virtual void release(Slot&& slot) = 0; }; @@ -64,7 +71,8 @@ public: // 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; + Slot acquire(const Locale& locale, LineBreakStyle lbStyle, + LineBreakWordStyle lbWordStyle) override; void release(Slot&& slot) override; static ICULineBreakerPoolImpl& getInstance() { @@ -99,7 +107,8 @@ public: // 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); + ssize_t followingWithLocale(const Locale& locale, LineBreakStyle lbStyle, + LineBreakWordStyle lbWordStyle, size_t from); // Current offset of iterator, equal to 0 at BOT or last return from next() ssize_t current() const; diff --git a/tests/data/Ascii.ttf b/tests/data/Ascii.ttf Binary files differindex 2e6835b..dc56be5 100644 --- a/tests/data/Ascii.ttf +++ b/tests/data/Ascii.ttf diff --git a/tests/data/Ascii.ttx b/tests/data/Ascii.ttx index c0a2ef6..3830e9b 100644 --- a/tests/data/Ascii.ttx +++ b/tests/data/Ascii.ttx @@ -225,6 +225,7 @@ <map code="0x02B4" name="2em" /> <!-- 'δ' --> <map code="0x02B5" name="2em" /> <!-- 'ε' --> <map code="0x02B6" name="2em" /> <!-- 'ζ' --> + <map code="0x2010" name="1em" /> <!-- Hyphen Character --> </cmap_format_12> </cmap> diff --git a/tests/data/EmojiBase.ttf b/tests/data/EmojiBase.ttf Binary files differindex 659226d..3ae29f2 100644 --- a/tests/data/EmojiBase.ttf +++ b/tests/data/EmojiBase.ttf diff --git a/tests/data/EmojiBase.ttx b/tests/data/EmojiBase.ttx index e6e3da8..ea6684c 100644 --- a/tests/data/EmojiBase.ttx +++ b/tests/data/EmojiBase.ttx @@ -17,41 +17,45 @@ <GlyphOrder> <!-- The 'id' attribute is only for humans; it is ignored when parsed. --> - <GlyphID id="0" name=".notdef"/> + <GlyphID name=".notdef"/> <!-- Compbining characters for Emoji --> - <GlyphID id="1" name="U+200D"/> - <GlyphID id="2" name="U+1F3FB"/> - <GlyphID id="3" name="U+1F3FC"/> + <GlyphID name="U+200D"/> + <GlyphID name="U+1F3FB"/> + <GlyphID name="U+1F3FC"/> <!-- Random Emoji --> - <GlyphID id="4" name="U+1F9B0"/> - <GlyphID id="5" name="U+1F9B1"/> - <GlyphID id="6" name="U+1F9B2"/> - <GlyphID id="7" name="U+1F9B3"/> - <GlyphID id="8" name="U+1F9B4"/> - <GlyphID id="9" name="U+1F9B5"/> - <GlyphID id="10" name="U+1F9B6"/> - <GlyphID id="11" name="U+1F9B7"/> - <GlyphID id="12" name="U+1F9B8"/> - <GlyphID id="13" name="U+1F9B9"/> - - <!-- Unassigned Code Points --> - <GlyphID id="14" name="U+E0000"/> - <GlyphID id="15" name="U+E0001"/> + <GlyphID name="U+1F9B0"/> + <GlyphID name="U+1F9B1"/> + <GlyphID name="U+1F9B2"/> + <GlyphID name="U+1F9B3"/> + <GlyphID name="U+1F9B4"/> + <GlyphID name="U+1F9B5"/> + <GlyphID name="U+1F9B6"/> + <GlyphID name="U+1F9B7"/> + <GlyphID name="U+1F9B8"/> + <GlyphID name="U+1F9B9"/> + <GlyphID name="U+1F9BA"/> <!-- Ligature Form. Not in cmap --> - <GlyphID id="16" name="LigaForm1"/> - <GlyphID id="17" name="LigaForm2"/> - <GlyphID id="18" name="LigaForm3"/> - <GlyphID id="19" name="LigaForm4"/> - <GlyphID id="20" name="LigaForm5"/> - <GlyphID id="21" name="LigaForm6"/> - <GlyphID id="22" name="LigaForm7"/> + <GlyphID name="LigaForm1"/> + <GlyphID name="LigaForm2"/> + <GlyphID name="LigaForm3"/> + <GlyphID name="LigaForm4"/> + <GlyphID name="LigaForm5"/> + <GlyphID name="LigaForm6"/> + <GlyphID name="LigaForm7"/> <!-- Regional Indicators --> - <GlyphID id="23" name="U+1F1E6"/> - <GlyphID id="24" name="U+1F1E7"/> + <GlyphID name="U+1F1E6"/> + <GlyphID name="U+1F1E7"/> + + <!-- Tag Character --> + <GlyphID name="U+E0001"/> + <GlyphID name="U+E0002"/> + <GlyphID name="U+E0003"/> + <GlyphID name="U+E0004"/> + <GlyphID name="U+E007F"/> </GlyphOrder> @@ -185,8 +189,12 @@ <mtx name="U+1F9B7" width="500" lsb="93"/> <mtx name="U+1F9B8" width="500" lsb="93"/> <mtx name="U+1F9B9" width="500" lsb="93"/> - <mtx name="U+E0000" width="500" lsb="93"/> + <mtx name="U+1F9BA" width="500" lsb="93"/> <mtx name="U+E0001" width="500" lsb="93"/> + <mtx name="U+E0002" width="500" lsb="93"/> + <mtx name="U+E0003" width="500" lsb="93"/> + <mtx name="U+E0004" width="500" lsb="93"/> + <mtx name="U+E007F" width="500" lsb="93"/> <mtx name="LigaForm1" width="500" lsb="93"/> <mtx name="LigaForm2" width="500" lsb="93"/> <mtx name="LigaForm3" width="500" lsb="93"/> @@ -214,8 +222,12 @@ <map code="0x1F9B7" name="U+1F9B7" /> <map code="0x1F9B8" name="U+1F9B8" /> <map code="0x1F9B9" name="U+1F9B9" /> - <map code="0xE0000" name="U+E0000" /> + <map code="0x1F9BA" name="U+1F9BA" /> <map code="0xE0001" name="U+E0001" /> + <map code="0xE0002" name="U+E0002" /> + <map code="0xE0003" name="U+E0003" /> + <map code="0xE0004" name="U+E0004" /> + <map code="0xE007F" name="U+E007F" /> </cmap_format_12> </cmap> @@ -372,7 +384,7 @@ </contour> <instructions><assembly></assembly></instructions> </TTGlyph> - <TTGlyph name="U+E0000" xMin="0" yMin="0" xMax="100" yMax="100"> + <TTGlyph name="U+1F9BA" xMin="0" yMin="0" xMax="100" yMax="100"> <contour> <pt x="0" y="0" on="1" /> <pt x="100" y="0" on="1" /> @@ -390,6 +402,42 @@ </contour> <instructions><assembly></assembly></instructions> </TTGlyph> + <TTGlyph name="U+E0002" 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> + <TTGlyph name="U+E0003" 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> + <TTGlyph name="U+E0004" 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> + <TTGlyph name="U+E007F" 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> <TTGlyph name="LigaForm1" xMin="0" yMin="0" xMax="100" yMax="100"> <contour> <pt x="0" y="0" on="1" /> @@ -522,6 +570,12 @@ <LigatureSet glyph="U+1F9B2"> <Ligature components="U+200D,U+1F9B3,U+200D,U+1F9B4" glyph="LigaForm4"/> </LigatureSet> + <LigatureSet glyph="U+1F9B3"> + <Ligature components="U+200D,U+1F9B3,U+200D,U+1F9B4" glyph="LigaForm4"/> + </LigatureSet> + <LigatureSet glyph="U+1F9B4"> + <Ligature components="U+200D,U+1F9B3,U+200D,U+1F9B4" glyph="LigaForm4"/> + </LigatureSet> <LigatureSet glyph="U+1F9B6"> <!-- U+1F9B6 U+1F3FB -> LigaForm3 --> <Ligature components="U+1F3FB" glyph="LigaForm3"/> @@ -530,6 +584,10 @@ <Ligature components="U+1F1E6" glyph="LigaForm6"/> <Ligature components="U+1F1E7" glyph="LigaForm7"/> </LigatureSet> + <LigatureSet glyph="U+1F9BA"> + <Ligature components="U+E0001,U+E0002,U+E007F" glyph="LigaForm6"/> + <Ligature components="U+E0003,U+E0004,U+E007F" glyph="LigaForm7"/> + </LigatureSet> </LigatureSubst> </Lookup> </LookupList> diff --git a/tests/data/OverrideEmoji.ttf b/tests/data/OverrideEmoji.ttf Binary files differindex 890796b..42efe59 100644 --- a/tests/data/OverrideEmoji.ttf +++ b/tests/data/OverrideEmoji.ttf diff --git a/tests/data/OverrideEmoji.ttx b/tests/data/OverrideEmoji.ttx index d5d4091..57db17f 100644 --- a/tests/data/OverrideEmoji.ttx +++ b/tests/data/OverrideEmoji.ttx @@ -17,52 +17,54 @@ <GlyphOrder> <!-- The 'id' attribute is only for humans; it is ignored when parsed. --> - <GlyphID id="0" name=".notdef"/> + <GlyphID name=".notdef"/> <!-- Compbining characters for Emoji --> - <GlyphID id="1" name="U+200D"/> - <GlyphID id="2" name="U+1F3FB"/> - <GlyphID id="3" name="U+1F3FC"/> + <GlyphID name="U+200D"/> + <GlyphID name="U+1F3FB"/> + <GlyphID name="U+1F3FC"/> <!-- Random Emoji --> - <GlyphID id="4" name="U+1F9B0"/> - <GlyphID id="5" name="U+1F9B1"/> - <GlyphID id="6" name="U+1F9B2"/> - <GlyphID id="7" name="U+1F9B3"/> - <GlyphID id="8" name="U+1F9B4"/> - <GlyphID id="9" name="U+1F9B5"/> + <GlyphID name="U+1F9B0"/> + <GlyphID name="U+1F9B1"/> + <GlyphID name="U+1F9B2"/> + <GlyphID name="U+1F9B3"/> + <GlyphID name="U+1F9B4"/> + <GlyphID name="U+1F9B5"/> <!-- Following four glyphs are removed from EmojiBase.ttf for verifying fallback. - <GlyphID id="10" name="U+1F9B6"/> - <GlyphID id="11" name="U+1F9B7"/> - <GlyphID id="12" name="U+1F9B8"/> - <GlyphID id="13" name="U+1F9B9"/> - --> - - <!-- Unassigned Code Points --> - <GlyphID id="14" name="U+E0000"/> - <!-- - Following glyph is removed from EmojiBase.ttf for verifying fallback. - <GlyphID id="15" name="U+E0001"/> + <GlyphID name="U+1F9B6"/> + <GlyphID name="U+1F9B7"/> + <GlyphID name="U+1F9B8"/> + <GlyphID name="U+1F9B9"/> --> + <GlyphID name="U+1F9BA"/> <!-- Ligature Form. Not in cmap --> - <GlyphID id="16" name="LigaForm1"/> - <GlyphID id="17" name="LigaForm2"/> - <GlyphID id="18" name="LigaForm3"/> + <GlyphID name="LigaForm1"/> + <GlyphID name="LigaForm2"/> + <GlyphID name="LigaForm3"/> <!-- Following glyphs are removed from EmojiBase.ttf for verifying fallback. - <GlyphID id="19" name="LigaForm4"/> - <GlyphID id="20" name="LigaForm5"/> + <GlyphID name="LigaForm4"/> + <GlyphID name="LigaForm5"/> --> - <GlyphID id="21" name="LigaForm6"/> + <GlyphID name="LigaForm6"/> <!-- Following glyph is removed from EmojiBase.ttf for verifying fallback. - <GlyphID id="22" name="LigaForm7"/> + <GlyphID name="LigaForm7"/> --> - <GlyphID id="23" name="U+1F1E6"/> - <GlyphID id="24" name="U+1F1E7"/> + <GlyphID name="U+1F1E6"/> + <GlyphID name="U+1F1E7"/> + + <!-- Tag Character --> + <GlyphID name="U+E0001"/> + <GlyphID name="U+E0002"/> + <GlyphID name="U+E0003"/> + <GlyphID name="U+E0004"/> + <GlyphID name="U+E007F"/> + </GlyphOrder> <head> @@ -198,12 +200,12 @@ <mtx name="U+1F9B8" width="500" lsb="93"/> <mtx name="U+1F9B9" width="500" lsb="93"/> --> - <mtx name="U+E0000" width="500" lsb="93"/> - <!-- - Following glyph is removed from EmojiBase.ttf for verifying fallback. - <mtx name="U+1F9B6" width="500" lsb="93"/> - --> + <mtx name="U+1F9BA" width="500" lsb="93"/> <mtx name="U+E0001" width="500" lsb="93"/> + <mtx name="U+E0002" width="500" lsb="93"/> + <mtx name="U+E0003" width="500" lsb="93"/> + <mtx name="U+E0004" width="500" lsb="93"/> + <mtx name="U+E007F" width="500" lsb="93"/> <mtx name="LigaForm1" width="500" lsb="93"/> <mtx name="LigaForm2" width="500" lsb="93"/> <mtx name="LigaForm3" width="500" lsb="93"/> @@ -240,11 +242,12 @@ <map code="0x1F9B8" name="U+1F9B8" /> <map code="0x1F9B9" name="U+1F9B9" /> --> - <map code="0xE0000" name="U+E0000" /> - <!-- - Following glyph is removed from EmojiBase.ttf for verifying fallback. + <map code="0x1F9BA" name="U+1F9BA" /> <map code="0xE0001" name="U+E0001" /> - --> + <map code="0xE0002" name="U+E0002" /> + <map code="0xE0003" name="U+E0003" /> + <map code="0xE0004" name="U+E0004" /> + <map code="0xE007F" name="U+E007F" /> </cmap_format_12> </cmap> @@ -372,7 +375,52 @@ <TTGlyph name="U+1F9B8"> <TTGlyph name="U+1F9B9"> --> - <TTGlyph name="U+E0000" xMin="0" yMin="0" xMax="100" yMax="100"> + <TTGlyph name="U+1F9BA" 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> + <TTGlyph name="U+E0001" 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> + <TTGlyph name="U+E0002" 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> + <TTGlyph name="U+E0003" 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> + <TTGlyph name="U+E0004" 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> + <TTGlyph name="U+E007F" xMin="0" yMin="0" xMax="100" yMax="100"> <contour> <pt x="0" y="0" on="1" /> <pt x="100" y="0" on="1" /> @@ -381,10 +429,6 @@ </contour> <instructions><assembly></assembly></instructions> </TTGlyph> - <!-- - Following glyph is removed from EmojiBase.ttf for verifying fallback. - <TTGlyph name="U+E0001"/> - --> <TTGlyph name="LigaForm1" xMin="0" yMin="0" xMax="100" yMax="100"> <contour> <pt x="0" y="0" on="1" /> @@ -504,6 +548,15 @@ <LigatureSet glyph="U+1F9B2"> <Ligature components="U+200D,U+1F9B3" glyph="LigaForm3"/> </LigatureSet> + <LigatureSet glyph="U+1F9B3"> + <Ligature components="U+200D,U+1F9B3,U+200D,U+1F9B4" glyph="LigaForm3"/> + </LigatureSet> + <!-- + Following ligature is removed from the EmojiBase.ttf for verifying fallback + <LigatureSet glyph="U+1F9B4"> + <Ligature components="U+200D,U+1F9B3,U+200D,U+1F9B4" glyph="LigaForm3"/> + </LigatureSet> + --> <!-- Following ligature is removed from the EmojIBase.ttf for verifying fallback. <LigatureSet glyph="U+1F9B6"> @@ -514,6 +567,13 @@ <Ligature components="U+1F1E6" glyph="LigaForm6"/> <Ligature components="U+1F1E7" glyph=".notdef"/> </LigatureSet> + <LigatureSet glyph="U+1F9BA"> + <Ligature components="U+E0001,U+E0002,U+E007F" glyph="LigaForm1"/> + <!-- + Following ligature is removed from the EmojIBase.ttf for verifying fallback. + <Ligature components="U+E0003,U+E0004,U+E007F" glyph="LigaForm1"/> + --> + </LigatureSet> </LigatureSubst> </Lookup> </LookupList> diff --git a/tests/perftests/WordBreaker.cpp b/tests/perftests/WordBreaker.cpp index 6a8b1c4..b75f284 100644 --- a/tests/perftests/WordBreaker.cpp +++ b/tests/perftests/WordBreaker.cpp @@ -28,7 +28,7 @@ static void BM_WordBreaker_English(benchmark::State& state) { "eiusmod tempor incididunt ut labore et dolore magna aliqua."; WordBreaker wb; - wb.followingWithLocale(Locale("en-US"), 0); + wb.followingWithLocale(Locale("en-US"), LineBreakStyle::None, LineBreakWordStyle::None, 0); std::vector<uint16_t> text = utf8ToUtf16(kLoremIpsum); while (state.KeepRunning()) { wb.setText(text.data(), text.size()); diff --git a/tests/unittest/Android.bp b/tests/unittest/Android.bp index cae9003..d36c52f 100644 --- a/tests/unittest/Android.bp +++ b/tests/unittest/Android.bp @@ -53,6 +53,7 @@ cc_test { "FontCollectionTest.cpp", "FontCollectionItemizeTest.cpp", "FontFamilyTest.cpp", + "FontFeatureTest.cpp", "FontFileParserTest.cpp", "FontLanguageListCacheTest.cpp", "FontUtilsTest.cpp", diff --git a/tests/unittest/FontCollectionItemizeTest.cpp b/tests/unittest/FontCollectionItemizeTest.cpp index 6f1e194..e3f8a6b 100644 --- a/tests/unittest/FontCollectionItemizeTest.cpp +++ b/tests/unittest/FontCollectionItemizeTest.cpp @@ -1629,7 +1629,13 @@ TEST(FontCollectionItemizeTest, customFallbackTest) { EXPECT_EQ(customFallbackFamily->getFont(0), runs[0].fakedFont.font.get()); } -std::string itemizeEmojiAndFontPostScriptName(const std::string& txt) { +struct ItemizeResult { + int start; + int end; + std::string psName; +}; + +std::vector<ItemizeResult> itemizeEmojiAndFontPostScriptNames(const std::string& txt) { auto firstFamily = buildFontFamily(kAsciiFont); auto OverrideEmojiFamily = buildFontFamily("OverrideEmoji.ttf", "und-Zsye"); auto emojiBaseFamily = buildFontFamily("EmojiBase.ttf", "und-Zsye"); @@ -1640,21 +1646,29 @@ std::string itemizeEmojiAndFontPostScriptName(const std::string& txt) { auto collection = std::make_shared<FontCollection>(families); auto runs = itemize(collection, txt.c_str()); - EXPECT_EQ(1u, runs.size()); - return FontFileParser(runs[0].fakedFont.font->baseFont()).getPostScriptName().value(); + std::vector<ItemizeResult> out; + for (const auto& run : runs) { + auto psName = FontFileParser(run.fakedFont.font->baseFont()).getPostScriptName().value(); + out.push_back({run.start, run.end, psName}); + } + return out; +} + +std::string itemizeEmojiAndFontPostScriptName(const std::string& txt) { + auto results = itemizeEmojiAndFontPostScriptNames(txt); + EXPECT_EQ(1u, results.size()); + return results[0].psName; } TEST(FontCollectionItemizeTest, emojiFallback) { - // OverrideEmojiFont supports U+1F9B0, U+E0000, U+1F3FB and U+1F9B0 U+1F3FB sequence. + // OverrideEmojiFont supports U+1F9B0, U+1F3FB and U+1F9B0 U+1F3FB sequence. // Use Override font. EXPECT_EQ("OverrideEmojiFont", itemizeEmojiAndFontPostScriptName("U+1F9B0")); - EXPECT_EQ("OverrideEmojiFont", itemizeEmojiAndFontPostScriptName("U+E0000")); EXPECT_EQ("OverrideEmojiFont", itemizeEmojiAndFontPostScriptName("U+1F9B0 U+1F3FB")); EXPECT_EQ("OverrideEmojiFont", itemizeEmojiAndFontPostScriptName("U+1F9B0 U+FE0F U+1F3FB")); - // OverrideEmojiFont doesn't suppot U+1F9B6 U+E0001 and U+1F3FC. + // OverrideEmojiFont doesn't suppot U+1F9B6 and U+1F3FC. EXPECT_EQ("EmojiBaseFont", itemizeEmojiAndFontPostScriptName("U+1F9B6")); - EXPECT_EQ("EmojiBaseFont", itemizeEmojiAndFontPostScriptName("U+E0001")); EXPECT_EQ("EmojiBaseFont", itemizeEmojiAndFontPostScriptName("U+1F9B6 U+1F3FC")); EXPECT_EQ("EmojiBaseFont", itemizeEmojiAndFontPostScriptName("U+1F9B6 U+FE0F U+1F3FC")); @@ -1670,6 +1684,94 @@ TEST(FontCollectionItemizeTest, emojiFallback) { itemizeEmojiAndFontPostScriptName("U+1F9B2 U+200D U+1F9B3 U+200D U+1F9B4")); } +TEST(FontCollectionItemizeTest, customEmojiFallback) { + auto results = itemizeEmojiAndFontPostScriptNames("U+1F9B6 U+1F9B0"); + EXPECT_EQ(0, results[0].start); + EXPECT_EQ(2, results[0].end); + EXPECT_EQ("EmojiBaseFont", results[0].psName); + + EXPECT_EQ(2, results[1].start); + EXPECT_EQ(4, results[1].end); + EXPECT_EQ("OverrideEmojiFont", results[1].psName); + + results = itemizeEmojiAndFontPostScriptNames("U+1F9B0 U+1F9B6"); + EXPECT_EQ(0, results[0].start); + EXPECT_EQ(2, results[0].end); + EXPECT_EQ("OverrideEmojiFont", results[0].psName); + + EXPECT_EQ(2, results[1].start); + EXPECT_EQ(4, results[1].end); + EXPECT_EQ("EmojiBaseFont", results[1].psName); +} + +TEST(FontCollectionItemizeTest, customEmojiFallback_modifier) { + auto results = itemizeEmojiAndFontPostScriptNames("U+1F9B0 U+1F3FC U+1F9B0 U+1F3FB"); + EXPECT_EQ(0, results[0].start); + EXPECT_EQ(4, results[0].end); + EXPECT_EQ("EmojiBaseFont", results[0].psName); + + EXPECT_EQ(4, results[1].start); + EXPECT_EQ(8, results[1].end); + EXPECT_EQ("OverrideEmojiFont", results[1].psName); + + results = itemizeEmojiAndFontPostScriptNames("U+1F9B0 U+1F3FB U+1F9B0 U+1F3FC"); + EXPECT_EQ(0, results[0].start); + EXPECT_EQ(4, results[0].end); + EXPECT_EQ("OverrideEmojiFont", results[0].psName); + + EXPECT_EQ(4, results[1].start); + EXPECT_EQ(8, results[1].end); + EXPECT_EQ("EmojiBaseFont", results[1].psName); +} + +TEST(FontCollectionItemizeTest, customEmojiFallback_zwj) { + auto results = itemizeEmojiAndFontPostScriptNames( + "U+1F9B4 U+200D U+1F9B3 U+200D U+1F9B4 " + "U+1F9B3 U+200D U+1F9B3 U+200D U+1F9B4 "); + EXPECT_EQ(0, results[0].start); + EXPECT_EQ(8, results[0].end); + EXPECT_EQ("EmojiBaseFont", results[0].psName); + + EXPECT_EQ(8, results[1].start); + EXPECT_EQ(16, results[1].end); + EXPECT_EQ("OverrideEmojiFont", results[1].psName); + + results = itemizeEmojiAndFontPostScriptNames( + "U+1F9B3 U+200D U+1F9B3 U+200D U+1F9B4 " + "U+1F9B4 U+200D U+1F9B3 U+200D U+1F9B4 "); + EXPECT_EQ(0, results[0].start); + EXPECT_EQ(8, results[0].end); + EXPECT_EQ("OverrideEmojiFont", results[0].psName); + + EXPECT_EQ(8, results[1].start); + EXPECT_EQ(16, results[1].end); + EXPECT_EQ("EmojiBaseFont", results[1].psName); +} + +TEST(FontCollectionItemizeTest, customEmojiFallback_tagSequence) { + auto results = itemizeEmojiAndFontPostScriptNames( + "U+1F9BA U+E0003 U+E0004 U+E007F " + "U+1F9BA U+E0001 U+E0002 U+E007F"); + EXPECT_EQ(0, results[0].start); + EXPECT_EQ(8, results[0].end); + EXPECT_EQ("EmojiBaseFont", results[0].psName); + + EXPECT_EQ(8, results[1].start); + EXPECT_EQ(16, results[1].end); + EXPECT_EQ("OverrideEmojiFont", results[1].psName); + + results = itemizeEmojiAndFontPostScriptNames( + "U+1F9BA U+E0001 U+E0002 U+E007F " + "U+1F9BA U+E0003 U+E0004 U+E007F"); + EXPECT_EQ(0, results[0].start); + EXPECT_EQ(8, results[0].end); + EXPECT_EQ("OverrideEmojiFont", results[0].psName); + + EXPECT_EQ(8, results[1].start); + EXPECT_EQ(16, results[1].end); + EXPECT_EQ("EmojiBaseFont", results[1].psName); +} + TEST(FontCollectionItemizeTest, emojiFlagFallback) { // If the OverrideEmojiFont supports U+1F1E6 U+1F1E6, use that font. EXPECT_EQ("OverrideEmojiFont", itemizeEmojiAndFontPostScriptName("U+1F1E6 U+1F1E6")); diff --git a/tests/unittest/FontFamilyTest.cpp b/tests/unittest/FontFamilyTest.cpp index fd2fc9a..ce710f3 100644 --- a/tests/unittest/FontFamilyTest.cpp +++ b/tests/unittest/FontFamilyTest.cpp @@ -109,13 +109,18 @@ TEST(LocaleTest, getStringTest) { EXPECT_EQ("de-Latn-DE-1901", createLocale("de-1901").getString()); EXPECT_EQ("de-Latn-DE-1996", createLocale("de-DE-1996").getString()); - // Line Break subtag - EXPECT_EQ("ja-Jpan-JP-u-lb-loose", createLocale("ja-JP-u-lb-loose").getString()); - EXPECT_EQ("ja-Jpan-JP-u-lb-normal", createLocale("ja-JP-u-lb-normal").getString()); - EXPECT_EQ("ja-Jpan-JP-u-lb-strict", createLocale("ja-JP-u-lb-strict").getString()); - EXPECT_EQ("ja-Jpan-JP-u-lb-loose", createLocale("ja-JP-u-lb-loose-em-emoji").getString()); - EXPECT_EQ("ja-Jpan-JP-u-lb-strict", createLocale("ja-JP-u-em-default-lb-strict").getString()); + // Line Break subtag is dropped from getString(). + EXPECT_EQ("ja-Jpan-JP", createLocale("ja-JP-u-lb-loose").getString()); + EXPECT_EQ("ja-Jpan-JP", createLocale("ja-JP-u-lb-normal").getString()); + EXPECT_EQ("ja-Jpan-JP", createLocale("ja-JP-u-lb-strict").getString()); + EXPECT_EQ("ja-Jpan-JP", createLocale("ja-JP-u-lb-loose-em-emoji").getString()); + EXPECT_EQ("ja-Jpan-JP", createLocale("ja-JP-u-em-default-lb-strict").getString()); EXPECT_EQ("ja-Jpan-JP", createLocale("ja-JP-u-lb-bogus").getString()); + EXPECT_EQ("ja-Jpan-JP", createLocale("ja-JP-u-lw-phrase").getString()); + EXPECT_EQ("ja-Jpan-JP", createLocale("ja-JP-u-lb-normal-lw-phrase").getString()); + EXPECT_EQ("ja-Jpan-JP", createLocale("ja-JP-u-lb-strict-lw-phrase").getString()); + EXPECT_EQ("ja-Jpan-JP", createLocale("ja-JP-u-lb-loose-lw-phrase-em-emoji").getString()); + EXPECT_EQ("ja-Jpan-JP", createLocale("ja-JP-u-em-default-lb-strict-lw-phrase").getString()); // Emoji subtag is dropped from getString(). EXPECT_EQ("es-Latn-419", createLocale("es-419-u-em-emoji").getString()); @@ -125,6 +130,29 @@ TEST(LocaleTest, getStringTest) { EXPECT_EQ("en-Latn-US", createLocale("und-Abcdefgh").getString()); } +TEST(LocaleTest, getStringWithLineBreakOptionTest) { + EXPECT_EQ("en-Latn-US", createLocale("en").getStringWithLineBreakOption( + LineBreakStyle::None, LineBreakWordStyle::None)); + EXPECT_EQ("en-Latn-US-u-lb-loose", createLocale("en").getStringWithLineBreakOption( + LineBreakStyle::Loose, LineBreakWordStyle::None)); + EXPECT_EQ("en-Latn-US-u-lb-normal", createLocale("en").getStringWithLineBreakOption( + LineBreakStyle::Normal, LineBreakWordStyle::None)); + EXPECT_EQ("en-Latn-US-u-lb-strict", createLocale("en").getStringWithLineBreakOption( + LineBreakStyle::Strict, LineBreakWordStyle::None)); + + EXPECT_EQ("en-Latn-US-u-lw-phrase", createLocale("en").getStringWithLineBreakOption( + LineBreakStyle::None, LineBreakWordStyle::Phrase)); + EXPECT_EQ("en-Latn-US-u-lb-loose-lw-phrase", + createLocale("en").getStringWithLineBreakOption(LineBreakStyle::Loose, + LineBreakWordStyle::Phrase)); + EXPECT_EQ("en-Latn-US-u-lb-normal-lw-phrase", + createLocale("en").getStringWithLineBreakOption(LineBreakStyle::Normal, + LineBreakWordStyle::Phrase)); + EXPECT_EQ("en-Latn-US-u-lb-strict-lw-phrase", + createLocale("en").getStringWithLineBreakOption(LineBreakStyle::Strict, + LineBreakWordStyle::Phrase)); +} + TEST(LocaleTest, invalidLanguageTagTest) { // just make sure no crash happens LocaleListCache::getId("ja-JP-u-lb-lb-strict"); } @@ -287,14 +315,13 @@ TEST(LocaleTest, ScriptMatchTest) { }; for (const auto& testCase : testCases) { - hb_script_t script = hb_script_from_iso15924_tag( - HB_TAG(testCase.requestedScript[0], testCase.requestedScript[1], - testCase.requestedScript[2], testCase.requestedScript[3])); + uint32_t script = HB_TAG(testCase.requestedScript[0], testCase.requestedScript[1], + testCase.requestedScript[2], testCase.requestedScript[3]); if (testCase.isSupported) { - EXPECT_TRUE(createLocale(testCase.baseScript).supportsHbScript(script)) + EXPECT_TRUE(createLocale(testCase.baseScript).supportsScript(script)) << testCase.baseScript << " should support " << testCase.requestedScript; } else { - EXPECT_FALSE(createLocale(testCase.baseScript).supportsHbScript(script)) + EXPECT_FALSE(createLocale(testCase.baseScript).supportsScript(script)) << testCase.baseScript << " shouldn't support " << testCase.requestedScript; } } diff --git a/tests/unittest/FontFeatureTest.cpp b/tests/unittest/FontFeatureTest.cpp new file mode 100644 index 0000000..7f9cdf4 --- /dev/null +++ b/tests/unittest/FontFeatureTest.cpp @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2021 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 "FontFeatureUtils.h" +#include "FontTestUtils.h" +#include "minikin/MinikinPaint.h" + +namespace minikin { + +namespace { + +constexpr hb_tag_t chws_tag = HB_TAG('c', 'h', 'w', 's'); +constexpr hb_tag_t clig_tag = HB_TAG('c', 'l', 'i', 'g'); +constexpr hb_tag_t halt_tag = HB_TAG('h', 'a', 'l', 't'); +constexpr hb_tag_t liga_tag = HB_TAG('l', 'i', 'g', 'a'); +constexpr hb_tag_t palt_tag = HB_TAG('p', 'a', 'l', 't'); +constexpr hb_tag_t ruby_tag = HB_TAG('r', 'u', 'b', 'y'); + +bool compareFeatureTag(hb_feature_t l, hb_feature_t r) { + return l.tag < r.tag; +} + +} // namespace + +class DefaultFontFeatureTest : public testing::Test { +protected: + std::shared_ptr<FontCollection> font; + + virtual void SetUp() override { font = buildFontCollection("Ascii.ttf"); } +}; + +TEST_F(DefaultFontFeatureTest, default) { + auto f = cleanAndAddDefaultFontFeatures(MinikinPaint(font)); + EXPECT_EQ(1u, f.size()); + EXPECT_EQ(chws_tag, f[0].tag); + EXPECT_TRUE(f[0].value); +} + +TEST_F(DefaultFontFeatureTest, disable) { + auto paint = MinikinPaint(font); + paint.fontFeatureSettings = "\"chws\" off"; + + auto f = cleanAndAddDefaultFontFeatures(paint); + std::sort(f.begin(), f.end(), compareFeatureTag); + + EXPECT_EQ(1u, f.size()); + EXPECT_EQ(chws_tag, f[0].tag); + EXPECT_FALSE(f[0].value); +} + +TEST_F(DefaultFontFeatureTest, preserve) { + auto paint = MinikinPaint(font); + paint.fontFeatureSettings = "\"ruby\" on"; + + auto f = cleanAndAddDefaultFontFeatures(paint); + std::sort(f.begin(), f.end(), compareFeatureTag); + + EXPECT_EQ(2u, f.size()); + EXPECT_EQ(chws_tag, f[0].tag); + EXPECT_TRUE(f[0].value); + EXPECT_EQ(ruby_tag, f[1].tag); + EXPECT_TRUE(f[1].value); +} + +TEST_F(DefaultFontFeatureTest, large_letter_spacing) { + auto paint = MinikinPaint(font); + paint.letterSpacing = 1.0; // em + + auto f = cleanAndAddDefaultFontFeatures(paint); + std::sort(f.begin(), f.end(), compareFeatureTag); + + EXPECT_EQ(3u, f.size()); + EXPECT_EQ(chws_tag, f[0].tag); + EXPECT_TRUE(f[0].value); + EXPECT_EQ(clig_tag, f[1].tag); + EXPECT_FALSE(f[1].value); + EXPECT_EQ(liga_tag, f[2].tag); + EXPECT_FALSE(f[2].value); +} + +TEST_F(DefaultFontFeatureTest, halt_disable_chws) { + auto paint = MinikinPaint(font); + paint.fontFeatureSettings = "\"halt\" on"; + + auto f = cleanAndAddDefaultFontFeatures(paint); + EXPECT_EQ(1u, f.size()); + EXPECT_EQ(halt_tag, f[0].tag); + EXPECT_TRUE(f[0].value); +} + +TEST_F(DefaultFontFeatureTest, palt_disable_chws) { + auto paint = MinikinPaint(font); + paint.fontFeatureSettings = "\"palt\" on"; + + auto f = cleanAndAddDefaultFontFeatures(paint); + EXPECT_EQ(1u, f.size()); + EXPECT_EQ(palt_tag, f[0].tag); + EXPECT_TRUE(f[0].value); +} + +TEST_F(DefaultFontFeatureTest, halt_disable_chws_large_letter_spacing) { + auto paint = MinikinPaint(font); + paint.letterSpacing = 1.0; // em + paint.fontFeatureSettings = "\"halt\" on"; + + auto f = cleanAndAddDefaultFontFeatures(paint); + std::sort(f.begin(), f.end(), compareFeatureTag); + + EXPECT_EQ(3u, f.size()); + EXPECT_EQ(clig_tag, f[0].tag); + EXPECT_FALSE(f[0].value); + EXPECT_EQ(halt_tag, f[1].tag); + EXPECT_TRUE(f[1].value); + EXPECT_EQ(liga_tag, f[2].tag); + EXPECT_FALSE(f[2].value); +} + +TEST_F(DefaultFontFeatureTest, palt_disable_chws_large_letter_spacing) { + auto paint = MinikinPaint(font); + paint.letterSpacing = 1.0; // em + paint.fontFeatureSettings = "\"palt\" on"; + + auto f = cleanAndAddDefaultFontFeatures(paint); + std::sort(f.begin(), f.end(), compareFeatureTag); + + EXPECT_EQ(3u, f.size()); + EXPECT_EQ(clig_tag, f[0].tag); + EXPECT_FALSE(f[0].value); + EXPECT_EQ(liga_tag, f[1].tag); + EXPECT_FALSE(f[1].value); + EXPECT_EQ(palt_tag, f[2].tag); + EXPECT_TRUE(f[2].value); +} + +} // namespace minikin diff --git a/tests/unittest/GreedyLineBreakerTest.cpp b/tests/unittest/GreedyLineBreakerTest.cpp index e9da1a1..3c395f4 100644 --- a/tests/unittest/GreedyLineBreakerTest.cpp +++ b/tests/unittest/GreedyLineBreakerTest.cpp @@ -79,10 +79,11 @@ protected: MinikinPaint paint(fc); paint.size = 10.0f; // Make 1em=10px paint.localeListId = LocaleListCache::getId(lang); - builder.addStyleRun(0, textBuffer.size(), std::move(paint), false); - std::unique_ptr<MeasuredText> measuredText = - builder.build(textBuffer, false /* compute hyphenation */, - false /* compute full layout */, nullptr /* no hint */); + builder.addStyleRun(0, textBuffer.size(), std::move(paint), (int)LineBreakStyle::None, + (int)LineBreakWordStyle::None, false); + std::unique_ptr<MeasuredText> measuredText = builder.build( + textBuffer, false /* compute hyphenation */, false /* compute full layout */, + false /* ignore kerning */, nullptr /* no hint */); RectangleLineWidth rectangleLineWidth(lineWidth); TabStops tabStops(nullptr, 0, 10); return breakLineGreedy(textBuffer, *measuredText, rectangleLineWidth, tabStops, @@ -108,10 +109,11 @@ TEST_F(GreedyLineBreakerTest, roundingError) { float measured = Layout::measureText(textBuffer, Range(0, textBuffer.size()), Bidi::LTR, paint, StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, nullptr); - builder.addStyleRun(0, textBuffer.size(), std::move(paint), false); - std::unique_ptr<MeasuredText> measuredText = - builder.build(textBuffer, false /* compute hyphenation */, - false /* compute full layout */, nullptr /* no hint */); + builder.addStyleRun(0, textBuffer.size(), std::move(paint), (int)LineBreakStyle::None, + (int)LineBreakWordStyle::None, false); + std::unique_ptr<MeasuredText> measuredText = builder.build( + textBuffer, false /* compute hyphenation */, false /* compute full layout */, + false /* ignore kerning */, nullptr /* no hint */); RectangleLineWidth rectangleLineWidth(measured); TabStops tabStops(nullptr, 0, 10); LineBreakResult r = breakLineGreedy(textBuffer, *measuredText, rectangleLineWidth, tabStops, @@ -703,9 +705,9 @@ TEST_F(GreedyLineBreakerTest, testZeroWidthCharacter) { MeasuredTextBuilder builder; builder.addCustomRun<ConstantRun>(Range(0, textBuf.size()), "en-US", CHAR_WIDTH, ASCENT, DESCENT); - std::unique_ptr<MeasuredText> measuredText = - builder.build(textBuf, false /* compute hyphenation */, - false /* compute full layout */, nullptr /* no hint */); + std::unique_ptr<MeasuredText> measuredText = builder.build( + textBuf, false /* compute hyphenation */, false /* compute full layout */, + false /* ignore kerning */, nullptr /* no hint */); RectangleLineWidth rectangleLineWidth(LINE_WIDTH); TabStops tabStops(nullptr, 0, 10); const auto actual = @@ -723,9 +725,9 @@ TEST_F(GreedyLineBreakerTest, testZeroWidthCharacter) { MeasuredTextBuilder builder; builder.addCustomRun<ConstantRun>(Range(0, textBuf.size()), "en-US", CHAR_WIDTH, ASCENT, DESCENT); - std::unique_ptr<MeasuredText> measuredText = - builder.build(textBuf, false /* compute hyphenation */, - false /* compute full layout */, nullptr /* no hint */); + std::unique_ptr<MeasuredText> measuredText = builder.build( + textBuf, false /* compute hyphenation */, false /* compute full layout */, + false /* ignore kerning */, nullptr /* no hint */); RectangleLineWidth rectangleLineWidth(LINE_WIDTH); TabStops tabStops(nullptr, 0, 10); const auto actual = @@ -754,9 +756,9 @@ TEST_F(GreedyLineBreakerTest, testLocaleSwitchTest) { builder.addCustomRun<ConstantRun>(Range(0, 18), "en-US", CHAR_WIDTH, ASCENT, DESCENT); builder.addCustomRun<ConstantRun>(Range(18, textBuf.size()), "en-US", CHAR_WIDTH, ASCENT, DESCENT); - std::unique_ptr<MeasuredText> measuredText = - builder.build(textBuf, false /* compute hyphenation */, - false /* compute full layout */, nullptr /* no hint */); + std::unique_ptr<MeasuredText> measuredText = builder.build( + textBuf, false /* compute hyphenation */, false /* compute full layout */, + false /* ignore kerning */, nullptr /* no hint */); RectangleLineWidth rectangleLineWidth(LINE_WIDTH); TabStops tabStops(nullptr, 0, 0); @@ -775,9 +777,9 @@ TEST_F(GreedyLineBreakerTest, testLocaleSwitchTest) { builder.addCustomRun<ConstantRun>(Range(0, 18), "en-US", CHAR_WIDTH, ASCENT, DESCENT); builder.addCustomRun<ConstantRun>(Range(18, textBuf.size()), "fr-FR", CHAR_WIDTH, ASCENT, DESCENT); - std::unique_ptr<MeasuredText> measuredText = - builder.build(textBuf, false /* compute hyphenation */, - false /* compute full layout */, nullptr /* no hint */); + std::unique_ptr<MeasuredText> measuredText = builder.build( + textBuf, false /* compute hyphenation */, false /* compute full layout */, + false /* ignore kerning */, nullptr /* no hint */); RectangleLineWidth rectangleLineWidth(LINE_WIDTH); TabStops tabStops(nullptr, 0, 0); @@ -839,9 +841,9 @@ TEST_F(GreedyLineBreakerTest, testLocaleSwitch_InEmailOrUrl) { builder.addCustomRun<ConstantRun>(Range(0, 18), "en-US", CHAR_WIDTH, ASCENT, DESCENT); builder.addCustomRun<ConstantRun>(Range(18, textBuf.size()), "fr-FR", CHAR_WIDTH, ASCENT, DESCENT); - std::unique_ptr<MeasuredText> measuredText = - builder.build(textBuf, false /* compute hyphenation */, - false /* compute full layout */, nullptr /* no hint */); + std::unique_ptr<MeasuredText> measuredText = builder.build( + textBuf, false /* compute hyphenation */, false /* compute full layout */, + false /* ignore kerning */, nullptr /* no hint */); RectangleLineWidth rectangleLineWidth(LINE_WIDTH); TabStops tabStops(nullptr, 0, 0); @@ -862,9 +864,9 @@ TEST_F(GreedyLineBreakerTest, testLocaleSwitch_InEmailOrUrl) { builder.addCustomRun<ConstantRun>(Range(0, 18), "en-US", CHAR_WIDTH, ASCENT, DESCENT); builder.addCustomRun<ConstantRun>(Range(18, textBuf.size()), "fr-FR", CHAR_WIDTH, ASCENT, DESCENT); - std::unique_ptr<MeasuredText> measuredText = - builder.build(textBuf, false /* compute hyphenation */, - false /* compute full layout */, nullptr /* no hint */); + std::unique_ptr<MeasuredText> measuredText = builder.build( + textBuf, false /* compute hyphenation */, false /* compute full layout */, + false /* ignore kerning */, nullptr /* no hint */); RectangleLineWidth rectangleLineWidth(LINE_WIDTH); TabStops tabStops(nullptr, 0, 0); @@ -893,9 +895,9 @@ TEST_F(GreedyLineBreakerTest, CrashFix_Space_Tab) { MeasuredTextBuilder builder; builder.addCustomRun<ConstantRun>(Range(0, textBuf.size()), "en-US", CHAR_WIDTH, ASCENT, DESCENT); - std::unique_ptr<MeasuredText> measuredText = - builder.build(textBuf, false /* compute hyphenation */, - false /* compute full layout */, nullptr /* no hint */); + std::unique_ptr<MeasuredText> measuredText = builder.build( + textBuf, false /* compute hyphenation */, false /* compute full layout */, + false /* ignore kerning */, nullptr /* no hint */); RectangleLineWidth rectangleLineWidth(LINE_WIDTH); TabStops tabStops(nullptr, 0, CHAR_WIDTH); @@ -1062,9 +1064,9 @@ TEST_F(GreedyLineBreakerTest, testReplacementSpanNotBreakTest_SingleChar) { builder.addCustomRun<ConstantRun>(Range(20, textBuf.size()), "en-US", CHAR_WIDTH, ASCENT, DESCENT); - std::unique_ptr<MeasuredText> measuredText = - builder.build(textBuf, false /* compute hyphenation */, - false /* compute full layout */, nullptr /* no hint */); + std::unique_ptr<MeasuredText> measuredText = builder.build( + textBuf, false /* compute hyphenation */, false /* compute full layout */, + false /* ignore kerning */, nullptr /* no hint */); RectangleLineWidth rectangleLineWidth(width); TabStops tabStops(nullptr, 0, 0); return breakLineGreedy(textBuf, *measuredText, rectangleLineWidth, tabStops, DO_HYPHEN); @@ -1157,9 +1159,9 @@ TEST_F(GreedyLineBreakerTest, testReplacementSpanNotBreakTest_MultipleChars) { builder.addCustomRun<ConstantRun>(Range(11, textBuf.size()), "en-US", CHAR_WIDTH, ASCENT, DESCENT); - std::unique_ptr<MeasuredText> measuredText = - builder.build(textBuf, false /* compute hyphenation */, - false /* compute full layout */, nullptr /* no hint */); + std::unique_ptr<MeasuredText> measuredText = builder.build( + textBuf, false /* compute hyphenation */, false /* compute full layout */, + false /* ignore kerning */, nullptr /* no hint */); RectangleLineWidth rectangleLineWidth(width); TabStops tabStops(nullptr, 0, 0); return breakLineGreedy(textBuf, *measuredText, rectangleLineWidth, tabStops, DO_HYPHEN); @@ -1245,9 +1247,9 @@ TEST_F(GreedyLineBreakerTest, testReplacementSpanNotBreakTest_CJK) { builder.addCustomRun<ConstantRun>(Range(5, textBuf.size()), "ja-JP", CHAR_WIDTH, ASCENT, DESCENT); - std::unique_ptr<MeasuredText> measuredText = - builder.build(textBuf, false /* compute hyphenation */, - false /* compute full layout */, nullptr /* no hint */); + std::unique_ptr<MeasuredText> measuredText = builder.build( + textBuf, false /* compute hyphenation */, false /* compute full layout */, + false /* ignore kerning */, nullptr /* no hint */); RectangleLineWidth rectangleLineWidth(width); TabStops tabStops(nullptr, 0, 0); return breakLineGreedy(textBuf, *measuredText, rectangleLineWidth, tabStops, DO_HYPHEN); @@ -1396,9 +1398,9 @@ TEST_F(GreedyLineBreakerTest, testReplacementSpanNotBreakTest_with_punctuation) builder.addCustomRun<ConstantRun>(Range(11, textBuf.size()), "en-US", CHAR_WIDTH, ASCENT, DESCENT); - std::unique_ptr<MeasuredText> measuredText = - builder.build(textBuf, false /* compute hyphenation */, - false /* compute full layout */, nullptr /* no hint */); + std::unique_ptr<MeasuredText> measuredText = builder.build( + textBuf, false /* compute hyphenation */, false /* compute full layout */, + false /* ignore kerning */, nullptr /* no hint */); RectangleLineWidth rectangleLineWidth(width); TabStops tabStops(nullptr, 0, 0); return breakLineGreedy(textBuf, *measuredText, rectangleLineWidth, tabStops, DO_HYPHEN); diff --git a/tests/unittest/LineBreakerTestHelper.h b/tests/unittest/LineBreakerTestHelper.h index 9d973d5..5a86175 100644 --- a/tests/unittest/LineBreakerTestHelper.h +++ b/tests/unittest/LineBreakerTestHelper.h @@ -91,6 +91,14 @@ public: const LayoutPieces&, const MinikinPaint&, uint32_t, StartHyphenEdit, EndHyphenEdit, Layout*) const {} + virtual float measureText(const U16StringPiece&) const { return 0; } + + virtual LineBreakStyle lineBreakStyle() const override { return LineBreakStyle::None; } + + virtual LineBreakWordStyle lineBreakWordStyle() const override { + return LineBreakWordStyle::None; + } + private: MinikinPaint mPaint; uint32_t mLocaleListId; diff --git a/tests/unittest/MeasuredTextTest.cpp b/tests/unittest/MeasuredTextTest.cpp index e5766a1..845b5f1 100644 --- a/tests/unittest/MeasuredTextTest.cpp +++ b/tests/unittest/MeasuredTextTest.cpp @@ -32,22 +32,24 @@ TEST(MeasuredTextTest, RunTests) { constexpr uint32_t CHAR_COUNT = 6; constexpr float REPLACEMENT_WIDTH = 20.0f; auto font = buildFontCollection("Ascii.ttf"); + int lbStyle = (int)LineBreakStyle::None; + int lbWordStyle = (int)LineBreakWordStyle::None; MeasuredTextBuilder builder; MinikinPaint paint1(font); paint1.size = 10.0f; // make 1em = 10px - builder.addStyleRun(0, 2, std::move(paint1), false /* is RTL */); + builder.addStyleRun(0, 2, std::move(paint1), lbStyle, lbWordStyle, 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 */); + builder.addStyleRun(4, 6, std::move(paint2), lbStyle, lbWordStyle, 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 */, - nullptr /* no hint */); + false /* ignore kerning */, nullptr /* no hint */); ASSERT_TRUE(measuredText); @@ -61,13 +63,15 @@ TEST(MeasuredTextTest, RunTests) { TEST(MeasuredTextTest, getBoundsTest) { auto text = utf8ToUtf16("Hello, World!"); auto font = buildFontCollection("Ascii.ttf"); + int lbStyle = (int)LineBreakStyle::None; + int lbWordStyle = (int)LineBreakWordStyle::None; MeasuredTextBuilder builder; MinikinPaint paint(font); paint.size = 10.0f; - builder.addStyleRun(0, text.size(), std::move(paint), false /* is RTL */); + builder.addStyleRun(0, text.size(), std::move(paint), lbStyle, lbWordStyle, false /* is RTL */); auto mt = builder.build(text, true /* hyphenation */, true /* full layout */, - nullptr /* no hint */); + false /* ignore kerning */, nullptr /* no hint */); EXPECT_EQ(MinikinRect(0.0f, 0.0f, 0.0f, 0.0f), mt->getBounds(text, Range(0, 0))); EXPECT_EQ(MinikinRect(0.0f, 10.0f, 10.0f, 0.0f), mt->getBounds(text, Range(0, 1))); @@ -79,13 +83,15 @@ TEST(MeasuredTextTest, getBoundsTest) { TEST(MeasuredTextTest, getBoundsTest_LTR) { auto text = utf8ToUtf16("\u0028"); // U+0028 has 1em in LTR, 3em in RTL. auto font = buildFontCollection("Bbox.ttf"); + int lbStyle = (int)LineBreakStyle::None; + int lbWordStyle = (int)LineBreakWordStyle::None; MeasuredTextBuilder builder; MinikinPaint paint(font); paint.size = 10.0f; - builder.addStyleRun(0, text.size(), std::move(paint), false /* is RTL */); + builder.addStyleRun(0, text.size(), std::move(paint), lbStyle, lbWordStyle, false /* is RTL */); auto mt = builder.build(text, true /* hyphenation */, true /* full layout */, - nullptr /* no hint */); + false /* ignore kerning */, nullptr /* no hint */); EXPECT_EQ(MinikinRect(0.0f, 10.0f, 10.0f, 0.0f), mt->getBounds(text, Range(0, 1))); } @@ -93,13 +99,15 @@ TEST(MeasuredTextTest, getBoundsTest_LTR) { TEST(MeasuredTextTest, getBoundsTest_RTL) { auto text = utf8ToUtf16("\u0028"); // U+0028 has 1em in LTR, 3em in RTL. auto font = buildFontCollection("Bbox.ttf"); + int lbStyle = (int)LineBreakStyle::None; + int lbWordStyle = (int)LineBreakWordStyle::None; MeasuredTextBuilder builder; MinikinPaint paint(font); paint.size = 10.0f; - builder.addStyleRun(0, text.size(), std::move(paint), true /* is RTL */); + builder.addStyleRun(0, text.size(), std::move(paint), lbStyle, lbWordStyle, true /* is RTL */); auto mt = builder.build(text, true /* hyphenation */, true /* full layout */, - nullptr /* no hint */); + false /* ignore kerning */, nullptr /* no hint */); EXPECT_EQ(MinikinRect(0.0f, 30.0f, 30.0f, 0.0f), mt->getBounds(text, Range(0, 2))); } @@ -108,16 +116,19 @@ TEST(MeasuredTextTest, getBoundsTest_multiStyle) { auto text = utf8ToUtf16("Hello, World!"); auto font = buildFontCollection("Ascii.ttf"); uint32_t helloLength = 7; // length of "Hello, " + int lbStyle = (int)LineBreakStyle::None; + int lbWordStyle = (int)LineBreakWordStyle::None; MeasuredTextBuilder builder; MinikinPaint paint(font); paint.size = 10.0f; - builder.addStyleRun(0, helloLength, std::move(paint), false /* is RTL */); + builder.addStyleRun(0, helloLength, std::move(paint), lbStyle, lbWordStyle, false /* is RTL */); MinikinPaint paint2(font); paint2.size = 20.0f; - builder.addStyleRun(helloLength, text.size(), std::move(paint2), false /* is RTL */); + builder.addStyleRun(helloLength, text.size(), std::move(paint2), lbStyle, lbWordStyle, + false /* is RTL */); auto mt = builder.build(text, true /* hyphenation */, true /* full layout */, - nullptr /* no hint */); + false /* ignore kerning */, nullptr /* no hint */); EXPECT_EQ(MinikinRect(0.0f, 0.0f, 0.0f, 0.0f), mt->getBounds(text, Range(0, 0))); EXPECT_EQ(MinikinRect(0.0f, 10.0f, 10.0f, 0.0f), mt->getBounds(text, Range(0, 1))); @@ -132,13 +143,15 @@ TEST(MeasuredTextTest, getBoundsTest_multiStyle) { TEST(MeasuredTextTest, getExtentTest) { auto text = utf8ToUtf16("Hello, World!"); auto font = buildFontCollection("Ascii.ttf"); + int lbStyle = (int)LineBreakStyle::None; + int lbWordStyle = (int)LineBreakWordStyle::None; MeasuredTextBuilder builder; MinikinPaint paint(font); paint.size = 10.0f; - builder.addStyleRun(0, text.size(), std::move(paint), false /* is RTL */); + builder.addStyleRun(0, text.size(), std::move(paint), lbStyle, lbWordStyle, false /* is RTL */); auto mt = builder.build(text, true /* hyphenation */, true /* full layout */, - nullptr /* no hint */); + false /* ignore kernign */, nullptr /* no hint */); EXPECT_EQ(MinikinExtent(0.0f, 0.0f), mt->getExtent(text, Range(0, 0))); EXPECT_EQ(MinikinExtent(-80.0f, 20.0f), mt->getExtent(text, Range(0, 1))); @@ -151,16 +164,19 @@ TEST(MeasuredTextTest, getExtentTest_multiStyle) { auto text = utf8ToUtf16("Hello, World!"); auto font = buildFontCollection("Ascii.ttf"); uint32_t helloLength = 7; // length of "Hello, " + int lbStyle = (int)LineBreakStyle::None; + int lbWordStyle = (int)LineBreakWordStyle::None; MeasuredTextBuilder builder; MinikinPaint paint(font); paint.size = 10.0f; - builder.addStyleRun(0, helloLength, std::move(paint), false /* is RTL */); + builder.addStyleRun(0, helloLength, std::move(paint), lbStyle, lbWordStyle, false /* is RTL */); MinikinPaint paint2(font); paint2.size = 20.0f; - builder.addStyleRun(helloLength, text.size(), std::move(paint2), false /* is RTL */); + builder.addStyleRun(helloLength, text.size(), std::move(paint2), 0 /* no line break */, + 0 /* no line break word style */, false /* is RTL */); auto mt = builder.build(text, true /* hyphenation */, true /* full layout */, - nullptr /* no hint */); + false /* ignore kerning */, nullptr /* no hint */); EXPECT_EQ(MinikinExtent(0.0f, 0.0f), mt->getExtent(text, Range(0, 0))); EXPECT_EQ(MinikinExtent(-80.0f, 20.0f), mt->getExtent(text, Range(0, 1))); @@ -176,13 +192,15 @@ TEST(MeasuredTextTest, buildLayoutTest) { auto text = utf8ToUtf16("Hello, World!"); auto font = buildFontCollection("Ascii.ttf"); Range fullContext(0, text.size()); + int lbStyle = (int)LineBreakStyle::None; + int lbWordStyle = (int)LineBreakWordStyle::None; MeasuredTextBuilder builder; MinikinPaint paint(font); paint.size = 10.0f; - builder.addStyleRun(0, text.size(), std::move(paint), false /* is RTL */); + builder.addStyleRun(0, text.size(), std::move(paint), lbStyle, lbWordStyle, false /* is RTL */); auto mt = builder.build(text, true /* hyphenation */, true /* full layout */, - nullptr /* no hint */); + false /* ignore kerning */, nullptr /* no hint */); MinikinRect rect; MinikinPaint samePaint(font); @@ -256,16 +274,19 @@ TEST(MeasuredTextTest, buildLayoutTest_multiStyle) { auto font = buildFontCollection("Ascii.ttf"); uint32_t helloLength = 7; // length of "Hello, " Range fullContext(0, text.size()); + int lbStyle = (int)LineBreakStyle::None; + int lbWordStyle = (int)LineBreakWordStyle::None; MeasuredTextBuilder builder; MinikinPaint paint(font); paint.size = 10.0f; - builder.addStyleRun(0, helloLength, std::move(paint), false /* is RTL */); + builder.addStyleRun(0, helloLength, std::move(paint), lbStyle, lbWordStyle, false /* is RTL */); MinikinPaint paint2(font); paint2.size = 20.0f; - builder.addStyleRun(helloLength, text.size(), std::move(paint2), false /* is RTL */); + builder.addStyleRun(helloLength, text.size(), std::move(paint2), lbStyle, lbWordStyle, + false /* is RTL */); auto mt = builder.build(text, true /* hyphenation */, true /* full layout */, - nullptr /* no hint */); + false /* ignore kerning */, nullptr /* no hint */); MinikinRect rect; MinikinPaint samePaint(font); @@ -341,13 +362,15 @@ TEST(MeasuredTextTest, buildLayoutTest_differentPaint) { auto text = utf8ToUtf16("Hello, World!"); auto font = buildFontCollection("Ascii.ttf"); Range fullContext(0, text.size()); + int lbStyle = (int)LineBreakStyle::None; + int lbWordStyle = (int)LineBreakWordStyle::None; MeasuredTextBuilder builder; MinikinPaint paint(font); paint.size = 10.0f; - builder.addStyleRun(0, text.size(), std::move(paint), false /* is RTL */); + builder.addStyleRun(0, text.size(), std::move(paint), lbStyle, lbWordStyle, false /* is RTL */); auto mt = builder.build(text, true /* hyphenation */, true /* full layout */, - nullptr /* no hint */); + false /* ignore kerning */, nullptr /* no hint */); MinikinRect rect; MinikinPaint differentPaint(font); @@ -421,16 +444,19 @@ TEST(MeasuredTextTest, buildLayoutTest_multiStyle_differentPaint) { auto font = buildFontCollection("Ascii.ttf"); uint32_t helloLength = 7; // length of "Hello, " Range fullContext(0, text.size()); + int lbStyle = (int)LineBreakStyle::None; + int lbWordStyle = (int)LineBreakWordStyle::None; MeasuredTextBuilder builder; MinikinPaint paint(font); paint.size = 10.0f; - builder.addStyleRun(0, helloLength, std::move(paint), false /* is RTL */); + builder.addStyleRun(0, helloLength, std::move(paint), lbStyle, lbWordStyle, false /* is RTL */); MinikinPaint paint2(font); paint2.size = 20.0f; - builder.addStyleRun(helloLength, text.size(), std::move(paint2), false /* is RTL */); + builder.addStyleRun(helloLength, text.size(), std::move(paint2), lbStyle, lbWordStyle, + false /* is RTL */); auto mt = builder.build(text, true /* hyphenation */, true /* full layout */, - nullptr /* no hint */); + false /* ignore kerning */, nullptr /* no hint */); MinikinRect rect; MinikinPaint differentPaint(font); @@ -532,4 +558,63 @@ TEST(MeasuredTextTest, buildLayoutTest_multiStyle_differentPaint) { EXPECT_EQ(MinikinRect(0.0f, 30.0f, 390.0f, 0.0f), rect); } +TEST(MeasuredTextTest, testLineBreakStyle_from_builder) { + auto text = utf8ToUtf16("Hello, World!"); + auto font = buildFontCollection("Ascii.ttf"); + int lbStyle = (int)LineBreakStyle::Loose; // loose + int lbWordStyle = (int)LineBreakWordStyle::Phrase; // phrase + + MeasuredTextBuilder looseStyleBuilder; + MinikinPaint paint(font); + looseStyleBuilder.addStyleRun(0, text.size(), std::move(paint), lbStyle, lbWordStyle, false); + auto mt = looseStyleBuilder.build(text, true /* hyphenation */, true /* full layout */, + false /* ignore kerning */, nullptr /* no hint */); + + EXPECT_EQ((size_t)1, mt->runs.size()); + EXPECT_EQ(LineBreakStyle::Loose, mt->runs[0]->lineBreakStyle()); + EXPECT_EQ(LineBreakWordStyle::Phrase, mt->runs[0]->lineBreakWordStyle()); + + lbStyle = (int)LineBreakStyle::Normal; // normal + MeasuredTextBuilder normalStyleBuilder; + MinikinPaint normalStylePaint(font); + normalStyleBuilder.addStyleRun(0, text.size(), std::move(normalStylePaint), lbStyle, + lbWordStyle, false); + mt = normalStyleBuilder.build(text, true /* hyphenation */, true /* full layout */, + false /* ignore kerning */, nullptr /* no hint */); + + EXPECT_EQ((size_t)1, mt->runs.size()); + EXPECT_EQ(LineBreakStyle::Normal, mt->runs[0]->lineBreakStyle()); + EXPECT_EQ(LineBreakWordStyle::Phrase, mt->runs[0]->lineBreakWordStyle()); + + lbStyle = (int)LineBreakStyle::Strict; // strict + lbWordStyle = (int)LineBreakWordStyle::None; // no word style + MeasuredTextBuilder strictStyleBuilder; + MinikinPaint strictStylePaint(font); + strictStyleBuilder.addStyleRun(0, text.size(), std::move(strictStylePaint), lbStyle, + lbWordStyle, false); + mt = strictStyleBuilder.build(text, true /* hyphenation */, true /* full layout */, + false /* ignore kerning */, nullptr /* no hint */); + + EXPECT_EQ((size_t)1, mt->runs.size()); + EXPECT_EQ(LineBreakStyle::Strict, mt->runs[0]->lineBreakStyle()); + EXPECT_EQ(LineBreakWordStyle::None, mt->runs[0]->lineBreakWordStyle()); +} + +TEST(MeasuredTextTest, testLineBreakStyle_from_run) { + auto text = utf8ToUtf16("Hello, World!"); + auto font = buildFontCollection("Ascii.ttf"); + int lbStyle = (int)LineBreakStyle::Strict; + int lbWordStyle = (int)LineBreakWordStyle::Phrase; + Range range(0, text.size()); + MinikinPaint paint(font); + + StyleRun styleRun(range, std::move(paint), lbStyle, lbWordStyle, false /* isRtl */); + EXPECT_EQ(LineBreakStyle::Strict, styleRun.lineBreakStyle()); + EXPECT_EQ(LineBreakWordStyle::Phrase, styleRun.lineBreakWordStyle()); + + ReplacementRun replacementRun(range, 10.0f /* width */, 0 /* locale list id */); + EXPECT_EQ(LineBreakStyle::None, replacementRun.lineBreakStyle()); + EXPECT_EQ(LineBreakWordStyle::None, replacementRun.lineBreakWordStyle()); +} + } // namespace minikin diff --git a/tests/unittest/OptimalLineBreakerTest.cpp b/tests/unittest/OptimalLineBreakerTest.cpp index d6801cc..a5f8625 100644 --- a/tests/unittest/OptimalLineBreakerTest.cpp +++ b/tests/unittest/OptimalLineBreakerTest.cpp @@ -65,25 +65,21 @@ public: protected: LineBreakResult doLineBreak(const U16StringPiece& textBuffer, BreakStrategy strategy, - HyphenationFrequency frequency, float lineWidth) { - return doLineBreak(textBuffer, strategy, frequency, "en-US", lineWidth); - } - - LineBreakResult doLineBreak(const U16StringPiece& textBuffer, BreakStrategy strategy, HyphenationFrequency frequency, const std::string& lang, - float lineWidth) { + float lineWidth, bool ignoreKerning) { MeasuredTextBuilder builder; auto family1 = buildFontFamily("Ascii.ttf"); auto family2 = buildFontFamily("CustomExtent.ttf"); std::vector<std::shared_ptr<FontFamily>> families = {family1, family2}; auto fc = std::make_shared<FontCollection>(families); MinikinPaint paint(fc); - paint.size = 10.0f; // Make 1em=1px + paint.size = 10.0f; // Make 1em=10px paint.localeListId = LocaleListCache::getId(lang); - builder.addStyleRun(0, textBuffer.size(), std::move(paint), false); + builder.addStyleRun(0, textBuffer.size(), std::move(paint), 0, 0, false); + bool computeHyphen = frequency != HyphenationFrequency::None; std::unique_ptr<MeasuredText> measuredText = - builder.build(textBuffer, true /* compute hyphenation */, - false /* compute full layout */, nullptr /* no hint */); + builder.build(textBuffer, computeHyphen, false /* compute full layout */, + ignoreKerning, nullptr /* no hint */); return doLineBreak(textBuffer, *measuredText, strategy, frequency, lineWidth); } @@ -95,6 +91,33 @@ protected: false /* justified */); } + void expectBreak(const std::vector<LineBreakExpectation>& expect, + const U16StringPiece& textBuffer, BreakStrategy strategy, + HyphenationFrequency frequency, const std::string& lang, float lineWidth) { + { + char msg[256] = {}; + snprintf(msg, 256, "width = %f, lang = %s, strategy = %u, frequency = %u, fullyHyphen", + lineWidth, lang.c_str(), (uint32_t)strategy, (uint32_t)frequency); + SCOPED_TRACE(msg); + auto actual = doLineBreak(textBuffer, strategy, frequency, lang, lineWidth, + false /* ignoreKerning */); + EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl + << " vs " << std::endl + << toString(textBuffer, actual); + } + { + char msg[256] = {}; + snprintf(msg, 256, "width = %f, lang = %s, strategy = %u, frequency = %u, fullyHyphen", + lineWidth, lang.c_str(), (uint32_t)strategy, (uint32_t)frequency); + SCOPED_TRACE(msg); + auto actual = doLineBreak(textBuffer, strategy, frequency, lang, lineWidth, + true /* ignoreKerning */); + EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl + << " vs " << std::endl + << toString(textBuffer, actual); + } + } + private: std::vector<uint8_t> mHyphenationPattern; }; @@ -116,44 +139,20 @@ TEST_F(OptimalLineBreakerTest, testBreakWithoutHyphenation) { {"This is an example text.", 240, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT}, }; - auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); - actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); - actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); - actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); + expectBreak(expect, textBuf, HIGH_QUALITY, NO_HYPHENATION, "en-US", LINE_WIDTH); + expectBreak(expect, textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, "en-US", LINE_WIDTH); + expectBreak(expect, textBuf, BALANCED, NO_HYPHENATION, "en-US", LINE_WIDTH); + expectBreak(expect, textBuf, BALANCED, NORMAL_HYPHENATION, "en-US", LINE_WIDTH); } { constexpr float LINE_WIDTH = 240; std::vector<LineBreakExpectation> expect = { {"This is an example text.", 240, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT}, }; - auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); - actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); - actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); - actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); + expectBreak(expect, textBuf, HIGH_QUALITY, NO_HYPHENATION, "en-US", LINE_WIDTH); + expectBreak(expect, textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, "en-US", LINE_WIDTH); + expectBreak(expect, textBuf, BALANCED, NO_HYPHENATION, "en-US", LINE_WIDTH); + expectBreak(expect, textBuf, BALANCED, NORMAL_HYPHENATION, "en-US", LINE_WIDTH); } { constexpr float LINE_WIDTH = 230; @@ -164,14 +163,8 @@ TEST_F(OptimalLineBreakerTest, testBreakWithoutHyphenation) { }; // clang-format on - auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); - actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); + expectBreak(expect, textBuf, HIGH_QUALITY, NO_HYPHENATION, "en-US", LINE_WIDTH); + expectBreak(expect, textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, "en-US", LINE_WIDTH); // clang-format off expect = { @@ -179,21 +172,17 @@ TEST_F(OptimalLineBreakerTest, testBreakWithoutHyphenation) { { "example text." , 130, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT }, }; // clang-format on - actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); + expectBreak(expect, textBuf, BALANCED, NO_HYPHENATION, "en-US", LINE_WIDTH); + // clang-format off expect = { { "This is an ex-" , 140, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT }, { "ample text." , 110, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT }, }; // clang-format on - actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); + expectBreak(expect, textBuf, BALANCED, NORMAL_HYPHENATION, "en-US", LINE_WIDTH); } + return; { constexpr float LINE_WIDTH = 170; // clang-format off @@ -203,40 +192,31 @@ TEST_F(OptimalLineBreakerTest, testBreakWithoutHyphenation) { }; // clang-format on - auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); + expectBreak(expect, textBuf, HIGH_QUALITY, NO_HYPHENATION, "en-US", LINE_WIDTH); + // clang-format off expect = { { "This is an exam-" , 160, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT }, { "ple text." , 90, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT }, }; // clang-format on - actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); + expectBreak(expect, textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, "en-US", LINE_WIDTH); + // clang-format off expect = { { "This is an " , 100, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT }, { "example text." , 130, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT }, }; // clang-format on - actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); + expectBreak(expect, textBuf, BALANCED, NO_HYPHENATION, "en-US", LINE_WIDTH); + // clang-format off expect = { { "This is an ex-", 140, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT }, { "ample text." , 110, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT }, }; // clang-format on - actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); + expectBreak(expect, textBuf, BALANCED, NORMAL_HYPHENATION, "en-US", LINE_WIDTH); } { constexpr float LINE_WIDTH = 160; @@ -246,41 +226,31 @@ TEST_F(OptimalLineBreakerTest, testBreakWithoutHyphenation) { { "example text." , 130, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT }, }; // clang-format on + expectBreak(expect, textBuf, HIGH_QUALITY, NO_HYPHENATION, "en-US", LINE_WIDTH); - auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_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 exam-" , 160, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT }, { "ple text." , 90, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT }, }; // clang-format on - actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); + expectBreak(expect, textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, "en-US", LINE_WIDTH); + // clang-format off expect = { { "This is an " , 100, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT }, { "example text." , 130, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT }, }; // clang-format on - actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); + expectBreak(expect, textBuf, BALANCED, NO_HYPHENATION, "en-US", LINE_WIDTH); + // clang-format off expect = { { "This is an ex-", 140, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT }, { "ample text." , 110, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT }, }; // clang-format on - actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); + expectBreak(expect, textBuf, BALANCED, NORMAL_HYPHENATION, "en-US", LINE_WIDTH); } { constexpr float LINE_WIDTH = 150; @@ -290,41 +260,31 @@ TEST_F(OptimalLineBreakerTest, testBreakWithoutHyphenation) { { "example text." , 130, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT }, }; // clang-format on + expectBreak(expect, textBuf, HIGH_QUALITY, NO_HYPHENATION, "en-US", LINE_WIDTH); - auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_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 ex-", 140, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT }, { "ample text." , 110, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT }, }; // clang-format on - actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); + expectBreak(expect, textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, "en-US", LINE_WIDTH); + // clang-format off expect = { { "This is an " , 100, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT }, { "example text." , 130, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT }, }; // clang-format on - actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); + expectBreak(expect, textBuf, BALANCED, NO_HYPHENATION, "en-US", LINE_WIDTH); + // clang-format off expect = { { "This is an ex-", 140, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT }, { "ample text." , 110, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT }, }; // clang-format on - actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); + expectBreak(expect, textBuf, BALANCED, NORMAL_HYPHENATION, "en-US", LINE_WIDTH); } { constexpr float LINE_WIDTH = 130; @@ -335,22 +295,10 @@ TEST_F(OptimalLineBreakerTest, testBreakWithoutHyphenation) { }; // clang-format on - auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); - actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); - actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); - actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); + expectBreak(expect, textBuf, HIGH_QUALITY, NO_HYPHENATION, "en-US", LINE_WIDTH); + expectBreak(expect, textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, "en-US", LINE_WIDTH); + expectBreak(expect, textBuf, BALANCED, NO_HYPHENATION, "en-US", LINE_WIDTH); + expectBreak(expect, textBuf, BALANCED, NORMAL_HYPHENATION, "en-US", LINE_WIDTH); } { constexpr float LINE_WIDTH = 120; @@ -362,18 +310,10 @@ TEST_F(OptimalLineBreakerTest, testBreakWithoutHyphenation) { }; // clang-format on - auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); - actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); - actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); + expectBreak(expect, textBuf, HIGH_QUALITY, NO_HYPHENATION, "en-US", LINE_WIDTH); + expectBreak(expect, textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, "en-US", LINE_WIDTH); + expectBreak(expect, textBuf, BALANCED, NO_HYPHENATION, "en-US", LINE_WIDTH); + // clang-format off expect = { { "This is " , 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT }, @@ -381,10 +321,8 @@ TEST_F(OptimalLineBreakerTest, testBreakWithoutHyphenation) { { "ple text.", 90, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT }, }; // clang-format on - actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); + + expectBreak(expect, textBuf, BALANCED, NORMAL_HYPHENATION, "en-US", LINE_WIDTH); } { constexpr float LINE_WIDTH = 90; @@ -396,11 +334,8 @@ TEST_F(OptimalLineBreakerTest, testBreakWithoutHyphenation) { { "text." , 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT }, }; // clang-format on + expectBreak(expect, textBuf, HIGH_QUALITY, NO_HYPHENATION, "en-US", LINE_WIDTH); - auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); // clang-format off expect = { { "This is " , 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT }, @@ -408,10 +343,8 @@ TEST_F(OptimalLineBreakerTest, testBreakWithoutHyphenation) { { "ple text.", 90, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT }, }; // clang-format on - actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); + expectBreak(expect, textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, "en-US", LINE_WIDTH); + // clang-format off expect = { { "This " , 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT }, @@ -420,10 +353,8 @@ TEST_F(OptimalLineBreakerTest, testBreakWithoutHyphenation) { { "text." , 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT }, }; // clang-format on - actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); + expectBreak(expect, textBuf, BALANCED, NO_HYPHENATION, "en-US", LINE_WIDTH); + // clang-format off expect = { { "This is " , 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT }, @@ -431,10 +362,7 @@ TEST_F(OptimalLineBreakerTest, testBreakWithoutHyphenation) { { "ple text.", 90, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT }, }; // clang-format on - actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); + expectBreak(expect, textBuf, BALANCED, NORMAL_HYPHENATION, "en-US", LINE_WIDTH); } { constexpr float LINE_WIDTH = 80; @@ -447,14 +375,9 @@ TEST_F(OptimalLineBreakerTest, testBreakWithoutHyphenation) { }; // clang-format on - auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); - actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); + expectBreak(expect, textBuf, HIGH_QUALITY, NO_HYPHENATION, "en-US", LINE_WIDTH); + expectBreak(expect, textBuf, BALANCED, NO_HYPHENATION, "en-US", LINE_WIDTH); + // clang-format off expect = { { "This is ", 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT }, @@ -463,14 +386,8 @@ TEST_F(OptimalLineBreakerTest, testBreakWithoutHyphenation) { { "text." , 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT }, }; // clang-format on - actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); - actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); + expectBreak(expect, textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, "en-US", LINE_WIDTH); + expectBreak(expect, textBuf, BALANCED, NORMAL_HYPHENATION, "en-US", LINE_WIDTH); } { constexpr float LINE_WIDTH = 70; @@ -483,14 +400,8 @@ TEST_F(OptimalLineBreakerTest, testBreakWithoutHyphenation) { }; // clang-format on - auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); - actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); + expectBreak(expect, textBuf, HIGH_QUALITY, NO_HYPHENATION, "en-US", LINE_WIDTH); + expectBreak(expect, textBuf, BALANCED, NO_HYPHENATION, "en-US", LINE_WIDTH); // clang-format off expect = { { "This is ", 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT }, @@ -499,14 +410,8 @@ TEST_F(OptimalLineBreakerTest, testBreakWithoutHyphenation) { { "text." , 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT }, }; // clang-format on - actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); - actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); + expectBreak(expect, textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, "en-US", LINE_WIDTH); + expectBreak(expect, textBuf, BALANCED, NORMAL_HYPHENATION, "en-US", LINE_WIDTH); } { constexpr float LINE_WIDTH = 60; @@ -521,14 +426,8 @@ TEST_F(OptimalLineBreakerTest, testBreakWithoutHyphenation) { }; // clang-format on - auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); - actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); + expectBreak(expect, textBuf, HIGH_QUALITY, NO_HYPHENATION, "en-US", LINE_WIDTH); + expectBreak(expect, textBuf, BALANCED, NO_HYPHENATION, "en-US", LINE_WIDTH); // clang-format off expect = { { "This " , 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT }, @@ -538,14 +437,8 @@ TEST_F(OptimalLineBreakerTest, testBreakWithoutHyphenation) { { "text." , 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT }, }; // clang-format on - actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); - actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); + expectBreak(expect, textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, "en-US", LINE_WIDTH); + expectBreak(expect, textBuf, BALANCED, NORMAL_HYPHENATION, "en-US", LINE_WIDTH); } { constexpr float LINE_WIDTH = 50; @@ -560,14 +453,8 @@ TEST_F(OptimalLineBreakerTest, testBreakWithoutHyphenation) { }; // clang-format on - auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); - actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); + expectBreak(expect, textBuf, HIGH_QUALITY, NO_HYPHENATION, "en-US", LINE_WIDTH); + expectBreak(expect, textBuf, BALANCED, NO_HYPHENATION, "en-US", LINE_WIDTH); // clang-format off expect = { { "This " , 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT }, @@ -577,14 +464,8 @@ TEST_F(OptimalLineBreakerTest, testBreakWithoutHyphenation) { { "text." , 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT }, }; // clang-format on - actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); - actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); + expectBreak(expect, textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, "en-US", LINE_WIDTH); + expectBreak(expect, textBuf, BALANCED, NORMAL_HYPHENATION, "en-US", LINE_WIDTH); } { constexpr float LINE_WIDTH = 40; @@ -600,11 +481,8 @@ TEST_F(OptimalLineBreakerTest, testBreakWithoutHyphenation) { { "." , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT }, }; // clang-format on + expectBreak(expect, textBuf, HIGH_QUALITY, NO_HYPHENATION, "en-US", LINE_WIDTH); - auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); // clang-format off expect = { { "This " , 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT }, @@ -618,10 +496,8 @@ TEST_F(OptimalLineBreakerTest, testBreakWithoutHyphenation) { { "ext." , 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT }, }; // clang-format on - actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); + expectBreak(expect, textBuf, BALANCED, NO_HYPHENATION, "en-US", LINE_WIDTH); + // clang-format off expect = { { "This ", 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT }, @@ -634,10 +510,7 @@ TEST_F(OptimalLineBreakerTest, testBreakWithoutHyphenation) { { "." , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT }, }; // clang-format on - actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); + expectBreak(expect, textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, "en-US", LINE_WIDTH); // clang-format off expect = { { "This ", 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT }, @@ -651,10 +524,7 @@ TEST_F(OptimalLineBreakerTest, testBreakWithoutHyphenation) { { "xt." , 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT }, }; // clang-format on - actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); + expectBreak(expect, textBuf, BALANCED, NORMAL_HYPHENATION, "en-US", LINE_WIDTH); } { constexpr float LINE_WIDTH = 30; @@ -673,11 +543,8 @@ TEST_F(OptimalLineBreakerTest, testBreakWithoutHyphenation) { { "t." , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT }, }; // clang-format on + expectBreak(expect, textBuf, HIGH_QUALITY, NO_HYPHENATION, "en-US", LINE_WIDTH); - auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, 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? @@ -694,10 +561,8 @@ TEST_F(OptimalLineBreakerTest, testBreakWithoutHyphenation) { { "xt." , 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT }, }; // clang-format on - actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); + expectBreak(expect, textBuf, BALANCED, NO_HYPHENATION, "en-US", LINE_WIDTH); + // clang-format off expect = { // TODO: Is this desperate break working correctly? @@ -712,10 +577,8 @@ TEST_F(OptimalLineBreakerTest, testBreakWithoutHyphenation) { { "t." , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT }, }; // clang-format on - actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); + expectBreak(expect, textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, "en-US", LINE_WIDTH); + // clang-format off expect = { // TODO: Is this desperate break working correctly? @@ -731,10 +594,7 @@ TEST_F(OptimalLineBreakerTest, testBreakWithoutHyphenation) { {"xt." , 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT}, }; // clang-format on - actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); + expectBreak(expect, textBuf, BALANCED, NORMAL_HYPHENATION, "en-US", LINE_WIDTH); } { constexpr float LINE_WIDTH = 20; @@ -754,15 +614,9 @@ TEST_F(OptimalLineBreakerTest, testBreakWithoutHyphenation) { { "." , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT }, }; // clang-format on + expectBreak(expect, textBuf, HIGH_QUALITY, NO_HYPHENATION, "en-US", LINE_WIDTH); + expectBreak(expect, textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, "en-US", LINE_WIDTH); - auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); - actual = doLineBreak(textBuf, 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 = { { "Th" , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT }, @@ -780,14 +634,8 @@ TEST_F(OptimalLineBreakerTest, testBreakWithoutHyphenation) { { "t." , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT }, }; // clang-format on - actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); - actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); + expectBreak(expect, textBuf, BALANCED, NO_HYPHENATION, "en-US", LINE_WIDTH); + expectBreak(expect, textBuf, BALANCED, NORMAL_HYPHENATION, "en-US", LINE_WIDTH); } { constexpr float LINE_WIDTH = 10; @@ -816,22 +664,10 @@ TEST_F(OptimalLineBreakerTest, testBreakWithoutHyphenation) { }; // clang-format on - auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); - actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); - actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); - actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); + expectBreak(expect, textBuf, HIGH_QUALITY, NO_HYPHENATION, "en-US", LINE_WIDTH); + expectBreak(expect, textBuf, BALANCED, NO_HYPHENATION, "en-US", LINE_WIDTH); + expectBreak(expect, textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, "en-US", LINE_WIDTH); + expectBreak(expect, textBuf, BALANCED, NORMAL_HYPHENATION, "en-US", LINE_WIDTH); } } @@ -852,11 +688,7 @@ TEST_F(OptimalLineBreakerTest, testHyphenationStartLineChange) { {"czerwono-niebieska", 180, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT}, }; - const auto actual = - doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, "pl", LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); + expectBreak(expect, textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, "pl", LINE_WIDTH); } { constexpr float LINE_WIDTH = 180; @@ -864,11 +696,7 @@ TEST_F(OptimalLineBreakerTest, testHyphenationStartLineChange) { {"czerwono-niebieska", 180, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT}, }; - const auto actual = - doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, "pl", LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); + expectBreak(expect, textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, "pl", LINE_WIDTH); } { constexpr float LINE_WIDTH = 130; @@ -879,11 +707,7 @@ TEST_F(OptimalLineBreakerTest, testHyphenationStartLineChange) { }; // clang-format on - const auto actual = - doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, "pl", LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); + expectBreak(expect, textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, "pl", LINE_WIDTH); } } @@ -898,20 +722,14 @@ TEST_F(OptimalLineBreakerTest, testZeroWidthLine) { { const auto textBuf = utf8ToUtf16(""); std::vector<LineBreakExpectation> expect = {}; - const auto actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); + expectBreak(expect, textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, "en-US", LINE_WIDTH); } { const auto textBuf = utf8ToUtf16("A"); std::vector<LineBreakExpectation> expect = { {"A", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT}, }; - const auto actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); + expectBreak(expect, textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, "en-US", LINE_WIDTH); } { const auto textBuf = utf8ToUtf16("AB"); @@ -919,10 +737,7 @@ TEST_F(OptimalLineBreakerTest, testZeroWidthLine) { {"A", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT}, {"B", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT}, }; - const auto actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); + expectBreak(expect, textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, "en-US", LINE_WIDTH); } } @@ -942,9 +757,9 @@ TEST_F(OptimalLineBreakerTest, testZeroWidthCharacter) { MeasuredTextBuilder builder; builder.addCustomRun<ConstantRun>(Range(0, textBuf.size()), "en-US", CHAR_WIDTH, ASCENT, DESCENT); - std::unique_ptr<MeasuredText> measuredText = - builder.build(textBuf, true /* compute hyphenation */, - false /* compute full layout */, nullptr /* no hint */); + std::unique_ptr<MeasuredText> measuredText = builder.build( + textBuf, true /* compute hyphenation */, false /* compute full layout */, + false /* ignore kerning */, nullptr /* no hint */); const auto actual = doLineBreak(textBuf, *measuredText, HIGH_QUALITY, NORMAL_HYPHENATION, LINE_WIDTH); @@ -961,9 +776,9 @@ TEST_F(OptimalLineBreakerTest, testZeroWidthCharacter) { MeasuredTextBuilder builder; builder.addCustomRun<ConstantRun>(Range(0, textBuf.size()), "en-US", CHAR_WIDTH, ASCENT, DESCENT); - std::unique_ptr<MeasuredText> measuredText = - builder.build(textBuf, true /* compute hyphenation */, - false /* compute full layout */, nullptr /* no hint */); + std::unique_ptr<MeasuredText> measuredText = builder.build( + textBuf, true /* compute hyphenation */, false /* compute full layout */, + false /* ignore kerning */, nullptr /* no hint */); const auto actual = doLineBreak(textBuf, *measuredText, HIGH_QUALITY, NORMAL_HYPHENATION, LINE_WIDTH); @@ -992,9 +807,9 @@ TEST_F(OptimalLineBreakerTest, testLocaleSwitchTest) { builder.addCustomRun<ConstantRun>(Range(0, 18), "en-US", CHAR_WIDTH, ASCENT, DESCENT); builder.addCustomRun<ConstantRun>(Range(18, textBuf.size()), "en-US", CHAR_WIDTH, ASCENT, DESCENT); - std::unique_ptr<MeasuredText> measuredText = - builder.build(textBuf, true /* compute hyphenation */, - false /* compute full layout */, nullptr /* no hint */); + std::unique_ptr<MeasuredText> measuredText = builder.build( + textBuf, true /* compute hyphenation */, false /* compute full layout */, + false /* ignore kerning */, nullptr /* no hint */); const auto actual = doLineBreak(textBuf, *measuredText, HIGH_QUALITY, NORMAL_HYPHENATION, LINE_WIDTH); @@ -1011,9 +826,9 @@ TEST_F(OptimalLineBreakerTest, testLocaleSwitchTest) { builder.addCustomRun<ConstantRun>(Range(0, 18), "en-US", CHAR_WIDTH, ASCENT, DESCENT); builder.addCustomRun<ConstantRun>(Range(18, textBuf.size()), "fr-FR", CHAR_WIDTH, ASCENT, DESCENT); - std::unique_ptr<MeasuredText> measuredText = - builder.build(textBuf, true /* compute hyphenation */, - false /* compute full layout */, nullptr /* no hint */); + std::unique_ptr<MeasuredText> measuredText = builder.build( + textBuf, true /* compute hyphenation */, false /* compute full layout */, + false /* ignore kerning */, nullptr /* no hint */); const auto actual = doLineBreak(textBuf, *measuredText, HIGH_QUALITY, NORMAL_HYPHENATION, LINE_WIDTH); EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl @@ -1040,28 +855,17 @@ TEST_F(OptimalLineBreakerTest, testEmailOrUrl) { {".b", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT}, }; // clang-format on - auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); - actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); + expectBreak(expect, textBuf, HIGH_QUALITY, NO_HYPHENATION, "en-US", LINE_WIDTH); + expectBreak(expect, textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, "en-US", LINE_WIDTH); + // clang-format off expect = { {"This is an url: ", 150, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT}, {"http://a.b", 100, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT}, }; // clang-format on - actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); - actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); + expectBreak(expect, textBuf, BALANCED, NO_HYPHENATION, "en-US", LINE_WIDTH); + expectBreak(expect, textBuf, BALANCED, NORMAL_HYPHENATION, "en-US", LINE_WIDTH); } { constexpr float LINE_WIDTH = 240; @@ -1073,22 +877,10 @@ TEST_F(OptimalLineBreakerTest, testEmailOrUrl) { }; // clang-format on - auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); - actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); - actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); - actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); + expectBreak(expect, textBuf, HIGH_QUALITY, NO_HYPHENATION, "en-US", LINE_WIDTH); + expectBreak(expect, textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, "en-US", LINE_WIDTH); + expectBreak(expect, textBuf, BALANCED, NO_HYPHENATION, "en-US", LINE_WIDTH); + expectBreak(expect, textBuf, BALANCED, NORMAL_HYPHENATION, "en-US", LINE_WIDTH); } } @@ -1109,9 +901,9 @@ TEST_F(OptimalLineBreakerTest, testLocaleSwitch_InEmailOrUrl) { builder.addCustomRun<ConstantRun>(Range(0, 18), "en-US", CHAR_WIDTH, ASCENT, DESCENT); builder.addCustomRun<ConstantRun>(Range(18, textBuf.size()), "fr-FR", CHAR_WIDTH, ASCENT, DESCENT); - std::unique_ptr<MeasuredText> measured = - builder.build(textBuf, true /* compute hyphenation */, - false /* compute full layout */, nullptr /* no hint */); + std::unique_ptr<MeasuredText> measured = builder.build( + textBuf, true /* compute hyphenation */, false /* compute full layout */, + false /* ignore kerning */, nullptr /* no hint */); // clang-format off std::vector<LineBreakExpectation> expect = { @@ -1152,9 +944,9 @@ TEST_F(OptimalLineBreakerTest, testLocaleSwitch_InEmailOrUrl) { builder.addCustomRun<ConstantRun>(Range(0, 18), "en-US", CHAR_WIDTH, ASCENT, DESCENT); builder.addCustomRun<ConstantRun>(Range(18, textBuf.size()), "fr-FR", CHAR_WIDTH, ASCENT, DESCENT); - std::unique_ptr<MeasuredText> measured = - builder.build(textBuf, true /* compute hyphenation */, - false /* compute full layout */, nullptr /* no hint */); + std::unique_ptr<MeasuredText> measured = builder.build( + textBuf, true /* compute hyphenation */, false /* compute full layout */, + false /* ignore kerning */, nullptr /* no hint */); // clang-format off std::vector<LineBreakExpectation> expect = { @@ -1196,10 +988,7 @@ TEST_F(OptimalLineBreakerTest, ExtentTest) { CUSTOM_ASCENT, CUSTOM_DESCENT}, }; - auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHEN, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); + expectBreak(expect, textBuf, HIGH_QUALITY, NO_HYPHEN, "en-US", LINE_WIDTH); } { constexpr float LINE_WIDTH = 200; @@ -1208,10 +997,7 @@ TEST_F(OptimalLineBreakerTest, ExtentTest) { CUSTOM_ASCENT, CUSTOM_DESCENT}, }; - auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHEN, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); + expectBreak(expect, textBuf, HIGH_QUALITY, NO_HYPHEN, "en-US", LINE_WIDTH); } { constexpr float LINE_WIDTH = 190; @@ -1220,10 +1006,7 @@ TEST_F(OptimalLineBreakerTest, ExtentTest) { CUSTOM_DESCENT}, {"Japanese.", 90, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT}, }; - auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHEN, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); + expectBreak(expect, textBuf, HIGH_QUALITY, NO_HYPHEN, "en-US", LINE_WIDTH); } { constexpr float LINE_WIDTH = 90; @@ -1233,10 +1016,7 @@ TEST_F(OptimalLineBreakerTest, ExtentTest) { CUSTOM_DESCENT}, {"Japanese.", 90, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT}, }; - auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHEN, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); + expectBreak(expect, textBuf, HIGH_QUALITY, NO_HYPHEN, "en-US", LINE_WIDTH); } { constexpr float LINE_WIDTH = 50; @@ -1247,10 +1027,7 @@ TEST_F(OptimalLineBreakerTest, ExtentTest) { {"Japan", 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT}, {"ese.", 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT}, }; - auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHEN, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); + expectBreak(expect, textBuf, HIGH_QUALITY, NO_HYPHEN, "en-US", LINE_WIDTH); } { constexpr float LINE_WIDTH = 40; @@ -1262,10 +1039,7 @@ TEST_F(OptimalLineBreakerTest, ExtentTest) { {"nese", 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT}, {".", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT}, }; - auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHEN, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); + expectBreak(expect, textBuf, HIGH_QUALITY, NO_HYPHEN, "en-US", LINE_WIDTH); } { constexpr float LINE_WIDTH = 20; @@ -1282,10 +1056,7 @@ TEST_F(OptimalLineBreakerTest, ExtentTest) { {"se", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT}, {".", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT}, }; - auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHEN, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); + expectBreak(expect, textBuf, HIGH_QUALITY, NO_HYPHEN, "en-US", LINE_WIDTH); } { constexpr float LINE_WIDTH = 10; @@ -1308,10 +1079,7 @@ TEST_F(OptimalLineBreakerTest, ExtentTest) { {"e", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT}, {".", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT}, }; - auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHEN, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); + expectBreak(expect, textBuf, HIGH_QUALITY, NO_HYPHEN, "en-US", LINE_WIDTH); } } @@ -1331,9 +1099,9 @@ TEST_F(OptimalLineBreakerTest, testReplacementSpanNotBreakTest_SingleChar) { builder.addCustomRun<ConstantRun>(Range(21, textBuf.size()), "en-US", CHAR_WIDTH, ASCENT, DESCENT); - std::unique_ptr<MeasuredText> measuredText = - builder.build(textBuf, false /* compute hyphenation */, - false /* compute full layout */, nullptr /* no hint */); + std::unique_ptr<MeasuredText> measuredText = builder.build( + textBuf, false /* compute hyphenation */, false /* compute full layout */, + false /* ignore kerning */, nullptr /* no hint */); RectangleLineWidth rectangleLineWidth(width); TabStops tabStops(nullptr, 0, 0); return breakLineOptimal(textBuf, *measuredText, rectangleLineWidth, @@ -1424,9 +1192,9 @@ TEST_F(OptimalLineBreakerTest, testReplacementSpanNotBreakTest_MultipleChars) { builder.addCustomRun<ConstantRun>(Range(11, textBuf.size()), "en-US", CHAR_WIDTH, ASCENT, DESCENT); - std::unique_ptr<MeasuredText> measuredText = - builder.build(textBuf, false /* compute hyphenation */, - false /* compute full layout */, nullptr /* no hint */); + std::unique_ptr<MeasuredText> measuredText = builder.build( + textBuf, false /* compute hyphenation */, false /* compute full layout */, + false /* ignore kerning */, nullptr /* no hint */); RectangleLineWidth rectangleLineWidth(width); TabStops tabStops(nullptr, 0, 0); return breakLineOptimal(textBuf, *measuredText, rectangleLineWidth, @@ -1512,9 +1280,9 @@ TEST_F(OptimalLineBreakerTest, testReplacementSpanNotBreakTest_continuedReplacem builder.addReplacementRun(8, 11, 3 * CHAR_WIDTH, LocaleListCache::getId("en-US")); builder.addReplacementRun(11, 19, 8 * CHAR_WIDTH, LocaleListCache::getId("en-US")); builder.addReplacementRun(19, 24, 5 * CHAR_WIDTH, LocaleListCache::getId("en-US")); - std::unique_ptr<MeasuredText> measuredText = - builder.build(textBuf, false /* compute hyphenation */, - false /* compute full layout */, nullptr /* no hint */); + std::unique_ptr<MeasuredText> measuredText = builder.build( + textBuf, false /* compute hyphenation */, false /* compute full layout */, + false /* ignore kerning */, nullptr /* no hint */); RectangleLineWidth rectangleLineWidth(width); TabStops tabStops(nullptr, 0, 0); return breakLineOptimal(textBuf, *measuredText, rectangleLineWidth, @@ -1588,9 +1356,9 @@ TEST_F(OptimalLineBreakerTest, testReplacementSpanNotBreakTest_CJK) { builder.addCustomRun<ConstantRun>(Range(5, textBuf.size()), "ja-JP", CHAR_WIDTH, ASCENT, DESCENT); - std::unique_ptr<MeasuredText> measuredText = - builder.build(textBuf, false /* compute hyphenation */, - false /* compute full layout */, nullptr /* no hint */); + std::unique_ptr<MeasuredText> measuredText = builder.build( + textBuf, false /* compute hyphenation */, false /* compute full layout */, + false /* ignore krening */, nullptr /* no hint */); RectangleLineWidth rectangleLineWidth(width); TabStops tabStops(nullptr, 0, 0); return breakLineOptimal(textBuf, *measuredText, rectangleLineWidth, @@ -1745,9 +1513,9 @@ TEST_F(OptimalLineBreakerTest, testReplacementSpan_GraphemeLineBreakWithMultiple builder.addCustomRun<ConstantRun>(Range(19, 21), "en-US", CHAR_WIDTH, ASCENT, DESCENT); builder.addReplacementRun(21, 26, 5 * CHAR_WIDTH, LocaleListCache::getId("en-US")); - std::unique_ptr<MeasuredText> measuredText = - builder.build(textBuf, false /* compute hyphenation */, - false /* compute full layout */, nullptr /* no hint */); + std::unique_ptr<MeasuredText> measuredText = builder.build( + textBuf, false /* compute hyphenation */, false /* compute full layout */, + false /* ignore kerning */, nullptr /* no hint */); RectangleLineWidth rectangleLineWidth(width); TabStops tabStops(nullptr, 0, 0); return breakLineOptimal(textBuf, *measuredText, rectangleLineWidth, @@ -1880,9 +1648,9 @@ TEST_F(OptimalLineBreakerTest, testReplacementSpanNotBreakTest_with_punctuation) builder.addCustomRun<ConstantRun>(Range(11, textBuf.size()), "en-US", CHAR_WIDTH, ASCENT, DESCENT); - std::unique_ptr<MeasuredText> measuredText = - builder.build(textBuf, false /* compute hyphenation */, - false /* compute full layout */, nullptr /* no hint */); + std::unique_ptr<MeasuredText> measuredText = builder.build( + textBuf, false /* compute hyphenation */, false /* compute full layout */, + false /* ignore kerning */, nullptr /* no hint */); RectangleLineWidth rectangleLineWidth(width); TabStops tabStops(nullptr, 0, 0); return breakLineOptimal(textBuf, *measuredText, rectangleLineWidth, @@ -2070,14 +1838,8 @@ TEST_F(OptimalLineBreakerTest, testControllCharAfterSpace) { {"\u2066example", 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT}, }; - auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); - actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, LINE_WIDTH); - EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl - << " vs " << std::endl - << toString(textBuf, actual); + expectBreak(expect, textBuf, HIGH_QUALITY, NO_HYPHENATION, "en-US", LINE_WIDTH); + expectBreak(expect, textBuf, BALANCED, NO_HYPHENATION, "en-US", LINE_WIDTH); } } @@ -2096,10 +1858,10 @@ TEST_F(OptimalLineBreakerTest, roundingError) { float measured = Layout::measureText(textBuffer, Range(0, textBuffer.size()), Bidi::LTR, paint, StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, nullptr); - builder.addStyleRun(0, textBuffer.size(), std::move(paint), false); - std::unique_ptr<MeasuredText> measuredText = - builder.build(textBuffer, false /* compute hyphenation */, - false /* compute full layout */, nullptr /* no hint */); + builder.addStyleRun(0, textBuffer.size(), std::move(paint), 0, 0, false); + std::unique_ptr<MeasuredText> measuredText = builder.build( + textBuffer, false /* compute hyphenation */, false /* compute full layout */, + false /* ignore kerning */, nullptr /* no hint */); RectangleLineWidth rectangleLineWidth(measured); TabStops tabStops(nullptr, 0, 10); LineBreakResult r = doLineBreak(textBuffer, *measuredText, BreakStrategy::Balanced, diff --git a/tests/unittest/WordBreakerTests.cpp b/tests/unittest/WordBreakerTests.cpp index 26e7c19..4b5fc35 100644 --- a/tests/unittest/WordBreakerTests.cpp +++ b/tests/unittest/WordBreakerTests.cpp @@ -37,8 +37,9 @@ TEST(WordBreakerTest, basic) { WordBreaker breaker; breaker.setText(buf, NELEM(buf)); EXPECT_EQ(0, breaker.current()); - EXPECT_EQ(6, breaker.followingWithLocale(Locale("en-US"), 0)); // after "hello " - EXPECT_EQ(0, breaker.wordStart()); // "hello" + EXPECT_EQ(6, breaker.followingWithLocale(Locale("en-US"), LineBreakStyle::None, + LineBreakWordStyle::None, 0)); // after "hello " + EXPECT_EQ(0, breaker.wordStart()); // "hello" EXPECT_EQ(5, breaker.wordEnd()); EXPECT_EQ(0, breaker.breakBadness()); EXPECT_EQ(6, breaker.current()); @@ -51,11 +52,13 @@ TEST(WordBreakerTest, basic) { TEST(WordBreakerTest, softHyphen) { uint16_t buf[] = {'h', 'e', 'l', 0x00AD, 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd'}; + auto lbStyle = LineBreakStyle::None; + auto lbWordStyle = LineBreakWordStyle::None; WordBreaker breaker; breaker.setText(buf, NELEM(buf)); EXPECT_EQ(0, breaker.current()); // after "hel{SOFT HYPHEN}lo " - EXPECT_EQ(7, breaker.followingWithLocale(Locale("en-US"), 0)); + EXPECT_EQ(7, breaker.followingWithLocale(Locale("en-US"), lbStyle, lbWordStyle, 0)); EXPECT_EQ(0, breaker.wordStart()); // "hel{SOFT HYPHEN}lo" EXPECT_EQ(6, breaker.wordEnd()); EXPECT_EQ(0, breaker.breakBadness()); @@ -68,10 +71,13 @@ TEST(WordBreakerTest, softHyphen) { TEST(WordBreakerTest, hardHyphen) { // Hyphens should not allow breaks anymore. uint16_t buf[] = {'s', 'u', 'g', 'a', 'r', '-', 'f', 'r', 'e', 'e'}; + auto lbStyle = LineBreakStyle::None; + auto lbWordStyle = LineBreakWordStyle::None; WordBreaker breaker; breaker.setText(buf, NELEM(buf)); EXPECT_EQ(0, breaker.current()); - EXPECT_EQ((ssize_t)NELEM(buf), breaker.followingWithLocale(Locale("en-US"), 0)); + EXPECT_EQ((ssize_t)NELEM(buf), + breaker.followingWithLocale(Locale("en-US"), lbStyle, lbWordStyle, 0)); EXPECT_EQ(0, breaker.wordStart()); EXPECT_EQ((ssize_t)NELEM(buf), breaker.wordEnd()); EXPECT_EQ(0, breaker.breakBadness()); @@ -79,12 +85,15 @@ TEST(WordBreakerTest, hardHyphen) { TEST(WordBreakerTest, postfixAndPrefix) { uint16_t buf[] = {'U', 'S', 0x00A2, ' ', 'J', 'P', 0x00A5}; // US¢ JP¥ + auto lbStyle = LineBreakStyle::None; + auto lbWordStyle = LineBreakWordStyle::None; WordBreaker breaker; breaker.setText(buf, NELEM(buf)); EXPECT_EQ(0, breaker.current()); - EXPECT_EQ(4, breaker.followingWithLocale(Locale("en-US"), 0)); // after CENT SIGN - EXPECT_EQ(0, breaker.wordStart()); // "US¢" + EXPECT_EQ(4, breaker.followingWithLocale(Locale("en-US"), lbStyle, lbWordStyle, + 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 @@ -94,12 +103,15 @@ TEST(WordBreakerTest, postfixAndPrefix) { TEST(WordBreakerTest, myanmarKinzi) { uint16_t buf[] = {0x1004, 0x103A, 0x1039, 0x1000, 0x102C}; // NGA, ASAT, VIRAMA, KA, UU + auto lbStyle = LineBreakStyle::None; + auto lbWordStyle = LineBreakWordStyle::None; WordBreaker breaker; breaker.setText(buf, NELEM(buf)); EXPECT_EQ(0, breaker.current()); // end of string - EXPECT_EQ((ssize_t)NELEM(buf), breaker.followingWithLocale(Locale("en-US"), 0)); + EXPECT_EQ((ssize_t)NELEM(buf), + breaker.followingWithLocale(Locale("en-US"), lbStyle, lbWordStyle, 0)); EXPECT_EQ(0, breaker.wordStart()); EXPECT_EQ((ssize_t)NELEM(buf), breaker.wordEnd()); } @@ -115,11 +127,13 @@ TEST(WordBreakerTest, zwjEmojiSequences) { // CAT FACE + zwj + BUST IN SILHOUETTE UTF16(0x1F431), 0x200D, UTF16(0x1F464), }; + auto lbStyle = LineBreakStyle::None; + auto lbWordStyle = LineBreakWordStyle::None; WordBreaker breaker; breaker.setText(buf, NELEM(buf)); EXPECT_EQ(0, breaker.current()); // after man + zwj + heart + zwj + man - EXPECT_EQ(7, breaker.followingWithLocale(Locale("en-US"), 0)); + EXPECT_EQ(7, breaker.followingWithLocale(Locale("en-US"), lbStyle, lbWordStyle, 0)); EXPECT_EQ(0, breaker.wordStart()); EXPECT_EQ(7, breaker.wordEnd()); EXPECT_EQ(17, breaker.next()); // after woman + zwj + heart + zwj + woman @@ -139,11 +153,13 @@ TEST(WordBreakerTest, emojiWithModifier) { 0x270C, 0xFE0F, UTF16(0x1F3FF) // victory hand + emoji style + type 6 fitzpatrick modifier }; + auto lbStyle = LineBreakStyle::None; + auto lbWordStyle = LineBreakWordStyle::None; WordBreaker breaker; breaker.setText(buf, NELEM(buf)); EXPECT_EQ(0, breaker.current()); // after boy + type 1-2 fitzpatrick modifier - EXPECT_EQ(4, breaker.followingWithLocale(Locale("en-US"), 0)); + EXPECT_EQ(4, breaker.followingWithLocale(Locale("en-US"), lbStyle, lbWordStyle, 0)); EXPECT_EQ(0, breaker.wordStart()); EXPECT_EQ(4, breaker.wordEnd()); EXPECT_EQ((ssize_t)NELEM(buf), breaker.next()); // end @@ -163,10 +179,12 @@ TEST(WordBreakerTest, unicode10Emoji) { // WHITE SMILING FACE + VS16 + SLED 0x263A, 0xFE0F, UTF16(0x1F6F7), }; + auto lbStyle = LineBreakStyle::None; + auto lbWordStyle = LineBreakWordStyle::None; WordBreaker breaker; breaker.setText(buf, NELEM(buf)); EXPECT_EQ(0, breaker.current()); - EXPECT_EQ(2, breaker.followingWithLocale(Locale("en"), 0)); + EXPECT_EQ(2, breaker.followingWithLocale(Locale("en"), lbStyle, lbWordStyle, 0)); EXPECT_EQ(0, breaker.wordStart()); EXPECT_EQ(2, breaker.wordEnd()); @@ -209,12 +227,14 @@ TEST(WordBreakerTest, flagsSequenceSingleFlag) { uint16_t buf[BUF_SIZE]; size_t size; ParseUnicode(buf, BUF_SIZE, flags.c_str(), &size, nullptr); + auto lbStyle = LineBreakStyle::None; + auto lbWordStyle = LineBreakWordStyle::None; WordBreaker breaker; breaker.setText(buf, size); EXPECT_EQ(0, breaker.current()); // end of the first flag - EXPECT_EQ(kFlagLength, breaker.followingWithLocale(Locale("en-US"), 0)); + EXPECT_EQ(kFlagLength, breaker.followingWithLocale(Locale("en-US"), lbStyle, lbWordStyle, 0)); EXPECT_EQ(0, breaker.wordStart()); EXPECT_EQ(kFlagLength, breaker.wordEnd()); EXPECT_EQ(static_cast<ssize_t>(size), breaker.next()); @@ -234,12 +254,14 @@ TEST(WordBreakerTest, flagsSequence) { uint16_t buf[BUF_SIZE]; size_t size; ParseUnicode(buf, BUF_SIZE, flagSequence.c_str(), &size, nullptr); + auto lbStyle = LineBreakStyle::None; + auto lbWordStyle = LineBreakWordStyle::None; WordBreaker breaker; breaker.setText(buf, size); EXPECT_EQ(0, breaker.current()); // end of the first flag sequence - EXPECT_EQ(kFlagLength, breaker.followingWithLocale(Locale("en-US"), 0)); + EXPECT_EQ(kFlagLength, breaker.followingWithLocale(Locale("en-US"), lbStyle, lbWordStyle, 0)); EXPECT_EQ(0, breaker.wordStart()); EXPECT_EQ(kFlagLength, breaker.wordEnd()); EXPECT_EQ(static_cast<ssize_t>(size), breaker.next()); @@ -250,11 +272,14 @@ TEST(WordBreakerTest, flagsSequence) { TEST(WordBreakerTest, punct) { uint16_t buf[] = {0x00A1, 0x00A1, 'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', '!'}; + auto lbStyle = LineBreakStyle::None; + auto lbWordStyle = LineBreakWordStyle::None; WordBreaker breaker; breaker.setText(buf, NELEM(buf)); EXPECT_EQ(0, breaker.current()); - EXPECT_EQ(9, breaker.followingWithLocale(Locale("en-US"), 0)); // after "¡¡hello, " - EXPECT_EQ(2, breaker.wordStart()); // "hello" + EXPECT_EQ(9, breaker.followingWithLocale(Locale("en-US"), lbStyle, lbWordStyle, + 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 @@ -266,10 +291,13 @@ TEST(WordBreakerTest, punct) { TEST(WordBreakerTest, email) { uint16_t buf[] = {'f', 'o', 'o', '@', 'e', 'x', 'a', 'm', 'p', 'l', 'e', '.', 'c', 'o', 'm', ' ', 'x'}; + auto lbStyle = LineBreakStyle::None; + auto lbWordStyle = LineBreakWordStyle::None; WordBreaker breaker; breaker.setText(buf, NELEM(buf)); EXPECT_EQ(0, breaker.current()); - EXPECT_EQ(11, breaker.followingWithLocale(Locale("en-US"), 0)); // after "foo@example" + EXPECT_EQ(11, breaker.followingWithLocale(Locale("en-US"), lbStyle, lbWordStyle, + 0)); // after "foo@example" EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd()); EXPECT_EQ(1, breaker.breakBadness()); EXPECT_EQ(16, breaker.next()); // after ".com " @@ -284,10 +312,13 @@ TEST(WordBreakerTest, email) { 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'}; + auto lbStyle = LineBreakStyle::None; + auto lbWordStyle = LineBreakWordStyle::None; WordBreaker breaker; breaker.setText(buf, NELEM(buf)); EXPECT_EQ(0, breaker.current()); - EXPECT_EQ(7, breaker.followingWithLocale(Locale("en-US"), 0)); // after "mailto:" + EXPECT_EQ(7, breaker.followingWithLocale(Locale("en-US"), lbStyle, lbWordStyle, + 0)); // after "mailto:" EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd()); EXPECT_EQ(1, breaker.breakBadness()); EXPECT_EQ(18, breaker.next()); // after "foo@example" @@ -307,10 +338,13 @@ TEST(WordBreakerTest, mailto) { TEST(WordBreakerTest, emailNonAscii) { uint16_t buf[] = {'f', 'o', 'o', '@', 'e', 'x', 'a', 'm', 'p', 'l', 'e', '.', 'c', 'o', 'm', 0x4E00}; + auto lbStyle = LineBreakStyle::None; + auto lbWordStyle = LineBreakWordStyle::None; WordBreaker breaker; breaker.setText(buf, NELEM(buf)); EXPECT_EQ(0, breaker.current()); - EXPECT_EQ(11, breaker.followingWithLocale(Locale("en-US"), 0)); // after "foo@example" + EXPECT_EQ(11, breaker.followingWithLocale(Locale("en-US"), lbStyle, lbWordStyle, + 0)); // after "foo@example" EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd()); EXPECT_EQ(1, breaker.breakBadness()); EXPECT_EQ(15, breaker.next()); // after ".com" @@ -325,10 +359,13 @@ TEST(WordBreakerTest, emailNonAscii) { TEST(WordBreakerTest, emailCombining) { uint16_t buf[] = {'f', 'o', 'o', '@', 'e', 'x', 'a', 'm', 'p', 'l', 'e', '.', 'c', 'o', 'm', 0x0303, ' ', 'x'}; + auto lbStyle = LineBreakStyle::None; + auto lbWordStyle = LineBreakWordStyle::None; WordBreaker breaker; breaker.setText(buf, NELEM(buf)); EXPECT_EQ(0, breaker.current()); - EXPECT_EQ(11, breaker.followingWithLocale(Locale("en-US"), 0)); // after "foo@example" + EXPECT_EQ(11, breaker.followingWithLocale(Locale("en-US"), lbStyle, lbWordStyle, + 0)); // after "foo@example" EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd()); EXPECT_EQ(1, breaker.breakBadness()); EXPECT_EQ(17, breaker.next()); // after ".com̃ " @@ -342,10 +379,13 @@ TEST(WordBreakerTest, emailCombining) { TEST(WordBreakerTest, lonelyAt) { uint16_t buf[] = {'a', ' ', '@', ' ', 'b'}; + auto lbStyle = LineBreakStyle::None; + auto lbWordStyle = LineBreakWordStyle::None; WordBreaker breaker; breaker.setText(buf, NELEM(buf)); EXPECT_EQ(0, breaker.current()); - EXPECT_EQ(2, breaker.followingWithLocale(Locale("en-US"), 0)); // after "a " + EXPECT_EQ(2, + breaker.followingWithLocale(Locale("en-US"), lbStyle, lbWordStyle, 0)); // after "a " EXPECT_EQ(0, breaker.wordStart()); // "a" EXPECT_EQ(1, breaker.wordEnd()); EXPECT_EQ(0, breaker.breakBadness()); @@ -361,10 +401,13 @@ TEST(WordBreakerTest, lonelyAt) { TEST(WordBreakerTest, url) { uint16_t buf[] = {'h', 't', 't', 'p', ':', '/', '/', 'e', 'x', 'a', 'm', 'p', 'l', 'e', '.', 'c', 'o', 'm', ' ', 'x'}; + auto lbStyle = LineBreakStyle::None; + auto lbWordStyle = LineBreakWordStyle::None; WordBreaker breaker; breaker.setText(buf, NELEM(buf)); EXPECT_EQ(0, breaker.current()); - EXPECT_EQ(5, breaker.followingWithLocale(Locale("en-US"), 0)); // after "http:" + EXPECT_EQ(5, breaker.followingWithLocale(Locale("en-US"), lbStyle, lbWordStyle, + 0)); // after "http:" EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd()); EXPECT_EQ(1, breaker.breakBadness()); EXPECT_EQ(7, breaker.next()); // after "//" @@ -387,10 +430,13 @@ TEST(WordBreakerTest, urlBreakChars) { uint16_t buf[] = {'h', 't', 't', 'p', ':', '/', '/', 'a', '.', 'b', '/', '~', 'c', ',', 'd', '-', 'e', '?', 'f', '=', 'g', '&', 'h', '#', 'i', '%', 'j', '_', 'k', '/', 'l'}; + auto lbStyle = LineBreakStyle::None; + auto lbWordStyle = LineBreakWordStyle::None; WordBreaker breaker; breaker.setText(buf, NELEM(buf)); EXPECT_EQ(0, breaker.current()); - EXPECT_EQ(5, breaker.followingWithLocale(Locale("en-US"), 0)); // after "http:" + EXPECT_EQ(5, breaker.followingWithLocale(Locale("en-US"), lbStyle, lbWordStyle, + 0)); // after "http:" EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd()); EXPECT_EQ(1, breaker.breakBadness()); EXPECT_EQ(7, breaker.next()); // after "//" @@ -445,10 +491,13 @@ TEST(WordBreakerTest, urlBreakChars) { TEST(WordBreakerTest, urlNoHyphenBreak) { uint16_t buf[] = {'h', 't', 't', 'p', ':', '/', '/', 'a', '-', '/', 'b'}; + auto lbStyle = LineBreakStyle::None; + auto lbWordStyle = LineBreakWordStyle::None; WordBreaker breaker; breaker.setText(buf, NELEM(buf)); EXPECT_EQ(0, breaker.current()); - EXPECT_EQ(5, breaker.followingWithLocale(Locale("en-US"), 0)); // after "http:" + EXPECT_EQ(5, breaker.followingWithLocale(Locale("en-US"), lbStyle, lbWordStyle, + 0)); // after "http:" EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd()); EXPECT_EQ(7, breaker.next()); // after "//" EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd()); @@ -460,10 +509,13 @@ TEST(WordBreakerTest, urlNoHyphenBreak) { TEST(WordBreakerTest, urlEndsWithSlash) { uint16_t buf[] = {'h', 't', 't', 'p', ':', '/', '/', 'a', '/'}; + auto lbStyle = LineBreakStyle::None; + auto lbWordStyle = LineBreakWordStyle::None; WordBreaker breaker; breaker.setText(buf, NELEM(buf)); EXPECT_EQ(0, breaker.current()); - EXPECT_EQ(5, breaker.followingWithLocale(Locale("en-US"), 0)); // after "http:" + EXPECT_EQ(5, breaker.followingWithLocale(Locale("en-US"), lbStyle, lbWordStyle, + 0)); // after "http:" EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd()); EXPECT_EQ(7, breaker.next()); // after "//" EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd()); @@ -475,19 +527,25 @@ TEST(WordBreakerTest, urlEndsWithSlash) { TEST(WordBreakerTest, emailStartsWithSlash) { uint16_t buf[] = {'/', 'a', '@', 'b'}; + auto lbStyle = LineBreakStyle::None; + auto lbWordStyle = LineBreakWordStyle::None; WordBreaker breaker; breaker.setText(buf, NELEM(buf)); EXPECT_EQ(0, breaker.current()); - EXPECT_EQ((ssize_t)NELEM(buf), breaker.followingWithLocale(Locale("en-US"), 0)); // end + EXPECT_EQ((ssize_t)NELEM(buf), + breaker.followingWithLocale(Locale("en-US"), lbStyle, lbWordStyle, 0)); // end EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd()); } TEST(WordBreakerTest, setLocaleInsideUrl) { std::vector<uint16_t> buf = utf8ToUtf16("Hello http://abc/d.html World"); + auto lbStyle = LineBreakStyle::None; + auto lbWordStyle = LineBreakWordStyle::None; 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(6, breaker.followingWithLocale(Locale("en-US"), lbStyle, lbWordStyle, + 0)); // after "Hello " EXPECT_EQ(0, breaker.wordStart()); EXPECT_EQ(5, breaker.wordEnd()); @@ -495,14 +553,16 @@ TEST(WordBreakerTest, setLocaleInsideUrl) { 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_EQ(11, breaker.followingWithLocale(Locale("en-US"), lbStyle, lbWordStyle, + 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_EQ(13, breaker.followingWithLocale(Locale("en-US"), lbStyle, lbWordStyle, + 12)); // after "//" EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd()); EXPECT_EQ(16, breaker.next()); // after "abc" EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd()); @@ -525,6 +585,8 @@ TEST(WordBreakerTest, spaceAfterSpace) { }; constexpr uint16_t CHAR_SPACE = 0x0020; + auto lbStyle = LineBreakStyle::None; + auto lbWordStyle = LineBreakWordStyle::None; for (uint16_t sp : SPACES) { char msg[64] = {}; @@ -536,7 +598,8 @@ TEST(WordBreakerTest, spaceAfterSpace) { breaker.setText(buf.data(), buf.size()); EXPECT_EQ(0, breaker.current()); - EXPECT_EQ(2, breaker.followingWithLocale(Locale("en-US"), 0)); // after "a " + EXPECT_EQ(2, breaker.followingWithLocale(Locale("en-US"), lbStyle, lbWordStyle, + 0)); // after "a " EXPECT_EQ(0, breaker.wordStart()); EXPECT_EQ(1, breaker.wordEnd()); @@ -567,21 +630,37 @@ TEST(WordBreakerTest, LineBreakerPool_acquire_without_release) { 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); + ICULineBreakerPool::Slot enUSBreaker = + pool.acquire(enUS, LineBreakStyle::Loose, LineBreakWordStyle::None); + ICULineBreakerPool::Slot enUSBreaker2 = + pool.acquire(enUS, LineBreakStyle::Loose, LineBreakWordStyle::None); + ICULineBreakerPool::Slot enUSBreaker3 = + pool.acquire(enUS, LineBreakStyle::Strict, LineBreakWordStyle::None); + ICULineBreakerPool::Slot frFRBreaker = + pool.acquire(frFR, LineBreakStyle::None, LineBreakWordStyle::None); + ICULineBreakerPool::Slot frFRBreaker2 = + pool.acquire(frFR, LineBreakStyle::None, LineBreakWordStyle::Phrase); EXPECT_NE(nullptr, enUSBreaker.breaker.get()); EXPECT_NE(nullptr, enUSBreaker2.breaker.get()); + EXPECT_NE(nullptr, enUSBreaker3.breaker.get()); EXPECT_NE(nullptr, frFRBreaker.breaker.get()); + EXPECT_NE(nullptr, frFRBreaker2.breaker.get()); EXPECT_NE(enUSBreaker.breaker.get(), enUSBreaker2.breaker.get()); + EXPECT_NE(enUSBreaker.breaker.get(), enUSBreaker3.breaker.get()); EXPECT_NE(enUSBreaker.breaker.get(), frFRBreaker.breaker.get()); EXPECT_NE(enUSBreaker2.breaker.get(), frFRBreaker.breaker.get()); + EXPECT_NE(enUSBreaker2.breaker.get(), frFRBreaker2.breaker.get()); + EXPECT_NE(enUSBreaker2.breaker.get(), enUSBreaker3.breaker.get()); EXPECT_EQ(enUSBreaker.localeId, enUSBreaker2.localeId); + EXPECT_EQ(enUSBreaker.localeId, enUSBreaker3.localeId); EXPECT_NE(enUSBreaker.localeId, frFRBreaker.localeId); + EXPECT_NE(enUSBreaker.localeId, frFRBreaker2.localeId); EXPECT_NE(enUSBreaker2.localeId, frFRBreaker.localeId); + EXPECT_NE(enUSBreaker2.localeId, frFRBreaker2.localeId); + EXPECT_EQ(frFRBreaker.localeId, frFRBreaker2.localeId); } TEST(WordBreakerTest, LineBreakerPool_acquire_with_release) { @@ -591,7 +670,8 @@ TEST(WordBreakerTest, LineBreakerPool_acquire_with_release) { const Locale frFR("fr-Latn-FR"); // All following three breakers must be the different instances. - ICULineBreakerPool::Slot enUSBreaker = pool.acquire(enUS); + ICULineBreakerPool::Slot enUSBreaker = + pool.acquire(enUS, LineBreakStyle::Loose, LineBreakWordStyle::None); uint64_t enUSBreakerLocaleId = enUSBreaker.localeId; UBreakIterator* enUSBreakerPtr = enUSBreaker.breaker.get(); @@ -600,14 +680,28 @@ TEST(WordBreakerTest, LineBreakerPool_acquire_with_release) { EXPECT_EQ(nullptr, enUSBreaker.breaker.get()); // acquire must return a different instance if the locale is different. - ICULineBreakerPool::Slot frFRBreaker = pool.acquire(frFR); + ICULineBreakerPool::Slot frFRBreaker = + pool.acquire(frFR, LineBreakStyle::Loose, LineBreakWordStyle::None); 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); + ICULineBreakerPool::Slot enUSBreaker2 = + pool.acquire(enUS, LineBreakStyle::Loose, LineBreakWordStyle::None); EXPECT_EQ(enUSBreakerPtr, enUSBreaker2.breaker.get()); EXPECT_EQ(enUSBreakerLocaleId, enUSBreaker2.localeId); + + // acquire must return a different instance if the line break is different. + ICULineBreakerPool::Slot frFRBreaker2 = + pool.acquire(frFR, LineBreakStyle::Normal, LineBreakWordStyle::None); + ICULineBreakerPool::Slot frFRBreaker3 = + pool.acquire(frFR, LineBreakStyle::Normal, LineBreakWordStyle::Phrase); + EXPECT_NE(frFRBreaker.breaker.get(), frFRBreaker2.breaker.get()); + EXPECT_NE(frFRBreaker.breaker.get(), frFRBreaker3.breaker.get()); + EXPECT_NE(frFRBreaker2.breaker.get(), frFRBreaker3.breaker.get()); + EXPECT_EQ(frFRBreaker.localeId, frFRBreaker2.localeId); + EXPECT_EQ(frFRBreaker.localeId, frFRBreaker3.localeId); + EXPECT_EQ(frFRBreaker2.localeId, frFRBreaker3.localeId); } TEST(WordBreakerTest, LineBreakerPool_exceeds_pool_size) { @@ -620,7 +714,7 @@ TEST(WordBreakerTest, LineBreakerPool_exceeds_pool_size) { // Make pool full. for (size_t i = 0; i < MAX_POOL_SIZE * 2; i++) { - slots[i] = pool.acquire(enUS); + slots[i] = pool.acquire(enUS, LineBreakStyle::None, LineBreakWordStyle::None); EXPECT_EQ(0U, pool.getPoolSize()); } diff --git a/tools/mk_hyb_file.py b/tools/mk_hyb_file.py index a76db93..89949f9 100755 --- a/tools/mk_hyb_file.py +++ b/tools/mk_hyb_file.py @@ -542,8 +542,26 @@ def verify_hyb_file(hyb_fn, pat_fn, chr_fn, hyp_fn): # EXCEPTION for Armenian (hy), we don't really deal with the uppercase form of U+0587 if u'\u0587' in reconstructed_chr: - reconstructed_chr.remove(u'\u0587') - reconstructed_chr.append(u'\u0587\u0535\u0552') + reconstructed_chr.remove(u'\u0587') + reconstructed_chr.append(u'\u0587\u0535\u0552') + + # EXCEPTION for Greek (el), we don't really deal with the uppercase form of + # U+03C2, U+03C3, U+0390, U+03B0 + if u'\u03C2' in reconstructed_chr: + reconstructed_chr.remove(u'\u03C2') + reconstructed_chr.append(u'\u03C2\u03A3') + + if u'\u03C3' in reconstructed_chr: + reconstructed_chr.remove(u'\u03C3') + reconstructed_chr.append(u'\u03C3\u03A3') + + if u'\u0390' in reconstructed_chr: + reconstructed_chr.remove(u'\u0390') + reconstructed_chr.append(u'\u0390\u0390') + + if u'\u03B0' in reconstructed_chr: + reconstructed_chr.remove(u'\u03B0') + reconstructed_chr.append(u'\u03B0\u03B0') assert verify_file_sorted(reconstructed_chr, chr_fn), 'alphabet table not verified' |