summaryrefslogtreecommitdiffstats
path: root/intl/components/src/Bidi.cpp
blob: 2ce355c8ebd8b1619ed6628190557ecfb6b59b7c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "mozilla/intl/Bidi.h"
#include "mozilla/Casting.h"
#include "mozilla/intl/ICU4CGlue.h"

#include "unicode/ubidi.h"

namespace mozilla::intl {

Bidi::Bidi() { mBidi = ubidi_open(); }
Bidi::~Bidi() { ubidi_close(mBidi.GetMut()); }

ICUResult Bidi::SetParagraph(Span<const char16_t> aParagraph,
                             BidiEmbeddingLevel aLevel) {
  // Do not allow any reordering of the runs, as this can change the
  // performance characteristics of working with runs. In the default mode,
  // the levels can be iterated over directly, rather than relying on computing
  // logical runs on the fly. This can have negative performance characteristics
  // compared to iterating over the levels.
  //
  // In the UBIDI_REORDER_RUNS_ONLY the levels are encoded with additional
  // information which can be safely ignored in this Bidi implementation.
  // Note that this check is here since setting the mode must be done before
  // calls to setting the paragraph.
  MOZ_ASSERT(ubidi_getReorderingMode(mBidi.GetMut()) == UBIDI_REORDER_DEFAULT);

  UErrorCode status = U_ZERO_ERROR;
  ubidi_setPara(mBidi.GetMut(), aParagraph.Elements(),
                AssertedCast<int32_t>(aParagraph.Length()), aLevel, nullptr,
                &status);

  mLevels = nullptr;

  return ToICUResult(status);
}

Bidi::ParagraphDirection Bidi::GetParagraphDirection() const {
  switch (ubidi_getDirection(mBidi.GetConst())) {
    case UBIDI_LTR:
      return Bidi::ParagraphDirection::LTR;
    case UBIDI_RTL:
      return Bidi::ParagraphDirection::RTL;
    case UBIDI_MIXED:
      return Bidi::ParagraphDirection::Mixed;
    case UBIDI_NEUTRAL:
      // This is only used in `ubidi_getBaseDirection` which is unused in this
      // API.
      MOZ_ASSERT_UNREACHABLE("Unexpected UBiDiDirection value.");
  };
  return Bidi::ParagraphDirection::Mixed;
}

/* static */
void Bidi::ReorderVisual(const BidiEmbeddingLevel* aLevels, int32_t aLength,
                         int32_t* aIndexMap) {
  ubidi_reorderVisual(reinterpret_cast<const uint8_t*>(aLevels), aLength,
                      aIndexMap);
}

/* static */
Bidi::BaseDirection Bidi::GetBaseDirection(Span<const char16_t> aParagraph) {
  UBiDiDirection direction = ubidi_getBaseDirection(
      aParagraph.Elements(), AssertedCast<int32_t>(aParagraph.Length()));

  switch (direction) {
    case UBIDI_LTR:
      return Bidi::BaseDirection::LTR;
    case UBIDI_RTL:
      return Bidi::BaseDirection::RTL;
    case UBIDI_NEUTRAL:
      return Bidi::BaseDirection::Neutral;
    case UBIDI_MIXED:
      MOZ_ASSERT_UNREACHABLE("Unexpected UBiDiDirection value.");
  }

  return Bidi::BaseDirection::Neutral;
}

static BidiDirection ToBidiDirection(UBiDiDirection aDirection) {
  switch (aDirection) {
    case UBIDI_LTR:
      return BidiDirection::LTR;
    case UBIDI_RTL:
      return BidiDirection::RTL;
    case UBIDI_MIXED:
    case UBIDI_NEUTRAL:
      MOZ_ASSERT_UNREACHABLE("Unexpected UBiDiDirection value.");
  }
  return BidiDirection::LTR;
}

Result<int32_t, ICUError> Bidi::CountRuns() {
  UErrorCode status = U_ZERO_ERROR;
  int32_t runCount = ubidi_countRuns(mBidi.GetMut(), &status);
  if (U_FAILURE(status)) {
    return Err(ToICUError(status));
  }

  mLength = ubidi_getProcessedLength(mBidi.GetConst());
  mLevels = mLength > 0 ? reinterpret_cast<const BidiEmbeddingLevel*>(
                              ubidi_getLevels(mBidi.GetMut(), &status))
                        : nullptr;
  if (U_FAILURE(status)) {
    return Err(ToICUError(status));
  }

  return runCount;
}

void Bidi::GetLogicalRun(int32_t aLogicalStart, int32_t* aLogicalLimitOut,
                         BidiEmbeddingLevel* aLevelOut) {
  MOZ_ASSERT(mLevels, "CountRuns hasn't been run?");
  MOZ_RELEASE_ASSERT(aLogicalStart < mLength, "Out of bound");
  BidiEmbeddingLevel level = mLevels[aLogicalStart];
  int32_t limit;
  for (limit = aLogicalStart + 1; limit < mLength; limit++) {
    if (mLevels[limit] != level) {
      break;
    }
  }
  *aLogicalLimitOut = limit;
  *aLevelOut = level;
}

BidiEmbeddingLevel Bidi::GetParagraphEmbeddingLevel() const {
  return BidiEmbeddingLevel(ubidi_getParaLevel(mBidi.GetConst()));
}

BidiDirection Bidi::GetVisualRun(int32_t aRunIndex, int32_t* aLogicalStart,
                                 int32_t* aLength) {
  return ToBidiDirection(
      ubidi_getVisualRun(mBidi.GetMut(), aRunIndex, aLogicalStart, aLength));
}

}  // namespace mozilla::intl