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
30namespace minikin {
31
32static 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
52BidiText::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
75BidiText::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