1
|
/*******************************************************************************
|
2
|
* Copyright (c) 2004, 2010 IBM Corporation and others.
|
3
|
* All rights reserved. This program and the accompanying materials
|
4
|
* are made available under the terms of the Eclipse Public License v1.0
|
5
|
* which accompanies this distribution, and is available at
|
6
|
* http://www.eclipse.org/legal/epl-v10.html
|
7
|
*
|
8
|
* Contributors:
|
9
|
* IBM Corporation - initial API and implementation
|
10
|
*******************************************************************************/
|
11
|
package org.eclipse.draw2d.text;
|
12
|
|
13
|
//import com.ibm.icu.text.Bidi;
|
14
|
|
15
|
import java.util.ArrayList;
|
16
|
import java.util.List;
|
17
|
|
18
|
import org.eclipse.draw2d.rap.swt.SWT;
|
19
|
import org.eclipse.swt.graphics.TextLayout;
|
20
|
|
21
|
/**
|
22
|
* A helper class for a BlockFlow that does Bidi evaluation of all the text in
|
23
|
* that block.
|
24
|
* <p>
|
25
|
* WARNING: This class is for INTERNAL use only.
|
26
|
*
|
27
|
* @author Pratik Shah
|
28
|
* @since 3.1
|
29
|
*/
|
30
|
public final class BidiProcessor {
|
31
|
|
32
|
/*
|
33
|
* Workaround for Mac OS. AWT DLL cannot start properly on carbon.
|
34
|
* Waiting for bug 82104
|
35
|
*/
|
36
|
private static final boolean isMacOS = SWT.getPlatform().equals("carbon") || SWT.getPlatform().equals("cocoa"); //$NON-NLS-1$ //$NON-NLS-2$
|
37
|
|
38
|
/**
|
39
|
* A helper class to hold information about contributions made to this
|
40
|
* processor.
|
41
|
*
|
42
|
* @author Pratik Shah
|
43
|
* @since 3.1
|
44
|
*/
|
45
|
private static class BidiEntry {
|
46
|
int begin, end;
|
47
|
FlowFigure fig;
|
48
|
|
49
|
BidiEntry(FlowFigure fig, int offset, int length) {
|
50
|
this.fig = fig;
|
51
|
this.begin = offset;
|
52
|
this.end = offset + length;
|
53
|
}
|
54
|
}
|
55
|
|
56
|
/**
|
57
|
* A singleton instance.
|
58
|
*/
|
59
|
public static final BidiProcessor INSTANCE = new BidiProcessor();
|
60
|
|
61
|
private StringBuffer bidiText;
|
62
|
private List list = new ArrayList();
|
63
|
private int orientation = SWT.LEFT_TO_RIGHT;
|
64
|
|
65
|
private BidiProcessor() {
|
66
|
}
|
67
|
|
68
|
/**
|
69
|
* Records a String contribution for this bidi context. Contributions are
|
70
|
* concatenated (in the order that they were contributed) to make the final
|
71
|
* String which will determine the bidi info for all contributors.
|
72
|
*
|
73
|
* @param fig
|
74
|
* the figure that is contributing the given text
|
75
|
* @param str
|
76
|
* the text contributed by the given figure
|
77
|
* @see #addControlChar(char)
|
78
|
*/
|
79
|
public void add(FlowFigure fig, String str) {
|
80
|
// We are currently tracking empty contributions ("")
|
81
|
list.add(new BidiEntry(fig, bidiText.length(), str.length()));
|
82
|
bidiText.append(str);
|
83
|
}
|
84
|
|
85
|
/**
|
86
|
* Records a character contribution for this bidi context. Contributions are
|
87
|
* concatenated (in the order that they were contributed) to make the final
|
88
|
* String which will determine the bidi info for all contributors.
|
89
|
*
|
90
|
* @param fig
|
91
|
* the figure that is contributing the given text
|
92
|
* @param c
|
93
|
* the character being added
|
94
|
* @see #addControlChar(char)
|
95
|
*/
|
96
|
public void add(FlowFigure fig, char c) {
|
97
|
list.add(new BidiEntry(fig, bidiText.length(), 1));
|
98
|
bidiText.append(c);
|
99
|
}
|
100
|
|
101
|
/**
|
102
|
* This methods allows FlowFigures to contribute text that may effect the
|
103
|
* bidi evaluation, but is not text that is visible on the screen. The bidi
|
104
|
* level of such text is reported back to the contributing figure.
|
105
|
*
|
106
|
* @param c
|
107
|
* the control character
|
108
|
*/
|
109
|
public void addControlChar(char c) {
|
110
|
bidiText.append(c);
|
111
|
}
|
112
|
|
113
|
/**
|
114
|
* Breaks the given int array into bidi levels for each figure based on
|
115
|
* their contributed text, and assigns those levels to each figure. Also
|
116
|
* determines if shaping needs to occur between figures and sets the
|
117
|
* appendJoiner, prependJoiner accordingly.
|
118
|
*
|
119
|
* @param levels
|
120
|
* the calculated levels of all the text in the block
|
121
|
*/
|
122
|
private void assignResults(int[] levels) {
|
123
|
BidiEntry prevEntry = null, entry = null;
|
124
|
BidiInfo prevInfo = null, info = null;
|
125
|
int end = 2, start = 0;
|
126
|
for (int i = 0; i < list.size(); i++) {
|
127
|
entry = (BidiEntry) list.get(i);
|
128
|
info = new BidiInfo();
|
129
|
|
130
|
while (levels[end] < entry.end)
|
131
|
end += 2;
|
132
|
|
133
|
int levelInfo[];
|
134
|
if (end == start) {
|
135
|
levelInfo = new int[1];
|
136
|
if (prevInfo != null)
|
137
|
levelInfo[0] = prevInfo.levelInfo[prevInfo.levelInfo.length - 1];
|
138
|
else
|
139
|
levelInfo[0] = (orientation == SWT.LEFT_TO_RIGHT) ? 0 : 1;
|
140
|
} else {
|
141
|
levelInfo = new int[end - start - 1];
|
142
|
System.arraycopy(levels, start + 1, levelInfo, 0,
|
143
|
levelInfo.length);
|
144
|
}
|
145
|
for (int j = 1; j < levelInfo.length; j += 2)
|
146
|
levelInfo[j] -= entry.begin;
|
147
|
info.levelInfo = levelInfo;
|
148
|
|
149
|
// Compare current and previous for joiners, and commit previous
|
150
|
// BidiInfo.
|
151
|
if (prevEntry != null) {
|
152
|
if (// if we started in the middle of a level run
|
153
|
levels[start] < entry.begin
|
154
|
// and the level run is odd
|
155
|
&& levels[start + 1] % 2 == 1
|
156
|
// and the first character of this figure is Arabic
|
157
|
&& isJoiner(entry.begin)
|
158
|
// and the last character of the previous figure was
|
159
|
// Arabic
|
160
|
&& isPrecedingJoiner(entry.begin))
|
161
|
prevInfo.trailingJoiner = info.leadingJoiner = true;
|
162
|
prevEntry.fig.setBidiInfo(prevInfo);
|
163
|
}
|
164
|
prevEntry = entry;
|
165
|
prevInfo = info;
|
166
|
if (entry.end == levels[end])
|
167
|
start = end;
|
168
|
else
|
169
|
start = end - 2;
|
170
|
}
|
171
|
if (entry != null)
|
172
|
entry.fig.setBidiInfo(info);
|
173
|
}
|
174
|
|
175
|
private boolean isJoiner(int begin) {
|
176
|
return begin < bidiText.length()
|
177
|
&& isJoiningCharacter(bidiText.charAt(begin));
|
178
|
}
|
179
|
|
180
|
/**
|
181
|
* @param the
|
182
|
* character to be evaluated
|
183
|
* @return <code>true</code> if the given character is Arabic or ZWJ
|
184
|
*/
|
185
|
private boolean isJoiningCharacter(char c) {
|
186
|
return Character.getDirectionality(c) == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC
|
187
|
|| c == BidiChars.ZWJ;
|
188
|
}
|
189
|
|
190
|
private boolean isPrecedingJoiner(int begin) {
|
191
|
return begin > 0 && isJoiningCharacter(bidiText.charAt(begin - 1));
|
192
|
}
|
193
|
|
194
|
/**
|
195
|
* Processes the contributed text, determines the Bidi levels, and assigns
|
196
|
* them to the FlowFigures that made thet contributions. This class is for
|
197
|
* INTERNAL use only. Shaping of visually contiguous Arabic characters that
|
198
|
* are split in different figures is also handled. This method will do
|
199
|
* nothing if the contributed text does not require Bidi evaluation. All
|
200
|
* contributions are discarded at the end of this method.
|
201
|
*/
|
202
|
public void process() {
|
203
|
try {
|
204
|
if (bidiText.length() == 0 || isMacOS)
|
205
|
return;
|
206
|
char[] chars = new char[bidiText.length()];
|
207
|
bidiText.getChars(0, bidiText.length(), chars, 0);
|
208
|
|
209
|
//Not supported in RAP
|
210
|
// if (orientation != SWT.RIGHT_TO_LEFT
|
211
|
// && !Bidi.requiresBidi(chars, 0, chars.length - 1))
|
212
|
// return;
|
213
|
|
214
|
int[] levels = new int[15];
|
215
|
TextLayout layout = FlowUtilities.getTextLayout();
|
216
|
|
217
|
layout.setOrientation(orientation);
|
218
|
layout.setText(bidiText.toString());
|
219
|
int j = 0, offset, prevLevel = -1;
|
220
|
for (offset = 0; offset < chars.length; offset++) {
|
221
|
int newLevel = layout.getLevel(offset);
|
222
|
if (newLevel != prevLevel) {
|
223
|
if (j + 3 > levels.length) {
|
224
|
int temp[] = levels;
|
225
|
levels = new int[levels.length * 2 + 1];
|
226
|
System.arraycopy(temp, 0, levels, 0, temp.length);
|
227
|
}
|
228
|
levels[j++] = offset;
|
229
|
levels[j++] = newLevel;
|
230
|
prevLevel = newLevel;
|
231
|
}
|
232
|
}
|
233
|
levels[j++] = offset;
|
234
|
|
235
|
if (j != levels.length) {
|
236
|
int[] newLevels = new int[j];
|
237
|
System.arraycopy(levels, 0, newLevels, 0, j);
|
238
|
levels = newLevels;
|
239
|
}
|
240
|
assignResults(levels);
|
241
|
|
242
|
// reset the orientation of the layout, in case it was set to RTL
|
243
|
layout.setOrientation(SWT.LEFT_TO_RIGHT);
|
244
|
} finally {
|
245
|
// will cause the fields to be reset for the next string to be
|
246
|
// processed
|
247
|
bidiText = null;
|
248
|
list.clear();
|
249
|
}
|
250
|
}
|
251
|
|
252
|
/**
|
253
|
* Sets the paragraph embedding. The given orientation will be used on
|
254
|
* TextLayout when determining the Bidi levels.
|
255
|
*
|
256
|
* @param newOrientation
|
257
|
* SWT.LEFT_TO_RIGHT or SWT.RIGHT_TO_LEFT
|
258
|
*/
|
259
|
public void setOrientation(int newOrientation) {
|
260
|
bidiText = new StringBuffer();
|
261
|
list.clear();
|
262
|
orientation = newOrientation;
|
263
|
}
|
264
|
|
265
|
}
|