/* * Copyright (c) 2015, 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. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * 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. */ #include #include #include "hb.h" #include "hb-jdk.h" #include "hb-ot.h" #ifdef MACOSX #include "hb-coretext.h" #endif #include "scriptMapping.h" static jclass gvdClass = 0; static const char* gvdClassName = "sun/font/GlyphLayout$GVData"; static jfieldID gvdCountFID = 0; static jfieldID gvdFlagsFID = 0; static jfieldID gvdGlyphsFID = 0; static jfieldID gvdPositionsFID = 0; static jfieldID gvdIndicesFID = 0; static void getFloat(JNIEnv* env, jobject pt, jfloat *x, jfloat *y) { *x = (*env)->GetFloatField(env, pt, sunFontIDs.xFID); *y = (*env)->GetFloatField(env, pt, sunFontIDs.yFID); } static void putFloat(JNIEnv* env, jobject pt, jfloat x, jfloat y) { (*env)->SetFloatField(env, pt, sunFontIDs.xFID, x); (*env)->SetFloatField(env, pt, sunFontIDs.yFID, y); } void init_JNI_IDs(JNIEnv *env) { CHECK_NULL(gvdClass = (*env)->FindClass(env, gvdClassName)); CHECK_NULL(gvdClass = (jclass)(*env)->NewGlobalRef(env, gvdClass)); CHECK_NULL(gvdCountFID = (*env)->GetFieldID(env, gvdClass, "_count", "I")); CHECK_NULL(gvdFlagsFID = (*env)->GetFieldID(env, gvdClass, "_flags", "I")); CHECK_NULL(gvdGlyphsFID = (*env)->GetFieldID(env, gvdClass, "_glyphs", "[I")); CHECK_NULL(gvdPositionsFID = (*env)->GetFieldID(env, gvdClass, "_positions", "[F")); gvdIndicesFID = (*env)->GetFieldID(env, gvdClass, "_indices", "[I"); } // 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) { int i; float x=0, y=0; float startX, startY; float scale = 1.0f/64.0f; unsigned int* glyphs; float* positions; init_JNI_IDs(env); int initialCount = (*env)->GetIntField(env, gvdata, gvdCountFID); jarray glyphArray = (jarray)(*env)->GetObjectField(env, gvdata, gvdGlyphsFID); jarray 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. int glyphArrayLen = (*env)->GetArrayLength(env, glyphArray); int posArrayLen = (*env)->GetArrayLength(env, posArray); int maxGlyphs = glyphCount + initialCount; if ((maxGlyphs > glyphArrayLen) || (maxGlyphs * 2 + 2 > posArrayLen)) { JNU_ThrowArrayIndexOutOfBoundsException(env, ""); return 0; } getFloat(env, startPt, &startX, &startY); glyphs = (unsigned int*)(*env)->GetPrimitiveArrayCritical(env, glyphArray, NULL); positions = (jfloat*)(*env)->GetPrimitiveArrayCritical(env, posArray, NULL); 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] ); jarray inxArray = (jarray)(*env)->GetObjectField(env, gvdata, gvdIndicesFID); unsigned int* indices = (unsigned int*)(*env)->GetPrimitiveArrayCritical(env, inxArray, NULL); int prevCluster = -1; 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; } static float euclidianDistance(float a, float b) { float root; if (a < 0) { a = -a; } if (b < 0) { b = -b; } if (a == 0) { return b; } if (b == 0) { return a; } /* Do an initial approximation, in root */ root = a > b ? a + (b / 2) : b + (a / 2); /* An unrolled Newton-Raphson iteration sequence */ root = (root + (a * (a / root)) + (b * (b / root)) + 1) / 2; root = (root + (a * (a / root)) + (b * (b / root)) + 1) / 2; root = (root + (a * (a / root)) + (b * (b / root)) + 1) / 2; return root; } JDKFontInfo* createJDKFontInfo(JNIEnv *env, jobject font2D, jobject fontStrike, jlong pScaler, jlong pNativeFont, jfloatArray matrix, jboolean aat) { JDKFontInfo *fi = (JDKFontInfo*)malloc(sizeof(JDKFontInfo)); if (!fi) { return NULL; } fi->env = env; // this is valid only for the life of this JNI call. fi->font2D = font2D; fi->fontStrike = fontStrike; fi->nativeFont = pNativeFont; fi->aat = aat; (*env)->GetFloatArrayRegion(env, matrix, 0, 4, fi->matrix); fi->xPtSize = euclidianDistance(fi->matrix[0], fi->matrix[1]); fi->yPtSize = euclidianDistance(fi->matrix[2], fi->matrix[3]); return fi; } #define TYPO_RTL 0x80000000 JNIEXPORT jboolean JNICALL Java_sun_font_SunLayoutEngine_shape (JNIEnv *env, jclass cls, jobject font2D, jobject fontStrike, jfloatArray matrix, jlong pScaler, jlong pNativeFont, jboolean aat, jcharArray text, jobject gvdata, jint script, jint offset, jint limit, jint baseIndex, jobject startPt, jint flags, jint slot) { hb_buffer_t *buffer; hb_font_t* hbfont; jchar *chars; jsize len; int glyphCount; hb_glyph_info_t *glyphInfo; hb_glyph_position_t *glyphPos; hb_direction_t direction = HB_DIRECTION_LTR; hb_feature_t *features = NULL; int featureCount = 0; //int xs, ys; int i; unsigned int buflen; JDKFontInfo *jdkFontInfo = createJDKFontInfo(env, font2D, fontStrike, pScaler, pNativeFont, matrix, aat); if (!jdkFontInfo) { return JNI_FALSE; } jdkFontInfo->env = env; // this is valid only for the life of this JNI call. jdkFontInfo->font2D = font2D; jdkFontInfo->fontStrike = fontStrike; hbfont = hb_jdk_font_create(jdkFontInfo, NULL); buffer = hb_buffer_create(); hb_buffer_set_script(buffer, getHBScriptCode(script)); hb_buffer_set_language(buffer, hb_ot_tag_to_language(HB_OT_TAG_DEFAULT_LANGUAGE)); if ((flags & TYPO_RTL) != 0) { direction = HB_DIRECTION_RTL; } hb_buffer_set_direction(buffer, direction); /* features = malloc(sizeof(hb_feature_t) * numFeatures); if (features) { hb_feature_from_string("kern", -1, &features[featureCount++]); hb_feature_from_string("liga", -1, &features[featureCount++]); } */ chars = (*env)->GetCharArrayElements(env, text, NULL); len = (*env)->GetArrayLength(env, text); hb_buffer_add_utf16(buffer, chars, len, offset, limit-offset); 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); hb_buffer_destroy (buffer); hb_font_destroy(hbfont); free((void*)jdkFontInfo); if (features != NULL) free(features); return JNI_TRUE; }