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