Revive support of duplicated entry in cmap format 4

Minikin rejects unordered (including duplicated) cmap entries for
security reasons. (Issue 32178311).
The duplicated entries are invalid for cmap format 12 or cmap format 14
but valid for cmap format 4.

Bug: 76103006
Test: manual
Test: minikin_test
Test: atest CtsWidgetTestCases:EditTextTest
    CtsWidgetTestCases:TextViewFadingEdgeTest
    FrameworksCoreTests:TextViewFallbackLineSpacingTest
    FrameworksCoreTests:TextViewTest FrameworksCoreTests:TypefaceTest
    CtsGraphicsTestCases:TypefaceTest CtsWidgetTestCases:TextViewTest
    CtsTextTestCases FrameworksCoreTests:android.text
    CtsWidgetTestCases:TextViewPrecomputedTextTest

Change-Id: I902a11a93d01ccb609662e86e5ae2f3897940fb4
diff --git a/libs/minikin/CmapCoverage.cpp b/libs/minikin/CmapCoverage.cpp
index c00cec7..f6143d3 100644
--- a/libs/minikin/CmapCoverage.cpp
+++ b/libs/minikin/CmapCoverage.cpp
@@ -61,6 +61,22 @@
     }
 }
 
+// Returns true if the range is appended. Otherwise returns false as an error.
+static bool addRangeCmap4(std::vector<uint32_t>& coverage, uint32_t start, uint32_t end) {
+    if (!coverage.empty() && coverage.back() > end) {
+        // Reject unordered end code points.
+        return false;
+    }
+    if (coverage.empty() || coverage.back() < start) {
+        coverage.push_back(start);
+        coverage.push_back(end);
+        return true;
+    } else {
+        coverage.back() = end;
+        return true;
+    }
+}
+
 // Returns Range from given ranges vector. Returns invalidRange if i is out of range.
 static inline Range getRange(const std::vector<uint32_t>& r, size_t i) {
     return i + 1 < r.size() ? Range({r[i], r[i + 1]}) : Range::invalidRange();
@@ -157,13 +173,13 @@
         if (rangeOffset == 0) {
             uint32_t delta = readU16(data, kHeaderSize + 2 * (2 * segCount + i));
             if (((end + delta) & 0xffff) > end - start) {
-                if (!addRange(coverage, start, end + 1)) {
+                if (!addRangeCmap4(coverage, start, end + 1)) {
                     return false;
                 }
             } else {
                 for (uint32_t j = start; j < end + 1; j++) {
                     if (((j + delta) & 0xffff) != 0) {
-                        if (!addRange(coverage, j, j + 1)) {
+                        if (!addRangeCmap4(coverage, j, j + 1)) {
                             return false;
                         }
                     }
@@ -179,7 +195,7 @@
                 }
                 uint32_t glyphId = readU16(data, actualRangeOffset);
                 if (glyphId != 0) {
-                    if (!addRange(coverage, j, j + 1)) {
+                    if (!addRangeCmap4(coverage, j, j + 1)) {
                         return false;
                     }
                 }
diff --git a/tests/unittest/CmapCoverageTest.cpp b/tests/unittest/CmapCoverageTest.cpp
index febfc37..9dba583 100644
--- a/tests/unittest/CmapCoverageTest.cpp
+++ b/tests/unittest/CmapCoverageTest.cpp
@@ -490,6 +490,31 @@
         EXPECT_EQ(0U, coverage.length());
         EXPECT_TRUE(vsTables.empty());
     }
+    {
+        SCOPED_TRACE("Reversed end code points");
+        std::vector<uint8_t> table =
+                buildCmapFormat4Table(std::vector<uint16_t>({'b', 'b', 'a', 'a'}));
+        CmapBuilder builder(1);
+        builder.appendTable(0, 0, table);
+        std::vector<uint8_t> cmap = builder.build();
+
+        SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables);
+        EXPECT_EQ(0U, coverage.length());
+        EXPECT_TRUE(vsTables.empty());
+    }
+}
+
+TEST(CmapCoverageTest, duplicatedCmap4EntryTest) {
+    std::vector<std::unique_ptr<SparseBitSet>> vsTables;
+    std::vector<uint8_t> table = buildCmapFormat4Table(std::vector<uint16_t>({'a', 'b', 'b', 'b'}));
+    CmapBuilder builder(1);
+    builder.appendTable(0, 0, table);
+    std::vector<uint8_t> cmap = builder.build();
+
+    SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables);
+    EXPECT_TRUE(coverage.get('a'));
+    EXPECT_TRUE(coverage.get('b'));
+    EXPECT_TRUE(vsTables.empty());
 }
 
 TEST(CmapCoverageTest, brokenFormat12Table) {