DO NOT MERGE - Merge Android 10 into master
Bug: 139893257
Change-Id: Ia5e3d32375c7e7228d46477e9269958d8f5fbdfc
diff --git a/Android.bp b/Android.bp
index e875a17..adbd4e6 100644
--- a/Android.bp
+++ b/Android.bp
@@ -2,6 +2,11 @@
name: "libminikin_headers",
host_supported: true,
export_include_dirs: ["include"],
+ target: {
+ windows: {
+ enabled: true,
+ },
+ },
}
subdirs = [
diff --git a/include/minikin/AndroidLineBreakerHelper.h b/include/minikin/AndroidLineBreakerHelper.h
index 462644d..302d2b1 100644
--- a/include/minikin/AndroidLineBreakerHelper.h
+++ b/include/minikin/AndroidLineBreakerHelper.h
@@ -27,15 +27,12 @@
class AndroidLineWidth : public LineWidth {
public:
AndroidLineWidth(float firstWidth, int32_t firstLineCount, float restWidth,
- const std::vector<float>& indents, const std::vector<float>& leftPaddings,
- const std::vector<float>& rightPaddings, int32_t indentsAndPaddingsOffset)
+ const std::vector<float>& indents, int32_t indentsOffset)
: mFirstWidth(firstWidth),
mFirstLineCount(firstLineCount),
mRestWidth(restWidth),
mIndents(indents),
- mLeftPaddings(leftPaddings),
- mRightPaddings(rightPaddings),
- mOffset(indentsAndPaddingsOffset) {}
+ mOffset(indentsOffset) {}
float getAt(size_t lineNo) const override {
const float width = ((ssize_t)lineNo < (ssize_t)mFirstLineCount) ? mFirstWidth : mRestWidth;
@@ -54,10 +51,6 @@
return minWidth;
}
- float getLeftPaddingAt(size_t lineNo) const override { return get(mLeftPaddings, lineNo); }
-
- float getRightPaddingAt(size_t lineNo) const override { return get(mRightPaddings, lineNo); }
-
private:
float get(const std::vector<float>& vec, size_t lineNo) const {
if (vec.empty()) {
@@ -75,32 +68,27 @@
const int32_t mFirstLineCount;
const float mRestWidth;
const std::vector<float>& mIndents;
- const std::vector<float>& mLeftPaddings;
- const std::vector<float>& mRightPaddings;
const int32_t mOffset;
};
class StaticLayoutNative {
public:
StaticLayoutNative(BreakStrategy strategy, HyphenationFrequency frequency, bool isJustified,
- std::vector<float>&& indents, std::vector<float>&& leftPaddings,
- std::vector<float>&& rightPaddings)
+ std::vector<float>&& indents)
: mStrategy(strategy),
mFrequency(frequency),
mIsJustified(isJustified),
- mIndents(std::move(indents)),
- mLeftPaddings(std::move(leftPaddings)),
- mRightPaddings(std::move(rightPaddings)) {}
+ mIndents(std::move(indents)) {}
LineBreakResult computeBreaks(const U16StringPiece& textBuf, const MeasuredText& measuredText,
// Line width arguments
float firstWidth, int32_t firstWidthLineCount, float restWidth,
int32_t indentsOffset,
// Tab stop arguments
- const int32_t* tabStops, int32_t tabStopSize,
- int32_t defaultTabStopWidth) const {
+ const float* tabStops, int32_t tabStopSize,
+ float defaultTabStopWidth) const {
AndroidLineWidth lineWidth(firstWidth, firstWidthLineCount, restWidth, mIndents,
- mLeftPaddings, mRightPaddings, indentsOffset);
+ indentsOffset);
return breakIntoLines(textBuf, mStrategy, mFrequency, mIsJustified, measuredText, lineWidth,
TabStops(tabStops, tabStopSize, defaultTabStopWidth));
}
diff --git a/include/minikin/FamilyVariant.h b/include/minikin/FamilyVariant.h
new file mode 100644
index 0000000..1734a1f
--- /dev/null
+++ b/include/minikin/FamilyVariant.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MINIKIN_FAMILY_VARIANT_H
+#define MINIKIN_FAMILY_VARIANT_H
+
+#include <cstdint>
+
+namespace minikin {
+
+// Must be the same value as FontConfig.java
+enum class FamilyVariant : uint8_t {
+ DEFAULT = 0, // Must be the same as FontConfig.VARIANT_DEFAULT
+ COMPACT = 1, // Must be the same as FontConfig.VARIANT_COMPACT
+ ELEGANT = 2, // Must be the same as FontConfig.VARIANT_ELEGANT
+};
+
+} // namespace minikin
+
+#endif // MINIKIN_FAMILY_VARIANT_H
diff --git a/include/minikin/Font.h b/include/minikin/Font.h
new file mode 100644
index 0000000..eeb074e
--- /dev/null
+++ b/include/minikin/Font.h
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MINIKIN_FONT_H
+#define MINIKIN_FONT_H
+
+#include <memory>
+#include <unordered_set>
+
+#include "minikin/FontStyle.h"
+#include "minikin/FontVariation.h"
+#include "minikin/HbUtils.h"
+#include "minikin/Macros.h"
+#include "minikin/MinikinFont.h"
+
+namespace minikin {
+
+class Font;
+
+// attributes representing transforms (fake bold, fake italic) to match styles
+class FontFakery {
+public:
+ FontFakery() : mFakeBold(false), mFakeItalic(false) {}
+ FontFakery(bool fakeBold, bool fakeItalic) : mFakeBold(fakeBold), mFakeItalic(fakeItalic) {}
+ // TODO: want to support graded fake bolding
+ bool isFakeBold() { return mFakeBold; }
+ bool isFakeItalic() { return mFakeItalic; }
+ inline bool operator==(const FontFakery& o) const {
+ return mFakeBold == o.mFakeBold && mFakeItalic == o.mFakeItalic;
+ }
+ inline bool operator!=(const FontFakery& o) const { return !(*this == o); }
+
+private:
+ bool mFakeBold;
+ bool mFakeItalic;
+};
+
+struct FakedFont {
+ inline bool operator==(const FakedFont& o) const {
+ return font == o.font && fakery == o.fakery;
+ }
+ inline bool operator!=(const FakedFont& o) const { return !(*this == o); }
+
+ // ownership is the enclosing FontCollection
+ const Font* font;
+ FontFakery fakery;
+};
+
+// Represents a single font file.
+class Font {
+public:
+ class Builder {
+ public:
+ Builder(const std::shared_ptr<MinikinFont>& typeface) : mTypeface(typeface) {}
+
+ // Override the font style. If not called, info from OS/2 table is used.
+ Builder& setStyle(FontStyle style) {
+ mWeight = style.weight();
+ mSlant = style.slant();
+ mIsWeightSet = mIsSlantSet = true;
+ return *this;
+ }
+
+ // Override the font weight. If not called, info from OS/2 table is used.
+ Builder& setWeight(uint16_t weight) {
+ mWeight = weight;
+ mIsWeightSet = true;
+ return *this;
+ }
+
+ // Override the font slant. If not called, info from OS/2 table is used.
+ Builder& setSlant(FontStyle::Slant slant) {
+ mSlant = slant;
+ mIsSlantSet = true;
+ return *this;
+ }
+
+ Font build();
+
+ private:
+ std::shared_ptr<MinikinFont> mTypeface;
+ uint16_t mWeight = static_cast<uint16_t>(FontStyle::Weight::NORMAL);
+ FontStyle::Slant mSlant = FontStyle::Slant::UPRIGHT;
+ bool mIsWeightSet = false;
+ bool mIsSlantSet = false;
+ };
+
+ Font(Font&& o) = default;
+ Font& operator=(Font&& o) = default;
+
+ Font& operator=(const Font& o) {
+ mTypeface = o.mTypeface;
+ mStyle = o.mStyle;
+ mBaseFont = HbFontUniquePtr(hb_font_reference(o.mBaseFont.get()));
+ return *this;
+ }
+ Font(const Font& o) { *this = o; }
+
+ inline const std::shared_ptr<MinikinFont>& typeface() const { return mTypeface; }
+ inline FontStyle style() const { return mStyle; }
+ inline const HbFontUniquePtr& baseFont() const { return mBaseFont; }
+
+ std::unordered_set<AxisTag> getSupportedAxes() const;
+
+private:
+ // Use Builder instead.
+ Font(std::shared_ptr<MinikinFont>&& typeface, FontStyle style, HbFontUniquePtr&& baseFont)
+ : mTypeface(std::move(typeface)), mStyle(style), mBaseFont(std::move(baseFont)) {}
+
+ static HbFontUniquePtr prepareFont(const std::shared_ptr<MinikinFont>& typeface);
+ static FontStyle analyzeStyle(const HbFontUniquePtr& font);
+
+ std::shared_ptr<MinikinFont> mTypeface;
+ FontStyle mStyle;
+ HbFontUniquePtr mBaseFont;
+};
+
+} // namespace minikin
+
+#endif // MINIKIN_FONT_H
diff --git a/include/minikin/FontCollection.h b/include/minikin/FontCollection.h
index 642bb6f..f136384 100644
--- a/include/minikin/FontCollection.h
+++ b/include/minikin/FontCollection.h
@@ -23,6 +23,7 @@
#include "minikin/FontFamily.h"
#include "minikin/MinikinFont.h"
+#include "minikin/U16StringPiece.h"
namespace minikin {
@@ -40,8 +41,15 @@
int end;
};
- void itemize(const uint16_t* string, size_t string_length, const MinikinPaint& paint,
- std::vector<Run>* result) const;
+ // Perform the itemization until given max runs.
+ std::vector<Run> itemize(U16StringPiece text, FontStyle style, uint32_t localeListId,
+ FamilyVariant familyVariant, uint32_t runMax) const;
+
+ // Perform the itemization until end of the text.
+ std::vector<Run> itemize(U16StringPiece text, FontStyle style, uint32_t localeListId,
+ FamilyVariant familyVariant) const {
+ return itemize(text, style, localeListId, familyVariant, text.size());
+ }
// Returns true if there is a glyph for the code point and variation selector pair.
// Returns false if no fonts have a glyph for the code point and variation
@@ -79,10 +87,9 @@
const std::shared_ptr<FontFamily>& getFamilyForChar(uint32_t ch, uint32_t vs,
uint32_t localeListId,
- FontFamily::Variant variant) const;
+ FamilyVariant variant) const;
- uint32_t calcFamilyScore(uint32_t ch, uint32_t vs, FontFamily::Variant variant,
- uint32_t localeListId,
+ uint32_t calcFamilyScore(uint32_t ch, uint32_t vs, FamilyVariant variant, uint32_t localeListId,
const std::shared_ptr<FontFamily>& fontFamily) const;
uint32_t calcCoverageScore(uint32_t ch, uint32_t vs, uint32_t localeListId,
@@ -91,8 +98,7 @@
static uint32_t calcLocaleMatchingScore(uint32_t userLocaleListId,
const FontFamily& fontFamily);
- static uint32_t calcVariantMatchingScore(FontFamily::Variant variant,
- const FontFamily& fontFamily);
+ static uint32_t calcVariantMatchingScore(FamilyVariant variant, const FontFamily& fontFamily);
// unique id for this font collection (suitable for cache key)
uint32_t mId;
diff --git a/include/minikin/FontFamily.h b/include/minikin/FontFamily.h
index 66fe3a8..4aafaa0 100644
--- a/include/minikin/FontFamily.h
+++ b/include/minikin/FontFamily.h
@@ -22,6 +22,8 @@
#include <unordered_set>
#include <vector>
+#include "minikin/FamilyVariant.h"
+#include "minikin/Font.h"
#include "minikin/FontStyle.h"
#include "minikin/HbUtils.h"
#include "minikin/Macros.h"
@@ -29,117 +31,17 @@
namespace minikin {
-class Font;
-class MinikinFont;
-
-// attributes representing transforms (fake bold, fake italic) to match styles
-class FontFakery {
-public:
- FontFakery() : mFakeBold(false), mFakeItalic(false) {}
- FontFakery(bool fakeBold, bool fakeItalic) : mFakeBold(fakeBold), mFakeItalic(fakeItalic) {}
- // TODO: want to support graded fake bolding
- bool isFakeBold() { return mFakeBold; }
- bool isFakeItalic() { return mFakeItalic; }
-
-private:
- bool mFakeBold;
- bool mFakeItalic;
-};
-
-struct FakedFont {
- // ownership is the enclosing FontCollection
- const Font* font;
- FontFakery fakery;
-};
-
-typedef uint32_t AxisTag;
-
-// Represents a single font file.
-class Font {
-public:
- class Builder {
- public:
- Builder(const std::shared_ptr<MinikinFont>& typeface) : mTypeface(typeface) {}
-
- // Override the font style. If not called, info from OS/2 table is used.
- Builder& setStyle(FontStyle style) {
- mWeight = style.weight();
- mSlant = style.slant();
- mIsWeightSet = mIsSlantSet = true;
- return *this;
- }
-
- // Override the font weight. If not called, info from OS/2 table is used.
- Builder& setWeight(uint16_t weight) {
- mWeight = weight;
- mIsWeightSet = true;
- return *this;
- }
-
- // Override the font slant. If not called, info from OS/2 table is used.
- Builder& setSlant(FontStyle::Slant slant) {
- mSlant = slant;
- mIsSlantSet = true;
- return *this;
- }
-
- Font build();
-
- private:
- std::shared_ptr<MinikinFont> mTypeface;
- uint16_t mWeight = static_cast<uint16_t>(FontStyle::Weight::NORMAL);
- FontStyle::Slant mSlant = FontStyle::Slant::UPRIGHT;
- bool mIsWeightSet = false;
- bool mIsSlantSet = false;
- };
-
- Font(Font&& o) = default;
- Font& operator=(Font&& o) = default;
-
- inline const std::shared_ptr<MinikinFont>& typeface() const { return mTypeface; }
- inline FontStyle style() const { return mStyle; }
- inline const HbFontUniquePtr& baseFont() const { return mBaseFont; }
-
- std::unordered_set<AxisTag> getSupportedAxes() const;
-
-private:
- // Use Builder instead.
- Font(std::shared_ptr<MinikinFont>&& typeface, FontStyle style, HbFontUniquePtr&& baseFont)
- : mTypeface(std::move(typeface)), mStyle(style), mBaseFont(std::move(baseFont)) {}
-
- static HbFontUniquePtr prepareFont(const std::shared_ptr<MinikinFont>& typeface);
- static FontStyle analyzeStyle(const HbFontUniquePtr& font);
-
- std::shared_ptr<MinikinFont> mTypeface;
- FontStyle mStyle;
- HbFontUniquePtr mBaseFont;
-
- MINIKIN_PREVENT_COPY_AND_ASSIGN(Font);
-};
-
-struct FontVariation {
- FontVariation(AxisTag axisTag, float value) : axisTag(axisTag), value(value) {}
- AxisTag axisTag;
- float value;
-};
-
class FontFamily {
public:
- // Must be the same value as FontConfig.java
- enum class Variant : uint8_t {
- DEFAULT = 0, // Must be the same as FontConfig.VARIANT_DEFAULT
- COMPACT = 1, // Must be the same as FontConfig.VARIANT_COMPACT
- ELEGANT = 2, // Must be the same as FontConfig.VARIANT_ELEGANT
- };
-
explicit FontFamily(std::vector<Font>&& fonts);
- FontFamily(Variant variant, std::vector<Font>&& fonts);
- FontFamily(uint32_t localeListId, Variant variant, std::vector<Font>&& fonts);
+ FontFamily(FamilyVariant variant, std::vector<Font>&& fonts);
+ FontFamily(uint32_t localeListId, FamilyVariant variant, std::vector<Font>&& fonts,
+ bool isCustomFallback);
FakedFont getClosestMatch(FontStyle style) const;
uint32_t localeListId() const { return mLocaleListId; }
- Variant variant() const { return mVariant; }
+ FamilyVariant variant() const { return mVariant; }
// API's for enumerating the fonts in a family. These don't guarantee any particular order
size_t getNumFonts() const { return mFonts.size(); }
@@ -147,6 +49,7 @@
FontStyle getStyle(size_t index) const { return mFonts[index].style(); }
bool isColorEmojiFamily() const { return mIsColorEmoji; }
const std::unordered_set<AxisTag>& supportedAxes() const { return mSupportedAxes; }
+ bool isCustomFallback() const { return mIsCustomFallback; }
// Get Unicode coverage.
const SparseBitSet& getCoverage() const { return mCoverage; }
@@ -167,10 +70,11 @@
void computeCoverage();
uint32_t mLocaleListId;
- Variant mVariant;
+ FamilyVariant mVariant;
std::vector<Font> mFonts;
std::unordered_set<AxisTag> mSupportedAxes;
bool mIsColorEmoji;
+ bool mIsCustomFallback;
SparseBitSet mCoverage;
std::vector<std::unique_ptr<SparseBitSet>> mCmapFmt14Coverage;
diff --git a/include/minikin/FontVariation.h b/include/minikin/FontVariation.h
new file mode 100644
index 0000000..0c38d6a
--- /dev/null
+++ b/include/minikin/FontVariation.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MINIKIN_FONT_VARIATION_H
+#define MINIKIN_FONT_VARIATION_H
+
+#include <cstdint>
+
+namespace minikin {
+
+typedef uint32_t AxisTag;
+
+struct FontVariation {
+ FontVariation(AxisTag axisTag, float value) : axisTag(axisTag), value(value) {}
+ AxisTag axisTag;
+ float value;
+};
+
+} // namespace minikin
+
+#endif // MINIKIN_FONT_VARIATION_H
diff --git a/include/minikin/GraphemeBreak.h b/include/minikin/GraphemeBreak.h
index 8d8e359..445fa63 100644
--- a/include/minikin/GraphemeBreak.h
+++ b/include/minikin/GraphemeBreak.h
@@ -17,6 +17,7 @@
#ifndef MINIKIN_GRAPHEME_BREAK_H
#define MINIKIN_GRAPHEME_BREAK_H
+#include <cstddef>
#include <cstdint>
namespace minikin {
diff --git a/include/minikin/Hasher.h b/include/minikin/Hasher.h
new file mode 100644
index 0000000..8a79b61
--- /dev/null
+++ b/include/minikin/Hasher.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MINIKIN_HASHER_H
+#define MINIKIN_HASHER_H
+
+#include <cstdint>
+
+#include <string>
+
+#include "minikin/Macros.h"
+
+namespace minikin {
+
+// Provides a Jenkins hash implementation.
+class Hasher {
+public:
+ Hasher() : mHash(0u) {}
+
+ IGNORE_INTEGER_OVERFLOW inline Hasher& update(uint32_t data) {
+ mHash += data;
+ mHash += (mHash << 10);
+ mHash ^= (mHash >> 6);
+ return *this;
+ }
+
+ inline Hasher& updateShorts(const uint16_t* data, uint32_t length) {
+ update(length);
+ uint32_t i;
+ for (i = 0; i < (length & -2); i += 2) {
+ update((uint32_t)data[i] | ((uint32_t)data[i + 1] << 16));
+ }
+ if (length & 1) {
+ update((uint32_t)data[i]);
+ }
+ return *this;
+ }
+
+ inline Hasher& updateString(const std::string& str) {
+ uint32_t size = str.size();
+ update(size);
+ uint32_t i;
+ for (i = 0; i < (size & -4); i += 4) {
+ update((uint32_t)str[i] | ((uint32_t)str[i + 1] << 8) | ((uint32_t)str[i + 2] << 16) |
+ ((uint32_t)str[i + 3] << 24));
+ }
+ if (size & 3) {
+ uint32_t data = str[i];
+ data |= ((size & 3) > 1) ? ((uint32_t)str[i + 1] << 8) : 0;
+ data |= ((size & 3) > 2) ? ((uint32_t)str[i + 2] << 16) : 0;
+ update(data);
+ }
+ return *this;
+ }
+
+ IGNORE_INTEGER_OVERFLOW inline uint32_t hash() {
+ uint32_t hash = mHash;
+ hash += (hash << 3);
+ hash ^= (hash >> 11);
+ hash += (hash << 15);
+ return hash;
+ }
+
+private:
+ uint32_t mHash;
+};
+
+} // namespace minikin
+
+#endif // MINIKIN_HASHER_H
diff --git a/include/minikin/HbUtils.h b/include/minikin/HbUtils.h
index 68e4ab8..68fdbba 100644
--- a/include/minikin/HbUtils.h
+++ b/include/minikin/HbUtils.h
@@ -18,10 +18,19 @@
#define MINIKIN_HB_UTILS_H
#include <hb.h>
+#include <cmath>
#include <memory>
namespace minikin {
+inline float HBFixedToFloat(hb_position_t v) {
+ return scalbnf(v, -8);
+}
+
+inline hb_position_t HBFloatToFixed(float v) {
+ return scalbnf(v, +8);
+}
+
struct HbBlobDeleter {
void operator()(hb_blob_t* v) { hb_blob_destroy(v); }
};
diff --git a/include/minikin/Layout.h b/include/minikin/Layout.h
index ba32d98..19f3109 100644
--- a/include/minikin/Layout.h
+++ b/include/minikin/Layout.h
@@ -22,9 +22,9 @@
#include <vector>
#include <gtest/gtest_prod.h>
-#include <utils/JenkinsHash.h>
#include "minikin/FontCollection.h"
+#include "minikin/LayoutCore.h"
#include "minikin/Range.h"
#include "minikin/U16StringPiece.h"
@@ -34,14 +34,11 @@
struct LayoutPieces;
struct LayoutGlyph {
- // index into mFaces and mHbFonts vectors. We could imagine
- // moving this into a run length representation, because it's
- // more efficient for long strings, and we'll probably need
- // something like that for paint attributes (color, underline,
- // fake b/i, etc), as having those per-glyph is bloated.
- int font_ix;
+ LayoutGlyph(FakedFont font, uint32_t glyph_id, float x, float y)
+ : font(font), glyph_id(glyph_id), x(x), y(y) {}
+ FakedFont font;
- unsigned int glyph_id;
+ uint32_t glyph_id;
float x;
float y;
};
@@ -68,66 +65,34 @@
// may not mutate it at the same time.
class Layout {
public:
- Layout()
- : mGlyphs(),
- mAdvances(),
- mExtents(),
- mFaces(),
- mAdvance(0),
- mBounds() {
- mBounds.setEmpty();
+ Layout(const U16StringPiece& str, const Range& range, Bidi bidiFlags, const MinikinPaint& paint,
+ StartHyphenEdit startHyphen, EndHyphenEdit endHyphen)
+ : mAdvance(0) {
+ doLayout(str, range, bidiFlags, paint, startHyphen, endHyphen);
}
- Layout(Layout&& layout) = default;
-
- Layout(const Layout&) = default;
- Layout& operator=(const Layout&) = default;
-
- void dump() const;
-
- void doLayout(const U16StringPiece& str, const Range& range, Bidi bidiFlags,
- const MinikinPaint& paint, StartHyphenEdit startHyphen, EndHyphenEdit endHyphen);
-
- void doLayoutWithPrecomputedPieces(const U16StringPiece& str, const Range& range,
- Bidi bidiFlags, const MinikinPaint& paint,
- StartHyphenEdit startHyphen, EndHyphenEdit endHyphen,
- const LayoutPieces& pieces);
- static std::pair<float, MinikinRect> getBoundsWithPrecomputedPieces(const U16StringPiece& str,
- const Range& range,
- Bidi bidiFlags,
- const MinikinPaint& paint,
- const LayoutPieces& pieces);
+ Layout(uint32_t count) : mAdvance(0) {
+ mAdvances.resize(count, 0);
+ mGlyphs.reserve(count);
+ }
static float measureText(const U16StringPiece& str, const Range& range, Bidi bidiFlags,
const MinikinPaint& paint, StartHyphenEdit startHyphen,
- EndHyphenEdit endHyphen, float* advances, MinikinExtent* extents,
- LayoutPieces* pieces);
+ EndHyphenEdit endHyphen, float* advances);
- inline const std::vector<float>& advances() const { return mAdvances; }
+ const std::vector<float>& advances() const { return mAdvances; }
// public accessors
- size_t nGlyphs() const;
- const MinikinFont* getFont(int i) const;
- FontFakery getFakery(int i) const;
- unsigned int getGlyphId(int i) const;
- float getX(int i) const;
- float getY(int i) const;
-
- float getAdvance() const;
-
- // Get advances, copying into caller-provided buffer. The size of this
- // buffer must match the length of the string (count arg to doLayout).
- void getAdvances(float* advances) const;
-
- // Get extents, copying into caller-provided buffer. The size of this buffer must match the
- // length of the string (count arg to doLayout).
- void getExtents(MinikinExtent* extents) const;
-
- // The i parameter is an offset within the buf relative to start, it is < count, where
- // start and count are the parameters to doLayout
+ size_t nGlyphs() const { return mGlyphs.size(); }
+ const MinikinFont* getFont(int i) const { return mGlyphs[i].font.font->typeface().get(); }
+ FontFakery getFakery(int i) const { return mGlyphs[i].font.fakery; }
+ unsigned int getGlyphId(int i) const { return mGlyphs[i].glyph_id; }
+ float getX(int i) const { return mGlyphs[i].x; }
+ float getY(int i) const { return mGlyphs[i].y; }
+ float getAdvance() const { return mAdvance; }
float getCharAdvance(size_t i) const { return mAdvances[i]; }
-
- void getBounds(MinikinRect* rect) const;
+ const std::vector<float>& getAdvances() const { return mAdvances; }
+ void getBounds(MinikinRect* rect) const { rect->set(mBounds); }
const MinikinRect& getBounds() const { return mBounds; }
// Purge all caches, useful in low memory conditions
@@ -136,26 +101,14 @@
// Dump minikin internal statistics, cache usage, cache hit ratio, etc.
static void dumpMinikinStats(int fd);
- uint32_t getMemoryUsage() const {
- return sizeof(LayoutGlyph) * nGlyphs() + sizeof(float) * mAdvances.size() +
- sizeof(MinikinExtent) * mExtents.size() + sizeof(FakedFont) * mFaces.size() +
- sizeof(float /* mAdvance */) + sizeof(MinikinRect /* mBounds */);
- }
-
// Append another layout (for example, cached value) into this one
- void appendLayout(const Layout& src, size_t start, float extraAdvance);
+ void appendLayout(const LayoutPiece& src, size_t start, float extraAdvance);
private:
- friend class LayoutCacheKey;
- friend class LayoutCache;
-
FRIEND_TEST(LayoutTest, doLayoutWithPrecomputedPiecesTest);
- // Find a face in the mFaces vector. If not found, push back the entry to mFaces.
- uint8_t findOrPushBackFace(const FakedFont& face);
-
- // Clears layout, ready to be used again
- void reset();
+ void doLayout(const U16StringPiece& str, const Range& range, Bidi bidiFlags,
+ const MinikinPaint& paint, StartHyphenEdit startHyphen, EndHyphenEdit endHyphen);
// Lay out a single bidi run
// When layout is not null, layout info will be stored in the object.
@@ -163,16 +116,13 @@
static float doLayoutRunCached(const U16StringPiece& textBuf, const Range& range, bool isRtl,
const MinikinPaint& paint, size_t dstStart,
StartHyphenEdit startHyphen, EndHyphenEdit endHyphen,
- const LayoutPieces* lpIn, Layout* layout, float* advances,
- MinikinExtent* extents, MinikinRect* bounds,
- LayoutPieces* lpOut);
+ Layout* layout, float* advances);
// Lay out a single word
static float doLayoutWord(const uint16_t* buf, size_t start, size_t count, size_t bufSize,
bool isRtl, const MinikinPaint& paint, size_t bufStart,
- StartHyphenEdit startHyphen, EndHyphenEdit endHyphen,
- const LayoutPieces* lpIn, Layout* layout, float* advances,
- MinikinExtent* extents, MinikinRect* bounds, LayoutPieces* lpOut);
+ StartHyphenEdit startHyphen, EndHyphenEdit endHyphen, Layout* layout,
+ float* advances);
// Lay out a single bidi run
void doLayoutRun(const uint16_t* buf, size_t start, size_t count, size_t bufSize, bool isRtl,
@@ -181,12 +131,9 @@
std::vector<LayoutGlyph> mGlyphs;
- // The following three vectors are defined per code unit, so their length is identical to the
- // input text.
+ // This vector defined per code unit, so their length is identical to the input text.
std::vector<float> mAdvances;
- std::vector<MinikinExtent> mExtents;
- std::vector<FakedFont> mFaces;
float mAdvance;
MinikinRect mBounds;
};
diff --git a/include/minikin/LayoutCache.h b/include/minikin/LayoutCache.h
index e99fbe4..058891b 100644
--- a/include/minikin/LayoutCache.h
+++ b/include/minikin/LayoutCache.h
@@ -17,15 +17,22 @@
#ifndef MINIKIN_LAYOUT_CACHE_H
#define MINIKIN_LAYOUT_CACHE_H
-#include "minikin/Layout.h"
+#include "minikin/LayoutCore.h"
#include <mutex>
-#include <utils/JenkinsHash.h>
#include <utils/LruCache.h>
-namespace minikin {
+#include "minikin/FontCollection.h"
+#include "minikin/Hasher.h"
+#include "minikin/MinikinPaint.h"
+#ifdef _WIN32
+#include <io.h>
+#endif
+
+namespace minikin {
+const uint32_t LENGTH_LIMIT_CACHE = 128;
// Layout cache datatypes
class LayoutCacheKey {
public:
@@ -42,7 +49,7 @@
mSkewX(paint.skewX),
mLetterSpacing(paint.letterSpacing),
mWordSpacing(paint.wordSpacing),
- mPaintFlags(paint.paintFlags),
+ mFontFlags(paint.fontFlags),
mLocaleListId(paint.localeListId),
mFamilyVariant(paint.familyVariant),
mStartHyphen(startHyphen),
@@ -54,7 +61,7 @@
return mId == o.mId && mStart == o.mStart && mCount == o.mCount && mStyle == o.mStyle &&
mSize == o.mSize && mScaleX == o.mScaleX && mSkewX == o.mSkewX &&
mLetterSpacing == o.mLetterSpacing && mWordSpacing == o.mWordSpacing &&
- mPaintFlags == o.mPaintFlags && mLocaleListId == o.mLocaleListId &&
+ mFontFlags == o.mFontFlags && mLocaleListId == o.mLocaleListId &&
mFamilyVariant == o.mFamilyVariant && mStartHyphen == o.mStartHyphen &&
mEndHyphen == o.mEndHyphen && mIsRtl == o.mIsRtl && mNchars == o.mNchars &&
!memcmp(mChars, o.mChars, mNchars * sizeof(uint16_t));
@@ -72,13 +79,6 @@
mChars = NULL;
}
- void doLayout(Layout* layout, const MinikinPaint& paint) const {
- layout->mAdvances.resize(mCount, 0);
- layout->mExtents.resize(mCount);
- layout->doLayoutRun(mChars, mStart, mCount, mNchars, mIsRtl, paint, mStartHyphen,
- mEndHyphen);
- }
-
uint32_t getMemoryUsage() const { return sizeof(LayoutCacheKey) + sizeof(uint16_t) * mNchars; }
private:
@@ -93,9 +93,9 @@
float mSkewX;
float mLetterSpacing;
float mWordSpacing;
- int32_t mPaintFlags;
+ int32_t mFontFlags;
uint32_t mLocaleListId;
- FontFamily::Variant mFamilyVariant;
+ FamilyVariant mFamilyVariant;
StartHyphenEdit mStartHyphen;
EndHyphenEdit mEndHyphen;
bool mIsRtl;
@@ -104,29 +104,27 @@
android::hash_t mHash;
android::hash_t computeHash() const {
- uint32_t hash = android::JenkinsHashMix(0, mId);
- hash = android::JenkinsHashMix(hash, mStart);
- hash = android::JenkinsHashMix(hash, mCount);
- hash = android::JenkinsHashMix(hash, android::hash_type(mStyle.identifier()));
- hash = android::JenkinsHashMix(hash, android::hash_type(mSize));
- hash = android::JenkinsHashMix(hash, android::hash_type(mScaleX));
- hash = android::JenkinsHashMix(hash, android::hash_type(mSkewX));
- hash = android::JenkinsHashMix(hash, android::hash_type(mLetterSpacing));
- hash = android::JenkinsHashMix(hash, android::hash_type(mWordSpacing));
- hash = android::JenkinsHashMix(hash, android::hash_type(mPaintFlags));
- hash = android::JenkinsHashMix(hash, android::hash_type(mLocaleListId));
- hash = android::JenkinsHashMix(hash,
- android::hash_type(static_cast<uint8_t>(mFamilyVariant)));
- hash = android::JenkinsHashMix(
- hash,
- android::hash_type(static_cast<uint8_t>(packHyphenEdit(mStartHyphen, mEndHyphen))));
- hash = android::JenkinsHashMix(hash, android::hash_type(mIsRtl));
- hash = android::JenkinsHashMixShorts(hash, mChars, mNchars);
- return android::JenkinsHashWhiten(hash);
+ return Hasher()
+ .update(mId)
+ .update(mStart)
+ .update(mCount)
+ .update(mStyle.identifier())
+ .update(mSize)
+ .update(mScaleX)
+ .update(mSkewX)
+ .update(mLetterSpacing)
+ .update(mWordSpacing)
+ .update(mFontFlags)
+ .update(mLocaleListId)
+ .update(static_cast<uint8_t>(mFamilyVariant))
+ .update(packHyphenEdit(mStartHyphen, mEndHyphen))
+ .update(mIsRtl)
+ .updateShorts(mChars, mNchars)
+ .hash();
}
};
-class LayoutCache : private android::OnEntryRemoved<LayoutCacheKey, Layout*> {
+class LayoutCache : private android::OnEntryRemoved<LayoutCacheKey, LayoutPiece*> {
public:
void clear() {
std::lock_guard<std::mutex> lock(mMutex);
@@ -138,29 +136,26 @@
void getOrCreate(const U16StringPiece& text, const Range& range, const MinikinPaint& paint,
bool dir, StartHyphenEdit startHyphen, EndHyphenEdit endHyphen, F& f) {
LayoutCacheKey key(text, range, paint, dir, startHyphen, endHyphen);
- if (paint.skipCache()) {
- Layout layoutForWord;
- key.doLayout(&layoutForWord, paint);
- f(layoutForWord);
+ if (paint.skipCache() || range.getLength() >= LENGTH_LIMIT_CACHE) {
+ f(LayoutPiece(text, range, dir, paint, startHyphen, endHyphen), paint);
return;
}
-
mRequestCount++;
{
std::lock_guard<std::mutex> lock(mMutex);
- Layout* layout = mCache.get(key);
+ LayoutPiece* layout = mCache.get(key);
if (layout != nullptr) {
mCacheHitCount++;
- f(*layout);
+ f(*layout, paint);
return;
}
}
// Doing text layout takes long time, so releases the mutex during doing layout.
// Don't care even if we do the same layout in other thred.
key.copyText();
- std::unique_ptr<Layout> layout = std::make_unique<Layout>();
- key.doLayout(layout.get(), paint);
- f(*layout);
+ std::unique_ptr<LayoutPiece> layout =
+ std::make_unique<LayoutPiece>(text, range, dir, paint, startHyphen, endHyphen);
+ f(*layout, paint);
{
std::lock_guard<std::mutex> lock(mMutex);
mCache.put(key, layout.release());
@@ -169,10 +164,23 @@
void dumpStats(int fd) {
std::lock_guard<std::mutex> lock(mMutex);
+#ifdef _WIN32
+ float ratio = (mRequestCount == 0) ? 0 : mCacheHitCount / (float)mRequestCount;
+ int count = _scprintf(
+ "\nLayout Cache Info:\n Usage: %zd/%zd entries\n Hit ratio: %d/%d (%f)\n",
+ mCache.size(), kMaxEntries, mCacheHitCount, mRequestCount, ratio);
+ int size = count + 1;
+ char* buffer = new char[size];
+ sprintf_s(buffer, size,
+ "\nLayout Cache Info:\n Usage: %zd/%zd entries\n Hit ratio: %d/%d (%f)\n",
+ mCache.size(), kMaxEntries, mCacheHitCount, mRequestCount, ratio);
+ _write(fd, buffer, sizeof(buffer));
+#else
dprintf(fd, "\nLayout Cache Info:\n");
dprintf(fd, " Usage: %zd/%zd entries\n", mCache.size(), kMaxEntries);
float ratio = (mRequestCount == 0) ? 0 : mCacheHitCount / (float)mRequestCount;
dprintf(fd, " Hit ratio: %d/%d (%f)\n", mCacheHitCount, mRequestCount, ratio);
+#endif
}
static LayoutCache& getInstance() {
@@ -185,14 +193,19 @@
mCache.setOnEntryRemovedListener(this);
}
+ uint32_t getCacheSize() {
+ std::lock_guard<std::mutex> lock(mMutex);
+ return mCache.size();
+ }
+
private:
// callback for OnEntryRemoved
- void operator()(LayoutCacheKey& key, Layout*& value) {
+ void operator()(LayoutCacheKey& key, LayoutPiece*& value) {
key.freeText();
delete value;
}
- android::LruCache<LayoutCacheKey, Layout*> mCache GUARDED_BY(mMutex);
+ android::LruCache<LayoutCacheKey, LayoutPiece*> mCache GUARDED_BY(mMutex);
int32_t mRequestCount;
int32_t mCacheHitCount;
diff --git a/include/minikin/LayoutCore.h b/include/minikin/LayoutCore.h
new file mode 100644
index 0000000..852b985
--- /dev/null
+++ b/include/minikin/LayoutCore.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MINIKIN_LAYOUT_CORE_H
+#define MINIKIN_LAYOUT_CORE_H
+
+#include <vector>
+
+#include <gtest/gtest_prod.h>
+
+#include "minikin/FontFamily.h"
+#include "minikin/Hyphenator.h"
+#include "minikin/MinikinExtent.h"
+#include "minikin/MinikinFont.h"
+#include "minikin/MinikinRect.h"
+#include "minikin/Range.h"
+#include "minikin/U16StringPiece.h"
+
+namespace minikin {
+
+struct MinikinPaint;
+
+struct Point {
+ Point() : x(0), y(0) {}
+ Point(float x, float y) : x(x), y(y) {}
+ bool operator==(const Point& o) const { return x == o.x && y == o.y; }
+ float x;
+ float y;
+};
+
+// Immutable, recycle-able layout result.
+class LayoutPiece {
+public:
+ LayoutPiece(const U16StringPiece& textBuf, const Range& range, bool isRtl,
+ const MinikinPaint& paint, StartHyphenEdit startHyphen, EndHyphenEdit endHyphen);
+
+ // Low level accessors.
+ const std::vector<uint8_t>& fontIndices() const { return mFontIndices; }
+ const std::vector<uint32_t> glyphIds() const { return mGlyphIds; }
+ const std::vector<Point> points() const { return mPoints; }
+ const std::vector<float> advances() const { return mAdvances; }
+ float advance() const { return mAdvance; }
+ const MinikinRect& bounds() const { return mBounds; }
+ const MinikinExtent& extent() const { return mExtent; }
+ const std::vector<FakedFont>& fonts() const { return mFonts; }
+
+ // Helper accessors
+ uint32_t glyphCount() const { return mGlyphIds.size(); }
+ const FakedFont& fontAt(int glyphPos) const { return mFonts[mFontIndices[glyphPos]]; }
+ uint32_t glyphIdAt(int glyphPos) const { return mGlyphIds[glyphPos]; }
+ const Point& pointAt(int glyphPos) const { return mPoints[glyphPos]; }
+
+ uint32_t getMemoryUsage() const {
+ return sizeof(uint8_t) * mFontIndices.size() + sizeof(uint32_t) * mGlyphIds.size() +
+ sizeof(Point) * mPoints.size() + sizeof(float) * mAdvances.size() + sizeof(float) +
+ sizeof(MinikinRect) + sizeof(MinikinExtent);
+ }
+
+private:
+ FRIEND_TEST(LayoutTest, doLayoutWithPrecomputedPiecesTest);
+
+ std::vector<uint8_t> mFontIndices; // per glyph
+ std::vector<uint32_t> mGlyphIds; // per glyph
+ std::vector<Point> mPoints; // per glyph
+
+ std::vector<float> mAdvances; // per code units
+
+ float mAdvance;
+ MinikinRect mBounds;
+ MinikinExtent mExtent;
+
+ std::vector<FakedFont> mFonts;
+};
+
+// For gtest output
+inline std::ostream& operator<<(std::ostream& os, const Point& p) {
+ return os << "(" << p.x << ", " << p.y << ")";
+}
+} // namespace minikin
+
+#endif // MINIKIN_LAYOUT_CORE_H
diff --git a/include/minikin/LayoutPieces.h b/include/minikin/LayoutPieces.h
index f581372..7985ecc 100644
--- a/include/minikin/LayoutPieces.h
+++ b/include/minikin/LayoutPieces.h
@@ -19,54 +19,97 @@
#include <unordered_map>
-#include "minikin/Layout.h"
#include "minikin/LayoutCache.h"
+#include "minikin/LayoutCore.h"
+#include "minikin/MinikinPaint.h"
namespace minikin {
struct LayoutPieces {
- struct KeyHasher {
- std::size_t operator()(const LayoutCacheKey& key) const { return key.hash(); }
+ const static uint32_t kNoPaintId = static_cast<uint32_t>(-1);
+
+ struct Key {
+ Key(const Range& range, HyphenEdit hyphenEdit, bool dir, uint32_t paintId)
+ : range(range), hyphenEdit(hyphenEdit), dir(dir), paintId(paintId) {}
+
+ Range range;
+ HyphenEdit hyphenEdit;
+ bool dir;
+ uint32_t paintId;
+
+ uint32_t hash() const {
+ return Hasher()
+ .update(range.getStart())
+ .update(range.getEnd())
+ .update(hyphenEdit)
+ .update(dir)
+ .update(paintId)
+ .hash();
+ }
+
+ bool operator==(const Key& o) const {
+ return range == o.range && hyphenEdit == o.hyphenEdit && dir == o.dir &&
+ paintId == o.paintId;
+ }
+
+ uint32_t getMemoryUsage() const {
+ return sizeof(Range) + sizeof(HyphenEdit) + sizeof(bool) + sizeof(uint32_t);
+ }
};
- LayoutPieces() {}
+ struct KeyHasher {
+ std::size_t operator()(const Key& key) const { return key.hash(); }
+ };
- ~LayoutPieces() {
- for (const auto it : offsetMap) {
- const_cast<LayoutCacheKey*>(&it.first)->freeText();
+ struct PaintHasher {
+ std::size_t operator()(const MinikinPaint& paint) const { return paint.hash(); }
+ };
+
+ LayoutPieces() : nextPaintId(0) {}
+ ~LayoutPieces() {}
+
+ uint32_t nextPaintId;
+ std::unordered_map<MinikinPaint, uint32_t, PaintHasher> paintMap;
+ std::unordered_map<Key, LayoutPiece, KeyHasher> offsetMap;
+
+ void insert(const Range& range, HyphenEdit edit, const LayoutPiece& layout, bool dir,
+ const MinikinPaint& paint) {
+ uint32_t paintId = findPaintId(paint);
+ if (paintId == kNoPaintId) {
+ paintId = nextPaintId++;
+ paintMap.insert(std::make_pair(paint, paintId));
}
- }
-
- std::unordered_map<LayoutCacheKey, Layout, KeyHasher> offsetMap;
-
- void insert(const U16StringPiece& textBuf, const Range& range, const MinikinPaint& paint,
- bool dir, StartHyphenEdit startEdit, EndHyphenEdit endEdit, const Layout& layout) {
- auto result = offsetMap.emplace(
- std::piecewise_construct,
- std::forward_as_tuple(textBuf, range, paint, dir, startEdit, endEdit),
- std::forward_as_tuple(layout));
- if (result.second) {
- const_cast<LayoutCacheKey*>(&result.first->first)->copyText();
- }
+ offsetMap.emplace(std::piecewise_construct,
+ std::forward_as_tuple(range, edit, dir, paintId),
+ std::forward_as_tuple(layout));
}
template <typename F>
- void getOrCreate(const U16StringPiece& textBuf, const Range& range, const MinikinPaint& paint,
- bool dir, StartHyphenEdit startEdit, EndHyphenEdit endEdit, F& f) const {
- auto it = offsetMap.find(LayoutCacheKey(textBuf, range, paint, dir, startEdit, endEdit));
+ void getOrCreate(const U16StringPiece& textBuf, const Range& range, const Range& context,
+ const MinikinPaint& paint, bool dir, StartHyphenEdit startEdit,
+ EndHyphenEdit endEdit, uint32_t paintId, F& f) const {
+ const HyphenEdit edit = packHyphenEdit(startEdit, endEdit);
+ auto it = offsetMap.find(Key(range, edit, dir, paintId));
if (it == offsetMap.end()) {
- LayoutCache::getInstance().getOrCreate(textBuf, range, paint, dir, startEdit, endEdit,
- f);
+ LayoutCache::getInstance().getOrCreate(textBuf.substr(context),
+ range - context.getStart(), paint, dir,
+ startEdit, endEdit, f);
} else {
- f(it->second);
+ f(it->second, paint);
}
}
+ uint32_t findPaintId(const MinikinPaint& paint) const {
+ auto paintIt = paintMap.find(paint);
+ return paintIt == paintMap.end() ? kNoPaintId : paintIt->second;
+ }
+
uint32_t getMemoryUsage() const {
uint32_t result = 0;
for (const auto& i : offsetMap) {
result += i.first.getMemoryUsage() + i.second.getMemoryUsage();
}
+ result += (sizeof(MinikinPaint) + sizeof(uint32_t)) * paintMap.size();
return result;
}
};
diff --git a/include/minikin/LineBreaker.h b/include/minikin/LineBreaker.h
index 2974c20..3410339 100644
--- a/include/minikin/LineBreaker.h
+++ b/include/minikin/LineBreaker.h
@@ -53,7 +53,7 @@
class TabStops {
public:
// Caller must free stops. stops can be nullprt.
- TabStops(const int32_t* stops, size_t nStops, int32_t tabWidth)
+ TabStops(const float* stops, size_t nStops, float tabWidth)
: mStops(stops), mStopsSize(nStops), mTabWidth(tabWidth) {}
float nextTab(float widthSoFar) const {
@@ -66,9 +66,9 @@
}
private:
- const int32_t* mStops;
+ const float* mStops;
size_t mStopsSize;
- int32_t mTabWidth;
+ float mTabWidth;
};
// Implement this for the additional information during line breaking.
@@ -83,12 +83,6 @@
// Called to find out the minimum line width. This mut not return negative values.
virtual float getMin() const = 0;
-
- // Called to find out the available left-side padding for the line.
- virtual float getLeftPaddingAt(size_t lineNo) const = 0;
-
- // Called to find out the available right-side padding for the line.
- virtual float getRightPaddingAt(size_t lineNo) const = 0;
};
struct LineBreakResult {
diff --git a/include/minikin/Macros.h b/include/minikin/Macros.h
index be1b713..dd13bd8 100644
--- a/include/minikin/Macros.h
+++ b/include/minikin/Macros.h
@@ -16,6 +16,12 @@
#ifndef MINIKIN_MACROS_H
#define MINIKIN_MACROS_H
+#if defined(__clang__)
+#define IGNORE_INTEGER_OVERFLOW __attribute__((no_sanitize("integer")))
+#else
+#define IGNORE_INTEGER_OVERFLOW // no-op
+#endif // __clang__
+
#define MINIKIN_PREVENT_COPY_AND_ASSIGN(Type) \
Type(const Type&) = delete; \
Type& operator=(const Type&) = delete
diff --git a/include/minikin/MeasuredText.h b/include/minikin/MeasuredText.h
index d33a1ca..2c4b3f8 100644
--- a/include/minikin/MeasuredText.h
+++ b/include/minikin/MeasuredText.h
@@ -38,31 +38,39 @@
// Returns true if this run is RTL. Otherwise returns false.
virtual bool isRtl() const = 0;
- // Returns true if this run is a target of hyphenation. Otherwise return false.
- virtual bool canHyphenate() const = 0;
+ // Returns true if this run can be broken into multiple pieces for line breaking.
+ virtual bool canBreak() const = 0;
// Returns the locale list ID for this run.
virtual uint32_t getLocaleListId() const = 0;
// Fills the each character's advances, extents and overhangs.
- virtual void getMetrics(const U16StringPiece& text, float* advances, MinikinExtent* extents,
- LayoutPieces* piece) const = 0;
+ virtual void getMetrics(const U16StringPiece& text, std::vector<float>* advances,
+ LayoutPieces* precomputed, LayoutPieces* outPieces) const = 0;
virtual std::pair<float, MinikinRect> getBounds(const U16StringPiece& text, const Range& range,
const LayoutPieces& pieces) const = 0;
+ virtual MinikinExtent getExtent(const U16StringPiece& text, const Range& range,
+ const LayoutPieces& pieces) const = 0;
+
+ virtual void appendLayout(const U16StringPiece& text, const Range& range,
+ const Range& contextRange, const LayoutPieces& pieces,
+ const MinikinPaint& paint, uint32_t outOrigin,
+ StartHyphenEdit startHyphen, EndHyphenEdit endHyphen,
+ Layout* outLayout) const = 0;
// Following two methods are only called when the implementation returns true for
- // canHyphenate method.
+ // canBreak method.
// Returns the paint pointer used for this run.
- // Returns null if canHyphenate has not returned true.
+ // Returns null if canBreak has not returned true.
virtual const MinikinPaint* getPaint() const { return nullptr; }
// Measures the hyphenation piece and fills each character's advances and overhangs.
virtual float measureHyphenPiece(const U16StringPiece& /* text */,
const Range& /* hyphenPieceRange */,
StartHyphenEdit /* startHyphen */,
- EndHyphenEdit /* endHyphen */, float* /* advances */,
+ EndHyphenEdit /* endHyphen */,
LayoutPieces* /* pieces */) const {
return 0.0;
}
@@ -78,32 +86,29 @@
StyleRun(const Range& range, MinikinPaint&& paint, bool isRtl)
: Run(range), mPaint(std::move(paint)), mIsRtl(isRtl) {}
- bool canHyphenate() const override { return true; }
+ bool canBreak() const override { return true; }
uint32_t getLocaleListId() const override { return mPaint.localeListId; }
bool isRtl() const override { return mIsRtl; }
- void getMetrics(const U16StringPiece& text, float* advances, MinikinExtent* extents,
- LayoutPieces* pieces) const override {
- Bidi bidiFlag = mIsRtl ? Bidi::FORCE_RTL : Bidi::FORCE_LTR;
- Layout::measureText(text, mRange, bidiFlag, mPaint, StartHyphenEdit::NO_EDIT,
- EndHyphenEdit::NO_EDIT, advances, extents, pieces);
- }
+ void getMetrics(const U16StringPiece& text, std::vector<float>* advances,
+ LayoutPieces* precomputed, LayoutPieces* outPieces) const override;
std::pair<float, MinikinRect> getBounds(const U16StringPiece& text, const Range& range,
- const LayoutPieces& pieces) const override {
- Bidi bidiFlag = mIsRtl ? Bidi::FORCE_RTL : Bidi::FORCE_LTR;
- return Layout::getBoundsWithPrecomputedPieces(text, range, bidiFlag, mPaint, pieces);
- }
+ const LayoutPieces& pieces) const override;
+
+ MinikinExtent getExtent(const U16StringPiece& text, const Range& range,
+ const LayoutPieces& pieces) const override;
+
+ void appendLayout(const U16StringPiece& text, const Range& range, const Range& contextRange,
+ const LayoutPieces& pieces, const MinikinPaint& paint, uint32_t outOrigin,
+ StartHyphenEdit startHyphen, EndHyphenEdit endHyphen,
+ Layout* outLayout) const override;
const MinikinPaint* getPaint() const override { return &mPaint; }
float measureHyphenPiece(const U16StringPiece& text, const Range& range,
- StartHyphenEdit startHyphen, EndHyphenEdit endHyphen, float* advances,
- LayoutPieces* pieces) const override {
- Bidi bidiFlag = mIsRtl ? Bidi::FORCE_RTL : Bidi::FORCE_LTR;
- return Layout::measureText(text, range, bidiFlag, mPaint, startHyphen, endHyphen, advances,
- nullptr /* extent */, pieces);
- }
+ StartHyphenEdit startHyphen, EndHyphenEdit endHyphen,
+ LayoutPieces* pieces) const override;
private:
MinikinPaint mPaint;
@@ -116,12 +121,12 @@
: Run(range), mWidth(width), mLocaleListId(localeListId) {}
bool isRtl() const { return false; }
- bool canHyphenate() const { return false; }
+ bool canBreak() const { return false; }
uint32_t getLocaleListId() const { return mLocaleListId; }
- void getMetrics(const U16StringPiece& /* unused */, float* advances,
- MinikinExtent* /* unused */, LayoutPieces* /* pieces */) const override {
- advances[0] = mWidth;
+ void getMetrics(const U16StringPiece& /* text */, std::vector<float>* advances,
+ LayoutPieces* /* precomputed */, LayoutPieces* /* outPieces */) const override {
+ (*advances)[mRange.getStart()] = mWidth;
// TODO: Get the extents information from the caller.
}
@@ -132,6 +137,17 @@
return std::make_pair(mWidth, MinikinRect());
}
+ MinikinExtent getExtent(const U16StringPiece& /* text */, const Range& /* range */,
+ const LayoutPieces& /* pieces */) const override {
+ return MinikinExtent();
+ }
+
+ void appendLayout(const U16StringPiece& /* text */, const Range& /* range */,
+ const Range& /* contextRange */, const LayoutPieces& /* pieces */,
+ const MinikinPaint& /* paint */, uint32_t /* outOrigin */,
+ StartHyphenEdit /* startHyphen */, EndHyphenEdit /* endHyphen */,
+ Layout* /* outLayout*/) const override {}
+
private:
const float mWidth;
const uint32_t mLocaleListId;
@@ -160,10 +176,6 @@
// Character widths.
std::vector<float> widths;
- // Font vertical extents for characters.
- // TODO: Introduce compression for extents. Usually, this has the same values for all chars.
- std::vector<MinikinExtent> extents;
-
// Hyphenation points.
std::vector<HyphenBreak> hyphenBreaks;
@@ -175,14 +187,15 @@
LayoutPieces layoutPieces;
uint32_t getMemoryUsage() const {
- return sizeof(float) * widths.size() + sizeof(MinikinExtent) * extents.size() +
- sizeof(HyphenBreak) * hyphenBreaks.size() + layoutPieces.getMemoryUsage();
+ return sizeof(float) * widths.size() + sizeof(HyphenBreak) * hyphenBreaks.size() +
+ layoutPieces.getMemoryUsage();
}
- void buildLayout(const U16StringPiece& textBuf, const Range& range, const MinikinPaint& paint,
- Bidi bidiFlag, StartHyphenEdit startHyphen, EndHyphenEdit endHyphen,
- Layout* layout);
- MinikinRect getBounds(const U16StringPiece& textBuf, const Range& range);
+ Layout buildLayout(const U16StringPiece& textBuf, const Range& range, const Range& contextRange,
+ const MinikinPaint& paint, StartHyphenEdit startHyphen,
+ EndHyphenEdit endHyphen);
+ MinikinRect getBounds(const U16StringPiece& textBuf, const Range& range) const;
+ MinikinExtent getExtent(const U16StringPiece& textBuf, const Range& range) const;
MeasuredText(MeasuredText&&) = default;
MeasuredText& operator=(MeasuredText&&) = default;
@@ -192,13 +205,14 @@
private:
friend class MeasuredTextBuilder;
- void measure(const U16StringPiece& textBuf, bool computeHyphenation, bool computeLayout);
+ void measure(const U16StringPiece& textBuf, bool computeHyphenation, bool computeLayout,
+ MeasuredText* hint);
// Use MeasuredTextBuilder instead.
MeasuredText(const U16StringPiece& textBuf, std::vector<std::unique_ptr<Run>>&& runs,
- bool computeHyphenation, bool computeLayout)
- : widths(textBuf.size()), extents(textBuf.size()), runs(std::move(runs)) {
- measure(textBuf, computeHyphenation, computeLayout);
+ bool computeHyphenation, bool computeLayout, MeasuredText* hint)
+ : widths(textBuf.size()), runs(std::move(runs)) {
+ measure(textBuf, computeHyphenation, computeLayout, hint);
}
};
@@ -221,10 +235,10 @@
}
std::unique_ptr<MeasuredText> build(const U16StringPiece& textBuf, bool computeHyphenation,
- bool computeLayout) {
+ bool computeLayout, 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));
+ return std::unique_ptr<MeasuredText>(new MeasuredText(
+ textBuf, std::move(mRuns), computeHyphenation, computeLayout, hint));
}
MINIKIN_PREVENT_COPY_ASSIGN_AND_MOVE(MeasuredTextBuilder);
diff --git a/include/minikin/Measurement.h b/include/minikin/Measurement.h
index 55c8474..c8b97d1 100644
--- a/include/minikin/Measurement.h
+++ b/include/minikin/Measurement.h
@@ -17,6 +17,7 @@
#ifndef MINIKIN_MEASUREMENT_H
#define MINIKIN_MEASUREMENT_H
+#include <cstddef>
#include <cstdint>
namespace minikin {
diff --git a/include/minikin/MinikinExtent.h b/include/minikin/MinikinExtent.h
new file mode 100644
index 0000000..ac9b49f
--- /dev/null
+++ b/include/minikin/MinikinExtent.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MINIKIN_MINIKIN_EXTENT_H
+#define MINIKIN_MINIKIN_EXTENT_H
+
+#include <ostream>
+
+namespace minikin {
+
+// For holding vertical extents.
+struct MinikinExtent {
+ MinikinExtent() : ascent(0), descent(0) {}
+ MinikinExtent(float ascent, float descent) : ascent(ascent), descent(descent) {}
+ bool operator==(const MinikinExtent& o) const {
+ return ascent == o.ascent && descent == o.descent;
+ }
+ float ascent; // negative
+ float descent; // positive
+
+ void reset() { ascent = descent = 0.0; }
+
+ void extendBy(const MinikinExtent& e) {
+ ascent = std::min(ascent, e.ascent);
+ descent = std::max(descent, e.descent);
+ }
+};
+
+// For gtest output
+inline std::ostream& operator<<(std::ostream& os, const MinikinExtent& e) {
+ return os << e.ascent << ", " << e.descent;
+}
+} // namespace minikin
+
+#endif // MINIKIN_MINIKIN_EXTENT_H
diff --git a/include/minikin/MinikinFont.h b/include/minikin/MinikinFont.h
index f57e4f0..a5ba074 100644
--- a/include/minikin/MinikinFont.h
+++ b/include/minikin/MinikinFont.h
@@ -14,113 +14,24 @@
* limitations under the License.
*/
-#ifndef MINIKIN_FONT_H
-#define MINIKIN_FONT_H
+#ifndef MINIKIN_MINIKIN_FONT_H
+#define MINIKIN_MINIKIN_FONT_H
-#include <string>
+#include <cstdint>
+#include <memory>
+#include <vector>
-#include "minikin/FontFamily.h"
-#include "minikin/Hyphenator.h"
-
-// An abstraction for platform fonts, allowing Minikin to be used with
-// multiple actual implementations of fonts.
+#include "minikin/FontVariation.h"
namespace minikin {
-class FontCollection;
-class MinikinFont;
+class FontFakery;
+struct MinikinExtent;
+struct MinikinPaint;
+struct MinikinRect;
-// Possibly move into own .h file?
-// Note: if you add a field here, either add it to LayoutCacheKey or to skipCache()
-struct MinikinPaint {
- MinikinPaint(const std::shared_ptr<FontCollection>& font)
- : size(0),
- scaleX(0),
- skewX(0),
- letterSpacing(0),
- wordSpacing(0),
- paintFlags(0),
- localeListId(0),
- familyVariant(FontFamily::Variant::DEFAULT),
- fontFeatureSettings(),
- font(font) {}
-
- bool skipCache() const { return !fontFeatureSettings.empty(); }
-
- float size;
- float scaleX;
- float skewX;
- float letterSpacing;
- float wordSpacing;
- uint32_t paintFlags;
- uint32_t localeListId;
- FontStyle fontStyle;
- FontFamily::Variant familyVariant;
- std::string fontFeatureSettings;
- std::shared_ptr<FontCollection> font;
-
- void copyFrom(const MinikinPaint& paint) { *this = paint; }
-
- MinikinPaint(MinikinPaint&&) = default;
- MinikinPaint& operator=(MinikinPaint&&) = default;
-
- inline bool operator==(const MinikinPaint& paint) {
- return size == paint.size && scaleX == paint.scaleX && skewX == paint.skewX &&
- letterSpacing == paint.letterSpacing && wordSpacing == paint.wordSpacing &&
- paintFlags == paint.paintFlags && localeListId == paint.localeListId &&
- fontStyle == paint.fontStyle && familyVariant == paint.familyVariant &&
- fontFeatureSettings == paint.fontFeatureSettings && font.get() == paint.font.get();
- }
-
-private:
- // Forbid implicit copy and assign. Use copyFrom instead.
- MinikinPaint(const MinikinPaint&) = default;
- MinikinPaint& operator=(const MinikinPaint&) = default;
-};
-
-// Only a few flags affect layout, but those that do should have values
-// consistent with Android's paint flags.
-enum MinikinPaintFlags {
- LinearTextFlag = 0x40,
-};
-
-struct MinikinRect {
- float mLeft = 0.0;
- float mTop = 0.0;
- float mRight = 0.0;
- float mBottom = 0.0;
- bool isEmpty() const { return mLeft == mRight || mTop == mBottom; }
- void set(const MinikinRect& r) {
- mLeft = r.mLeft;
- mTop = r.mTop;
- mRight = r.mRight;
- mBottom = r.mBottom;
- }
- void offset(float dx, float dy) {
- mLeft += dx;
- mTop += dy;
- mRight += dx;
- mBottom += dy;
- }
- void setEmpty() { mLeft = mTop = mRight = mBottom = 0.0; }
- void join(const MinikinRect& r);
-};
-
-// For holding vertical extents.
-struct MinikinExtent {
- float ascent = 0.0; // negative
- float descent = 0.0; // positive
- float line_gap = 0.0; // positive
-
- void reset() { ascent = descent = line_gap = 0.0; }
-
- void extendBy(const MinikinExtent& e) {
- ascent = std::min(ascent, e.ascent);
- descent = std::max(descent, e.descent);
- line_gap = std::max(line_gap, e.line_gap);
- }
-};
-
+// An abstraction for platform fonts, allowing Minikin to be used with
+// multiple actual implementations of fonts.
class MinikinFont {
public:
explicit MinikinFont(int32_t uniqueId) : mUniqueId(uniqueId) {}
@@ -129,6 +40,13 @@
virtual float GetHorizontalAdvance(uint32_t glyph_id, const MinikinPaint& paint,
const FontFakery& fakery) const = 0;
+ virtual void GetHorizontalAdvances(uint16_t* glyph_ids, uint32_t count,
+ const MinikinPaint& paint, const FontFakery& fakery,
+ float* outAdvances) const {
+ for (uint32_t i = 0; i < count; ++i) {
+ outAdvances[i] = GetHorizontalAdvance(glyph_ids[i], paint, fakery);
+ }
+ }
virtual void GetBounds(MinikinRect* bounds, uint32_t glyph_id, const MinikinPaint& paint,
const FontFakery& fakery) const = 0;
@@ -165,4 +83,4 @@
} // namespace minikin
-#endif // MINIKIN_FONT_H
+#endif // MINIKIN_MINIKIN_FONT_H
diff --git a/include/minikin/MinikinPaint.h b/include/minikin/MinikinPaint.h
new file mode 100644
index 0000000..54b476f
--- /dev/null
+++ b/include/minikin/MinikinPaint.h
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2013 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_MINIKIN_PAINT_H
+#define MINIKIN_MINIKIN_PAINT_H
+
+#include <memory>
+#include <string>
+
+#include "minikin/FamilyVariant.h"
+#include "minikin/FontCollection.h"
+#include "minikin/FontFamily.h"
+#include "minikin/Hasher.h"
+
+namespace minikin {
+
+class FontCollection;
+
+// These describe what is stored in MinikinPaint.fontFlags.
+enum MinikinFontFlags {
+ Embolden_Shift = 0,
+ LinearMetrics_Shift = 1,
+ Subpixel_Shift = 2,
+ EmbeddedBitmaps_Shift = 3,
+ ForceAutoHinting_Shift = 4,
+
+ Embolden_Flag = 1 << Embolden_Shift,
+ LinearMetrics_Flag = 1 << LinearMetrics_Shift,
+ Subpixel_Flag = 1 << Subpixel_Shift,
+ EmbeddedBitmaps_Flag = 1 << EmbeddedBitmaps_Shift,
+ ForceAutoHinting_Flag = 1 << ForceAutoHinting_Shift,
+};
+
+// Possibly move into own .h file?
+// Note: if you add a field here, either add it to LayoutCacheKey or to skipCache()
+struct MinikinPaint {
+ MinikinPaint(const std::shared_ptr<FontCollection>& font)
+ : size(0),
+ scaleX(0),
+ skewX(0),
+ letterSpacing(0),
+ wordSpacing(0),
+ fontFlags(0),
+ localeListId(0),
+ familyVariant(FamilyVariant::DEFAULT),
+ fontFeatureSettings(),
+ font(font) {}
+
+ bool skipCache() const { return !fontFeatureSettings.empty(); }
+
+ float size;
+ float scaleX;
+ float skewX;
+ float letterSpacing;
+ float wordSpacing;
+ uint32_t fontFlags;
+ uint32_t localeListId;
+ FontStyle fontStyle;
+ FamilyVariant familyVariant;
+ std::string fontFeatureSettings;
+ std::shared_ptr<FontCollection> font;
+
+ void copyFrom(const MinikinPaint& paint) { *this = paint; }
+
+ MinikinPaint(const MinikinPaint&) = default;
+ MinikinPaint& operator=(const MinikinPaint&) = default;
+
+ MinikinPaint(MinikinPaint&&) = default;
+ MinikinPaint& operator=(MinikinPaint&&) = default;
+
+ inline bool operator==(const MinikinPaint& paint) const {
+ return size == paint.size && scaleX == paint.scaleX && skewX == paint.skewX &&
+ letterSpacing == paint.letterSpacing && wordSpacing == paint.wordSpacing &&
+ fontFlags == paint.fontFlags && localeListId == paint.localeListId &&
+ fontStyle == paint.fontStyle && familyVariant == paint.familyVariant &&
+ fontFeatureSettings == paint.fontFeatureSettings && font.get() == paint.font.get();
+ }
+
+ uint32_t hash() const {
+ return Hasher()
+ .update(size)
+ .update(scaleX)
+ .update(skewX)
+ .update(letterSpacing)
+ .update(wordSpacing)
+ .update(fontFlags)
+ .update(localeListId)
+ .update(fontStyle.identifier())
+ .update(static_cast<uint8_t>(familyVariant))
+ .updateString(fontFeatureSettings)
+ .update(font->getId())
+ .hash();
+ }
+};
+
+} // namespace minikin
+
+#endif // MINIKIN_MINIKIN_PAINT_H
diff --git a/include/minikin/MinikinRect.h b/include/minikin/MinikinRect.h
new file mode 100644
index 0000000..6a3d88b
--- /dev/null
+++ b/include/minikin/MinikinRect.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MINIKIN_MINIKIN_RECT_H
+#define MINIKIN_MINIKIN_RECT_H
+
+#include <ostream>
+
+namespace minikin {
+
+struct MinikinRect {
+ MinikinRect() : mLeft(0), mTop(0), mRight(0), mBottom(0) {}
+ MinikinRect(float left, float top, float right, float bottom)
+ : mLeft(left), mTop(top), mRight(right), mBottom(bottom) {}
+ bool operator==(const MinikinRect& o) const {
+ return mLeft == o.mLeft && mTop == o.mTop && mRight == o.mRight && mBottom == o.mBottom;
+ }
+ float mLeft;
+ float mTop;
+ float mRight;
+ float mBottom;
+
+ bool isEmpty() const { return mLeft == mRight || mTop == mBottom; }
+ void set(const MinikinRect& r) {
+ mLeft = r.mLeft;
+ mTop = r.mTop;
+ mRight = r.mRight;
+ mBottom = r.mBottom;
+ }
+ void offset(float dx, float dy) {
+ mLeft += dx;
+ mTop += dy;
+ mRight += dx;
+ mBottom += dy;
+ }
+ void setEmpty() { mLeft = mTop = mRight = mBottom = 0.0; }
+ void join(const MinikinRect& r) {
+ if (isEmpty()) {
+ set(r);
+ } else if (!r.isEmpty()) {
+ mLeft = std::min(mLeft, r.mLeft);
+ mTop = std::min(mTop, r.mTop);
+ mRight = std::max(mRight, r.mRight);
+ mBottom = std::max(mBottom, r.mBottom);
+ }
+ }
+};
+
+// For gtest output
+inline std::ostream& operator<<(std::ostream& os, const MinikinRect& r) {
+ return os << "(" << r.mLeft << ", " << r.mTop << ")-(" << r.mRight << ", " << r.mBottom << ")";
+}
+
+} // namespace minikin
+
+#endif // MINIKIN_MINIKIN_RECT_H
diff --git a/include/minikin/Range.h b/include/minikin/Range.h
index 4c7fbfa..991912d 100644
--- a/include/minikin/Range.h
+++ b/include/minikin/Range.h
@@ -87,6 +87,10 @@
inline bool operator!=(const Range& o) const { return !(*this == o); }
+ inline Range operator+(int32_t shift) const { return Range(mStart + shift, mEnd + shift); }
+
+ inline Range operator-(int32_t shift) const { return Range(mStart - shift, mEnd - shift); }
+
private:
// Helper class for "for (uint32_t i : range)" style for-loop.
class RangeIterator {
diff --git a/include/minikin/SystemFonts.h b/include/minikin/SystemFonts.h
new file mode 100644
index 0000000..4108215
--- /dev/null
+++ b/include/minikin/SystemFonts.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MINIKIN_SYSTEM_FONTS_H
+#define MINIKIN_SYSTEM_FONTS_H
+
+#include <map>
+#include <memory>
+#include <string>
+
+#include "minikin/FontCollection.h"
+#include "minikin/U16StringPiece.h"
+
+namespace minikin {
+
+// Provides a system font mapping.
+class SystemFonts {
+public:
+ static std::shared_ptr<FontCollection> findFontCollection(const std::string& familyName) {
+ return getInstance().findFontCollectionInternal(familyName);
+ }
+
+ // Do not call this function outside Zygote process.
+ static void registerFallback(const std::string& familyName,
+ const std::shared_ptr<FontCollection>& fc) {
+ return getInstance().registerFallbackInternal(familyName, fc);
+ }
+
+ // Do not call this function outside Zygote process.
+ static void registerDefault(const std::shared_ptr<FontCollection>& fc) {
+ return getInstance().registerDefaultInternal(fc);
+ }
+
+protected:
+ // Visible for testing purposes.
+ SystemFonts() {}
+ virtual ~SystemFonts() {}
+
+ std::shared_ptr<FontCollection> findFontCollectionInternal(const std::string& familyName) const;
+ void registerFallbackInternal(const std::string& familyName,
+ const std::shared_ptr<FontCollection>& fc) {
+ mSystemFallbacks.insert(std::make_pair(familyName, fc));
+ }
+
+ void registerDefaultInternal(const std::shared_ptr<FontCollection>& fc) {
+ mDefaultFallback = fc;
+ }
+
+private:
+ static SystemFonts& getInstance();
+
+ // There is no mutex guard here since registerFallback is designed to be
+ // called only in Zygote.
+ std::map<std::string, std::shared_ptr<FontCollection>> mSystemFallbacks;
+ std::shared_ptr<FontCollection> mDefaultFallback;
+};
+
+} // namespace minikin
+
+#endif // MINIKIN_SYSTEM_FONTS_H
diff --git a/libs/minikin/Android.bp b/libs/minikin/Android.bp
index 7cd3858..99dd015 100644
--- a/libs/minikin/Android.bp
+++ b/libs/minikin/Android.bp
@@ -23,65 +23,37 @@
name: "libminikin",
host_supported: true,
srcs: [
+ "BidiUtils.cpp",
+ "CmapCoverage.cpp",
+ "Emoji.cpp",
+ "FontCollection.cpp",
+ "FontFamily.cpp",
+ "FontUtils.cpp",
+ "GraphemeBreak.cpp",
+ "GreedyLineBreaker.cpp",
"Hyphenator.cpp",
+ "HyphenatorMap.cpp",
+ "Layout.cpp",
+ "LayoutCore.cpp",
+ "LayoutUtils.cpp",
+ "LineBreaker.cpp",
+ "LineBreakerUtil.cpp",
+ "Locale.cpp",
+ "LocaleListCache.cpp",
+ "MeasuredText.cpp",
+ "Measurement.cpp",
+ "MinikinInternal.cpp",
+ "OptimalLineBreaker.cpp",
+ "SparseBitSet.cpp",
+ "SystemFonts.cpp",
+ "WordBreaker.cpp",
],
cflags: ["-Wall", "-Werror"],
- target: {
- android: {
- srcs: [
- "BidiUtils.cpp",
- "CmapCoverage.cpp",
- "Emoji.cpp",
- "FontCollection.cpp",
- "FontFamily.cpp",
- "FontUtils.cpp",
- "GraphemeBreak.cpp",
- "GreedyLineBreaker.cpp",
- "HyphenatorMap.cpp",
- "Layout.cpp",
- "LayoutUtils.cpp",
- "LineBreaker.cpp",
- "LineBreakerUtil.cpp",
- "Locale.cpp",
- "LocaleListCache.cpp",
- "MeasuredText.cpp",
- "Measurement.cpp",
- "MinikinInternal.cpp",
- "OptimalLineBreaker.cpp",
- "SparseBitSet.cpp",
- "WordBreaker.cpp",
- ],
- shared_libs: [
- "libandroidicu",
- "libharfbuzz_ng",
- "libft2",
- "libz",
- "libutils",
- ],
- export_shared_lib_headers: [
- "libandroidicu",
- // TODO: clean up Minikin so it doesn't need the freetype include
- "libft2",
- ],
-
- sanitize: {
- misc_undefined: [
- "signed-integer-overflow",
- // b/26432628.
- //"unsigned-integer-overflow",
- ],
- },
- },
- host: {
- shared_libs: [
- "libicui18n",
- "libicuuc",
- ],
- export_shared_lib_headers: [
- "libicui18n",
- "libicuuc",
- ],
- },
+ sanitize: {
+ misc_undefined: [
+ "signed-integer-overflow",
+ "unsigned-integer-overflow",
+ ],
},
cppflags: [
"-Werror",
@@ -97,13 +69,43 @@
},
shared_libs: [
"liblog",
+ "libharfbuzz_ng",
],
header_libs: [
"libbase_headers",
"libminikin_headers",
+ "libutils_headers",
],
export_header_lib_headers: ["libminikin_headers"],
whole_static_libs: ["libgtest_prod"],
clang: true,
+
+ target: {
+ android: {
+ shared_libs: [
+ "libandroidicu",
+ ],
+ export_shared_lib_headers: [
+ "libandroidicu",
+ ],
+ },
+ host: {
+ shared_libs: [
+ "libicui18n",
+ "libicuuc",
+ ],
+ export_shared_lib_headers: [
+ "libicui18n",
+ "libicuuc",
+ ],
+ },
+ windows: {
+ enabled: true,
+ cppflags: [
+ "-Wno-ignored-attributes",
+ "-Wno-thread-safety",
+ ],
+ },
+ },
}
diff --git a/libs/minikin/CmapCoverage.cpp b/libs/minikin/CmapCoverage.cpp
index f6143d3..8d04ce8 100644
--- a/libs/minikin/CmapCoverage.cpp
+++ b/libs/minikin/CmapCoverage.cpp
@@ -21,6 +21,7 @@
#include <algorithm>
#include <vector>
+#include "minikin/Range.h"
#include "minikin/SparseBitSet.h"
#include "MinikinInternal.h"
diff --git a/libs/minikin/Emoji.cpp b/libs/minikin/Emoji.cpp
index ad83189..cd52f52 100644
--- a/libs/minikin/Emoji.cpp
+++ b/libs/minikin/Emoji.cpp
@@ -19,18 +19,20 @@
namespace minikin {
bool isNewEmoji(uint32_t c) {
- // Emoji characters new in Unicode emoji 11
- // From https://www.unicode.org/Public/emoji/11.0/emoji-data.txt
- // TODO: Remove once emoji-data.text 11 is in ICU or update to 11.
- if (c < 0x1F6F9 || c > 0x1F9FF) {
+ // Emoji characters new in Unicode emoji 12
+ // From https://www.unicode.org/Public/emoji/12.0/emoji-data.txt
+ // TODO: Remove once emoji-data.text 12 is in ICU or update to 12.
+ if (c < 0x1F6D5 || c > 0x1FA95) {
// Optimization for characters outside the new emoji range.
return false;
}
- return c == 0x265F || c == 0x267E || c == 0x1F6F9 || (0x1F94D <= c && c <= 0x1F94F) ||
- (0x1F96C <= c && c <= 0x1F970) || (0x1F973 <= c && c <= 0x1F976) || c == 0x1F97A ||
- (0x1F97C <= c && c <= 0x1F97F) || (0x1F998 <= c && c <= 0x1F9A2) ||
- (0x1F9B0 <= c && c <= 0x1F9B9) || (0x1F9C1 <= c && c <= 0x1F9C2) ||
- (0x1F9E7 <= c && c <= 0x1F9FF);
+ return c == 0x1F6D5 || c == 0x1F6FA || c == 0x1F93F || c == 0x1F971 || c == 0x1F97B ||
+ (0x1F7E0 <= c && c <= 0x1F7EB) || (0x1F90D <= c && c <= 0x1F90F) ||
+ (0x1F9A5 <= c && c <= 0x1F9AA) || (0x1F9AE <= c && c <= 0x1F9AF) ||
+ (0x1F9BA <= c && c <= 0x1F9BF) || (0x1F9C3 <= c && c <= 0x1F9CA) ||
+ (0x1F9CD <= c && c <= 0x1F9CF) || (0x1FA70 <= c && c <= 0x1FA73) ||
+ (0x1FA78 <= c && c <= 0x1FA7A) || (0x1FA80 <= c && c <= 0x1FA82) ||
+ (0x1FA90 <= c && c <= 0x1FA95);
}
bool isEmoji(uint32_t c) {
diff --git a/libs/minikin/FontCollection.cpp b/libs/minikin/FontCollection.cpp
index 1bf2668..6cbabea 100644
--- a/libs/minikin/FontCollection.cpp
+++ b/libs/minikin/FontCollection.cpp
@@ -123,7 +123,7 @@
// base character.
// - kFirstFontScore: When the font is the first font family in the collection and it supports the
// given character or variation sequence.
-uint32_t FontCollection::calcFamilyScore(uint32_t ch, uint32_t vs, FontFamily::Variant variant,
+uint32_t FontCollection::calcFamilyScore(uint32_t ch, uint32_t vs, FamilyVariant variant,
uint32_t localeListId,
const std::shared_ptr<FontFamily>& fontFamily) const {
const uint32_t coverageScore = calcCoverageScore(ch, vs, localeListId, fontFamily);
@@ -159,7 +159,7 @@
return kUnsupportedFontScore;
}
- if ((vs == 0 || hasVSGlyph) && mFamilies[0] == fontFamily) {
+ if ((vs == 0 || hasVSGlyph) && (mFamilies[0] == fontFamily || fontFamily->isCustomFallback())) {
// If the first font family supports the given character or variation sequence, always use
// it.
return kFirstFontScore;
@@ -228,16 +228,16 @@
// Calculates a font score based on variant ("compact" or "elegant") matching.
// - Returns 1 if the font doesn't have variant or the variant matches with the text style.
// - No score if the font has a variant but it doesn't match with the text style.
-uint32_t FontCollection::calcVariantMatchingScore(FontFamily::Variant variant,
+uint32_t FontCollection::calcVariantMatchingScore(FamilyVariant variant,
const FontFamily& fontFamily) {
- const FontFamily::Variant familyVariant = fontFamily.variant();
- if (familyVariant == FontFamily::Variant::DEFAULT) {
+ const FamilyVariant familyVariant = fontFamily.variant();
+ if (familyVariant == FamilyVariant::DEFAULT) {
return 1;
}
if (familyVariant == variant) {
return 1;
}
- if (variant == FontFamily::Variant::DEFAULT && familyVariant == FontFamily::Variant::COMPACT) {
+ if (variant == FamilyVariant::DEFAULT && familyVariant == FamilyVariant::COMPACT) {
// If default is requested, prefer compat variation.
return 1;
}
@@ -249,8 +249,9 @@
// 2. Calculate a score for the font family. See comments in calcFamilyScore for the detail.
// 3. Highest score wins, with ties resolved to the first font.
// This method never returns nullptr.
-const std::shared_ptr<FontFamily>& FontCollection::getFamilyForChar(
- uint32_t ch, uint32_t vs, uint32_t localeListId, FontFamily::Variant variant) const {
+const std::shared_ptr<FontFamily>& FontCollection::getFamilyForChar(uint32_t ch, uint32_t vs,
+ uint32_t localeListId,
+ FamilyVariant variant) const {
if (ch >= mMaxChar) {
return mFamilies[0];
}
@@ -367,17 +368,19 @@
constexpr uint32_t REPLACEMENT_CHARACTER = 0xFFFD;
-void FontCollection::itemize(const uint16_t* string, size_t string_size, const MinikinPaint& paint,
- vector<Run>* result) const {
- const FontFamily::Variant familyVariant = paint.familyVariant;
- const FontStyle style = paint.fontStyle;
- const uint32_t localeListId = paint.localeListId;
+std::vector<FontCollection::Run> FontCollection::itemize(U16StringPiece text, FontStyle style,
+ uint32_t localeListId,
+ FamilyVariant familyVariant,
+ uint32_t runMax) const {
+ const uint16_t* string = text.data();
+ const uint32_t string_size = text.size();
+ std::vector<Run> result;
const FontFamily* lastFamily = nullptr;
Run* run = nullptr;
if (string_size == 0) {
- return;
+ return result;
}
const uint32_t kEndOfString = 0xFFFFFFFF;
@@ -430,7 +433,7 @@
if (run != nullptr) {
run->end -= prevChLength;
if (run->start == run->end) {
- result->pop_back();
+ result.pop_back();
}
}
start -= prevChLength;
@@ -442,8 +445,8 @@
// start to be 0 to include those characters).
start = 0;
}
- result->push_back({family->getClosestMatch(style), static_cast<int>(start), 0});
- run = &result->back();
+ result.push_back({family->getClosestMatch(style), static_cast<int>(start), 0});
+ run = &result.back();
lastFamily = family.get();
}
}
@@ -451,13 +454,28 @@
if (run != nullptr) {
run->end = nextUtf16Pos; // exclusive
}
+
+ // Stop searching the remaining characters if the result length gets runMax + 2.
+ // When result.size gets runMax + 2 here, the run between [0, runMax) was finalized.
+ // If the result.size() equals to runMax, the run may be still expanding.
+ // if the result.size() equals to runMax + 2, the last run may be removed and the last run
+ // may be exntended the previous run with above workaround.
+ if (result.size() >= 2 && runMax == result.size() - 2) {
+ break;
+ }
} while (nextCh != kEndOfString);
if (lastFamily == nullptr) {
// No character needed any font support, so it doesn't really matter which font they end up
// getting displayed in. We put the whole string in one run, using the first font.
- result->push_back({mFamilies[0]->getClosestMatch(style), 0, static_cast<int>(string_size)});
+ result.push_back({mFamilies[0]->getClosestMatch(style), 0, static_cast<int>(string_size)});
}
+
+ if (result.size() > runMax) {
+ // The itemization has terminated since it reaches the runMax. Remove last unfinalized runs.
+ result.resize(runMax);
+ }
+ return result;
}
FakedFont FontCollection::baseFontFaked(FontStyle style) {
diff --git a/libs/minikin/FontFamily.cpp b/libs/minikin/FontFamily.cpp
index b95d858..85a5142 100644
--- a/libs/minikin/FontFamily.cpp
+++ b/libs/minikin/FontFamily.cpp
@@ -24,9 +24,9 @@
#include <hb-ot.h>
#include <hb.h>
#include <log/log.h>
-#include <utils/JenkinsHash.h>
#include "minikin/CmapCoverage.h"
+#include "minikin/FamilyVariant.h"
#include "minikin/HbUtils.h"
#include "minikin/MinikinFont.h"
@@ -105,17 +105,20 @@
}
FontFamily::FontFamily(std::vector<Font>&& fonts)
- : FontFamily(Variant::DEFAULT, std::move(fonts)) {}
+ : FontFamily(FamilyVariant::DEFAULT, std::move(fonts)) {}
-FontFamily::FontFamily(Variant variant, std::vector<Font>&& fonts)
- : FontFamily(LocaleListCache::kEmptyListId, variant, std::move(fonts)) {}
+FontFamily::FontFamily(FamilyVariant variant, std::vector<Font>&& fonts)
+ : FontFamily(LocaleListCache::kEmptyListId, variant, std::move(fonts),
+ false /* isCustomFallback */) {}
-FontFamily::FontFamily(uint32_t localeListId, Variant variant, std::vector<Font>&& fonts)
+FontFamily::FontFamily(uint32_t localeListId, FamilyVariant variant, std::vector<Font>&& fonts,
+ bool isCustomFallback)
: mLocaleListId(localeListId),
mVariant(variant),
mFonts(std::move(fonts)),
mIsColorEmoji(LocaleListCache::getById(localeListId).getEmojiStyle() ==
- EmojiStyle::EMOJI) {
+ EmojiStyle::EMOJI),
+ mIsCustomFallback(isCustomFallback) {
MINIKIN_ASSERT(!mFonts.empty(), "FontFamily must contain at least one font.");
computeCoverage();
}
@@ -235,7 +238,8 @@
fonts.push_back(Font::Builder(minikinFont).setStyle(font.style()).build());
}
- return std::shared_ptr<FontFamily>(new FontFamily(mLocaleListId, mVariant, std::move(fonts)));
+ return std::shared_ptr<FontFamily>(
+ new FontFamily(mLocaleListId, mVariant, std::move(fonts), mIsCustomFallback));
}
} // namespace minikin
diff --git a/libs/minikin/GraphemeBreak.cpp b/libs/minikin/GraphemeBreak.cpp
index 52b99c0..2bb5c17 100644
--- a/libs/minikin/GraphemeBreak.cpp
+++ b/libs/minikin/GraphemeBreak.cpp
@@ -106,10 +106,6 @@
if ((p1 == U_GCB_LVT || p1 == U_GCB_T) && p2 == U_GCB_T) {
return false;
}
- // Rule GB9, x (Extend | ZWJ); Rule GB9a, x SpacingMark; Rule GB9b, Prepend x
- if (p2 == U_GCB_EXTEND || p2 == U_GCB_ZWJ || p2 == U_GCB_SPACING_MARK || p1 == U_GCB_PREPEND) {
- return false;
- }
// This is used to decide font-dependent grapheme clusters. If we don't have the advance
// information, we become conservative in grapheme breaking and assume that it has no advance.
@@ -122,48 +118,32 @@
return true;
}
- // Note: For Rule GB10 and GB11 below, we do not use the Unicode line breaking properties for
- // determining emoji-ness and carry our own data, because our data could be more fresh than what
- // ICU provides.
- //
- // Tailored version of Rule GB10, (E_Base | EBG) Extend* × E_Modifier.
- // The rule itself says do not break between emoji base and emoji modifiers, skipping all Extend
- // characters. Variation selectors are considered Extend, so they are handled fine.
- //
- // We tailor this by requiring that an actual ligature is formed. If the font doesn't form a
- // ligature, we allow a break before the modifier.
- if (isEmojiModifier(c2)) {
- uint32_t c0 = c1;
+ // Rule GB9, x (Extend | ZWJ); Rule GB9a, x SpacingMark; Rule GB9b, Prepend x
+ if (p2 == U_GCB_EXTEND || p2 == U_GCB_ZWJ || p2 == U_GCB_SPACING_MARK || p1 == U_GCB_PREPEND) {
+ return false;
+ }
+
+ // Tailored version of Rule GB11
+ // \p{Extended_Pictographic} Extend* ZWJ x \p{Extended_Pictographic}
+ if (offset_back > start && p1 == U_GCB_ZWJ &&
+ u_hasBinaryProperty(c2, UCHAR_EXTENDED_PICTOGRAPHIC)) {
+ uint32_t c0 = 0;
size_t offset_backback = offset_back;
- int32_t p0 = p1;
- if (p0 == U_GCB_EXTEND && offset_backback > start) {
- // skip over emoji variation selector
+ int32_t p0 = 0;
+
+ U16_PREV(buf, start, offset_backback, c0);
+ p0 = tailoredGraphemeClusterBreak(c0);
+
+ while (p0 == U_GCB_EXTEND && offset_backback > start) {
U16_PREV(buf, start, offset_backback, c0);
p0 = tailoredGraphemeClusterBreak(c0);
}
- if (isEmojiBase(c0)) {
+
+ if (u_hasBinaryProperty(c0, UCHAR_EXTENDED_PICTOGRAPHIC)) {
return false;
}
}
- // Tailored version of Rule GB11, ZWJ × (Glue_After_Zwj | EBG)
- // We try to make emoji sequences with ZWJ a single grapheme cluster, but only if they actually
- // merge to one cluster. So we are more relaxed than the UAX #29 rules in accepting any emoji
- // character after the ZWJ, but are tighter in that we only treat it as one cluster if a
- // ligature is actually formed and we also require the character before the ZWJ to also be an
- // emoji.
- if (p1 == U_GCB_ZWJ && isEmoji(c2) && offset_back > start) {
- // look at character before ZWJ to see that both can participate in an emoji zwj sequence
- uint32_t c0 = 0;
- size_t offset_backback = offset_back;
- U16_PREV(buf, start, offset_backback, c0);
- if (c0 == 0xFE0F && offset_backback > start) {
- // skip over emoji variation selector
- U16_PREV(buf, start, offset_backback, c0);
- }
- if (isEmoji(c0)) {
- return false;
- }
- }
+
// Tailored version of Rule GB12 and Rule GB13 that look at even-odd cases.
// sot (RI RI)* RI x RI
// [^RI] (RI RI)* RI x RI
diff --git a/libs/minikin/GreedyLineBreaker.cpp b/libs/minikin/GreedyLineBreaker.cpp
index 661a42e..f6952a9 100644
--- a/libs/minikin/GreedyLineBreaker.cpp
+++ b/libs/minikin/GreedyLineBreaker.cpp
@@ -78,7 +78,7 @@
void updateLineWidth(uint16_t c, float width);
// Break line if current line exceeds the line limit.
- void processLineBreak(uint32_t offset, WordBreaker* breaker);
+ void processLineBreak(uint32_t offset, WordBreaker* breaker, bool doHyphenation);
// Try to break with previous word boundary.
// Returns false if unable to break by word boundary.
@@ -96,7 +96,8 @@
// This method only breaks at the first offset which has the longest width for the line width
// limit. This method don't keep line breaking even if the rest of the word exceeds the line
// width limit.
- void doLineBreakWithGraphemeBounds(const Range& range);
+ // This method return true if there is no characters to be processed.
+ bool doLineBreakWithGraphemeBounds(const Range& range);
// Info about the line currently processing.
uint32_t mLineNum = 0;
@@ -193,9 +194,9 @@
continue; // Not a hyphenation point.
}
- const float width = targetRun->measureHyphenPiece(mTextBuf, contextRange.split(i).first,
- mStartHyphenEdit, editForThisLine(hyph),
- nullptr /* advances */, nullptr);
+ const float width =
+ targetRun->measureHyphenPiece(mTextBuf, contextRange.split(i).first,
+ mStartHyphenEdit, editForThisLine(hyph), nullptr);
if (width <= mLineWidthLimit) {
// There are still space, remember current offset and look up next hyphenation point.
@@ -214,7 +215,7 @@
const StartHyphenEdit nextLineStartHyphenEdit = editForNextLine(hyph);
const float remainingCharWidths = targetRun->measureHyphenPiece(
mTextBuf, contextRange.split(prevOffset).second, nextLineStartHyphenEdit,
- EndHyphenEdit::NO_EDIT, nullptr /* advances */, nullptr);
+ EndHyphenEdit::NO_EDIT, nullptr);
breakLineAt(prevOffset, prevWidth,
remainingCharWidths - (mSumOfCharWidths - mLineWidth), remainingCharWidths,
editForThisLine(hyph), nextLineStartHyphenEdit);
@@ -243,7 +244,7 @@
const StartHyphenEdit nextLineStartHyphenEdit = editForNextLine(hyph);
const float remainingCharWidths = targetRun->measureHyphenPiece(
mTextBuf, contextRange.split(prevOffset).second, nextLineStartHyphenEdit,
- EndHyphenEdit::NO_EDIT, nullptr /* advances */, nullptr);
+ EndHyphenEdit::NO_EDIT, nullptr);
breakLineAt(prevOffset, prevWidth, remainingCharWidths - (mSumOfCharWidths - mLineWidth),
remainingCharWidths, editForThisLine(hyph), nextLineStartHyphenEdit);
@@ -253,7 +254,7 @@
}
// TODO: Respect trailing line end spaces.
-void GreedyLineBreaker::doLineBreakWithGraphemeBounds(const Range& range) {
+bool GreedyLineBreaker::doLineBreakWithGraphemeBounds(const Range& range) {
double width = mMeasuredText.widths[range.getStart()];
// Starting from + 1 since at least one character needs to be assigned to a line.
@@ -269,7 +270,7 @@
// This method only breaks at the first longest offset, since we may want to hyphenate
// the rest of the word.
- return;
+ return false;
} else {
width += w;
}
@@ -278,6 +279,7 @@
// Reaching here means even one character (or cluster) doesn't fit the line.
// Give up and break at the end of this range.
breakLineAt(range.getEnd(), mLineWidth, 0, 0, EndHyphenEdit::NO_EDIT, StartHyphenEdit::NO_EDIT);
+ return true;
}
void GreedyLineBreaker::updateLineWidth(uint16_t c, float width) {
@@ -292,15 +294,18 @@
}
}
-void GreedyLineBreaker::processLineBreak(uint32_t offset, WordBreaker* breaker) {
+void GreedyLineBreaker::processLineBreak(uint32_t offset, WordBreaker* breaker,
+ bool doHyphenation) {
while (mLineWidth > mLineWidthLimit) {
const Range lineRange(getPrevLineBreakOffset(), offset); // The range we need to address.
if (tryLineBreakWithWordBreak()) {
continue; // The word in the new line may still be too long for the line limit.
- } else if (tryLineBreakWithHyphenation(lineRange, breaker)) {
+ } else if (doHyphenation && tryLineBreakWithHyphenation(lineRange, breaker)) {
continue; // TODO: we may be able to return here.
} else {
- doLineBreakWithGraphemeBounds(lineRange);
+ if (doLineBreakWithGraphemeBounds(lineRange)) {
+ return;
+ }
}
}
@@ -337,8 +342,10 @@
updateLineWidth(mTextBuf[i], mMeasuredText.widths[i]);
if ((i + 1) == nextWordBoundaryOffset) {
- // Only process line break at word boundary.
- processLineBreak(i + 1, &wordBreaker);
+ // Only process line break at word boundary and the run can break into some pieces.
+ if (run->canBreak() || nextWordBoundaryOffset == range.getEnd()) {
+ processLineBreak(i + 1, &wordBreaker, run->canBreak());
+ }
nextWordBoundaryOffset = wordBreaker.next();
}
}
@@ -359,15 +366,12 @@
for (const auto& breakPoint : mBreakPoints) {
// TODO: compute these during line breaking if these takes longer time.
bool hasTabChar = false;
- MinikinExtent extent = {0.0, 0.0, 0.0};
for (uint32_t i = prevBreakOffset; i < breakPoint.offset; ++i) {
- const uint16_t c = mTextBuf[i];
- hasTabChar |= c == CHAR_TAB;
- if (!isLineSpaceExcludeChar(c)) {
- extent.extendBy(mMeasuredText.extents[i]);
- }
+ hasTabChar |= mTextBuf[i] == CHAR_TAB;
}
+ MinikinExtent extent =
+ mMeasuredText.getExtent(mTextBuf, Range(prevBreakOffset, breakPoint.offset));
out.breakPoints.push_back(breakPoint.offset);
out.widths.push_back(breakPoint.lineWidth);
out.ascents.push_back(extent.ascent);
diff --git a/libs/minikin/Layout.cpp b/libs/minikin/Layout.cpp
index d05a56e..f37ae1f 100644
--- a/libs/minikin/Layout.cpp
+++ b/libs/minikin/Layout.cpp
@@ -29,7 +29,6 @@
#include <log/log.h>
#include <unicode/ubidi.h>
#include <unicode/utf16.h>
-#include <utils/JenkinsHash.h>
#include <utils/LruCache.h>
#include "minikin/Emoji.h"
@@ -39,233 +38,34 @@
#include "minikin/Macros.h"
#include "BidiUtils.h"
+#include "LayoutSplitter.h"
#include "LayoutUtils.h"
#include "LocaleListCache.h"
#include "MinikinInternal.h"
namespace minikin {
-namespace {
-
-struct SkiaArguments {
- const MinikinFont* font;
- const MinikinPaint* paint;
- FontFakery fakery;
-};
-
-static hb_position_t harfbuzzGetGlyphHorizontalAdvance(hb_font_t* /* hbFont */, void* fontData,
- hb_codepoint_t glyph, void* /* userData */) {
- SkiaArguments* args = reinterpret_cast<SkiaArguments*>(fontData);
- float advance = args->font->GetHorizontalAdvance(glyph, *args->paint, args->fakery);
- return 256 * advance + 0.5;
-}
-
-static hb_bool_t harfbuzzGetGlyphHorizontalOrigin(hb_font_t* /* hbFont */, void* /* fontData */,
- hb_codepoint_t /* glyph */,
- hb_position_t* /* x */, hb_position_t* /* y */,
- void* /* userData */) {
- // Just return true, following the way that Harfbuzz-FreeType implementation does.
- return true;
-}
-
-// TODO: Initialize in Zygote if it helps.
-hb_font_funcs_t* getFontFuncs() {
- static hb_font_funcs_t* fontFuncs = nullptr;
- static std::once_flag once;
- std::call_once(once, [&]() {
- fontFuncs = hb_font_funcs_create();
- // Override the h_advance function since we can't use HarfBuzz's implemenation. It may
- // return the wrong value if the font uses hinting aggressively.
- hb_font_funcs_set_glyph_h_advance_func(fontFuncs, harfbuzzGetGlyphHorizontalAdvance, 0, 0);
- hb_font_funcs_set_glyph_h_origin_func(fontFuncs, harfbuzzGetGlyphHorizontalOrigin, 0, 0);
- hb_font_funcs_make_immutable(fontFuncs);
- });
- return fontFuncs;
-}
-
-// TODO: Initialize in Zygote if it helps.
-hb_font_funcs_t* getFontFuncsForEmoji() {
- static hb_font_funcs_t* fontFuncs = nullptr;
- static std::once_flag once;
- std::call_once(once, [&]() {
- fontFuncs = hb_font_funcs_create();
- // Don't override the h_advance function since we use HarfBuzz's implementation for emoji
- // for performance reasons.
- // Note that it is technically possible for a TrueType font to have outline and embedded
- // bitmap at the same time. We ignore modified advances of hinted outline glyphs in that
- // case.
- hb_font_funcs_set_glyph_h_origin_func(fontFuncs, harfbuzzGetGlyphHorizontalOrigin, 0, 0);
- hb_font_funcs_make_immutable(fontFuncs);
- });
- return fontFuncs;
-}
-
-} // namespace
-
-void MinikinRect::join(const MinikinRect& r) {
- if (isEmpty()) {
- set(r);
- } else if (!r.isEmpty()) {
- mLeft = std::min(mLeft, r.mLeft);
- mTop = std::min(mTop, r.mTop);
- mRight = std::max(mRight, r.mRight);
- mBottom = std::max(mBottom, r.mBottom);
- }
-}
-
-void Layout::reset() {
- mGlyphs.clear();
- mFaces.clear();
- mBounds.setEmpty();
- mAdvances.clear();
- mExtents.clear();
- mAdvance = 0;
-}
-
-static bool isColorBitmapFont(const HbFontUniquePtr& font) {
- HbBlob cbdt(font, HB_TAG('C', 'B', 'D', 'T'));
- return cbdt;
-}
-
-static float HBFixedToFloat(hb_position_t v) {
- return scalbnf(v, -8);
-}
-
-static hb_position_t HBFloatToFixed(float v) {
- return scalbnf(v, +8);
-}
-
-void Layout::dump() const {
- for (size_t i = 0; i < mGlyphs.size(); i++) {
- const LayoutGlyph& glyph = mGlyphs[i];
- std::cout << glyph.glyph_id << ": " << glyph.x << ", " << glyph.y << std::endl;
- }
-}
-
-uint8_t Layout::findOrPushBackFace(const FakedFont& face) {
- MINIKIN_ASSERT(mFaces.size() < MAX_FAMILY_COUNT, "mFaces must not exceeds %d",
- MAX_FAMILY_COUNT);
- uint8_t ix = 0;
- for (; ix < mFaces.size(); ix++) {
- if (mFaces[ix].font == face.font) {
- return ix;
- }
- }
- ix = mFaces.size();
- mFaces.push_back(face);
- return ix;
-}
-
-static hb_codepoint_t decodeUtf16(const uint16_t* chars, size_t len, ssize_t* iter) {
- UChar32 result;
- U16_NEXT(chars, *iter, (ssize_t)len, result);
- if (U_IS_SURROGATE(result)) { // isolated surrogate
- result = 0xFFFDu; // U+FFFD REPLACEMENT CHARACTER
- }
- return (hb_codepoint_t)result;
-}
-
-static hb_script_t getScriptRun(const uint16_t* chars, size_t len, ssize_t* iter) {
- if (size_t(*iter) == len) {
- return HB_SCRIPT_UNKNOWN;
- }
- uint32_t cp = decodeUtf16(chars, len, iter);
- hb_unicode_funcs_t* unicode_func = hb_unicode_funcs_get_default();
- hb_script_t current_script = hb_unicode_script(unicode_func, cp);
- for (;;) {
- if (size_t(*iter) == len) break;
- const ssize_t prev_iter = *iter;
- cp = decodeUtf16(chars, len, iter);
- const hb_script_t script = hb_unicode_script(unicode_func, cp);
- if (script != current_script) {
- if (current_script == HB_SCRIPT_INHERITED || current_script == HB_SCRIPT_COMMON) {
- current_script = script;
- } else if (script == HB_SCRIPT_INHERITED || script == HB_SCRIPT_COMMON) {
- continue;
- } else {
- *iter = prev_iter;
- break;
- }
- }
- }
- if (current_script == HB_SCRIPT_INHERITED) {
- current_script = HB_SCRIPT_COMMON;
- }
-
- return current_script;
-}
-
-/**
- * Disable certain scripts (mostly those with cursive connection) from having letterspacing
- * applied. See https://github.com/behdad/harfbuzz/issues/64 for more details.
- */
-static bool isScriptOkForLetterspacing(hb_script_t script) {
- return !(script == HB_SCRIPT_ARABIC || script == HB_SCRIPT_NKO ||
- script == HB_SCRIPT_PSALTER_PAHLAVI || script == HB_SCRIPT_MANDAIC ||
- script == HB_SCRIPT_MONGOLIAN || script == HB_SCRIPT_PHAGS_PA ||
- script == HB_SCRIPT_DEVANAGARI || script == HB_SCRIPT_BENGALI ||
- script == HB_SCRIPT_GURMUKHI || script == HB_SCRIPT_MODI ||
- script == HB_SCRIPT_SHARADA || script == HB_SCRIPT_SYLOTI_NAGRI ||
- script == HB_SCRIPT_TIRHUTA || script == HB_SCRIPT_OGHAM);
-}
-
void Layout::doLayout(const U16StringPiece& textBuf, const Range& range, Bidi bidiFlags,
const MinikinPaint& paint, StartHyphenEdit startHyphen,
EndHyphenEdit endHyphen) {
const uint32_t count = range.getLength();
- reset();
mAdvances.resize(count, 0);
- mExtents.resize(count);
-
+ mGlyphs.reserve(count);
for (const BidiText::RunInfo& runInfo : BidiText(textBuf, range, bidiFlags)) {
doLayoutRunCached(textBuf, runInfo.range, runInfo.isRtl, paint, range.getStart(),
- startHyphen, endHyphen, nullptr, this, nullptr, nullptr, nullptr,
- nullptr);
+ startHyphen, endHyphen, this, nullptr);
}
}
-void Layout::doLayoutWithPrecomputedPieces(const U16StringPiece& textBuf, const Range& range,
- Bidi bidiFlags, const MinikinPaint& paint,
- StartHyphenEdit startHyphen, EndHyphenEdit endHyphen,
- const LayoutPieces& lpIn) {
- const uint32_t count = range.getLength();
- reset();
- mAdvances.resize(count, 0);
- mExtents.resize(count);
-
- for (const BidiText::RunInfo& runInfo : BidiText(textBuf, range, bidiFlags)) {
- doLayoutRunCached(textBuf, runInfo.range, runInfo.isRtl, paint, range.getStart(),
- startHyphen, endHyphen, &lpIn, this, nullptr, nullptr, nullptr, nullptr);
- }
-}
-
-std::pair<float, MinikinRect> Layout::getBoundsWithPrecomputedPieces(const U16StringPiece& textBuf,
- const Range& range,
- Bidi bidiFlags,
- const MinikinPaint& paint,
- const LayoutPieces& pieces) {
- MinikinRect rect;
- float advance = 0;
- for (const BidiText::RunInfo& runInfo : BidiText(textBuf, range, bidiFlags)) {
- advance += doLayoutRunCached(textBuf, runInfo.range, runInfo.isRtl, paint, 0,
- StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, &pieces,
- nullptr, nullptr, nullptr, &rect, nullptr);
- }
- return std::make_pair(advance, rect);
-}
-
float Layout::measureText(const U16StringPiece& textBuf, const Range& range, Bidi bidiFlags,
const MinikinPaint& paint, StartHyphenEdit startHyphen,
- EndHyphenEdit endHyphen, float* advances, MinikinExtent* extents,
- LayoutPieces* pieces) {
+ EndHyphenEdit endHyphen, float* advances) {
float advance = 0;
for (const BidiText::RunInfo& runInfo : BidiText(textBuf, range, bidiFlags)) {
const size_t offset = range.toRangeOffset(runInfo.range.getStart());
float* advancesForRun = advances ? advances + offset : nullptr;
- MinikinExtent* extentsForRun = extents ? extents + offset : nullptr;
advance += doLayoutRunCached(textBuf, runInfo.range, runInfo.isRtl, paint, 0, startHyphen,
- endHyphen, nullptr, nullptr, advancesForRun, extentsForRun,
- nullptr, pieces);
+ endHyphen, nullptr, advancesForRun);
}
return advance;
}
@@ -273,134 +73,69 @@
float Layout::doLayoutRunCached(const U16StringPiece& textBuf, const Range& range, bool isRtl,
const MinikinPaint& paint, size_t dstStart,
StartHyphenEdit startHyphen, EndHyphenEdit endHyphen,
- const LayoutPieces* lpIn, Layout* layout, float* advances,
- MinikinExtent* extents, MinikinRect* bounds, LayoutPieces* lpOut) {
+ Layout* layout, float* advances) {
if (!range.isValid()) {
return 0.0f; // ICU failed to retrieve the bidi run?
}
- const uint16_t* buf = textBuf.data();
- const uint32_t bufSize = textBuf.size();
- const uint32_t start = range.getStart();
- const uint32_t end = range.getEnd();
float advance = 0;
- if (!isRtl) {
- // left to right
- uint32_t wordstart =
- start == bufSize ? start : getPrevWordBreakForCache(buf, start + 1, bufSize);
- uint32_t wordend;
- for (size_t iter = start; iter < end; iter = wordend) {
- wordend = getNextWordBreakForCache(buf, iter, bufSize);
- const uint32_t wordcount = std::min(end, wordend) - iter;
- const uint32_t offset = iter - start;
- advance += doLayoutWord(buf + wordstart, iter - wordstart, wordcount,
- wordend - wordstart, isRtl, paint, iter - dstStart,
- // Only apply hyphen to the first or last word in the string.
- iter == start ? startHyphen : StartHyphenEdit::NO_EDIT,
- wordend >= end ? endHyphen : EndHyphenEdit::NO_EDIT, lpIn,
- layout, advances ? advances + offset : nullptr,
- extents ? extents + offset : nullptr, bounds, lpOut);
- wordstart = wordend;
- }
- } else {
- // right to left
- uint32_t wordstart;
- uint32_t wordend = end == 0 ? 0 : getNextWordBreakForCache(buf, end - 1, bufSize);
- for (size_t iter = end; iter > start; iter = wordstart) {
- wordstart = getPrevWordBreakForCache(buf, iter, bufSize);
- uint32_t bufStart = std::max(start, wordstart);
- const uint32_t offset = bufStart - start;
- advance += doLayoutWord(buf + wordstart, bufStart - wordstart, iter - bufStart,
- wordend - wordstart, isRtl, paint, bufStart - dstStart,
- // Only apply hyphen to the first (rightmost) or last (leftmost)
- // word in the string.
- wordstart <= start ? startHyphen : StartHyphenEdit::NO_EDIT,
- iter == end ? endHyphen : EndHyphenEdit::NO_EDIT, lpIn, layout,
- advances ? advances + offset : nullptr,
- extents ? extents + offset : nullptr, bounds, lpOut);
- wordend = wordstart;
- }
+ for (const auto[context, piece] : LayoutSplitter(textBuf, range, isRtl)) {
+ // Hyphenation only applies to the start/end of run.
+ const StartHyphenEdit pieceStartHyphen =
+ (piece.getStart() == range.getStart()) ? startHyphen : StartHyphenEdit::NO_EDIT;
+ const EndHyphenEdit pieceEndHyphen =
+ (piece.getEnd() == range.getEnd()) ? endHyphen : EndHyphenEdit::NO_EDIT;
+ float* advancesForRun =
+ advances ? advances + (piece.getStart() - range.getStart()) : nullptr;
+ advance += doLayoutWord(textBuf.data() + context.getStart(),
+ piece.getStart() - context.getStart(), piece.getLength(),
+ context.getLength(), isRtl, paint, piece.getStart() - dstStart,
+ pieceStartHyphen, pieceEndHyphen, layout, advancesForRun);
}
return advance;
}
class LayoutAppendFunctor {
public:
- LayoutAppendFunctor(const U16StringPiece& textBuf, const Range& range,
- const MinikinPaint& paint, bool dir, StartHyphenEdit startEdit,
- EndHyphenEdit endEdit, Layout* layout, float* advances,
- MinikinExtent* extents, LayoutPieces* pieces, float* totalAdvance,
- MinikinRect* bounds, uint32_t outOffset, float wordSpacing)
- : mTextBuf(textBuf),
- mRange(range),
- mPaint(paint),
- mDir(dir),
- mStartEdit(startEdit),
- mEndEdit(endEdit),
- mLayout(layout),
+ LayoutAppendFunctor(Layout* layout, float* advances, float* totalAdvance, uint32_t outOffset,
+ float wordSpacing)
+ : mLayout(layout),
mAdvances(advances),
- mExtents(extents),
- mPieces(pieces),
mTotalAdvance(totalAdvance),
- mBounds(bounds),
mOutOffset(outOffset),
mWordSpacing(wordSpacing) {}
- void operator()(const Layout& layout) {
+ void operator()(const LayoutPiece& layoutPiece, const MinikinPaint& /* paint */) {
if (mLayout) {
- mLayout->appendLayout(layout, mOutOffset, mWordSpacing);
+ mLayout->appendLayout(layoutPiece, mOutOffset, mWordSpacing);
}
if (mAdvances) {
- layout.getAdvances(mAdvances);
+ const std::vector<float>& advances = layoutPiece.advances();
+ std::copy(advances.begin(), advances.end(), mAdvances);
}
if (mTotalAdvance) {
- *mTotalAdvance = layout.getAdvance();
- }
- if (mExtents) {
- layout.getExtents(mExtents);
- }
- if (mBounds) {
- mBounds->join(layout.getBounds());
- }
- if (mPieces) {
- mPieces->insert(mTextBuf, mRange, mPaint, mDir, mStartEdit, mEndEdit, layout);
+ *mTotalAdvance = layoutPiece.advance();
}
}
private:
- const U16StringPiece& mTextBuf;
- const Range& mRange;
- const MinikinPaint& mPaint;
- bool mDir;
- StartHyphenEdit mStartEdit;
- EndHyphenEdit mEndEdit;
Layout* mLayout;
float* mAdvances;
- MinikinExtent* mExtents;
- LayoutPieces* mPieces;
float* mTotalAdvance;
- MinikinRect* mBounds;
const uint32_t mOutOffset;
const float mWordSpacing;
};
float Layout::doLayoutWord(const uint16_t* buf, size_t start, size_t count, size_t bufSize,
bool isRtl, const MinikinPaint& paint, size_t bufStart,
- StartHyphenEdit startHyphen, EndHyphenEdit endHyphen,
- const LayoutPieces* lpIn, Layout* layout, float* advances,
- MinikinExtent* extents, MinikinRect* bounds, LayoutPieces* lpOut) {
+ StartHyphenEdit startHyphen, EndHyphenEdit endHyphen, Layout* layout,
+ float* advances) {
float wordSpacing = count == 1 && isWordSpace(buf[start]) ? paint.wordSpacing : 0;
- float totalAdvance;
+ float totalAdvance = 0;
const U16StringPiece textBuf(buf, bufSize);
const Range range(start, start + count);
- LayoutAppendFunctor f(textBuf, range, paint, isRtl, startHyphen, endHyphen, layout, advances,
- extents, lpOut, &totalAdvance, bounds, bufStart, wordSpacing);
- if (lpIn != nullptr) {
- lpIn->getOrCreate(textBuf, range, paint, isRtl, startHyphen, endHyphen, f);
- } else {
- LayoutCache::getInstance().getOrCreate(textBuf, range, paint, isRtl, startHyphen, endHyphen,
- f);
- }
+ LayoutAppendFunctor f(layout, advances, &totalAdvance, bufStart, wordSpacing);
+ LayoutCache::getInstance().getOrCreate(textBuf, range, paint, isRtl, startHyphen, endHyphen, f);
if (wordSpacing != 0) {
totalAdvance += wordSpacing;
@@ -411,414 +146,22 @@
return totalAdvance;
}
-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);
- }
+void Layout::appendLayout(const LayoutPiece& src, size_t start, float extraAdvance) {
+ for (size_t i = 0; i < src.glyphCount(); i++) {
+ mGlyphs.emplace_back(src.fontAt(i), src.glyphIdAt(i), mAdvance + src.pointAt(i).x,
+ src.pointAt(i).y);
}
-}
-
-static inline hb_codepoint_t determineHyphenChar(hb_codepoint_t preferredHyphen, hb_font_t* font) {
- hb_codepoint_t glyph;
- if (preferredHyphen == 0x058A /* ARMENIAN_HYPHEN */
- || preferredHyphen == 0x05BE /* HEBREW PUNCTUATION MAQAF */
- || preferredHyphen == 0x1400 /* CANADIAN SYLLABIC HYPHEN */) {
- if (hb_font_get_nominal_glyph(font, preferredHyphen, &glyph)) {
- return preferredHyphen;
- } else {
- // The original hyphen requested was not supported. Let's try and see if the
- // Unicode hyphen is supported.
- preferredHyphen = CHAR_HYPHEN;
- }
- }
- if (preferredHyphen == CHAR_HYPHEN) { /* HYPHEN */
- // Fallback to ASCII HYPHEN-MINUS if the font didn't have a glyph for the preferred hyphen.
- // Note that we intentionally don't do anything special if the font doesn't have a
- // HYPHEN-MINUS either, so a tofu could be shown, hinting towards something missing.
- if (!hb_font_get_nominal_glyph(font, preferredHyphen, &glyph)) {
- return 0x002D; // HYPHEN-MINUS
- }
- }
- return 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);
- for (size_t i = 0; i < size; i++) {
- hb_buffer_add(buffer.get(), determineHyphenChar(chars[i], font.get()), cluster);
- }
-}
-
-// Returns the cluster value assigned to the first codepoint added to the buffer, which can be used
-// to translate cluster values returned by HarfBuzz to input indices.
-static inline uint32_t addToHbBuffer(const HbBufferUniquePtr& buffer, const uint16_t* buf,
- size_t start, size_t count, size_t bufSize,
- ssize_t scriptRunStart, ssize_t scriptRunEnd,
- StartHyphenEdit inStartHyphen, EndHyphenEdit inEndHyphen,
- const HbFontUniquePtr& hbFont) {
- // Only hyphenate the very first script run for starting hyphens.
- const StartHyphenEdit startHyphen =
- (scriptRunStart == 0) ? inStartHyphen : StartHyphenEdit::NO_EDIT;
- // Only hyphenate the very last script run for ending hyphens.
- const EndHyphenEdit endHyphen =
- (static_cast<size_t>(scriptRunEnd) == count) ? inEndHyphen : EndHyphenEdit::NO_EDIT;
-
- // In the following code, we drop the pre-context and/or post-context if there is a
- // hyphen edit at that end. This is not absolutely necessary, since HarfBuzz uses
- // contexts only for joining scripts at the moment, e.g. to determine if the first or
- // last letter of a text range to shape should take a joining form based on an
- // adjacent letter or joiner (that comes from the context).
- //
- // TODO: Revisit this for:
- // 1. Desperate breaks for joining scripts like Arabic (where it may be better to keep
- // the context);
- // 2. Special features like start-of-word font features (not implemented in HarfBuzz
- // yet).
-
- // We don't have any start-of-line replacement edit yet, so we don't need to check for
- // those.
- if (isInsertion(startHyphen)) {
- // A cluster value of zero guarantees that the inserted hyphen will be in the same
- // cluster with the next codepoint, since there is no pre-context.
- addHyphenToHbBuffer(buffer, hbFont, startHyphen, 0 /* cluster */);
- }
-
- const uint16_t* hbText;
- int hbTextLength;
- unsigned int hbItemOffset;
- unsigned int hbItemLength = scriptRunEnd - scriptRunStart; // This is >= 1.
-
- const bool hasEndInsertion = isInsertion(endHyphen);
- const bool hasEndReplacement = isReplacement(endHyphen);
- if (hasEndReplacement) {
- // Skip the last code unit while copying the buffer for HarfBuzz if it's a replacement. We
- // don't need to worry about non-BMP characters yet since replacements are only done for
- // code units at the moment.
- hbItemLength -= 1;
- }
-
- if (startHyphen == StartHyphenEdit::NO_EDIT) {
- // No edit at the beginning. Use the whole pre-context.
- hbText = buf;
- hbItemOffset = start + scriptRunStart;
- } else {
- // There's an edit at the beginning. Drop the pre-context and start the buffer at where we
- // want to start shaping.
- hbText = buf + start + scriptRunStart;
- hbItemOffset = 0;
- }
-
- if (endHyphen == EndHyphenEdit::NO_EDIT) {
- // No edit at the end, use the whole post-context.
- hbTextLength = (buf + bufSize) - hbText;
- } else {
- // There is an edit at the end. Drop the post-context.
- hbTextLength = hbItemOffset + hbItemLength;
- }
-
- hb_buffer_add_utf16(buffer.get(), hbText, hbTextLength, hbItemOffset, hbItemLength);
-
- unsigned int numCodepoints;
- hb_glyph_info_t* cpInfo = hb_buffer_get_glyph_infos(buffer.get(), &numCodepoints);
-
- // Add the hyphen at the end, if there's any.
- if (hasEndInsertion || hasEndReplacement) {
- // When a hyphen is inserted, by assigning the added hyphen and the last
- // codepoint added to the HarfBuzz buffer to the same cluster, we can make sure
- // that they always remain in the same cluster, even if the last codepoint gets
- // merged into another cluster (for example when it's a combining mark).
- //
- // When a replacement happens instead, we want it to get the cluster value of
- // the character it's replacing, which is one "codepoint length" larger than
- // the last cluster. But since the character replaced is always just one
- // code unit, we can just add 1.
- uint32_t hyphenCluster;
- if (numCodepoints == 0) {
- // Nothing was added to the HarfBuzz buffer. This can only happen if
- // we have a replacement that is replacing a one-code unit script run.
- hyphenCluster = 0;
- } else {
- hyphenCluster = cpInfo[numCodepoints - 1].cluster + (uint32_t)hasEndReplacement;
- }
- addHyphenToHbBuffer(buffer, hbFont, endHyphen, hyphenCluster);
- // Since we have just added to the buffer, cpInfo no longer necessarily points to
- // the right place. Refresh it.
- cpInfo = hb_buffer_get_glyph_infos(buffer.get(), nullptr /* we don't need the size */);
- }
- return cpInfo[0].cluster;
-}
-
-void Layout::doLayoutRun(const uint16_t* buf, size_t start, size_t count, size_t bufSize,
- bool isRtl, const MinikinPaint& paint, StartHyphenEdit startHyphen,
- EndHyphenEdit endHyphen) {
- HbBufferUniquePtr buffer(hb_buffer_create());
- std::vector<FontCollection::Run> items;
- paint.font->itemize(buf + start, count, paint, &items);
-
- 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<HbFontUniquePtr> hbFonts;
- double size = paint.size;
- double scaleX = paint.scaleX;
-
- float x = mAdvance;
- float y = 0;
- for (int run_ix = isRtl ? items.size() - 1 : 0;
- isRtl ? run_ix >= 0 : run_ix < static_cast<int>(items.size());
- isRtl ? --run_ix : ++run_ix) {
- FontCollection::Run& run = items[run_ix];
- const FakedFont& fakedFont = run.fakedFont;
- const uint8_t font_ix = findOrPushBackFace(fakedFont);
- if (hbFonts.size() == font_ix) { // findOrPushBackFace push backed the new face.
- // We override some functions which are not thread safe.
- HbFontUniquePtr font(hb_font_create_sub_font(fakedFont.font->baseFont().get()));
- hb_font_set_funcs(
- font.get(), isColorBitmapFont(font) ? getFontFuncsForEmoji() : getFontFuncs(),
- new SkiaArguments({fakedFont.font->typeface().get(), &paint, fakedFont.fakery}),
- [](void* data) { delete reinterpret_cast<SkiaArguments*>(data); });
- hbFonts.push_back(std::move(font));
- }
- const HbFontUniquePtr& hbFont = hbFonts[font_ix];
-
- MinikinExtent verticalExtent;
- fakedFont.font->typeface()->GetFontExtent(&verticalExtent, paint, fakedFont.fakery);
- std::fill(&mExtents[run.start], &mExtents[run.end], verticalExtent);
-
- hb_font_set_ppem(hbFont.get(), size * scaleX, size);
- hb_font_set_scale(hbFont.get(), HBFloatToFixed(size * scaleX), HBFloatToFixed(size));
-
- const bool is_color_bitmap_font = isColorBitmapFont(hbFont);
-
- // TODO: if there are multiple scripts within a font in an RTL run,
- // we need to reorder those runs. This is unlikely with our current
- // font stack, but should be done for correctness.
-
- // Note: scriptRunStart and scriptRunEnd, as well as run.start and run.end, run between 0
- // and count.
- ssize_t scriptRunEnd;
- for (ssize_t scriptRunStart = run.start; scriptRunStart < run.end;
- scriptRunStart = scriptRunEnd) {
- scriptRunEnd = scriptRunStart;
- hb_script_t script = getScriptRun(buf + start, run.end, &scriptRunEnd /* iterator */);
- // After the last line, scriptRunEnd is guaranteed to have increased, since the only
- // time getScriptRun does not increase its iterator is when it has already reached the
- // end of the buffer. But that can't happen, since if we have already reached the end
- // of the buffer, we should have had (scriptRunEnd == run.end), which means
- // (scriptRunStart == run.end) which is impossible due to the exit condition of the for
- // loop. So we can be sure that scriptRunEnd > scriptRunStart.
-
- double letterSpace = 0.0;
- double letterSpaceHalfLeft = 0.0;
- double letterSpaceHalfRight = 0.0;
-
- if (paint.letterSpacing != 0.0 && isScriptOkForLetterspacing(script)) {
- letterSpace = paint.letterSpacing * size * scaleX;
- if ((paint.paintFlags & LinearTextFlag) == 0) {
- letterSpace = round(letterSpace);
- letterSpaceHalfLeft = floor(letterSpace * 0.5);
- } else {
- letterSpaceHalfLeft = letterSpace * 0.5;
- }
- letterSpaceHalfRight = letterSpace - letterSpaceHalfLeft;
- }
-
- hb_buffer_clear_contents(buffer.get());
- hb_buffer_set_script(buffer.get(), script);
- hb_buffer_set_direction(buffer.get(), isRtl ? HB_DIRECTION_RTL : HB_DIRECTION_LTR);
- const LocaleList& localeList = LocaleListCache::getById(paint.localeListId);
- if (localeList.size() != 0) {
- hb_language_t hbLanguage = localeList.getHbLanguage(0);
- for (size_t i = 0; i < localeList.size(); ++i) {
- if (localeList[i].supportsHbScript(script)) {
- hbLanguage = localeList.getHbLanguage(i);
- break;
- }
- }
- hb_buffer_set_language(buffer.get(), hbLanguage);
- }
-
- const uint32_t clusterStart =
- addToHbBuffer(buffer, buf, start, count, bufSize, scriptRunStart, scriptRunEnd,
- startHyphen, endHyphen, hbFont);
-
- hb_shape(hbFont.get(), buffer.get(), features.empty() ? NULL : &features[0],
- features.size());
- unsigned int numGlyphs;
- hb_glyph_info_t* info = hb_buffer_get_glyph_infos(buffer.get(), &numGlyphs);
- hb_glyph_position_t* positions = hb_buffer_get_glyph_positions(buffer.get(), NULL);
-
- // At this point in the code, the cluster values in the info buffer correspond to the
- // input characters with some shift. The cluster value clusterStart corresponds to the
- // first character passed to HarfBuzz, which is at buf[start + scriptRunStart] whose
- // advance needs to be saved into mAdvances[scriptRunStart]. So cluster values need to
- // be reduced by (clusterStart - scriptRunStart) to get converted to indices of
- // mAdvances.
- const ssize_t clusterOffset = clusterStart - scriptRunStart;
-
- if (numGlyphs) {
- mAdvances[info[0].cluster - clusterOffset] += letterSpaceHalfLeft;
- x += letterSpaceHalfLeft;
- }
- for (unsigned int i = 0; i < numGlyphs; i++) {
- const size_t clusterBaseIndex = info[i].cluster - clusterOffset;
- if (i > 0 && info[i - 1].cluster != info[i].cluster) {
- mAdvances[info[i - 1].cluster - clusterOffset] += letterSpaceHalfRight;
- mAdvances[clusterBaseIndex] += letterSpaceHalfLeft;
- x += letterSpace;
- }
-
- hb_codepoint_t glyph_ix = info[i].codepoint;
- float xoff = HBFixedToFloat(positions[i].x_offset);
- float yoff = -HBFixedToFloat(positions[i].y_offset);
- xoff += yoff * paint.skewX;
- LayoutGlyph glyph = {font_ix, glyph_ix, x + xoff, y + yoff};
- mGlyphs.push_back(glyph);
- float xAdvance = HBFixedToFloat(positions[i].x_advance);
- if ((paint.paintFlags & LinearTextFlag) == 0) {
- xAdvance = roundf(xAdvance);
- }
- MinikinRect glyphBounds;
- hb_glyph_extents_t extents = {};
- if (is_color_bitmap_font &&
- hb_font_get_glyph_extents(hbFont.get(), glyph_ix, &extents)) {
- // Note that it is technically possible for a TrueType font to have outline and
- // embedded bitmap at the same time. We ignore modified bbox of hinted outline
- // glyphs in that case.
- glyphBounds.mLeft = roundf(HBFixedToFloat(extents.x_bearing));
- glyphBounds.mTop = roundf(HBFixedToFloat(-extents.y_bearing));
- glyphBounds.mRight = roundf(HBFixedToFloat(extents.x_bearing + extents.width));
- glyphBounds.mBottom =
- roundf(HBFixedToFloat(-extents.y_bearing - extents.height));
- } else {
- fakedFont.font->typeface()->GetBounds(&glyphBounds, glyph_ix, paint,
- fakedFont.fakery);
- }
- glyphBounds.offset(xoff, yoff);
-
- if (clusterBaseIndex < count) {
- mAdvances[clusterBaseIndex] += xAdvance;
- } else {
- ALOGE("cluster %zu (start %zu) out of bounds of count %zu", clusterBaseIndex,
- start, count);
- }
- glyphBounds.offset(x, y);
- mBounds.join(glyphBounds);
- x += xAdvance;
- }
- if (numGlyphs) {
- mAdvances[info[numGlyphs - 1].cluster - clusterOffset] += letterSpaceHalfRight;
- x += letterSpaceHalfRight;
- }
- }
- }
- mAdvance = x;
-}
-
-void Layout::appendLayout(const Layout& src, size_t start, float extraAdvance) {
- int fontMapStack[16];
- int* fontMap;
- if (src.mFaces.size() < sizeof(fontMapStack) / sizeof(fontMapStack[0])) {
- fontMap = fontMapStack;
- } else {
- fontMap = new int[src.mFaces.size()];
- }
- for (size_t i = 0; i < src.mFaces.size(); i++) {
- uint8_t font_ix = findOrPushBackFace(src.mFaces[i]);
- fontMap[i] = font_ix;
- }
- int x0 = mAdvance;
- for (size_t i = 0; i < src.mGlyphs.size(); i++) {
- const LayoutGlyph& srcGlyph = src.mGlyphs[i];
- int font_ix = fontMap[srcGlyph.font_ix];
- unsigned int glyph_id = srcGlyph.glyph_id;
- float x = x0 + srcGlyph.x;
- float y = srcGlyph.y;
- LayoutGlyph glyph = {font_ix, glyph_id, x, y};
- mGlyphs.push_back(glyph);
- }
- for (size_t i = 0; i < src.mAdvances.size(); i++) {
- mAdvances[i + start] = src.mAdvances[i];
+ const std::vector<float>& advances = src.advances();
+ for (size_t i = 0; i < advances.size(); i++) {
+ mAdvances[i + start] = advances[i];
if (i == 0) {
mAdvances[start] += extraAdvance;
}
- mExtents[i + start] = src.mExtents[i];
}
- MinikinRect srcBounds(src.mBounds);
- srcBounds.offset(x0, 0);
+ MinikinRect srcBounds(src.bounds());
+ srcBounds.offset(mAdvance, 0);
mBounds.join(srcBounds);
- mAdvance += src.mAdvance + extraAdvance;
-
- if (fontMap != fontMapStack) {
- delete[] fontMap;
- }
-}
-
-size_t Layout::nGlyphs() const {
- return mGlyphs.size();
-}
-
-const MinikinFont* Layout::getFont(int i) const {
- const LayoutGlyph& glyph = mGlyphs[i];
- return mFaces[glyph.font_ix].font->typeface().get();
-}
-
-FontFakery Layout::getFakery(int i) const {
- const LayoutGlyph& glyph = mGlyphs[i];
- return mFaces[glyph.font_ix].fakery;
-}
-
-unsigned int Layout::getGlyphId(int i) const {
- const LayoutGlyph& glyph = mGlyphs[i];
- return glyph.glyph_id;
-}
-
-float Layout::getX(int i) const {
- const LayoutGlyph& glyph = mGlyphs[i];
- return glyph.x;
-}
-
-float Layout::getY(int i) const {
- const LayoutGlyph& glyph = mGlyphs[i];
- return glyph.y;
-}
-
-float Layout::getAdvance() const {
- return mAdvance;
-}
-
-void Layout::getAdvances(float* advances) const {
- memcpy(advances, &mAdvances[0], mAdvances.size() * sizeof(float));
-}
-
-void Layout::getExtents(MinikinExtent* extents) const {
- memcpy(extents, &mExtents[0], mExtents.size() * sizeof(MinikinExtent));
-}
-
-void Layout::getBounds(MinikinRect* bounds) const {
- bounds->set(mBounds);
+ mAdvance += src.advance() + extraAdvance;
}
void Layout::purgeCaches() {
diff --git a/libs/minikin/LayoutCore.cpp b/libs/minikin/LayoutCore.cpp
new file mode 100644
index 0000000..ddf31b6
--- /dev/null
+++ b/libs/minikin/LayoutCore.cpp
@@ -0,0 +1,555 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "Minikin"
+
+#include "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>
+#include <unicode/ubidi.h>
+#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 "BidiUtils.h"
+#include "LayoutUtils.h"
+#include "LocaleListCache.h"
+#include "MinikinInternal.h"
+
+namespace minikin {
+
+namespace {
+
+struct SkiaArguments {
+ const MinikinFont* font;
+ const MinikinPaint* paint;
+ FontFakery fakery;
+};
+
+// Returns true if the character needs to be excluded for the line spacing.
+inline bool isLineSpaceExcludeChar(uint16_t c) {
+ return c == CHAR_LINE_FEED || c == CHAR_CARRIAGE_RETURN;
+}
+
+static hb_position_t harfbuzzGetGlyphHorizontalAdvance(hb_font_t* /* hbFont */, void* fontData,
+ hb_codepoint_t glyph, void* /* userData */) {
+ SkiaArguments* args = reinterpret_cast<SkiaArguments*>(fontData);
+ float advance = args->font->GetHorizontalAdvance(glyph, *args->paint, args->fakery);
+ return 256 * advance + 0.5;
+}
+
+static void harfbuzzGetGlyphHorizontalAdvances(hb_font_t* /* hbFont */, void* fontData,
+ unsigned int count,
+ const hb_codepoint_t* first_glyph,
+ unsigned glyph_stride, hb_position_t* first_advance,
+ unsigned advance_stride, void* /* userData */) {
+ SkiaArguments* args = reinterpret_cast<SkiaArguments*>(fontData);
+ std::vector<uint16_t> glyphVec(count);
+ std::vector<float> advVec(count);
+
+ const hb_codepoint_t* glyph = first_glyph;
+ for (uint32_t i = 0; i < count; ++i) {
+ glyphVec[i] = *glyph;
+ glyph = reinterpret_cast<const hb_codepoint_t*>(reinterpret_cast<const uint8_t*>(glyph) +
+ glyph_stride);
+ }
+
+ args->font->GetHorizontalAdvances(glyphVec.data(), count, *args->paint, args->fakery,
+ advVec.data());
+
+ hb_position_t* advances = first_advance;
+ for (uint32_t i = 0; i < count; ++i) {
+ *advances = HBFloatToFixed(advVec[i]);
+ advances = reinterpret_cast<hb_position_t*>(reinterpret_cast<uint8_t*>(advances) +
+ advance_stride);
+ }
+}
+
+static hb_bool_t harfbuzzGetGlyphHorizontalOrigin(hb_font_t* /* hbFont */, void* /* fontData */,
+ hb_codepoint_t /* glyph */,
+ hb_position_t* /* x */, hb_position_t* /* y */,
+ void* /* userData */) {
+ // Just return true, following the way that Harfbuzz-FreeType implementation does.
+ return true;
+}
+
+hb_font_funcs_t* getFontFuncs() {
+ static hb_font_funcs_t* fontFuncs = nullptr;
+ static std::once_flag once;
+ std::call_once(once, [&]() {
+ fontFuncs = hb_font_funcs_create();
+ // Override the h_advance function since we can't use HarfBuzz's implemenation. It may
+ // return the wrong value if the font uses hinting aggressively.
+ hb_font_funcs_set_glyph_h_advance_func(fontFuncs, harfbuzzGetGlyphHorizontalAdvance, 0, 0);
+ hb_font_funcs_set_glyph_h_advances_func(fontFuncs, harfbuzzGetGlyphHorizontalAdvances, 0,
+ 0);
+ hb_font_funcs_set_glyph_h_origin_func(fontFuncs, harfbuzzGetGlyphHorizontalOrigin, 0, 0);
+ hb_font_funcs_make_immutable(fontFuncs);
+ });
+ return fontFuncs;
+}
+
+hb_font_funcs_t* getFontFuncsForEmoji() {
+ static hb_font_funcs_t* fontFuncs = nullptr;
+ static std::once_flag once;
+ std::call_once(once, [&]() {
+ fontFuncs = hb_font_funcs_create();
+ // Don't override the h_advance function since we use HarfBuzz's implementation for emoji
+ // for performance reasons.
+ // Note that it is technically possible for a TrueType font to have outline and embedded
+ // bitmap at the same time. We ignore modified advances of hinted outline glyphs in that
+ // case.
+ hb_font_funcs_set_glyph_h_origin_func(fontFuncs, harfbuzzGetGlyphHorizontalOrigin, 0, 0);
+ hb_font_funcs_make_immutable(fontFuncs);
+ });
+ return fontFuncs;
+}
+
+static bool isColorBitmapFont(const HbFontUniquePtr& font) {
+ HbBlob cbdt(font, HB_TAG('C', 'B', 'D', 'T'));
+ return cbdt;
+}
+
+static hb_codepoint_t decodeUtf16(const uint16_t* chars, size_t len, ssize_t* iter) {
+ UChar32 result;
+ U16_NEXT(chars, *iter, (ssize_t)len, result);
+ if (U_IS_SURROGATE(result)) { // isolated surrogate
+ result = 0xFFFDu; // U+FFFD REPLACEMENT CHARACTER
+ }
+ return (hb_codepoint_t)result;
+}
+
+static hb_script_t getScriptRun(const uint16_t* chars, size_t len, ssize_t* iter) {
+ if (size_t(*iter) == len) {
+ return HB_SCRIPT_UNKNOWN;
+ }
+ uint32_t cp = decodeUtf16(chars, len, iter);
+ hb_unicode_funcs_t* unicode_func = hb_unicode_funcs_get_default();
+ hb_script_t current_script = hb_unicode_script(unicode_func, cp);
+ for (;;) {
+ if (size_t(*iter) == len) break;
+ const ssize_t prev_iter = *iter;
+ cp = decodeUtf16(chars, len, iter);
+ const hb_script_t script = hb_unicode_script(unicode_func, cp);
+ if (script != current_script) {
+ if (current_script == HB_SCRIPT_INHERITED || current_script == HB_SCRIPT_COMMON) {
+ current_script = script;
+ } else if (script == HB_SCRIPT_INHERITED || script == HB_SCRIPT_COMMON) {
+ continue;
+ } else {
+ *iter = prev_iter;
+ break;
+ }
+ }
+ }
+ if (current_script == HB_SCRIPT_INHERITED) {
+ current_script = HB_SCRIPT_COMMON;
+ }
+
+ return current_script;
+}
+
+/**
+ * Disable certain scripts (mostly those with cursive connection) from having letterspacing
+ * applied. See https://github.com/behdad/harfbuzz/issues/64 for more details.
+ */
+static bool isScriptOkForLetterspacing(hb_script_t script) {
+ return !(script == HB_SCRIPT_ARABIC || script == HB_SCRIPT_NKO ||
+ script == HB_SCRIPT_PSALTER_PAHLAVI || script == HB_SCRIPT_MANDAIC ||
+ script == HB_SCRIPT_MONGOLIAN || script == HB_SCRIPT_PHAGS_PA ||
+ script == HB_SCRIPT_DEVANAGARI || script == HB_SCRIPT_BENGALI ||
+ script == HB_SCRIPT_GURMUKHI || script == HB_SCRIPT_MODI ||
+ script == HB_SCRIPT_SHARADA || script == HB_SCRIPT_SYLOTI_NAGRI ||
+ script == HB_SCRIPT_TIRHUTA || script == HB_SCRIPT_OGHAM);
+}
+
+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 */
+ || preferredHyphen == 0x05BE /* HEBREW PUNCTUATION MAQAF */
+ || preferredHyphen == 0x1400 /* CANADIAN SYLLABIC HYPHEN */) {
+ if (hb_font_get_nominal_glyph(font, preferredHyphen, &glyph)) {
+ return preferredHyphen;
+ } else {
+ // The original hyphen requested was not supported. Let's try and see if the
+ // Unicode hyphen is supported.
+ preferredHyphen = CHAR_HYPHEN;
+ }
+ }
+ if (preferredHyphen == CHAR_HYPHEN) { /* HYPHEN */
+ // Fallback to ASCII HYPHEN-MINUS if the font didn't have a glyph for the preferred hyphen.
+ // Note that we intentionally don't do anything special if the font doesn't have a
+ // HYPHEN-MINUS either, so a tofu could be shown, hinting towards something missing.
+ if (!hb_font_get_nominal_glyph(font, preferredHyphen, &glyph)) {
+ return 0x002D; // HYPHEN-MINUS
+ }
+ }
+ return 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);
+ for (size_t i = 0; i < size; i++) {
+ hb_buffer_add(buffer.get(), determineHyphenChar(chars[i], font.get()), cluster);
+ }
+}
+
+// Returns the cluster value assigned to the first codepoint added to the buffer, which can be used
+// to translate cluster values returned by HarfBuzz to input indices.
+static inline uint32_t addToHbBuffer(const HbBufferUniquePtr& buffer, const uint16_t* buf,
+ size_t start, size_t count, size_t bufSize,
+ ssize_t scriptRunStart, ssize_t scriptRunEnd,
+ StartHyphenEdit inStartHyphen, EndHyphenEdit inEndHyphen,
+ const HbFontUniquePtr& hbFont) {
+ // Only hyphenate the very first script run for starting hyphens.
+ const StartHyphenEdit startHyphen =
+ (scriptRunStart == 0) ? inStartHyphen : StartHyphenEdit::NO_EDIT;
+ // Only hyphenate the very last script run for ending hyphens.
+ const EndHyphenEdit endHyphen =
+ (static_cast<size_t>(scriptRunEnd) == count) ? inEndHyphen : EndHyphenEdit::NO_EDIT;
+
+ // In the following code, we drop the pre-context and/or post-context if there is a
+ // hyphen edit at that end. This is not absolutely necessary, since HarfBuzz uses
+ // contexts only for joining scripts at the moment, e.g. to determine if the first or
+ // last letter of a text range to shape should take a joining form based on an
+ // adjacent letter or joiner (that comes from the context).
+ //
+ // TODO: Revisit this for:
+ // 1. Desperate breaks for joining scripts like Arabic (where it may be better to keep
+ // the context);
+ // 2. Special features like start-of-word font features (not implemented in HarfBuzz
+ // yet).
+
+ // We don't have any start-of-line replacement edit yet, so we don't need to check for
+ // those.
+ if (isInsertion(startHyphen)) {
+ // A cluster value of zero guarantees that the inserted hyphen will be in the same
+ // cluster with the next codepoint, since there is no pre-context.
+ addHyphenToHbBuffer(buffer, hbFont, startHyphen, 0 /* cluster */);
+ }
+
+ const uint16_t* hbText;
+ int hbTextLength;
+ unsigned int hbItemOffset;
+ unsigned int hbItemLength = scriptRunEnd - scriptRunStart; // This is >= 1.
+
+ const bool hasEndInsertion = isInsertion(endHyphen);
+ const bool hasEndReplacement = isReplacement(endHyphen);
+ if (hasEndReplacement) {
+ // Skip the last code unit while copying the buffer for HarfBuzz if it's a replacement. We
+ // don't need to worry about non-BMP characters yet since replacements are only done for
+ // code units at the moment.
+ hbItemLength -= 1;
+ }
+
+ if (startHyphen == StartHyphenEdit::NO_EDIT) {
+ // No edit at the beginning. Use the whole pre-context.
+ hbText = buf;
+ hbItemOffset = start + scriptRunStart;
+ } else {
+ // There's an edit at the beginning. Drop the pre-context and start the buffer at where we
+ // want to start shaping.
+ hbText = buf + start + scriptRunStart;
+ hbItemOffset = 0;
+ }
+
+ if (endHyphen == EndHyphenEdit::NO_EDIT) {
+ // No edit at the end, use the whole post-context.
+ hbTextLength = (buf + bufSize) - hbText;
+ } else {
+ // There is an edit at the end. Drop the post-context.
+ hbTextLength = hbItemOffset + hbItemLength;
+ }
+
+ hb_buffer_add_utf16(buffer.get(), hbText, hbTextLength, hbItemOffset, hbItemLength);
+
+ unsigned int numCodepoints;
+ hb_glyph_info_t* cpInfo = hb_buffer_get_glyph_infos(buffer.get(), &numCodepoints);
+
+ // Add the hyphen at the end, if there's any.
+ if (hasEndInsertion || hasEndReplacement) {
+ // When a hyphen is inserted, by assigning the added hyphen and the last
+ // codepoint added to the HarfBuzz buffer to the same cluster, we can make sure
+ // that they always remain in the same cluster, even if the last codepoint gets
+ // merged into another cluster (for example when it's a combining mark).
+ //
+ // When a replacement happens instead, we want it to get the cluster value of
+ // the character it's replacing, which is one "codepoint length" larger than
+ // the last cluster. But since the character replaced is always just one
+ // code unit, we can just add 1.
+ uint32_t hyphenCluster;
+ if (numCodepoints == 0) {
+ // Nothing was added to the HarfBuzz buffer. This can only happen if
+ // we have a replacement that is replacing a one-code unit script run.
+ hyphenCluster = 0;
+ } else {
+ hyphenCluster = cpInfo[numCodepoints - 1].cluster + (uint32_t)hasEndReplacement;
+ }
+ addHyphenToHbBuffer(buffer, hbFont, endHyphen, hyphenCluster);
+ // Since we have just added to the buffer, cpInfo no longer necessarily points to
+ // the right place. Refresh it.
+ cpInfo = hb_buffer_get_glyph_infos(buffer.get(), nullptr /* we don't need the size */);
+ }
+ return cpInfo[0].cluster;
+}
+
+} // namespace
+
+LayoutPiece::LayoutPiece(const U16StringPiece& textBuf, const Range& range, bool isRtl,
+ const MinikinPaint& paint, StartHyphenEdit startHyphen,
+ EndHyphenEdit endHyphen) {
+ const uint16_t* buf = textBuf.data();
+ const size_t start = range.getStart();
+ const size_t count = range.getLength();
+ const size_t bufSize = textBuf.size();
+
+ mAdvances.resize(count, 0); // Need zero filling.
+
+ // Usually the number of glyphs are less than number of code units.
+ mFontIndices.reserve(count);
+ mGlyphIds.reserve(count);
+ mPoints.reserve(count);
+
+ HbBufferUniquePtr buffer(hb_buffer_create());
+ std::vector<FontCollection::Run> items = paint.font->itemize(
+ textBuf.substr(range), 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<HbFontUniquePtr> hbFonts;
+ double size = paint.size;
+ double scaleX = paint.scaleX;
+
+ std::unordered_map<const Font*, uint32_t> fontMap;
+
+ float x = 0;
+ float y = 0;
+ for (int run_ix = isRtl ? items.size() - 1 : 0;
+ isRtl ? run_ix >= 0 : run_ix < static_cast<int>(items.size());
+ isRtl ? --run_ix : ++run_ix) {
+ FontCollection::Run& run = items[run_ix];
+ const FakedFont& fakedFont = run.fakedFont;
+ auto it = fontMap.find(fakedFont.font);
+ uint8_t font_ix;
+ if (it == fontMap.end()) {
+ // First time to see this font.
+ font_ix = mFonts.size();
+ mFonts.push_back(fakedFont);
+ fontMap.insert(std::make_pair(fakedFont.font, font_ix));
+
+ // We override some functions which are not thread safe.
+ HbFontUniquePtr font(hb_font_create_sub_font(fakedFont.font->baseFont().get()));
+ hb_font_set_funcs(
+ font.get(), isColorBitmapFont(font) ? getFontFuncsForEmoji() : getFontFuncs(),
+ new SkiaArguments({fakedFont.font->typeface().get(), &paint, fakedFont.fakery}),
+ [](void* data) { delete reinterpret_cast<SkiaArguments*>(data); });
+ hbFonts.push_back(std::move(font));
+ } else {
+ font_ix = it->second;
+ }
+ const HbFontUniquePtr& hbFont = hbFonts[font_ix];
+
+ bool needExtent = false;
+ for (int i = run.start; i < run.end; ++i) {
+ if (!isLineSpaceExcludeChar(buf[i])) {
+ needExtent = true;
+ break;
+ }
+ }
+ if (needExtent) {
+ MinikinExtent verticalExtent;
+ fakedFont.font->typeface()->GetFontExtent(&verticalExtent, paint, fakedFont.fakery);
+ mExtent.extendBy(verticalExtent);
+ }
+
+ hb_font_set_ppem(hbFont.get(), size * scaleX, size);
+ hb_font_set_scale(hbFont.get(), HBFloatToFixed(size * scaleX), HBFloatToFixed(size));
+
+ const bool is_color_bitmap_font = isColorBitmapFont(hbFont);
+
+ // TODO: if there are multiple scripts within a font in an RTL run,
+ // we need to reorder those runs. This is unlikely with our current
+ // font stack, but should be done for correctness.
+
+ // Note: scriptRunStart and scriptRunEnd, as well as run.start and run.end, run between 0
+ // and count.
+ ssize_t scriptRunEnd;
+ for (ssize_t scriptRunStart = run.start; scriptRunStart < run.end;
+ scriptRunStart = scriptRunEnd) {
+ scriptRunEnd = scriptRunStart;
+ hb_script_t script = getScriptRun(buf + start, run.end, &scriptRunEnd /* iterator */);
+ // After the last line, scriptRunEnd is guaranteed to have increased, since the only
+ // time getScriptRun does not increase its iterator is when it has already reached the
+ // end of the buffer. But that can't happen, since if we have already reached the end
+ // of the buffer, we should have had (scriptRunEnd == run.end), which means
+ // (scriptRunStart == run.end) which is impossible due to the exit condition of the for
+ // loop. So we can be sure that scriptRunEnd > scriptRunStart.
+
+ double letterSpace = 0.0;
+ double letterSpaceHalfLeft = 0.0;
+ double letterSpaceHalfRight = 0.0;
+
+ if (paint.letterSpacing != 0.0 && isScriptOkForLetterspacing(script)) {
+ letterSpace = paint.letterSpacing * size * scaleX;
+ if ((paint.fontFlags & LinearMetrics_Flag) == 0) {
+ letterSpace = round(letterSpace);
+ letterSpaceHalfLeft = floor(letterSpace * 0.5);
+ } else {
+ letterSpaceHalfLeft = letterSpace * 0.5;
+ }
+ letterSpaceHalfRight = letterSpace - letterSpaceHalfLeft;
+ }
+
+ hb_buffer_clear_contents(buffer.get());
+ hb_buffer_set_script(buffer.get(), script);
+ hb_buffer_set_direction(buffer.get(), isRtl ? HB_DIRECTION_RTL : HB_DIRECTION_LTR);
+ const LocaleList& localeList = LocaleListCache::getById(paint.localeListId);
+ if (localeList.size() != 0) {
+ hb_language_t hbLanguage = localeList.getHbLanguage(0);
+ for (size_t i = 0; i < localeList.size(); ++i) {
+ if (localeList[i].supportsHbScript(script)) {
+ hbLanguage = localeList.getHbLanguage(i);
+ break;
+ }
+ }
+ hb_buffer_set_language(buffer.get(), hbLanguage);
+ }
+
+ const uint32_t clusterStart =
+ addToHbBuffer(buffer, buf, start, count, bufSize, scriptRunStart, scriptRunEnd,
+ startHyphen, endHyphen, hbFont);
+
+ hb_shape(hbFont.get(), buffer.get(), features.empty() ? NULL : &features[0],
+ features.size());
+ unsigned int numGlyphs;
+ hb_glyph_info_t* info = hb_buffer_get_glyph_infos(buffer.get(), &numGlyphs);
+ hb_glyph_position_t* positions = hb_buffer_get_glyph_positions(buffer.get(), NULL);
+
+ // At this point in the code, the cluster values in the info buffer correspond to the
+ // input characters with some shift. The cluster value clusterStart corresponds to the
+ // first character passed to HarfBuzz, which is at buf[start + scriptRunStart] whose
+ // advance needs to be saved into mAdvances[scriptRunStart]. So cluster values need to
+ // be reduced by (clusterStart - scriptRunStart) to get converted to indices of
+ // mAdvances.
+ const ssize_t clusterOffset = clusterStart - scriptRunStart;
+
+ if (numGlyphs) {
+ mAdvances[info[0].cluster - clusterOffset] += letterSpaceHalfLeft;
+ x += letterSpaceHalfLeft;
+ }
+ for (unsigned int i = 0; i < numGlyphs; i++) {
+ const size_t clusterBaseIndex = info[i].cluster - clusterOffset;
+ if (i > 0 && info[i - 1].cluster != info[i].cluster) {
+ mAdvances[info[i - 1].cluster - clusterOffset] += letterSpaceHalfRight;
+ mAdvances[clusterBaseIndex] += letterSpaceHalfLeft;
+ x += letterSpace;
+ }
+
+ hb_codepoint_t glyph_ix = info[i].codepoint;
+ float xoff = HBFixedToFloat(positions[i].x_offset);
+ float yoff = -HBFixedToFloat(positions[i].y_offset);
+ xoff += yoff * paint.skewX;
+ mFontIndices.push_back(font_ix);
+ mGlyphIds.push_back(glyph_ix);
+ mPoints.emplace_back(x + xoff, y + yoff);
+ float xAdvance = HBFixedToFloat(positions[i].x_advance);
+ if ((paint.fontFlags & LinearMetrics_Flag) == 0) {
+ xAdvance = roundf(xAdvance);
+ }
+ MinikinRect glyphBounds;
+ hb_glyph_extents_t extents = {};
+ if (is_color_bitmap_font &&
+ hb_font_get_glyph_extents(hbFont.get(), glyph_ix, &extents)) {
+ // Note that it is technically possible for a TrueType font to have outline and
+ // embedded bitmap at the same time. We ignore modified bbox of hinted outline
+ // glyphs in that case.
+ glyphBounds.mLeft = roundf(HBFixedToFloat(extents.x_bearing));
+ glyphBounds.mTop = roundf(HBFixedToFloat(-extents.y_bearing));
+ glyphBounds.mRight = roundf(HBFixedToFloat(extents.x_bearing + extents.width));
+ glyphBounds.mBottom =
+ roundf(HBFixedToFloat(-extents.y_bearing - extents.height));
+ } else {
+ fakedFont.font->typeface()->GetBounds(&glyphBounds, glyph_ix, paint,
+ fakedFont.fakery);
+ }
+ glyphBounds.offset(xoff, yoff);
+
+ if (clusterBaseIndex < count) {
+ mAdvances[clusterBaseIndex] += xAdvance;
+ } else {
+ ALOGE("cluster %zu (start %zu) out of bounds of count %zu", clusterBaseIndex,
+ start, count);
+ }
+ glyphBounds.offset(x, y);
+ mBounds.join(glyphBounds);
+ x += xAdvance;
+ }
+ if (numGlyphs) {
+ mAdvances[info[numGlyphs - 1].cluster - clusterOffset] += letterSpaceHalfRight;
+ x += letterSpaceHalfRight;
+ }
+ }
+ }
+ mFontIndices.shrink_to_fit();
+ mGlyphIds.shrink_to_fit();
+ mPoints.shrink_to_fit();
+ mAdvance = x;
+}
+
+} // namespace minikin
diff --git a/libs/minikin/LayoutSplitter.h b/libs/minikin/LayoutSplitter.h
new file mode 100644
index 0000000..319a702
--- /dev/null
+++ b/libs/minikin/LayoutSplitter.h
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MINIKIN_LAYOUT_SPLITTER_H
+#define MINIKIN_LAYOUT_SPLITTER_H
+
+#define LOG_TAG "Minikin"
+
+#include "minikin/Layout.h"
+
+#include <memory>
+
+#include <unicode/ubidi.h>
+
+#include "minikin/Macros.h"
+#include "minikin/U16StringPiece.h"
+
+#include "LayoutUtils.h"
+
+namespace minikin {
+
+// LayoutSplitter split the input text into recycle-able pieces.
+//
+// LayoutSplitter basically splits the text before and after space characters.
+//
+// Here is an example of how the LayoutSplitter split the text into layout pieces.
+// Input:
+// Text : T h i s _ i s _ a n _ e x a m p l e _ t e x t .
+// Range : |-------------------|
+//
+// Output:
+// Context Range : |---|-|---|-|-------------|
+// Piece Range : |-|-|---|-|---------|
+//
+// Input:
+// Text : T h i s _ i s _ a n _ e x a m p l e _ t e x t .
+// Range : |-------|
+//
+// Output:
+// Context Range : |-------------|
+// Piece Range : |-------|
+class LayoutSplitter {
+public:
+ LayoutSplitter(const U16StringPiece& textBuf, const Range& range, bool isRtl)
+ : mTextBuf(textBuf), mRange(range), mIsRtl(isRtl) {}
+
+ class iterator {
+ public:
+ bool operator==(const iterator& o) const { return mPos == o.mPos && mParent == o.mParent; }
+
+ bool operator!=(const iterator& o) const { return !(*this == o); }
+
+ std::pair<Range, Range> operator*() const {
+ return std::make_pair(mContextRange, mPieceRange);
+ }
+
+ iterator& operator++() {
+ const U16StringPiece& textBuf = mParent->mTextBuf;
+ const Range& range = mParent->mRange;
+ if (mParent->mIsRtl) {
+ mPos = mPieceRange.getStart();
+ mContextRange.setStart(getPrevWordBreakForCache(textBuf, mPos));
+ mContextRange.setEnd(mPos);
+ mPieceRange.setStart(std::max(mContextRange.getStart(), range.getStart()));
+ mPieceRange.setEnd(mPos);
+ } else {
+ mPos = mPieceRange.getEnd();
+ mContextRange.setStart(mPos);
+ mContextRange.setEnd(getNextWordBreakForCache(textBuf, mPos));
+ mPieceRange.setStart(mPos);
+ mPieceRange.setEnd(std::min(mContextRange.getEnd(), range.getEnd()));
+ }
+ return *this;
+ }
+
+ private:
+ friend class LayoutSplitter;
+
+ iterator(const LayoutSplitter* parent, uint32_t pos) : mParent(parent), mPos(pos) {
+ const U16StringPiece& textBuf = mParent->mTextBuf;
+ const Range& range = mParent->mRange;
+ if (parent->mIsRtl) {
+ mContextRange.setStart(getPrevWordBreakForCache(textBuf, pos));
+ mContextRange.setEnd(getNextWordBreakForCache(textBuf, pos == 0 ? 0 : pos - 1));
+ mPieceRange.setStart(std::max(mContextRange.getStart(), range.getStart()));
+ mPieceRange.setEnd(pos);
+ } else {
+ mContextRange.setStart(
+ getPrevWordBreakForCache(textBuf, pos == range.getEnd() ? pos : pos + 1));
+ mContextRange.setEnd(getNextWordBreakForCache(textBuf, pos));
+ mPieceRange.setStart(pos);
+ mPieceRange.setEnd(std::min(mContextRange.getEnd(), range.getEnd()));
+ }
+ }
+
+ const LayoutSplitter* mParent;
+ uint32_t mPos;
+ Range mContextRange;
+ Range mPieceRange;
+ };
+
+ iterator begin() const { return iterator(this, mIsRtl ? mRange.getEnd() : mRange.getStart()); }
+ iterator end() const { return iterator(this, mIsRtl ? mRange.getStart() : mRange.getEnd()); }
+
+private:
+ U16StringPiece mTextBuf;
+ Range mRange; // The range in the original buffer. Used for range check.
+ bool mIsRtl; // The paragraph direction.
+
+ MINIKIN_PREVENT_COPY_AND_ASSIGN(LayoutSplitter);
+};
+
+} // namespace minikin
+
+#endif // MINIKIN_LAYOUT_SPLITTER_H
diff --git a/libs/minikin/LayoutUtils.cpp b/libs/minikin/LayoutUtils.cpp
index e79ea8c..3c258cf 100644
--- a/libs/minikin/LayoutUtils.cpp
+++ b/libs/minikin/LayoutUtils.cpp
@@ -48,14 +48,14 @@
/**
* Return offset of previous word break. It is either < offset or == 0.
*/
-size_t getPrevWordBreakForCache(const uint16_t* chars, size_t offset, size_t len) {
+uint32_t getPrevWordBreakForCache(const U16StringPiece& textBuf, uint32_t offset) {
if (offset == 0) return 0;
- if (offset > len) offset = len;
- if (isWordBreakBefore(chars[offset - 1])) {
+ if (offset > textBuf.size()) offset = textBuf.size();
+ if (isWordBreakBefore(textBuf[offset - 1])) {
return offset - 1;
}
- for (size_t i = offset - 1; i > 0; i--) {
- if (isWordBreakBefore(chars[i]) || isWordBreakAfter(chars[i - 1])) {
+ for (uint32_t i = offset - 1; i > 0; i--) {
+ if (isWordBreakBefore(textBuf[i]) || isWordBreakAfter(textBuf[i - 1])) {
return i;
}
}
@@ -65,20 +65,20 @@
/**
* Return offset of next word break. It is either > offset or == len.
*/
-size_t getNextWordBreakForCache(const uint16_t* chars, size_t offset, size_t len) {
- if (offset >= len) return len;
- if (isWordBreakAfter(chars[offset])) {
+uint32_t getNextWordBreakForCache(const U16StringPiece& textBuf, uint32_t offset) {
+ if (offset >= textBuf.size()) return textBuf.size();
+ if (isWordBreakAfter(textBuf[offset])) {
return offset + 1;
}
- for (size_t i = offset + 1; i < len; i++) {
+ for (uint32_t i = offset + 1; i < textBuf.size(); i++) {
// No need to check isWordBreakAfter(chars[i - 1]) since it is checked
// in previous iteration. Note that isWordBreakBefore returns true
// whenever isWordBreakAfter returns true.
- if (isWordBreakBefore(chars[i])) {
+ if (isWordBreakBefore(textBuf[i])) {
return i;
}
}
- return len;
+ return textBuf.size();
}
} // namespace minikin
diff --git a/libs/minikin/LayoutUtils.h b/libs/minikin/LayoutUtils.h
index 3128148..3c5c4d1 100644
--- a/libs/minikin/LayoutUtils.h
+++ b/libs/minikin/LayoutUtils.h
@@ -19,6 +19,8 @@
#include <cstdint>
+#include "minikin/U16StringPiece.h"
+
namespace minikin {
/*
@@ -33,7 +35,7 @@
* kerning or complex script processing. This is necessarily a
* heuristic, but should be accurate most of the time.
*/
-size_t getPrevWordBreakForCache(const uint16_t* chars, size_t offset, size_t len);
+uint32_t getPrevWordBreakForCache(const U16StringPiece& textBuf, uint32_t offset);
/**
* Return offset of next word break. It is either > offset or == len.
@@ -42,7 +44,7 @@
* kerning or complex script processing. This is necessarily a
* heuristic, but should be accurate most of the time.
*/
-size_t getNextWordBreakForCache(const uint16_t* chars, size_t offset, size_t len);
+uint32_t getNextWordBreakForCache(const U16StringPiece& textBuf, uint32_t offset);
} // namespace minikin
#endif // MINIKIN_LAYOUT_UTILS_H
diff --git a/libs/minikin/LineBreakerUtil.h b/libs/minikin/LineBreakerUtil.h
index 64f4371..eb058cc 100644
--- a/libs/minikin/LineBreakerUtil.h
+++ b/libs/minikin/LineBreakerUtil.h
@@ -55,11 +55,6 @@
|| c == 0x3000;
}
-// Returns true if the character needs to be excluded for the line spacing.
-inline bool isLineSpaceExcludeChar(uint16_t c) {
- return c == CHAR_LINE_FEED || c == CHAR_CARRIAGE_RETURN;
-}
-
inline Locale getEffectiveLocale(uint32_t localeListId) {
const LocaleList& localeList = LocaleListCache::getById(localeListId);
return localeList.empty() ? Locale() : localeList[0];
@@ -89,14 +84,14 @@
auto hyphenPart = contextRange.split(i);
U16StringPiece firstText = textBuf.substr(hyphenPart.first);
U16StringPiece secondText = textBuf.substr(hyphenPart.second);
- const float first = run.measureHyphenPiece(firstText, Range(0, firstText.size()),
- StartHyphenEdit::NO_EDIT /* start hyphen edit */,
- editForThisLine(hyph) /* end hyphen edit */,
- nullptr /* advances */, pieces);
- const float second = run.measureHyphenPiece(secondText, Range(0, secondText.size()),
- editForNextLine(hyph) /* start hyphen edit */,
- EndHyphenEdit::NO_EDIT /* end hyphen edit */,
- nullptr /* advances */, pieces);
+ 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);
}
@@ -153,7 +148,6 @@
// The user of CharProcessor must call updateLocaleIfNecessary with valid locale at least one
// time before feeding characters.
void updateLocaleIfNecessary(const Run& run) {
- // Update locale if necessary.
uint32_t newLocaleListId = run.getLocaleListId();
if (localeListId != newLocaleListId) {
Locale locale = getEffectiveLocale(newLocaleListId);
@@ -164,11 +158,13 @@
}
// Process one character.
- void feedChar(uint32_t idx, uint16_t c, float w) {
+ void feedChar(uint32_t idx, uint16_t c, float w, bool canBreakHere) {
if (idx == nextWordBreak) {
- prevWordBreak = nextWordBreak;
+ if (canBreakHere) {
+ prevWordBreak = nextWordBreak;
+ sumOfCharWidthsAtPrevWordBreak = sumOfCharWidths;
+ }
nextWordBreak = breaker.next();
- sumOfCharWidthsAtPrevWordBreak = sumOfCharWidths;
}
if (isWordSpace(c)) {
rawSpaceCount += 1;
diff --git a/libs/minikin/Locale.cpp b/libs/minikin/Locale.cpp
index 4209413..c1ec389 100644
--- a/libs/minikin/Locale.cpp
+++ b/libs/minikin/Locale.cpp
@@ -34,8 +34,9 @@
return LocaleListCache::getId(locales);
}
-// Check if a language code supports emoji according to its subtag
-static bool isEmojiSubtag(const char* buf, size_t bufLen, const char* subtag, size_t subtagLen) {
+// Check if a language code supports extension such as emoji and line break etc. according to its
+// subtag
+static bool isSubtag(const char* buf, size_t bufLen, const char* subtag, size_t subtagLen) {
if (bufLen < subtagLen) {
return false;
}
@@ -206,7 +207,7 @@
}
}
- mEmojiStyle = resolveEmojiStyle(input.data(), input.length());
+ resolveUnicodeExtension(input.data(), input.length());
finalize:
if (mEmojiStyle == EmojiStyle::EMPTY) {
@@ -214,23 +215,58 @@
}
}
+void Locale::resolveUnicodeExtension(const char* buf, size_t length) {
+ static const char kPrefix[] = "-u-";
+ const char* pos = std::search(buf, buf + length, kPrefix, kPrefix + strlen(kPrefix));
+ if (pos != buf + length) {
+ pos += strlen(kPrefix);
+ const size_t remainingLength = length - (pos - buf);
+ mLBStyle = resolveLineBreakStyle(pos, remainingLength);
+ mEmojiStyle = resolveEmojiStyle(pos, remainingLength);
+ }
+}
+
// static
-EmojiStyle Locale::resolveEmojiStyle(const char* buf, size_t length) {
- // First, lookup emoji subtag.
- // 10 is the length of "-u-em-text", which is the shortest emoji subtag,
- // unnecessary comparison can be avoided if total length is smaller than 10.
- const size_t kMinSubtagLength = 10;
+// 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[] = "-u-em-";
+ 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 (isEmojiSubtag(pos, remainingLength, "emoji", 5)) {
+ 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,
+ // unnecessary comparison can be avoided if total length is smaller than 10.
+ const size_t kMinSubtagLength = 7;
+ if (length >= kMinSubtagLength) {
+ static const char kPrefix[] = "em-";
+ const char* pos = std::search(buf, buf + length, kPrefix, kPrefix + strlen(kPrefix));
+ if (pos != buf + length) { // found
+ pos += strlen(kPrefix);
+ const size_t remainingLength = length - (pos - buf);
+ if (isSubtag(pos, remainingLength, "emoji", 5)) {
return EmojiStyle::EMOJI;
- } else if (isEmojiSubtag(pos, remainingLength, "text", 4)) {
+ } else if (isSubtag(pos, remainingLength, "text", 4)) {
return EmojiStyle::TEXT;
- } else if (isEmojiSubtag(pos, remainingLength, "default", 7)) {
+ } else if (isSubtag(pos, remainingLength, "default", 7)) {
return EmojiStyle::DEFAULT;
}
}
@@ -291,7 +327,7 @@
}
std::string Locale::getString() const {
- char buf[24];
+ char buf[32] = {};
size_t i;
if (mLanguage == NO_LANGUAGE) {
buf[0] = 'u';
@@ -330,6 +366,42 @@
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);
}
diff --git a/libs/minikin/Locale.h b/libs/minikin/Locale.h
index f030f92..8052d6c 100644
--- a/libs/minikin/Locale.h
+++ b/libs/minikin/Locale.h
@@ -63,12 +63,20 @@
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.
struct Locale {
public:
- enum class Variant : uint16_t { // Up to 12 bits
+ enum class Variant : uint16_t {
NO_VARIANT = 0x0000,
GERMAN_1901_ORTHOGRAPHY = 0x0001,
GERMAN_1996_ORTHOGRAPHY = 0x0002,
@@ -81,7 +89,8 @@
mRegion(NO_REGION),
mSubScriptBits(0ul),
mVariant(Variant::NO_VARIANT),
- mEmojiStyle(EmojiStyle::EMPTY) {}
+ mEmojiStyle(EmojiStyle::EMPTY),
+ mLBStyle(LineBreakStyle::EMPTY) {}
// Parse from string
Locale(const StringPiece& buf);
@@ -89,7 +98,7 @@
bool operator==(const Locale other) const {
return !isUnsupported() && isEqualScript(other) && mLanguage == other.mLanguage &&
mRegion == other.mRegion && mVariant == other.mVariant &&
- mEmojiStyle == other.mEmojiStyle;
+ mLBStyle == other.mLBStyle && mEmojiStyle == other.mEmojiStyle;
}
bool operator!=(const Locale other) const { return !(*this == other); }
@@ -98,10 +107,12 @@
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() || hasEmojiStyle();
+ return hasLanguage() || hasScript() || hasRegion() || hasVariant() || hasLBStyle() ||
+ hasEmojiStyle();
}
inline bool isUnsupported() const { return !isSupported(); }
@@ -121,9 +132,18 @@
// 0 = no match, 1 = script match, 2 = script and primary language match.
int calcScoreFor(const LocaleList& supported) const;
+ // Identifier pattern:
+ // |-------|-------|-------|-------|-------|-------|-------|-------|
+ // lllllllllllllll Language Code
+ // ssssssssssssssssssss Script Code
+ // rrrrrrrrrrrrrrr Region Code
+ // ee Emoji Style
+ // bb Line Break Style
+ // XXXXXXXX Free
+ // vv German Variant
uint64_t getIdentifier() const {
return ((uint64_t)mLanguage << 49) | ((uint64_t)mScript << 29) | ((uint64_t)mRegion << 14) |
- ((uint64_t)mEmojiStyle << 12) | (uint64_t)mVariant;
+ ((uint64_t)mEmojiStyle << 12) | ((uint64_t)mLBStyle << 10) | (uint64_t)mVariant;
}
Locale getPartialLocale(SubtagBits bits) const;
@@ -157,9 +177,13 @@
Variant mVariant;
EmojiStyle mEmojiStyle;
+ LineBreakStyle mLBStyle;
+
+ void resolveUnicodeExtension(const char* buf, size_t length);
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 bbc6091..e5c8dd5 100644
--- a/libs/minikin/MeasuredText.cpp
+++ b/libs/minikin/MeasuredText.cpp
@@ -19,30 +19,130 @@
#include "minikin/Layout.h"
+#include "BidiUtils.h"
+#include "LayoutSplitter.h"
#include "LayoutUtils.h"
#include "LineBreakerUtil.h"
namespace minikin {
+// Helper class for composing character advances.
+class AdvancesCompositor {
+public:
+ AdvancesCompositor(std::vector<float>* outAdvances, LayoutPieces* outPieces)
+ : mOutAdvances(outAdvances), mOutPieces(outPieces) {}
+
+ void setNextRange(const Range& range, bool dir) {
+ mRange = range;
+ mDir = dir;
+ }
+
+ void operator()(const LayoutPiece& layoutPiece, const MinikinPaint& paint) {
+ const std::vector<float>& advances = layoutPiece.advances();
+ std::copy(advances.begin(), advances.end(), mOutAdvances->begin() + mRange.getStart());
+
+ if (mOutPieces != nullptr) {
+ mOutPieces->insert(mRange, 0 /* no edit */, layoutPiece, mDir, paint);
+ }
+ }
+
+private:
+ Range mRange;
+ bool mDir;
+ std::vector<float>* mOutAdvances;
+ LayoutPieces* mOutPieces;
+};
+
+void StyleRun::getMetrics(const U16StringPiece& textBuf, std::vector<float>* advances,
+ LayoutPieces* precomputed, LayoutPieces* outPieces) const {
+ AdvancesCompositor compositor(advances, outPieces);
+ const Bidi bidiFlag = mIsRtl ? Bidi::FORCE_RTL : Bidi::FORCE_LTR;
+ const uint32_t paintId =
+ (precomputed == nullptr) ? LayoutPieces::kNoPaintId : precomputed->findPaintId(mPaint);
+ for (const BidiText::RunInfo info : BidiText(textBuf, mRange, bidiFlag)) {
+ for (const auto[context, piece] : LayoutSplitter(textBuf, info.range, info.isRtl)) {
+ compositor.setNextRange(piece, info.isRtl);
+ if (paintId == LayoutPieces::kNoPaintId) {
+ LayoutCache::getInstance().getOrCreate(
+ textBuf.substr(context), piece - context.getStart(), mPaint, info.isRtl,
+ StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, compositor);
+ } else {
+ precomputed->getOrCreate(textBuf, piece, context, mPaint, info.isRtl,
+ StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, paintId,
+ compositor);
+ }
+ }
+ }
+}
+
+// Helper class for composing total amount of advance
+class TotalAdvanceCompositor {
+public:
+ TotalAdvanceCompositor(LayoutPieces* outPieces) : mTotalAdvance(0), mOutPieces(outPieces) {}
+
+ void setNextContext(const Range& range, HyphenEdit edit, bool dir) {
+ mRange = range;
+ mEdit = edit;
+ mDir = dir;
+ }
+
+ void operator()(const LayoutPiece& layoutPiece, const MinikinPaint& paint) {
+ mTotalAdvance += layoutPiece.advance();
+ if (mOutPieces != nullptr) {
+ mOutPieces->insert(mRange, mEdit, layoutPiece, mDir, paint);
+ }
+ }
+
+ float advance() const { return mTotalAdvance; }
+
+private:
+ float mTotalAdvance;
+ Range mRange;
+ HyphenEdit mEdit;
+ bool mDir;
+ LayoutPieces* mOutPieces;
+};
+
+float StyleRun::measureHyphenPiece(const U16StringPiece& textBuf, const Range& range,
+ StartHyphenEdit startHyphen, EndHyphenEdit endHyphen,
+ LayoutPieces* pieces) const {
+ TotalAdvanceCompositor compositor(pieces);
+ const Bidi bidiFlag = mIsRtl ? Bidi::FORCE_RTL : Bidi::FORCE_LTR;
+ for (const BidiText::RunInfo info : BidiText(textBuf, range, bidiFlag)) {
+ for (const auto[context, piece] : LayoutSplitter(textBuf, info.range, info.isRtl)) {
+ const StartHyphenEdit startEdit =
+ piece.getStart() == range.getStart() ? startHyphen : StartHyphenEdit::NO_EDIT;
+ const EndHyphenEdit endEdit =
+ piece.getEnd() == range.getEnd() ? endHyphen : EndHyphenEdit::NO_EDIT;
+
+ compositor.setNextContext(piece, packHyphenEdit(startEdit, endEdit), info.isRtl);
+ LayoutCache::getInstance().getOrCreate(textBuf.substr(context),
+ piece - context.getStart(), mPaint, info.isRtl,
+ startEdit, endEdit, compositor);
+ }
+ }
+ return compositor.advance();
+}
+
void MeasuredText::measure(const U16StringPiece& textBuf, bool computeHyphenation,
- bool computeLayout) {
+ bool computeLayout, MeasuredText* hint) {
if (textBuf.size() == 0) {
return;
}
+
LayoutPieces* piecesOut = computeLayout ? &layoutPieces : nullptr;
CharProcessor proc(textBuf);
for (const auto& run : runs) {
const Range& range = run->getRange();
- const uint32_t runOffset = range.getStart();
- run->getMetrics(textBuf, widths.data() + runOffset, extents.data() + runOffset, piecesOut);
+ run->getMetrics(textBuf, &widths, hint ? &hint->layoutPieces : nullptr, piecesOut);
- if (!computeHyphenation || !run->canHyphenate()) {
+ if (!computeHyphenation || !run->canBreak()) {
continue;
}
proc.updateLocaleIfNecessary(*run);
for (uint32_t i = range.getStart(); i < range.getEnd(); ++i) {
- proc.feedChar(i, textBuf[i], widths[i]);
+ proc.feedChar(i, textBuf[i], widths[i], run->canBreak());
const uint32_t nextCharOffset = i + 1;
if (nextCharOffset != proc.nextWordBreak) {
@@ -55,30 +155,171 @@
}
}
-void MeasuredText::buildLayout(const U16StringPiece& textBuf, const Range& range,
- const MinikinPaint& paint, Bidi bidiFlags,
- StartHyphenEdit startHyphen, EndHyphenEdit endHyphen,
- Layout* layout) {
- layout->doLayoutWithPrecomputedPieces(textBuf, range, bidiFlags, paint, startHyphen, endHyphen,
- layoutPieces);
+// Helper class for composing Layout object.
+class LayoutCompositor {
+public:
+ LayoutCompositor(Layout* outLayout, float extraAdvance)
+ : mOutLayout(outLayout), mExtraAdvance(extraAdvance) {}
+
+ void setOutOffset(uint32_t outOffset) { mOutOffset = outOffset; }
+
+ void operator()(const LayoutPiece& layoutPiece, const MinikinPaint& /* paint */) {
+ mOutLayout->appendLayout(layoutPiece, mOutOffset, mExtraAdvance);
+ }
+
+ uint32_t mOutOffset;
+ Layout* mOutLayout;
+ float mExtraAdvance;
+};
+
+void StyleRun::appendLayout(const U16StringPiece& textBuf, const Range& range,
+ const Range& /* context */, const LayoutPieces& pieces,
+ const MinikinPaint& paint, uint32_t outOrigin,
+ StartHyphenEdit startHyphen, EndHyphenEdit endHyphen,
+ Layout* outLayout) const {
+ float wordSpacing = range.getLength() == 1 && isWordSpace(textBuf[range.getStart()])
+ ? mPaint.wordSpacing
+ : 0;
+ bool canUsePrecomputedResult = mPaint == paint;
+
+ LayoutCompositor compositor(outLayout, wordSpacing);
+ const Bidi bidiFlag = mIsRtl ? Bidi::FORCE_RTL : Bidi::FORCE_LTR;
+ const uint32_t paintId = pieces.findPaintId(mPaint);
+ for (const BidiText::RunInfo info : BidiText(textBuf, range, bidiFlag)) {
+ for (const auto[context, piece] : LayoutSplitter(textBuf, info.range, info.isRtl)) {
+ compositor.setOutOffset(piece.getStart() - outOrigin);
+ const StartHyphenEdit startEdit =
+ range.getStart() == piece.getStart() ? startHyphen : StartHyphenEdit::NO_EDIT;
+ const EndHyphenEdit endEdit =
+ range.getEnd() == piece.getEnd() ? endHyphen : EndHyphenEdit::NO_EDIT;
+
+ if (canUsePrecomputedResult) {
+ pieces.getOrCreate(textBuf, piece, context, mPaint, info.isRtl, startEdit, endEdit,
+ paintId, compositor);
+ } else {
+ LayoutCache::getInstance().getOrCreate(textBuf.substr(context),
+ piece - context.getStart(), paint,
+ info.isRtl, startEdit, endEdit, compositor);
+ }
+ }
+ }
}
-MinikinRect MeasuredText::getBounds(const U16StringPiece& textBuf, const Range& range) {
- MinikinRect rect;
- float advance = 0.0f;
+// Helper class for composing bounding box.
+class BoundsCompositor {
+public:
+ BoundsCompositor() : mAdvance(0) {}
+
+ void operator()(const LayoutPiece& layoutPiece, const MinikinPaint& /* paint */) {
+ MinikinRect tmpBounds = layoutPiece.bounds();
+ tmpBounds.offset(mAdvance, 0);
+ mBounds.join(tmpBounds);
+ mAdvance += layoutPiece.advance();
+ }
+
+ const MinikinRect& bounds() const { return mBounds; }
+ float advance() const { return mAdvance; }
+
+private:
+ float mAdvance;
+ MinikinRect mBounds;
+};
+
+std::pair<float, MinikinRect> StyleRun::getBounds(const U16StringPiece& textBuf, const Range& range,
+ const LayoutPieces& pieces) const {
+ BoundsCompositor compositor;
+ const Bidi bidiFlag = mIsRtl ? Bidi::FORCE_RTL : Bidi::FORCE_LTR;
+ const uint32_t paintId = pieces.findPaintId(mPaint);
+ for (const BidiText::RunInfo info : BidiText(textBuf, range, bidiFlag)) {
+ for (const auto[context, piece] : LayoutSplitter(textBuf, info.range, info.isRtl)) {
+ pieces.getOrCreate(textBuf, piece, context, mPaint, info.isRtl,
+ StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, paintId,
+ compositor);
+ }
+ }
+ return std::make_pair(compositor.advance(), compositor.bounds());
+}
+
+// Helper class for composing total extent.
+class ExtentCompositor {
+public:
+ ExtentCompositor() {}
+
+ void operator()(const LayoutPiece& layoutPiece, const MinikinPaint& /* paint */) {
+ mExtent.extendBy(layoutPiece.extent());
+ }
+
+ const MinikinExtent& extent() const { return mExtent; }
+
+private:
+ MinikinExtent mExtent;
+};
+
+MinikinExtent StyleRun::getExtent(const U16StringPiece& textBuf, const Range& range,
+ const LayoutPieces& pieces) const {
+ ExtentCompositor compositor;
+ Bidi bidiFlag = mIsRtl ? Bidi::FORCE_RTL : Bidi::FORCE_LTR;
+ const uint32_t paintId = pieces.findPaintId(mPaint);
+ for (const BidiText::RunInfo info : BidiText(textBuf, range, bidiFlag)) {
+ for (const auto[context, piece] : LayoutSplitter(textBuf, info.range, info.isRtl)) {
+ pieces.getOrCreate(textBuf, piece, context, mPaint, info.isRtl,
+ StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, paintId,
+ compositor);
+ }
+ }
+ return compositor.extent();
+}
+
+Layout MeasuredText::buildLayout(const U16StringPiece& textBuf, const Range& range,
+ const Range& contextRange, const MinikinPaint& paint,
+ StartHyphenEdit startHyphen, EndHyphenEdit endHyphen) {
+ Layout outLayout(range.getLength());
for (const auto& run : runs) {
const Range& runRange = run->getRange();
if (!Range::intersects(range, runRange)) {
continue;
}
- std::pair<float, MinikinRect> next =
+ const Range targetRange = Range::intersection(runRange, range);
+ StartHyphenEdit startEdit =
+ targetRange.getStart() == range.getStart() ? startHyphen : StartHyphenEdit::NO_EDIT;
+ EndHyphenEdit endEdit =
+ targetRange.getEnd() == range.getEnd() ? endHyphen : EndHyphenEdit::NO_EDIT;
+ run->appendLayout(textBuf, targetRange, contextRange, layoutPieces, paint, range.getStart(),
+ startEdit, endEdit, &outLayout);
+ }
+ return outLayout;
+}
+
+MinikinRect MeasuredText::getBounds(const U16StringPiece& textBuf, const Range& range) const {
+ MinikinRect rect;
+ float totalAdvance = 0.0f;
+
+ for (const auto& run : runs) {
+ const Range& runRange = run->getRange();
+ if (!Range::intersects(range, runRange)) {
+ continue;
+ }
+ auto[advance, bounds] =
run->getBounds(textBuf, Range::intersection(runRange, range), layoutPieces);
- MinikinRect nextRect = next.second;
- nextRect.offset(advance, 0);
- rect.join(nextRect);
- advance += next.first;
+ bounds.offset(totalAdvance, 0);
+ rect.join(bounds);
+ totalAdvance += advance;
}
return rect;
}
+MinikinExtent MeasuredText::getExtent(const U16StringPiece& textBuf, const Range& range) const {
+ MinikinExtent extent;
+ for (const auto& run : runs) {
+ const Range& runRange = run->getRange();
+ if (!Range::intersects(range, runRange)) {
+ continue;
+ }
+ MinikinExtent runExtent =
+ run->getExtent(textBuf, Range::intersection(runRange, range), layoutPieces);
+ extent.extendBy(runExtent);
+ }
+ return extent;
+}
+
} // namespace minikin
diff --git a/libs/minikin/OptimalLineBreaker.cpp b/libs/minikin/OptimalLineBreaker.cpp
index 5b3a6fc..3e6319b 100644
--- a/libs/minikin/OptimalLineBreaker.cpp
+++ b/libs/minikin/OptimalLineBreaker.cpp
@@ -221,7 +221,7 @@
// Compute penalty parameters.
float hyphenPenalty = 0.0f;
- if (run->canHyphenate()) {
+ if (run->canBreak()) {
auto penalties = computePenalties(*run, lineWidth, frequency, isJustified);
hyphenPenalty = penalties.first;
result.linePenalty = std::max(penalties.second, result.linePenalty);
@@ -231,7 +231,7 @@
for (uint32_t i = range.getStart(); i < range.getEnd(); ++i) {
MINIKIN_ASSERT(textBuf[i] != CHAR_TAB, "TAB is not supported in optimal line breaker");
- proc.feedChar(i, textBuf[i], measured.widths[i]);
+ proc.feedChar(i, textBuf[i], measured.widths[i], run->canBreak());
const uint32_t nextCharOffset = i + 1;
if (nextCharOffset != proc.nextWordBreak) {
@@ -255,7 +255,8 @@
proc, hyphenPenalty, isRtl, &result);
// We skip breaks for zero-width characters inside replacement spans.
- if (nextCharOffset == range.getEnd() || measured.widths[nextCharOffset] > 0) {
+ if (run->getPaint() != nullptr || nextCharOffset == range.getEnd() ||
+ measured.widths[nextCharOffset] > 0) {
const float penalty = hyphenPenalty * proc.wordBreakPenalty();
result.pushWordBreak(nextCharOffset, proc.sumOfCharWidths, proc.effectiveWidth,
penalty, proc.rawSpaceCount, proc.effectiveSpaceCount, isRtl);
@@ -284,25 +285,8 @@
LineBreakResult finishBreaksOptimal(const U16StringPiece& textBuf, const MeasuredText& measured,
const std::vector<OptimalBreaksData>& breaksData,
const std::vector<Candidate>& candidates);
-
- MinikinExtent computeMaxExtent(const U16StringPiece& textBuf, const MeasuredText& measured,
- uint32_t start, uint32_t end) const;
};
-// Find the needed extent between the start and end ranges. start is inclusive and end is exclusive.
-// Both are indices of the source string.
-MinikinExtent LineBreakOptimizer::computeMaxExtent(const U16StringPiece& textBuf,
- const MeasuredText& measured, uint32_t start,
- uint32_t end) const {
- MinikinExtent res = {0.0, 0.0, 0.0};
- for (uint32_t j = start; j < end; j++) {
- if (!isLineSpaceExcludeChar(textBuf[j])) {
- res.extendBy(measured.extents[j]);
- }
- }
- return res;
-}
-
// Follow "prev" links in candidates array, and copy to result arrays.
LineBreakResult LineBreakOptimizer::finishBreaksOptimal(
const U16StringPiece& textBuf, const MeasuredText& measured,
@@ -318,7 +302,7 @@
result.breakPoints.push_back(cand.offset);
result.widths.push_back(cand.postBreak - prev.preBreak);
- MinikinExtent extent = computeMaxExtent(textBuf, measured, prev.offset, cand.offset);
+ MinikinExtent extent = measured.getExtent(textBuf, Range(prev.offset, cand.offset));
result.ascents.push_back(extent.ascent);
result.descents.push_back(extent.descent);
diff --git a/libs/minikin/SystemFonts.cpp b/libs/minikin/SystemFonts.cpp
new file mode 100644
index 0000000..287fc61
--- /dev/null
+++ b/libs/minikin/SystemFonts.cpp
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "Minikin"
+
+#include "minikin/SystemFonts.h"
+
+namespace minikin {
+
+SystemFonts& SystemFonts::getInstance() {
+ static SystemFonts systemFonts;
+ return systemFonts;
+}
+
+std::shared_ptr<FontCollection> SystemFonts::findFontCollectionInternal(
+ const std::string& familyName) const {
+ auto it = mSystemFallbacks.find(familyName);
+ if (it != mSystemFallbacks.end()) {
+ return it->second;
+ }
+ // TODO: Lookup by PostScript name.
+ return mDefaultFallback;
+}
+
+} // namespace minikin
diff --git a/tests/Android.bp b/tests/Android.bp
index 9f6d534..1d8c019 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -8,10 +8,13 @@
"data/Cherokee.ttf",
"data/ColorEmojiFont.ttf",
"data/ColorTextMixedEmojiFont.ttf",
+ "data/CustomExtent.ttf",
"data/Emoji.ttf",
+ "data/Hiragana.ttf",
"data/Italic.ttf",
"data/Ja.ttf",
"data/Ko.ttf",
+ "data/Ligature.ttf",
"data/LayoutTestFont.ttf",
"data/MultiAxis.ttf",
"data/NoCmapFormat14.ttf",
diff --git a/tests/data/CustomExtent.ttf b/tests/data/CustomExtent.ttf
new file mode 100644
index 0000000..7079677
--- /dev/null
+++ b/tests/data/CustomExtent.ttf
Binary files differ
diff --git a/tests/data/CustomExtent.ttx b/tests/data/CustomExtent.ttx
new file mode 100644
index 0000000..1aba9df
--- /dev/null
+++ b/tests/data/CustomExtent.ttx
@@ -0,0 +1,189 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
+
+ <GlyphOrder>
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name="1em"/>
+ </GlyphOrder>
+
+ <head>
+ <tableVersion value="1.0"/>
+ <fontRevision value="1.0"/>
+ <checkSumAdjustment value="0x640cdb2f"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="100"/>
+ <created value="Fri Mar 17 07:26:00 2017"/>
+ <macStyle value="00000000 00000000"/>
+ <lowestRecPPEM value="7"/>
+ <fontDirectionHint value="2"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="0x00010000"/>
+ <ascent value="0"/>
+ <descent value="0"/>
+ <lineGap value="0"/>
+ <caretSlopeRise value="1"/>
+ <caretSlopeRun value="0"/>
+ <caretOffset value="0"/>
+ <reserved0 value="0"/>
+ <reserved1 value="0"/>
+ <reserved2 value="0"/>
+ <reserved3 value="0"/>
+ <metricDataFormat value="0"/>
+ </hhea>
+
+ <maxp>
+ <tableVersion value="0x10000"/>
+ <maxZones value="0"/>
+ <maxTwilightPoints value="0"/>
+ <maxStorage value="0"/>
+ <maxFunctionDefs value="0"/>
+ <maxInstructionDefs value="0"/>
+ <maxStackElements value="0"/>
+ <maxSizeOfInstructions value="0"/>
+ <maxComponentElements value="0"/>
+ </maxp>
+
+ <OS_2>
+ <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+ will be recalculated by the compiler -->
+ <version value="3"/>
+ <xAvgCharWidth value="594"/>
+ <usWeightClass value="400"/>
+ <usWidthClass value="5"/>
+ <fsType value="00000000 00001000"/>
+ <ySubscriptXSize value="650"/>
+ <ySubscriptYSize value="600"/>
+ <ySubscriptXOffset value="0"/>
+ <ySubscriptYOffset value="75"/>
+ <ySuperscriptXSize value="650"/>
+ <ySuperscriptYSize value="600"/>
+ <ySuperscriptXOffset value="0"/>
+ <ySuperscriptYOffset value="350"/>
+ <yStrikeoutSize value="50"/>
+ <yStrikeoutPosition value="300"/>
+ <sFamilyClass value="0"/>
+ <panose>
+ <bFamilyType value="0"/>
+ <bSerifStyle value="0"/>
+ <bWeight value="5"/>
+ <bProportion value="0"/>
+ <bContrast value="0"/>
+ <bStrokeVariation value="0"/>
+ <bArmStyle value="0"/>
+ <bLetterForm value="0"/>
+ <bMidline value="0"/>
+ <bXHeight value="0"/>
+ </panose>
+ <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+ <achVendID value="UKWN"/>
+ <fsSelection value="00000000 01000000"/>
+ <usFirstCharIndex value="32"/>
+ <usLastCharIndex value="122"/>
+ <sTypoAscender value="1600"/>
+ <sTypoDescender value="-400"/>
+ <sTypoLineGap value="200"/>
+ <usWinAscent value="1000"/>
+ <usWinDescent value="200"/>
+ <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="500"/>
+ <sCapHeight value="700"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="0"/>
+ </OS_2>
+
+ <hmtx>
+ <mtx name=".notdef" width="50" lsb="0"/>
+ <mtx name="1em" width="100" lsb="0"/>
+ </hmtx>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_12 format="12" reserved="0" length="6" nGroups="1" platformID="3" platEncID="10" language="0">
+ <map code="0x3042" name="1em" /> <!-- HIRAGANA LETTER A -->
+ <map code="0x3044" name="1em" /> <!-- HIRAGANA LETTER I -->
+ <map code="0x3046" name="1em" /> <!-- HIRAGANA LETTER U -->
+ <map code="0x3048" name="1em" /> <!-- HIRAGANA LETTER E -->
+ <map code="0x304A" name="1em" /> <!-- HIRAGANA LETTER O -->
+ </cmap_format_12>
+ </cmap>
+
+ <loca>
+ <!-- The 'loca' table will be calculated by the compiler -->
+ </loca>
+
+ <glyf>
+ <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" />
+ <TTGlyph name="1em" xMin="0" yMin="0" xMax="100" yMax="100">
+ <contour>
+ <pt x="0" y="0" on="1" />
+ <pt x="100" y="0" on="1" />
+ <pt x="100" y="100" on="1" />
+ <pt x="0" y="100" on="1" />
+ </contour>
+ <instructions><assembly></assembly></instructions>
+ </TTGlyph>
+ </glyf>
+
+ <name>
+ <namerecord nameID="1" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Font for Hiragana
+ </namerecord>
+ <namerecord nameID="2" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Regular
+ </namerecord>
+ <namerecord nameID="4" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Font for Hiragana
+ </namerecord>
+ <namerecord nameID="6" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ SampleFont-Regular
+ </namerecord>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ Sample Font
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ Sample Font
+ </namerecord>
+ <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+ SampleFont-Regular
+ </namerecord>
+ </name>
+
+ <post>
+ <formatType value="3.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="-75"/>
+ <underlineThickness value="50"/>
+ <isFixedPitch value="0"/>
+ <minMemType42 value="0"/>
+ <maxMemType42 value="0"/>
+ <minMemType1 value="0"/>
+ <maxMemType1 value="0"/>
+ </post>
+
+</ttFont>
diff --git a/tests/data/Hiragana.ttf b/tests/data/Hiragana.ttf
new file mode 100644
index 0000000..9634f0f
--- /dev/null
+++ b/tests/data/Hiragana.ttf
Binary files differ
diff --git a/tests/data/Hiragana.ttx b/tests/data/Hiragana.ttx
new file mode 100644
index 0000000..f0f5c44
--- /dev/null
+++ b/tests/data/Hiragana.ttx
@@ -0,0 +1,189 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
+
+ <GlyphOrder>
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name="2em"/>
+ </GlyphOrder>
+
+ <head>
+ <tableVersion value="1.0"/>
+ <fontRevision value="1.0"/>
+ <checkSumAdjustment value="0x640cdb2f"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="100"/>
+ <created value="Fri Mar 17 07:26:00 2017"/>
+ <macStyle value="00000000 00000000"/>
+ <lowestRecPPEM value="7"/>
+ <fontDirectionHint value="2"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="0x00010000"/>
+ <ascent value="0"/>
+ <descent value="0"/>
+ <lineGap value="0"/>
+ <caretSlopeRise value="1"/>
+ <caretSlopeRun value="0"/>
+ <caretOffset value="0"/>
+ <reserved0 value="0"/>
+ <reserved1 value="0"/>
+ <reserved2 value="0"/>
+ <reserved3 value="0"/>
+ <metricDataFormat value="0"/>
+ </hhea>
+
+ <maxp>
+ <tableVersion value="0x10000"/>
+ <maxZones value="0"/>
+ <maxTwilightPoints value="0"/>
+ <maxStorage value="0"/>
+ <maxFunctionDefs value="0"/>
+ <maxInstructionDefs value="0"/>
+ <maxStackElements value="0"/>
+ <maxSizeOfInstructions value="0"/>
+ <maxComponentElements value="0"/>
+ </maxp>
+
+ <OS_2>
+ <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+ will be recalculated by the compiler -->
+ <version value="3"/>
+ <xAvgCharWidth value="594"/>
+ <usWeightClass value="400"/>
+ <usWidthClass value="5"/>
+ <fsType value="00000000 00001000"/>
+ <ySubscriptXSize value="650"/>
+ <ySubscriptYSize value="600"/>
+ <ySubscriptXOffset value="0"/>
+ <ySubscriptYOffset value="75"/>
+ <ySuperscriptXSize value="650"/>
+ <ySuperscriptYSize value="600"/>
+ <ySuperscriptXOffset value="0"/>
+ <ySuperscriptYOffset value="350"/>
+ <yStrikeoutSize value="50"/>
+ <yStrikeoutPosition value="300"/>
+ <sFamilyClass value="0"/>
+ <panose>
+ <bFamilyType value="0"/>
+ <bSerifStyle value="0"/>
+ <bWeight value="5"/>
+ <bProportion value="0"/>
+ <bContrast value="0"/>
+ <bStrokeVariation value="0"/>
+ <bArmStyle value="0"/>
+ <bLetterForm value="0"/>
+ <bMidline value="0"/>
+ <bXHeight value="0"/>
+ </panose>
+ <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+ <achVendID value="UKWN"/>
+ <fsSelection value="00000000 01000000"/>
+ <usFirstCharIndex value="32"/>
+ <usLastCharIndex value="122"/>
+ <sTypoAscender value="1600"/>
+ <sTypoDescender value="-400"/>
+ <sTypoLineGap value="200"/>
+ <usWinAscent value="1000"/>
+ <usWinDescent value="200"/>
+ <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="500"/>
+ <sCapHeight value="700"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="0"/>
+ </OS_2>
+
+ <hmtx>
+ <mtx name=".notdef" width="50" lsb="0"/>
+ <mtx name="2em" width="200" lsb="0"/>
+ </hmtx>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_12 format="12" reserved="0" length="6" nGroups="1" platformID="3" platEncID="10" language="0">
+ <map code="0x3042" name="2em" /> <!-- HIRAGANA LETTER A -->
+ <map code="0x3044" name="2em" /> <!-- HIRAGANA LETTER I -->
+ <map code="0x3046" name="2em" /> <!-- HIRAGANA LETTER U -->
+ <map code="0x3048" name="2em" /> <!-- HIRAGANA LETTER E -->
+ <map code="0x304A" name="2em" /> <!-- HIRAGANA LETTER O -->
+ </cmap_format_12>
+ </cmap>
+
+ <loca>
+ <!-- The 'loca' table will be calculated by the compiler -->
+ </loca>
+
+ <glyf>
+ <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" />
+ <TTGlyph name="2em" xMin="0" yMin="0" xMax="200" yMax="200">
+ <contour>
+ <pt x="0" y="0" on="1" />
+ <pt x="200" y="0" on="1" />
+ <pt x="200" y="200" on="1" />
+ <pt x="0" y="200" on="1" />
+ </contour>
+ <instructions><assembly></assembly></instructions>
+ </TTGlyph>
+ </glyf>
+
+ <name>
+ <namerecord nameID="1" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Font for Hiragana
+ </namerecord>
+ <namerecord nameID="2" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Regular
+ </namerecord>
+ <namerecord nameID="4" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Font for Hiragana
+ </namerecord>
+ <namerecord nameID="6" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ SampleFont-Regular
+ </namerecord>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ Sample Font
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ Sample Font
+ </namerecord>
+ <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+ SampleFont-Regular
+ </namerecord>
+ </name>
+
+ <post>
+ <formatType value="3.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="-75"/>
+ <underlineThickness value="50"/>
+ <isFixedPitch value="0"/>
+ <minMemType42 value="0"/>
+ <maxMemType42 value="0"/>
+ <minMemType1 value="0"/>
+ <maxMemType1 value="0"/>
+ </post>
+
+</ttFont>
diff --git a/tests/data/LayoutTestFont.ttf b/tests/data/LayoutTestFont.ttf
index 7076023..2b7cc4b 100644
--- a/tests/data/LayoutTestFont.ttf
+++ b/tests/data/LayoutTestFont.ttf
Binary files differ
diff --git a/tests/data/LayoutTestFont.ttx b/tests/data/LayoutTestFont.ttx
index 7971a03..22f66f0 100644
--- a/tests/data/LayoutTestFont.ttx
+++ b/tests/data/LayoutTestFont.ttx
@@ -156,13 +156,69 @@
<glyf>
<TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" />
<TTGlyph name="0em" xMin="0" yMin="0" xMax="0" yMax="0" />
- <TTGlyph name="1em" xMin="0" yMin="0" xMax="0" yMax="0" />
- <TTGlyph name="3em" xMin="0" yMin="0" xMax="0" yMax="0" />
- <TTGlyph name="5em" xMin="0" yMin="0" xMax="0" yMax="0" />
- <TTGlyph name="7em" xMin="0" yMin="0" xMax="0" yMax="0" />
- <TTGlyph name="10em" xMin="0" yMin="0" xMax="0" yMax="0" />
- <TTGlyph name="50em" xMin="0" yMin="0" xMax="0" yMax="0" />
- <TTGlyph name="100em" xMin="0" yMin="0" xMax="0" yMax="0" />
+ <TTGlyph name="1em" xMin="0" yMin="0" xMax="100" yMax="100">
+ <contour>
+ <pt x="0" y="0" on="1" />
+ <pt x="0" y="100" on="1" />
+ <pt x="100" y="100" on="1" />
+ <pt x="100" y="0" on="1" />
+ </contour>
+ <instructions />
+ </TTGlyph>
+ <TTGlyph name="3em" xMin="0" yMin="0" xMax="300" yMax="300">
+ <contour>
+ <pt x="0" y="0" on="1" />
+ <pt x="0" y="300" on="1" />
+ <pt x="300" y="300" on="1" />
+ <pt x="300" y="0" on="1" />
+ </contour>
+ <instructions />
+ </TTGlyph>
+ <TTGlyph name="5em" xMin="0" yMin="0" xMax="500" yMax="500">
+ <contour>
+ <pt x="0" y="0" on="1" />
+ <pt x="0" y="500" on="1" />
+ <pt x="500" y="500" on="1" />
+ <pt x="500" y="0" on="1" />
+ </contour>
+ <instructions />
+ </TTGlyph>
+ <TTGlyph name="7em" xMin="0" yMin="0" xMax="700" yMax="700">
+ <contour>
+ <pt x="0" y="0" on="1" />
+ <pt x="0" y="700" on="1" />
+ <pt x="700" y="700" on="1" />
+ <pt x="700" y="0" on="1" />
+ </contour>
+ <instructions />
+ </TTGlyph>
+ <TTGlyph name="10em" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1" />
+ <pt x="0" y="1000" on="1" />
+ <pt x="1000" y="1000" on="1" />
+ <pt x="1000" y="0" on="1" />
+ </contour>
+ <instructions />
+ </TTGlyph>
+ <TTGlyph name="50em" xMin="0" yMin="0" xMax="5000" yMax="5000">
+ <contour>
+ <pt x="0" y="0" on="1" />
+ <pt x="0" y="5000" on="1" />
+ <pt x="5000" y="5000" on="1" />
+ <pt x="5000" y="0" on="1" />
+ </contour>
+ <instructions />
+ </TTGlyph>
+ <TTGlyph name="100em" xMin="0" yMin="0" xMax="10000" yMax="10000">
+ <contour>
+ <pt x="0" y="0" on="1" />
+ <pt x="0" y="10000" on="1" />
+ <pt x="10000" y="10000" on="1" />
+ <pt x="10000" y="0" on="1" />
+ </contour>
+ <instructions />
+ </TTGlyph>
</glyf>
<name>
diff --git a/tests/data/Ligature.ttf b/tests/data/Ligature.ttf
new file mode 100644
index 0000000..a7503f0
--- /dev/null
+++ b/tests/data/Ligature.ttf
Binary files differ
diff --git a/tests/data/Ligature.ttx b/tests/data/Ligature.ttx
new file mode 100644
index 0000000..1f494a2
--- /dev/null
+++ b/tests/data/Ligature.ttx
@@ -0,0 +1,362 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
+
+ <GlyphOrder>
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name="1em"/>
+ <GlyphID id="2" name="f"/>
+ <GlyphID id="3" name="i"/>
+ <GlyphID id="4" name="fi"/>
+ </GlyphOrder>
+
+ <head>
+ <tableVersion value="1.0"/>
+ <fontRevision value="1.0"/>
+ <checkSumAdjustment value="0x640cdb2f"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="100"/>
+ <created value="Fri Mar 17 07:26:00 2017"/>
+ <macStyle value="00000000 00000000"/>
+ <lowestRecPPEM value="7"/>
+ <fontDirectionHint value="2"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="0x00010000"/>
+ <ascent value="0"/>
+ <descent value="0"/>
+ <lineGap value="0"/>
+ <caretSlopeRise value="1"/>
+ <caretSlopeRun value="0"/>
+ <caretOffset value="0"/>
+ <reserved0 value="0"/>
+ <reserved1 value="0"/>
+ <reserved2 value="0"/>
+ <reserved3 value="0"/>
+ <metricDataFormat value="0"/>
+ </hhea>
+
+ <maxp>
+ <tableVersion value="0x10000"/>
+ <maxZones value="0"/>
+ <maxTwilightPoints value="0"/>
+ <maxStorage value="0"/>
+ <maxFunctionDefs value="0"/>
+ <maxInstructionDefs value="0"/>
+ <maxStackElements value="0"/>
+ <maxSizeOfInstructions value="0"/>
+ <maxComponentElements value="0"/>
+ </maxp>
+
+ <OS_2>
+ <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+ will be recalculated by the compiler -->
+ <version value="3"/>
+ <xAvgCharWidth value="594"/>
+ <usWeightClass value="400"/>
+ <usWidthClass value="5"/>
+ <fsType value="00000000 00001000"/>
+ <ySubscriptXSize value="650"/>
+ <ySubscriptYSize value="600"/>
+ <ySubscriptXOffset value="0"/>
+ <ySubscriptYOffset value="75"/>
+ <ySuperscriptXSize value="650"/>
+ <ySuperscriptYSize value="600"/>
+ <ySuperscriptXOffset value="0"/>
+ <ySuperscriptYOffset value="350"/>
+ <yStrikeoutSize value="50"/>
+ <yStrikeoutPosition value="300"/>
+ <sFamilyClass value="0"/>
+ <panose>
+ <bFamilyType value="0"/>
+ <bSerifStyle value="0"/>
+ <bWeight value="5"/>
+ <bProportion value="0"/>
+ <bContrast value="0"/>
+ <bStrokeVariation value="0"/>
+ <bArmStyle value="0"/>
+ <bLetterForm value="0"/>
+ <bMidline value="0"/>
+ <bXHeight value="0"/>
+ </panose>
+ <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+ <achVendID value="UKWN"/>
+ <fsSelection value="00000000 01000000"/>
+ <usFirstCharIndex value="32"/>
+ <usLastCharIndex value="122"/>
+ <sTypoAscender value="800"/>
+ <sTypoDescender value="-200"/>
+ <sTypoLineGap value="200"/>
+ <usWinAscent value="1000"/>
+ <usWinDescent value="200"/>
+ <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="500"/>
+ <sCapHeight value="700"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="0"/>
+ </OS_2>
+
+ <hmtx>
+ <mtx name=".notdef" width="50" lsb="0"/>
+ <mtx name="1em" width="100" lsb="0"/>
+ <mtx name="f" width="100" lsb="0"/>
+ <mtx name="i" width="100" lsb="0"/>
+ <mtx name="fi" width="100" lsb="0"/>
+ </hmtx>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_12 format="12" reserved="0" length="6" nGroups="1" platformID="3" platEncID="10" language="0">
+ <map code="0x0020" name="1em" /> <!-- ' ' -->
+ <map code="0x0021" name="1em" /> <!-- '!' -->
+ <map code="0x0022" name="1em" /> <!-- '"' -->
+ <map code="0x0023" name="1em" /> <!-- '#' -->
+ <map code="0x0024" name="1em" /> <!-- '$' -->
+ <map code="0x0025" name="1em" /> <!-- '%' -->
+ <map code="0x0026" name="1em" /> <!-- '&' -->
+ <map code="0x0027" name="1em" /> <!-- ''' -->
+ <map code="0x0028" name="1em" /> <!-- '(' -->
+ <map code="0x0029" name="1em" /> <!-- ')' -->
+ <map code="0x002A" name="1em" /> <!-- '*' -->
+ <map code="0x002B" name="1em" /> <!-- '+' -->
+ <map code="0x002C" name="1em" /> <!-- ''' -->
+ <map code="0x002D" name="1em" /> <!-- '-' -->
+ <map code="0x002E" name="1em" /> <!-- '.' -->
+ <map code="0x002F" name="1em" /> <!-- '/' -->
+ <map code="0x0030" name="1em" /> <!-- '0' -->
+ <map code="0x0031" name="1em" /> <!-- '1' -->
+ <map code="0x0032" name="1em" /> <!-- '2' -->
+ <map code="0x0033" name="1em" /> <!-- '3' -->
+ <map code="0x0034" name="1em" /> <!-- '4' -->
+ <map code="0x0035" name="1em" /> <!-- '5' -->
+ <map code="0x0036" name="1em" /> <!-- '6' -->
+ <map code="0x0037" name="1em" /> <!-- '7' -->
+ <map code="0x0038" name="1em" /> <!-- '8' -->
+ <map code="0x0039" name="1em" /> <!-- '9' -->
+ <map code="0x003A" name="1em" /> <!-- ':' -->
+ <map code="0x003B" name="1em" /> <!-- ';' -->
+ <map code="0x003C" name="1em" /> <!-- '<' -->
+ <map code="0x003D" name="1em" /> <!-- '=' -->
+ <map code="0x003E" name="1em" /> <!-- '>' -->
+ <map code="0x003F" name="1em" /> <!-- '?' -->
+ <map code="0x0040" name="1em" /> <!-- '@' -->
+ <map code="0x0041" name="1em" /> <!-- 'A' -->
+ <map code="0x0042" name="1em" /> <!-- 'B' -->
+ <map code="0x0043" name="1em" /> <!-- 'C' -->
+ <map code="0x0044" name="1em" /> <!-- 'D' -->
+ <map code="0x0045" name="1em" /> <!-- 'E' -->
+ <map code="0x0046" name="1em" /> <!-- 'F' -->
+ <map code="0x0047" name="1em" /> <!-- 'G' -->
+ <map code="0x0048" name="1em" /> <!-- 'H' -->
+ <map code="0x0049" name="1em" /> <!-- 'I' -->
+ <map code="0x004A" name="1em" /> <!-- 'J' -->
+ <map code="0x004B" name="1em" /> <!-- 'K' -->
+ <map code="0x004C" name="1em" /> <!-- 'L' -->
+ <map code="0x004D" name="1em" /> <!-- 'M' -->
+ <map code="0x004E" name="1em" /> <!-- 'N' -->
+ <map code="0x004F" name="1em" /> <!-- 'O' -->
+ <map code="0x0050" name="1em" /> <!-- 'P' -->
+ <map code="0x0051" name="1em" /> <!-- 'Q' -->
+ <map code="0x0052" name="1em" /> <!-- 'R' -->
+ <map code="0x0053" name="1em" /> <!-- 'S' -->
+ <map code="0x0054" name="1em" /> <!-- 'T' -->
+ <map code="0x0055" name="1em" /> <!-- 'U' -->
+ <map code="0x0056" name="1em" /> <!-- 'V' -->
+ <map code="0x0057" name="1em" /> <!-- 'W' -->
+ <map code="0x0058" name="1em" /> <!-- 'X' -->
+ <map code="0x0059" name="1em" /> <!-- 'Y' -->
+ <map code="0x005A" name="1em" /> <!-- 'Z' -->
+ <map code="0x005B" name="1em" /> <!-- '[' -->
+ <map code="0x005C" name="1em" /> <!-- '\' -->
+ <map code="0x005D" name="1em" /> <!-- ']' -->
+ <map code="0x005E" name="1em" /> <!-- '^' -->
+ <map code="0x005F" name="1em" /> <!-- '_' -->
+ <map code="0x0060" name="1em" /> <!-- '`' -->
+ <map code="0x0061" name="1em" /> <!-- 'a' -->
+ <map code="0x0062" name="1em" /> <!-- 'b' -->
+ <map code="0x0063" name="1em" /> <!-- 'c' -->
+ <map code="0x0064" name="1em" /> <!-- 'd' -->
+ <map code="0x0065" name="1em" /> <!-- 'e' -->
+ <map code="0x0066" name="f" /> <!-- 'f' -->
+ <map code="0x0067" name="1em" /> <!-- 'g' -->
+ <map code="0x0068" name="1em" /> <!-- 'h' -->
+ <map code="0x0069" name="i" /> <!-- 'i' -->
+ <map code="0x006A" name="1em" /> <!-- 'j' -->
+ <map code="0x006B" name="1em" /> <!-- 'k' -->
+ <map code="0x006C" name="1em" /> <!-- 'l' -->
+ <map code="0x006D" name="1em" /> <!-- 'm' -->
+ <map code="0x006E" name="1em" /> <!-- 'n' -->
+ <map code="0x006F" name="1em" /> <!-- 'o' -->
+ <map code="0x0070" name="1em" /> <!-- 'p' -->
+ <map code="0x0071" name="1em" /> <!-- 'q' -->
+ <map code="0x0072" name="1em" /> <!-- 'r' -->
+ <map code="0x0073" name="1em" /> <!-- 's' -->
+ <map code="0x0074" name="1em" /> <!-- 't' -->
+ <map code="0x0075" name="1em" /> <!-- 'u' -->
+ <map code="0x0076" name="1em" /> <!-- 'v' -->
+ <map code="0x0077" name="1em" /> <!-- 'w' -->
+ <map code="0x0078" name="1em" /> <!-- 'x' -->
+ <map code="0x0079" name="1em" /> <!-- 'y' -->
+ <map code="0x007A" name="1em" /> <!-- 'z' -->
+ <map code="0x007B" name="1em" /> <!-- '{' -->
+ <map code="0x007C" name="1em" /> <!-- '|' -->
+ <map code="0x007D" name="1em" /> <!-- '}' -->
+ <map code="0x007E" name="1em" /> <!-- '~' -->
+ </cmap_format_12>
+ </cmap>
+
+ <loca>
+ <!-- The 'loca' table will be calculated by the compiler -->
+ </loca>
+
+ <glyf>
+ <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" />
+ <TTGlyph name="1em" xMin="0" yMin="0" xMax="100" yMax="100">
+ <contour>
+ <pt x="0" y="0" on="1" />
+ <pt x="100" y="0" on="1" />
+ <pt x="100" y="100" on="1" />
+ <pt x="0" y="100" on="1" />
+ </contour>
+ <instructions><assembly></assembly></instructions>
+ </TTGlyph>
+ <TTGlyph name="f" 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="i" 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="fi" xMin="0" yMin="0" xMax="100" yMax="100">
+ <contour>
+ <pt x="0" y="0" on="1" />
+ <pt x="100" y="0" on="1" />
+ <pt x="100" y="100" on="1" />
+ <pt x="0" y="100" on="1" />
+ </contour>
+ <instructions><assembly></assembly></instructions>
+ </TTGlyph>
+ </glyf>
+
+ <name>
+ <namerecord nameID="1" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Font for ASCII with Ligature
+ </namerecord>
+ <namerecord nameID="2" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Regular
+ </namerecord>
+ <namerecord nameID="4" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Font for ASCII
+ </namerecord>
+ <namerecord nameID="6" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ SampleFont-Regular
+ </namerecord>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ Sample Font
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ Sample Font
+ </namerecord>
+ <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+ SampleFont-Regular
+ </namerecord>
+ </name>
+
+ <post>
+ <formatType value="3.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="-75"/>
+ <underlineThickness value="50"/>
+ <isFixedPitch value="0"/>
+ <minMemType42 value="0"/>
+ <maxMemType42 value="0"/>
+ <minMemType1 value="0"/>
+ <maxMemType1 value="0"/>
+ </post>
+
+ <GSUB>
+ <Version value="0x00010000"/>
+ <ScriptList>
+ <ScriptRecord index="0">
+ <ScriptTag value="DFLT" />
+ <Script>
+ <DefaultLangSys>
+ <ReqFeatureIndex value="65535"/>
+ <FeatureIndex index="0" value="0"/>
+ <FeatureIndex index="1" value="1"/>
+ </DefaultLangSys>
+ </Script>
+ </ScriptRecord>
+ </ScriptList>
+ <FeatureList>
+ <FeatureRecord index="0">
+ <FeatureTag value="ccmp"/>
+ <Feature>
+ <LookupListIndex index="0" value="0" />
+ </Feature>
+ </FeatureRecord>
+ <FeatureRecord index="1">
+ <FeatureTag value="liga"/>
+ <Feature>
+ <LookupListIndex index="1" value="1" />
+ </Feature>
+ </FeatureRecord>
+ </FeatureList>
+ <LookupList>
+ <Lookup index="0">
+ <LookupType value="4" />
+ <LookupFlag value="0" />
+ <LigatureSubst index="0" Format="1">
+ <LigatureSet glyph="f">
+ <Ligature components="i" glyph="fi" />
+ </LigatureSet>
+ </LigatureSubst>
+ </Lookup>
+ <Lookup index="1">
+ <LookupType value="4" />
+ <LookupFlag value="0" />
+ <LigatureSubst index="0" Format="1">
+ <LigatureSet glyph="f">
+ <Ligature components="f" glyph="fi" />
+ </LigatureSet>
+ </LigatureSubst>
+ </Lookup>
+ </LookupList>
+ </GSUB>
+
+</ttFont>
diff --git a/tests/perftests/FontCollection.cpp b/tests/perftests/FontCollection.cpp
index 819f125..15d1c5e 100644
--- a/tests/perftests/FontCollection.cpp
+++ b/tests/perftests/FontCollection.cpp
@@ -21,6 +21,7 @@
#include <benchmark/benchmark.h>
#include "minikin/LocaleList.h"
+#include "minikin/MinikinPaint.h"
#include "FontTestUtils.h"
#include "MinikinInternal.h"
@@ -95,7 +96,8 @@
while (state.KeepRunning()) {
result.clear();
- collection->itemize(buffer, utf16_length, paint, &result);
+ collection->itemize(U16StringPiece(buffer, utf16_length), paint.fontStyle,
+ paint.localeListId, paint.familyVariant);
}
}
diff --git a/tests/stresstest/MultithreadTest.cpp b/tests/stresstest/MultithreadTest.cpp
index 5d5ae03..560b517 100644
--- a/tests/stresstest/MultithreadTest.cpp
+++ b/tests/stresstest/MultithreadTest.cpp
@@ -26,6 +26,7 @@
#include "minikin/FontCollection.h"
#include "minikin/Macros.h"
+#include "minikin/MinikinPaint.h"
#include "FontTestUtils.h"
#include "MinikinInternal.h"
@@ -73,15 +74,13 @@
for (int j = 0; j < LAYOUT_COUNT_PER_COLLECTION; ++j) {
// Generates 10 of 3-letter words so that the word sometimes hit the cache.
- Layout layout;
std::vector<uint16_t> text = generateTestText(&mt, 3, 10);
- layout.doLayout(text, Range(0, text.size()), Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
- EndHyphenEdit::NO_EDIT);
- std::vector<float> advances(text.size());
- layout.getAdvances(advances.data());
- for (size_t k = 0; k < advances.size(); ++k) {
+ Layout layout(text, Range(0, text.size()), Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
+ EndHyphenEdit::NO_EDIT);
+ for (size_t k = 0; k < text.size(); ++k) {
// All characters in Ascii.ttf has 1.0em horizontal advance.
- LOG_ALWAYS_FATAL_IF(advances[k] != 10.0f, "Memory corruption detected.");
+ LOG_ALWAYS_FATAL_IF(layout.getCharAdvance(k) != 10.0f,
+ "Memory corruption detected.");
}
}
}
diff --git a/tests/unittest/Android.bp b/tests/unittest/Android.bp
index 4f5ae23..0971a06 100644
--- a/tests/unittest/Android.bp
+++ b/tests/unittest/Android.bp
@@ -43,16 +43,20 @@
"BidiUtilsTest.cpp",
"CmapCoverageTest.cpp",
"EmojiTest.cpp",
+ "FontTest.cpp",
"FontCollectionTest.cpp",
"FontCollectionItemizeTest.cpp",
"FontFamilyTest.cpp",
"FontLanguageListCacheTest.cpp",
"FontUtilsTest.cpp",
+ "HasherTest.cpp",
"HyphenatorMapTest.cpp",
"HyphenatorTest.cpp",
"GraphemeBreakTests.cpp",
"GreedyLineBreakerTest.cpp",
"LayoutCacheTest.cpp",
+ "LayoutCoreTest.cpp",
+ "LayoutSplitterTest.cpp",
"LayoutTest.cpp",
"LayoutUtilsTest.cpp",
"LocaleListTest.cpp",
@@ -61,6 +65,7 @@
"OptimalLineBreakerTest.cpp",
"SparseBitSetTest.cpp",
"StringPieceTest.cpp",
+ "SystemFontsTest.cpp",
"TestMain.cpp",
"UnicodeUtilsTest.cpp",
"WordBreakerTests.cpp",
diff --git a/tests/unittest/AndroidLineBreakerHelperTest.cpp b/tests/unittest/AndroidLineBreakerHelperTest.cpp
index ea977c0..db47e9d 100644
--- a/tests/unittest/AndroidLineBreakerHelperTest.cpp
+++ b/tests/unittest/AndroidLineBreakerHelperTest.cpp
@@ -26,7 +26,7 @@
const std::vector<float> EMPTY;
{
AndroidLineWidth lineWidth(-10 /* first width */, 1 /* first count */, 0 /* rest width */,
- EMPTY, EMPTY, EMPTY, 0);
+ EMPTY, 0);
EXPECT_LE(0.0f, lineWidth.getMin());
for (int i = 0; i < LINE_COUNT; ++i) {
@@ -35,7 +35,7 @@
}
{
AndroidLineWidth lineWidth(0 /* first width */, 0 /* first count */, -10 /* rest width */,
- EMPTY, EMPTY, EMPTY, 0);
+ EMPTY, 0);
EXPECT_LE(0.0f, lineWidth.getMin());
for (int i = 0; i < LINE_COUNT; ++i) {
@@ -45,7 +45,7 @@
{
std::vector<float> indents = {10};
AndroidLineWidth lineWidth(0 /* first width */, 0 /* first count */, 0 /* rest width */,
- indents, EMPTY, EMPTY, 0);
+ indents, 0);
EXPECT_LE(0.0f, lineWidth.getMin());
for (int i = 0; i < LINE_COUNT; ++i) {
diff --git a/tests/unittest/EmojiTest.cpp b/tests/unittest/EmojiTest.cpp
index 32ffde7..643a112 100644
--- a/tests/unittest/EmojiTest.cpp
+++ b/tests/unittest/EmojiTest.cpp
@@ -32,6 +32,19 @@
EXPECT_TRUE(isEmoji(0x1F6F7)); // SLED
EXPECT_TRUE(isEmoji(0x1F9E6)); // SOCKS
+ EXPECT_TRUE(isEmoji(0x1F6D5)); // HINDU TEMPLE
+ EXPECT_TRUE(isEmoji(0x1F7E7)); // ORANGE SQUARE
+ EXPECT_TRUE(isEmoji(0x1F9CF)); // DEAF PERSON
+ EXPECT_TRUE(isEmoji(0x1F9CE)); // PERSON KNEELING
+ EXPECT_TRUE(isEmoji(0x1F9A6)); // OTTER
+ EXPECT_TRUE(isEmoji(0x1F9A9)); // FLAMINGO
+ EXPECT_TRUE(isEmoji(0x1F9C6)); // FALAFEL
+ EXPECT_TRUE(isEmoji(0x1F9AA)); // OYSTER
+ EXPECT_TRUE(isEmoji(0x1FA82)); // PARACHUTE
+ EXPECT_TRUE(isEmoji(0x1FA80)); // YO-YO
+ EXPECT_TRUE(isEmoji(0x1FA70)); // BALLET SHOES
+ EXPECT_TRUE(isEmoji(0x1FA79)); // ADHESIVE BANDAGE
+
EXPECT_FALSE(isEmoji(0x0000)); // <control>
EXPECT_FALSE(isEmoji(0x0061)); // LATIN SMALL LETTER A
EXPECT_FALSE(isEmoji(0x1F93B)); // MODERN PENTATHLON
diff --git a/tests/unittest/FontCollectionItemizeTest.cpp b/tests/unittest/FontCollectionItemizeTest.cpp
index 2fbba98..8cd95aa 100644
--- a/tests/unittest/FontCollectionItemizeTest.cpp
+++ b/tests/unittest/FontCollectionItemizeTest.cpp
@@ -22,6 +22,7 @@
#include "minikin/FontFamily.h"
#include "minikin/LocaleList.h"
+#include "minikin/MinikinPaint.h"
#include "FontTestUtils.h"
#include "FreeTypeMinikinFontForTest.h"
@@ -43,6 +44,7 @@
const char kLatinItalicFont[] = "Italic.ttf";
const char kZH_HansFont[] = "ZhHans.ttf";
const char kZH_HantFont[] = "ZhHant.ttf";
+const char kAsciiFont[] = "Ascii.ttf";
const char kEmojiXmlFile[] = "emoji.xml";
const char kNoGlyphFont[] = "NoGlyphFont.ttf";
@@ -54,37 +56,48 @@
const char kNoCmapFormat14Font[] = "VariationSelectorTest-Regular.ttf";
// Utility functions for calling itemize function.
-void itemize(const std::shared_ptr<FontCollection>& collection, const char* str, FontStyle style,
- const std::string& localeList, std::vector<FontCollection::Run>* result) {
+std::vector<FontCollection::Run> itemize(const std::shared_ptr<FontCollection>& collection,
+ const char* str, FontStyle style,
+ const std::string& localeList) {
const size_t BUF_SIZE = 256;
uint16_t buf[BUF_SIZE];
size_t len;
- result->clear();
ParseUnicode(buf, BUF_SIZE, str, &len, NULL);
const uint32_t localeListId = registerLocaleList(localeList);
- MinikinPaint paint(collection);
- paint.fontStyle = style;
- paint.localeListId = localeListId;
- collection->itemize(buf, len, paint, result);
+ auto result = collection->itemize(U16StringPiece(buf, len), style, localeListId,
+ FamilyVariant::DEFAULT);
+
+ // Check the same result has returned by calling with maxRun.
+ for (uint32_t runMax = 1; runMax <= result.size(); runMax++) {
+ auto resultWithRunMax = collection->itemize(U16StringPiece(buf, len), style, localeListId,
+ FamilyVariant::DEFAULT, runMax);
+ EXPECT_EQ(runMax, resultWithRunMax.size());
+ for (uint32_t i = 0; i < runMax; ++i) {
+ EXPECT_EQ(result[i].start, resultWithRunMax[i].start);
+ EXPECT_EQ(result[i].end, resultWithRunMax[i].end);
+ EXPECT_EQ(result[i].fakedFont, resultWithRunMax[i].fakedFont);
+ }
+ }
+ return result;
}
// Overloaded version for default font style.
-void itemize(const std::shared_ptr<FontCollection>& collection, const char* str,
- const std::string& localeList, std::vector<FontCollection::Run>* result) {
- itemize(collection, str, FontStyle(), localeList, result);
+std::vector<FontCollection::Run> itemize(const std::shared_ptr<FontCollection>& collection,
+ const char* str, const std::string& localeList) {
+ return itemize(collection, str, FontStyle(), localeList);
}
// Overloaded version for empty locale list id.
-void itemize(const std::shared_ptr<FontCollection>& collection, const char* str, FontStyle style,
- std::vector<FontCollection::Run>* result) {
- itemize(collection, str, style, "", result);
+std::vector<FontCollection::Run> itemize(const std::shared_ptr<FontCollection>& collection,
+ const char* str, FontStyle style) {
+ return itemize(collection, str, style, "");
}
// Overloaded version for default font style and empty locale list id.
-void itemize(const std::shared_ptr<FontCollection>& collection, const char* str,
- std::vector<FontCollection::Run>* result) {
- itemize(collection, str, FontStyle(), "", result);
+std::vector<FontCollection::Run> itemize(const std::shared_ptr<FontCollection>& collection,
+ const char* str) {
+ return itemize(collection, str, FontStyle(), "");
}
// Utility function to obtain font path associated with run.
@@ -101,14 +114,13 @@
TEST(FontCollectionItemizeTest, itemize_latin) {
auto collection = buildFontCollectionFromXml(kItemizeFontXml);
- std::vector<FontCollection::Run> runs;
const FontStyle kRegularStyle = FontStyle();
const FontStyle kItalicStyle = FontStyle(FontStyle::Slant::ITALIC);
const FontStyle kBoldStyle = FontStyle(FontStyle::Weight::BOLD);
const FontStyle kBoldItalicStyle = FontStyle(FontStyle::Weight::BOLD, FontStyle::Slant::ITALIC);
- itemize(collection, "'a' 'b' 'c' 'd' 'e'", kRegularStyle, &runs);
+ auto runs = itemize(collection, "'a' 'b' 'c' 'd' 'e'", kRegularStyle);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(5, runs[0].end);
@@ -116,7 +128,7 @@
EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());
EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());
- itemize(collection, "'a' 'b' 'c' 'd' 'e'", kItalicStyle, &runs);
+ runs = itemize(collection, "'a' 'b' 'c' 'd' 'e'", kItalicStyle);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(5, runs[0].end);
@@ -124,7 +136,7 @@
EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());
EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());
- itemize(collection, "'a' 'b' 'c' 'd' 'e'", kBoldStyle, &runs);
+ runs = itemize(collection, "'a' 'b' 'c' 'd' 'e'", kBoldStyle);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(5, runs[0].end);
@@ -132,7 +144,7 @@
EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());
EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());
- itemize(collection, "'a' 'b' 'c' 'd' 'e'", kBoldItalicStyle, &runs);
+ runs = itemize(collection, "'a' 'b' 'c' 'd' 'e'", kBoldItalicStyle);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(5, runs[0].end);
@@ -142,7 +154,7 @@
// Continue if the specific characters (e.g. hyphen, comma, etc.) is
// followed.
- itemize(collection, "'a' ',' '-' 'd' '!'", kRegularStyle, &runs);
+ runs = itemize(collection, "'a' ',' '-' 'd' '!'", kRegularStyle);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(5, runs[0].end);
@@ -150,7 +162,7 @@
EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());
EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());
- itemize(collection, "'a' ',' '-' 'd' '!'", kRegularStyle, &runs);
+ runs = itemize(collection, "'a' ',' '-' 'd' '!'", kRegularStyle);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(5, runs[0].end);
@@ -160,7 +172,7 @@
// U+0301 (COMBINING ACUTE ACCENT) must be in the same run with preceding
// chars if the font supports it.
- itemize(collection, "'a' U+0301", kRegularStyle, &runs);
+ runs = itemize(collection, "'a' U+0301", kRegularStyle);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
@@ -173,36 +185,35 @@
// The regular font and the Cherokee font both support U+0301 (COMBINING ACUTE ACCENT). Since
// it's a combining mark, it should come from whatever font the base character comes from.
auto collection = buildFontCollectionFromXml(kItemizeFontXml);
- std::vector<FontCollection::Run> runs;
- itemize(collection, "'a' U+0301", &runs);
+ auto runs = itemize(collection, "'a' U+0301");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
EXPECT_EQ(kLatinFont, getFontName(runs[0]));
// CHEROKEE LETTER A, COMBINING ACUTE ACCENT
- itemize(collection, "U+13A0 U+0301", &runs);
+ runs = itemize(collection, "U+13A0 U+0301");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
EXPECT_EQ(kCherokeeFont, getFontName(runs[0]));
// CHEROKEE LETTER A, COMBINING ACUTE ACCENT, COMBINING ACUTE ACCENT
- itemize(collection, "U+13A0 U+0301 U+0301", &runs);
+ runs = itemize(collection, "U+13A0 U+0301 U+0301");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(3, runs[0].end);
EXPECT_EQ(kCherokeeFont, getFontName(runs[0]));
- itemize(collection, "U+0301", &runs);
+ runs = itemize(collection, "U+0301");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(1, runs[0].end);
EXPECT_EQ(kLatinFont, getFontName(runs[0]));
// COMBINING ACUTE ACCENT, CHEROKEE LETTER A, COMBINING ACUTE ACCENT
- itemize(collection, "U+0301 U+13A0 U+0301", &runs);
+ runs = itemize(collection, "U+0301 U+13A0 U+0301");
ASSERT_EQ(2U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(1, runs[0].end);
@@ -214,9 +225,8 @@
TEST(FontCollectionItemizeTest, itemize_emoji) {
auto collection = buildFontCollectionFromXml(kItemizeFontXml);
- std::vector<FontCollection::Run> runs;
- itemize(collection, "U+1F469 U+1F467", &runs);
+ auto runs = itemize(collection, "U+1F469 U+1F467");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(4, runs[0].end);
@@ -226,7 +236,7 @@
// U+20E3(COMBINING ENCLOSING KEYCAP) must be in the same run with preceding
// character if the font supports.
- itemize(collection, "'0' U+20E3", &runs);
+ runs = itemize(collection, "'0' U+20E3");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
@@ -234,7 +244,7 @@
EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());
EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());
- itemize(collection, "U+1F470 U+20E3", &runs);
+ runs = itemize(collection, "U+1F470 U+20E3");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(3, runs[0].end);
@@ -242,7 +252,7 @@
EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());
EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());
- itemize(collection, "U+242EE U+1F470 U+20E3", &runs);
+ runs = itemize(collection, "U+242EE U+1F470 U+20E3");
ASSERT_EQ(2U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
@@ -258,7 +268,7 @@
// Currently there is no fonts which has a glyph for 'a' + U+20E3, so they
// are splitted into two.
- itemize(collection, "'a' U+20E3", &runs);
+ runs = itemize(collection, "'a' U+20E3");
ASSERT_EQ(2U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(1, runs[0].end);
@@ -275,10 +285,9 @@
TEST(FontCollectionItemizeTest, itemize_non_latin) {
auto collection = buildFontCollectionFromXml(kItemizeFontXml);
- std::vector<FontCollection::Run> runs;
// All Japanese Hiragana characters.
- itemize(collection, "U+3042 U+3044 U+3046 U+3048 U+304A", "ja-JP", &runs);
+ auto runs = itemize(collection, "U+3042 U+3044 U+3046 U+3048 U+304A", "ja-JP");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(5, runs[0].end);
@@ -287,7 +296,7 @@
EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());
// All Korean Hangul characters.
- itemize(collection, "U+B300 U+D55C U+BBFC U+AD6D", "en-US", &runs);
+ runs = itemize(collection, "U+B300 U+D55C U+BBFC U+AD6D", "en-US");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(4, runs[0].end);
@@ -297,7 +306,7 @@
// All Han characters ja, zh-Hans font having.
// Japanese font should be selected if the specified language is Japanese.
- itemize(collection, "U+81ED U+82B1 U+5FCD", "ja-JP", &runs);
+ runs = itemize(collection, "U+81ED U+82B1 U+5FCD", "ja-JP");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(3, runs[0].end);
@@ -307,7 +316,7 @@
// Simplified Chinese font should be selected if the specified language is Simplified
// Chinese.
- itemize(collection, "U+81ED U+82B1 U+5FCD", "zh-Hans", &runs);
+ runs = itemize(collection, "U+81ED U+82B1 U+5FCD", "zh-Hans");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(3, runs[0].end);
@@ -317,7 +326,7 @@
// Fallbacks to other fonts if there is no glyph in the specified language's
// font. There is no character U+4F60 in Japanese.
- itemize(collection, "U+81ED U+4F60 U+5FCD", "ja-JP", &runs);
+ runs = itemize(collection, "U+81ED U+4F60 U+5FCD", "ja-JP");
ASSERT_EQ(3U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(1, runs[0].end);
@@ -338,7 +347,7 @@
EXPECT_FALSE(runs[2].fakedFont.fakery.isFakeItalic());
// Tone mark.
- itemize(collection, "U+4444 U+302D", "", &runs);
+ runs = itemize(collection, "U+4444 U+302D", "");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
@@ -349,7 +358,7 @@
// Both zh-Hant and ja fonts support U+242EE, but zh-Hans doesn't.
// Here, ja and zh-Hant font should have the same score but ja should be selected since it is
// listed before zh-Hant.
- itemize(collection, "U+242EE", "zh-Hans", &runs);
+ runs = itemize(collection, "U+242EE", "zh-Hans");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
@@ -360,9 +369,8 @@
TEST(FontCollectionItemizeTest, itemize_mixed) {
auto collection = buildFontCollectionFromXml(kItemizeFontXml);
- std::vector<FontCollection::Run> runs;
- itemize(collection, "'a' U+4F60 'b' U+4F60 'c'", "en-US", &runs);
+ auto runs = itemize(collection, "'a' U+4F60 'b' U+4F60 'c'", "en-US");
ASSERT_EQ(5U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(1, runs[0].end);
@@ -397,7 +405,6 @@
TEST(FontCollectionItemizeTest, itemize_variationSelector) {
auto collection = buildFontCollectionFromXml(kItemizeFontXml);
- std::vector<FontCollection::Run> runs;
// A glyph for U+4FAE is provided by both Japanese font and Simplified
// Chinese font. Also a glyph for U+242EE is provided by both Japanese and
@@ -406,19 +413,19 @@
// U+4FAE is available in both zh_Hans and ja font, but U+4FAE,U+FE00 is
// only available in ja font.
- itemize(collection, "U+4FAE", "zh-Hans", &runs);
+ auto runs = itemize(collection, "U+4FAE", "zh-Hans");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(1, runs[0].end);
EXPECT_EQ(kZH_HansFont, getFontName(runs[0]));
- itemize(collection, "U+4FAE U+FE00", "zh-Hans", &runs);
+ runs = itemize(collection, "U+4FAE U+FE00", "zh-Hans");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
EXPECT_EQ(kJAFont, getFontName(runs[0]));
- itemize(collection, "U+4FAE U+4FAE U+FE00", "zh-Hans", &runs);
+ runs = itemize(collection, "U+4FAE U+4FAE U+FE00", "zh-Hans");
ASSERT_EQ(2U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(1, runs[0].end);
@@ -427,7 +434,7 @@
EXPECT_EQ(3, runs[1].end);
EXPECT_EQ(kJAFont, getFontName(runs[1]));
- itemize(collection, "U+4FAE U+4FAE U+FE00 U+4FAE", "zh-Hans", &runs);
+ runs = itemize(collection, "U+4FAE U+4FAE U+FE00 U+4FAE", "zh-Hans");
ASSERT_EQ(3U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(1, runs[0].end);
@@ -440,14 +447,14 @@
EXPECT_EQ(kZH_HansFont, getFontName(runs[2]));
// Validation selector after validation selector.
- itemize(collection, "U+4FAE U+FE00 U+FE00", "zh-Hans", &runs);
+ runs = itemize(collection, "U+4FAE U+FE00 U+FE00", "zh-Hans");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(3, runs[0].end);
EXPECT_EQ(kJAFont, getFontName(runs[0]));
// No font supports U+242EE U+FE0E.
- itemize(collection, "U+4FAE U+FE0E", "zh-Hans", &runs);
+ runs = itemize(collection, "U+4FAE U+FE0E", "zh-Hans");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
@@ -456,19 +463,19 @@
// Surrogate pairs handling.
// U+242EE is available in ja font and zh_Hant font.
// U+242EE U+FE00 is available only in ja font.
- itemize(collection, "U+242EE", "zh-Hant", &runs);
+ runs = itemize(collection, "U+242EE", "zh-Hant");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
EXPECT_EQ(kZH_HantFont, getFontName(runs[0]));
- itemize(collection, "U+242EE U+FE00", "zh-Hant", &runs);
+ runs = itemize(collection, "U+242EE U+FE00", "zh-Hant");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(3, runs[0].end);
EXPECT_EQ(kJAFont, getFontName(runs[0]));
- itemize(collection, "U+242EE U+242EE U+FE00", "zh-Hant", &runs);
+ runs = itemize(collection, "U+242EE U+242EE U+FE00", "zh-Hant");
ASSERT_EQ(2U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
@@ -477,7 +484,7 @@
EXPECT_EQ(5, runs[1].end);
EXPECT_EQ(kJAFont, getFontName(runs[1]));
- itemize(collection, "U+242EE U+242EE U+FE00 U+242EE", "zh-Hant", &runs);
+ runs = itemize(collection, "U+242EE U+242EE U+FE00 U+242EE", "zh-Hant");
ASSERT_EQ(3U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
@@ -490,27 +497,27 @@
EXPECT_EQ(kZH_HantFont, getFontName(runs[2]));
// Validation selector after validation selector.
- itemize(collection, "U+242EE U+FE00 U+FE00", "zh-Hans", &runs);
+ runs = itemize(collection, "U+242EE U+FE00 U+FE00", "zh-Hans");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(4, runs[0].end);
EXPECT_EQ(kJAFont, getFontName(runs[0]));
// No font supports U+242EE U+FE0E
- itemize(collection, "U+242EE U+FE0E", "zh-Hant", &runs);
+ runs = itemize(collection, "U+242EE U+FE0E", "zh-Hant");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(3, runs[0].end);
EXPECT_EQ(kZH_HantFont, getFontName(runs[0]));
// Isolated variation selector supplement.
- itemize(collection, "U+FE00", "", &runs);
+ runs = itemize(collection, "U+FE00", "");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(1, runs[0].end);
EXPECT_TRUE(runs[0].fakedFont.font == nullptr || kLatinFont == getFontName(runs[0]));
- itemize(collection, "U+FE00", "zh-Hant", &runs);
+ runs = itemize(collection, "U+FE00", "zh-Hant");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(1, runs[0].end);
@@ -518,14 +525,14 @@
// First font family (Regular.ttf) supports U+203C but doesn't support U+203C U+FE0F.
// Emoji.ttf font supports U+203C U+FE0F. Emoji.ttf should be selected.
- itemize(collection, "U+203C U+FE0F", "zh-Hant", &runs);
+ runs = itemize(collection, "U+203C U+FE0F", "zh-Hant");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
EXPECT_EQ(kEmojiFont, getFontName(runs[0]));
// First font family (Regular.ttf) supports U+203C U+FE0E.
- itemize(collection, "U+203C U+FE0E", "zh-Hant", &runs);
+ runs = itemize(collection, "U+203C U+FE0E", "zh-Hant");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
@@ -534,7 +541,6 @@
TEST(FontCollectionItemizeTest, itemize_variationSelectorSupplement) {
auto collection = buildFontCollectionFromXml(kItemizeFontXml);
- std::vector<FontCollection::Run> runs;
// A glyph for U+845B is provided by both Japanese font and Simplified
// Chinese font. Also a glyph for U+242EE is provided by both Japanese and
@@ -543,19 +549,19 @@
// U+845B is available in both zh_Hans and ja font, but U+845B,U+E0100 is
// only available in ja font.
- itemize(collection, "U+845B", "zh-Hans", &runs);
+ auto runs = itemize(collection, "U+845B", "zh-Hans");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(1, runs[0].end);
EXPECT_EQ(kZH_HansFont, getFontName(runs[0]));
- itemize(collection, "U+845B U+E0100", "zh-Hans", &runs);
+ runs = itemize(collection, "U+845B U+E0100", "zh-Hans");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(3, runs[0].end);
EXPECT_EQ(kJAFont, getFontName(runs[0]));
- itemize(collection, "U+845B U+845B U+E0100", "zh-Hans", &runs);
+ runs = itemize(collection, "U+845B U+845B U+E0100", "zh-Hans");
ASSERT_EQ(2U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(1, runs[0].end);
@@ -564,7 +570,7 @@
EXPECT_EQ(4, runs[1].end);
EXPECT_EQ(kJAFont, getFontName(runs[1]));
- itemize(collection, "U+845B U+845B U+E0100 U+845B", "zh-Hans", &runs);
+ runs = itemize(collection, "U+845B U+845B U+E0100 U+845B", "zh-Hans");
ASSERT_EQ(3U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(1, runs[0].end);
@@ -577,14 +583,14 @@
EXPECT_EQ(kZH_HansFont, getFontName(runs[2]));
// Validation selector after validation selector.
- itemize(collection, "U+845B U+E0100 U+E0100", "zh-Hans", &runs);
+ runs = itemize(collection, "U+845B U+E0100 U+E0100", "zh-Hans");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(5, runs[0].end);
EXPECT_EQ(kJAFont, getFontName(runs[0]));
// No font supports U+845B U+E01E0.
- itemize(collection, "U+845B U+E01E0", "zh-Hans", &runs);
+ runs = itemize(collection, "U+845B U+E01E0", "zh-Hans");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(3, runs[0].end);
@@ -594,19 +600,19 @@
// Surrogate pairs handling.
// U+242EE is available in ja font and zh_Hant font.
// U+242EE U+E0100 is available only in ja font.
- itemize(collection, "U+242EE", "zh-Hant", &runs);
+ runs = itemize(collection, "U+242EE", "zh-Hant");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
EXPECT_EQ(kZH_HantFont, getFontName(runs[0]));
- itemize(collection, "U+242EE U+E0101", "zh-Hant", &runs);
+ runs = itemize(collection, "U+242EE U+E0101", "zh-Hant");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(4, runs[0].end);
EXPECT_EQ(kJAFont, getFontName(runs[0]));
- itemize(collection, "U+242EE U+242EE U+E0101", "zh-Hant", &runs);
+ runs = itemize(collection, "U+242EE U+242EE U+E0101", "zh-Hant");
ASSERT_EQ(2U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
@@ -615,7 +621,7 @@
EXPECT_EQ(6, runs[1].end);
EXPECT_EQ(kJAFont, getFontName(runs[1]));
- itemize(collection, "U+242EE U+242EE U+E0101 U+242EE", "zh-Hant", &runs);
+ runs = itemize(collection, "U+242EE U+242EE U+E0101 U+242EE", "zh-Hant");
ASSERT_EQ(3U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
@@ -628,27 +634,27 @@
EXPECT_EQ(kZH_HantFont, getFontName(runs[2]));
// Validation selector after validation selector.
- itemize(collection, "U+242EE U+E0100 U+E0100", "zh-Hant", &runs);
+ runs = itemize(collection, "U+242EE U+E0100 U+E0100", "zh-Hant");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(6, runs[0].end);
EXPECT_EQ(kJAFont, getFontName(runs[0]));
// No font supports U+242EE U+E01E0.
- itemize(collection, "U+242EE U+E01E0", "zh-Hant", &runs);
+ runs = itemize(collection, "U+242EE U+E01E0", "zh-Hant");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(4, runs[0].end);
EXPECT_EQ(kZH_HantFont, getFontName(runs[0]));
// Isolated variation selector supplement.
- itemize(collection, "U+E0100", "", &runs);
+ runs = itemize(collection, "U+E0100", "");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
EXPECT_TRUE(runs[0].fakedFont.font == nullptr || kLatinFont == getFontName(runs[0]));
- itemize(collection, "U+E0100", "zh-Hant", &runs);
+ runs = itemize(collection, "U+E0100", "zh-Hant");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
@@ -657,31 +663,29 @@
TEST(FontCollectionItemizeTest, itemize_no_crash) {
auto collection = buildFontCollectionFromXml(kItemizeFontXml);
- std::vector<FontCollection::Run> runs;
// Broken Surrogate pairs. Check only not crashing.
- itemize(collection, "'a' U+D83D 'a'", &runs);
- itemize(collection, "'a' U+DC69 'a'", &runs);
- itemize(collection, "'a' U+D83D U+D83D 'a'", &runs);
- itemize(collection, "'a' U+DC69 U+DC69 'a'", &runs);
+ auto runs = itemize(collection, "'a' U+D83D 'a'");
+ runs = itemize(collection, "'a' U+DC69 'a'");
+ runs = itemize(collection, "'a' U+D83D U+D83D 'a'");
+ runs = itemize(collection, "'a' U+DC69 U+DC69 'a'");
// Isolated variation selector. Check only not crashing.
- itemize(collection, "U+FE00 U+FE00", &runs);
- itemize(collection, "U+E0100 U+E0100", &runs);
- itemize(collection, "U+FE00 U+E0100", &runs);
- itemize(collection, "U+E0100 U+FE00", &runs);
+ runs = itemize(collection, "U+FE00 U+FE00");
+ runs = itemize(collection, "U+E0100 U+E0100");
+ runs = itemize(collection, "U+FE00 U+E0100");
+ runs = itemize(collection, "U+E0100 U+FE00");
// Tone mark only. Check only not crashing.
- itemize(collection, "U+302D", &runs);
- itemize(collection, "U+302D U+302D", &runs);
+ runs = itemize(collection, "U+302D");
+ runs = itemize(collection, "U+302D U+302D");
// Tone mark and variation selector mixed. Check only not crashing.
- itemize(collection, "U+FE00 U+302D U+E0100", &runs);
+ runs = itemize(collection, "U+FE00 U+302D U+E0100");
}
TEST(FontCollectionItemizeTest, itemize_fakery) {
auto collection = buildFontCollectionFromXml(kItemizeFontXml);
- std::vector<FontCollection::Run> runs;
FontStyle kBoldStyle(FontStyle::Weight::BOLD);
FontStyle kItalicStyle(FontStyle::Slant::ITALIC);
@@ -691,7 +695,7 @@
// the differences between desired and actual font style.
// All Japanese Hiragana characters.
- itemize(collection, "U+3042 U+3044 U+3046 U+3048 U+304A", kBoldStyle, "ja-JP", &runs);
+ auto runs = itemize(collection, "U+3042 U+3044 U+3046 U+3048 U+304A", kBoldStyle, "ja-JP");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(5, runs[0].end);
@@ -700,7 +704,7 @@
EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());
// All Japanese Hiragana characters.
- itemize(collection, "U+3042 U+3044 U+3046 U+3048 U+304A", kItalicStyle, "ja-JP", &runs);
+ runs = itemize(collection, "U+3042 U+3044 U+3046 U+3048 U+304A", kItalicStyle, "ja-JP");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(5, runs[0].end);
@@ -709,7 +713,7 @@
EXPECT_TRUE(runs[0].fakedFont.fakery.isFakeItalic());
// All Japanese Hiragana characters.
- itemize(collection, "U+3042 U+3044 U+3046 U+3048 U+304A", kBoldItalicStyle, "ja-JP", &runs);
+ runs = itemize(collection, "U+3042 U+3044 U+3046 U+3048 U+304A", kBoldItalicStyle, "ja-JP");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(5, runs[0].end);
@@ -730,9 +734,7 @@
std::shared_ptr<FontCollection> collection(new FontCollection(families));
- std::vector<FontCollection::Run> runs;
-
- itemize(collection, "U+717D U+FE02", &runs);
+ auto runs = itemize(collection, "U+717D U+FE02");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
@@ -741,63 +743,62 @@
TEST(FontCollectionItemizeTest, itemize_format_chars) {
auto collection = buildFontCollectionFromXml(kItemizeFontXml);
- std::vector<FontCollection::Run> runs;
- itemize(collection, "'a' U+061C 'b'", &runs);
+ auto runs = itemize(collection, "'a' U+061C 'b'");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(3, runs[0].end);
EXPECT_EQ(kLatinFont, getFontName(runs[0]));
- itemize(collection, "'a' U+200D 'b'", &runs);
+ runs = itemize(collection, "'a' U+200D 'b'");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(3, runs[0].end);
EXPECT_EQ(kLatinFont, getFontName(runs[0]));
- itemize(collection, "U+3042 U+061C U+3042", &runs);
+ runs = itemize(collection, "U+3042 U+061C U+3042");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(3, runs[0].end);
EXPECT_EQ(kJAFont, getFontName(runs[0]));
- itemize(collection, "U+061C 'b'", &runs);
+ runs = itemize(collection, "U+061C 'b'");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
EXPECT_EQ(kLatinFont, getFontName(runs[0]));
- itemize(collection, "U+061C U+3042", &runs);
+ runs = itemize(collection, "U+061C U+3042");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
EXPECT_EQ(kJAFont, getFontName(runs[0]));
- itemize(collection, "U+061C", &runs);
+ runs = itemize(collection, "U+061C");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(1, runs[0].end);
EXPECT_EQ(kLatinFont, getFontName(runs[0]));
- itemize(collection, "U+061C U+061C U+061C", &runs);
+ runs = itemize(collection, "U+061C U+061C U+061C");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(3, runs[0].end);
EXPECT_EQ(kLatinFont, getFontName(runs[0]));
- itemize(collection, "U+200D U+20E3", &runs);
+ runs = itemize(collection, "U+200D U+20E3");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
EXPECT_EQ(kEmojiFont, getFontName(runs[0]));
- itemize(collection, "U+200D", &runs);
+ runs = itemize(collection, "U+200D");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(1, runs[0].end);
EXPECT_EQ(kLatinFont, getFontName(runs[0]));
- itemize(collection, "U+20E3", &runs);
+ runs = itemize(collection, "U+20E3");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(1, runs[0].end);
@@ -927,8 +928,9 @@
std::make_shared<FreeTypeMinikinFontForTest>(getTestFontPath(kNoGlyphFont));
std::vector<Font> fonts;
fonts.push_back(Font::Builder(firstFamilyMinikinFont).build());
- auto firstFamily = std::make_shared<FontFamily>(
- registerLocaleList("und"), FontFamily::Variant::DEFAULT, std::move(fonts));
+ auto firstFamily =
+ std::make_shared<FontFamily>(registerLocaleList("und"), FamilyVariant::DEFAULT,
+ std::move(fonts), false /* isCustomFallback */);
families.push_back(firstFamily);
// Prepare font families
@@ -941,16 +943,15 @@
std::make_shared<FreeTypeMinikinFontForTest>(getTestFontPath(kJAFont));
std::vector<Font> fonts;
fonts.push_back(Font::Builder(minikinFont).build());
- auto family =
- std::make_shared<FontFamily>(registerLocaleList(testCase.fontLocales[i]),
- FontFamily::Variant::DEFAULT, std::move(fonts));
+ auto family = std::make_shared<FontFamily>(registerLocaleList(testCase.fontLocales[i]),
+ FamilyVariant::DEFAULT, std::move(fonts),
+ false /* isCustomFallback */);
families.push_back(family);
fontLocaleIdxMap.insert(std::make_pair(minikinFont.get(), i));
}
std::shared_ptr<FontCollection> collection(new FontCollection(families));
// Do itemize
- std::vector<FontCollection::Run> runs;
- itemize(collection, "U+9AA8", testCase.userPreferredLocale, &runs);
+ auto runs = itemize(collection, "U+9AA8", testCase.userPreferredLocale);
ASSERT_EQ(1U, runs.size());
ASSERT_NE(nullptr, runs[0].fakedFont.font);
@@ -1266,8 +1267,7 @@
SCOPED_TRACE("Test for \"" + testCase.testString + "\" with locales " +
testCase.requestedLocales);
- std::vector<FontCollection::Run> runs;
- itemize(collection, testCase.testString.c_str(), testCase.requestedLocales, &runs);
+ auto runs = itemize(collection, testCase.testString.c_str(), testCase.requestedLocales);
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(testCase.expectedFont, getFontName(runs[0]));
}
@@ -1275,11 +1275,10 @@
TEST(FontCollectionItemizeTest, itemize_emojiSelection_withFE0E) {
auto collection = buildFontCollectionFromXml(kEmojiXmlFile);
- std::vector<FontCollection::Run> runs;
// U+00A9 is a text default emoji which is only available in TextEmojiFont.ttf.
// TextEmojiFont.ttf should be selected.
- itemize(collection, "U+00A9 U+FE0E", &runs);
+ auto runs = itemize(collection, "U+00A9 U+FE0E");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
@@ -1287,7 +1286,7 @@
// U+00A9 is a text default emoji which is only available in ColorEmojiFont.ttf.
// ColorEmojiFont.ttf should be selected.
- itemize(collection, "U+00AE U+FE0E", &runs);
+ runs = itemize(collection, "U+00AE U+FE0E");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
@@ -1296,7 +1295,7 @@
// U+203C is a text default emoji which is available in both TextEmojiFont.ttf and
// ColorEmojiFont.ttf. TextEmojiFont.ttf should be selected.
- itemize(collection, "U+203C U+FE0E", &runs);
+ runs = itemize(collection, "U+203C U+FE0E");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
@@ -1304,7 +1303,7 @@
// U+2049 is a text default emoji which is not available either TextEmojiFont.ttf or
// ColorEmojiFont.ttf. No font should be selected.
- itemize(collection, "U+2049 U+FE0E", &runs);
+ runs = itemize(collection, "U+2049 U+FE0E");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
@@ -1312,7 +1311,7 @@
// U+231A is a emoji default emoji which is available only in TextEmojifFont.
// TextEmojiFont.ttf sohuld be selected.
- itemize(collection, "U+231A U+FE0E", &runs);
+ runs = itemize(collection, "U+231A U+FE0E");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
@@ -1320,7 +1319,7 @@
// U+231B is a emoji default emoji which is available only in ColorEmojiFont.ttf.
// ColorEmojiFont.ttf should be selected.
- itemize(collection, "U+231B U+FE0E", &runs);
+ runs = itemize(collection, "U+231B U+FE0E");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
@@ -1330,7 +1329,7 @@
// U+23E9 is a emoji default emoji which is available in both TextEmojiFont.ttf and
// ColorEmojiFont.ttf. TextEmojiFont.ttf should be selected even if U+23E9 is emoji default
// emoji since U+FE0E is appended.
- itemize(collection, "U+23E9 U+FE0E", &runs);
+ runs = itemize(collection, "U+23E9 U+FE0E");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
@@ -1338,7 +1337,7 @@
// U+23EA is a emoji default emoji but which is not available in either TextEmojiFont.ttf or
// ColorEmojiFont.ttf. No font should be selected.
- itemize(collection, "U+23EA U+FE0E", &runs);
+ runs = itemize(collection, "U+23EA U+FE0E");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
@@ -1346,7 +1345,7 @@
// U+26FA U+FE0E is specified but ColorTextMixedEmojiFont has a variation sequence U+26F9 U+FE0F
// in its cmap, so ColorTextMixedEmojiFont should be selected instaed of ColorEmojiFont.
- itemize(collection, "U+26FA U+FE0E", &runs);
+ runs = itemize(collection, "U+26FA U+FE0E");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
@@ -1355,11 +1354,10 @@
TEST(FontCollectionItemizeTest, itemize_emojiSelection_withFE0F) {
auto collection = buildFontCollectionFromXml(kEmojiXmlFile);
- std::vector<FontCollection::Run> runs;
// U+00A9 is a text default emoji which is available only in TextEmojiFont.ttf.
// TextEmojiFont.ttf shoudl be selected.
- itemize(collection, "U+00A9 U+FE0F", &runs);
+ auto runs = itemize(collection, "U+00A9 U+FE0F");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
@@ -1368,7 +1366,7 @@
// U+00AE is a text default emoji which is available only in ColorEmojiFont.ttf.
// ColorEmojiFont.ttf should be selected.
- itemize(collection, "U+00AE U+FE0F", &runs);
+ runs = itemize(collection, "U+00AE U+FE0F");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
@@ -1377,7 +1375,7 @@
// U+203C is a text default emoji which is available in both TextEmojiFont.ttf and
// ColorEmojiFont.ttf. ColorEmojiFont.ttf should be selected even if U+203C is a text default
// emoji since U+FF0F is appended.
- itemize(collection, "U+203C U+FE0F", &runs);
+ runs = itemize(collection, "U+203C U+FE0F");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
@@ -1385,7 +1383,7 @@
// U+2049 is a text default emoji which is not available in either TextEmojiFont.ttf or
// ColorEmojiFont.ttf. No font should be selected.
- itemize(collection, "U+2049 U+FE0F", &runs);
+ runs = itemize(collection, "U+2049 U+FE0F");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
@@ -1393,7 +1391,7 @@
// U+231A is a emoji default emoji which is available only in TextEmojiFont.ttf.
// TextEmojiFont.ttf should be selected.
- itemize(collection, "U+231A U+FE0F", &runs);
+ runs = itemize(collection, "U+231A U+FE0F");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
@@ -1402,7 +1400,7 @@
// U+231B is a emoji default emoji which is available only in ColorEmojiFont.ttf.
// ColorEmojiFont.ttf should be selected.
- itemize(collection, "U+231B U+FE0F", &runs);
+ runs = itemize(collection, "U+231B U+FE0F");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
@@ -1410,7 +1408,7 @@
// U+23E9 is a emoji default emoji which is available in both TextEmojiFont.ttf and
// ColorEmojiFont.ttf. ColorEmojiFont.ttf should be selected.
- itemize(collection, "U+23E9 U+FE0F", &runs);
+ runs = itemize(collection, "U+23E9 U+FE0F");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
@@ -1418,7 +1416,7 @@
// U+23EA is a emoji default emoji which is not available in either TextEmojiFont.ttf or
// ColorEmojiFont.ttf. No font should be selected.
- itemize(collection, "U+23EA U+FE0F", &runs);
+ runs = itemize(collection, "U+23EA U+FE0F");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
@@ -1426,7 +1424,7 @@
// U+26F9 U+FE0F is specified but ColorTextMixedEmojiFont has a variation sequence U+26F9 U+FE0F
// in its cmap, so ColorTextMixedEmojiFont should be selected instaed of ColorEmojiFont.
- itemize(collection, "U+26F9 U+FE0F", &runs);
+ runs = itemize(collection, "U+26F9 U+FE0F");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
@@ -1435,24 +1433,23 @@
TEST(FontCollectionItemizeTest, itemize_emojiSelection_with_skinTone) {
auto collection = buildFontCollectionFromXml(kEmojiXmlFile);
- std::vector<FontCollection::Run> runs;
// TextEmoji font is selected since it is listed before ColorEmoji font.
- itemize(collection, "U+261D", &runs);
+ auto runs = itemize(collection, "U+261D");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(1, runs[0].end);
EXPECT_EQ(kTextEmojiFont, getFontName(runs[0]));
// If skin tone is specified, it should be colored.
- itemize(collection, "U+261D U+1F3FD", &runs);
+ runs = itemize(collection, "U+261D U+1F3FD");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(3, runs[0].end);
EXPECT_EQ(kColorEmojiFont, getFontName(runs[0]));
// Still color font is selected if an emoji variation selector is specified.
- itemize(collection, "U+261D U+FE0F U+1F3FD", &runs);
+ runs = itemize(collection, "U+261D U+FE0F U+1F3FD");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(4, runs[0].end);
@@ -1460,7 +1457,7 @@
// Text font should be selected if a text variation selector is specified and skin tone is
// rendered by itself.
- itemize(collection, "U+261D U+FE0E U+1F3FD", &runs);
+ runs = itemize(collection, "U+261D U+FE0E U+1F3FD");
ASSERT_EQ(2U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
@@ -1472,16 +1469,15 @@
TEST(FontCollectionItemizeTest, itemize_PrivateUseArea) {
auto collection = buildFontCollectionFromXml(kEmojiXmlFile);
- std::vector<FontCollection::Run> runs;
// Should not set nullptr to the result run. (Issue 26808815)
- itemize(collection, "U+FEE10", &runs);
+ auto runs = itemize(collection, "U+FEE10");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(2, runs[0].end);
EXPECT_EQ(kNoGlyphFont, getFontName(runs[0]));
- itemize(collection, "U+FEE40 U+FE4C5", &runs);
+ runs = itemize(collection, "U+FEE40 U+FE4C5");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(4, runs[0].end);
@@ -1490,21 +1486,20 @@
TEST(FontCollectionItemizeTest, itemize_genderBalancedEmoji) {
auto collection = buildFontCollectionFromXml(kEmojiXmlFile);
- std::vector<FontCollection::Run> runs;
- itemize(collection, "U+1F469 U+200D U+1F373", &runs);
+ auto runs = itemize(collection, "U+1F469 U+200D U+1F373");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(5, runs[0].end);
EXPECT_EQ(kColorEmojiFont, getFontName(runs[0]));
- itemize(collection, "U+1F469 U+200D U+2695 U+FE0F", &runs);
+ runs = itemize(collection, "U+1F469 U+200D U+2695 U+FE0F");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(5, runs[0].end);
EXPECT_EQ(kColorEmojiFont, getFontName(runs[0]));
- itemize(collection, "U+1F469 U+200D U+2695", &runs);
+ runs = itemize(collection, "U+1F469 U+200D U+2695");
ASSERT_EQ(1U, runs.size());
EXPECT_EQ(0, runs[0].start);
EXPECT_EQ(4, runs[0].end);
@@ -1525,11 +1520,10 @@
// Both fontA/fontB support U+35A8 but don't support U+35A8 U+E0100. The first font should be
// selected.
- std::vector<FontCollection::Run> runs;
- itemize(collection, "U+35A8 U+E0100", &runs);
+ auto runs = itemize(collection, "U+35A8 U+E0100");
EXPECT_EQ(familyA->getFont(0), runs[0].fakedFont.font);
- itemize(reversedCollection, "U+35A8 U+E0100", &runs);
+ runs = itemize(reversedCollection, "U+35A8 U+E0100");
EXPECT_EQ(familyB->getFont(0), runs[0].fakedFont.font);
}
@@ -1549,11 +1543,10 @@
// Both hasCmapFormat14Font/noCmapFormat14Font support U+5380 but don't support U+5380 U+E0100.
// The first font should be selected.
- std::vector<FontCollection::Run> runs;
- itemize(collection, "U+5380 U+E0100", &runs);
+ auto runs = itemize(collection, "U+5380 U+E0100");
EXPECT_EQ(hasCmapFormat14Family->getFont(0), runs[0].fakedFont.font);
- itemize(reversedCollection, "U+5380 U+E0100", &runs);
+ runs = itemize(reversedCollection, "U+5380 U+E0100");
EXPECT_EQ(noCmapFormat14Family->getFont(0), runs[0].fakedFont.font);
}
@@ -1565,48 +1558,65 @@
std::vector<std::shared_ptr<FontFamily>> families = {dummyFamily, textEmojiFamily,
colorEmojiFamily};
auto collection = std::make_shared<FontCollection>(families);
- std::vector<FontCollection::Run> runs;
// Both textEmojiFamily and colorEmojiFamily supports U+203C and U+23E9.
// U+203C is text default emoji, and U+23E9 is color default emoji.
- itemize(collection, "U+203C", "en-US,en-Zsym", &runs);
+ auto runs = itemize(collection, "U+203C", "en-US,en-Zsym");
EXPECT_EQ(textEmojiFamily->getFont(0), runs[0].fakedFont.font);
- itemize(collection, "U+23E9", "en-US,en-Zsym", &runs);
+ runs = itemize(collection, "U+23E9", "en-US,en-Zsym");
EXPECT_EQ(textEmojiFamily->getFont(0), runs[0].fakedFont.font);
- itemize(collection, "U+203C", "en-US,en-Zsye", &runs);
+ runs = itemize(collection, "U+203C", "en-US,en-Zsye");
EXPECT_EQ(colorEmojiFamily->getFont(0), runs[0].fakedFont.font);
- itemize(collection, "U+23E9", "en-US,en-Zsye", &runs);
+ runs = itemize(collection, "U+23E9", "en-US,en-Zsye");
EXPECT_EQ(colorEmojiFamily->getFont(0), runs[0].fakedFont.font);
- itemize(collection, "U+203C", "ja-Zsym-JP", &runs);
+ runs = itemize(collection, "U+203C", "ja-Zsym-JP");
EXPECT_EQ(textEmojiFamily->getFont(0), runs[0].fakedFont.font);
- itemize(collection, "U+23E9", "ja-Zsym-JP", &runs);
+ runs = itemize(collection, "U+23E9", "ja-Zsym-JP");
EXPECT_EQ(textEmojiFamily->getFont(0), runs[0].fakedFont.font);
- itemize(collection, "U+203C", "ja-Zsye-JP", &runs);
+ runs = itemize(collection, "U+203C", "ja-Zsye-JP");
EXPECT_EQ(colorEmojiFamily->getFont(0), runs[0].fakedFont.font);
- itemize(collection, "U+23E9", "ja-Zsye-JP", &runs);
+ runs = itemize(collection, "U+23E9", "ja-Zsye-JP");
EXPECT_EQ(colorEmojiFamily->getFont(0), runs[0].fakedFont.font);
- itemize(collection, "U+203C", "ja-JP-u-em-text", &runs);
+ runs = itemize(collection, "U+203C", "ja-JP-u-em-text");
EXPECT_EQ(textEmojiFamily->getFont(0), runs[0].fakedFont.font);
- itemize(collection, "U+23E9", "ja-JP-u-em-text", &runs);
+ runs = itemize(collection, "U+23E9", "ja-JP-u-em-text");
EXPECT_EQ(textEmojiFamily->getFont(0), runs[0].fakedFont.font);
- itemize(collection, "U+203C", "ja-JP-u-em-emoji", &runs);
+ runs = itemize(collection, "U+203C", "ja-JP-u-em-emoji");
EXPECT_EQ(colorEmojiFamily->getFont(0), runs[0].fakedFont.font);
- itemize(collection, "U+23E9", "ja-JP-u-em-emoji", &runs);
+ runs = itemize(collection, "U+23E9", "ja-JP-u-em-emoji");
EXPECT_EQ(colorEmojiFamily->getFont(0), runs[0].fakedFont.font);
- itemize(collection, "U+203C", "ja-JP,und-Zsym", &runs);
+ runs = itemize(collection, "U+203C", "ja-JP,und-Zsym");
EXPECT_EQ(textEmojiFamily->getFont(0), runs[0].fakedFont.font);
- itemize(collection, "U+23E9", "ja-JP,und-Zsym", &runs);
+ runs = itemize(collection, "U+23E9", "ja-JP,und-Zsym");
EXPECT_EQ(textEmojiFamily->getFont(0), runs[0].fakedFont.font);
- itemize(collection, "U+203C", "ja-JP,und-Zsye", &runs);
+ runs = itemize(collection, "U+203C", "ja-JP,und-Zsye");
EXPECT_EQ(colorEmojiFamily->getFont(0), runs[0].fakedFont.font);
- itemize(collection, "U+23E9", "ja-JP,und-Zsye", &runs);
+ runs = itemize(collection, "U+23E9", "ja-JP,und-Zsye");
EXPECT_EQ(colorEmojiFamily->getFont(0), runs[0].fakedFont.font);
}
+TEST(FontCollectionItemizeTest, customFallbackTest) {
+ auto firstFamily = buildFontFamily(kNoGlyphFont);
+ auto customFallbackFamily = buildFontFamily(kAsciiFont, "", true /* isCustomFallback */);
+ auto languageFamily = buildFontFamily(kAsciiFont, "ja-JP");
+
+ std::vector<std::shared_ptr<FontFamily>> families = {firstFamily, customFallbackFamily,
+ languageFamily};
+
+ auto collection = std::make_shared<FontCollection>(families);
+
+ auto runs = itemize(collection, "'a'", "");
+ EXPECT_EQ(customFallbackFamily->getFont(0), runs[0].fakedFont.font);
+ runs = itemize(collection, "'a'", "en-US");
+ EXPECT_EQ(customFallbackFamily->getFont(0), runs[0].fakedFont.font);
+ runs = itemize(collection, "'a'", "ja-JP");
+ EXPECT_EQ(customFallbackFamily->getFont(0), runs[0].fakedFont.font);
+}
+
} // namespace minikin
diff --git a/tests/unittest/FontFamilyTest.cpp b/tests/unittest/FontFamilyTest.cpp
index 4eb6bcc..2b70faf 100644
--- a/tests/unittest/FontFamilyTest.cpp
+++ b/tests/unittest/FontFamilyTest.cpp
@@ -108,6 +108,14 @@
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());
+ EXPECT_EQ("ja-Jpan-JP", createLocale("ja-JP-u-lb-bogus").getString());
+
// Emoji subtag is dropped from getString().
EXPECT_EQ("es-Latn-419", createLocale("es-419-u-em-emoji").getString());
EXPECT_EQ("es-Latn-419", createLocale("es-Latn-419-u-em-emoji").getString());
@@ -116,6 +124,10 @@
EXPECT_EQ("en-Latn-US", createLocale("und-Abcdefgh").getString());
}
+TEST(LocaleTest, invalidLanguageTagTest) { // just make sure no crash happens
+ LocaleListCache::getId("ja-JP-u-lb-lb-strict");
+}
+
TEST(LocaleTest, testReconstruction) {
EXPECT_EQ("en", createLocaleWithoutICUSanitization("en").getString());
EXPECT_EQ("fil", createLocaleWithoutICUSanitization("fil").getString());
diff --git a/tests/unittest/FontTest.cpp b/tests/unittest/FontTest.cpp
new file mode 100644
index 0000000..ff2f9bc
--- /dev/null
+++ b/tests/unittest/FontTest.cpp
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "minikin/Font.h"
+
+#include <gtest/gtest.h>
+
+#include "FontTestUtils.h"
+#include "FreeTypeMinikinFontForTest.h"
+
+namespace minikin {
+
+TEST(FontTest, CopyTest) {
+ auto minikinFont = std::make_shared<FreeTypeMinikinFontForTest>(getTestFontPath("Ascii.ttf"));
+ {
+ Font font = Font::Builder(minikinFont).build();
+ {
+ Font copied(font);
+ EXPECT_EQ(font.typeface(), copied.typeface());
+ EXPECT_EQ(font.style(), copied.style());
+ EXPECT_EQ(font.baseFont(), copied.baseFont());
+ }
+ {
+ Font copied = font;
+ EXPECT_EQ(font.typeface(), copied.typeface());
+ EXPECT_EQ(font.style(), copied.style());
+ EXPECT_EQ(font.baseFont(), copied.baseFont());
+ }
+ }
+}
+
+} // namespace minikin
diff --git a/tests/unittest/GraphemeBreakTests.cpp b/tests/unittest/GraphemeBreakTests.cpp
index bccd631..87e114c 100644
--- a/tests/unittest/GraphemeBreakTests.cpp
+++ b/tests/unittest/GraphemeBreakTests.cpp
@@ -256,17 +256,17 @@
EXPECT_FALSE(IsBreakWithAdvances(ligated2_2, "U+1F918 | U+1F3FF"));
EXPECT_FALSE(IsBreakWithAdvances(ligated2_2, "U+1F933 | U+1F3FF"));
// Reptition of the tests above, with the knowledge that they are not ligated.
- // const float unligated1_2[] = {1.0, 1.0, 0.0};
- // const float unligated2_2[] = {1.0, 0.0, 1.0, 0.0};
- // EXPECT_TRUE(IsBreakWithAdvances(unligated1_2, "U+261D | U+1F3FB"));
- // EXPECT_TRUE(IsBreakWithAdvances(unligated1_2, "U+270C | U+1F3FB"));
- // EXPECT_TRUE(IsBreakWithAdvances(unligated2_2, "U+1F466 | U+1F3FB"));
- // EXPECT_TRUE(IsBreakWithAdvances(unligated2_2, "U+1F466 | U+1F3FC"));
- // EXPECT_TRUE(IsBreakWithAdvances(unligated2_2, "U+1F466 | U+1F3FD"));
- // EXPECT_TRUE(IsBreakWithAdvances(unligated2_2, "U+1F466 | U+1F3FE"));
- // EXPECT_TRUE(IsBreakWithAdvances(unligated2_2, "U+1F466 | U+1F3FF"));
- // EXPECT_TRUE(IsBreakWithAdvances(unligated2_2, "U+1F918 | U+1F3FF"));
- // EXPECT_TRUE(IsBreakWithAdvances(unligated2_2, "U+1F933 | U+1F3FF"));
+ const float unligated1_2[] = {1.0, 1.0, 0.0};
+ const float unligated2_2[] = {1.0, 0.0, 1.0, 0.0};
+ EXPECT_TRUE(IsBreakWithAdvances(unligated1_2, "U+261D | U+1F3FB"));
+ EXPECT_TRUE(IsBreakWithAdvances(unligated1_2, "U+270C | U+1F3FB"));
+ EXPECT_TRUE(IsBreakWithAdvances(unligated2_2, "U+1F466 | U+1F3FB"));
+ EXPECT_TRUE(IsBreakWithAdvances(unligated2_2, "U+1F466 | U+1F3FC"));
+ EXPECT_TRUE(IsBreakWithAdvances(unligated2_2, "U+1F466 | U+1F3FD"));
+ EXPECT_TRUE(IsBreakWithAdvances(unligated2_2, "U+1F466 | U+1F3FE"));
+ EXPECT_TRUE(IsBreakWithAdvances(unligated2_2, "U+1F466 | U+1F3FF"));
+ EXPECT_TRUE(IsBreakWithAdvances(unligated2_2, "U+1F918 | U+1F3FF"));
+ EXPECT_TRUE(IsBreakWithAdvances(unligated2_2, "U+1F933 | U+1F3FF"));
// adding extend characters between emoji base and modifier doesn't affect grapheme cluster
EXPECT_FALSE(IsBreak("U+270C U+FE0E | U+1F3FB")); // victory hand + text style + modifier
@@ -276,15 +276,9 @@
EXPECT_FALSE(IsBreakWithAdvances(ligated1_1_2, "U+270C U+FE0E | U+1F3FB"));
EXPECT_FALSE(IsBreakWithAdvances(ligated1_1_2, "U+270C U+FE0F | U+1F3FB"));
// Reptition of the first two tests, with the knowledge that they are not ligated.
- // const float unligated1_1_2[] = {1.0, 0.0, 1.0, 0.0};
- // EXPECT_TRUE(IsBreakWithAdvances(unligated1_1_2, "U+270C U+FE0E | U+1F3FB"));
- // EXPECT_TRUE(IsBreakWithAdvances(unligated1_1_2, "U+270C U+FE0F | U+1F3FB"));
-
- // heart is not an emoji base
- // EXPECT_TRUE(IsBreak("U+2764 | U+1F3FB")); // heart + modifier
- // EXPECT_TRUE(IsBreak("U+2764 U+FE0E | U+1F3FB")); // heart + emoji style + modifier
- // EXPECT_TRUE(IsBreak("U+2764 U+FE0F | U+1F3FB")); // heart + emoji style + modifier
- // EXPECT_TRUE(IsBreak("U+1F3FB | U+1F3FB")); // modifier + modifier
+ const float unligated1_1_2[] = {1.0, 0.0, 1.0, 0.0};
+ EXPECT_TRUE(IsBreakWithAdvances(unligated1_1_2, "U+270C U+FE0E | U+1F3FB"));
+ EXPECT_TRUE(IsBreakWithAdvances(unligated1_1_2, "U+270C U+FE0F | U+1F3FB"));
// rat is not an emoji modifer
EXPECT_TRUE(IsBreak("U+1F466 | U+1F400")); // boy + rat
@@ -318,4 +312,9 @@
EXPECT_TRUE(GraphemeBreak::isGraphemeBreak(nullptr, string, 2, 3, 5));
}
+TEST(GraphemeBreak, startWithZWJ) {
+ // It used to be looking before the ZWJ char even if it is the start of the text.
+ IsBreak("U+200D | U+1F5E8"); // UB sanitizer will catch if minikin looks the char before ZWJ
+}
+
} // namespace minikin
diff --git a/tests/unittest/GreedyLineBreakerTest.cpp b/tests/unittest/GreedyLineBreakerTest.cpp
index ad5824d..13cc03c 100644
--- a/tests/unittest/GreedyLineBreakerTest.cpp
+++ b/tests/unittest/GreedyLineBreakerTest.cpp
@@ -39,6 +39,14 @@
using line_breaker_test_helper::sameLineBreak;
using line_breaker_test_helper::toString;
+// The ascent/descent of Ascii.ttf with text size = 10.
+constexpr float ASCENT = -80.0f;
+constexpr float DESCENT = 20.0f;
+
+// The ascent/descent of CustomExtent.ttf with text size = 10.
+constexpr float CUSTOM_ASCENT = -160.0f;
+constexpr float CUSTOM_DESCENT = 40.0f;
+
class GreedyLineBreakerTest : public testing::Test {
public:
GreedyLineBreakerTest() {}
@@ -57,16 +65,24 @@
protected:
LineBreakResult doLineBreak(const U16StringPiece& textBuffer, bool doHyphenation,
- float charWidth, float lineWidth) {
- return doLineBreak(textBuffer, doHyphenation, charWidth, "en-US", lineWidth);
+ float lineWidth) {
+ return doLineBreak(textBuffer, doHyphenation, "en-US", lineWidth);
}
LineBreakResult doLineBreak(const U16StringPiece& textBuffer, bool doHyphenation,
- float charWidth, const std::string& lang, float lineWidth) {
+ const std::string& lang, float lineWidth) {
MeasuredTextBuilder builder;
- builder.addCustomRun<ConstantRun>(Range(0, textBuffer.size()), lang, charWidth);
- std::unique_ptr<MeasuredText> measuredText = builder.build(
- textBuffer, false /* compute hyphenation */, false /* compute full layout */);
+ 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=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 */);
RectangleLineWidth rectangleLineWidth(lineWidth);
TabStops tabStops(nullptr, 0, 10);
return breakLineGreedy(textBuffer, *measuredText, rectangleLineWidth, tabStops,
@@ -78,7 +94,6 @@
};
TEST_F(GreedyLineBreakerTest, testBreakWithoutHyphenation) {
- constexpr float CHAR_WIDTH = 10.0;
constexpr bool NO_HYPHEN = false; // No hyphenation in this test case.
const std::vector<uint16_t> textBuf = utf8ToUtf16("This is an example text.");
@@ -86,198 +101,198 @@
constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
// Note that disable clang-format everywhere since aligned expectation is more readable.
{
- constexpr float LINE_WIDTH = 1000 * CHAR_WIDTH;
+ constexpr float LINE_WIDTH = 1000;
std::vector<LineBreakExpectation> expect = {
- {"This is an example text.", 24 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ {"This is an example text.", 240, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
};
- const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+ const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
<< " vs " << std::endl
<< toString(textBuf, actual);
}
{
- constexpr float LINE_WIDTH = 24 * CHAR_WIDTH;
+ constexpr float LINE_WIDTH = 240;
std::vector<LineBreakExpectation> expect = {
- {"This is an example text.", 24 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ {"This is an example text.", 240, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
};
- const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+ const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
<< " vs " << std::endl
<< toString(textBuf, actual);
}
{
- constexpr float LINE_WIDTH = 23 * CHAR_WIDTH;
+ constexpr float LINE_WIDTH = 230;
// clang-format off
std::vector<LineBreakExpectation> expect = {
- { "This is an example ", 18 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "text." , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "This is an example ", 180, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "text." , 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
};
// clang-format on
- const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+ const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
<< " vs " << std::endl
<< toString(textBuf, actual);
}
{
- constexpr float LINE_WIDTH = 8 * CHAR_WIDTH;
+ constexpr float LINE_WIDTH = 80;
// clang-format off
std::vector<LineBreakExpectation> expect = {
- { "This is ", 7 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "an " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "example ", 7 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "text." , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "This is ", 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "an " , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "example ", 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "text." , 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
};
// clang-format on
- const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+ const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
<< " vs " << std::endl
<< toString(textBuf, actual);
}
{
- constexpr float LINE_WIDTH = 7 * CHAR_WIDTH;
+ constexpr float LINE_WIDTH = 70;
// clang-format off
std::vector<LineBreakExpectation> expect = {
- { "This is ", 7 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "an " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "example ", 7 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "text." , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "This is ", 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "an " , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "example ", 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "text." , 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
};
// clang-format on
- const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+ const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
<< " vs " << std::endl
<< toString(textBuf, actual);
}
{
- constexpr float LINE_WIDTH = 6 * CHAR_WIDTH;
+ constexpr float LINE_WIDTH = 60;
// clang-format off
std::vector<LineBreakExpectation> expect = {
- { "This " , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "is an ", 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "exampl", 6 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "e " , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "text." , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "This " , 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "is an ", 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "exampl", 60, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "e " , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "text." , 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
};
// clang-format on
- const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+ const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
<< " vs " << std::endl
<< toString(textBuf, actual);
}
{
- constexpr float LINE_WIDTH = 5 * CHAR_WIDTH;
+ constexpr float LINE_WIDTH = 50;
// clang-format off
std::vector<LineBreakExpectation> expect = {
- { "This " , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "is an ", 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "examp" , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "le " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "text." , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "This " , 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "is an ", 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "examp" , 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "le " , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "text." , 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
};
// clang-format on
- const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+ const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
<< " vs " << std::endl
<< toString(textBuf, actual);
}
{
- constexpr float LINE_WIDTH = 4 * CHAR_WIDTH;
+ constexpr float LINE_WIDTH = 40;
// clang-format off
std::vector<LineBreakExpectation> expect = {
- { "This " , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "is " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "an " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "exam" , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "ple " , 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "text" , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "." , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "This " , 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "is " , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "an " , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "exam" , 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "ple " , 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "text" , 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "." , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
};
// clang-format on
- const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+ const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
<< " vs " << std::endl
<< toString(textBuf, actual);
}
{
- constexpr float LINE_WIDTH = 3 * CHAR_WIDTH;
+ constexpr float LINE_WIDTH = 30;
// clang-format off
std::vector<LineBreakExpectation> expect = {
- { "Thi" , 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "s " , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "is " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "an " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "exa" , 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "mpl" , 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "e " , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "tex" , 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "t." , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "Thi" , 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "s " , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "is " , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "an " , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "exa" , 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "mpl" , 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "e " , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "tex" , 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "t." , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
};
// clang-format on
- const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+ const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
<< " vs " << std::endl
<< toString(textBuf, actual);
}
{
- constexpr float LINE_WIDTH = 2 * CHAR_WIDTH;
+ constexpr float LINE_WIDTH = 20;
// clang-format off
std::vector<LineBreakExpectation> expect = {
- { "Th" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "is ", 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "is ", 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "an ", 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "ex" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "am" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "pl" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "e " , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "te" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "xt" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "." , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "Th" , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "is ", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "is ", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "an ", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "ex" , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "am" , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "pl" , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "e " , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "te" , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "xt" , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "." , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
};
// clang-format on
- const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+ const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
<< " vs " << std::endl
<< toString(textBuf, actual);
}
{
- constexpr float LINE_WIDTH = 1 * CHAR_WIDTH;
+ constexpr float LINE_WIDTH = 10;
// clang-format off
std::vector<LineBreakExpectation> expect = {
- { "T" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "h" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "i" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "s ", 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "i" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "s ", 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "a" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "n ", 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "e" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "x" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "a" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "m" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "p" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "l" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "e ", 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "t" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "e" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "x" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "t" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "." , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "T" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "h" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "i" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "s ", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "i" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "s ", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "a" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "n ", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "e" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "x" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "a" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "m" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "p" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "l" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "e ", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "t" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "e" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "x" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "t" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "." , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
};
// clang-format on
- const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+ const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
<< " vs " << std::endl
<< toString(textBuf, actual);
@@ -285,7 +300,6 @@
}
TEST_F(GreedyLineBreakerTest, testBreakWithHyphenation) {
- constexpr float CHAR_WIDTH = 10.0;
constexpr bool NO_HYPHEN = true; // Do hyphenation in this test case.
// "hyphenation" is hyphnated to "hy-phen-a-tion".
const std::vector<uint16_t> textBuf = utf8ToUtf16("Hyphenation is hyphenation.");
@@ -296,262 +310,264 @@
// Note that disable clang-format everywhere since aligned expectation is more readable.
{
- constexpr float LINE_WIDTH = 1000 * CHAR_WIDTH;
+ constexpr float LINE_WIDTH = 1000;
std::vector<LineBreakExpectation> expect = {
- {"Hyphenation is hyphenation.", 27 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ {"Hyphenation is hyphenation.", 270, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT,
+ DESCENT},
};
- const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+ const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
<< " vs " << std::endl
<< toString(textBuf, actual);
}
{
- constexpr float LINE_WIDTH = 27 * CHAR_WIDTH;
+ constexpr float LINE_WIDTH = 270;
std::vector<LineBreakExpectation> expect = {
- {"Hyphenation is hyphenation.", 27 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ {"Hyphenation is hyphenation.", 270, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT,
+ DESCENT},
};
- const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+ const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
<< " vs " << std::endl
<< toString(textBuf, actual);
}
{
- constexpr float LINE_WIDTH = 26 * CHAR_WIDTH;
+ constexpr float LINE_WIDTH = 260;
// clang-format off
std::vector<LineBreakExpectation> expect = {
- { "Hyphenation is " , 14 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "hyphenation." , 12 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "Hyphenation is " , 140, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "hyphenation." , 120, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
};
// clang-format on
- const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+ const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
<< " vs " << std::endl
<< toString(textBuf, actual);
}
{
- constexpr float LINE_WIDTH = 17 * CHAR_WIDTH;
+ constexpr float LINE_WIDTH = 170;
// clang-format off
std::vector<LineBreakExpectation> expect = {
- { "Hyphenation is " , 14 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "hyphenation." , 12 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "Hyphenation is " , 140, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "hyphenation." , 120, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
};
// clang-format on
- const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+ const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
<< " vs " << std::endl
<< toString(textBuf, actual);
}
{
- constexpr float LINE_WIDTH = 12 * CHAR_WIDTH;
+ constexpr float LINE_WIDTH = 120;
// clang-format off
std::vector<LineBreakExpectation> expect = {
- { "Hyphenation " , 11 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "is " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "hyphenation." , 12 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "Hyphenation " , 110, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "is " , 20 , NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "hyphenation." , 120, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
};
// clang-format on
- const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+ const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
<< " vs " << std::endl
<< toString(textBuf, actual);
}
{
- constexpr float LINE_WIDTH = 10 * CHAR_WIDTH;
+ constexpr float LINE_WIDTH = 100;
// clang-format off
std::vector<LineBreakExpectation> expect = {
- { "Hyphena-", 8 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
- { "tion is ", 7 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "hyphena-", 8 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
- { "tion." , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "Hyphena-", 80, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+ { "tion is ", 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "hyphena-", 80, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+ { "tion." , 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
};
// clang-format on
- const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+ const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
<< " vs " << std::endl
<< toString(textBuf, actual);
}
{
- constexpr float LINE_WIDTH = 8 * CHAR_WIDTH;
+ constexpr float LINE_WIDTH = 80;
// clang-format off
std::vector<LineBreakExpectation> expect = {
- { "Hyphena-", 8 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
- { "tion is ", 7 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "hyphena-", 8 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
- { "tion." , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "Hyphena-", 80, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+ { "tion is ", 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "hyphena-", 80, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+ { "tion." , 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
};
// clang-format on
- const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+ const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
<< " vs " << std::endl
<< toString(textBuf, actual);
}
{
- constexpr float LINE_WIDTH = 7 * CHAR_WIDTH;
+ constexpr float LINE_WIDTH = 70;
// clang-format off
std::vector<LineBreakExpectation> expect = {
- { "Hyphen-", 7 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
- { "ation " , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "is " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "hyphen-", 7 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
- { "ation." , 6 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "Hyphen-", 70, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+ { "ation " , 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "is " , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "hyphen-", 70, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+ { "ation." , 60, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
};
// clang-format on
- const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+ const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
<< " vs " << std::endl
<< toString(textBuf, actual);
}
{
- constexpr float LINE_WIDTH = 6 * CHAR_WIDTH;
+ constexpr float LINE_WIDTH = 60;
// clang-format off
std::vector<LineBreakExpectation> expect = {
- { "Hy-" , 3 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
- { "phena-", 6 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
- { "tion " , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "is " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "hy-" , 3 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
- { "phena-", 6 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
- { "tion." , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "Hy-" , 30, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+ { "phena-", 60, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+ { "tion " , 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "is " , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "hy-" , 30, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+ { "phena-", 60, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+ { "tion." , 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
};
// clang-format on
- const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+ const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
<< " vs " << std::endl
<< toString(textBuf, actual);
}
{
- constexpr float LINE_WIDTH = 5 * CHAR_WIDTH;
+ constexpr float LINE_WIDTH = 50;
// clang-format off
std::vector<LineBreakExpectation> expect = {
- { "Hy-" , 3 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
- { "phen-" , 5 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
- { "ation ", 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "is " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "hy-" , 3 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
- { "phen-" , 5 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
- { "a-" , 2 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
- { "tion." , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "Hy-" , 30, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+ { "phen-" , 50, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+ { "ation ", 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "is " , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "hy-" , 30, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+ { "phen-" , 50, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+ { "a-" , 20, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+ { "tion." , 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
};
// clang-format on
- const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+ const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
<< " vs " << std::endl
<< toString(textBuf, actual);
}
{
- constexpr float LINE_WIDTH = 4 * CHAR_WIDTH;
+ constexpr float LINE_WIDTH = 40;
// clang-format off
std::vector<LineBreakExpectation> expect = {
- { "Hy-" , 3 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
- { "phen" , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "a-" , 2 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
- { "tion ", 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "is " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "hy-" , 3 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
- { "phen" , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "a-" , 2 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
- { "tion" , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "." , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "Hy-" , 30, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+ { "phen" , 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "a-" , 20, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+ { "tion ", 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "is " , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "hy-" , 30, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+ { "phen" , 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "a-" , 20, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+ { "tion" , 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "." , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
};
// clang-format on
- const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+ const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
<< " vs " << std::endl
<< toString(textBuf, actual);
}
{
- constexpr float LINE_WIDTH = 3 * CHAR_WIDTH;
+ constexpr float LINE_WIDTH = 30;
// clang-format off
std::vector<LineBreakExpectation> expect = {
- { "Hy-", 3 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
- { "phe", 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "na-", 3 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
- { "tio", 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "n " , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "is ", 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "hy-", 3 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
- { "phe", 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "na-", 3 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
- { "tio", 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "n." , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "Hy-", 30, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+ { "phe", 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "na-", 30, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+ { "tio", 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "n " , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "is ", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "hy-", 30, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+ { "phe", 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "na-", 30, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+ { "tio", 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "n." , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
};
// clang-format on
- const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+ const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
<< " vs " << std::endl
<< toString(textBuf, actual);
}
{
- constexpr float LINE_WIDTH = 2 * CHAR_WIDTH;
+ constexpr float LINE_WIDTH = 20;
// clang-format off
std::vector<LineBreakExpectation> expect = {
- { "Hy" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "ph" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "en" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "a-" , 2 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
- { "ti" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "on ", 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "is ", 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "hy" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "ph" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "en" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "a-" , 2 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
- { "ti" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "on" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "." , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "Hy" , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "ph" , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "en" , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "a-" , 20, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+ { "ti" , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "on ", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "is ", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "hy" , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "ph" , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "en" , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "a-" , 20, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+ { "ti" , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "on" , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "." , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
};
// clang-format on
- const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+ const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
<< " vs " << std::endl
<< toString(textBuf, actual);
}
{
- constexpr float LINE_WIDTH = 1 * CHAR_WIDTH;
+ constexpr float LINE_WIDTH = 10;
// clang-format off
std::vector<LineBreakExpectation> expect = {
- { "H" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "y" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "p" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "h" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "e" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "n" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "a" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "t" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "i" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "o" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "n ", 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "i" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "s ", 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "h" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "y" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "p" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "h" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "e" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "n" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "a" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "t" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "i" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "o" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "n" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "." , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "H" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "y" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "p" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "h" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "e" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "n" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "a" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "t" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "i" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "o" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "n ", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "i" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "s ", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "h" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "y" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "p" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "h" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "e" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "n" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "a" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "t" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "i" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "o" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "n" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "." , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
};
// clang-format on
- const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+ const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
<< " vs " << std::endl
<< toString(textBuf, actual);
@@ -559,7 +575,6 @@
}
TEST_F(GreedyLineBreakerTest, testHyphenationStartLineChange) {
- constexpr float CHAR_WIDTH = 10.0;
constexpr bool DO_HYPHEN = true; // Do hyphenation in this test case.
// "hyphenation" is hyphnated to "hy-phen-a-tion".
const std::vector<uint16_t> textBuf = utf8ToUtf16("czerwono-niebieska");
@@ -570,37 +585,37 @@
// Note that disable clang-format everywhere since aligned expectation is more readable.
{
- constexpr float LINE_WIDTH = 1000 * CHAR_WIDTH;
+ constexpr float LINE_WIDTH = 1000;
std::vector<LineBreakExpectation> expect = {
- {"czerwono-niebieska", 18 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ {"czerwono-niebieska", 180, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
};
- const auto actual = doLineBreak(textBuf, DO_HYPHEN, CHAR_WIDTH, "pl", LINE_WIDTH);
+ const auto actual = doLineBreak(textBuf, DO_HYPHEN, "pl", LINE_WIDTH);
EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
<< " vs " << std::endl
<< toString(textBuf, actual);
}
{
- constexpr float LINE_WIDTH = 18 * CHAR_WIDTH;
+ constexpr float LINE_WIDTH = 180;
std::vector<LineBreakExpectation> expect = {
- {"czerwono-niebieska", 18 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ {"czerwono-niebieska", 180, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
};
- const auto actual = doLineBreak(textBuf, DO_HYPHEN, CHAR_WIDTH, "pl", LINE_WIDTH);
+ const auto actual = doLineBreak(textBuf, DO_HYPHEN, "pl", LINE_WIDTH);
EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
<< " vs " << std::endl
<< toString(textBuf, actual);
}
{
- constexpr float LINE_WIDTH = 13 * CHAR_WIDTH;
+ constexpr float LINE_WIDTH = 130;
// clang-format off
std::vector<LineBreakExpectation> expect = {
- {"czerwono-" , 9 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
- {"-niebieska", 10 * CHAR_WIDTH, START_HYPHEN, NO_END_HYPHEN},
+ {"czerwono-" , 90, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"-niebieska", 100, START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
};
// clang-format on
- const auto actual = doLineBreak(textBuf, DO_HYPHEN, CHAR_WIDTH, "pl", LINE_WIDTH);
+ const auto actual = doLineBreak(textBuf, DO_HYPHEN, "pl", LINE_WIDTH);
EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
<< " vs " << std::endl
<< toString(textBuf, actual);
@@ -608,9 +623,8 @@
}
TEST_F(GreedyLineBreakerTest, testZeroWidthLine) {
- constexpr float CHAR_WIDTH = 10.0;
constexpr bool DO_HYPHEN = true; // Do hyphenation in this test case.
- constexpr float LINE_WIDTH = 0 * CHAR_WIDTH;
+ constexpr float LINE_WIDTH = 0;
constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
@@ -618,7 +632,7 @@
{
const auto textBuf = utf8ToUtf16("");
std::vector<LineBreakExpectation> expect = {};
- const auto actual = doLineBreak(textBuf, DO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+ const auto actual = doLineBreak(textBuf, DO_HYPHEN, LINE_WIDTH);
EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
<< " vs " << std::endl
<< toString(textBuf, actual);
@@ -626,9 +640,9 @@
{
const auto textBuf = utf8ToUtf16("A");
std::vector<LineBreakExpectation> expect = {
- {"A", 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ {"A", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
};
- const auto actual = doLineBreak(textBuf, DO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+ const auto actual = doLineBreak(textBuf, DO_HYPHEN, LINE_WIDTH);
EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
<< " vs " << std::endl
<< toString(textBuf, actual);
@@ -636,10 +650,10 @@
{
const auto textBuf = utf8ToUtf16("AB");
std::vector<LineBreakExpectation> expect = {
- {"A", 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
- {"B", 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ {"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, DO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+ const auto actual = doLineBreak(textBuf, DO_HYPHEN, LINE_WIDTH);
EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
<< " vs " << std::endl
<< toString(textBuf, actual);
@@ -656,9 +670,19 @@
constexpr float LINE_WIDTH = 1.0;
const auto textBuf = utf8ToUtf16("This is an example text.");
std::vector<LineBreakExpectation> expect = {
- {"This is an example text.", 0, NO_START_HYPHEN, NO_END_HYPHEN},
+ {"This is an example text.", 0, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
};
- const auto actual = doLineBreak(textBuf, DO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+
+ 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 */);
+ RectangleLineWidth rectangleLineWidth(LINE_WIDTH);
+ TabStops tabStops(nullptr, 0, 10);
+ const auto actual =
+ breakLineGreedy(textBuf, *measuredText, rectangleLineWidth, tabStops, DO_HYPHEN);
EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
<< " vs " << std::endl
<< toString(textBuf, actual);
@@ -667,9 +691,18 @@
constexpr float LINE_WIDTH = 0.0;
const auto textBuf = utf8ToUtf16("This is an example text.");
std::vector<LineBreakExpectation> expect = {
- {"This is an example text.", 0, NO_START_HYPHEN, NO_END_HYPHEN},
+ {"This is an example text.", 0, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
};
- const auto actual = doLineBreak(textBuf, DO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+ 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 */);
+ RectangleLineWidth rectangleLineWidth(LINE_WIDTH);
+ TabStops tabStops(nullptr, 0, 10);
+ const auto actual =
+ breakLineGreedy(textBuf, *measuredText, rectangleLineWidth, tabStops, DO_HYPHEN);
EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
<< " vs " << std::endl
<< toString(textBuf, actual);
@@ -683,18 +716,20 @@
constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
- constexpr float LINE_WIDTH = 24 * CHAR_WIDTH;
+ constexpr float LINE_WIDTH = 240;
const auto textBuf = utf8ToUtf16("This is an example text.");
{
std::vector<LineBreakExpectation> expect = {
- {"This is an example text.", 24 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ {"This is an example text.", 240, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
};
MeasuredTextBuilder builder;
- builder.addCustomRun<ConstantRun>(Range(0, 18), "en-US", CHAR_WIDTH);
- builder.addCustomRun<ConstantRun>(Range(18, textBuf.size()), "en-US", CHAR_WIDTH);
- std::unique_ptr<MeasuredText> measuredText = builder.build(
- textBuf, false /* compute hyphenation */, false /* compute full layout */);
+ 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 */);
RectangleLineWidth rectangleLineWidth(LINE_WIDTH);
TabStops tabStops(nullptr, 0, 0);
@@ -706,14 +741,16 @@
}
{
std::vector<LineBreakExpectation> expect = {
- {"This is an example text.", 24 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ {"This is an example text.", 240, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
};
MeasuredTextBuilder builder;
- builder.addCustomRun<ConstantRun>(Range(0, 18), "en-US", CHAR_WIDTH);
- builder.addCustomRun<ConstantRun>(Range(18, textBuf.size()), "fr-FR", CHAR_WIDTH);
- std::unique_ptr<MeasuredText> measuredText = builder.build(
- textBuf, false /* compute hyphenation */, false /* compute full layout */);
+ 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 */);
RectangleLineWidth rectangleLineWidth(LINE_WIDTH);
TabStops tabStops(nullptr, 0, 0);
@@ -726,31 +763,30 @@
}
TEST_F(GreedyLineBreakerTest, testEmailOrUrl) {
- constexpr float CHAR_WIDTH = 10.0;
constexpr bool DO_HYPHEN = true; // Do hyphenation in this test case.
constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
{
- constexpr float LINE_WIDTH = 24 * CHAR_WIDTH;
+ constexpr float LINE_WIDTH = 240;
const auto textBuf = utf8ToUtf16("This is an url: http://a.b");
std::vector<LineBreakExpectation> expect = {
- {"This is an url: ", 15 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
- {"http://a.b", 10 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ {"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},
};
- const auto actual = doLineBreak(textBuf, DO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+ const auto actual = doLineBreak(textBuf, DO_HYPHEN, LINE_WIDTH);
EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
<< " vs " << std::endl
<< toString(textBuf, actual);
}
{
- constexpr float LINE_WIDTH = 24 * CHAR_WIDTH;
+ constexpr float LINE_WIDTH = 240;
const auto textBuf = utf8ToUtf16("This is an email: a@example.com");
std::vector<LineBreakExpectation> expect = {
- {"This is an email: ", 17 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
- {"a@example.com", 13 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ {"This is an email: ", 170, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"a@example.com", 130, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
};
- const auto actual = doLineBreak(textBuf, DO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+ const auto actual = doLineBreak(textBuf, DO_HYPHEN, LINE_WIDTH);
EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
<< " vs " << std::endl
<< toString(textBuf, actual);
@@ -764,19 +800,21 @@
constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
- constexpr float LINE_WIDTH = 24 * CHAR_WIDTH;
+ constexpr float LINE_WIDTH = 240;
{
const auto textBuf = utf8ToUtf16("This is an url: http://a.b");
std::vector<LineBreakExpectation> expect = {
- {"This is an url: ", 15 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
- {"http://a.b", 10 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ {"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},
};
MeasuredTextBuilder builder;
- builder.addCustomRun<ConstantRun>(Range(0, 18), "en-US", CHAR_WIDTH);
- builder.addCustomRun<ConstantRun>(Range(18, textBuf.size()), "fr-FR", CHAR_WIDTH);
- std::unique_ptr<MeasuredText> measuredText = builder.build(
- textBuf, false /* compute hyphenation */, false /* compute full layout */);
+ 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 */);
RectangleLineWidth rectangleLineWidth(LINE_WIDTH);
TabStops tabStops(nullptr, 0, 0);
@@ -789,15 +827,17 @@
{
const auto textBuf = utf8ToUtf16("This is an email: a@example.com");
std::vector<LineBreakExpectation> expect = {
- {"This is an email: ", 17 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
- {"a@example.com", 13 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ {"This is an email: ", 170, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"a@example.com", 130, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
};
MeasuredTextBuilder builder;
- builder.addCustomRun<ConstantRun>(Range(0, 18), "en-US", CHAR_WIDTH);
- builder.addCustomRun<ConstantRun>(Range(18, textBuf.size()), "fr-FR", CHAR_WIDTH);
- std::unique_ptr<MeasuredText> measuredText = builder.build(
- textBuf, false /* compute hyphenation */, false /* compute full layout */);
+ 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 */);
RectangleLineWidth rectangleLineWidth(LINE_WIDTH);
TabStops tabStops(nullptr, 0, 0);
@@ -817,16 +857,18 @@
constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
{
- constexpr float LINE_WIDTH = 5 * CHAR_WIDTH;
+ constexpr float LINE_WIDTH = 50;
const auto textBuf = utf8ToUtf16("a \tb");
std::vector<LineBreakExpectation> expect = {
- {"a \tb", 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ {"a \tb", 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
};
MeasuredTextBuilder builder;
- builder.addCustomRun<ConstantRun>(Range(0, textBuf.size()), "en-US", CHAR_WIDTH);
- std::unique_ptr<MeasuredText> measuredText = builder.build(
- textBuf, false /* compute hyphenation */, false /* compute full layout */);
+ 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 */);
RectangleLineWidth rectangleLineWidth(LINE_WIDTH);
TabStops tabStops(nullptr, 0, CHAR_WIDTH);
@@ -838,5 +880,685 @@
}
}
+TEST_F(GreedyLineBreakerTest, ExtentTest) {
+ constexpr bool NO_HYPHEN = false; // No hyphenation in this test case.
+ const std::vector<uint16_t> textBuf = utf8ToUtf16("The \u3042\u3044\u3046 is Japanese.");
+
+ constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
+ constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
+ {
+ constexpr float LINE_WIDTH = 1000;
+ std::vector<LineBreakExpectation> expect = {
+ {"The \u3042\u3044\u3046 is Japanese.", 200, NO_START_HYPHEN, NO_END_HYPHEN,
+ CUSTOM_ASCENT, CUSTOM_DESCENT},
+ };
+
+ const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 200;
+ std::vector<LineBreakExpectation> expect = {
+ {"The \u3042\u3044\u3046 is Japanese.", 200, NO_START_HYPHEN, NO_END_HYPHEN,
+ CUSTOM_ASCENT, CUSTOM_DESCENT},
+ };
+
+ const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 190;
+ std::vector<LineBreakExpectation> expect = {
+ {"The \u3042\u3044\u3046 is ", 100, NO_START_HYPHEN, NO_END_HYPHEN, CUSTOM_ASCENT,
+ CUSTOM_DESCENT},
+ {"Japanese.", 90, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ };
+
+ const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 90;
+ std::vector<LineBreakExpectation> expect = {
+ {"The \u3042\u3044\u3046 ", 70, NO_START_HYPHEN, NO_END_HYPHEN, CUSTOM_ASCENT,
+ CUSTOM_DESCENT},
+ {"is ", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"Japanese.", 90, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ };
+
+ const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 50;
+ std::vector<LineBreakExpectation> expect = {
+ {"The \u3042", 50, NO_START_HYPHEN, NO_END_HYPHEN, CUSTOM_ASCENT, CUSTOM_DESCENT},
+ {"\u3044\u3046 is ", 50, NO_START_HYPHEN, NO_END_HYPHEN, CUSTOM_ASCENT,
+ CUSTOM_DESCENT},
+ {"Japan", 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"ese.", 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ };
+
+ const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 40;
+ std::vector<LineBreakExpectation> expect = {
+ {"The ", 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"\u3042\u3044\u3046 ", 30, NO_START_HYPHEN, NO_END_HYPHEN, CUSTOM_ASCENT,
+ CUSTOM_DESCENT},
+ {"is ", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"Japa", 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"nese", 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {".", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ };
+
+ const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 20;
+ std::vector<LineBreakExpectation> expect = {
+ {"Th", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"e ", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"\u3042\u3044", 20, NO_START_HYPHEN, NO_END_HYPHEN, CUSTOM_ASCENT, CUSTOM_DESCENT},
+ {"\u3046 ", 10, NO_START_HYPHEN, NO_END_HYPHEN, CUSTOM_ASCENT, CUSTOM_DESCENT},
+ {"is ", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"Ja", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"pa", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"ne", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"se", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {".", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ };
+
+ const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 10;
+ std::vector<LineBreakExpectation> expect = {
+ {"T", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"h", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"e ", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"\u3042", 10, NO_START_HYPHEN, NO_END_HYPHEN, CUSTOM_ASCENT, CUSTOM_DESCENT},
+ {"\u3044", 10, NO_START_HYPHEN, NO_END_HYPHEN, CUSTOM_ASCENT, CUSTOM_DESCENT},
+ {"\u3046 ", 10, NO_START_HYPHEN, NO_END_HYPHEN, CUSTOM_ASCENT, CUSTOM_DESCENT},
+ {"i", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"s ", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"J", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"a", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"p", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"a", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"n", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"e", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"s", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"e", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {".", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ };
+
+ const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+}
+
+TEST_F(GreedyLineBreakerTest, testReplacementSpanNotBreakTest_SingleChar) {
+ constexpr float CHAR_WIDTH = 10.0;
+ constexpr bool DO_HYPHEN = true; // Do hyphenation in this test case.
+
+ constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
+ constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
+
+ const auto textBuf = utf8ToUtf16("This is an example \u2639 text.");
+
+ // In this test case, assign a replacement run for "U+2639" with 5 times of CHAR_WIDTH.
+ auto doLineBreak = [=](float width) {
+ MeasuredTextBuilder builder;
+ builder.addCustomRun<ConstantRun>(Range(0, 19), "en-US", CHAR_WIDTH, ASCENT, DESCENT);
+ builder.addReplacementRun(19, 20, 5 * CHAR_WIDTH, LocaleListCache::getId("en-US"));
+ 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 */);
+ RectangleLineWidth rectangleLineWidth(width);
+ TabStops tabStops(nullptr, 0, 0);
+ return breakLineGreedy(textBuf, *measuredText, rectangleLineWidth, tabStops, DO_HYPHEN);
+ };
+
+ {
+ constexpr float LINE_WIDTH = 100;
+ // "is an" is a single replacement span. Do not break.
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ {"This is an ", 100, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"example ", 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"\u2639 ", 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"text.", 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ };
+ // clang-format on
+ const auto actual = doLineBreak(LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 90;
+ // "is an" is a single replacement span. Do not break.
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ {"This is ", 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"an ", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"example ", 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"\u2639 ", 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"text.", 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ };
+ // clang-format on
+ const auto actual = doLineBreak(LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 10;
+ // "is an" is a single replacement span. Do not break.
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ {"T", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"h", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"i", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"s ", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"i", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"s ", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"a", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"n ", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"e", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"x", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"a", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"m", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"p", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"l", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"e ", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ // TODO: This should be "\u2639 " since should not count the trailing line end space
+ {"\u2639", 50, NO_START_HYPHEN, NO_END_HYPHEN, 0, 0},
+ {" ", 0, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"t", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"e", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"x", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"t", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {".", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ };
+ // clang-format on
+ const auto actual = doLineBreak(LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+}
+
+TEST_F(GreedyLineBreakerTest, testReplacementSpanNotBreakTest_MultipleChars) {
+ constexpr float CHAR_WIDTH = 10.0;
+ constexpr bool DO_HYPHEN = true; // Do hyphenation in this test case.
+
+ constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
+ constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
+
+ const auto textBuf = utf8ToUtf16("This is an example text.");
+
+ // In this test case, assign a replacement run for "is an " with 5 times of CHAR_WIDTH.
+ auto doLineBreak = [=](float width) {
+ MeasuredTextBuilder builder;
+ builder.addCustomRun<ConstantRun>(Range(0, 5), "en-US", CHAR_WIDTH, ASCENT, DESCENT);
+ builder.addReplacementRun(5, 11, 5 * CHAR_WIDTH, LocaleListCache::getId("en-US"));
+ 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 */);
+ RectangleLineWidth rectangleLineWidth(width);
+ TabStops tabStops(nullptr, 0, 0);
+ return breakLineGreedy(textBuf, *measuredText, rectangleLineWidth, tabStops, DO_HYPHEN);
+ };
+
+ {
+ constexpr float LINE_WIDTH = 100;
+ // "is an" is a single replacement span. Do not break.
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ {"This is an ", 100, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"example ", 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"text.", 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ };
+ // clang-format on
+ const auto actual = doLineBreak(LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 90;
+ // "is an" is a single replacement span. Do not break.
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ {"This ", 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"is an ", 50, NO_START_HYPHEN, NO_END_HYPHEN, 0, 0},
+ {"example ",70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"text.", 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ };
+ // clang-format on
+ const auto actual = doLineBreak(LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 10;
+ // "is an" is a single replacement span. Do not break.
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ {"T", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"h", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"i", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"s ", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"is an ", 50, NO_START_HYPHEN, NO_END_HYPHEN, 0, 0},
+ {"e", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"x", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"a", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"m", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"p", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"l", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"e ", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"t", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"e", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"x", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"t", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {".", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ };
+ // clang-format on
+ const auto actual = doLineBreak(LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+}
+
+TEST_F(GreedyLineBreakerTest, testReplacementSpanNotBreakTest_CJK) {
+ constexpr float CHAR_WIDTH = 10.0;
+ constexpr bool DO_HYPHEN = true; // Do hyphenation in this test case.
+
+ constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
+ constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
+
+ // Example string: "Today is a sunny day." in Japanese.
+ const auto textBuf = utf8ToUtf16("\u672C\u65E5\u306F\u6674\u5929\u306A\u308A");
+
+ // In this test case, assign a replacement run for "\u6674\u5929" with 5 times of CHAR_WIDTH.
+ auto doLineBreak = [=](float width) {
+ MeasuredTextBuilder builder;
+ builder.addCustomRun<ConstantRun>(Range(0, 3), "ja-JP", CHAR_WIDTH, ASCENT, DESCENT);
+ builder.addReplacementRun(3, 5, 5 * CHAR_WIDTH, LocaleListCache::getId("ja-JP"));
+ 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 */);
+ RectangleLineWidth rectangleLineWidth(width);
+ TabStops tabStops(nullptr, 0, 0);
+ return breakLineGreedy(textBuf, *measuredText, rectangleLineWidth, tabStops, DO_HYPHEN);
+ };
+
+ {
+ constexpr float LINE_WIDTH = 100;
+ // "\u6674\u5929" is a single replacement span. Do not break.
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ {"\u672C\u65E5\u306F\u6674\u5929\u306A\u308A",
+ 100, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ };
+ // clang-format on
+ const auto actual = doLineBreak(LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 90;
+ // "\u6674\u5929" is a single replacement span. Do not break.
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ {"\u672C\u65E5\u306F\u6674\u5929\u306A",
+ 90, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"\u308A",
+ 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ };
+ // clang-format on
+ const auto actual = doLineBreak(LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 80;
+ // "\u6674\u5929" is a single replacement span. Do not break.
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ {"\u672C\u65E5\u306F\u6674\u5929",
+ 80, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"\u306A\u308A",
+ 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ };
+ // clang-format on
+ const auto actual = doLineBreak(LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 70;
+ // "\u6674\u5929" is a single replacement span. Do not break.
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ {"\u672C\u65E5\u306F", 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"\u6674\u5929\u306A\u308A", 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ };
+ // clang-format on
+ const auto actual = doLineBreak(LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 60;
+ // "\u6674\u5929" is a single replacement span. Do not break.
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ {"\u672C\u65E5\u306F", 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"\u6674\u5929\u306A", 60, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"\u308A", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ };
+ // clang-format on
+ const auto actual = doLineBreak(LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 50;
+ // "\u6674\u5929" is a single replacement span. Do not break.
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ {"\u672C\u65E5\u306F", 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"\u6674\u5929", 50, NO_START_HYPHEN, NO_END_HYPHEN, 0, 0},
+ {"\u306A\u308A", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ };
+ // clang-format on
+ const auto actual = doLineBreak(LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 40;
+ // "\u6674\u5929" is a single replacement span. Do not break.
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ {"\u672C\u65E5\u306F", 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"\u6674\u5929", 50, NO_START_HYPHEN, NO_END_HYPHEN, 0, 0},
+ {"\u306A\u308A", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ };
+ // clang-format on
+ const auto actual = doLineBreak(LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 10;
+ // "\u6674\u5929" is a single replacement span. Do not break.
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ {"\u672C", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"\u65E5", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"\u306F", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"\u6674\u5929", 50, NO_START_HYPHEN, NO_END_HYPHEN, 0, 0},
+ {"\u306A", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"\u308A", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ };
+ // clang-format on
+ const auto actual = doLineBreak(LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+}
+
+TEST_F(GreedyLineBreakerTest, testReplacementSpanNotBreakTest_with_punctuation) {
+ constexpr float CHAR_WIDTH = 10.0;
+ constexpr bool DO_HYPHEN = true; // Do hyphenation in this test case.
+
+ constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
+ constexpr EndHyphenEdit END_HYPHEN = EndHyphenEdit::INSERT_HYPHEN;
+ constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
+
+ const auto textBuf = utf8ToUtf16("This (is an) example text.");
+
+ // In this test case, assign a replacement run for "U+2639" with 5 times of CHAR_WIDTH.
+ auto doLineBreak = [=](float width) {
+ MeasuredTextBuilder builder;
+ builder.addCustomRun<ConstantRun>(Range(0, 6), "en-US", CHAR_WIDTH, ASCENT, DESCENT);
+ builder.addReplacementRun(6, 11, 5 * CHAR_WIDTH, LocaleListCache::getId("en-US"));
+ 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 */);
+ RectangleLineWidth rectangleLineWidth(width);
+ TabStops tabStops(nullptr, 0, 0);
+ return breakLineGreedy(textBuf, *measuredText, rectangleLineWidth, tabStops, DO_HYPHEN);
+ };
+
+ {
+ constexpr float LINE_WIDTH = 1000;
+ // "is an" is a single replacement span. Do not break.
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ {"This (is an) example text.",
+ 260, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ };
+ // clang-format on
+ const auto actual = doLineBreak(LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 250;
+ // "is an" is a single replacement span. Do not break.
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ {"This (is an) example ", 200, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"text.", 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ };
+ // clang-format on
+ const auto actual = doLineBreak(LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 190;
+ // "is an" is a single replacement span. Do not break.
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ {"This (is an) ", 120, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"example text.", 130, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ };
+ // clang-format on
+ const auto actual = doLineBreak(LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 120;
+ // "is an" is a single replacement span. Do not break.
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ {"This (is an) ", 120, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"example ", 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"text.", 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ };
+ // clang-format on
+ const auto actual = doLineBreak(LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 110;
+ // "is an" is a single replacement span. Do not break.
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ {"This ", 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"(is an) ", 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"example ", 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"text.", 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ };
+ // clang-format on
+ const auto actual = doLineBreak(LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 60;
+ // "is an" is a single replacement span. Do not break.
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ {"This ", 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"(is an", 60, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {") ", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"exam-", 50, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT},
+ {"ple ", 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"text.", 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ };
+ // clang-format on
+ const auto actual = doLineBreak(LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 50;
+ // "is an" is a single replacement span. Do not break.
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ {"This ", 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"(", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"is an", 50, NO_START_HYPHEN, NO_END_HYPHEN, 0, 0},
+ {") ", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"exam-", 50, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT},
+ {"ple ", 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"text.", 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ };
+ // clang-format on
+ const auto actual = doLineBreak(LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 40;
+ // "is an" is a single replacement span. Do not break.
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ {"This ", 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"(", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"is an", 50, NO_START_HYPHEN, NO_END_HYPHEN, 0, 0},
+ {") ", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"ex-", 30, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT},
+ {"am-", 30, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT},
+ {"ple ", 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"text", 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {".", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ };
+ // clang-format on
+ const auto actual = doLineBreak(LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 10;
+ // "is an" is a single replacement span. Do not break.
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ {"T", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"h", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"i", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"s ", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"(", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"is an", 50, NO_START_HYPHEN, NO_END_HYPHEN, 0, 0},
+ {") ", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"e", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"x", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"a", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"m", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"p", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"l", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"e ", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"t", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"e", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"x", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"t", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {".", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ };
+ // clang-format on
+ const auto actual = doLineBreak(LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+}
+
+TEST_F(GreedyLineBreakerTest, testControllCharAfterSpace) {
+ constexpr bool NO_HYPHEN = false; // No hyphenation in this test case.
+ const std::vector<uint16_t> textBuf = utf8ToUtf16("example \u2066example");
+
+ constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
+ constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
+ {
+ constexpr float LINE_WIDTH = 90;
+ std::vector<LineBreakExpectation> expect = {
+ {"example ", 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"\u2066example", 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ };
+
+ const auto actual = doLineBreak(textBuf, NO_HYPHEN, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+}
} // namespace
} // namespace minikin
diff --git a/tests/unittest/HasherTest.cpp b/tests/unittest/HasherTest.cpp
new file mode 100644
index 0000000..8e11cc6
--- /dev/null
+++ b/tests/unittest/HasherTest.cpp
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "minikin/Hasher.h"
+
+#include <gtest/gtest.h>
+
+namespace minikin {
+
+TEST(HasherTest, hasherTest) {
+ EXPECT_EQ(Hasher().hash(), Hasher().hash());
+ EXPECT_EQ(Hasher().update(1).hash(), Hasher().update(1).hash());
+
+ uint16_t shorts1[] = {1, 2, 3, 4, 5};
+ uint16_t shorts2[] = {1, 2, 3, 4, 5};
+ EXPECT_EQ(Hasher().updateShorts(shorts1, 5).hash(), Hasher().updateShorts(shorts2, 5).hash());
+
+ Hasher hasher;
+ hasher.update(1);
+ EXPECT_EQ(hasher.hash(), hasher.hash());
+ hasher.update(2);
+ EXPECT_EQ(hasher.hash(), hasher.hash());
+}
+
+} // namespace minikin
diff --git a/tests/unittest/LayoutCacheTest.cpp b/tests/unittest/LayoutCacheTest.cpp
index b7ad0ba..e0dd5f3 100644
--- a/tests/unittest/LayoutCacheTest.cpp
+++ b/tests/unittest/LayoutCacheTest.cpp
@@ -29,18 +29,19 @@
class TestableLayoutCache : public LayoutCache {
public:
TestableLayoutCache(uint32_t maxEntries) : LayoutCache(maxEntries) {}
+ using LayoutCache::getCacheSize;
};
class LayoutCapture {
public:
LayoutCapture() {}
- void operator()(const Layout& layout) { mLayout = &layout; }
+ void operator()(const LayoutPiece& layout, const MinikinPaint& /* dir */) { mLayout = &layout; }
- const Layout* get() const { return mLayout; }
+ const LayoutPiece* get() const { return mLayout; }
private:
- const Layout* mLayout;
+ const LayoutPiece* mLayout;
};
TEST(LayoutCacheTest, cacheHitTest) {
@@ -198,11 +199,11 @@
SCOPED_TRACE("Different paint flags");
auto collection = buildFontCollection("Ascii.ttf");
MinikinPaint paint1(collection);
- paint1.paintFlags = 0;
+ paint1.fontFlags = 0;
layoutCache.getOrCreate(text1, Range(0, text1.size()), paint1, false /* LTR */,
StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout1);
MinikinPaint paint2(collection);
- paint2.paintFlags = LinearTextFlag;
+ paint2.fontFlags = LinearMetrics_Flag;
layoutCache.getOrCreate(text1, Range(0, text1.size()), paint2, false /* LTR */,
StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout2);
EXPECT_NE(layout1.get(), layout2.get());
@@ -224,11 +225,11 @@
SCOPED_TRACE("Different family variant");
auto collection = buildFontCollection("Ascii.ttf");
MinikinPaint paint1(collection);
- paint1.familyVariant = FontFamily::Variant::DEFAULT;
+ paint1.familyVariant = FamilyVariant::DEFAULT;
layoutCache.getOrCreate(text1, Range(0, text1.size()), paint1, false /* LTR */,
StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout1);
MinikinPaint paint2(collection);
- paint2.familyVariant = FontFamily::Variant::COMPACT;
+ paint2.familyVariant = FamilyVariant::COMPACT;
layoutCache.getOrCreate(text1, Range(0, text1.size()), paint2, false /* LTR */,
StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout2);
EXPECT_NE(layout1.get(), layout2.get());
@@ -260,7 +261,7 @@
EndHyphenEdit::NO_EDIT, layout1);
for (char c = 'a'; c <= 'z'; c++) {
- auto text1 = utf8ToUtf16(std::string(c, 10));
+ auto text1 = utf8ToUtf16(std::string(10, c));
LayoutCapture layout2;
layoutCache.getOrCreate(text1, Range(0, text1.size()), paint, false /* LTR */,
StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout2);
@@ -272,4 +273,18 @@
EXPECT_NE(layout1.get(), layout3.get());
}
+TEST(LayoutCacheTest, cacheLengthLimitTest) {
+ auto text = utf8ToUtf16(std::string(130, 'a'));
+ Range range(0, text.size());
+ MinikinPaint paint(buildFontCollection("Ascii.ttf"));
+
+ TestableLayoutCache layoutCache(140);
+
+ LayoutCapture layout;
+ layoutCache.getOrCreate(text, range, paint, false /* LTR */, StartHyphenEdit::NO_EDIT,
+ EndHyphenEdit::NO_EDIT, layout);
+
+ EXPECT_EQ(layoutCache.getCacheSize(), 0u);
+}
+
} // namespace minikin
diff --git a/tests/unittest/LayoutCoreTest.cpp b/tests/unittest/LayoutCoreTest.cpp
new file mode 100644
index 0000000..ef972a0
--- /dev/null
+++ b/tests/unittest/LayoutCoreTest.cpp
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "minikin/LayoutCore.h"
+
+#include <gtest/gtest.h>
+
+#include "minikin/FontCollection.h"
+#include "minikin/LayoutPieces.h"
+
+#include "FontTestUtils.h"
+#include "UnicodeUtils.h"
+
+namespace minikin {
+namespace {
+
+static LayoutPiece buildLayout(const std::string& text, const MinikinPaint& paint) {
+ auto utf16 = utf8ToUtf16(text);
+ return LayoutPiece(utf16, Range(0, utf16.size()), false /* rtl */, paint,
+ StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT);
+}
+
+static LayoutPiece buildLayout(const std::string& text, const std::vector<std::string>& fonts) {
+ std::vector<std::shared_ptr<FontFamily>> families;
+ for (const auto& fontPath : fonts) {
+ families.push_back(buildFontFamily(fontPath));
+ }
+ auto fc = std::make_shared<FontCollection>(families);
+ MinikinPaint paint(fc);
+ paint.size = 10.0f; // make 1em = 10px
+ return buildLayout(text, paint);
+}
+
+static LayoutPiece buildLayout(const std::string& text, const std::vector<std::string>& fonts,
+ const std::string fontFeaturesSettings) {
+ std::vector<std::shared_ptr<FontFamily>> families;
+ for (const auto& fontPath : fonts) {
+ families.push_back(buildFontFamily(fontPath));
+ }
+ auto fc = std::make_shared<FontCollection>(families);
+ MinikinPaint paint(fc);
+ paint.size = 10.0f; // make 1em = 10px
+ paint.fontFeatureSettings = fontFeaturesSettings;
+ return buildLayout(text, paint);
+}
+
+TEST(LayoutPieceTest, doLayoutTest) {
+ // The LayoutTestFont.ttf has following coverage, extent, width and bbox.
+ // Ascender: 10em, Descender: -2em
+ // U+0020: 10em, (0, 0) - (10, 10)
+ // U+002E (.): 10em, (0, 0) - (10, 10)
+ // U+0043 (C): 100em, (0, 0) - (100, 100)
+ // U+0049 (I): 1em, (0, 0) - (1, 1)
+ // U+004C (L): 50em, (0, 0) - (50, 50)
+ // U+0056 (V): 5em, (0, 0) - (5, 5)
+ // U+0058 (X): 10em, (0, 0) - (10, 10)
+ // U+005F (_): 0em, (0, 0) - (0, 0)
+ // U+FFFD (invalid surrogate will be replaced to this): 7em, (0, 0) - (7, 7)
+ // U+10331 (\uD800\uDF31): 10em, (0, 0) - (10, 10)
+ {
+ auto layout = buildLayout("I", {"LayoutTestFont.ttf"});
+ EXPECT_EQ(1u, layout.glyphCount());
+ EXPECT_EQ(Point(0, 0), layout.pointAt(0));
+ EXPECT_EQ(MinikinRect(0.0f, 10.0f, 10.0f, 0.0f), layout.bounds());
+ EXPECT_EQ(MinikinExtent(-100.0f, 20.0f), layout.extent());
+ EXPECT_EQ(1u, layout.fonts().size());
+ EXPECT_TRUE(layout.fontAt(0).font);
+ EXPECT_EQ(1u, layout.advances().size());
+ EXPECT_EQ(10.0f, layout.advances()[0]);
+ EXPECT_EQ(10.0f, layout.advance());
+ }
+ {
+ auto layout = buildLayout("II", {"LayoutTestFont.ttf"});
+ EXPECT_EQ(2u, layout.glyphCount());
+ EXPECT_EQ(Point(0, 0), layout.pointAt(0));
+ EXPECT_EQ(Point(10.0f, 0), layout.pointAt(1));
+ EXPECT_EQ(MinikinRect(0.0f, 10.0f, 20.0f, 0.0f), layout.bounds());
+ EXPECT_EQ(MinikinExtent(-100.0f, 20.0f), layout.extent());
+ EXPECT_EQ(1u, layout.fonts().size());
+ EXPECT_TRUE(layout.fontAt(0).font);
+ EXPECT_TRUE(layout.fontAt(1).font);
+ EXPECT_EQ(layout.fontAt(0), layout.fontAt(1));
+ EXPECT_EQ(2u, layout.advances().size());
+ EXPECT_EQ(10.0f, layout.advances()[0]);
+ EXPECT_EQ(10.0f, layout.advances()[1]);
+ EXPECT_EQ(20.0f, layout.advance());
+ }
+ {
+ auto layout = buildLayout("IV", {"LayoutTestFont.ttf"});
+ EXPECT_EQ(2u, layout.glyphCount());
+ EXPECT_EQ(Point(0, 0), layout.pointAt(0));
+ EXPECT_EQ(Point(10.0f, 0), layout.pointAt(1));
+ EXPECT_EQ(MinikinRect(0.0f, 10.0f, 60.0f, 0.0f), layout.bounds());
+ EXPECT_EQ(MinikinExtent(-100.0f, 20.0f), layout.extent());
+ EXPECT_EQ(1u, layout.fonts().size());
+ EXPECT_TRUE(layout.fontAt(0).font);
+ EXPECT_TRUE(layout.fontAt(1).font);
+ EXPECT_EQ(layout.fontAt(0), layout.fontAt(1));
+ EXPECT_EQ(2u, layout.advances().size());
+ EXPECT_EQ(10.0f, layout.advances()[0]);
+ EXPECT_EQ(50.0f, layout.advances()[1]);
+ EXPECT_EQ(60.0f, layout.advance());
+ }
+}
+
+TEST(LayoutPieceTest, doLayoutTest_MultiFont) {
+ // See doLayoutTest for the details of LayoutTestFont.ttf
+ // The Hiragana.ttf has following coverage, extent, width and bbox.
+ // Ascender: 16em, Descender: -4em
+ // U+3042: 2em, (0, 0) - (2, 2)
+ // U+3044: 2em, (0, 0) - (2, 2)
+ // U+3046: 2em, (0, 0) - (2, 2)
+ // U+3048: 2em, (0, 0) - (2, 2)
+ // U+304A: 2em, (0, 0) - (2, 2)
+ {
+ auto layout = buildLayout("I\u3042", {"LayoutTestFont.ttf", "Hiragana.ttf"});
+ EXPECT_EQ(2u, layout.glyphCount());
+ EXPECT_EQ(Point(0, 0), layout.pointAt(0));
+ EXPECT_EQ(Point(10.0f, 0), layout.pointAt(1));
+ EXPECT_EQ(MinikinRect(0.0f, 10.0f, 30.0f, 0.0f), layout.bounds());
+ EXPECT_EQ(MinikinExtent(-160.0f, 40.0f), layout.extent());
+ EXPECT_EQ(2u, layout.fonts().size());
+ EXPECT_TRUE(layout.fontAt(0).font);
+ EXPECT_TRUE(layout.fontAt(1).font);
+ EXPECT_NE(layout.fontAt(0), layout.fontAt(1));
+ EXPECT_EQ(2u, layout.advances().size());
+ EXPECT_EQ(10.0f, layout.advances()[0]);
+ EXPECT_EQ(20.0f, layout.advances()[1]);
+ EXPECT_EQ(30.0f, layout.advance());
+ }
+ {
+ auto layout = buildLayout("\u3042I", {"LayoutTestFont.ttf", "Hiragana.ttf"});
+ EXPECT_EQ(2u, layout.glyphCount());
+ EXPECT_EQ(Point(0, 0), layout.pointAt(0));
+ EXPECT_EQ(Point(20.0f, 0), layout.pointAt(1));
+ EXPECT_EQ(MinikinRect(0.0f, 10.0f, 30.0f, 0.0f), layout.bounds());
+ EXPECT_EQ(MinikinExtent(-160.0f, 40.0f), layout.extent());
+ EXPECT_EQ(2u, layout.fonts().size());
+ EXPECT_TRUE(layout.fontAt(0).font);
+ EXPECT_TRUE(layout.fontAt(1).font);
+ EXPECT_NE(layout.fontAt(0), layout.fontAt(1));
+ EXPECT_EQ(2u, layout.advances().size());
+ EXPECT_EQ(20.0f, layout.advances()[0]);
+ EXPECT_EQ(10.0f, layout.advances()[1]);
+ EXPECT_EQ(30.0f, layout.advance());
+ }
+}
+
+TEST(LayoutPieceTest, doLayoutTest_Ligature) {
+ // Ligature.ttf support all ASCII characters.
+ // Ascender: 8em, Descender: -2em
+ // U+0020..U+007E: 1em, (0, 0) - (1, 1)
+ // Also this has ligature entry for fi as "ccmp" feature, ff as "liga" feature.
+ {
+ auto layout = buildLayout("fi", {"Ligature.ttf"});
+ EXPECT_EQ(1u, layout.glyphCount());
+ EXPECT_EQ(Point(0, 0), layout.pointAt(0));
+ EXPECT_EQ(MinikinRect(0.0f, 10.0f, 10.0f, 0.0f), layout.bounds());
+ EXPECT_EQ(MinikinExtent(-80.0f, 20.0f), layout.extent());
+ EXPECT_EQ(1u, layout.fonts().size());
+ EXPECT_TRUE(layout.fontAt(0).font);
+ EXPECT_EQ(2u, layout.advances().size());
+ EXPECT_EQ(10.0f, layout.advances()[0]);
+ EXPECT_EQ(0.0f, layout.advances()[1]); // Ligature assigns all width to the first char.
+ EXPECT_EQ(10.0f, layout.advance());
+ }
+ {
+ auto layout = buildLayout("ff", {"Ligature.ttf"});
+ EXPECT_EQ(1u, layout.glyphCount());
+ EXPECT_EQ(Point(0, 0), layout.pointAt(0));
+ EXPECT_EQ(MinikinRect(0.0f, 10.0f, 10.0f, 0.0f), layout.bounds());
+ EXPECT_EQ(MinikinExtent(-80.0f, 20.0f), layout.extent());
+ EXPECT_EQ(1u, layout.fonts().size());
+ EXPECT_TRUE(layout.fontAt(0).font);
+ EXPECT_EQ(2u, layout.advances().size());
+ EXPECT_EQ(10.0f, layout.advances()[0]);
+ EXPECT_EQ(0.0f, layout.advances()[1]); // Ligature assigns all width to the first char.
+ EXPECT_EQ(10.0f, layout.advance());
+ }
+ {
+ auto layout = buildLayout("fi", {"Ligature.ttf"}, "'liga' off");
+ EXPECT_EQ(1u, layout.glyphCount());
+ EXPECT_EQ(Point(0, 0), layout.pointAt(0));
+ EXPECT_EQ(MinikinRect(0.0f, 10.0f, 10.0f, 0.0f), layout.bounds());
+ EXPECT_EQ(MinikinExtent(-80.0f, 20.0f), layout.extent());
+ EXPECT_EQ(1u, layout.fonts().size());
+ EXPECT_TRUE(layout.fontAt(0).font);
+ EXPECT_EQ(2u, layout.advances().size());
+ EXPECT_EQ(10.0f, layout.advances()[0]);
+ EXPECT_EQ(0.0f, layout.advances()[1]); // Ligature assigns all width to the first char.
+ EXPECT_EQ(10.0f, layout.advance());
+ }
+ {
+ auto layout = buildLayout("ff", {"Ligature.ttf"}, "'liga' off");
+ EXPECT_EQ(2u, layout.glyphCount());
+ EXPECT_EQ(Point(0, 0), layout.pointAt(0));
+ EXPECT_EQ(MinikinRect(0.0f, 10.0f, 20.0f, 0.0f), layout.bounds());
+ EXPECT_EQ(MinikinExtent(-80.0f, 20.0f), layout.extent());
+ EXPECT_EQ(1u, layout.fonts().size());
+ EXPECT_TRUE(layout.fontAt(0).font);
+ EXPECT_TRUE(layout.fontAt(1).font);
+ EXPECT_EQ(2u, layout.advances().size());
+ EXPECT_EQ(layout.fontAt(0), layout.fontAt(1));
+ EXPECT_EQ(10.0f, layout.advances()[0]);
+ EXPECT_EQ(10.0f, layout.advances()[1]);
+ EXPECT_EQ(20.0f, layout.advance());
+ }
+ {
+ auto layout = buildLayout("fii", {"Ligature.ttf"});
+ EXPECT_EQ(2u, layout.glyphCount());
+ EXPECT_EQ(Point(0, 0), layout.pointAt(0));
+ EXPECT_EQ(MinikinRect(0.0f, 10.0f, 20.0f, 0.0f), layout.bounds());
+ EXPECT_EQ(MinikinExtent(-80.0f, 20.0f), layout.extent());
+ EXPECT_EQ(1u, layout.fonts().size());
+ EXPECT_TRUE(layout.fontAt(0).font);
+ EXPECT_TRUE(layout.fontAt(1).font);
+ EXPECT_EQ(layout.fontAt(0), layout.fontAt(1));
+ EXPECT_EQ(3u, layout.advances().size());
+ EXPECT_EQ(10.0f, layout.advances()[0]);
+ EXPECT_EQ(0.0f, layout.advances()[1]); // Ligature assigns all width to the first char.
+ EXPECT_EQ(10.0f, layout.advances()[2]);
+ EXPECT_EQ(20.0f, layout.advance());
+ }
+ {
+ auto layout = buildLayout("if", {"Ligature.ttf"});
+ EXPECT_EQ(2u, layout.glyphCount());
+ EXPECT_EQ(Point(0, 0), layout.pointAt(0));
+ EXPECT_EQ(MinikinRect(0.0f, 10.0f, 20.0f, 0.0f), layout.bounds());
+ EXPECT_EQ(MinikinExtent(-80.0f, 20.0f), layout.extent());
+ EXPECT_EQ(1u, layout.fonts().size());
+ EXPECT_TRUE(layout.fontAt(0).font);
+ EXPECT_TRUE(layout.fontAt(1).font);
+ EXPECT_EQ(layout.fontAt(0), layout.fontAt(1));
+ EXPECT_EQ(2u, layout.advances().size());
+ EXPECT_EQ(10.0f, layout.advances()[0]);
+ EXPECT_EQ(10.0f, layout.advances()[1]);
+ EXPECT_EQ(20.0f, layout.advance());
+ }
+}
+
+} // namespace
+} // namespace minikin
diff --git a/tests/unittest/LayoutSplitterTest.cpp b/tests/unittest/LayoutSplitterTest.cpp
new file mode 100644
index 0000000..4b75d9c
--- /dev/null
+++ b/tests/unittest/LayoutSplitterTest.cpp
@@ -0,0 +1,327 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+
+#include "minikin/FontCollection.h"
+#include "minikin/LayoutPieces.h"
+
+#include "FontTestUtils.h"
+#include "LayoutSplitter.h"
+#include "UnicodeUtils.h"
+
+namespace minikin {
+namespace {
+
+std::pair<std::vector<uint16_t>, Range> parseTestString(const std::string& text) {
+ auto utf16 = utf8ToUtf16(text);
+ Range range;
+ std::vector<uint16_t> outText;
+ for (uint16_t c : utf16) {
+ switch (c) {
+ case '(':
+ range.setStart(outText.size());
+ break;
+ case ')':
+ range.setEnd(outText.size());
+ break;
+ default:
+ outText.push_back(c);
+ }
+ }
+ return std::make_pair(outText, range);
+}
+
+std::pair<Range, Range> parseExpectString(const std::string& text) {
+ auto utf16 = utf8ToUtf16(text);
+ Range context;
+ Range piece;
+ uint32_t textPos = 0;
+ for (uint16_t c : utf16) {
+ switch (c) {
+ case '[':
+ context.setStart(textPos);
+ break;
+ case ']':
+ context.setEnd(textPos);
+ break;
+ case '(':
+ piece.setStart(textPos);
+ break;
+ case ')':
+ piece.setEnd(textPos);
+ break;
+ default:
+ textPos++;
+ }
+ }
+ return std::make_pair(context, piece);
+}
+
+std::string buildDebugString(const U16StringPiece& textBuf, const Range& context,
+ const Range& piece) {
+ std::vector<uint16_t> out;
+ out.reserve(textBuf.size() + 4);
+ for (uint32_t i = 0; i < textBuf.size() + 1; ++i) {
+ if (i == context.getStart()) {
+ out.push_back('[');
+ }
+ if (i == piece.getStart()) {
+ out.push_back('(');
+ }
+ if (i == piece.getEnd()) {
+ out.push_back(')');
+ }
+ if (i == context.getEnd()) {
+ out.push_back(']');
+ }
+ if (i != textBuf.size()) {
+ out.push_back(textBuf[i]);
+ }
+ }
+ return utf16ToUtf8(out);
+}
+
+TEST(LayoutSplitterTest, LTR_Latin) {
+ struct TestCase {
+ std::string testStr;
+ std::vector<std::string> expects;
+ } testCases[] = {
+ {"(This is an example text.)",
+ {
+ "[(This)] is an example text.", "This[( )]is an example text.",
+ "This [(is)] an example text.", "This is[( )]an example text.",
+ "This is [(an)] example text.", "This is an[( )]example text.",
+ "This is an [(example)] text.", "This is an example[( )]text.",
+ "This is an example [(text.)]",
+ }},
+ {"This( is an example )text.",
+ {
+ "This[( )]is an example text.", "This [(is)] an example text.",
+ "This is[( )]an example text.", "This is [(an)] example text.",
+ "This is an[( )]example text.", "This is an [(example)] text.",
+ "This is an example[( )]text.",
+ }},
+ {"This (is an example) text.",
+ {
+ "This [(is)] an example text.", "This is[( )]an example text.",
+ "This is [(an)] example text.", "This is an[( )]example text.",
+ "This is an [(example)] text.",
+ }},
+ {"Th(is is an example te)xt.",
+ {
+ "[Th(is)] is an example text.", "This[( )]is an example text.",
+ "This [(is)] an example text.", "This is[( )]an example text.",
+ "This is [(an)] example text.", "This is an[( )]example text.",
+ "This is an [(example)] text.", "This is an example[( )]text.",
+ "This is an example [(te)xt.]",
+ }},
+ {"This is an ex(amp)le text.",
+ {
+ "This is an [ex(amp)le] text.",
+ }},
+ {"There are (three spaces.)",
+ {
+ "There are [(three)] spaces.", "There are three[( )] spaces.",
+ "There are three [( )] spaces.", "There are three [( )]spaces.",
+ "There are three [(spaces.)]",
+ }},
+ };
+
+ for (const auto& testCase : testCases) {
+ auto[text, range] = parseTestString(testCase.testStr);
+ uint32_t expectationIndex = 0;
+ for (auto[acContext, acPiece] : LayoutSplitter(text, range, false /* isRtl */)) {
+ ASSERT_NE(expectationIndex, testCase.expects.size());
+ const std::string expectString = testCase.expects[expectationIndex++];
+ auto[exContext, exPiece] = parseExpectString(expectString);
+ EXPECT_EQ(acContext, exContext)
+ << expectString << " vs " << buildDebugString(text, acContext, acPiece);
+ EXPECT_EQ(acPiece, exPiece)
+ << expectString << " vs " << buildDebugString(text, acContext, acPiece);
+ }
+ EXPECT_EQ(expectationIndex, testCase.expects.size()) << "Expectations Remains";
+ }
+}
+
+TEST(LayoutSplitterTest, RTL_Latin) {
+ struct TestCase {
+ std::string testStr;
+ std::vector<std::string> expects;
+ } testCases[] = {
+ {"(This is an example text.)",
+ {
+ "This is an example [(text.)]", "This is an example[( )]text.",
+ "This is an [(example)] text.", "This is an[( )]example text.",
+ "This is [(an)] example text.", "This is[( )]an example text.",
+ "This [(is)] an example text.", "This[( )]is an example text.",
+ "[(This)] is an example text.",
+ }},
+ {"This( is an example )text.",
+ {
+ "This is an example[( )]text.", "This is an [(example)] text.",
+ "This is an[( )]example text.", "This is [(an)] example text.",
+ "This is[( )]an example text.", "This [(is)] an example text.",
+ "This[( )]is an example text.",
+ }},
+ {"This (is an example) text.",
+ {
+ "This is an [(example)] text.", "This is an[( )]example text.",
+ "This is [(an)] example text.", "This is[( )]an example text.",
+ "This [(is)] an example text.",
+ }},
+ {"Th(is is an example te)xt.",
+ {
+ "This is an example [(te)xt.]", "This is an example[( )]text.",
+ "This is an [(example)] text.", "This is an[( )]example text.",
+ "This is [(an)] example text.", "This is[( )]an example text.",
+ "This [(is)] an example text.", "This[( )]is an example text.",
+ "[Th(is)] is an example text.",
+ }},
+ {"This is an ex(amp)le text.",
+ {
+ "This is an [ex(amp)le] text.",
+ }},
+ {"There are (three spaces.)",
+ {
+ "There are three [(spaces.)]", "There are three [( )]spaces.",
+ "There are three [( )] spaces.", "There are three[( )] spaces.",
+ "There are [(three)] spaces.",
+ }},
+ };
+
+ for (const auto& testCase : testCases) {
+ auto[text, range] = parseTestString(testCase.testStr);
+ uint32_t expectationIndex = 0;
+ for (auto[acContext, acPiece] : LayoutSplitter(text, range, true /* isRtl */)) {
+ ASSERT_NE(expectationIndex, testCase.expects.size());
+ const std::string expectString = testCase.expects[expectationIndex++];
+ auto[exContext, exPiece] = parseExpectString(expectString);
+ EXPECT_EQ(acContext, exContext)
+ << expectString << " vs " << buildDebugString(text, acContext, acPiece);
+ EXPECT_EQ(acPiece, exPiece)
+ << expectString << " vs " << buildDebugString(text, acContext, acPiece);
+ }
+ EXPECT_EQ(expectationIndex, testCase.expects.size()) << "Expectations Remains";
+ }
+}
+
+TEST(LayoutSplitterTest, LTR_CJK) {
+ struct TestCase {
+ std::string testStr;
+ std::vector<std::string> expects;
+ } testCases[] = {
+ {// All Kanji text
+ "(\u6614\u8005\u8358\u5468\u5922\u70BA\u80E1\u8776)",
+ {
+ "[(\u6614)]\u8005\u8358\u5468\u5922\u70BA\u80E1\u8776",
+ "\u6614[(\u8005)]\u8358\u5468\u5922\u70BA\u80E1\u8776",
+ "\u6614\u8005[(\u8358)]\u5468\u5922\u70BA\u80E1\u8776",
+ "\u6614\u8005\u8358[(\u5468)]\u5922\u70BA\u80E1\u8776",
+ "\u6614\u8005\u8358\u5468[(\u5922)]\u70BA\u80E1\u8776",
+ "\u6614\u8005\u8358\u5468\u5922[(\u70BA)]\u80E1\u8776",
+ "\u6614\u8005\u8358\u5468\u5922\u70BA[(\u80E1)]\u8776",
+ "\u6614\u8005\u8358\u5468\u5922\u70BA\u80E1[(\u8776)]",
+ }},
+ {// Japanese text like as follows
+ // [Kanji][Kanji][Kana][Kanji][Kanji][Kana][Kana][Kana]
+ "(\u672C\u65E5\u306F\u6674\u5929\u306A\u308A\u3002)",
+ {
+ "[(\u672C)]\u65E5\u306F\u6674\u5929\u306A\u308A\u3002",
+ "\u672C[(\u65E5\u306F)]\u6674\u5929\u306A\u308A\u3002",
+ "\u672C\u65E5\u306F[(\u6674)]\u5929\u306A\u308A\u3002",
+ "\u672C\u65E5\u306F\u6674[(\u5929\u306A\u308A\u3002)]",
+ }},
+ {// Japanese text like as follows
+ // [Kanji][Kanji][Kana][Kanji][Kanji][Kana][Kana][Kana]
+ "\u672C\u65E5(\u306F\u6674\u5929\u306A)\u308A\u3002",
+ {
+ "\u672C[\u65E5(\u306F)]\u6674\u5929\u306A\u308A\u3002",
+ "\u672C\u65E5\u306F[(\u6674)]\u5929\u306A\u308A\u3002",
+ "\u672C\u65E5\u306F\u6674[(\u5929\u306A)\u308A\u3002]",
+ }},
+ };
+
+ for (const auto& testCase : testCases) {
+ auto[text, range] = parseTestString(testCase.testStr);
+ uint32_t expectationIndex = 0;
+ for (auto[acContext, acPiece] : LayoutSplitter(text, range, false /* isRtl */)) {
+ ASSERT_NE(expectationIndex, testCase.expects.size());
+ const std::string expectString = testCase.expects[expectationIndex++];
+ auto[exContext, exPiece] = parseExpectString(expectString);
+ EXPECT_EQ(acContext, exContext)
+ << expectString << " vs " << buildDebugString(text, acContext, acPiece);
+ EXPECT_EQ(acPiece, exPiece)
+ << expectString << " vs " << buildDebugString(text, acContext, acPiece);
+ }
+ EXPECT_EQ(expectationIndex, testCase.expects.size()) << "Expectations Remains";
+ }
+}
+
+TEST(LayoutSplitterTest, RTL_CJK) {
+ struct TestCase {
+ std::string testStr;
+ std::vector<std::string> expects;
+ } testCases[] = {
+ {// All Kanji text
+ "(\u6614\u8005\u8358\u5468\u5922\u70BA\u80E1\u8776)",
+ {
+ "\u6614\u8005\u8358\u5468\u5922\u70BA\u80E1[(\u8776)]",
+ "\u6614\u8005\u8358\u5468\u5922\u70BA[(\u80E1)]\u8776",
+ "\u6614\u8005\u8358\u5468\u5922[(\u70BA)]\u80E1\u8776",
+ "\u6614\u8005\u8358\u5468[(\u5922)]\u70BA\u80E1\u8776",
+ "\u6614\u8005\u8358[(\u5468)]\u5922\u70BA\u80E1\u8776",
+ "\u6614\u8005[(\u8358)]\u5468\u5922\u70BA\u80E1\u8776",
+ "\u6614[(\u8005)]\u8358\u5468\u5922\u70BA\u80E1\u8776",
+ "[(\u6614)]\u8005\u8358\u5468\u5922\u70BA\u80E1\u8776",
+ }},
+ {// Japanese text like as follows
+ // [Kanji][Kanji][Kana][Kanji][Kanji][Kana][Kana][Kana]
+ "(\u672C\u65E5\u306F\u6674\u5929\u306A\u308A\u3002)",
+ {
+ "\u672C\u65E5\u306F\u6674[(\u5929\u306A\u308A\u3002)]",
+ "\u672C\u65E5\u306F[(\u6674)]\u5929\u306A\u308A\u3002",
+ "\u672C[(\u65E5\u306F)]\u6674\u5929\u306A\u308A\u3002",
+ "[(\u672C)]\u65E5\u306F\u6674\u5929\u306A\u308A\u3002",
+ }},
+ {// Japanese text like as follows
+ // [Kanji][Kanji][Kana][Kanji][Kanji][Kana][Kana][Kana]
+ "\u672C\u65E5(\u306F\u6674\u5929\u306A)\u308A\u3002",
+ {
+ "\u672C\u65E5\u306F\u6674[(\u5929\u306A)\u308A\u3002]",
+ "\u672C\u65E5\u306F[(\u6674)]\u5929\u306A\u308A\u3002",
+ "\u672C[\u65E5(\u306F)]\u6674\u5929\u306A\u308A\u3002",
+ }},
+ };
+
+ for (const auto& testCase : testCases) {
+ auto[text, range] = parseTestString(testCase.testStr);
+ uint32_t expectationIndex = 0;
+ for (auto[acContext, acPiece] : LayoutSplitter(text, range, true /* isRtl */)) {
+ ASSERT_NE(expectationIndex, testCase.expects.size());
+ const std::string expectString = testCase.expects[expectationIndex++];
+ auto[exContext, exPiece] = parseExpectString(expectString);
+ EXPECT_EQ(acContext, exContext)
+ << expectString << " vs " << buildDebugString(text, acContext, acPiece);
+ EXPECT_EQ(acPiece, exPiece)
+ << expectString << " vs " << buildDebugString(text, acContext, acPiece);
+ }
+ EXPECT_EQ(expectationIndex, testCase.expects.size()) << "Expectations Remains";
+ }
+}
+
+} // namespace
+} // namespace minikin
diff --git a/tests/unittest/LayoutTest.cpp b/tests/unittest/LayoutTest.cpp
index 21109cb..4b97cee 100644
--- a/tests/unittest/LayoutTest.cpp
+++ b/tests/unittest/LayoutTest.cpp
@@ -26,41 +26,13 @@
namespace minikin {
-const float UNTOUCHED_MARKER = 1e+38;
-
-static void expectAdvances(std::vector<float> expected, float* advances, size_t length) {
- EXPECT_LE(expected.size(), length);
+static void expectAdvances(const std::vector<float>& expected, const std::vector<float>& advances) {
+ EXPECT_LE(expected.size(), advances.size());
for (size_t i = 0; i < expected.size(); ++i) {
EXPECT_EQ(expected[i], advances[i])
<< i << "th element is different. Expected: " << expected[i]
<< ", Actual: " << advances[i];
}
- EXPECT_EQ(UNTOUCHED_MARKER, advances[expected.size()]);
-}
-
-static void resetAdvances(float* advances, size_t length) {
- for (size_t i = 0; i < length; ++i) {
- advances[i] = UNTOUCHED_MARKER;
- }
-}
-
-static Layout doLayout(const std::string& text, const MinikinPaint& paint) {
- Layout layout;
- auto utf16 = utf8ToUtf16(text);
- Range range(0, utf16.size());
- layout.doLayout(utf16, range, Bidi::FORCE_LTR, paint, StartHyphenEdit::NO_EDIT,
- EndHyphenEdit::NO_EDIT);
- return layout;
-}
-
-static Layout doLayoutWithPrecomputedPieces(const std::string& text, const MinikinPaint& paint,
- const LayoutPieces& pieces) {
- Layout layout;
- auto utf16 = utf8ToUtf16(text);
- Range range(0, utf16.size());
- layout.doLayoutWithPrecomputedPieces(utf16, range, Bidi::FORCE_LTR, paint,
- StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, pieces);
- return layout;
}
class LayoutTest : public testing::Test {
@@ -80,11 +52,8 @@
MinikinPaint paint(mCollection);
paint.size = 10.0f; // make 1em = 10px
MinikinRect rect;
- const size_t kMaxAdvanceLength = 32;
- float advances[kMaxAdvanceLength];
std::vector<float> expectedValues;
- Layout layout;
std::vector<uint16_t> text;
// The mock implementation returns 10.0f advance and 0,0-10x10 bounds for all glyph.
@@ -92,81 +61,73 @@
SCOPED_TRACE("one word");
text = utf8ToUtf16("oneword");
Range range(0, text.size());
- layout.doLayout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
- EndHyphenEdit::NO_EDIT);
+ Layout layout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
+ EndHyphenEdit::NO_EDIT);
EXPECT_EQ(70.0f, layout.getAdvance());
layout.getBounds(&rect);
EXPECT_EQ(0.0f, rect.mLeft);
EXPECT_EQ(10.0f, rect.mTop);
EXPECT_EQ(70.0f, rect.mRight);
EXPECT_EQ(0.0f, rect.mBottom);
- resetAdvances(advances, kMaxAdvanceLength);
- layout.getAdvances(advances);
expectedValues.resize(text.size());
for (size_t i = 0; i < expectedValues.size(); ++i) {
expectedValues[i] = 10.0f;
}
- expectAdvances(expectedValues, advances, kMaxAdvanceLength);
+ expectAdvances(expectedValues, layout.getAdvances());
}
{
SCOPED_TRACE("two words");
text = utf8ToUtf16("two words");
Range range(0, text.size());
- layout.doLayout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
- EndHyphenEdit::NO_EDIT);
+ Layout layout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
+ EndHyphenEdit::NO_EDIT);
EXPECT_EQ(90.0f, layout.getAdvance());
layout.getBounds(&rect);
EXPECT_EQ(0.0f, rect.mLeft);
EXPECT_EQ(10.0f, rect.mTop);
EXPECT_EQ(90.0f, rect.mRight);
EXPECT_EQ(0.0f, rect.mBottom);
- resetAdvances(advances, kMaxAdvanceLength);
- layout.getAdvances(advances);
expectedValues.resize(text.size());
for (size_t i = 0; i < expectedValues.size(); ++i) {
expectedValues[i] = 10.0f;
}
- expectAdvances(expectedValues, advances, kMaxAdvanceLength);
+ expectAdvances(expectedValues, layout.getAdvances());
}
{
SCOPED_TRACE("three words");
text = utf8ToUtf16("three words test");
Range range(0, text.size());
- layout.doLayout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
- EndHyphenEdit::NO_EDIT);
+ Layout layout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
+ EndHyphenEdit::NO_EDIT);
EXPECT_EQ(160.0f, layout.getAdvance());
layout.getBounds(&rect);
EXPECT_EQ(0.0f, rect.mLeft);
EXPECT_EQ(10.0f, rect.mTop);
EXPECT_EQ(160.0f, rect.mRight);
EXPECT_EQ(0.0f, rect.mBottom);
- resetAdvances(advances, kMaxAdvanceLength);
- layout.getAdvances(advances);
expectedValues.resize(text.size());
for (size_t i = 0; i < expectedValues.size(); ++i) {
expectedValues[i] = 10.0f;
}
- expectAdvances(expectedValues, advances, kMaxAdvanceLength);
+ expectAdvances(expectedValues, layout.getAdvances());
}
{
SCOPED_TRACE("two spaces");
text = utf8ToUtf16("two spaces");
Range range(0, text.size());
- layout.doLayout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
- EndHyphenEdit::NO_EDIT);
+ Layout layout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
+ EndHyphenEdit::NO_EDIT);
EXPECT_EQ(110.0f, layout.getAdvance());
layout.getBounds(&rect);
EXPECT_EQ(0.0f, rect.mLeft);
EXPECT_EQ(10.0f, rect.mTop);
EXPECT_EQ(110.0f, rect.mRight);
EXPECT_EQ(0.0f, rect.mBottom);
- resetAdvances(advances, kMaxAdvanceLength);
- layout.getAdvances(advances);
expectedValues.resize(text.size());
for (size_t i = 0; i < expectedValues.size(); ++i) {
expectedValues[i] = 10.0f;
}
- expectAdvances(expectedValues, advances, kMaxAdvanceLength);
+ expectAdvances(expectedValues, layout.getAdvances());
}
}
@@ -174,13 +135,9 @@
MinikinPaint paint(mCollection);
paint.size = 10.0f; // make 1em = 10px
MinikinRect rect;
- const size_t kMaxAdvanceLength = 32;
- float advances[kMaxAdvanceLength];
std::vector<float> expectedValues;
std::vector<uint16_t> text;
- Layout layout;
-
paint.wordSpacing = 5.0f;
// The mock implementation returns 10.0f advance and 0,0-10x10 bounds for all glyph.
@@ -188,89 +145,78 @@
SCOPED_TRACE("one word");
text = utf8ToUtf16("oneword");
Range range(0, text.size());
- layout.doLayout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
- EndHyphenEdit::NO_EDIT);
+ Layout layout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
+ EndHyphenEdit::NO_EDIT);
EXPECT_EQ(70.0f, layout.getAdvance());
layout.getBounds(&rect);
EXPECT_EQ(0.0f, rect.mLeft);
EXPECT_EQ(10.0f, rect.mTop);
EXPECT_EQ(70.0f, rect.mRight);
EXPECT_EQ(0.0f, rect.mBottom);
- resetAdvances(advances, kMaxAdvanceLength);
- layout.getAdvances(advances);
expectedValues.resize(text.size());
for (size_t i = 0; i < expectedValues.size(); ++i) {
expectedValues[i] = 10.0f;
}
- expectAdvances(expectedValues, advances, kMaxAdvanceLength);
+ expectAdvances(expectedValues, layout.getAdvances());
}
{
SCOPED_TRACE("two words");
text = utf8ToUtf16("two words");
Range range(0, text.size());
- layout.doLayout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
- EndHyphenEdit::NO_EDIT);
+ Layout layout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
+ EndHyphenEdit::NO_EDIT);
EXPECT_EQ(95.0f, layout.getAdvance());
layout.getBounds(&rect);
EXPECT_EQ(0.0f, rect.mLeft);
EXPECT_EQ(10.0f, rect.mTop);
EXPECT_EQ(95.0f, rect.mRight);
EXPECT_EQ(0.0f, rect.mBottom);
- resetAdvances(advances, kMaxAdvanceLength);
- layout.getAdvances(advances);
- EXPECT_EQ(UNTOUCHED_MARKER, advances[text.size()]);
- resetAdvances(advances, kMaxAdvanceLength);
- layout.getAdvances(advances);
expectedValues.resize(text.size());
for (size_t i = 0; i < expectedValues.size(); ++i) {
expectedValues[i] = 10.0f;
}
expectedValues[3] = 15.0f;
- expectAdvances(expectedValues, advances, kMaxAdvanceLength);
+ expectAdvances(expectedValues, layout.getAdvances());
}
{
SCOPED_TRACE("three words test");
text = utf8ToUtf16("three words test");
Range range(0, text.size());
- layout.doLayout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
- EndHyphenEdit::NO_EDIT);
+ Layout layout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
+ EndHyphenEdit::NO_EDIT);
EXPECT_EQ(170.0f, layout.getAdvance());
layout.getBounds(&rect);
EXPECT_EQ(0.0f, rect.mLeft);
EXPECT_EQ(10.0f, rect.mTop);
EXPECT_EQ(170.0f, rect.mRight);
EXPECT_EQ(0.0f, rect.mBottom);
- resetAdvances(advances, kMaxAdvanceLength);
- layout.getAdvances(advances);
expectedValues.resize(text.size());
for (size_t i = 0; i < expectedValues.size(); ++i) {
expectedValues[i] = 10.0f;
}
expectedValues[5] = 15.0f;
expectedValues[11] = 15.0f;
- expectAdvances(expectedValues, advances, kMaxAdvanceLength);
+ expectAdvances(expectedValues, layout.getAdvances());
}
{
SCOPED_TRACE("two spaces");
text = utf8ToUtf16("two spaces");
Range range(0, text.size());
- layout.doLayout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
- EndHyphenEdit::NO_EDIT);
+ Layout layout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
+ EndHyphenEdit::NO_EDIT);
EXPECT_EQ(120.0f, layout.getAdvance());
layout.getBounds(&rect);
EXPECT_EQ(0.0f, rect.mLeft);
EXPECT_EQ(10.0f, rect.mTop);
EXPECT_EQ(120.0f, rect.mRight);
EXPECT_EQ(0.0f, rect.mBottom);
- resetAdvances(advances, kMaxAdvanceLength);
- layout.getAdvances(advances);
expectedValues.resize(text.size());
for (size_t i = 0; i < expectedValues.size(); ++i) {
expectedValues[i] = 10.0f;
}
expectedValues[3] = 15.0f;
expectedValues[4] = 15.0f;
- expectAdvances(expectedValues, advances, kMaxAdvanceLength);
+ expectAdvances(expectedValues, layout.getAdvances());
}
}
@@ -278,11 +224,8 @@
MinikinPaint paint(mCollection);
paint.size = 10.0f; // make 1em = 10px
MinikinRect rect;
- const size_t kMaxAdvanceLength = 32;
- float advances[kMaxAdvanceLength];
std::vector<float> expectedValues;
- Layout layout;
std::vector<uint16_t> text;
// Negative word spacing also should work.
@@ -292,86 +235,78 @@
SCOPED_TRACE("one word");
text = utf8ToUtf16("oneword");
Range range(0, text.size());
- layout.doLayout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
- EndHyphenEdit::NO_EDIT);
+ Layout layout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
+ EndHyphenEdit::NO_EDIT);
EXPECT_EQ(70.0f, layout.getAdvance());
layout.getBounds(&rect);
EXPECT_EQ(0.0f, rect.mLeft);
EXPECT_EQ(10.0f, rect.mTop);
EXPECT_EQ(70.0f, rect.mRight);
EXPECT_EQ(0.0f, rect.mBottom);
- resetAdvances(advances, kMaxAdvanceLength);
- layout.getAdvances(advances);
expectedValues.resize(text.size());
for (size_t i = 0; i < expectedValues.size(); ++i) {
expectedValues[i] = 10.0f;
}
- expectAdvances(expectedValues, advances, kMaxAdvanceLength);
+ expectAdvances(expectedValues, layout.getAdvances());
}
{
SCOPED_TRACE("two words");
text = utf8ToUtf16("two words");
Range range(0, text.size());
- layout.doLayout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
- EndHyphenEdit::NO_EDIT);
+ Layout layout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
+ EndHyphenEdit::NO_EDIT);
EXPECT_EQ(85.0f, layout.getAdvance());
layout.getBounds(&rect);
EXPECT_EQ(0.0f, rect.mLeft);
EXPECT_EQ(10.0f, rect.mTop);
EXPECT_EQ(85.0f, rect.mRight);
EXPECT_EQ(0.0f, rect.mBottom);
- resetAdvances(advances, kMaxAdvanceLength);
- layout.getAdvances(advances);
expectedValues.resize(text.size());
for (size_t i = 0; i < expectedValues.size(); ++i) {
expectedValues[i] = 10.0f;
}
expectedValues[3] = 5.0f;
- expectAdvances(expectedValues, advances, kMaxAdvanceLength);
+ expectAdvances(expectedValues, layout.getAdvances());
}
{
SCOPED_TRACE("three words");
text = utf8ToUtf16("three word test");
Range range(0, text.size());
- layout.doLayout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
- EndHyphenEdit::NO_EDIT);
+ Layout layout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
+ EndHyphenEdit::NO_EDIT);
EXPECT_EQ(140.0f, layout.getAdvance());
layout.getBounds(&rect);
EXPECT_EQ(0.0f, rect.mLeft);
EXPECT_EQ(10.0f, rect.mTop);
EXPECT_EQ(140.0f, rect.mRight);
EXPECT_EQ(0.0f, rect.mBottom);
- resetAdvances(advances, kMaxAdvanceLength);
- layout.getAdvances(advances);
expectedValues.resize(text.size());
for (size_t i = 0; i < expectedValues.size(); ++i) {
expectedValues[i] = 10.0f;
}
expectedValues[5] = 5.0f;
expectedValues[10] = 5.0f;
- expectAdvances(expectedValues, advances, kMaxAdvanceLength);
+ expectAdvances(expectedValues, layout.getAdvances());
}
{
SCOPED_TRACE("two spaces");
text = utf8ToUtf16("two spaces");
Range range(0, text.size());
- layout.doLayout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
- EndHyphenEdit::NO_EDIT);
+ Layout layout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
+ EndHyphenEdit::NO_EDIT);
EXPECT_EQ(100.0f, layout.getAdvance());
layout.getBounds(&rect);
EXPECT_EQ(0.0f, rect.mLeft);
EXPECT_EQ(10.0f, rect.mTop);
EXPECT_EQ(100.0f, rect.mRight);
EXPECT_EQ(0.0f, rect.mBottom);
- resetAdvances(advances, kMaxAdvanceLength);
- layout.getAdvances(advances);
expectedValues.resize(text.size());
for (size_t i = 0; i < expectedValues.size(); ++i) {
expectedValues[i] = 10.0f;
}
expectedValues[3] = 5.0f;
expectedValues[4] = 5.0f;
- expectAdvances(expectedValues, advances, kMaxAdvanceLength);
+ expectAdvances(expectedValues, layout.getAdvances());
}
}
@@ -382,13 +317,11 @@
std::vector<uint16_t> text = parseUnicodeString("'a' 'b' U+3042 U+3043 'c' 'd'");
Range range(0, text.size());
- Layout ltrLayout;
- ltrLayout.doLayout(text, range, Bidi::FORCE_LTR, paint, StartHyphenEdit::NO_EDIT,
- EndHyphenEdit::NO_EDIT);
+ Layout ltrLayout(text, range, Bidi::FORCE_LTR, paint, StartHyphenEdit::NO_EDIT,
+ EndHyphenEdit::NO_EDIT);
- Layout rtlLayout;
- rtlLayout.doLayout(text, range, Bidi::FORCE_RTL, paint, StartHyphenEdit::NO_EDIT,
- EndHyphenEdit::NO_EDIT);
+ Layout rtlLayout(text, range, Bidi::FORCE_RTL, paint, StartHyphenEdit::NO_EDIT,
+ EndHyphenEdit::NO_EDIT);
ASSERT_EQ(ltrLayout.nGlyphs(), rtlLayout.nGlyphs());
ASSERT_EQ(6u, ltrLayout.nGlyphs());
@@ -407,17 +340,14 @@
std::vector<uint16_t> text = parseUnicodeString("'1' '2' '3'");
Range range(0, text.size());
- Layout ltrLayout;
- ltrLayout.doLayout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
- EndHyphenEdit::NO_EDIT);
+ Layout ltrLayout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
+ EndHyphenEdit::NO_EDIT);
- Layout rtlLayout;
- rtlLayout.doLayout(text, range, Bidi::RTL, paint, StartHyphenEdit::NO_EDIT,
- EndHyphenEdit::NO_EDIT);
+ Layout rtlLayout(text, range, Bidi::RTL, paint, StartHyphenEdit::NO_EDIT,
+ EndHyphenEdit::NO_EDIT);
- Layout defaultRtlLayout;
- defaultRtlLayout.doLayout(text, range, Bidi::DEFAULT_RTL, paint, StartHyphenEdit::NO_EDIT,
- EndHyphenEdit::NO_EDIT);
+ Layout defaultRtlLayout(text, range, Bidi::DEFAULT_RTL, paint, StartHyphenEdit::NO_EDIT,
+ EndHyphenEdit::NO_EDIT);
const size_t nGlyphs = ltrLayout.nGlyphs();
ASSERT_EQ(3u, nGlyphs);
@@ -436,7 +366,6 @@
TEST_F(LayoutTest, hyphenationTest) {
MinikinPaint paint(mCollection);
paint.size = 10.0f; // make 1em = 10px
- Layout layout;
std::vector<uint16_t> text;
// The mock implementation returns 10.0f advance for all glyphs.
@@ -444,62 +373,44 @@
SCOPED_TRACE("one word with no hyphen edit");
text = utf8ToUtf16("oneword");
Range range(0, text.size());
- layout.doLayout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
- EndHyphenEdit::NO_EDIT);
+ Layout layout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
+ EndHyphenEdit::NO_EDIT);
EXPECT_EQ(70.0f, layout.getAdvance());
}
{
SCOPED_TRACE("one word with hyphen insertion at the end");
text = utf8ToUtf16("oneword");
Range range(0, text.size());
- layout.doLayout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
- EndHyphenEdit::INSERT_HYPHEN);
+ Layout layout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
+ EndHyphenEdit::INSERT_HYPHEN);
EXPECT_EQ(80.0f, layout.getAdvance());
}
{
SCOPED_TRACE("one word with hyphen replacement at the end");
text = utf8ToUtf16("oneword");
Range range(0, text.size());
- layout.doLayout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
- EndHyphenEdit::REPLACE_WITH_HYPHEN);
+ Layout layout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
+ EndHyphenEdit::REPLACE_WITH_HYPHEN);
EXPECT_EQ(70.0f, layout.getAdvance());
}
{
SCOPED_TRACE("one word with hyphen insertion at the start");
text = utf8ToUtf16("oneword");
Range range(0, text.size());
- layout.doLayout(text, range, Bidi::LTR, paint, StartHyphenEdit::INSERT_HYPHEN,
- EndHyphenEdit::NO_EDIT);
+ Layout layout(text, range, Bidi::LTR, paint, StartHyphenEdit::INSERT_HYPHEN,
+ EndHyphenEdit::NO_EDIT);
EXPECT_EQ(80.0f, layout.getAdvance());
}
{
SCOPED_TRACE("one word with hyphen insertion at the both ends");
text = utf8ToUtf16("oneword");
Range range(0, text.size());
- layout.doLayout(text, range, Bidi::LTR, paint, StartHyphenEdit::INSERT_HYPHEN,
- EndHyphenEdit::INSERT_HYPHEN);
+ Layout layout(text, range, Bidi::LTR, paint, StartHyphenEdit::INSERT_HYPHEN,
+ EndHyphenEdit::INSERT_HYPHEN);
EXPECT_EQ(90.0f, layout.getAdvance());
}
}
-TEST_F(LayoutTest, verticalExtentTest) {
- MinikinPaint paint(mCollection);
-
- std::vector<uint16_t> text = utf8ToUtf16("ab");
- Range range(0, text.size());
-
- Layout layout;
- layout.doLayout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
- EndHyphenEdit::NO_EDIT);
- MinikinExtent extents[text.size()];
- layout.getExtents(extents);
- for (size_t i = 0; i < text.size(); i++) {
- EXPECT_EQ(-10.0f, extents[i].ascent);
- EXPECT_EQ(20.0f, extents[i].descent);
- EXPECT_EQ(0.0f, extents[i].line_gap);
- }
-}
-
TEST_F(LayoutTest, measuredTextTest) {
// The test font has following coverage and width.
// U+0020: 10em
@@ -518,9 +429,8 @@
std::vector<uint16_t> text = utf8ToUtf16("I");
std::vector<float> advances(text.size());
Range range(0, text.size());
- EXPECT_EQ(1.0f,
- Layout::measureText(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
- EndHyphenEdit::NO_EDIT, advances.data(), nullptr, nullptr));
+ EXPECT_EQ(1.0f, Layout::measureText(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
+ EndHyphenEdit::NO_EDIT, advances.data()));
ASSERT_EQ(1u, advances.size());
EXPECT_EQ(1.0f, advances[0]);
}
@@ -529,9 +439,8 @@
std::vector<uint16_t> text = utf8ToUtf16("IV");
std::vector<float> advances(text.size());
Range range(0, text.size());
- EXPECT_EQ(6.0f,
- Layout::measureText(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
- EndHyphenEdit::NO_EDIT, advances.data(), nullptr, nullptr));
+ EXPECT_EQ(6.0f, Layout::measureText(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
+ EndHyphenEdit::NO_EDIT, advances.data()));
ASSERT_EQ(2u, advances.size());
EXPECT_EQ(1.0f, advances[0]);
EXPECT_EQ(5.0f, advances[1]);
@@ -543,7 +452,7 @@
Range range(0, text.size());
EXPECT_EQ(16.0f,
Layout::measureText(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
- EndHyphenEdit::NO_EDIT, advances.data(), nullptr, nullptr));
+ EndHyphenEdit::NO_EDIT, advances.data()));
ASSERT_EQ(3u, advances.size());
EXPECT_EQ(1.0f, advances[0]);
EXPECT_EQ(5.0f, advances[1]);
@@ -551,66 +460,6 @@
}
}
-TEST_F(LayoutTest, doLayoutWithPrecomputedPiecesTest) {
- float MARKER1 = 1e+16;
- float MARKER2 = 1e+17;
- auto fc = buildFontCollection("LayoutTestFont.ttf");
- MinikinPaint paint(fc);
- {
- LayoutPieces pieces;
-
- Layout inLayout = doLayout("I", MinikinPaint(fc));
- inLayout.mAdvances[0] = MARKER1; // Modify the advance to make sure this layout is used.
- pieces.insert(utf8ToUtf16("I"), Range(0, 1), paint, false /* dir */,
- StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, inLayout);
-
- Layout outLayout = doLayoutWithPrecomputedPieces("I", MinikinPaint(fc), pieces);
- EXPECT_EQ(MARKER1, outLayout.mAdvances[0]);
- }
- {
- LayoutPieces pieces;
-
- Layout inLayout = doLayout("I", MinikinPaint(fc));
- inLayout.mAdvances[0] = MARKER1;
- pieces.insert(utf8ToUtf16("I"), Range(0, 1), paint, false /* dir */,
- StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, inLayout);
-
- Layout outLayout = doLayoutWithPrecomputedPieces("II", MinikinPaint(fc), pieces);
- // The layout pieces are used in word units. Should not be used "I" for "II".
- EXPECT_NE(MARKER1, outLayout.mAdvances[0]);
- EXPECT_NE(MARKER1, outLayout.mAdvances[1]);
- }
- {
- LayoutPieces pieces;
-
- Layout inLayout = doLayout("I", MinikinPaint(fc));
- inLayout.mAdvances[0] = MARKER1;
- pieces.insert(utf8ToUtf16("I"), Range(0, 1), paint, false /* dir */,
- StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, inLayout);
-
- Layout outLayout = doLayoutWithPrecomputedPieces("I I", MinikinPaint(fc), pieces);
- EXPECT_EQ(MARKER1, outLayout.mAdvances[0]);
- EXPECT_EQ(MARKER1, outLayout.mAdvances[2]);
- }
- {
- LayoutPieces pieces;
-
- Layout inLayout = doLayout("I", MinikinPaint(fc));
- inLayout.mAdvances[0] = MARKER1; // Modify the advance to make sure this layout is used.
- pieces.insert(utf8ToUtf16("I"), Range(0, 1), paint, false /* dir */,
- StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, inLayout);
-
- inLayout = doLayout("V", MinikinPaint(fc));
- inLayout.mAdvances[0] = MARKER2; // Modify the advance to make sure this layout is used.
- pieces.insert(utf8ToUtf16("V"), Range(0, 1), paint, false /* dir */,
- StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, inLayout);
-
- Layout outLayout = doLayoutWithPrecomputedPieces("I V", MinikinPaint(fc), pieces);
- EXPECT_EQ(MARKER1, outLayout.mAdvances[0]);
- EXPECT_EQ(MARKER2, outLayout.mAdvances[2]);
- }
-}
-
// TODO: Add more test cases, e.g. measure text, letter spacing.
} // namespace minikin
diff --git a/tests/unittest/LayoutUtilsTest.cpp b/tests/unittest/LayoutUtilsTest.cpp
index 4c18cce..79aa01e 100644
--- a/tests/unittest/LayoutUtilsTest.cpp
+++ b/tests/unittest/LayoutUtilsTest.cpp
@@ -29,7 +29,7 @@
size_t size = 0U;
ParseUnicode(buf, BUF_SIZE, query_str, &size, &expected_breakpoint);
- EXPECT_EQ(expected_breakpoint, getNextWordBreakForCache(buf, offset_in, size))
+ EXPECT_EQ(expected_breakpoint, getNextWordBreakForCache(U16StringPiece(buf, size), offset_in))
<< "Expected position is [" << query_str << "] from offset " << offset_in;
}
@@ -40,7 +40,7 @@
size_t size = 0U;
ParseUnicode(buf, BUF_SIZE, query_str, &size, &expected_breakpoint);
- EXPECT_EQ(expected_breakpoint, getPrevWordBreakForCache(buf, offset_in, size))
+ EXPECT_EQ(expected_breakpoint, getPrevWordBreakForCache(U16StringPiece(buf, size), offset_in))
<< "Expected position is [" << query_str << "] from offset " << offset_in;
}
diff --git a/tests/unittest/LineBreakerTestHelper.h b/tests/unittest/LineBreakerTestHelper.h
index 70d6521..9d973d5 100644
--- a/tests/unittest/LineBreakerTestHelper.h
+++ b/tests/unittest/LineBreakerTestHelper.h
@@ -33,8 +33,6 @@
float getAt(size_t) const override { return mWidth; }
float getMin() const override { return mWidth; }
- float getLeftPaddingAt(size_t) const override { return 0; }
- float getRightPaddingAt(size_t) const override { return 0; }
private:
float mWidth;
@@ -43,18 +41,24 @@
// The run implemenataion for returning the same width for all characters.
class ConstantRun : public Run {
public:
- ConstantRun(const Range& range, const std::string& lang, float width)
- : Run(range), mPaint(nullptr /* font collection */), mWidth(width) {
+ ConstantRun(const Range& range, const std::string& lang, float width, float ascent,
+ float descent)
+ : Run(range),
+ mPaint(nullptr /* font collection */),
+ mWidth(width),
+ mAscent(ascent),
+ mDescent(descent) {
mLocaleListId = LocaleListCache::getId(lang);
}
virtual bool isRtl() const override { return false; }
- virtual bool canHyphenate() const override { return true; }
+ virtual bool canBreak() const override { return true; }
virtual uint32_t getLocaleListId() const { return mLocaleListId; }
- virtual void getMetrics(const U16StringPiece&, float* advances, MinikinExtent*,
+ virtual void getMetrics(const U16StringPiece&, std::vector<float>* advances, LayoutPieces*,
LayoutPieces*) const {
- std::fill(advances, advances + mRange.getLength(), mWidth);
+ std::fill(advances->begin() + mRange.getStart(), advances->begin() + mRange.getEnd(),
+ mWidth);
}
virtual std::pair<float, MinikinRect> getBounds(const U16StringPiece& /* text */,
@@ -63,10 +67,15 @@
return std::make_pair(mWidth, MinikinRect());
}
+ virtual MinikinExtent getExtent(const U16StringPiece& /* text */, const Range& /* range */,
+ const LayoutPieces& /* pieces */) const override {
+ return {mAscent, mDescent};
+ }
+
virtual const MinikinPaint* getPaint() const { return &mPaint; }
virtual float measureHyphenPiece(const U16StringPiece&, const Range& range,
- StartHyphenEdit start, EndHyphenEdit end, float*,
+ StartHyphenEdit start, EndHyphenEdit end,
LayoutPieces*) const {
uint32_t extraCharForHyphen = 0;
if (isInsertion(start)) {
@@ -78,21 +87,25 @@
return mWidth * (range.getLength() + extraCharForHyphen);
}
+ virtual void appendLayout(const U16StringPiece&, const Range&, const Range&,
+ const LayoutPieces&, const MinikinPaint&, uint32_t, StartHyphenEdit,
+ EndHyphenEdit, Layout*) const {}
+
private:
MinikinPaint mPaint;
uint32_t mLocaleListId;
float mWidth;
+ float mAscent;
+ float mDescent;
};
struct LineBreakExpectation {
- LineBreakExpectation(const std::string& lineContent, float width, StartHyphenEdit startEdit,
- EndHyphenEdit endEdit)
- : mLineContent(lineContent), mWidth(width), mStartEdit(startEdit), mEndEdit(endEdit){};
-
std::string mLineContent;
float mWidth;
StartHyphenEdit mStartEdit;
EndHyphenEdit mEndEdit;
+ float mAscent;
+ float mDescent;
};
static bool sameLineBreak(const std::vector<LineBreakExpectation>& expected,
@@ -134,6 +147,12 @@
if (expected[i].mEndEdit != endHyphenEdit(edit)) {
return false;
}
+ if (expected[i].mAscent != actual.ascents[i]) {
+ return false;
+ }
+ if (expected[i].mDescent != actual.descents[i]) {
+ return false;
+ }
}
return true;
}
@@ -146,8 +165,9 @@
char lineMsg[128] = {};
snprintf(lineMsg, sizeof(lineMsg),
- "Line %2d, Width: %5.1f, Hyphen(%hhu, %hhu), Text: \"%s\"\n", i, line.mWidth,
- line.mStartEdit, line.mEndEdit, line.mLineContent.c_str());
+ "Line %2d, Width: %5.1f, Hyphen(%hhu, %hhu), Extent(%5.1f, %5.1f), Text: \"%s\"\n",
+ i, line.mWidth, line.mStartEdit, line.mEndEdit, line.mAscent, line.mDescent,
+ line.mLineContent.c_str());
out += lineMsg;
}
return out;
@@ -172,8 +192,9 @@
}
char lineMsg[128] = {};
snprintf(lineMsg, sizeof(lineMsg),
- "Line %2d, Width: %5.1f, Hyphen(%hhu, %hhu), Text: \"%s\"\n", i, lines.widths[i],
- startEdit, endEdit, hyphenatedStr.c_str());
+ "Line %2d, Width: %5.1f, Hyphen(%hhu, %hhu), Extent(%5.1f, %5.1f), Text: \"%s\"\n",
+ i, lines.widths[i], startEdit, endEdit, lines.ascents[i], lines.descents[i],
+ hyphenatedStr.c_str());
out += lineMsg;
}
return out;
diff --git a/tests/unittest/MeasuredTextTest.cpp b/tests/unittest/MeasuredTextTest.cpp
index e8ed408..1934ed8 100644
--- a/tests/unittest/MeasuredTextTest.cpp
+++ b/tests/unittest/MeasuredTextTest.cpp
@@ -45,7 +45,8 @@
std::vector<uint16_t> text(CHAR_COUNT, 'a');
std::unique_ptr<MeasuredText> measuredText =
- builder.build(text, true /* compute hyphenation */, false /* compute full layout */);
+ builder.build(text, true /* compute hyphenation */, false /* compute full layout */,
+ nullptr /* no hint */);
ASSERT_TRUE(measuredText);
@@ -56,4 +57,410 @@
EXPECT_EQ(expectedWidths, measuredText->widths);
}
+TEST(MeasuredTextTest, getBoundsTest) {
+ auto text = utf8ToUtf16("Hello, World!");
+ auto font = buildFontCollection("Ascii.ttf");
+
+ MeasuredTextBuilder builder;
+ MinikinPaint paint(font);
+ paint.size = 10.0f;
+ builder.addStyleRun(0, text.size(), std::move(paint), false /* is RTL */);
+ auto mt = builder.build(text, true /* hyphenation */, true /* full layout */,
+ 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)));
+ EXPECT_EQ(MinikinRect(0.0f, 10.0f, 20.0f, 0.0f), mt->getBounds(text, Range(0, 2)));
+ EXPECT_EQ(MinikinRect(0.0f, 10.0f, 10.0f, 0.0f), mt->getBounds(text, Range(1, 2)));
+ EXPECT_EQ(MinikinRect(0.0f, 10.0f, 130.0f, 0.0f), mt->getBounds(text, Range(0, text.size())));
+}
+
+TEST(MeasuredTextTest, getBoundsTest_multiStyle) {
+ auto text = utf8ToUtf16("Hello, World!");
+ auto font = buildFontCollection("Ascii.ttf");
+ uint32_t helloLength = 7; // length of "Hello, "
+
+ MeasuredTextBuilder builder;
+ MinikinPaint paint(font);
+ paint.size = 10.0f;
+ builder.addStyleRun(0, helloLength, std::move(paint), false /* is RTL */);
+ MinikinPaint paint2(font);
+ paint2.size = 20.0f;
+ builder.addStyleRun(helloLength, text.size(), std::move(paint2), false /* is RTL */);
+ auto mt = builder.build(text, true /* hyphenation */, true /* full layout */,
+ 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)));
+ EXPECT_EQ(MinikinRect(0.0f, 10.0f, 20.0f, 0.0f), mt->getBounds(text, Range(0, 2)));
+ EXPECT_EQ(MinikinRect(0.0f, 10.0f, 10.0f, 0.0f), mt->getBounds(text, Range(1, 2)));
+ EXPECT_EQ(MinikinRect(0.0f, 0.0f, 0.0f, 0.0f), mt->getBounds(text, Range(7, 7)));
+ EXPECT_EQ(MinikinRect(0.0f, 20.0f, 20.0f, 0.0f), mt->getBounds(text, Range(7, 8)));
+ EXPECT_EQ(MinikinRect(0.0f, 10.0f, 30.0f, 0.0f), mt->getBounds(text, Range(6, 8)));
+ EXPECT_EQ(MinikinRect(0.0f, 10.0f, 190.0f, 0.0f), mt->getBounds(text, Range(0, text.size())));
+}
+
+TEST(MeasuredTextTest, getExtentTest) {
+ auto text = utf8ToUtf16("Hello, World!");
+ auto font = buildFontCollection("Ascii.ttf");
+
+ MeasuredTextBuilder builder;
+ MinikinPaint paint(font);
+ paint.size = 10.0f;
+ builder.addStyleRun(0, text.size(), std::move(paint), false /* is RTL */);
+ auto mt = builder.build(text, true /* hyphenation */, true /* full layout */,
+ 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)));
+ EXPECT_EQ(MinikinExtent(-80.0f, 20.0f), mt->getExtent(text, Range(0, 2)));
+ EXPECT_EQ(MinikinExtent(-80.0f, 20.0f), mt->getExtent(text, Range(1, 2)));
+ EXPECT_EQ(MinikinExtent(-80.0f, 20.0f), mt->getExtent(text, Range(0, text.size())));
+}
+
+TEST(MeasuredTextTest, getExtentTest_multiStyle) {
+ auto text = utf8ToUtf16("Hello, World!");
+ auto font = buildFontCollection("Ascii.ttf");
+ uint32_t helloLength = 7; // length of "Hello, "
+
+ MeasuredTextBuilder builder;
+ MinikinPaint paint(font);
+ paint.size = 10.0f;
+ builder.addStyleRun(0, helloLength, std::move(paint), false /* is RTL */);
+ MinikinPaint paint2(font);
+ paint2.size = 20.0f;
+ builder.addStyleRun(helloLength, text.size(), std::move(paint2), false /* is RTL */);
+ auto mt = builder.build(text, true /* hyphenation */, true /* full layout */,
+ 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)));
+ EXPECT_EQ(MinikinExtent(-80.0f, 20.0f), mt->getExtent(text, Range(0, 2)));
+ EXPECT_EQ(MinikinExtent(-80.0f, 20.0f), mt->getExtent(text, Range(1, 2)));
+ EXPECT_EQ(MinikinExtent(0.0f, 0.0f), mt->getExtent(text, Range(7, 7)));
+ EXPECT_EQ(MinikinExtent(-160.0f, 40.0f), mt->getExtent(text, Range(7, 8)));
+ EXPECT_EQ(MinikinExtent(-160.0f, 40.0f), mt->getExtent(text, Range(6, 8)));
+ EXPECT_EQ(MinikinExtent(-160.0f, 40.0f), mt->getExtent(text, Range(0, text.size())));
+}
+
+TEST(MeasuredTextTest, buildLayoutTest) {
+ auto text = utf8ToUtf16("Hello, World!");
+ auto font = buildFontCollection("Ascii.ttf");
+ Range fullContext(0, text.size());
+
+ MeasuredTextBuilder builder;
+ MinikinPaint paint(font);
+ paint.size = 10.0f;
+ builder.addStyleRun(0, text.size(), std::move(paint), false /* is RTL */);
+ auto mt = builder.build(text, true /* hyphenation */, true /* full layout */,
+ nullptr /* no hint */);
+
+ MinikinPaint samePaint(font);
+ samePaint.size = 10.0f;
+
+ Layout layout = mt->buildLayout(text, Range(0, 0), fullContext, samePaint,
+ StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT);
+ EXPECT_EQ(0u, layout.nGlyphs());
+
+ layout = mt->buildLayout(text, Range(0, 1), fullContext, samePaint, StartHyphenEdit::NO_EDIT,
+ EndHyphenEdit::NO_EDIT);
+ ASSERT_EQ(1u, layout.nGlyphs());
+ EXPECT_TRUE(layout.getFont(0));
+ EXPECT_EQ(0.0f, layout.getX(0));
+ EXPECT_EQ(0.0f, layout.getY(0));
+ EXPECT_EQ(10.0f, layout.getAdvance());
+ EXPECT_EQ(10.0f, layout.getCharAdvance(0));
+ EXPECT_EQ(1u, layout.getAdvances().size());
+ EXPECT_EQ(MinikinRect(0.0f, 10.0f, 10.0f, 0.0f), layout.getBounds());
+
+ layout = mt->buildLayout(text, Range(0, 2), fullContext, samePaint, StartHyphenEdit::NO_EDIT,
+ EndHyphenEdit::NO_EDIT);
+ ASSERT_EQ(2u, layout.nGlyphs());
+ EXPECT_TRUE(layout.getFont(0) && layout.getFont(0) == layout.getFont(1));
+ EXPECT_EQ(0.0f, layout.getX(0));
+ EXPECT_EQ(0.0f, layout.getY(0));
+ EXPECT_EQ(10.0f, layout.getX(1));
+ EXPECT_EQ(0.0f, layout.getY(1));
+ EXPECT_EQ(20.0f, layout.getAdvance());
+ EXPECT_EQ(10.0f, layout.getCharAdvance(0));
+ EXPECT_EQ(10.0f, layout.getCharAdvance(1));
+ EXPECT_EQ(2u, layout.getAdvances().size());
+ EXPECT_EQ(MinikinRect(0.0f, 10.0f, 20.0f, 0.0f), layout.getBounds());
+
+ layout = mt->buildLayout(text, Range(1, 2), fullContext, samePaint, StartHyphenEdit::NO_EDIT,
+ EndHyphenEdit::NO_EDIT);
+ ASSERT_EQ(1u, layout.nGlyphs());
+ EXPECT_TRUE(layout.getFont(0));
+ EXPECT_EQ(0.0f, layout.getX(0));
+ EXPECT_EQ(0.0f, layout.getY(0));
+ EXPECT_EQ(10.0f, layout.getAdvance());
+ EXPECT_EQ(10.0f, layout.getCharAdvance(0));
+ EXPECT_EQ(1u, layout.getAdvances().size());
+ EXPECT_EQ(MinikinRect(0.0f, 10.0f, 10.0f, 0.0f), layout.getBounds());
+
+ layout = mt->buildLayout(text, Range(0, text.size()), fullContext, samePaint,
+ StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT);
+ ASSERT_EQ(text.size(), layout.nGlyphs());
+ EXPECT_TRUE(layout.getFont(0));
+ for (uint32_t i = 0; i < text.size(); ++i) {
+ EXPECT_EQ(layout.getFont(0), layout.getFont(i)) << i;
+ EXPECT_EQ(10.0f * i, layout.getX(i)) << i;
+ EXPECT_EQ(0.0f, layout.getY(i)) << i;
+ EXPECT_EQ(10.0f, layout.getCharAdvance(i)) << i;
+ }
+ EXPECT_EQ(130.0f, layout.getAdvance());
+ EXPECT_EQ(text.size(), layout.getAdvances().size());
+ EXPECT_EQ(MinikinRect(0.0f, 10.0f, 130.0f, 0.0f), layout.getBounds());
+}
+
+TEST(MeasuredTextTest, buildLayoutTest_multiStyle) {
+ auto text = utf8ToUtf16("Hello, World!");
+ auto font = buildFontCollection("Ascii.ttf");
+ uint32_t helloLength = 7; // length of "Hello, "
+ Range fullContext(0, text.size());
+
+ MeasuredTextBuilder builder;
+ MinikinPaint paint(font);
+ paint.size = 10.0f;
+ builder.addStyleRun(0, helloLength, std::move(paint), false /* is RTL */);
+ MinikinPaint paint2(font);
+ paint2.size = 20.0f;
+ builder.addStyleRun(helloLength, text.size(), std::move(paint2), false /* is RTL */);
+ auto mt = builder.build(text, true /* hyphenation */, true /* full layout */,
+ nullptr /* no hint */);
+
+ MinikinPaint samePaint(font);
+ samePaint.size = 10.0f;
+
+ Layout layout = mt->buildLayout(text, Range(0, 0), fullContext, samePaint,
+ StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT);
+ EXPECT_EQ(0u, layout.nGlyphs());
+
+ layout = mt->buildLayout(text, Range(0, 1), fullContext, samePaint, StartHyphenEdit::NO_EDIT,
+ EndHyphenEdit::NO_EDIT);
+ ASSERT_EQ(1u, layout.nGlyphs());
+ EXPECT_TRUE(layout.getFont(0));
+ EXPECT_EQ(0.0f, layout.getX(0));
+ EXPECT_EQ(0.0f, layout.getY(0));
+ EXPECT_EQ(10.0f, layout.getAdvance());
+ EXPECT_EQ(10.0f, layout.getCharAdvance(0));
+ EXPECT_EQ(1u, layout.getAdvances().size());
+ EXPECT_EQ(MinikinRect(0.0f, 10.0f, 10.0f, 0.0f), layout.getBounds());
+
+ layout = mt->buildLayout(text, Range(0, 2), fullContext, samePaint, StartHyphenEdit::NO_EDIT,
+ EndHyphenEdit::NO_EDIT);
+ ASSERT_EQ(2u, layout.nGlyphs());
+ EXPECT_TRUE(layout.getFont(0) && layout.getFont(0) == layout.getFont(1));
+ EXPECT_EQ(0.0f, layout.getX(0));
+ EXPECT_EQ(0.0f, layout.getY(0));
+ EXPECT_EQ(10.0f, layout.getX(1));
+ EXPECT_EQ(0.0f, layout.getY(1));
+ EXPECT_EQ(20.0f, layout.getAdvance());
+ EXPECT_EQ(10.0f, layout.getCharAdvance(0));
+ EXPECT_EQ(10.0f, layout.getCharAdvance(1));
+ EXPECT_EQ(2u, layout.getAdvances().size());
+ EXPECT_EQ(MinikinRect(0.0f, 10.0f, 20.0f, 0.0f), layout.getBounds());
+
+ layout = mt->buildLayout(text, Range(1, 2), fullContext, samePaint, StartHyphenEdit::NO_EDIT,
+ EndHyphenEdit::NO_EDIT);
+ ASSERT_EQ(1u, layout.nGlyphs());
+ EXPECT_TRUE(layout.getFont(0));
+ EXPECT_EQ(0.0f, layout.getX(0));
+ EXPECT_EQ(0.0f, layout.getY(0));
+ EXPECT_EQ(10.0f, layout.getAdvance());
+ EXPECT_EQ(10.0f, layout.getCharAdvance(0));
+ EXPECT_EQ(1u, layout.getAdvances().size());
+ EXPECT_EQ(MinikinRect(0.0f, 10.0f, 10.0f, 0.0f), layout.getBounds());
+
+ layout = mt->buildLayout(text, Range(7, 7), fullContext, samePaint, StartHyphenEdit::NO_EDIT,
+ EndHyphenEdit::NO_EDIT);
+ EXPECT_EQ(0u, layout.nGlyphs());
+
+ MinikinPaint samePaint2(font);
+ samePaint2.size = 20.0f;
+ layout = mt->buildLayout(text, Range(7, 8), fullContext, samePaint2, StartHyphenEdit::NO_EDIT,
+ EndHyphenEdit::NO_EDIT);
+ ASSERT_EQ(1u, layout.nGlyphs());
+ EXPECT_TRUE(layout.getFont(0));
+ EXPECT_EQ(0.0f, layout.getX(0));
+ EXPECT_EQ(0.0f, layout.getY(0));
+ EXPECT_EQ(20.0f, layout.getAdvance());
+ EXPECT_EQ(20.0f, layout.getCharAdvance(0));
+ EXPECT_EQ(1u, layout.getAdvances().size());
+ EXPECT_EQ(MinikinRect(0.0f, 20.0f, 20.0f, 0.0f), layout.getBounds());
+}
+
+TEST(MeasuredTextTest, buildLayoutTest_differentPaint) {
+ auto text = utf8ToUtf16("Hello, World!");
+ auto font = buildFontCollection("Ascii.ttf");
+ Range fullContext(0, text.size());
+
+ MeasuredTextBuilder builder;
+ MinikinPaint paint(font);
+ paint.size = 10.0f;
+ builder.addStyleRun(0, text.size(), std::move(paint), false /* is RTL */);
+ auto mt = builder.build(text, true /* hyphenation */, true /* full layout */,
+ nullptr /* no hint */);
+
+ MinikinPaint differentPaint(font);
+ differentPaint.size = 20.0f;
+
+ Layout layout = mt->buildLayout(text, Range(0, 0), fullContext, differentPaint,
+ StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT);
+ EXPECT_EQ(0u, layout.nGlyphs());
+
+ layout = mt->buildLayout(text, Range(0, 1), fullContext, differentPaint,
+ StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT);
+ ASSERT_EQ(1u, layout.nGlyphs());
+ EXPECT_TRUE(layout.getFont(0));
+ EXPECT_EQ(0.0f, layout.getX(0));
+ EXPECT_EQ(0.0f, layout.getY(0));
+ EXPECT_EQ(20.0f, layout.getAdvance());
+ EXPECT_EQ(20.0f, layout.getCharAdvance(0));
+ EXPECT_EQ(1u, layout.getAdvances().size());
+ EXPECT_EQ(MinikinRect(0.0f, 20.0f, 20.0f, 0.0f), layout.getBounds());
+
+ layout = mt->buildLayout(text, Range(0, 2), fullContext, differentPaint,
+ StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT);
+ ASSERT_EQ(2u, layout.nGlyphs());
+ EXPECT_TRUE(layout.getFont(0) && layout.getFont(0) == layout.getFont(1));
+ EXPECT_EQ(0.0f, layout.getX(0));
+ EXPECT_EQ(0.0f, layout.getY(0));
+ EXPECT_EQ(20.0f, layout.getX(1));
+ EXPECT_EQ(0.0f, layout.getY(1));
+ EXPECT_EQ(40.0f, layout.getAdvance());
+ EXPECT_EQ(20.0f, layout.getCharAdvance(0));
+ EXPECT_EQ(20.0f, layout.getCharAdvance(1));
+ EXPECT_EQ(2u, layout.getAdvances().size());
+ EXPECT_EQ(MinikinRect(0.0f, 20.0f, 40.0f, 0.0f), layout.getBounds());
+
+ layout = mt->buildLayout(text, Range(1, 2), fullContext, differentPaint,
+ StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT);
+ ASSERT_EQ(1u, layout.nGlyphs());
+ EXPECT_TRUE(layout.getFont(0));
+ EXPECT_EQ(0.0f, layout.getX(0));
+ EXPECT_EQ(0.0f, layout.getY(0));
+ EXPECT_EQ(20.0f, layout.getAdvance());
+ EXPECT_EQ(20.0f, layout.getCharAdvance(0));
+ EXPECT_EQ(1u, layout.getAdvances().size());
+ EXPECT_EQ(MinikinRect(0.0f, 20.0f, 20.0f, 0.0f), layout.getBounds());
+
+ layout = mt->buildLayout(text, Range(0, text.size()), fullContext, differentPaint,
+ StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT);
+ ASSERT_EQ(text.size(), layout.nGlyphs());
+ EXPECT_TRUE(layout.getFont(0));
+ for (uint32_t i = 0; i < text.size(); ++i) {
+ EXPECT_EQ(layout.getFont(0), layout.getFont(i)) << i;
+ EXPECT_EQ(20.0f * i, layout.getX(i)) << i;
+ EXPECT_EQ(0.0f, layout.getY(i)) << i;
+ EXPECT_EQ(20.0f, layout.getCharAdvance(i)) << i;
+ }
+ EXPECT_EQ(260.0f, layout.getAdvance());
+ EXPECT_EQ(text.size(), layout.getAdvances().size());
+ EXPECT_EQ(MinikinRect(0.0f, 20.0f, 260.0f, 0.0f), layout.getBounds());
+}
+
+TEST(MeasuredTextTest, buildLayoutTest_multiStyle_differentPaint) {
+ auto text = utf8ToUtf16("Hello, World!");
+ auto font = buildFontCollection("Ascii.ttf");
+ uint32_t helloLength = 7; // length of "Hello, "
+ Range fullContext(0, text.size());
+
+ MeasuredTextBuilder builder;
+ MinikinPaint paint(font);
+ paint.size = 10.0f;
+ builder.addStyleRun(0, helloLength, std::move(paint), false /* is RTL */);
+ MinikinPaint paint2(font);
+ paint2.size = 20.0f;
+ builder.addStyleRun(helloLength, text.size(), std::move(paint2), false /* is RTL */);
+ auto mt = builder.build(text, true /* hyphenation */, true /* full layout */,
+ nullptr /* no hint */);
+
+ MinikinPaint differentPaint(font);
+ differentPaint.size = 30.0f;
+
+ Layout layout = mt->buildLayout(text, Range(0, 0), fullContext, differentPaint,
+ StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT);
+ EXPECT_EQ(0u, layout.nGlyphs());
+
+ layout = mt->buildLayout(text, Range(0, 1), fullContext, differentPaint,
+ StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT);
+ ASSERT_EQ(1u, layout.nGlyphs());
+ EXPECT_TRUE(layout.getFont(0));
+ EXPECT_EQ(0.0f, layout.getX(0));
+ EXPECT_EQ(0.0f, layout.getY(0));
+ EXPECT_EQ(30.0f, layout.getAdvance());
+ EXPECT_EQ(30.0f, layout.getCharAdvance(0));
+ EXPECT_EQ(1u, layout.getAdvances().size());
+ EXPECT_EQ(MinikinRect(0.0f, 30.0f, 30.0f, 0.0f), layout.getBounds());
+
+ layout = mt->buildLayout(text, Range(0, 2), fullContext, differentPaint,
+ StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT);
+ ASSERT_EQ(2u, layout.nGlyphs());
+ EXPECT_TRUE(layout.getFont(0) && layout.getFont(0) == layout.getFont(1));
+ EXPECT_EQ(0.0f, layout.getX(0));
+ EXPECT_EQ(0.0f, layout.getY(0));
+ EXPECT_EQ(30.0f, layout.getX(1));
+ EXPECT_EQ(0.0f, layout.getY(1));
+ EXPECT_EQ(60.0f, layout.getAdvance());
+ EXPECT_EQ(30.0f, layout.getCharAdvance(0));
+ EXPECT_EQ(30.0f, layout.getCharAdvance(1));
+ EXPECT_EQ(2u, layout.getAdvances().size());
+ EXPECT_EQ(MinikinRect(0.0f, 30.0f, 60.0f, 0.0f), layout.getBounds());
+
+ layout = mt->buildLayout(text, Range(1, 2), fullContext, differentPaint,
+ StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT);
+ ASSERT_EQ(1u, layout.nGlyphs());
+ EXPECT_TRUE(layout.getFont(0));
+ EXPECT_EQ(0.0f, layout.getX(0));
+ EXPECT_EQ(0.0f, layout.getY(0));
+ EXPECT_EQ(30.0f, layout.getAdvance());
+ EXPECT_EQ(30.0f, layout.getCharAdvance(0));
+ EXPECT_EQ(1u, layout.getAdvances().size());
+ EXPECT_EQ(MinikinRect(0.0f, 30.0f, 30.0f, 0.0f), layout.getBounds());
+
+ layout = mt->buildLayout(text, Range(7, 7), fullContext, differentPaint,
+ StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT);
+ EXPECT_EQ(0u, layout.nGlyphs());
+
+ layout = mt->buildLayout(text, Range(7, 8), fullContext, differentPaint,
+ StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT);
+ ASSERT_EQ(1u, layout.nGlyphs());
+ EXPECT_TRUE(layout.getFont(0));
+ EXPECT_EQ(0.0f, layout.getX(0));
+ EXPECT_EQ(0.0f, layout.getY(0));
+ EXPECT_EQ(30.0f, layout.getAdvance());
+ EXPECT_EQ(30.0f, layout.getCharAdvance(0));
+ EXPECT_EQ(1u, layout.getAdvances().size());
+ EXPECT_EQ(MinikinRect(0.0f, 30.0f, 30.0f, 0.0f), layout.getBounds());
+
+ layout = mt->buildLayout(text, Range(6, 8), fullContext, differentPaint,
+ StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT);
+ ASSERT_EQ(2u, layout.nGlyphs());
+ EXPECT_TRUE(layout.getFont(0) && layout.getFont(0) == layout.getFont(1));
+ EXPECT_EQ(0.0f, layout.getX(0));
+ EXPECT_EQ(0.0f, layout.getY(0));
+ EXPECT_EQ(30.0f, layout.getX(1));
+ EXPECT_EQ(0.0f, layout.getY(1));
+ EXPECT_EQ(60.0f, layout.getAdvance());
+ EXPECT_EQ(30.0f, layout.getCharAdvance(0));
+ EXPECT_EQ(30.0f, layout.getCharAdvance(1));
+ EXPECT_EQ(2u, layout.getAdvances().size());
+ EXPECT_EQ(MinikinRect(0.0f, 30.0f, 60.0f, 0.0f), layout.getBounds());
+
+ layout = mt->buildLayout(text, Range(0, text.size()), fullContext, differentPaint,
+ StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT);
+ ASSERT_EQ(text.size(), layout.nGlyphs());
+ EXPECT_TRUE(layout.getFont(0));
+ for (uint32_t i = 0; i < text.size(); ++i) {
+ EXPECT_EQ(layout.getFont(0), layout.getFont(i)) << i;
+ EXPECT_EQ(30.0f * i, layout.getX(i)) << i;
+ EXPECT_EQ(0.0f, layout.getY(i)) << i;
+ EXPECT_EQ(30.0f, layout.getCharAdvance(i)) << i;
+ }
+ EXPECT_EQ(390.0f, layout.getAdvance());
+ EXPECT_EQ(text.size(), layout.getAdvances().size());
+ EXPECT_EQ(MinikinRect(0.0f, 30.0f, 390.0f, 0.0f), layout.getBounds());
+}
+
} // namespace minikin
diff --git a/tests/unittest/OptimalLineBreakerTest.cpp b/tests/unittest/OptimalLineBreakerTest.cpp
index a4479ad..ef1f6a9 100644
--- a/tests/unittest/OptimalLineBreakerTest.cpp
+++ b/tests/unittest/OptimalLineBreakerTest.cpp
@@ -39,6 +39,14 @@
using line_breaker_test_helper::sameLineBreak;
using line_breaker_test_helper::toString;
+// The ascent/descent of Ascii.ttf with text size = 10.
+constexpr float ASCENT = -80.0f;
+constexpr float DESCENT = 20.0f;
+
+// The ascent/descent of CustomExtent.ttf with text size = 10.
+constexpr float CUSTOM_ASCENT = -160.0f;
+constexpr float CUSTOM_DESCENT = 40.0f;
+
class OptimalLineBreakerTest : public testing::Test {
public:
OptimalLineBreakerTest() {}
@@ -57,17 +65,25 @@
protected:
LineBreakResult doLineBreak(const U16StringPiece& textBuffer, BreakStrategy strategy,
- HyphenationFrequency frequency, float charWidth, float lineWidth) {
- return doLineBreak(textBuffer, strategy, frequency, charWidth, "en-US", lineWidth);
+ HyphenationFrequency frequency, float lineWidth) {
+ return doLineBreak(textBuffer, strategy, frequency, "en-US", lineWidth);
}
LineBreakResult doLineBreak(const U16StringPiece& textBuffer, BreakStrategy strategy,
- HyphenationFrequency frequency, float charWidth,
- const std::string& lang, float lineWidth) {
+ HyphenationFrequency frequency, const std::string& lang,
+ float lineWidth) {
MeasuredTextBuilder builder;
- builder.addCustomRun<ConstantRun>(Range(0, textBuffer.size()), lang, charWidth);
- std::unique_ptr<MeasuredText> measuredText = builder.build(
- textBuffer, true /* compute hyphenation */, false /* compute full layout */);
+ 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.localeListId = LocaleListCache::getId(lang);
+ builder.addStyleRun(0, textBuffer.size(), std::move(paint), false);
+ std::unique_ptr<MeasuredText> measuredText =
+ builder.build(textBuffer, true /* compute hyphenation */,
+ false /* compute full layout */, nullptr /* no hint */);
return doLineBreak(textBuffer, *measuredText, strategy, frequency, lineWidth);
}
@@ -84,7 +100,6 @@
};
TEST_F(OptimalLineBreakerTest, testBreakWithoutHyphenation) {
- constexpr float CHAR_WIDTH = 10.0;
constexpr BreakStrategy HIGH_QUALITY = BreakStrategy::HighQuality;
constexpr BreakStrategy BALANCED = BreakStrategy::Balanced;
constexpr HyphenationFrequency NO_HYPHENATION = HyphenationFrequency::None;
@@ -96,724 +111,724 @@
constexpr EndHyphenEdit END_HYPHEN = EndHyphenEdit::INSERT_HYPHEN;
// Note that disable clang-format everywhere since aligned expectation is more readable.
{
- constexpr float LINE_WIDTH = 1000 * CHAR_WIDTH;
+ constexpr float LINE_WIDTH = 1000;
std::vector<LineBreakExpectation> expect = {
- {"This is an example text.", 24 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ {"This is an example text.", 240, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
};
- auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, 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, CHAR_WIDTH, LINE_WIDTH);
+ 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, CHAR_WIDTH, LINE_WIDTH);
+ 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, CHAR_WIDTH, LINE_WIDTH);
+ actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, LINE_WIDTH);
EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
<< " vs " << std::endl
<< toString(textBuf, actual);
}
{
- constexpr float LINE_WIDTH = 24 * CHAR_WIDTH;
+ constexpr float LINE_WIDTH = 240;
std::vector<LineBreakExpectation> expect = {
- {"This is an example text.", 24 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ {"This is an example text.", 240, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
};
- auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, 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, CHAR_WIDTH, LINE_WIDTH);
+ 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, CHAR_WIDTH, LINE_WIDTH);
+ 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, CHAR_WIDTH, LINE_WIDTH);
+ actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, LINE_WIDTH);
EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
<< " vs " << std::endl
<< toString(textBuf, actual);
}
{
- constexpr float LINE_WIDTH = 23 * CHAR_WIDTH;
+ constexpr float LINE_WIDTH = 230;
// clang-format off
std::vector<LineBreakExpectation> expect = {
- { "This is an example " , 18 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "text." , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "This is an example " , 180, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "text." , 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
};
// clang-format on
- auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, 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, CHAR_WIDTH, LINE_WIDTH);
+ 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 = {
- { "This is an " , 10 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "example text." , 13 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "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, CHAR_WIDTH, LINE_WIDTH);
+ actual = doLineBreak(textBuf, BALANCED, 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-" , 14 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
- { "ample text." , 11 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "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, CHAR_WIDTH, LINE_WIDTH);
+ actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, LINE_WIDTH);
EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
<< " vs " << std::endl
<< toString(textBuf, actual);
}
{
- constexpr float LINE_WIDTH = 17 * CHAR_WIDTH;
+ constexpr float LINE_WIDTH = 170;
// clang-format off
std::vector<LineBreakExpectation> expect = {
- { "This is an " , 10 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "example text." , 13 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "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
- auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, 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-" , 16 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
- { "ple text." , 9 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "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, CHAR_WIDTH, LINE_WIDTH);
+ 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 = {
- { "This is an " , 10 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "example text." , 13 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "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, CHAR_WIDTH, LINE_WIDTH);
+ actual = doLineBreak(textBuf, BALANCED, 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-", 14 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
- { "ample text." , 11 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "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, CHAR_WIDTH, LINE_WIDTH);
+ actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, LINE_WIDTH);
EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
<< " vs " << std::endl
<< toString(textBuf, actual);
}
{
- constexpr float LINE_WIDTH = 16 * CHAR_WIDTH;
+ constexpr float LINE_WIDTH = 160;
// clang-format off
std::vector<LineBreakExpectation> expect = {
- { "This is an " , 10 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "example text." , 13 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "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
- auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, 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-" , 16 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
- { "ple text." , 9 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "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, CHAR_WIDTH, LINE_WIDTH);
+ 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 = {
- { "This is an " , 10 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "example text." , 13 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "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, CHAR_WIDTH, LINE_WIDTH);
+ actual = doLineBreak(textBuf, BALANCED, 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-", 14 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
- { "ample text." , 11 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "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, CHAR_WIDTH, LINE_WIDTH);
+ actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, LINE_WIDTH);
EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
<< " vs " << std::endl
<< toString(textBuf, actual);
}
{
- constexpr float LINE_WIDTH = 15 * CHAR_WIDTH;
+ constexpr float LINE_WIDTH = 150;
// clang-format off
std::vector<LineBreakExpectation> expect = {
- { "This is an " , 10 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "example text." , 13 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "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
- auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, 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-", 14 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
- { "ample text." , 11 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "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, CHAR_WIDTH, LINE_WIDTH);
+ 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 = {
- { "This is an " , 10 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "example text." , 13 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "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, CHAR_WIDTH, LINE_WIDTH);
+ actual = doLineBreak(textBuf, BALANCED, 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-", 14 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
- { "ample text." , 11 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "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, CHAR_WIDTH, LINE_WIDTH);
+ actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, LINE_WIDTH);
EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
<< " vs " << std::endl
<< toString(textBuf, actual);
}
{
- constexpr float LINE_WIDTH = 13 * CHAR_WIDTH;
+ constexpr float LINE_WIDTH = 130;
// clang-format off
std::vector<LineBreakExpectation> expect = {
- { "This is an " , 10 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "example text." , 13 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "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
- auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, 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, CHAR_WIDTH, LINE_WIDTH);
+ 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, CHAR_WIDTH, LINE_WIDTH);
+ 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, CHAR_WIDTH, LINE_WIDTH);
+ actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, LINE_WIDTH);
EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
<< " vs " << std::endl
<< toString(textBuf, actual);
}
{
- constexpr float LINE_WIDTH = 12 * CHAR_WIDTH;
+ constexpr float LINE_WIDTH = 120;
// clang-format off
std::vector<LineBreakExpectation> expect = {
- { "This is an ", 10 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "example " , 7 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "text." , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "This is an ", 100, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "example " , 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "text." , 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
};
// clang-format on
- auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, 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, CHAR_WIDTH, LINE_WIDTH);
+ 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, CHAR_WIDTH, LINE_WIDTH);
+ actual = doLineBreak(textBuf, BALANCED, 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 " , 7 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "an exam-" , 8 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
- { "ple text.", 9 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "This is " , 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "an exam-" , 80, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+ { "ple text.", 90, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
};
// clang-format on
- actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, LINE_WIDTH);
EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
<< " vs " << std::endl
<< toString(textBuf, actual);
}
{
- constexpr float LINE_WIDTH = 9 * CHAR_WIDTH;
+ constexpr float LINE_WIDTH = 90;
// clang-format off
std::vector<LineBreakExpectation> expect = {
- { "This " , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "is an " , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "example ", 7 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "text." , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "This " , 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "is an " , 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "example ", 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "text." , 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
};
// clang-format on
- auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, 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 " , 7 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "an exam-" , 8 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
- { "ple text.", 9 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "This is " , 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "an exam-" , 80, 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, CHAR_WIDTH, LINE_WIDTH);
+ 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 = {
- { "This " , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "is an " , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "example ", 7 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "text." , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "This " , 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "is an " , 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "example ", 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "text." , 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
};
// clang-format on
- actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ actual = doLineBreak(textBuf, BALANCED, 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 " , 7 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "an exam-" , 8 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
- { "ple text.", 9 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "This is " , 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "an exam-" , 80, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+ { "ple text.", 90, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
};
// clang-format on
- actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, LINE_WIDTH);
EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
<< " vs " << std::endl
<< toString(textBuf, actual);
}
{
- constexpr float LINE_WIDTH = 8 * CHAR_WIDTH;
+ constexpr float LINE_WIDTH = 80;
// clang-format off
std::vector<LineBreakExpectation> expect = {
- { "This " , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "is an " , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "example ", 7 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "text." , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "This " , 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "is an " , 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "example ", 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "text." , 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
};
// clang-format on
- auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, 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, BALANCED, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ actual = doLineBreak(textBuf, BALANCED, 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 ", 7 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "an ex-" , 6 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
- { "ample " , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "text." , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "This is ", 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "an ex-" , 60, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+ { "ample " , 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "text." , 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
};
// clang-format on
- actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ 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, CHAR_WIDTH, LINE_WIDTH);
+ actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, LINE_WIDTH);
EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
<< " vs " << std::endl
<< toString(textBuf, actual);
}
{
- constexpr float LINE_WIDTH = 7 * CHAR_WIDTH;
+ constexpr float LINE_WIDTH = 70;
// clang-format off
std::vector<LineBreakExpectation> expect = {
- { "This " , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "is an " , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "example ", 7 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "text." , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "This " , 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "is an " , 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "example ", 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "text." , 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
};
// clang-format on
- auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, 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, BALANCED, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ actual = doLineBreak(textBuf, BALANCED, 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 ", 7 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "an ex-" , 6 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
- { "ample " , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "text." , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "This is ", 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "an ex-" , 60, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+ { "ample " , 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "text." , 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
};
// clang-format on
- actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ 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, CHAR_WIDTH, LINE_WIDTH);
+ actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, LINE_WIDTH);
EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
<< " vs " << std::endl
<< toString(textBuf, actual);
}
{
- constexpr float LINE_WIDTH = 6 * CHAR_WIDTH;
+ constexpr float LINE_WIDTH = 60;
// clang-format off
std::vector<LineBreakExpectation> expect = {
- { "This " , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "is an ", 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "This " , 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "is an ", 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
// TODO: Is this desperate break working correctly?
- { "exa" , 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "mple " , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "text." , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "exa" , 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "mple " , 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "text." , 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
};
// clang-format on
- auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, 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, BALANCED, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, LINE_WIDTH);
EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
<< " vs " << std::endl
<< toString(textBuf, actual);
// clang-format off
expect = {
- { "This " , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "is an ", 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "exam-" , 5 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
- { "ple " , 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "text." , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "This " , 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "is an ", 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "exam-" , 50, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+ { "ple " , 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "text." , 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
};
// clang-format on
- actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ 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, CHAR_WIDTH, LINE_WIDTH);
+ actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, LINE_WIDTH);
EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
<< " vs " << std::endl
<< toString(textBuf, actual);
}
{
- constexpr float LINE_WIDTH = 5 * CHAR_WIDTH;
+ constexpr float LINE_WIDTH = 50;
// clang-format off
std::vector<LineBreakExpectation> expect = {
- { "This " , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "is an ", 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "This " , 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "is an ", 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
// TODO: Is this desperate break working correctly?
- { "exa" , 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "mple " , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "text." , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "exa" , 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "mple " , 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "text." , 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
};
// clang-format on
- auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, 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, BALANCED, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, LINE_WIDTH);
EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
<< " vs " << std::endl
<< toString(textBuf, actual);
// clang-format off
expect = {
- { "This " , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "is an ", 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "exam-" , 5 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
- { "ple " , 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "text." , 5 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "This " , 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "is an ", 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "exam-" , 50, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+ { "ple " , 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "text." , 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
};
// clang-format on
- actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ 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, CHAR_WIDTH, LINE_WIDTH);
+ actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, LINE_WIDTH);
EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
<< " vs " << std::endl
<< toString(textBuf, actual);
}
{
- constexpr float LINE_WIDTH = 4 * CHAR_WIDTH;
+ constexpr float LINE_WIDTH = 40;
// clang-format off
std::vector<LineBreakExpectation> expect = {
- { "This " , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "is " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "an " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "This " , 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "is " , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "an " , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
// TODO: Is this desperate break working correctly?
- { "exa" , 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "mple " , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "text" , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "." , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "exa" , 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "mple " , 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "text" , 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "." , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
};
// clang-format on
- auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, 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 " , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "is " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "an " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "This " , 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "is " , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "an " , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
// TODO: Is this desperate break working correctly?
- { "exa" , 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "mple " , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "exa" , 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "mple " , 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
// TODO: Is this desperate break working correctly?
- { "t" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "ext." , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "t" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "ext." , 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
};
// clang-format on
- actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, LINE_WIDTH);
EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
<< " vs " << std::endl
<< toString(textBuf, actual);
// clang-format off
expect = {
- { "This ", 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "is " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "an " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "ex-" , 3 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
- { "am-" , 3 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
- { "ple " , 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "text" , 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "." , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "This ", 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "is " , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "an " , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "ex-" , 30, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+ { "am-" , 30, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+ { "ple " , 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "text" , 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "." , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
};
// clang-format on
- actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ 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 = {
- { "This ", 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "is " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "an " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "ex-" , 3 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
- { "am-" , 3 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
- { "ple " , 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "This ", 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "is " , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "an " , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "ex-" , 30, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+ { "am-" , 30, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+ { "ple " , 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
// TODO: Is this desperate break working correctly?
- { "te" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "xt." , 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "te" , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "xt." , 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
};
// clang-format on
- actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, LINE_WIDTH);
EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
<< " vs " << std::endl
<< toString(textBuf, actual);
}
{
- constexpr float LINE_WIDTH = 3 * CHAR_WIDTH;
+ constexpr float LINE_WIDTH = 30;
// clang-format off
std::vector<LineBreakExpectation> expect = {
// TODO: Is this desperate break working correctly?
- { "T" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "his ", 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "is " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "an " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "T" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "his ", 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "is " , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "an " , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
// TODO: Is this desperate break working correctly?
- { "e" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "xam" , 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "ple ", 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "tex" , 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "t." , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "e" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "xam" , 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "ple ", 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "tex" , 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "t." , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
};
// clang-format on
- auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, 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?
- { "T" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "his ", 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "is " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "an " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "T" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "his ", 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "is " , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "an " , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
// TODO: Is this desperate break working correctly?
- { "e" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "xam" , 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "ple ", 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "e" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "xam" , 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "ple ", 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
// TODO: Is this desperate break working correctly?
- { "te" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "xt." , 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "te" , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "xt." , 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
};
// clang-format on
- actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ actual = doLineBreak(textBuf, BALANCED, 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?
- { "T" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "his ", 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "is " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "an " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "ex-" , 3 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
- { "am-" , 3 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN },
- { "ple ", 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "tex" , 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "t." , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "T" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "his ", 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "is " , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "an " , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "ex-" , 30, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+ { "am-" , 30, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+ { "ple ", 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "tex" , 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "t." , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
};
// clang-format on
- actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ 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 = {
// TODO: Is this desperate break working correctly?
- {"T" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
- {"his ", 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
- {"is " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
- {"an " , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
- {"ex-" , 3 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN},
- {"am-" , 3 * CHAR_WIDTH, NO_START_HYPHEN, END_HYPHEN},
- {"ple ", 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ {"T" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"his ", 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"is " , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"an " , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"ex-" , 30, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT},
+ {"am-" , 30, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT},
+ {"ple ", 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
// TODO: Is this desperate break working correctly?
- {"te" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
- {"xt." , 3 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ {"te" , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"xt." , 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
};
// clang-format on
- actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, LINE_WIDTH);
EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
<< " vs " << std::endl
<< toString(textBuf, actual);
}
{
- constexpr float LINE_WIDTH = 2 * CHAR_WIDTH;
+ constexpr float LINE_WIDTH = 20;
// clang-format off
std::vector<LineBreakExpectation> expect = {
- { "Th" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "is ", 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "is ", 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "an ", 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "Th" , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "is ", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "is ", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "an ", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
// TODO: Is this desperate break working correctly?
- { "e" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "xa" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "mp" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "le ", 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "te" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "xt" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "." , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "e" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "xa" , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "mp" , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "le ", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "te" , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "xt" , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "." , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
};
// clang-format on
- auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, 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, CHAR_WIDTH, LINE_WIDTH);
+ 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" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "is ", 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "is ", 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "an ", 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "Th" , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "is ", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "is ", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "an ", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
// TODO: Is this desperate break working correctly?
- { "e" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "xa" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "mp" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "le ", 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "e" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "xa" , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "mp" , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "le ", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
// TODO: Is this desperate break working correctly?
- { "t" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "ex" , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "t." , 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "t" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "ex" , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "t." , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
};
// clang-format on
- actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ 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, CHAR_WIDTH, LINE_WIDTH);
+ actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, LINE_WIDTH);
EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
<< " vs " << std::endl
<< toString(textBuf, actual);
}
{
- constexpr float LINE_WIDTH = 1 * CHAR_WIDTH;
+ constexpr float LINE_WIDTH = 10;
// clang-format off
std::vector<LineBreakExpectation> expect = {
- { "T" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "h" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "i" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "s ", 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "i" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "s ", 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "a" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "n ", 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "e" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "x" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "a" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "m" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "p" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "l" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "e ", 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "t" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "e" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "x" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "t" , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
- { "." , 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN },
+ { "T" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "h" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "i" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "s ", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "i" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "s ", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "a" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "n ", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "e" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "x" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "a" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "m" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "p" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "l" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "e ", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "t" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "e" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "x" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "t" , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "." , 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
};
// clang-format on
- auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, 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, BALANCED, NO_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ 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, CHAR_WIDTH, LINE_WIDTH);
+ 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, CHAR_WIDTH, LINE_WIDTH);
+ actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, LINE_WIDTH);
EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
<< " vs " << std::endl
<< toString(textBuf, actual);
@@ -821,7 +836,6 @@
}
TEST_F(OptimalLineBreakerTest, testHyphenationStartLineChange) {
- constexpr float CHAR_WIDTH = 10.0;
constexpr BreakStrategy HIGH_QUALITY = BreakStrategy::HighQuality;
constexpr HyphenationFrequency NORMAL_HYPHENATION = HyphenationFrequency::Normal;
// "hyphenation" is hyphnated to "hy-phen-a-tion".
@@ -833,40 +847,40 @@
// Note that disable clang-format everywhere since aligned expectation is more readable.
{
- constexpr float LINE_WIDTH = 1000 * CHAR_WIDTH;
+ constexpr float LINE_WIDTH = 1000;
std::vector<LineBreakExpectation> expect = {
- {"czerwono-niebieska", 18 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ {"czerwono-niebieska", 180, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
};
- const auto actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, "pl",
- LINE_WIDTH);
+ 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);
}
{
- constexpr float LINE_WIDTH = 18 * CHAR_WIDTH;
+ constexpr float LINE_WIDTH = 180;
std::vector<LineBreakExpectation> expect = {
- {"czerwono-niebieska", 18 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ {"czerwono-niebieska", 180, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
};
- const auto actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, "pl",
- LINE_WIDTH);
+ 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);
}
{
- constexpr float LINE_WIDTH = 13 * CHAR_WIDTH;
+ constexpr float LINE_WIDTH = 130;
// clang-format off
std::vector<LineBreakExpectation> expect = {
- {"czerwono-" , 9 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
- {"-niebieska", 10 * CHAR_WIDTH, START_HYPHEN, NO_END_HYPHEN},
+ {"czerwono-" , 90, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"-niebieska", 100, START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
};
// clang-format on
- const auto actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, "pl",
- LINE_WIDTH);
+ 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);
@@ -874,10 +888,9 @@
}
TEST_F(OptimalLineBreakerTest, testZeroWidthLine) {
- constexpr float CHAR_WIDTH = 10.0;
constexpr BreakStrategy HIGH_QUALITY = BreakStrategy::HighQuality;
constexpr HyphenationFrequency NORMAL_HYPHENATION = HyphenationFrequency::Normal;
- constexpr float LINE_WIDTH = 0 * CHAR_WIDTH;
+ constexpr float LINE_WIDTH = 0;
constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
@@ -885,8 +898,7 @@
{
const auto textBuf = utf8ToUtf16("");
std::vector<LineBreakExpectation> expect = {};
- const auto actual =
- doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ 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);
@@ -894,10 +906,9 @@
{
const auto textBuf = utf8ToUtf16("A");
std::vector<LineBreakExpectation> expect = {
- {"A", 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ {"A", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
};
- const auto actual =
- doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ 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);
@@ -905,11 +916,10 @@
{
const auto textBuf = utf8ToUtf16("AB");
std::vector<LineBreakExpectation> expect = {
- {"A", 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
- {"B", 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ {"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, CHAR_WIDTH, LINE_WIDTH);
+ 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);
@@ -927,10 +937,17 @@
constexpr float LINE_WIDTH = 1.0;
const auto textBuf = utf8ToUtf16("This is an example text.");
std::vector<LineBreakExpectation> expect = {
- {"This is an example text.", 0, NO_START_HYPHEN, NO_END_HYPHEN},
+ {"This is an example text.", 0, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
};
+ 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 */);
+
const auto actual =
- doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ doLineBreak(textBuf, *measuredText, HIGH_QUALITY, NORMAL_HYPHENATION, LINE_WIDTH);
EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
<< " vs " << std::endl
<< toString(textBuf, actual);
@@ -939,10 +956,17 @@
constexpr float LINE_WIDTH = 0.0;
const auto textBuf = utf8ToUtf16("This is an example text.");
std::vector<LineBreakExpectation> expect = {
- {"This is an example text.", 0, NO_START_HYPHEN, NO_END_HYPHEN},
+ {"This is an example text.", 0, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
};
+ 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 */);
+
const auto actual =
- doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+ doLineBreak(textBuf, *measuredText, HIGH_QUALITY, NORMAL_HYPHENATION, LINE_WIDTH);
EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
<< " vs " << std::endl
<< toString(textBuf, actual);
@@ -957,18 +981,20 @@
constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
- constexpr float LINE_WIDTH = 24 * CHAR_WIDTH;
+ constexpr float LINE_WIDTH = 240;
const auto textBuf = utf8ToUtf16("This is an example text.");
{
std::vector<LineBreakExpectation> expect = {
- {"This is an example text.", 24 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ {"This is an example text.", 240, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
};
MeasuredTextBuilder builder;
- builder.addCustomRun<ConstantRun>(Range(0, 18), "en-US", CHAR_WIDTH);
- builder.addCustomRun<ConstantRun>(Range(18, textBuf.size()), "en-US", CHAR_WIDTH);
- std::unique_ptr<MeasuredText> measuredText = builder.build(
- textBuf, true /* compute hyphenation */, false /* compute full layout */);
+ 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 */);
const auto actual =
doLineBreak(textBuf, *measuredText, HIGH_QUALITY, NORMAL_HYPHENATION, LINE_WIDTH);
@@ -978,14 +1004,16 @@
}
{
std::vector<LineBreakExpectation> expect = {
- {"This is an example text.", 24 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ {"This is an example text.", 240, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
};
MeasuredTextBuilder builder;
- builder.addCustomRun<ConstantRun>(Range(0, 18), "en-US", CHAR_WIDTH);
- builder.addCustomRun<ConstantRun>(Range(18, textBuf.size()), "fr-FR", CHAR_WIDTH);
- std::unique_ptr<MeasuredText> measuredText = builder.build(
- textBuf, true /* compute hyphenation */, false /* compute full layout */);
+ 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 */);
const auto actual =
doLineBreak(textBuf, *measuredText, HIGH_QUALITY, NORMAL_HYPHENATION, LINE_WIDTH);
EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
@@ -995,7 +1023,6 @@
}
TEST_F(OptimalLineBreakerTest, testEmailOrUrl) {
- constexpr float CHAR_WIDTH = 10.0;
constexpr BreakStrategy HIGH_QUALITY = BreakStrategy::HighQuality;
constexpr BreakStrategy BALANCED = BreakStrategy::Balanced;
constexpr HyphenationFrequency NO_HYPHENATION = HyphenationFrequency::None;
@@ -1004,61 +1031,61 @@
constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
{
- constexpr float LINE_WIDTH = 24 * CHAR_WIDTH;
+ constexpr float LINE_WIDTH = 240;
const auto textBuf = utf8ToUtf16("This is an url: http://a.b");
// clang-format off
std::vector<LineBreakExpectation> expect = {
// TODO: Fix this. Prefer not to break inside URL.
- {"This is an url: http://a", 24 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
- {".b", 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ {"This is an url: http://a", 240, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {".b", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
};
// clang-format on
- auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, 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, CHAR_WIDTH, LINE_WIDTH);
+ 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 = {
- {"This is an url: ", 15 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
- {"http://a.b", 10 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ {"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, CHAR_WIDTH, LINE_WIDTH);
+ 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, CHAR_WIDTH, LINE_WIDTH);
+ actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, LINE_WIDTH);
EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
<< " vs " << std::endl
<< toString(textBuf, actual);
}
{
- constexpr float LINE_WIDTH = 24 * CHAR_WIDTH;
+ constexpr float LINE_WIDTH = 240;
const auto textBuf = utf8ToUtf16("This is an email: a@example.com");
// clang-format off
std::vector<LineBreakExpectation> expect = {
- {"This is an email: ", 17 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
- {"a@example.com" , 13 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ {"This is an email: ", 170, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"a@example.com" , 130, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
};
// clang-format on
- auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, 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, CHAR_WIDTH, LINE_WIDTH);
+ 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, CHAR_WIDTH, LINE_WIDTH);
+ 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, CHAR_WIDTH, LINE_WIDTH);
+ actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, LINE_WIDTH);
EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
<< " vs " << std::endl
<< toString(textBuf, actual);
@@ -1075,20 +1102,22 @@
constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
- constexpr float LINE_WIDTH = 24 * CHAR_WIDTH;
+ constexpr float LINE_WIDTH = 240;
{
const auto textBuf = utf8ToUtf16("This is an url: http://a.b");
MeasuredTextBuilder builder;
- builder.addCustomRun<ConstantRun>(Range(0, 18), "en-US", CHAR_WIDTH);
- builder.addCustomRun<ConstantRun>(Range(18, textBuf.size()), "fr-FR", CHAR_WIDTH);
- std::unique_ptr<MeasuredText> measured = builder.build(
- textBuf, true /* compute hyphenation */, false /* compute full layout */);
+ 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 */);
// clang-format off
std::vector<LineBreakExpectation> expect = {
// TODO: Fix this. Prefer not to break inside URL.
- {"This is an url: http://a", 24 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
- {".b", 2 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ {"This is an url: http://a", 240, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {".b", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
};
// clang-format on
@@ -1103,8 +1132,8 @@
// clang-format off
expect = {
- {"This is an url: ", 15 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
- {"http://a.b", 10 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ {"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
@@ -1120,15 +1149,17 @@
{
const auto textBuf = utf8ToUtf16("This is an email: a@example.com");
MeasuredTextBuilder builder;
- builder.addCustomRun<ConstantRun>(Range(0, 18), "en-US", CHAR_WIDTH);
- builder.addCustomRun<ConstantRun>(Range(18, textBuf.size()), "fr-FR", CHAR_WIDTH);
- std::unique_ptr<MeasuredText> measured = builder.build(
- textBuf, true /* compute hyphenation */, false /* compute full layout */);
+ 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 */);
// clang-format off
std::vector<LineBreakExpectation> expect = {
- {"This is an email: ", 17 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
- {"a@example.com", 13 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+ {"This is an email: ", 170, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"a@example.com", 130, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
};
// clang-format on
@@ -1151,5 +1182,828 @@
}
}
+TEST_F(OptimalLineBreakerTest, ExtentTest) {
+ constexpr HyphenationFrequency NO_HYPHEN = HyphenationFrequency::None;
+ const std::vector<uint16_t> textBuf = utf8ToUtf16("The \u3042\u3044\u3046 is Japanese.");
+
+ constexpr BreakStrategy HIGH_QUALITY = BreakStrategy::HighQuality;
+ constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
+ constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
+ {
+ constexpr float LINE_WIDTH = 1000;
+ std::vector<LineBreakExpectation> expect = {
+ {"The \u3042\u3044\u3046 is Japanese.", 200, NO_START_HYPHEN, NO_END_HYPHEN,
+ 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);
+ }
+ {
+ constexpr float LINE_WIDTH = 200;
+ std::vector<LineBreakExpectation> expect = {
+ {"The \u3042\u3044\u3046 is Japanese.", 200, NO_START_HYPHEN, NO_END_HYPHEN,
+ 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);
+ }
+ {
+ constexpr float LINE_WIDTH = 190;
+ std::vector<LineBreakExpectation> expect = {
+ {"The \u3042\u3044\u3046 is ", 100, NO_START_HYPHEN, NO_END_HYPHEN, CUSTOM_ASCENT,
+ 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);
+ }
+ {
+ constexpr float LINE_WIDTH = 90;
+ std::vector<LineBreakExpectation> expect = {
+ {"The \u3042", 50, NO_START_HYPHEN, NO_END_HYPHEN, CUSTOM_ASCENT, CUSTOM_DESCENT},
+ {"\u3044\u3046 is ", 50, NO_START_HYPHEN, NO_END_HYPHEN, CUSTOM_ASCENT,
+ 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);
+ }
+ {
+ constexpr float LINE_WIDTH = 50;
+ std::vector<LineBreakExpectation> expect = {
+ {"The \u3042", 50, NO_START_HYPHEN, NO_END_HYPHEN, CUSTOM_ASCENT, CUSTOM_DESCENT},
+ {"\u3044\u3046 is ", 50, NO_START_HYPHEN, NO_END_HYPHEN, CUSTOM_ASCENT,
+ CUSTOM_DESCENT},
+ {"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);
+ }
+ {
+ constexpr float LINE_WIDTH = 40;
+ std::vector<LineBreakExpectation> expect = {
+ {"The ", 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"\u3042\u3044", 20, NO_START_HYPHEN, NO_END_HYPHEN, CUSTOM_ASCENT, CUSTOM_DESCENT},
+ {"\u3046 is ", 40, NO_START_HYPHEN, NO_END_HYPHEN, CUSTOM_ASCENT, CUSTOM_DESCENT},
+ {"Japa", 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"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);
+ }
+ {
+ constexpr float LINE_WIDTH = 20;
+ std::vector<LineBreakExpectation> expect = {
+ {"T", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"he ", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"\u3042", 10, NO_START_HYPHEN, NO_END_HYPHEN, CUSTOM_ASCENT, CUSTOM_DESCENT},
+ {"\u3044\u3046 ", 20, NO_START_HYPHEN, NO_END_HYPHEN, CUSTOM_ASCENT,
+ CUSTOM_DESCENT},
+ {"is ", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"Ja", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"pa", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"ne", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"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);
+ }
+ {
+ constexpr float LINE_WIDTH = 10;
+ std::vector<LineBreakExpectation> expect = {
+ {"T", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"h", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"e ", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"\u3042", 10, NO_START_HYPHEN, NO_END_HYPHEN, CUSTOM_ASCENT, CUSTOM_DESCENT},
+ {"\u3044", 10, NO_START_HYPHEN, NO_END_HYPHEN, CUSTOM_ASCENT, CUSTOM_DESCENT},
+ {"\u3046 ", 10, NO_START_HYPHEN, NO_END_HYPHEN, CUSTOM_ASCENT, CUSTOM_DESCENT},
+ {"i", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"s ", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"J", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"a", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"p", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"a", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"n", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"e", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"s", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"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);
+ }
+}
+
+TEST_F(OptimalLineBreakerTest, testReplacementSpanNotBreakTest_SingleChar) {
+ constexpr float CHAR_WIDTH = 10.0;
+
+ constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
+ constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
+
+ const auto textBuf = utf8ToUtf16("This is an example \u2639 text.");
+
+ // In this test case, assign a replacement run for "U+2639" with 5 times of CHAR_WIDTH.
+ auto doLineBreak = [=](float width) {
+ MeasuredTextBuilder builder;
+ builder.addCustomRun<ConstantRun>(Range(0, 19), "en-US", CHAR_WIDTH, ASCENT, DESCENT);
+ builder.addReplacementRun(19, 21, 5 * CHAR_WIDTH, LocaleListCache::getId("en-US"));
+ 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 */);
+ RectangleLineWidth rectangleLineWidth(width);
+ TabStops tabStops(nullptr, 0, 0);
+ return breakLineOptimal(textBuf, *measuredText, rectangleLineWidth,
+ BreakStrategy::HighQuality, HyphenationFrequency::None,
+ false /* justified */);
+ };
+
+ {
+ constexpr float LINE_WIDTH = 100;
+ // "is an" is a single replacement span. Do not break.
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ {"This is an ", 100, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"example ", 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"\u2639 text.", 100, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ };
+ // clang-format on
+ const auto actual = doLineBreak(LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 90;
+ // "is an" is a single replacement span. Do not break.
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ {"This ", 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"is an ", 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"example ",70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"\u2639 ", 50, NO_START_HYPHEN, NO_END_HYPHEN, 0, 0},
+ {"text.", 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ };
+ // clang-format on
+ const auto actual = doLineBreak(LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 10;
+ // "is an" is a single replacement span. Do not break.
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ {"T", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"h", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"i", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"s ", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"i", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"s ", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"a", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"n ", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"e", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"x", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"a", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"m", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"p", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"l", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"e ", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"\u2639 ",50, NO_START_HYPHEN, NO_END_HYPHEN, 0, 0},
+ {"t", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"e", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"x", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"t", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {".", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ };
+ // clang-format on
+ const auto actual = doLineBreak(LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+}
+
+TEST_F(OptimalLineBreakerTest, testReplacementSpanNotBreakTest_MultipleChars) {
+ constexpr float CHAR_WIDTH = 10.0;
+
+ constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
+ constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
+
+ const auto textBuf = utf8ToUtf16("This is an example text.");
+
+ // In this test case, assign a replacement run for "is an " with 5 times of CHAR_WIDTH.
+ auto doLineBreak = [=](float width) {
+ MeasuredTextBuilder builder;
+ builder.addCustomRun<ConstantRun>(Range(0, 5), "en-US", CHAR_WIDTH, ASCENT, DESCENT);
+ builder.addReplacementRun(5, 11, 5 * CHAR_WIDTH, LocaleListCache::getId("en-US"));
+ 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 */);
+ RectangleLineWidth rectangleLineWidth(width);
+ TabStops tabStops(nullptr, 0, 0);
+ return breakLineOptimal(textBuf, *measuredText, rectangleLineWidth,
+ BreakStrategy::HighQuality, HyphenationFrequency::None,
+ false /* justified */);
+ };
+
+ {
+ constexpr float LINE_WIDTH = 100;
+ // "is an" is a single replacement span. Do not break.
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ {"This is an ", 100, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"example ", 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"text.", 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ };
+ // clang-format on
+ const auto actual = doLineBreak(LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 90;
+ // "is an" is a single replacement span. Do not break.
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ {"This ", 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"is an ", 50, NO_START_HYPHEN, NO_END_HYPHEN, 0, 0},
+ {"example ",70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"text.", 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ };
+ // clang-format on
+ const auto actual = doLineBreak(LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 10;
+ // "is an" is a single replacement span. Do not break.
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ {"T", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"h", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"i", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"s ", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"is an ", 50, NO_START_HYPHEN, NO_END_HYPHEN, 0, 0},
+ {"e", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"x", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"a", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"m", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"p", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"l", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"e ", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"t", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"e", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"x", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"t", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {".", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ };
+ // clang-format on
+ const auto actual = doLineBreak(LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+}
+
+TEST_F(OptimalLineBreakerTest, testReplacementSpanNotBreakTest_CJK) {
+ constexpr float CHAR_WIDTH = 10.0;
+
+ constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
+ constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
+
+ // Example string: "Today is a sunny day." in Japanese.
+ const auto textBuf = utf8ToUtf16("\u672C\u65E5\u306F\u6674\u5929\u306A\u308A");
+
+ // In this test case, assign a replacement run for "\u6674\u5929" with 5 times of CHAR_WIDTH.
+ auto doLineBreak = [=](float width) {
+ MeasuredTextBuilder builder;
+ builder.addCustomRun<ConstantRun>(Range(0, 3), "ja-JP", CHAR_WIDTH, ASCENT, DESCENT);
+ builder.addReplacementRun(3, 5, 5 * CHAR_WIDTH, LocaleListCache::getId("ja-JP"));
+ 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 */);
+ RectangleLineWidth rectangleLineWidth(width);
+ TabStops tabStops(nullptr, 0, 0);
+ return breakLineOptimal(textBuf, *measuredText, rectangleLineWidth,
+ BreakStrategy::HighQuality, HyphenationFrequency::None,
+ false /* justified */);
+ };
+
+ {
+ constexpr float LINE_WIDTH = 100;
+ // "\u6674\u5929" is a single replacement span. Do not break.
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ {"\u672C\u65E5\u306F\u6674\u5929\u306A\u308A",
+ 100, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ };
+ // clang-format on
+ const auto actual = doLineBreak(LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 90;
+ // "\u6674\u5929" is a single replacement span. Do not break.
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ {"\u672C\u65E5\u306F\u6674\u5929\u306A",
+ 90, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"\u308A",
+ 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ };
+ // clang-format on
+ const auto actual = doLineBreak(LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 80;
+ // "\u6674\u5929" is a single replacement span. Do not break.
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ {"\u672C\u65E5\u306F\u6674\u5929",
+ 80, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"\u306A\u308A",
+ 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ };
+ // clang-format on
+ const auto actual = doLineBreak(LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 70;
+ // "\u6674\u5929" is a single replacement span. Do not break.
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ {"\u672C\u65E5\u306F", 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"\u6674\u5929\u306A\u308A", 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ };
+ // clang-format on
+ const auto actual = doLineBreak(LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 60;
+ // "\u6674\u5929" is a single replacement span. Do not break.
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ {"\u672C\u65E5\u306F", 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"\u6674\u5929\u306A", 60, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"\u308A", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ };
+ // clang-format on
+ const auto actual = doLineBreak(LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 50;
+ // "\u6674\u5929" is a single replacement span. Do not break.
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ {"\u672C\u65E5\u306F", 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"\u6674\u5929", 50, NO_START_HYPHEN, NO_END_HYPHEN, 0, 0},
+ {"\u306A\u308A", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ };
+ // clang-format on
+ const auto actual = doLineBreak(LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 40;
+ // "\u6674\u5929" is a single replacement span. Do not break.
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ {"\u672C\u65E5\u306F", 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"\u6674\u5929", 50, NO_START_HYPHEN, NO_END_HYPHEN, 0, 0},
+ {"\u306A\u308A", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ };
+ // clang-format on
+ const auto actual = doLineBreak(LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 10;
+ // "\u6674\u5929" is a single replacement span. Do not break.
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ {"\u672C", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"\u65E5", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"\u306F", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"\u6674\u5929", 50, NO_START_HYPHEN, NO_END_HYPHEN, 0, 0},
+ {"\u306A", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"\u308A", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ };
+ // clang-format on
+ const auto actual = doLineBreak(LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+}
+
+// http://b/119657685
+// Following test case is for verifying that the ReplacementSpan should not be broken into multiple
+// pieces. The actual break point is not a part of expectation. For example, it would be good to
+// break the starting offset of the ReplacementSpan for some case.
+TEST_F(OptimalLineBreakerTest, testReplacementSpan_GraphemeLineBreakWithMultipleRepalcementSpans) {
+ constexpr float CHAR_WIDTH = 10.0;
+
+ constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
+ constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
+
+ const auto textBuf = utf8ToUtf16("ab de\u00A0\u00A0fg ij\u00A0\u00A0kl no\u00A0\u00A0pq st");
+
+ auto doLineBreak = [=](float width) {
+ MeasuredTextBuilder builder;
+ builder.addReplacementRun(0, 5, 5 * CHAR_WIDTH, LocaleListCache::getId("en-US"));
+ builder.addCustomRun<ConstantRun>(Range(5, 7), "en-US", CHAR_WIDTH, ASCENT, DESCENT);
+ builder.addReplacementRun(7, 12, 5 * CHAR_WIDTH, LocaleListCache::getId("en-US"));
+ builder.addCustomRun<ConstantRun>(Range(12, 14), "en-US", CHAR_WIDTH, ASCENT, DESCENT);
+ builder.addReplacementRun(14, 19, 5 * CHAR_WIDTH, LocaleListCache::getId("en-US"));
+ 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 */);
+ RectangleLineWidth rectangleLineWidth(width);
+ TabStops tabStops(nullptr, 0, 0);
+ return breakLineOptimal(textBuf, *measuredText, rectangleLineWidth,
+ BreakStrategy::HighQuality, HyphenationFrequency::None,
+ false /* justified */);
+ };
+
+ {
+ constexpr float LINE_WIDTH = 1000;
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ {"ab de\u00A0\u00A0fg ij\u00A0\u00A0kl no\u00A0\u00A0pq st",
+ 260, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ };
+ // clang-format on
+ const auto actual = doLineBreak(LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 250;
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ {"ab de\u00A0\u00A0fg ij\u00A0\u00A0kl no\u00A0\u00A0",
+ 210, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"pq st",
+ 50, NO_START_HYPHEN, NO_END_HYPHEN, 0, 0},
+ };
+ // clang-format on
+ const auto actual = doLineBreak(LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 180;
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ {"ab de\u00A0\u00A0fg ij\u00A0\u00A0",
+ 140, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"kl no\u00A0\u00A0pq st",
+ 120, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ };
+ // clang-format on
+ const auto actual = doLineBreak(LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 130;
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ {"ab de\u00A0\u00A0fg ij\u00A0",
+ 130, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"\u00A0kl no\u00A0\u00A0pq st",
+ 130, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ };
+ // clang-format on
+ const auto actual = doLineBreak(LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 110;
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ {"ab de\u00A0", 60, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"\u00A0fg ij\u00A0\u00A0", 80, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"kl no\u00A0\u00A0", 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"pq st", 50, NO_START_HYPHEN, NO_END_HYPHEN, 0, 0},
+ };
+ // clang-format on
+ const auto actual = doLineBreak(LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 60;
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ {"ab de\u00A0", 60, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"\u00A0fg ij", 60, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"\u00A0\u00A0", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"kl no\u00A0", 60, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"\u00A0pq st", 60, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ };
+ // clang-format on
+ const auto actual = doLineBreak(LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 50;
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ {"ab de", 50, NO_START_HYPHEN, NO_END_HYPHEN, 0, 0},
+ {"\u00A0\u00A0", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"fg ij", 50, NO_START_HYPHEN, NO_END_HYPHEN, 0, 0},
+ {"\u00A0\u00A0", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"kl no", 50, NO_START_HYPHEN, NO_END_HYPHEN, 0, 0},
+ {"\u00A0\u00A0", 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"pq st", 50, NO_START_HYPHEN, NO_END_HYPHEN, 0, 0},
+ };
+ // clang-format on
+ const auto actual = doLineBreak(LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+}
+
+TEST_F(OptimalLineBreakerTest, testReplacementSpanNotBreakTest_with_punctuation) {
+ constexpr float CHAR_WIDTH = 10.0;
+
+ constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
+ constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
+
+ const auto textBuf = utf8ToUtf16("This (is an) example text.");
+
+ // In this test case, assign a replacement run for "U+2639" with 5 times of CHAR_WIDTH.
+ auto doLineBreak = [=](float width) {
+ MeasuredTextBuilder builder;
+ builder.addCustomRun<ConstantRun>(Range(0, 6), "en-US", CHAR_WIDTH, ASCENT, DESCENT);
+ builder.addReplacementRun(6, 11, 5 * CHAR_WIDTH, LocaleListCache::getId("en-US"));
+ 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 */);
+ RectangleLineWidth rectangleLineWidth(width);
+ TabStops tabStops(nullptr, 0, 0);
+ return breakLineOptimal(textBuf, *measuredText, rectangleLineWidth,
+ BreakStrategy::HighQuality, HyphenationFrequency::Normal,
+ false /* justified */);
+ };
+
+ {
+ constexpr float LINE_WIDTH = 1000;
+ // "is an" is a single replacement span. Do not break.
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ {"This (is an) example text.",
+ 260, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ };
+ // clang-format on
+ const auto actual = doLineBreak(LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 250;
+ // "is an" is a single replacement span. Do not break.
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ {"This (is an) example ", 200, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"text.", 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ };
+ // clang-format on
+ const auto actual = doLineBreak(LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 190;
+ // "is an" is a single replacement span. Do not break.
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ {"This (is an) ", 120, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"example text.", 130, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ };
+ // clang-format on
+ const auto actual = doLineBreak(LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 120;
+ // "is an" is a single replacement span. Do not break.
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ {"This (is an) ", 120, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"example ", 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"text.", 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ };
+ // clang-format on
+ const auto actual = doLineBreak(LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 110;
+ // "is an" is a single replacement span. Do not break.
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ {"This ", 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"(is an) ", 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"example ", 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"text.", 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ };
+ // clang-format on
+ const auto actual = doLineBreak(LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 60;
+ // "is an" is a single replacement span. Do not break.
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ {"This ", 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"(is an", 60, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {") ex", 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"ample ", 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"text.", 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ };
+ // clang-format on
+ const auto actual = doLineBreak(LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 50;
+ // "is an" is a single replacement span. Do not break.
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ {"This ", 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"(", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"is an", 50, NO_START_HYPHEN, NO_END_HYPHEN, 0, 0},
+ {") ex", 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"ample ", 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"text.", 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ };
+ // clang-format on
+ const auto actual = doLineBreak(LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 40;
+ // "is an" is a single replacement span. Do not break.
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ {"This ", 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ // TODO(nona): This might be wrongly broken. "(is an" should be broken into "(" and
+ // "is an" as the desperate break.
+ {"(is an", 60, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {") ", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"exa", 30, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"mple ", 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"text", 40, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {".", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ };
+ // clang-format on
+ const auto actual = doLineBreak(LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 10;
+ // "is an" is a single replacement span. Do not break.
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ {"T", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"h", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"i", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"s ", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ // TODO(nona): This might be wrongly broken. "(is an" should be broken into "(" and
+ // "is an" as the desperate break.
+ {"(is an", 60, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {") ", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"e", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"x", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"a", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"m", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"p", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"l", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"e ", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"t", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"e", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"x", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"t", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {".", 10, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ };
+ // clang-format on
+ const auto actual = doLineBreak(LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+}
+
+TEST_F(OptimalLineBreakerTest, testControllCharAfterSpace) {
+ constexpr BreakStrategy HIGH_QUALITY = BreakStrategy::HighQuality;
+ constexpr BreakStrategy BALANCED = BreakStrategy::Balanced;
+ constexpr HyphenationFrequency NO_HYPHENATION = HyphenationFrequency::None;
+ const std::vector<uint16_t> textBuf = utf8ToUtf16("example \u2066example");
+
+ constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
+ constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
+ {
+ constexpr float LINE_WIDTH = 90;
+ // Note that HarfBuzz assigns 0px for control characters regardless of glyph existence in
+ // the font.
+ std::vector<LineBreakExpectation> expect = {
+ {"example ", 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT},
+ {"\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);
+ }
+}
} // namespace
} // namespace minikin
diff --git a/tests/unittest/SystemFontsTest.cpp b/tests/unittest/SystemFontsTest.cpp
new file mode 100644
index 0000000..fe603a9
--- /dev/null
+++ b/tests/unittest/SystemFontsTest.cpp
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "minikin/SystemFonts.h"
+
+#include <gtest/gtest.h>
+
+#include "minikin/FontCollection.h"
+
+#include "FontTestUtils.h"
+
+namespace minikin {
+namespace {
+
+class TestableSystemFonts : public SystemFonts {
+public:
+ TestableSystemFonts() : SystemFonts() {}
+ virtual ~TestableSystemFonts() {}
+
+ std::shared_ptr<FontCollection> findFontCollection(const std::string& familyName) const {
+ return findFontCollectionInternal(familyName);
+ }
+
+ void registerFallback(const std::string& familyName,
+ const std::shared_ptr<FontCollection>& fc) {
+ registerFallbackInternal(familyName, fc);
+ }
+
+ void registerDefault(const std::shared_ptr<FontCollection>& fc) { registerDefaultInternal(fc); }
+};
+
+TEST(SystemFontsTest, registerAndLookup) {
+ TestableSystemFonts systemFonts;
+ auto fc = buildFontCollection("Ascii.ttf");
+ systemFonts.registerFallback("sans", fc);
+ EXPECT_EQ(fc, systemFonts.findFontCollection("sans"));
+}
+
+TEST(SystemFontsTest, registerDefaultAndLookup) {
+ TestableSystemFonts systemFonts;
+ auto fc = buildFontCollection("Ascii.ttf");
+ systemFonts.registerDefault(fc);
+ EXPECT_EQ(fc, systemFonts.findFontCollection("unknown-name"));
+}
+
+TEST(SystemFontsTest, registerDefaultAndFallback) {
+ TestableSystemFonts systemFonts;
+ auto fc1 = buildFontCollection("Ascii.ttf");
+ auto fc2 = buildFontCollection("Bold.ttf");
+ systemFonts.registerDefault(fc1);
+ systemFonts.registerFallback("sans", fc2);
+ EXPECT_EQ(fc1, systemFonts.findFontCollection("unknown-name"));
+ EXPECT_EQ(fc2, systemFonts.findFontCollection("sans"));
+}
+
+} // namespace
+} // namespace minikin
diff --git a/tests/util/Android.bp b/tests/util/Android.bp
index 561643b..8bf125b 100644
--- a/tests/util/Android.bp
+++ b/tests/util/Android.bp
@@ -9,7 +9,7 @@
],
cflags: ["-Wall", "-Werror"],
export_include_dirs: ["."],
- shared_libs: ["libxml2"],
+ shared_libs: ["libxml2", "libft2"],
static_libs: ["libminikin"],
header_libs: ["libminikin-headers-for-tests"],
}
diff --git a/tests/util/FontTestUtils.cpp b/tests/util/FontTestUtils.cpp
index 8aaac3d..4143c04 100644
--- a/tests/util/FontTestUtils.cpp
+++ b/tests/util/FontTestUtils.cpp
@@ -58,12 +58,12 @@
}
xmlChar* variantXmlch = xmlGetProp(familyNode, (const xmlChar*)"variant");
- FontFamily::Variant variant = FontFamily::Variant::DEFAULT;
+ FamilyVariant variant = FamilyVariant::DEFAULT;
if (variantXmlch) {
if (xmlStrcmp(variantXmlch, (const xmlChar*)"elegant") == 0) {
- variant = FontFamily::Variant::ELEGANT;
+ variant = FamilyVariant::ELEGANT;
} else if (xmlStrcmp(variantXmlch, (const xmlChar*)"compact") == 0) {
- variant = FontFamily::Variant::COMPACT;
+ variant = FamilyVariant::COMPACT;
}
}
@@ -109,7 +109,8 @@
family = std::make_shared<FontFamily>(variant, std::move(fonts));
} else {
uint32_t langId = registerLocaleList(std::string((const char*)lang, xmlStrlen(lang)));
- family = std::make_shared<FontFamily>(langId, variant, std::move(fonts));
+ family = std::make_shared<FontFamily>(langId, variant, std::move(fonts),
+ false /* isCustomFallback */);
}
families.push_back(family);
}
@@ -128,12 +129,13 @@
return std::make_shared<FontFamily>(std::move(fonts));
}
-std::shared_ptr<FontFamily> buildFontFamily(const std::string& filePath, const std::string& lang) {
+std::shared_ptr<FontFamily> buildFontFamily(const std::string& filePath, const std::string& lang,
+ bool isCustomFallback) {
auto font = std::make_shared<FreeTypeMinikinFontForTest>(getTestFontPath(filePath));
std::vector<Font> fonts;
fonts.push_back(Font::Builder(font).build());
- return std::make_shared<FontFamily>(LocaleListCache::getId(lang), FontFamily::Variant::DEFAULT,
- std::move(fonts));
+ return std::make_shared<FontFamily>(LocaleListCache::getId(lang), FamilyVariant::DEFAULT,
+ std::move(fonts), isCustomFallback);
}
} // namespace minikin
diff --git a/tests/util/FontTestUtils.h b/tests/util/FontTestUtils.h
index ba85093..660438b 100644
--- a/tests/util/FontTestUtils.h
+++ b/tests/util/FontTestUtils.h
@@ -59,7 +59,16 @@
/**
* Build new FontFamily from single file with locale.
*/
-std::shared_ptr<FontFamily> buildFontFamily(const std::string& filePath, const std::string& lang);
+std::shared_ptr<FontFamily> buildFontFamily(const std::string& filePath, const std::string& lang,
+ bool isCustomFallback);
+
+/**
+ * Build new FontFamily from single file with locale.
+ */
+inline std::shared_ptr<FontFamily> buildFontFamily(const std::string& filePath,
+ const std::string& lang) {
+ return buildFontFamily(filePath, lang, false /* isCustomFallback */);
+}
} // namespace minikin
#endif // MINIKIN_FONT_TEST_UTILS_H
diff --git a/tests/util/FreeTypeMinikinFontForTest.cpp b/tests/util/FreeTypeMinikinFontForTest.cpp
index 7f70772..1ea0631 100644
--- a/tests/util/FreeTypeMinikinFontForTest.cpp
+++ b/tests/util/FreeTypeMinikinFontForTest.cpp
@@ -29,7 +29,10 @@
#include <log/log.h>
#include FT_OUTLINE_H
+#include "minikin/MinikinExtent.h"
#include "minikin/MinikinFont.h"
+#include "minikin/MinikinPaint.h"
+#include "minikin/MinikinRect.h"
namespace minikin {
namespace {
@@ -104,13 +107,11 @@
bounds->mBottom = FTPosToFloat(bbox.yMin);
}
-void FreeTypeMinikinFontForTest::GetFontExtent(MinikinExtent* extent,
- const MinikinPaint& /* paint */,
+void FreeTypeMinikinFontForTest::GetFontExtent(MinikinExtent* extent, const MinikinPaint& paint,
const FontFakery& /* fakery */) const {
- // TODO: Retrieve font metrics from FreeType.
- extent->ascent = -10.0f;
- extent->descent = 20.0f;
- extent->line_gap = 0.0f;
+ float upem = mFtFace->units_per_EM;
+ extent->ascent = -static_cast<float>(mFtFace->ascender) * paint.size / upem;
+ extent->descent = -static_cast<float>(mFtFace->descender) * paint.size / upem;
}
} // namespace minikin
diff --git a/tests/util/FreeTypeMinikinFontForTest.h b/tests/util/FreeTypeMinikinFontForTest.h
index d63b2bf..4b6ea05 100644
--- a/tests/util/FreeTypeMinikinFontForTest.h
+++ b/tests/util/FreeTypeMinikinFontForTest.h
@@ -17,6 +17,8 @@
#ifndef MINIKIN_TEST_FREE_TYPE_MINIKIN_FONT_FOR_TEST_H
#define MINIKIN_TEST_FREE_TYPE_MINIKIN_FONT_FOR_TEST_H
+#include <string>
+
#include "minikin/MinikinFont.h"
#include <ft2build.h>