1 /* 2 * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 #include <jni_util.h> 27 #include <stdlib.h> 28 #include "hb.h" 29 #include "hb-jdk.h" 30 #include "hb-ot.h" 31 #ifdef MACOSX 32 #include "hb-coretext.h" 33 #endif 34 #include "scriptMapping.h" 35 36 static jclass gvdClass = 0; 37 static const char* gvdClassName = "sun/font/GlyphLayout$GVData"; 38 static jfieldID gvdCountFID = 0; 39 static jfieldID gvdFlagsFID = 0; 40 static jfieldID gvdGlyphsFID = 0; 41 static jfieldID gvdPositionsFID = 0; 42 static jfieldID gvdIndicesFID = 0; 43 static int jniInited = 0; 44 45 static void getFloat(JNIEnv* env, jobject pt, jfloat *x, jfloat *y) { 46 *x = (*env)->GetFloatField(env, pt, sunFontIDs.xFID); 47 *y = (*env)->GetFloatField(env, pt, sunFontIDs.yFID); 48 } 49 50 static void putFloat(JNIEnv* env, jobject pt, jfloat x, jfloat y) { 51 (*env)->SetFloatField(env, pt, sunFontIDs.xFID, x); 52 (*env)->SetFloatField(env, pt, sunFontIDs.yFID, y); 53 } 54 55 static int init_JNI_IDs(JNIEnv *env) { 56 if (jniInited) { 57 return jniInited; 58 } 59 CHECK_NULL_RETURN(gvdClass = (*env)->FindClass(env, gvdClassName), 0); 60 CHECK_NULL_RETURN(gvdClass = (jclass)(*env)->NewGlobalRef(env, gvdClass), 0); 61 CHECK_NULL_RETURN(gvdCountFID = (*env)->GetFieldID(env, gvdClass, "_count", "I"), 0); 62 CHECK_NULL_RETURN(gvdFlagsFID = (*env)->GetFieldID(env, gvdClass, "_flags", "I"), 0); 63 CHECK_NULL_RETURN(gvdGlyphsFID = (*env)->GetFieldID(env, gvdClass, "_glyphs", "[I"), 0); 64 CHECK_NULL_RETURN(gvdPositionsFID = (*env)->GetFieldID(env, gvdClass, "_positions", "[F"), 0); 65 CHECK_NULL_RETURN(gvdIndicesFID = (*env)->GetFieldID(env, gvdClass, "_indices", "[I"), 0); 66 jniInited = 1; 67 return jniInited; 68 } 69 70 // gmask is the composite font slot mask 71 // baseindex is to be added to the character (code point) index. 72 int storeGVData(JNIEnv* env, 73 jobject gvdata, jint slot, jint baseIndex, jobject startPt, 74 int glyphCount, hb_glyph_info_t *glyphInfo, 75 hb_glyph_position_t *glyphPos, hb_direction_t direction) { 76 77 int i; 78 float x=0, y=0; 79 float startX, startY; 80 float scale = 1.0f/64.0f; 81 unsigned int* glyphs; 82 float* positions; 83 84 if (!init_JNI_IDs(env)) { 85 return 0; 86 } 87 88 int initialCount = (*env)->GetIntField(env, gvdata, gvdCountFID); 89 jarray glyphArray = 90 (jarray)(*env)->GetObjectField(env, gvdata, gvdGlyphsFID); 91 jarray posArray = 92 (jarray)(*env)->GetObjectField(env, gvdata, gvdPositionsFID); 93 94 if (glyphArray == NULL || posArray == NULL) 95 { 96 JNU_ThrowArrayIndexOutOfBoundsException(env, ""); 97 return 0; 98 } 99 100 // The Java code catches the IIOBE and expands the storage 101 // and re-invokes layout. I suppose this is expected to be rare 102 // because at least in a single threaded case there should be 103 // re-use of the same container, but it is a little wasteful/distateful. 104 int glyphArrayLen = (*env)->GetArrayLength(env, glyphArray); 105 int posArrayLen = (*env)->GetArrayLength(env, posArray); 106 int maxGlyphs = glyphCount + initialCount; 107 if ((maxGlyphs > glyphArrayLen) || 108 (maxGlyphs * 2 + 2 > posArrayLen)) 109 { 110 JNU_ThrowArrayIndexOutOfBoundsException(env, ""); 111 return 0; 112 } 113 114 getFloat(env, startPt, &startX, &startY); 115 116 glyphs = 117 (unsigned int*)(*env)->GetPrimitiveArrayCritical(env, glyphArray, NULL); 118 positions = (jfloat*)(*env)->GetPrimitiveArrayCritical(env, posArray, NULL); 119 for (i = 0; i < glyphCount; i++) { 120 int storei = i + initialCount; 121 int index = glyphInfo[i].codepoint | slot; 122 if (i<glyphCount)glyphs[storei] = (unsigned int)index; 123 positions[(storei*2)] = startX + x + glyphPos[i].x_offset * scale; 124 positions[(storei*2)+1] = startY + y - glyphPos[i].y_offset * scale; 125 x += glyphPos[i].x_advance * scale; 126 y += glyphPos[i].y_advance * scale; 127 } 128 int storeadv = initialCount+glyphCount; 129 // The final slot in the positions array is important 130 // because when the GlyphVector is created from this 131 // data it determines the overall advance of the glyphvector 132 // and this is used in positioning the next glyphvector 133 // during rendering where text is broken into runs. 134 // We also need to report it back into "pt", so layout can 135 // pass it back down for that next run in this code. 136 positions[(storeadv*2)] = startX + x; 137 positions[(storeadv*2)+1] = startY + y; 138 (*env)->ReleasePrimitiveArrayCritical(env, glyphArray, glyphs, 0); 139 (*env)->ReleasePrimitiveArrayCritical(env, posArray, positions, 0); 140 putFloat(env, startPt,positions[(storeadv*2)],positions[(storeadv*2)+1] ); 141 jarray inxArray = 142 (jarray)(*env)->GetObjectField(env, gvdata, gvdIndicesFID); 143 unsigned int* indices = 144 (unsigned int*)(*env)->GetPrimitiveArrayCritical(env, inxArray, NULL); 145 int prevCluster = -1; 146 for (i = 0; i < glyphCount; i++) { 147 int cluster = glyphInfo[i].cluster; 148 if (direction == HB_DIRECTION_LTR) { 149 // I need to understand what hb does when processing a substring 150 // I expected the cluster index to be from the start of the text 151 // to process. 152 // Instead it appears to be from the start of the whole thing. 153 indices[i+initialCount] = cluster; 154 } else { 155 indices[i+initialCount] = baseIndex + glyphCount -1 -i; 156 } 157 } 158 (*env)->ReleasePrimitiveArrayCritical(env, inxArray, indices, 0); 159 (*env)->SetIntField(env, gvdata, gvdCountFID, initialCount+glyphCount); 160 return initialCount+glyphCount; 161 } 162 163 static float euclidianDistance(float a, float b) 164 { 165 float root; 166 if (a < 0) { 167 a = -a; 168 } 169 170 if (b < 0) { 171 b = -b; 172 } 173 174 if (a == 0) { 175 return b; 176 } 177 178 if (b == 0) { 179 return a; 180 } 181 182 /* Do an initial approximation, in root */ 183 root = a > b ? a + (b / 2) : b + (a / 2); 184 185 /* An unrolled Newton-Raphson iteration sequence */ 186 root = (root + (a * (a / root)) + (b * (b / root)) + 1) / 2; 187 root = (root + (a * (a / root)) + (b * (b / root)) + 1) / 2; 188 root = (root + (a * (a / root)) + (b * (b / root)) + 1) / 2; 189 190 return root; 191 } 192 193 JDKFontInfo* 194 createJDKFontInfo(JNIEnv *env, 195 jobject font2D, 196 jobject fontStrike, 197 jfloat ptSize, 198 jlong pScaler, 199 jlong pNativeFont, 200 jfloatArray matrix, 201 jboolean aat) { 202 203 204 JDKFontInfo *fi = (JDKFontInfo*)malloc(sizeof(JDKFontInfo)); 205 if (!fi) { 206 return NULL; 207 } 208 fi->env = env; // this is valid only for the life of this JNI call. 209 fi->font2D = font2D; 210 fi->fontStrike = fontStrike; 211 fi->nativeFont = pNativeFont; 212 fi->aat = aat; 213 (*env)->GetFloatArrayRegion(env, matrix, 0, 4, fi->matrix); 214 fi->ptSize = ptSize; 215 fi->xPtSize = euclidianDistance(fi->matrix[0], fi->matrix[1]); 216 fi->yPtSize = euclidianDistance(fi->matrix[2], fi->matrix[3]); 217 218 return fi; 219 } 220 221 222 #define TYPO_RTL 0x80000000 223 224 JNIEXPORT jboolean JNICALL Java_sun_font_SunLayoutEngine_shape 225 (JNIEnv *env, jclass cls, 226 jobject font2D, 227 jobject fontStrike, 228 jfloat ptSize, 229 jfloatArray matrix, 230 jlong pScaler, 231 jlong pNativeFont, 232 jboolean aat, 233 jcharArray text, 234 jobject gvdata, 235 jint script, 236 jint offset, 237 jint limit, 238 jint baseIndex, 239 jobject startPt, 240 jint flags, 241 jint slot) { 242 243 hb_buffer_t *buffer; 244 hb_font_t* hbfont; 245 jchar *chars; 246 jsize len; 247 int glyphCount; 248 hb_glyph_info_t *glyphInfo; 249 hb_glyph_position_t *glyphPos; 250 hb_direction_t direction = HB_DIRECTION_LTR; 251 hb_feature_t *features = NULL; 252 int featureCount = 0; 253 254 int i; 255 unsigned int buflen; 256 257 JDKFontInfo *jdkFontInfo = 258 createJDKFontInfo(env, font2D, fontStrike, ptSize, 259 pScaler, pNativeFont, matrix, aat); 260 if (!jdkFontInfo) { 261 return JNI_FALSE; 262 } 263 jdkFontInfo->env = env; // this is valid only for the life of this JNI call. 264 jdkFontInfo->font2D = font2D; 265 jdkFontInfo->fontStrike = fontStrike; 266 267 hbfont = hb_jdk_font_create(jdkFontInfo, NULL); 268 269 buffer = hb_buffer_create(); 270 hb_buffer_set_script(buffer, getHBScriptCode(script)); 271 hb_buffer_set_language(buffer, 272 hb_ot_tag_to_language(HB_OT_TAG_DEFAULT_LANGUAGE)); 273 if ((flags & TYPO_RTL) != 0) { 274 direction = HB_DIRECTION_RTL; 275 } 276 hb_buffer_set_direction(buffer, direction); 277 278 /* 279 features = malloc(sizeof(hb_feature_t) * numFeatures); 280 if (features) { 281 hb_feature_from_string("kern", -1, &features[featureCount++]); 282 hb_feature_from_string("liga", -1, &features[featureCount++]); 283 } 284 */ 285 286 chars = (*env)->GetCharArrayElements(env, text, NULL); 287 len = (*env)->GetArrayLength(env, text); 288 289 hb_buffer_add_utf16(buffer, chars, len, offset, limit-offset); 290 291 hb_shape_full(hbfont, buffer, features, featureCount, 0); 292 glyphCount = hb_buffer_get_length(buffer); 293 glyphInfo = hb_buffer_get_glyph_infos(buffer, 0); 294 glyphPos = hb_buffer_get_glyph_positions(buffer, &buflen); 295 for (i = 0; i < glyphCount; i++) { 296 int index = glyphInfo[i].codepoint; 297 int xadv = (glyphPos[i].x_advance); 298 int yadv = (glyphPos[i].y_advance); 299 } 300 // On "input" HB assigns a cluster index to each character in UTF-16. 301 // On output where a sequence of characters have been mapped to 302 // a glyph they are all mapped to the cluster index of the first character. 303 // The next cluster index will be that of the first character in the 304 // next cluster. So cluster indexes may 'skip' on output. 305 // This can also happen if there are supplementary code-points 306 // such that two UTF-16 characters are needed to make one codepoint. 307 // In RTL text you need to count down. 308 // So the following code tries to build the reverse map as expected 309 // by calling code. 310 311 storeGVData(env, gvdata, slot, baseIndex, startPt, 312 glyphCount, glyphInfo, glyphPos, direction); 313 314 hb_buffer_destroy (buffer); 315 hb_font_destroy(hbfont); 316 free((void*)jdkFontInfo); 317 if (features != NULL) free(features); 318 319 return JNI_TRUE; 320 } 321