xref: /aosp_15_r20/frameworks/minikin/libs/minikin/BidiUtils.cpp (revision 834a2baab5fdfc28e9a428ee87c7ea8f6a06a53d)
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 #include "BidiUtils.h"
18 
19 #include <algorithm>
20 
21 #include <unicode/ubidi.h>
22 #include <unicode/utf16.h>
23 
24 #include "minikin/Emoji.h"
25 
26 #include "MinikinInternal.h"
27 
28 namespace minikin {
29 
bidiToUBidiLevel(Bidi bidi)30 static inline UBiDiLevel bidiToUBidiLevel(Bidi bidi) {
31     switch (bidi) {
32         case Bidi::LTR:
33             return 0x00;
34         case Bidi::RTL:
35             return 0x01;
36         case Bidi::DEFAULT_LTR:
37             return UBIDI_DEFAULT_LTR;
38         case Bidi::DEFAULT_RTL:
39             return UBIDI_DEFAULT_RTL;
40         case Bidi::FORCE_LTR:
41         case Bidi::FORCE_RTL:
42             MINIKIN_NOT_REACHED("FORCE_LTR/FORCE_RTL can not be converted to UBiDiLevel.");
43             return 0x00;
44         default:
45             MINIKIN_NOT_REACHED("Unknown Bidi value.");
46             return 0x00;
47     }
48 }
49 
getRunInfoAt(uint32_t runOffset) const50 BidiText::RunInfo BidiText::getRunInfoAt(uint32_t runOffset) const {
51     MINIKIN_ASSERT(runOffset < mRunCount, "Out of range access. %d/%d", runOffset, mRunCount);
52     if (mRunCount == 1) {
53         // Single run. No need to iteract with UBiDi.
54         return {0, 1, mRange, mIsRtl};
55     }
56 
57     int32_t startRun = -1;
58     int32_t lengthRun = -1;
59     const UBiDiDirection runDir = ubidi_getVisualRun(mBidi.get(), runOffset, &startRun, &lengthRun);
60     if (startRun == -1 || lengthRun == -1) {
61         ALOGE("invalid visual run");
62         return {0, 1, Range::invalidRange(), false};
63     }
64     const uint32_t runStart = std::max(static_cast<uint32_t>(startRun), mRange.getStart());
65     const uint32_t runEnd = std::min(static_cast<uint32_t>(startRun + lengthRun), mRange.getEnd());
66     if (runEnd <= runStart) {
67         // skip the empty run.
68         return {0, 1, Range::invalidRange(), false};
69     }
70     return {runOffset, mRunCount, Range(runStart, runEnd), (runDir == UBIDI_RTL)};
71 }
72 
BidiText(const U16StringPiece & textBuf,const Range & range,Bidi bidiFlags)73 BidiText::BidiText(const U16StringPiece& textBuf, const Range& range, Bidi bidiFlags)
74         : mRange(range), mIsRtl(isRtl(bidiFlags)), mRunCount(1 /* by default, single run */) {
75     if (isOverride(bidiFlags)) {
76         // force single run.
77         return;
78     }
79 
80     mBidi.reset(ubidi_open());
81     if (!mBidi) {
82         ALOGE("error creating bidi object");
83         return;
84     }
85     UErrorCode status = U_ZERO_ERROR;
86     // Set callbacks to override bidi classes of new emoji
87     ubidi_setClassCallback(mBidi.get(), emojiBidiOverride, nullptr, nullptr, nullptr, &status);
88     if (!U_SUCCESS(status)) {
89         ALOGE("error setting bidi callback function, status = %d", status);
90         return;
91     }
92 
93     const UBiDiLevel bidiReq = bidiToUBidiLevel(bidiFlags);
94     ubidi_setPara(mBidi.get(), reinterpret_cast<const UChar*>(textBuf.data()), textBuf.size(),
95                   bidiReq, nullptr, &status);
96     if (!U_SUCCESS(status)) {
97         ALOGE("error calling ubidi_setPara, status = %d", status);
98         return;
99     }
100     // RTL paragraphs get an odd level, while LTR paragraphs get an even level,
101     const bool paraIsRTL = ubidi_getParaLevel(mBidi.get()) & 0x01;
102     const ssize_t rc = ubidi_countRuns(mBidi.get(), &status);
103     if (!U_SUCCESS(status) || rc < 0) {
104         ALOGW("error counting bidi runs, status = %d", status);
105         return;
106     }
107     if (rc == 0) {
108         mIsRtl = paraIsRTL;
109         return;
110     }
111     if (rc == 1) {
112         // If the paragraph is a single run, override the paragraph dirction with the run
113         // (actually the whole text) direction.
114         const UBiDiDirection runDir = ubidi_getVisualRun(mBidi.get(), 0, nullptr, nullptr);
115         mIsRtl = (runDir == UBIDI_RTL);
116         return;
117     }
118     mRunCount = rc;
119 }
120 
121 }  // namespace minikin
122