8144015: [PIT] failures of text layout font tests

8144023: [PIT] failure of text measurements in javax/swing/text/html/parser/Parser/6836089/bug6836089.java
8145542: The case failed automatically and thrown java.lang.ArrayIndexOutOfBoundsException exception
8151725: [macosx] ArrayIndexOOB exception when displaying Devanagari text in JEditorPane
8144240: [macosx][PIT] AIOOB in closed/javax/swing/text/GlyphPainter2/6427244/bug6427244.java
8152680: Regression in GlyphVector.getGlyphCharIndex behaviour
8158924: Incorrect i18n text document layout
8041480: ArrayIndexOutOfBoundsException when JTable contains certain string

Reviewed-by: serb, srl
This commit is contained in:
Phil Race 2016-09-01 11:29:20 -07:00
parent 63217bef37
commit 6f12a4ff0d
9 changed files with 1024 additions and 215 deletions

View File

@ -550,13 +550,16 @@ class ExtendedTextSourceLabel extends ExtendedTextLabel implements Decoration.La
return charinfo;
}
private static final boolean DEBUG = FontUtilities.debugFonts();
/*
* This takes the glyph info record obtained from the glyph vector and converts it into a similar record
* adjusted to represent character data instead. For economy we don't use glyph info records in this processing.
*
* Here are some constraints:
* - there can be more glyphs than characters (glyph insertion, perhaps based on normalization, has taken place)
* - there can not be fewer glyphs than characters (0xffff glyphs are inserted for characters ligaturized away)
* - there can be fewer glyphs than characters
* Some layout engines may insert 0xffff glyphs for characters ligaturized away, but
* not all do, and it cannot be relied upon.
* - each glyph maps to a single character, when multiple glyphs exist for a character they all map to it, but
* no two characters map to the same glyph
* - multiple glyphs mapping to the same character need not be in sequence (thai, tamil have split characters)
@ -578,7 +581,8 @@ class ExtendedTextSourceLabel extends ExtendedTextLabel implements Decoration.La
*
* The algorithm works in the following way:
* 1) we scan the glyphs ltr or rtl based on the bidi run direction
* 2) we can work in place, since we always consume a glyph for each char we write
* 2) Since the may be fewer glyphs than chars we cannot work in place.
* A new array is allocated for output.
* a) if the line is ltr, we start writing at position 0 until we finish, there may be leftver space
* b) if the line is rtl and 1-1, we start writing at position numChars/glyphs - 1 until we finish at 0
* c) otherwise if we don't finish at 0, we have to copy the data down
@ -594,7 +598,7 @@ class ExtendedTextSourceLabel extends ExtendedTextLabel implements Decoration.La
* iii) the x advance is the distance to the maximum x + adv of all glyphs whose advance is not zero
* iv) the y advance is the baseline
* v) vis x,y,w,h tightly encloses the vis x,y,w,h of all the glyphs with nonzero w and h
* 4) we can make some simple optimizations if we know some things:
* 4) In the future, we can make some simple optimizations to avoid copying if we know some things:
* a) if the mapping is 1-1, unidirectional, and there are no zero-adv glyphs, we just return the glyphinfo
* b) if the mapping is 1-1, unidirectional, we just adjust the remaining glyphs to originate at right/left of the base
* c) if the mapping is 1-1, we compute the base position and advance as we go, then go back to adjust the remaining glyphs
@ -625,23 +629,20 @@ class ExtendedTextSourceLabel extends ExtendedTextLabel implements Decoration.La
System.out.println(source);
}
/*
if ((gv.getDescriptionFlags() & 0x7) == 0) {
return glyphinfo;
}
*/
int numGlyphs = gv.getNumGlyphs();
if (numGlyphs == 0) {
return glyphinfo;
}
int[] indices = gv.getGlyphCharIndices(0, numGlyphs, null);
float[] charInfo = new float[source.getLength() * numvals];
boolean DEBUG = false;
if (DEBUG) {
System.err.println("number of glyphs: " + numGlyphs);
System.err.println("glyphinfo.len: " + glyphinfo.length);
System.err.println("indices.len: " + indices.length);
for (int i = 0; i < numGlyphs; ++i) {
System.err.println("g: " + i +
" v: " + gv.getGlyphCode(i) +
", x: " + glyphinfo[i*numvals+posx] +
", a: " + glyphinfo[i*numvals+advx] +
", n: " + indices[i]);
@ -650,22 +651,19 @@ class ExtendedTextSourceLabel extends ExtendedTextLabel implements Decoration.La
int minIndex = indices[0]; // smallest index seen this cluster
int maxIndex = minIndex; // largest index seen this cluster
int nextMin = 0; // expected smallest index for this cluster
int cp = 0; // character position
int cx = 0; // character index (logical)
int cc = 0;
int gp = 0; // glyph position
int gx = 0; // glyph index (visual)
int gxlimit = numGlyphs; // limit of gx, when we reach this we're done
int pdelta = numvals; // delta for incrementing positions
int xdelta = 1; // delta for incrementing indices
boolean ltr = (source.getLayoutFlags() & 0x1) == 0;
if (!ltr) {
boolean rtl = (source.getLayoutFlags() & 0x1) == 1;
if (rtl) {
minIndex = indices[numGlyphs - 1];
maxIndex = minIndex;
nextMin = 0; // still logical
cp = glyphinfo.length - numvals;
cx = 0; // still logical
cp = charInfo.length - numvals;
gp = glyphinfo.length - numvals;
gx = numGlyphs - 1;
gxlimit = -1;
@ -693,47 +691,36 @@ class ExtendedTextSourceLabel extends ExtendedTextLabel implements Decoration.La
float cposl = 0, cposr = 0, cvisl = 0, cvist = 0, cvisr = 0, cvisb = 0;
float baseline = 0;
// record if we have to copy data even when no cluster
boolean mustCopy = false;
while (gx != gxlimit) {
// start of new cluster
boolean haveCopy = false;
int clusterExtraGlyphs = 0;
minIndex = indices[gx];
maxIndex = minIndex;
cposl = glyphinfo[gp + posx];
cposr = cposl + glyphinfo[gp + advx];
cvisl = glyphinfo[gp + visx];
cvist = glyphinfo[gp + visy];
cvisr = cvisl + glyphinfo[gp + visw];
cvisb = cvist + glyphinfo[gp + vish];
// advance to next glyph
gx += xdelta;
gp += pdelta;
/*
while (gx != gxlimit && (glyphinfo[gp + advx] == 0 ||
minIndex != nextMin || indices[gx] <= maxIndex)) {
*/
while (gx != gxlimit &&
((glyphinfo[gp + advx] == 0) ||
(minIndex != nextMin) ||
(indices[gx] <= maxIndex) ||
(maxIndex - minIndex > clusterExtraGlyphs))) {
// initialize base data first time through, using base glyph
if (!haveCopy) {
int gps = gp - pdelta;
cposl = glyphinfo[gps + posx];
cposr = cposl + glyphinfo[gps + advx];
cvisl = glyphinfo[gps + visx];
cvist = glyphinfo[gps + visy];
cvisr = cvisl + glyphinfo[gps + visw];
cvisb = cvist + glyphinfo[gps + vish];
haveCopy = true;
++clusterExtraGlyphs; // have an extra glyph in this cluster
if (DEBUG) {
System.err.println("gp=" +gp +" adv=" + glyphinfo[gp + advx] +
" gx="+ gx+ " i[gx]="+indices[gx] +
" clusterExtraGlyphs="+clusterExtraGlyphs);
}
// have an extra glyph in this cluster
++clusterExtraGlyphs;
// adjust advance only if new glyph has non-zero advance
float radvx = glyphinfo[gp + advx];
if (radvx != 0) {
@ -764,110 +751,90 @@ class ExtendedTextSourceLabel extends ExtendedTextLabel implements Decoration.La
// done with cluster, gx and gp are set for next glyph
if (DEBUG) {
System.out.println("minIndex = " + minIndex + ", maxIndex = " + maxIndex);
System.err.println("minIndex = " + minIndex + ", maxIndex = " + maxIndex);
}
nextMin = maxIndex + 1;
// save adjustments to the base character and do common adjustments.
charInfo[cp + posx] = cposl;
charInfo[cp + posy] = baseline;
charInfo[cp + advx] = cposr - cposl;
charInfo[cp + advy] = 0;
charInfo[cp + visx] = cvisl;
charInfo[cp + visy] = cvist;
charInfo[cp + visw] = cvisr - cvisl;
charInfo[cp + vish] = cvisb - cvist;
cc++;
// do common character adjustments
glyphinfo[cp + posy] = baseline;
glyphinfo[cp + advy] = 0;
if (haveCopy) {
// save adjustments to the base character
glyphinfo[cp + posx] = cposl;
glyphinfo[cp + advx] = cposr - cposl;
glyphinfo[cp + visx] = cvisl;
glyphinfo[cp + visy] = cvist;
glyphinfo[cp + visw] = cvisr - cvisl;
glyphinfo[cp + vish] = cvisb - cvist;
// compare number of chars read with number of glyphs read.
// if more glyphs than chars, set mustCopy to true, as we'll always have
// to copy the data from here on out.
if (maxIndex - minIndex < clusterExtraGlyphs) {
mustCopy = true;
}
// Fix the characters that follow the base character.
// New values are all the same. Note we fix the number of characters
// we saw, not the number of glyphs we saw.
if (minIndex < maxIndex) {
if (!ltr) {
// if rtl, characters to left of base, else to right. reuse cposr.
cposr = cposl;
}
cvisr -= cvisl; // reuse, convert to deltas.
cvisb -= cvist;
int iMinIndex = minIndex, icp = cp / 8;
while (minIndex < maxIndex) {
++minIndex;
cx += xdelta;
cp += pdelta;
if (cp < 0 || cp >= glyphinfo.length) {
if (DEBUG) System.out.println("minIndex = " + iMinIndex + ", maxIndex = " + maxIndex + ", cp = " + icp);
}
glyphinfo[cp + posx] = cposr;
glyphinfo[cp + posy] = baseline;
glyphinfo[cp + advx] = 0;
glyphinfo[cp + advy] = 0;
glyphinfo[cp + visx] = cvisl;
glyphinfo[cp + visy] = cvist;
glyphinfo[cp + visw] = cvisr;
glyphinfo[cp + vish] = cvisb;
}
}
// no longer using this copy
haveCopy = false;
} else if (mustCopy) {
// out of synch, so we have to copy all the time now
int gpr = gp - pdelta;
glyphinfo[cp + posx] = glyphinfo[gpr + posx];
glyphinfo[cp + advx] = glyphinfo[gpr + advx];
glyphinfo[cp + visx] = glyphinfo[gpr + visx];
glyphinfo[cp + visy] = glyphinfo[gpr + visy];
glyphinfo[cp + visw] = glyphinfo[gpr + visw];
glyphinfo[cp + vish] = glyphinfo[gpr + vish];
/* We may have consumed multiple glyphs for this char position.
* Map those extra consumed glyphs to char positions that would follow
* up to the index prior to that which begins the next cluster.
* If we have reached the last glyph (reached gxlimit) then we need to
* map remaining unmapped chars to the same location as the last one.
*/
int tgt;
if (gx == gxlimit) {
tgt = charInfo.length / numvals;
} else {
tgt = indices[gx]-1;
}
// else glyphinfo is already at the correct character position, and is unchanged, so just leave it
if (DEBUG) {
System.err.println("gx=" + gx + " gxlimit=" + gxlimit +
" charInfo.len=" + charInfo.length +
" tgt=" + tgt + " cc=" + cc + " cp=" + cp);
}
while (cc < tgt) {
if (rtl) {
// if rtl, characters to left of base, else to right. reuse cposr.
cposr = cposl;
}
cvisr -= cvisl; // reuse, convert to deltas.
cvisb -= cvist;
// reset for new cluster
cp += pdelta;
cx += xdelta;
}
cp += pdelta;
if (mustCopy && !ltr) {
// data written to wrong end of array, need to shift down
if (cp < 0 || cp >= charInfo.length) {
if (DEBUG) {
System.err.println("Error : cp=" + cp +
" charInfo.length=" + charInfo.length);
}
break;
}
cp -= pdelta; // undo last increment, get start of valid character data in array
System.arraycopy(glyphinfo, cp, glyphinfo, 0, glyphinfo.length - cp);
if (DEBUG) {
System.err.println("Insert charIndex " + cc + " at pos="+cp);
}
charInfo[cp + posx] = cposr;
charInfo[cp + posy] = baseline;
charInfo[cp + advx] = 0;
charInfo[cp + advy] = 0;
charInfo[cp + visx] = cvisl;
charInfo[cp + visy] = cvist;
charInfo[cp + visw] = cvisr;
charInfo[cp + vish] = cvisb;
cc++;
}
cp += pdelta; // reset for new cluster
}
if (DEBUG) {
char[] chars = source.getChars();
int start = source.getStart();
int length = source.getLength();
System.out.println("char info for " + length + " characters");
for(int i = 0; i < length * numvals;) {
System.out.println(" ch: " + Integer.toHexString(chars[start + v2l(i / numvals)]) +
" x: " + glyphinfo[i++] +
" y: " + glyphinfo[i++] +
" xa: " + glyphinfo[i++] +
" ya: " + glyphinfo[i++] +
" l: " + glyphinfo[i++] +
" t: " + glyphinfo[i++] +
" w: " + glyphinfo[i++] +
" h: " + glyphinfo[i++]);
char[] chars = source.getChars();
int start = source.getStart();
int length = source.getLength();
System.err.println("char info for " + length + " characters");
for (int i = 0; i < length * numvals;) {
System.err.println(" ch: " + Integer.toHexString(chars[start + v2l(i / numvals)]) +
" x: " + charInfo[i++] +
" y: " + charInfo[i++] +
" xa: " + charInfo[i++] +
" ya: " + charInfo[i++] +
" l: " + charInfo[i++] +
" t: " + charInfo[i++] +
" w: " + charInfo[i++] +
" h: " + charInfo[i++]);
}
}
return glyphinfo;
return charInfo;
}
/**

View File

@ -40,6 +40,7 @@ static jfieldID gvdFlagsFID = 0;
static jfieldID gvdGlyphsFID = 0;
static jfieldID gvdPositionsFID = 0;
static jfieldID gvdIndicesFID = 0;
static jmethodID gvdGrowMID = 0;
static int jniInited = 0;
static void getFloat(JNIEnv* env, jobject pt, jfloat *x, jfloat *y) {
@ -63,73 +64,88 @@ static int init_JNI_IDs(JNIEnv *env) {
CHECK_NULL_RETURN(gvdGlyphsFID = (*env)->GetFieldID(env, gvdClass, "_glyphs", "[I"), 0);
CHECK_NULL_RETURN(gvdPositionsFID = (*env)->GetFieldID(env, gvdClass, "_positions", "[F"), 0);
CHECK_NULL_RETURN(gvdIndicesFID = (*env)->GetFieldID(env, gvdClass, "_indices", "[I"), 0);
CHECK_NULL_RETURN(gvdGrowMID = (*env)->GetMethodID(env, gvdClass, "grow", "()V"), 0);
jniInited = 1;
return jniInited;
}
// gmask is the composite font slot mask
// baseindex is to be added to the character (code point) index.
int storeGVData(JNIEnv* env,
jobject gvdata, jint slot, jint baseIndex, jobject startPt,
int glyphCount, hb_glyph_info_t *glyphInfo,
hb_glyph_position_t *glyphPos, hb_direction_t direction,
float devScale) {
jboolean storeGVData(JNIEnv* env,
jobject gvdata, jint slot,
jint baseIndex, int offset, jobject startPt,
int charCount, int glyphCount, hb_glyph_info_t *glyphInfo,
hb_glyph_position_t *glyphPos, float devScale) {
int i;
int i, needToGrow;
float x=0, y=0;
float startX, startY;
float startX, startY, advX, advY;
float scale = 1.0f / FloatToFixedScale / devScale;
unsigned int* glyphs;
float* positions;
int initialCount, glyphArrayLen, posArrayLen, maxGlyphs, storeadv;
int initialCount, glyphArrayLen, posArrayLen, maxGlyphs, storeadv, maxStore;
unsigned int* indices;
jarray glyphArray, posArray, inxArray;
if (!init_JNI_IDs(env)) {
return 0;
return JNI_FALSE;
}
initialCount = (*env)->GetIntField(env, gvdata, gvdCountFID);
glyphArray =
(jarray)(*env)->GetObjectField(env, gvdata, gvdGlyphsFID);
posArray =
(jarray)(*env)->GetObjectField(env, gvdata, gvdPositionsFID);
if (glyphArray == NULL || posArray == NULL)
{
JNU_ThrowArrayIndexOutOfBoundsException(env, "");
return 0;
}
// The Java code catches the IIOBE and expands the storage
// and re-invokes layout. I suppose this is expected to be rare
// because at least in a single threaded case there should be
// re-use of the same container, but it is a little wasteful/distateful.
glyphArrayLen = (*env)->GetArrayLength(env, glyphArray);
posArrayLen = (*env)->GetArrayLength(env, posArray);
maxGlyphs = glyphCount + initialCount;
if ((maxGlyphs > glyphArrayLen) ||
(maxGlyphs * 2 + 2 > posArrayLen))
{
JNU_ThrowArrayIndexOutOfBoundsException(env, "");
return 0;
}
do {
glyphArray = (jarray)(*env)->GetObjectField(env, gvdata, gvdGlyphsFID);
posArray = (jarray)(*env)->GetObjectField(env, gvdata, gvdPositionsFID);
inxArray = (jarray)(*env)->GetObjectField(env, gvdata, gvdIndicesFID);
if (glyphArray == NULL || posArray == NULL || inxArray == NULL) {
JNU_ThrowArrayIndexOutOfBoundsException(env, "");
return JNI_FALSE;
}
glyphArrayLen = (*env)->GetArrayLength(env, glyphArray);
posArrayLen = (*env)->GetArrayLength(env, posArray);
maxGlyphs = (charCount > glyphCount) ? charCount : glyphCount;
maxStore = maxGlyphs + initialCount;
needToGrow = (maxStore > glyphArrayLen) ||
(maxStore * 2 + 2 > posArrayLen);
if (needToGrow) {
(*env)->CallVoidMethod(env, gvdata, gvdGrowMID);
if ((*env)->ExceptionCheck(env)) {
return JNI_FALSE;
}
}
} while (needToGrow);
getFloat(env, startPt, &startX, &startY);
glyphs =
(unsigned int*)(*env)->GetPrimitiveArrayCritical(env, glyphArray, NULL);
if (glyphs == NULL) {
return JNI_FALSE;
}
positions = (jfloat*)(*env)->GetPrimitiveArrayCritical(env, posArray, NULL);
if (positions == NULL) {
(*env)->ReleasePrimitiveArrayCritical(env, glyphArray, glyphs, 0);
return JNI_FALSE;
}
indices =
(unsigned int*)(*env)->GetPrimitiveArrayCritical(env, inxArray, NULL);
if (indices == NULL) {
(*env)->ReleasePrimitiveArrayCritical(env, glyphArray, glyphs, 0);
(*env)->ReleasePrimitiveArrayCritical(env, posArray, positions, 0);
return JNI_FALSE;
}
for (i = 0; i < glyphCount; i++) {
int storei = i + initialCount;
int index = glyphInfo[i].codepoint | slot;
if (i<glyphCount)glyphs[storei] = (unsigned int)index;
positions[(storei*2)] = startX + x + glyphPos[i].x_offset * scale;
positions[(storei*2)+1] = startY + y - glyphPos[i].y_offset * scale;
int cluster = glyphInfo[i].cluster - offset;
indices[storei] = baseIndex + cluster;
glyphs[storei] = (unsigned int)(glyphInfo[i].codepoint | slot);
positions[storei*2] = startX + x + glyphPos[i].x_offset * scale;
positions[(storei*2)+1] = startY + y + glyphPos[i].y_offset * scale;
x += glyphPos[i].x_advance * scale;
y += glyphPos[i].y_advance * scale;
storei++;
}
storeadv = initialCount+glyphCount;
storeadv = initialCount + glyphCount;
// The final slot in the positions array is important
// because when the GlyphVector is created from this
// data it determines the overall advance of the glyphvector
@ -137,30 +153,17 @@ int storeGVData(JNIEnv* env,
// during rendering where text is broken into runs.
// We also need to report it back into "pt", so layout can
// pass it back down for that next run in this code.
positions[(storeadv*2)] = startX + x;
positions[(storeadv*2)+1] = startY + y;
advX = startX + x;
advY = startY + y;
positions[(storeadv*2)] = advX;
positions[(storeadv*2)+1] = advY;
(*env)->ReleasePrimitiveArrayCritical(env, glyphArray, glyphs, 0);
(*env)->ReleasePrimitiveArrayCritical(env, posArray, positions, 0);
putFloat(env, startPt,positions[(storeadv*2)],positions[(storeadv*2)+1] );
inxArray =
(jarray)(*env)->GetObjectField(env, gvdata, gvdIndicesFID);
indices =
(unsigned int*)(*env)->GetPrimitiveArrayCritical(env, inxArray, NULL);
for (i = 0; i < glyphCount; i++) {
int cluster = glyphInfo[i].cluster;
if (direction == HB_DIRECTION_LTR) {
// I need to understand what hb does when processing a substring
// I expected the cluster index to be from the start of the text
// to process.
// Instead it appears to be from the start of the whole thing.
indices[i+initialCount] = cluster;
} else {
indices[i+initialCount] = baseIndex + glyphCount -1 -i;
}
}
(*env)->ReleasePrimitiveArrayCritical(env, inxArray, indices, 0);
(*env)->SetIntField(env, gvdata, gvdCountFID, initialCount+glyphCount);
return initialCount+glyphCount;
putFloat(env, startPt, advX, advY);
(*env)->SetIntField(env, gvdata, gvdCountFID, storeadv);
return JNI_TRUE;
}
static float euclidianDistance(float a, float b)
@ -226,7 +229,9 @@ JDKFontInfo*
}
#define TYPO_RTL 0x80000000
#define TYPO_KERN 0x00000001
#define TYPO_LIGA 0x00000002
#define TYPO_RTL 0x80000000
JNIEXPORT jboolean JNICALL Java_sun_font_SunLayoutEngine_shape
(JNIEnv *env, jclass cls,
@ -255,10 +260,11 @@ JNIEXPORT jboolean JNICALL Java_sun_font_SunLayoutEngine_shape
hb_glyph_info_t *glyphInfo;
hb_glyph_position_t *glyphPos;
hb_direction_t direction = HB_DIRECTION_LTR;
hb_feature_t *features = NULL;
hb_feature_t *features = NULL;
int featureCount = 0;
int i;
char* kern = (flags & TYPO_KERN) ? "kern" : "-kern";
char* liga = (flags & TYPO_LIGA) ? "liga" : "-liga";
jboolean ret;
unsigned int buflen;
JDKFontInfo *jdkFontInfo =
@ -281,6 +287,8 @@ JNIEXPORT jboolean JNICALL Java_sun_font_SunLayoutEngine_shape
direction = HB_DIRECTION_RTL;
}
hb_buffer_set_direction(buffer, direction);
hb_buffer_set_cluster_level(buffer,
HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS);
chars = (*env)->GetCharArrayElements(env, text, NULL);
if ((*env)->ExceptionCheck(env)) {
@ -293,36 +301,26 @@ JNIEXPORT jboolean JNICALL Java_sun_font_SunLayoutEngine_shape
hb_buffer_add_utf16(buffer, chars, len, offset, limit-offset);
features = calloc(2, sizeof(hb_feature_t));
if (features) {
hb_feature_from_string(kern, -1, &features[featureCount++]);
hb_feature_from_string(liga, -1, &features[featureCount++]);
}
hb_shape_full(hbfont, buffer, features, featureCount, 0);
glyphCount = hb_buffer_get_length(buffer);
glyphInfo = hb_buffer_get_glyph_infos(buffer, 0);
glyphPos = hb_buffer_get_glyph_positions(buffer, &buflen);
for (i = 0; i < glyphCount; i++) {
int index = glyphInfo[i].codepoint;
int xadv = (glyphPos[i].x_advance);
int yadv = (glyphPos[i].y_advance);
}
// On "input" HB assigns a cluster index to each character in UTF-16.
// On output where a sequence of characters have been mapped to
// a glyph they are all mapped to the cluster index of the first character.
// The next cluster index will be that of the first character in the
// next cluster. So cluster indexes may 'skip' on output.
// This can also happen if there are supplementary code-points
// such that two UTF-16 characters are needed to make one codepoint.
// In RTL text you need to count down.
// So the following code tries to build the reverse map as expected
// by calling code.
storeGVData(env, gvdata, slot, baseIndex, startPt,
glyphCount, glyphInfo, glyphPos, direction,
jdkFontInfo->devScale);
ret = storeGVData(env, gvdata, slot, baseIndex, offset, startPt,
limit - offset, glyphCount, glyphInfo, glyphPos,
jdkFontInfo->devScale);
hb_buffer_destroy (buffer);
hb_font_destroy(hbfont);
free((void*)jdkFontInfo);
if (features != NULL) free(features);
(*env)->ReleaseCharArrayElements(env, text, chars, JNI_ABORT);
return JNI_TRUE;
return ret;
}

View File

@ -0,0 +1,44 @@
/*
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/* @test
* @summary Test getGlyphCharIndex() results from layout
* @bug 8152680
*
import java.awt.Font;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
public class GetGlyphCharIndexTest {
public static void main(String[] args) {
Font font = new Font(Font.MONOSPACED, Font.PLAIN, 12);
FontRenderContext frc = new FontRenderContext(null, false, false);
GlyphVector gv = font.layoutGlyphVector(frc, "abc".toCharArray(), 1, 3,
Font.LAYOUT_LEFT_TO_RIGHT);
int idx0 = gv.getGlyphCharIndex(0);
if (idx0 != 0) {
throw new RuntimeException("Expected 0, got " + idx0);
}
}
}

View File

@ -0,0 +1,143 @@
/*
* Copyright (c) 1999, 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @bug 4175418 8158924
* @author John Raley
* @summary This insures that bug 4175418: Font substitution in TextLayout /
* LineBreakMeasurer is inconsistent has been fixed. The problem was
* that text was measured in one Font, but lines were produced
* in a different font.
*/
/*
* (C) Copyright IBM Corp. 1999, All Rights Reserved
*/
import java.text.AttributedString;
import java.awt.font.LineBreakMeasurer;
import java.awt.font.TextLayout;
import java.awt.font.FontRenderContext;
import java.awt.font.TextAttribute;
/**
* This insures that bug 4175418: Font substitution in TextLayout /
* LineBreakMeasurer is inconsistent has been fixed. The problem was
* that text was measured in one Font, but lines were produced
* in a different font. One symptom of this problem is that lines are
* either too short or too long. This test line-breaks a paragraph
* and checks the line lengths to make sure breaks were chosen well.
* This can be checked because the paragraph is so simple.
*/
public class TestLineBreakWithFontSub {
public static void main(String[] args) {
new TestLineBreakWithFontSub().test();
System.out.println("Line break / font substitution test PASSED");
}
private static final String WORD = "word";
private static final String SPACING = " ";
// The Hebrew character in this string can trigger font substitution
private static final String MIXED = "A\u05D0";
private static final int NUM_WORDS = 12;
private static final FontRenderContext DEFAULT_FRC =
new FontRenderContext(null, false, false);
public void test() {
// construct a paragraph as follows: MIXED + [SPACING + WORD] + ...
StringBuffer text = new StringBuffer(MIXED);
for (int i=0; i < NUM_WORDS; i++) {
text.append(SPACING);
text.append(WORD);
}
AttributedString attrString = new AttributedString(text.toString());
attrString.addAttribute(TextAttribute.SIZE, new Float(24.0));
LineBreakMeasurer measurer = new LineBreakMeasurer(attrString.getIterator(),
DEFAULT_FRC);
// get width of a space-word sequence, in context
int sequenceLength = WORD.length()+SPACING.length();
measurer.setPosition(text.length() - sequenceLength);
TextLayout layout = measurer.nextLayout(10000.0f);
if (layout.getCharacterCount() != sequenceLength) {
throw new Error("layout length is incorrect!");
}
final float sequenceAdvance = layout.getVisibleAdvance();
float wrappingWidth = sequenceAdvance * 2;
// now run test with a variety of widths
while (wrappingWidth < (sequenceAdvance*NUM_WORDS)) {
measurer.setPosition(0);
checkMeasurer(measurer,
wrappingWidth,
sequenceAdvance,
text.length());
wrappingWidth += sequenceAdvance / 5;
}
}
/**
* Iterate through measurer and check that every line is
* not too long and not too short, but just right.
*/
private void checkMeasurer(LineBreakMeasurer measurer,
float wrappingWidth,
float sequenceAdvance,
int endPosition) {
do {
TextLayout layout = measurer.nextLayout(wrappingWidth);
float visAdvance = layout.getVisibleAdvance();
// Check that wrappingWidth-sequenceAdvance < visAdvance
// Also, if we're not at the end of the paragraph,
// check that visAdvance <= wrappingWidth
if (visAdvance > wrappingWidth) {
// line is too long for given wrapping width
throw new Error("layout is too long");
}
if (measurer.getPosition() < endPosition) {
if (visAdvance <= wrappingWidth - sequenceAdvance) {
// line is too short for given wrapping width;
// another word would have fit
throw new Error("room for more words on line. diff=" +
(wrappingWidth - sequenceAdvance - visAdvance));
}
}
} while (measurer.getPosition() != endPosition);
}
}

View File

@ -0,0 +1,167 @@
/*
* Copyright (c) 1998, 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* @test
* @bug 4178145 8144015
*/
/*
* Copyright 1998 IBM Corp. All Rights Reserved.
*/
import java.awt.Color;
import java.awt.Font;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.font.TextAttribute;
import java.awt.font.TextLayout;
import java.awt.font.TextHitInfo;
import java.awt.font.FontRenderContext;
import java.util.Hashtable;
/**
* This test ensures that TextLayout will not place a caret within
* an Arabic lam-alef ligature, and will correctly caret through
* bidirectional text with numbers.
*/
public class LigatureCaretTest {
public static void main(String[] args) {
//testBidiWithNumbers();
testLamAlef();
System.out.println("LigatureCaretTest PASSED");
}
// These values are for TextLayout constructors
private static final Hashtable map = new Hashtable();
static {
map.put(TextAttribute.FONT, new Font("Lucida Sans", Font.PLAIN, 24));
}
private static final FontRenderContext frc =
new FontRenderContext(null, false, false);
/**
* Caret through text mixed-direction text and check the results.
* If the test fails an Error is thrown.
* @exception an Error is thrown if the test fails
*/
public static void testBidiWithNumbers() {
String bidiWithNumbers = "abc\u05D0\u05D1\u05D2123abc";
// visual order for the text:
// abc123<gimel><bet><aleph>abc
int[] carets = { 0, 1, 2, 3, 7, 8, 6, 5, 4, 9, 10, 11, 12 };
TextLayout layout = new TextLayout(bidiWithNumbers, map, frc);
// Caret through TextLayout in both directions and check results.
for (int i=0; i < carets.length-1; i++) {
TextHitInfo hit = layout.getNextRightHit(carets[i]);
if (hit.getInsertionIndex() != carets[i+1]) {
throw new Error("right hit failed within layout");
}
}
if (layout.getNextRightHit(carets[carets.length-1]) != null) {
throw new Error("right hit failed at end of layout");
}
for (int i=carets.length-1; i > 0; i--) {
TextHitInfo hit = layout.getNextLeftHit(carets[i]);
if (hit.getInsertionIndex() != carets[i-1]) {
throw new Error("left hit failed within layout");
}
}
if (layout.getNextLeftHit(carets[0]) != null) {
throw new Error("left hit failed at end of layout");
}
}
/**
* Ensure proper careting and hit-testing behavior with
* a lam-alef ligature.
* If the test fails, an Error is thrown.
* @exception an Error is thrown if the test fails
*/
public static void testLamAlef() {
// lam-alef form a mandantory ligature.
final String lamAlef = "\u0644\u0627";
final String ltrText = "abcd";
// Create a TextLayout with just a lam-alef sequence. There
// should only be two valid caret positions: one at
// insertion offset 0 and the other at insertion offset 2.
TextLayout layout = new TextLayout(lamAlef, map, frc);
TextHitInfo hit;
hit = layout.getNextLeftHit(0);
if (hit.getInsertionIndex() != 2) {
throw new Error("Left hit failed. Hit:" + hit);
}
hit = layout.getNextRightHit(2);
if (hit.getInsertionIndex() != 0) {
throw new Error("Right hit failed. Hit:" + hit);
}
hit = layout.hitTestChar(layout.getAdvance()/2, 0);
if (hit.getInsertionIndex() != 0 && hit.getInsertionIndex() != 2) {
throw new Error("Hit-test allowed incorrect caret. Hit:" + hit);
}
// Create a TextLayout with some left-to-right text
// before the lam-alef sequence. There should not be
// a caret position between the lam and alef.
layout = new TextLayout(ltrText+lamAlef, map, frc);
final int ltrLen = ltrText.length();
final int layoutLen = layout.getCharacterCount();
for (int i=0; i < ltrLen; i++) {
hit = layout.getNextRightHit(i);
if (hit.getInsertionIndex() != i+1) {
throw new Error("Right hit failed in ltr text.");
}
}
hit = layout.getNextRightHit(ltrLen);
if (layoutLen != hit.getInsertionIndex()) {
throw new Error("Right hit failed at direction boundary.");
}
hit = layout.getNextLeftHit(layoutLen);
if (hit.getInsertionIndex() != ltrLen) {
throw new Error("Left hit failed at end of text.");
}
}
}

View File

@ -0,0 +1,52 @@
<!--
Copyright (c) 1999, 2016, Oracle and/or its affiliates. All rights reserved.
DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
This code is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License version 2 only, as
published by the Free Software Foundation.
This code is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
version 2 for more details (a copy is included in the LICENSE file that
accompanied this code).
You should have received a copy of the GNU General Public License version
2 along with this work; if not, write to the Free Software Foundation,
Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
or visit www.oracle.com if you need additional information or have any
questions.
-->
<html>
<head>
<title>Test Justification</title>
</head>
<body>
<!--
@test
@bug 4211728 4178140 8145542
@summary Justify several lines of text and verify that the lines are the same
length and cursor positions are correct.
Bug 4211728: TextLayout.draw() draws characters at wrong position.
Bug 4178140: TextLayout does not justify
@run applet/manual=yesno TestJustification.html
-->
<h3>Test Justification</h1>
<hr>
<p>Five lines of text should appear, all justified to the same width,
followed by a sixth line containing only roman characters and no spaces
which is not justified, and instead is centered.
Carets should appear between all characters. Pass the test if this is
true.
<p>
<applet code=TestJustification.class width=500 height=500>
alt="Your browser understands the &lt;APPLET&gt; tag but isn't running the applet, for some reason."
Your browser is completely ignoring the &lt;APPLET&gt; tag!
</applet>
</body>
</html>

View File

@ -0,0 +1,249 @@
/*
* Copyright (c) 1999, 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
*
* See TestJustification.html for main test.
*/
import java.applet.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.font.*;
import java.awt.geom.*;
import java.text.*;
public class TestJustification extends Applet {
JustificationPanel panel;
public void init() {
setLayout(new BorderLayout());
panel = new JustificationPanel("Bitstream Cyberbit");
add("Center", panel);
}
public void destroy() {
remove(panel);
}
// calls system.exit, not for use in tests.
public static void main(String args[]) {
TestJustification justificationTest = new TestJustification();
justificationTest.init();
justificationTest.start();
Frame f = new Frame("Test Justification");
f.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
f.add("Center", justificationTest);
f.setSize(500, 500);
f.show();
}
public String getAppletInfo() {
return "Test TextLayout.getJustifiedLayout()";
}
static class JustificationPanel extends Panel {
TextLayout[] layouts;
String fontname;
float height;
float oldfsize;
AttributedCharacterIterator lineText;
TextLayout[] lines;
int linecount;
float oldwidth;
JustificationPanel(String fontname) {
this.fontname = fontname;
}
private static final String[] texts = {
"This is an english Highlighting demo.", "Highlighting",
"This is an arabic \u0627\u0628\u062a\u062c \u062e\u0644\u0627\u062e demo.", "arabic \u0627\u0628\u062a\u062c",
"This is a hebrew \u05d0\u05d1\u05d2 \u05d3\u05d4\u05d5 demo.", "hebrew \u05d0\u05d1\u05d2",
"This is a cjk \u4e00\u4e01\u4e02\uac00\uac01\uc4fa\uf900\uf901\uf902 demo.", "cjk",
"NoSpaceCJK:\u4e00\u4e01\u4e02and\uac00\uac01\uc4faand\uf900\uf901\uf902", "No",
"NoSpaceRoman", "Space"
};
public void paint(Graphics g) {
Graphics2D g2d = (Graphics2D)g;
Dimension d = getSize();
Insets insets = getInsets();
float w = d.width - insets.left - insets.right;
float h = d.height - insets.top - insets.bottom;
int fsize = (int)w/25;
FontRenderContext frc = g2d.getFontRenderContext();
if (layouts == null || fsize != oldfsize) {
oldfsize = fsize;
Font f0 = new Font(fontname, Font.PLAIN, fsize);
Font f1 = new Font(fontname, Font.ITALIC, (int)(fsize * 1.5));
if (layouts == null) {
layouts = new TextLayout[texts.length / 2];
}
height = 0;
for (int i = 0; i < layouts.length; ++i) {
String text = texts[i*2];
String target = texts[i*2+1];
AttributedString astr = new AttributedString(text);
astr.addAttribute(TextAttribute.FONT, f0, 0, text.length());
int start = text.indexOf(target);
int limit = start + target.length();
astr.addAttribute(TextAttribute.FONT, f1, start, limit);
TextLayout layout = new TextLayout(astr.getIterator(), frc);
layout = layout.getJustifiedLayout(w - 20);
layouts[i] = layout;
height += layout.getAscent() + layout.getDescent() + layout.getLeading();
}
}
g2d.setColor(Color.white);
g2d.fill(new Rectangle.Float(insets.left, insets.top, w, h));
float basey = 20;
for (int i = 0; i < layouts.length; ++i) {
TextLayout layout = layouts[i];
float la = layout.getAscent();
float ld = layout.getDescent();
float ll = layout.getLeading();
float lw = layout.getAdvance();
float lh = la + ld + ll;
float lx = (w - lw) / 2f;
float ly = basey + layout.getAscent();
g2d.setColor(Color.black);
g2d.translate(insets.left + lx, insets.top + ly);
Rectangle2D bounds = new Rectangle2D.Float(0, -la, lw, lh);
g2d.draw(bounds);
layout.draw(g2d, 0, 0);
g2d.setColor(Color.red);
for (int j = 0, e = layout.getCharacterCount(); j <= e; ++j) {
Shape[] carets = layout.getCaretShapes(j, bounds);
g2d.draw(carets[0]);
}
g2d.translate(-insets.left - lx, -insets.top - ly);
basey += layout.getAscent() + layout.getDescent() + layout.getLeading();
}
// add LineBreakMeasurer-generated layouts
if (lineText == null) {
String text = "This is a long line of text that should be broken across multiple "
+ "lines and then justified to fit the break width. This test should pass if "
+ "these lines are justified to the same width, and fail otherwise. It should "
+ "also format the hebrew (\u05d0\u05d1\u05d2 \u05d3\u05d4\u05d5) and arabic "
+ "(\u0627\u0628\u062a\u062c \u062e\u0644\u0627\u062e) and CJK "
+ "(\u4e00\u4e01\u4e02\uac00\uac01\uc4fa\u67b1\u67b2\u67b3\u67b4\u67b5\u67b6\u67b7"
+ "\u67b8\u67b9) text correctly.";
Float regular = new Float(16.0);
Float big = new Float(24.0);
AttributedString astr = new AttributedString(text);
astr.addAttribute(TextAttribute.SIZE, regular, 0, text.length());
astr.addAttribute(TextAttribute.FAMILY, fontname, 0, text.length());
int ix = text.indexOf("broken");
astr.addAttribute(TextAttribute.SIZE, big, ix, ix + 6);
ix = text.indexOf("hebrew");
astr.addAttribute(TextAttribute.SIZE, big, ix, ix + 6);
ix = text.indexOf("arabic");
astr.addAttribute(TextAttribute.SIZE, big, ix, ix + 6);
ix = text.indexOf("CJK");
astr.addAttribute(TextAttribute.SIZE, big, ix, ix + 3);
lineText = astr.getIterator();
}
float width = w - 20;
if (lines == null || width != oldwidth) {
oldwidth = width;
lines = new TextLayout[10];
linecount = 0;
LineBreakMeasurer measurer = new LineBreakMeasurer(lineText, frc);
for (;;) {
TextLayout layout = measurer.nextLayout(width);
if (layout == null) {
break;
}
// justify all but last line
if (linecount > 0) {
lines[linecount - 1] = lines[linecount - 1].getJustifiedLayout(width);
}
if (linecount == lines.length) {
TextLayout[] nlines = new TextLayout[lines.length * 2];
System.arraycopy(lines, 0, nlines, 0, lines.length);
lines = nlines;
}
lines[linecount++] = layout;
}
}
float basex = insets.left + 10;
basey += 10;
g2d.setColor(Color.black);
for (int i = 0; i < linecount; ++i) {
TextLayout layout = lines[i];
basey += layout.getAscent();
float adv = layout.getAdvance();
float dx = layout.isLeftToRight() ? 0 : width - adv;
layout.draw(g2d, basex + dx, basey);
basey += layout.getDescent() + layout.getLeading();
}
}
}
}

View File

@ -0,0 +1,36 @@
/*
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @bug 8151725
* @summary Tests no exception creating a JEditorPane with Devanagari
*/
import javax.swing.JEditorPane;
public class DevanagariEditor {
public static void main(String[] args) {
new JEditorPane().setText("\u0930\u093E\u0915\u094D\u0937\u0938");
}
}

View File

@ -0,0 +1,153 @@
/*
* Copyright (c) 2011, 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*
*/
/* @test
@bug 6427244 8144240
@summary Test that pressing HOME correctly moves caret in I18N document.
@author Sergey Groznyh
@library ../../../regtesthelpers
@build JRobot Util TestCase
@run main bug6427244
*/
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Shape;
import java.awt.event.KeyEvent;
import javax.swing.JFrame;
import javax.swing.JTextPane;
import javax.swing.SwingUtilities;
import javax.swing.text.Position;
public class bug6427244 extends TestCase {
private static final JRobot ROBOT = JRobot.getRobot();
final static int TP_SIZE = 200;
final static String[] SPACES = new String[] {
"\u0020", // ASCII space
"\u2002", // EN space
"\u2003", // EM space
"\u2004", // THREE-PER-EM space
"\u2005", // ... etc.
"\u2006",
//"\u2007",
"\u2008",
"\u2009",
"\u200a",
"\u200b",
"\u205f",
"\u3000",
};
final static String[] WORDS = new String[] {
"It", "is", "a", "long", "paragraph", "for", "testing", "GlyphPainter2\n\n",
};
public static void main(String[] args) {
bug6427244 t = new bug6427244();
for (String space: SPACES) {
t.init(space);
t.runAllTests();
}
System.out.println("OK");
}
void init(final String space) {
try {
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
String text = null;
for (String word: WORDS) {
if (text == null) {
text = "";
} else {
text += space;
}
text += word;
}
tp = new JTextPane();
tp.setText(text +
"Some arabic: \u062a\u0641\u0627\u062d and some not.");
if (jf == null) {
jf = new JFrame();
jf.setTitle("bug6427244");
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.setSize(TP_SIZE, TP_SIZE);
jf.setVisible(true);
}
Container c = jf.getContentPane();
c.removeAll();
c.add(tp);
c.invalidate();
c.validate();
dim = c.getSize();
}
});
Util.blockTillDisplayed(tp);
ROBOT.waitForIdle();
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
public void testCaretPosition() {
Point p = tp.getLocationOnScreen();
// the right-top corner position
p.x += (dim.width - 5);
p.y += 5;
ROBOT.mouseMove(p.x, p.y);
ROBOT.clickMouse();
ROBOT.hitKey(KeyEvent.VK_HOME);
ROBOT.waitForIdle();
// this will fail if caret moves out of the 1st line.
assertEquals(0, getCaretOrdinate());
}
int getCaretOrdinate() {
final int[] y = new int[1];
try {
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
Shape s;
try {
s = tp.getUI().getRootView(tp).modelToView(
tp.getCaretPosition(), tp.getBounds(),
Position.Bias.Forward);
} catch (Exception e) {
throw new RuntimeException(e);
}
y[0] = s.getBounds().y;
}
});
} catch (Exception e) {
throw new RuntimeException(e);
}
return y[0];
}
JFrame jf;
JTextPane tp;
Dimension dim;
}