1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #define LOG_TAG "Minikin"
18 
19 #include "BidiUtils.h"
20 
21 #include <algorithm>
22 
23 #include <unicode/ubidi.h>
24 #include <unicode/utf16.h>
25 
26 #include "minikin/Emoji.h"
27 
28 #include "MinikinInternal.h"
29 
30 namespace minikin {
31 
bidiToUBidiLevel(Bidi bidi)32 static inline UBiDiLevel bidiToUBidiLevel(Bidi bidi) {
33     switch (bidi) {
34         case Bidi::LTR:
35             return 0x00;
36         case Bidi::RTL:
37             return 0x01;
38         case Bidi::DEFAULT_LTR:
39             return UBIDI_DEFAULT_LTR;
40         case Bidi::DEFAULT_RTL:
41             return UBIDI_DEFAULT_RTL;
42         case Bidi::FORCE_LTR:
43         case Bidi::FORCE_RTL:
44             MINIKIN_NOT_REACHED("FORCE_LTR/FORCE_RTL can not be converted to UBiDiLevel.");
45             return 0x00;
46         default:
47             MINIKIN_NOT_REACHED("Unknown Bidi value.");
48             return 0x00;
49     }
50 }
51 
getRunInfoAt(uint32_t runOffset) const52 BidiText::RunInfo BidiText::getRunInfoAt(uint32_t runOffset) const {
53     MINIKIN_ASSERT(runOffset < mRunCount, "Out of range access. %d/%d", runOffset, mRunCount);
54     if (mRunCount == 1) {
55         // Single run. No need to iteract with UBiDi.
56         return {mRange, mIsRtl};
57     }
58 
59     int32_t startRun = -1;
60     int32_t lengthRun = -1;
61     const UBiDiDirection runDir = ubidi_getVisualRun(mBidi.get(), runOffset, &startRun, &lengthRun);
62     if (startRun == -1 || lengthRun == -1) {
63         ALOGE("invalid visual run");
64         return {Range::invalidRange(), false};
65     }
66     const uint32_t runStart = std::max(static_cast<uint32_t>(startRun), mRange.getStart());
67     const uint32_t runEnd = std::min(static_cast<uint32_t>(startRun + lengthRun), mRange.getEnd());
68     if (runEnd <= runStart) {
69         // skip the empty run.
70         return {Range::invalidRange(), false};
71     }
72     return {Range(runStart, runEnd), (runDir == UBIDI_RTL)};
73 }
74 
BidiText(const U16StringPiece & textBuf,const Range & range,Bidi bidiFlags)75 BidiText::BidiText(const U16StringPiece& textBuf, const Range& range, Bidi bidiFlags)
76         : mRange(range), mIsRtl(isRtl(bidiFlags)), mRunCount(1 /* by default, single run */) {
77     if (isOverride(bidiFlags)) {
78         // force single run.
79         return;
80     }
81 
82     mBidi.reset(ubidi_open());
83     if (!mBidi) {
84         ALOGE("error creating bidi object");
85         return;
86     }
87     UErrorCode status = U_ZERO_ERROR;
88     // Set callbacks to override bidi classes of new emoji
89     ubidi_setClassCallback(mBidi.get(), emojiBidiOverride, nullptr, nullptr, nullptr, &status);
90     if (!U_SUCCESS(status)) {
91         ALOGE("error setting bidi callback function, status = %d", status);
92         return;
93     }
94 
95     const UBiDiLevel bidiReq = bidiToUBidiLevel(bidiFlags);
96     ubidi_setPara(mBidi.get(), reinterpret_cast<const UChar*>(textBuf.data()), textBuf.size(),
97                   bidiReq, nullptr, &status);
98     if (!U_SUCCESS(status)) {
99         ALOGE("error calling ubidi_setPara, status = %d", status);
100         return;
101     }
102     // RTL paragraphs get an odd level, while LTR paragraphs get an even level,
103     const bool paraIsRTL = ubidi_getParaLevel(mBidi.get()) & 0x01;
104     const ssize_t rc = ubidi_countRuns(mBidi.get(), &status);
105     if (!U_SUCCESS(status) || rc < 0) {
106         ALOGW("error counting bidi runs, status = %d", status);
107         return;
108     }
109     if (rc == 0) {
110         mIsRtl = paraIsRTL;
111         return;
112     }
113     if (rc == 1) {
114         // If the paragraph is a single run, override the paragraph dirction with the run
115         // (actually the whole text) direction.
116         const UBiDiDirection runDir = ubidi_getVisualRun(mBidi.get(), 0, nullptr, nullptr);
117         mIsRtl = (runDir == UBIDI_RTL);
118         return;
119     }
120     mRunCount = rc;
121 }
122 
123 }  // namespace minikin
124