blob: 67efafcd7e1fe77217789b40640c185a27c65669 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2004, 2010 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.draw2d.text;
//import com.ibm.icu.text.Bidi;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.TextLayout;
/**
* A helper class for a BlockFlow that does Bidi evaluation of all the text in
* that block.
* <p>
* WARNING: This class is for INTERNAL use only.
*
* @author Pratik Shah
* @since 3.1
*/
public final class BidiProcessor {
/**
* A helper class to hold information about contributions made to this
* processor.
*
* @author Pratik Shah
* @since 3.1
*/
private static class BidiEntry {
int begin, end;
FlowFigure fig;
BidiEntry(FlowFigure fig, int offset, int length) {
this.fig = fig;
this.begin = offset;
this.end = offset + length;
}
}
/**
* A singleton instance.
*/
public static final BidiProcessor INSTANCE = new BidiProcessor();
private StringBuffer bidiText;
private List list = new ArrayList();
private int orientation = SWT.LEFT_TO_RIGHT;
private BidiProcessor() {
}
/**
* Records a String contribution for this bidi context. Contributions are
* concatenated (in the order that they were contributed) to make the final
* String which will determine the bidi info for all contributors.
*
* @param fig
* the figure that is contributing the given text
* @param str
* the text contributed by the given figure
* @see #addControlChar(char)
*/
public void add(FlowFigure fig, String str) {
// We are currently tracking empty contributions ("")
list.add(new BidiEntry(fig, bidiText.length(), str.length()));
bidiText.append(str);
}
/**
* Records a character contribution for this bidi context. Contributions are
* concatenated (in the order that they were contributed) to make the final
* String which will determine the bidi info for all contributors.
*
* @param fig
* the figure that is contributing the given text
* @param c
* the character being added
* @see #addControlChar(char)
*/
public void add(FlowFigure fig, char c) {
list.add(new BidiEntry(fig, bidiText.length(), 1));
bidiText.append(c);
}
/**
* This methods allows FlowFigures to contribute text that may effect the
* bidi evaluation, but is not text that is visible on the screen. The bidi
* level of such text is reported back to the contributing figure.
*
* @param c
* the control character
*/
public void addControlChar(char c) {
bidiText.append(c);
}
/**
* Breaks the given int array into bidi levels for each figure based on
* their contributed text, and assigns those levels to each figure. Also
* determines if shaping needs to occur between figures and sets the
* appendJoiner, prependJoiner accordingly.
*
* @param levels
* the calculated levels of all the text in the block
*/
private void assignResults(int[] levels) {
BidiEntry prevEntry = null, entry = null;
BidiInfo prevInfo = null, info = null;
int end = 2, start = 0;
for (int i = 0; i < list.size(); i++) {
entry = (BidiEntry) list.get(i);
info = new BidiInfo();
while (levels[end] < entry.end)
end += 2;
int levelInfo[];
if (end == start) {
levelInfo = new int[1];
if (prevInfo != null)
levelInfo[0] = prevInfo.levelInfo[prevInfo.levelInfo.length - 1];
else
levelInfo[0] = (orientation == SWT.LEFT_TO_RIGHT) ? 0 : 1;
} else {
levelInfo = new int[end - start - 1];
System.arraycopy(levels, start + 1, levelInfo, 0,
levelInfo.length);
}
for (int j = 1; j < levelInfo.length; j += 2)
levelInfo[j] -= entry.begin;
info.levelInfo = levelInfo;
// Compare current and previous for joiners, and commit previous
// BidiInfo.
if (prevEntry != null) {
if (// if we started in the middle of a level run
levels[start] < entry.begin
// and the level run is odd
&& levels[start + 1] % 2 == 1
// and the first character of this figure is Arabic
&& isJoiner(entry.begin)
// and the last character of the previous figure was
// Arabic
&& isPrecedingJoiner(entry.begin))
prevInfo.trailingJoiner = info.leadingJoiner = true;
prevEntry.fig.setBidiInfo(prevInfo);
}
prevEntry = entry;
prevInfo = info;
if (entry.end == levels[end])
start = end;
else
start = end - 2;
}
if (entry != null)
entry.fig.setBidiInfo(info);
}
private boolean isJoiner(int begin) {
return begin < bidiText.length()
&& isJoiningCharacter(bidiText.charAt(begin));
}
/**
* @param the
* character to be evaluated
* @return <code>true</code> if the given character is Arabic or ZWJ
*/
private boolean isJoiningCharacter(char c) {
return Character.getDirectionality(c) == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC
|| c == BidiChars.ZWJ;
}
private boolean isPrecedingJoiner(int begin) {
return begin > 0 && isJoiningCharacter(bidiText.charAt(begin - 1));
}
/**
* Processes the contributed text, determines the Bidi levels, and assigns
* them to the FlowFigures that made thet contributions. This class is for
* INTERNAL use only. Shaping of visually contiguous Arabic characters that
* are split in different figures is also handled. This method will do
* nothing if the contributed text does not require Bidi evaluation. All
* contributions are discarded at the end of this method.
*/
public void process() {
try {
if (bidiText.length() == 0)
return;
char[] chars = new char[bidiText.length()];
bidiText.getChars(0, bidiText.length(), chars, 0);
//UNSUPPORTED - Not supported in RAP
// if (orientation != SWT.RIGHT_TO_LEFT
// && !Bidi.requiresBidi(chars, 0, chars.length - 1))
// return;
int[] levels = new int[15];
TextLayout layout = FlowUtilities.getTextLayout();
layout.setOrientation(orientation);
layout.setText(bidiText.toString());
int j = 0, offset, prevLevel = -1;
for (offset = 0; offset < chars.length; offset++) {
int newLevel = layout.getLevel(offset);
if (newLevel != prevLevel) {
if (j + 3 > levels.length) {
int temp[] = levels;
levels = new int[levels.length * 2 + 1];
System.arraycopy(temp, 0, levels, 0, temp.length);
}
levels[j++] = offset;
levels[j++] = newLevel;
prevLevel = newLevel;
}
}
levels[j++] = offset;
if (j != levels.length) {
int[] newLevels = new int[j];
System.arraycopy(levels, 0, newLevels, 0, j);
levels = newLevels;
}
assignResults(levels);
// reset the orientation of the layout, in case it was set to RTL
layout.setOrientation(SWT.LEFT_TO_RIGHT);
} finally {
// will cause the fields to be reset for the next string to be
// processed
bidiText = null;
list.clear();
}
}
/**
* Sets the paragraph embedding. The given orientation will be used on
* TextLayout when determining the Bidi levels.
*
* @param newOrientation
* SWT.LEFT_TO_RIGHT or SWT.RIGHT_TO_LEFT
*/
public void setOrientation(int newOrientation) {
bidiText = new StringBuffer();
list.clear();
orientation = newOrientation;
}
}