From 6f12a4ff0df3224f3a472e38c1f8e55809064b6a Mon Sep 17 00:00:00 2001 From: Phil Race Date: Thu, 1 Sep 2016 11:29:20 -0700 Subject: [PATCH] 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 --- .../sun/font/ExtendedTextSourceLabel.java | 231 +++++++--------- .../share/native/libfontmanager/HBShaper.c | 164 ++++++------ .../GlyphVector/GetGlyphCharIndexTest.java | 44 ++++ .../TestLineBreakWithFontSub.java | 143 ++++++++++ .../font/TextLayout/LigatureCaretTest.java | 167 ++++++++++++ .../font/TextLayout/TestJustification.html | 52 ++++ .../font/TextLayout/TestJustification.java | 249 ++++++++++++++++++ .../javax/swing/text/DevanagariEditor.java | 36 +++ .../GlyphPainter2/6427244/bug6427244.java | 153 +++++++++++ 9 files changed, 1024 insertions(+), 215 deletions(-) create mode 100644 jdk/test/java/awt/font/GlyphVector/GetGlyphCharIndexTest.java create mode 100644 jdk/test/java/awt/font/LineBreakMeasurer/TestLineBreakWithFontSub.java create mode 100644 jdk/test/java/awt/font/TextLayout/LigatureCaretTest.java create mode 100644 jdk/test/java/awt/font/TextLayout/TestJustification.html create mode 100644 jdk/test/java/awt/font/TextLayout/TestJustification.java create mode 100644 jdk/test/javax/swing/text/DevanagariEditor.java create mode 100644 jdk/test/javax/swing/text/GlyphPainter2/6427244/bug6427244.java diff --git a/jdk/src/java.desktop/share/classes/sun/font/ExtendedTextSourceLabel.java b/jdk/src/java.desktop/share/classes/sun/font/ExtendedTextSourceLabel.java index 54c5382129c..67aee9e18db 100644 --- a/jdk/src/java.desktop/share/classes/sun/font/ExtendedTextSourceLabel.java +++ b/jdk/src/java.desktop/share/classes/sun/font/ExtendedTextSourceLabel.java @@ -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; } /** diff --git a/jdk/src/java.desktop/share/native/libfontmanager/HBShaper.c b/jdk/src/java.desktop/share/native/libfontmanager/HBShaper.c index 3b35e27efda..3b7a463ffdf 100644 --- a/jdk/src/java.desktop/share/native/libfontmanager/HBShaper.c +++ b/jdk/src/java.desktop/share/native/libfontmanager/HBShaper.c @@ -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 (iReleasePrimitiveArrayCritical(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; } diff --git a/jdk/test/java/awt/font/GlyphVector/GetGlyphCharIndexTest.java b/jdk/test/java/awt/font/GlyphVector/GetGlyphCharIndexTest.java new file mode 100644 index 00000000000..1754e5f64cc --- /dev/null +++ b/jdk/test/java/awt/font/GlyphVector/GetGlyphCharIndexTest.java @@ -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); + } + } +} diff --git a/jdk/test/java/awt/font/LineBreakMeasurer/TestLineBreakWithFontSub.java b/jdk/test/java/awt/font/LineBreakMeasurer/TestLineBreakWithFontSub.java new file mode 100644 index 00000000000..472fca810a5 --- /dev/null +++ b/jdk/test/java/awt/font/LineBreakMeasurer/TestLineBreakWithFontSub.java @@ -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); + } +} diff --git a/jdk/test/java/awt/font/TextLayout/LigatureCaretTest.java b/jdk/test/java/awt/font/TextLayout/LigatureCaretTest.java new file mode 100644 index 00000000000..e59bccac996 --- /dev/null +++ b/jdk/test/java/awt/font/TextLayout/LigatureCaretTest.java @@ -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: + // abc123abc + + 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."); + } + } +} diff --git a/jdk/test/java/awt/font/TextLayout/TestJustification.html b/jdk/test/java/awt/font/TextLayout/TestJustification.html new file mode 100644 index 00000000000..c9e79f44cb2 --- /dev/null +++ b/jdk/test/java/awt/font/TextLayout/TestJustification.html @@ -0,0 +1,52 @@ + + + + +Test Justification + + + +

Test Justification

+
+

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. +

+ +alt="Your browser understands the <APPLET> tag but isn't running the applet, for some reason." +Your browser is completely ignoring the <APPLET> tag! + + + + diff --git a/jdk/test/java/awt/font/TextLayout/TestJustification.java b/jdk/test/java/awt/font/TextLayout/TestJustification.java new file mode 100644 index 00000000000..417ddd5adfc --- /dev/null +++ b/jdk/test/java/awt/font/TextLayout/TestJustification.java @@ -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(); + } + } + } +} diff --git a/jdk/test/javax/swing/text/DevanagariEditor.java b/jdk/test/javax/swing/text/DevanagariEditor.java new file mode 100644 index 00000000000..3781a1beae7 --- /dev/null +++ b/jdk/test/javax/swing/text/DevanagariEditor.java @@ -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"); + } +} diff --git a/jdk/test/javax/swing/text/GlyphPainter2/6427244/bug6427244.java b/jdk/test/javax/swing/text/GlyphPainter2/6427244/bug6427244.java new file mode 100644 index 00000000000..cc32aeb7690 --- /dev/null +++ b/jdk/test/javax/swing/text/GlyphPainter2/6427244/bug6427244.java @@ -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; +}