Enable nested bidi levels in a paragraph.
Changes the internal representation of direction information in the Directions object to be a visually-ordered list of start/length+direction pairs instead of a list of directionality inversion offsets. Rewrite Layout.getOffsetToLeft/RightOf to use run information instead of width metrics. Remove java Bidi, use native. Switch bidi tests to test native, expect levels instead of dirs. Add test of directionality. Leave in switch to turn new code off and restore previous behavior for now. Change-Id: Iea8bb46c678a18820e237c90f76007a084c83051
This commit is contained in:
@@ -310,7 +310,6 @@ extends Layout
|
||||
|
||||
Directions[] objects = new Directions[1];
|
||||
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
ints[START] = reflowed.getLineStart(i) |
|
||||
(reflowed.getParagraphDirection(i) << DIR_SHIFT) |
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -224,7 +224,7 @@ extends Layout
|
||||
|
||||
boolean easy = true;
|
||||
boolean altered = false;
|
||||
int dir = DEFAULT_DIR; // XXX
|
||||
int dir = DEFAULT_DIR; // XXX pass value in
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (chs[i] >= FIRST_RIGHT_TO_LEFT) {
|
||||
@@ -253,7 +253,8 @@ extends Layout
|
||||
|
||||
if (!easy) {
|
||||
// XXX put override flags, etc. into chdirs
|
||||
dir = bidi(dir, chs, chdirs, n, false);
|
||||
// XXX supply dir rather than force
|
||||
dir = AndroidBidi.bidi(DIR_REQUEST_DEFAULT_LTR, chs, chdirs, n, false);
|
||||
|
||||
// Do mirroring for right-to-left segments
|
||||
|
||||
@@ -606,239 +607,6 @@ extends Layout
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the unicode bidi algorithm on the first n chars in chs, returning
|
||||
* the char dirs in chInfo and the base line direction of the first
|
||||
* paragraph.
|
||||
*
|
||||
* XXX change result from dirs to levels
|
||||
*
|
||||
* @param dir the direction flag, either DIR_REQUEST_LTR,
|
||||
* DIR_REQUEST_RTL, DIR_REQUEST_DEFAULT_LTR, or DIR_REQUEST_DEFAULT_RTL.
|
||||
* @param chs the text to examine
|
||||
* @param chInfo on input, if hasInfo is true, override and other flags
|
||||
* representing out-of-band embedding information. On output, the generated
|
||||
* dirs of the text.
|
||||
* @param n the length of the text/information in chs and chInfo
|
||||
* @param hasInfo true if chInfo has input information, otherwise the
|
||||
* input data in chInfo is ignored.
|
||||
* @return the resolved direction level of the first paragraph, either
|
||||
* DIR_LEFT_TO_RIGHT or DIR_RIGHT_TO_LEFT.
|
||||
*/
|
||||
/* package */ static int bidi(int dir, char[] chs, byte[] chInfo, int n,
|
||||
boolean hasInfo) {
|
||||
|
||||
AndroidCharacter.getDirectionalities(chs, chInfo, n);
|
||||
|
||||
/*
|
||||
* Determine primary paragraph direction if not specified
|
||||
*/
|
||||
if (dir != DIR_REQUEST_LTR && dir != DIR_REQUEST_RTL) {
|
||||
// set up default
|
||||
dir = dir >= 0 ? DIR_LEFT_TO_RIGHT : DIR_RIGHT_TO_LEFT;
|
||||
for (int j = 0; j < n; j++) {
|
||||
int d = chInfo[j];
|
||||
|
||||
if (d == Character.DIRECTIONALITY_LEFT_TO_RIGHT) {
|
||||
dir = DIR_LEFT_TO_RIGHT;
|
||||
break;
|
||||
}
|
||||
if (d == Character.DIRECTIONALITY_RIGHT_TO_LEFT) {
|
||||
dir = DIR_RIGHT_TO_LEFT;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final byte SOR = dir == DIR_LEFT_TO_RIGHT ?
|
||||
Character.DIRECTIONALITY_LEFT_TO_RIGHT :
|
||||
Character.DIRECTIONALITY_RIGHT_TO_LEFT;
|
||||
|
||||
/*
|
||||
* XXX Explicit overrides should go here
|
||||
*/
|
||||
|
||||
/*
|
||||
* Weak type resolution
|
||||
*/
|
||||
|
||||
// dump(chdirs, n, "initial");
|
||||
|
||||
// W1 non spacing marks
|
||||
for (int j = 0; j < n; j++) {
|
||||
if (chInfo[j] == Character.NON_SPACING_MARK) {
|
||||
if (j == 0)
|
||||
chInfo[j] = SOR;
|
||||
else
|
||||
chInfo[j] = chInfo[j - 1];
|
||||
}
|
||||
}
|
||||
|
||||
// dump(chdirs, n, "W1");
|
||||
|
||||
// W2 european numbers
|
||||
byte cur = SOR;
|
||||
for (int j = 0; j < n; j++) {
|
||||
byte d = chInfo[j];
|
||||
|
||||
if (d == Character.DIRECTIONALITY_LEFT_TO_RIGHT ||
|
||||
d == Character.DIRECTIONALITY_RIGHT_TO_LEFT ||
|
||||
d == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC)
|
||||
cur = d;
|
||||
else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER) {
|
||||
if (cur ==
|
||||
Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC)
|
||||
chInfo[j] = Character.DIRECTIONALITY_ARABIC_NUMBER;
|
||||
}
|
||||
}
|
||||
|
||||
// dump(chdirs, n, "W2");
|
||||
|
||||
// W3 arabic letters
|
||||
for (int j = 0; j < n; j++) {
|
||||
if (chInfo[j] == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC)
|
||||
chInfo[j] = Character.DIRECTIONALITY_RIGHT_TO_LEFT;
|
||||
}
|
||||
|
||||
// dump(chdirs, n, "W3");
|
||||
|
||||
// W4 single separator between numbers
|
||||
for (int j = 1; j < n - 1; j++) {
|
||||
byte d = chInfo[j];
|
||||
byte prev = chInfo[j - 1];
|
||||
byte next = chInfo[j + 1];
|
||||
|
||||
if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_SEPARATOR) {
|
||||
if (prev == Character.DIRECTIONALITY_EUROPEAN_NUMBER &&
|
||||
next == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
|
||||
chInfo[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER;
|
||||
} else if (d == Character.DIRECTIONALITY_COMMON_NUMBER_SEPARATOR) {
|
||||
if (prev == Character.DIRECTIONALITY_EUROPEAN_NUMBER &&
|
||||
next == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
|
||||
chInfo[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER;
|
||||
if (prev == Character.DIRECTIONALITY_ARABIC_NUMBER &&
|
||||
next == Character.DIRECTIONALITY_ARABIC_NUMBER)
|
||||
chInfo[j] = Character.DIRECTIONALITY_ARABIC_NUMBER;
|
||||
}
|
||||
}
|
||||
|
||||
// dump(chdirs, n, "W4");
|
||||
|
||||
// W5 european number terminators
|
||||
boolean adjacent = false;
|
||||
for (int j = 0; j < n; j++) {
|
||||
byte d = chInfo[j];
|
||||
|
||||
if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
|
||||
adjacent = true;
|
||||
else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_TERMINATOR && adjacent)
|
||||
chInfo[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER;
|
||||
else
|
||||
adjacent = false;
|
||||
}
|
||||
|
||||
//dump(chdirs, n, "W5");
|
||||
|
||||
// W5 european number terminators part 2,
|
||||
// W6 separators and terminators
|
||||
adjacent = false;
|
||||
for (int j = n - 1; j >= 0; j--) {
|
||||
byte d = chInfo[j];
|
||||
|
||||
if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
|
||||
adjacent = true;
|
||||
else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_TERMINATOR) {
|
||||
if (adjacent)
|
||||
chInfo[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER;
|
||||
else
|
||||
chInfo[j] = Character.DIRECTIONALITY_OTHER_NEUTRALS;
|
||||
}
|
||||
else {
|
||||
adjacent = false;
|
||||
|
||||
if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_SEPARATOR ||
|
||||
d == Character.DIRECTIONALITY_COMMON_NUMBER_SEPARATOR ||
|
||||
d == Character.DIRECTIONALITY_PARAGRAPH_SEPARATOR ||
|
||||
d == Character.DIRECTIONALITY_SEGMENT_SEPARATOR)
|
||||
chInfo[j] = Character.DIRECTIONALITY_OTHER_NEUTRALS;
|
||||
}
|
||||
}
|
||||
|
||||
// dump(chdirs, n, "W6");
|
||||
|
||||
// W7 strong direction of european numbers
|
||||
cur = SOR;
|
||||
for (int j = 0; j < n; j++) {
|
||||
byte d = chInfo[j];
|
||||
|
||||
if (d == SOR ||
|
||||
d == Character.DIRECTIONALITY_LEFT_TO_RIGHT ||
|
||||
d == Character.DIRECTIONALITY_RIGHT_TO_LEFT)
|
||||
cur = d;
|
||||
|
||||
if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
|
||||
chInfo[j] = cur;
|
||||
}
|
||||
|
||||
// dump(chdirs, n, "W7");
|
||||
|
||||
// N1, N2 neutrals
|
||||
cur = SOR;
|
||||
for (int j = 0; j < n; j++) {
|
||||
byte d = chInfo[j];
|
||||
|
||||
if (d == Character.DIRECTIONALITY_LEFT_TO_RIGHT ||
|
||||
d == Character.DIRECTIONALITY_RIGHT_TO_LEFT) {
|
||||
cur = d;
|
||||
} else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER ||
|
||||
d == Character.DIRECTIONALITY_ARABIC_NUMBER) {
|
||||
cur = Character.DIRECTIONALITY_RIGHT_TO_LEFT;
|
||||
} else {
|
||||
byte dd = SOR;
|
||||
int k;
|
||||
|
||||
for (k = j + 1; k < n; k++) {
|
||||
dd = chInfo[k];
|
||||
|
||||
if (dd == Character.DIRECTIONALITY_LEFT_TO_RIGHT ||
|
||||
dd == Character.DIRECTIONALITY_RIGHT_TO_LEFT) {
|
||||
break;
|
||||
}
|
||||
if (dd == Character.DIRECTIONALITY_EUROPEAN_NUMBER ||
|
||||
dd == Character.DIRECTIONALITY_ARABIC_NUMBER) {
|
||||
dd = Character.DIRECTIONALITY_RIGHT_TO_LEFT;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (int y = j; y < k; y++) {
|
||||
if (dd == cur)
|
||||
chInfo[y] = cur;
|
||||
else
|
||||
chInfo[y] = SOR;
|
||||
}
|
||||
|
||||
j = k - 1;
|
||||
}
|
||||
}
|
||||
|
||||
// dump(chdirs, n, "final");
|
||||
|
||||
// extra: enforce that all tabs and surrogate characters go the
|
||||
// primary direction
|
||||
// TODO: actually do directions right for surrogates
|
||||
|
||||
for (int j = 0; j < n; j++) {
|
||||
char c = chs[j];
|
||||
|
||||
if (c == '\t' || (c >= 0xD800 && c <= 0xDFFF)) {
|
||||
chInfo[j] = SOR;
|
||||
}
|
||||
}
|
||||
|
||||
return dir;
|
||||
}
|
||||
|
||||
private static final char FIRST_CJK = '\u2E80';
|
||||
/**
|
||||
* Returns true if the specified character is one of those specified
|
||||
@@ -1062,49 +830,123 @@ extends Layout
|
||||
if (tab)
|
||||
lines[off + TAB] |= TAB_MASK;
|
||||
|
||||
{
|
||||
lines[off + DIR] |= dir << DIR_SHIFT;
|
||||
|
||||
int cur = Character.DIRECTIONALITY_LEFT_TO_RIGHT;
|
||||
int count = 0;
|
||||
|
||||
if (!easy) {
|
||||
for (int k = start; k < end; k++) {
|
||||
if (chdirs[k - pstart] != cur) {
|
||||
count++;
|
||||
cur = chdirs[k - pstart];
|
||||
}
|
||||
lines[off + DIR] |= dir << DIR_SHIFT;
|
||||
Directions linedirs = DIRS_ALL_LEFT_TO_RIGHT;
|
||||
// easy means all chars < the first RTL, so no emoji, no nothing
|
||||
// XXX a run with no text or all spaces is easy but might be an empty
|
||||
// RTL paragraph. Make sure easy is false if this is the case.
|
||||
if (easy) {
|
||||
mLineDirections[j] = linedirs;
|
||||
} else {
|
||||
int startOff = start - pstart;
|
||||
int baseLevel = dir == DIR_LEFT_TO_RIGHT ? 0 : 1;
|
||||
int curLevel = chdirs[startOff];
|
||||
int minLevel = curLevel;
|
||||
int runCount = 1;
|
||||
for (int i = start + 1; i < end; ++i) {
|
||||
int level = chdirs[i - pstart];
|
||||
if (level != curLevel) {
|
||||
curLevel = level;
|
||||
++runCount;
|
||||
}
|
||||
}
|
||||
|
||||
// add final run for trailing counter-directional whitespace
|
||||
int visEnd = end;
|
||||
if ((curLevel & 1) != (baseLevel & 1)) {
|
||||
// look for visible end
|
||||
while (--visEnd >= start) {
|
||||
char ch = text.charAt(visEnd);
|
||||
|
||||
Directions linedirs;
|
||||
if (ch == '\n') {
|
||||
--visEnd;
|
||||
break;
|
||||
}
|
||||
|
||||
if (count == 0) {
|
||||
linedirs = DIRS_ALL_LEFT_TO_RIGHT;
|
||||
if (ch != ' ' && ch != '\t') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
++visEnd;
|
||||
if (visEnd != end) {
|
||||
++runCount;
|
||||
}
|
||||
}
|
||||
|
||||
if (runCount == 1 && minLevel == baseLevel) {
|
||||
if ((minLevel & 1) != 0) {
|
||||
linedirs = DIRS_ALL_RIGHT_TO_LEFT;
|
||||
}
|
||||
// we're done, only one run on this line
|
||||
} else {
|
||||
short[] ld = new short[count + 1];
|
||||
|
||||
cur = Character.DIRECTIONALITY_LEFT_TO_RIGHT;
|
||||
count = 0;
|
||||
int here = start;
|
||||
|
||||
for (int k = start; k < end; k++) {
|
||||
if (chdirs[k - pstart] != cur) {
|
||||
// XXX check to make sure we don't
|
||||
// overflow short
|
||||
ld[count++] = (short) (k - here);
|
||||
cur = chdirs[k - pstart];
|
||||
here = k;
|
||||
int[] ld = new int[runCount * 2];
|
||||
int maxLevel = minLevel;
|
||||
int levelBits = minLevel << RUN_LEVEL_SHIFT;
|
||||
{
|
||||
// Start of first pair is always 0, we write
|
||||
// length then start at each new run, and the
|
||||
// last run length after we're done.
|
||||
int n = 1;
|
||||
int prev = start;
|
||||
curLevel = minLevel;
|
||||
for (int i = start; i < visEnd; ++i) {
|
||||
int level = chdirs[i - pstart];
|
||||
if (level != curLevel) {
|
||||
curLevel = level;
|
||||
if (level > maxLevel) {
|
||||
maxLevel = level;
|
||||
} else if (level < minLevel) {
|
||||
minLevel = level;
|
||||
}
|
||||
// XXX ignore run length limit of 2^RUN_LEVEL_SHIFT
|
||||
ld[n++] = (i - prev) | levelBits;
|
||||
ld[n++] = i - start;
|
||||
levelBits = curLevel << RUN_LEVEL_SHIFT;
|
||||
prev = i;
|
||||
}
|
||||
}
|
||||
ld[n] = (visEnd - prev) | levelBits;
|
||||
if (visEnd < end) {
|
||||
ld[++n] = visEnd - start;
|
||||
ld[++n] = (end - visEnd) | (baseLevel << RUN_LEVEL_SHIFT);
|
||||
}
|
||||
}
|
||||
|
||||
ld[count] = (short) (end - here);
|
||||
|
||||
if (count == 1 && ld[0] == 0) {
|
||||
linedirs = DIRS_ALL_RIGHT_TO_LEFT;
|
||||
// See if we need to swap any runs.
|
||||
// If the min level run direction doesn't match the base
|
||||
// direction, we always need to swap (at this point
|
||||
// we have more than one run).
|
||||
// Otherwise, we don't need to swap the lowest level.
|
||||
// Since there are no logically adjacent runs at the same
|
||||
// level, if the max level is the same as the (new) min
|
||||
// level, we have a series of alternating levels that
|
||||
// is already in order, so there's no more to do.
|
||||
//
|
||||
boolean swap;
|
||||
if ((minLevel & 1) == baseLevel) {
|
||||
minLevel += 1;
|
||||
swap = maxLevel > minLevel;
|
||||
} else {
|
||||
linedirs = new Directions(ld);
|
||||
swap = runCount > 1;
|
||||
}
|
||||
if (swap) {
|
||||
for (int level = maxLevel - 1; level >= minLevel; --level) {
|
||||
for (int i = 0; i < ld.length; i += 2) {
|
||||
if (chdirs[startOff + ld[i]] >= level) {
|
||||
int e = i + 2;
|
||||
while (e < ld.length && chdirs[startOff + ld[e]] >= level) {
|
||||
e += 2;
|
||||
}
|
||||
for (int low = i, hi = e - 2; low < hi; low += 2, hi -= 2) {
|
||||
int x = ld[low]; ld[low] = ld[hi]; ld[hi] = x;
|
||||
x = ld[low+1]; ld[low+1] = ld[hi+1]; ld[hi+1] = x;
|
||||
}
|
||||
i = e + 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
linedirs = new Directions(ld);
|
||||
}
|
||||
|
||||
mLineDirections[j] = linedirs;
|
||||
|
||||
@@ -22,7 +22,7 @@ import android.util.Log;
|
||||
import junit.framework.TestCase;
|
||||
|
||||
/**
|
||||
* Tests StaticLayout bidi implementation.
|
||||
* Quick check of native bidi implementation.
|
||||
*/
|
||||
public class StaticLayoutBidiTest extends TestCase {
|
||||
|
||||
@@ -41,73 +41,47 @@ public class StaticLayoutBidiTest extends TestCase {
|
||||
|
||||
//@SmallTest
|
||||
public void testAllLtr() {
|
||||
expectBidi(REQ_DL, "a test", "000000", L);
|
||||
expectNativeBidi(REQ_DL, "a test", "000000", L);
|
||||
}
|
||||
|
||||
//@SmallTest
|
||||
public void testLtrRtl() {
|
||||
expectBidi(REQ_DL, "abc " + ALEF + BET + GIMEL, "0000111", L);
|
||||
expectNativeBidi(REQ_DL, "abc " + ALEF + BET + GIMEL, "0000111", L);
|
||||
}
|
||||
|
||||
//@SmallTest
|
||||
public void testAllRtl() {
|
||||
expectBidi(REQ_DL, ALEF + SP + ALEF + BET + GIMEL + DALET, "111111", R);
|
||||
expectNativeBidi(REQ_DL, ALEF + SP + ALEF + BET + GIMEL + DALET, "111111", R);
|
||||
}
|
||||
|
||||
//@SmallTest
|
||||
public void testRtlLtr() {
|
||||
expectBidi(REQ_DL, ALEF + BET + GIMEL + " abc", "1111000", R);
|
||||
expectNativeBidi(REQ_DL, ALEF + BET + GIMEL + " abc", "1111222", R);
|
||||
}
|
||||
|
||||
//@SmallTest
|
||||
public void testRAllLtr() {
|
||||
expectBidi(REQ_R, "a test", "000000", R);
|
||||
expectNativeBidi(REQ_R, "a test", "222222", R);
|
||||
}
|
||||
|
||||
//@SmallTest
|
||||
public void testRLtrRtl() {
|
||||
expectBidi(REQ_R, "abc " + ALEF + BET + GIMEL, "0001111", R);
|
||||
expectNativeBidi(REQ_R, "abc " + ALEF + BET + GIMEL, "2221111", R);
|
||||
}
|
||||
|
||||
//@SmallTest
|
||||
public void testLAllRtl() {
|
||||
expectBidi(REQ_L, ALEF + SP + ALEF + BET + GIMEL + DALET, "111111", L);
|
||||
expectNativeBidi(REQ_L, ALEF + SP + ALEF + BET + GIMEL + DALET, "111111", L);
|
||||
}
|
||||
|
||||
//@SmallTest
|
||||
public void testLRtlLtr() {
|
||||
expectBidi(REQ_L, ALEF + BET + GIMEL + " abc", "1110000", L);
|
||||
}
|
||||
|
||||
private void expectBidi(int dir, String text,
|
||||
String expectedLevels, int expectedDir) {
|
||||
char[] chs = text.toCharArray();
|
||||
int n = chs.length;
|
||||
byte[] chInfo = new byte[n];
|
||||
|
||||
int resultDir = StaticLayout.bidi(dir, chs, chInfo, n, false);
|
||||
|
||||
{
|
||||
StringBuilder sb = new StringBuilder("info:");
|
||||
for (int i = 0; i < n; ++i) {
|
||||
sb.append(" ").append(String.valueOf(chInfo[i]));
|
||||
}
|
||||
Log.i("BIDI", sb.toString());
|
||||
}
|
||||
|
||||
char[] resultLevelChars = new char[n];
|
||||
for (int i = 0; i < n; ++i) {
|
||||
resultLevelChars[i] = (char)('0' + chInfo[i]);
|
||||
}
|
||||
String resultLevels = new String(resultLevelChars);
|
||||
assertEquals("direction", expectedDir, resultDir);
|
||||
assertEquals("levels", expectedLevels, resultLevels);
|
||||
expectNativeBidi(REQ_DL, ALEF + BET + GIMEL + " abc", "1111222", R);
|
||||
}
|
||||
|
||||
//@SmallTest
|
||||
public void testNativeBidi() {
|
||||
// native bidi returns levels, not simply directions
|
||||
expectNativeBidi(REQ_DL, ALEF + BET + GIMEL + " abc", "1111222", R);
|
||||
expectNativeBidi(REQ_L, ALEF + BET + GIMEL + " abc", "1110000", L);
|
||||
}
|
||||
|
||||
private void expectNativeBidi(int dir, String text,
|
||||
|
||||
@@ -0,0 +1,233 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package android.text;
|
||||
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
import android.text.Layout.Directions;
|
||||
import android.text.StaticLayoutTest.LayoutBuilder;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Formatter;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
public class StaticLayoutDirectionsTest extends TestCase {
|
||||
private static final char ALEF = '\u05d0';
|
||||
|
||||
private static Directions dirs(int ... dirs) {
|
||||
return new Directions(dirs);
|
||||
}
|
||||
|
||||
private static final int LVL1_1 = 1 | (1 << Layout.RUN_LEVEL_SHIFT);
|
||||
private static final int LVL2_1 = 1 | (2 << Layout.RUN_LEVEL_SHIFT);
|
||||
private static final int LVL2_2 = 2 | (2 << Layout.RUN_LEVEL_SHIFT);
|
||||
|
||||
private static String[] texts = {
|
||||
"",
|
||||
" ",
|
||||
"a",
|
||||
"a1",
|
||||
"aA",
|
||||
"a1b",
|
||||
"a1A",
|
||||
"aA1",
|
||||
"aAb",
|
||||
"aA1B",
|
||||
"aA1B2",
|
||||
|
||||
// rtl
|
||||
"A",
|
||||
"A1",
|
||||
"Aa",
|
||||
"A1B",
|
||||
"A1a",
|
||||
"Aa1",
|
||||
"AaB"
|
||||
};
|
||||
|
||||
// Expected directions are an array of start/length+level pairs,
|
||||
// in visual order from the leading margin.
|
||||
private static Directions[] expected = {
|
||||
Layout.DIRS_ALL_LEFT_TO_RIGHT,
|
||||
Layout.DIRS_ALL_LEFT_TO_RIGHT,
|
||||
Layout.DIRS_ALL_LEFT_TO_RIGHT,
|
||||
Layout.DIRS_ALL_LEFT_TO_RIGHT,
|
||||
dirs(0, 1, 1, LVL1_1),
|
||||
Layout.DIRS_ALL_LEFT_TO_RIGHT,
|
||||
dirs(0, 2, 2, LVL1_1),
|
||||
dirs(0, 1, 2, LVL2_1, 1, LVL1_1),
|
||||
dirs(0, 1, 1, LVL1_1, 2, 1),
|
||||
dirs(0, 1, 3, LVL1_1, 2, LVL2_1, 1, LVL1_1),
|
||||
dirs(0, 1, 4, LVL2_1, 3, LVL1_1, 2, LVL2_1, 1, LVL1_1),
|
||||
|
||||
// rtl
|
||||
Layout.DIRS_ALL_RIGHT_TO_LEFT,
|
||||
dirs(0, LVL1_1, 1, LVL2_1),
|
||||
dirs(0, LVL1_1, 1, LVL2_1),
|
||||
dirs(0, LVL1_1, 1, LVL2_1, 2, LVL1_1),
|
||||
dirs(0, LVL1_1, 1, LVL2_2),
|
||||
dirs(0, LVL1_1, 1, LVL2_2),
|
||||
dirs(0, LVL1_1, 1, LVL2_1, 2, LVL1_1),
|
||||
};
|
||||
|
||||
private static String pseudoBidiToReal(String src) {
|
||||
char[] chars = src.toCharArray();
|
||||
for (int j = 0; j < chars.length; ++j) {
|
||||
char c = chars[j];
|
||||
if (c >= 'A' && c <= 'D') {
|
||||
chars[j] = (char)(ALEF + c - 'A');
|
||||
}
|
||||
}
|
||||
|
||||
return new String(chars, 0, chars.length);
|
||||
}
|
||||
|
||||
// @SmallTest
|
||||
public void testDirections() {
|
||||
StringBuilder buf = new StringBuilder("\n");
|
||||
Formatter f = new Formatter(buf);
|
||||
|
||||
LayoutBuilder b = StaticLayoutTest.builder();
|
||||
for (int i = 0; i < texts.length; ++i) {
|
||||
b.setText(pseudoBidiToReal(texts[i]));
|
||||
checkDirections(b.build(), i, b.text, expected, f);
|
||||
}
|
||||
if (buf.length() > 1) {
|
||||
fail(buf.toString());
|
||||
}
|
||||
}
|
||||
|
||||
// @SmallTest
|
||||
public void testTrailingWhitespace() {
|
||||
LayoutBuilder b = StaticLayoutTest.builder();
|
||||
b.setText(pseudoBidiToReal("Ab c"));
|
||||
float width = b.paint.measureText(b.text, 0, 5); // exclude 'c'
|
||||
b.setWidth(Math.round(width));
|
||||
Layout l = b.build();
|
||||
if (l.getLineCount() != 2) {
|
||||
throw new RuntimeException("expected 2 lines, got: " + l.getLineCount());
|
||||
}
|
||||
Directions result = l.getLineDirections(0);
|
||||
Directions expected = dirs(0, LVL1_1, 1, LVL2_1, 2, 3 | (1 << Layout.RUN_LEVEL_SHIFT));
|
||||
expectDirections("split line", expected, result);
|
||||
}
|
||||
|
||||
public void testNextToRightOf() {
|
||||
LayoutBuilder b = StaticLayoutTest.builder();
|
||||
b.setText(pseudoBidiToReal("aA1B2"));
|
||||
// visual a2B1A positions 04321
|
||||
// 0: |a2B1A, strong is sol, after -> 0
|
||||
// 1: a|2B1A, strong is a, after ->, 1
|
||||
// 2: a2|B1A, strong is B, after -> 4
|
||||
// 3: a2B|1A, strong is B, before -> 3
|
||||
// 4: a2B1|A, strong is A, after -> 2
|
||||
// 5: a2B1A|, strong is eol, before -> 5
|
||||
int[] expected = { 0, 1, 4, 3, 2, 5 };
|
||||
Layout l = b.build();
|
||||
int n = 0;
|
||||
for (int i = 1; i < expected.length; ++i) {
|
||||
int t = l.getOffsetToRightOf(n);
|
||||
if (t != expected[i]) {
|
||||
fail("offset[" + i + "] to right of: " + n + " expected: " +
|
||||
expected[i] + " got: " + t);
|
||||
}
|
||||
n = t;
|
||||
}
|
||||
}
|
||||
|
||||
public void testNextToLeftOf() {
|
||||
LayoutBuilder b = StaticLayoutTest.builder();
|
||||
b.setText(pseudoBidiToReal("aA1B2"));
|
||||
int[] expected = { 0, 1, 4, 3, 2, 5 };
|
||||
Layout l = b.build();
|
||||
int n = 5;
|
||||
for (int i = expected.length - 1; --i >= 0;) {
|
||||
int t = l.getOffsetToLeftOf(n);
|
||||
if (t != expected[i]) {
|
||||
fail("offset[" + i + "] to left of: " + n + " expected: " +
|
||||
expected[i] + " got: " + t);
|
||||
}
|
||||
n = t;
|
||||
}
|
||||
}
|
||||
|
||||
// utility, not really a test
|
||||
public void testMeasureText1() {
|
||||
LayoutBuilder b = StaticLayoutTest.builder();
|
||||
String text = "ABC"; // "abAB"
|
||||
b.setText(pseudoBidiToReal(text));
|
||||
Layout l = b.build();
|
||||
Directions directions = l.getLineDirections(0);
|
||||
|
||||
TextPaint workPaint = new TextPaint();
|
||||
|
||||
int dir = -1; // LEFT_TO_RIGHT
|
||||
boolean trailing = true;
|
||||
boolean alt = true;
|
||||
do {
|
||||
dir = -dir;
|
||||
do {
|
||||
trailing = !trailing;
|
||||
for (int offset = 0, end = b.text.length(); offset <= end; ++offset) {
|
||||
float width = Layout.measureText(b.paint,
|
||||
workPaint,
|
||||
b.text,
|
||||
0, offset, end,
|
||||
dir, directions,
|
||||
trailing, false,
|
||||
null);
|
||||
Log.i("BIDI", "dir: " + dir + " trail: " + trailing +
|
||||
" offset: " + offset + " width: " + width);
|
||||
}
|
||||
} while (!trailing);
|
||||
} while (dir > 0);
|
||||
|
||||
}
|
||||
|
||||
// utility for displaying arrays in hex
|
||||
private static String hexArray(int[] array) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append('{');
|
||||
for (int i : array) {
|
||||
if (sb.length() > 1) {
|
||||
sb.append(", ");
|
||||
}
|
||||
sb.append(Integer.toHexString(i));
|
||||
}
|
||||
sb.append('}');
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private void checkDirections(Layout l, int i, String text,
|
||||
Directions[] expectedDirs, Formatter f) {
|
||||
Directions expected = expectedDirs[i];
|
||||
Directions result = l.getLineDirections(0);
|
||||
if (!Arrays.equals(expected.mDirections, result.mDirections)) {
|
||||
f.format("%n[%2d] '%s', %s != %s", i, text,
|
||||
hexArray(expected.mDirections),
|
||||
hexArray(result.mDirections));
|
||||
}
|
||||
}
|
||||
|
||||
private void expectDirections(String msg, Directions expected, Directions result) {
|
||||
if (!Arrays.equals(expected.mDirections, result.mDirections)) {
|
||||
fail("expected: " + hexArray(expected.mDirections) +
|
||||
" got: " + hexArray(result.mDirections));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -228,11 +228,11 @@ public class StaticLayoutTest extends TestCase {
|
||||
}
|
||||
}
|
||||
|
||||
private static LayoutBuilder builder() {
|
||||
/* package */ static LayoutBuilder builder() {
|
||||
return new LayoutBuilder();
|
||||
}
|
||||
|
||||
private static class LayoutBuilder {
|
||||
/* package */ static class LayoutBuilder {
|
||||
String text = "This is a test";
|
||||
TextPaint paint = new TextPaint(); // default
|
||||
int width = 100;
|
||||
|
||||
Reference in New Issue
Block a user