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:
parent
63217bef37
commit
6f12a4ff0d
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
167
jdk/test/java/awt/font/TextLayout/LigatureCaretTest.java
Normal file
167
jdk/test/java/awt/font/TextLayout/LigatureCaretTest.java
Normal 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.");
|
||||
}
|
||||
}
|
||||
}
|
52
jdk/test/java/awt/font/TextLayout/TestJustification.html
Normal file
52
jdk/test/java/awt/font/TextLayout/TestJustification.html
Normal 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 <APPLET> tag but isn't running the applet, for some reason."
|
||||
Your browser is completely ignoring the <APPLET> tag!
|
||||
</applet>
|
||||
</body>
|
||||
</html>
|
||||
|
249
jdk/test/java/awt/font/TextLayout/TestJustification.java
Normal file
249
jdk/test/java/awt/font/TextLayout/TestJustification.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
36
jdk/test/javax/swing/text/DevanagariEditor.java
Normal file
36
jdk/test/javax/swing/text/DevanagariEditor.java
Normal 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");
|
||||
}
|
||||
}
|
153
jdk/test/javax/swing/text/GlyphPainter2/6427244/bug6427244.java
Normal file
153
jdk/test/javax/swing/text/GlyphPainter2/6427244/bug6427244.java
Normal 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;
|
||||
}
|
Loading…
Reference in New Issue
Block a user