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 
  44 static void getFloat(JNIEnv* env, jobject pt, jfloat *x, jfloat *y) {
  45     *x = (*env)->GetFloatField(env, pt, sunFontIDs.xFID);
  46     *y = (*env)->GetFloatField(env, pt, sunFontIDs.yFID);
  47 }
  48 
  49 static void putFloat(JNIEnv* env, jobject pt, jfloat x, jfloat y) {
  50     (*env)->SetFloatField(env, pt, sunFontIDs.xFID, x);
  51     (*env)->SetFloatField(env, pt, sunFontIDs.yFID, y);
  52 }
  53 
  54 void init_JNI_IDs(JNIEnv *env) {
  55     CHECK_NULL(gvdClass = (*env)->FindClass(env, gvdClassName));
  56     CHECK_NULL(gvdClass = (jclass)(*env)->NewGlobalRef(env, gvdClass));
  57     CHECK_NULL(gvdCountFID = (*env)->GetFieldID(env, gvdClass, "_count", "I"));
  58     CHECK_NULL(gvdFlagsFID = (*env)->GetFieldID(env, gvdClass, "_flags", "I"));
  59     CHECK_NULL(gvdGlyphsFID = (*env)->GetFieldID(env, gvdClass, "_glyphs", "[I"));
  60     CHECK_NULL(gvdPositionsFID = (*env)->GetFieldID(env, gvdClass, "_positions", "[F"));
  61     gvdIndicesFID = (*env)->GetFieldID(env, gvdClass, "_indices", "[I");
  62 }
  63 
  64 // gmask is the composite font slot mask
  65 // baseindex is to be added to the character (code point) index.
  66 int storeGVData(JNIEnv* env,
  67                jobject gvdata, jint slot, jint baseIndex, jobject startPt,
  68                int glyphCount, hb_glyph_info_t *glyphInfo,
  69                hb_glyph_position_t *glyphPos, hb_direction_t direction) {
  70 
  71     int i;
  72     float x=0, y=0;
  73     float startX, startY;
  74     float scale = 1.0f/64.0f;
  75     unsigned int* glyphs;
  76     float* positions;
  77 
  78     init_JNI_IDs(env);
  79 
  80     int initialCount = (*env)->GetIntField(env, gvdata, gvdCountFID);
  81     jarray glyphArray =
  82        (jarray)(*env)->GetObjectField(env, gvdata, gvdGlyphsFID);
  83     jarray posArray =
  84         (jarray)(*env)->GetObjectField(env, gvdata, gvdPositionsFID);
  85 
  86     if (glyphArray == NULL || posArray == NULL)
  87     {
  88         JNU_ThrowArrayIndexOutOfBoundsException(env, "");
  89         return 0;
  90     }
  91 
  92     // The Java code catches the IIOBE and expands the storage
  93     // and re-invokes layout. I suppose this is expected to be rare
  94     // because at least in a single threaded case there should be
  95     // re-use of the same container, but it is a little wasteful/distateful.
  96     int glyphArrayLen = (*env)->GetArrayLength(env, glyphArray);
  97     int posArrayLen = (*env)->GetArrayLength(env, posArray);
  98     int maxGlyphs = glyphCount + initialCount;
  99     if ((maxGlyphs >  glyphArrayLen) ||
 100         (maxGlyphs * 2 + 2 >  posArrayLen))
 101     {
 102         JNU_ThrowArrayIndexOutOfBoundsException(env, "");
 103         return 0;
 104     }
 105 
 106     getFloat(env, startPt, &startX, &startY);
 107 
 108     glyphs =
 109         (unsigned int*)(*env)->GetPrimitiveArrayCritical(env, glyphArray, NULL);
 110     positions = (jfloat*)(*env)->GetPrimitiveArrayCritical(env, posArray, NULL);
 111     for (i = 0; i < glyphCount; i++) {
 112         int storei = i + initialCount;
 113         int index = glyphInfo[i].codepoint | slot;
 114         if (i<glyphCount)glyphs[storei] = (unsigned int)index;
 115         positions[(storei*2)] = startX + x + glyphPos[i].x_offset * scale;
 116         positions[(storei*2)+1] = startY + y - glyphPos[i].y_offset * scale;
 117         x += glyphPos[i].x_advance * scale;
 118         y += glyphPos[i].y_advance * scale;
 119     }
 120     int storeadv = initialCount+glyphCount;
 121     // The final slot in the positions array is important
 122     // because when the GlyphVector is created from this
 123     // data it determines the overall advance of the glyphvector
 124     // and this is used in positioning the next glyphvector
 125     // during rendering where text is broken into runs.
 126     // We also need to report it back into "pt", so layout can
 127     // pass it back down for that next run in this code.
 128     positions[(storeadv*2)] = startX + x;
 129     positions[(storeadv*2)+1] = startY + y;
 130     (*env)->ReleasePrimitiveArrayCritical(env, glyphArray, glyphs, 0);
 131     (*env)->ReleasePrimitiveArrayCritical(env, posArray, positions, 0);
 132     putFloat(env, startPt,positions[(storeadv*2)],positions[(storeadv*2)+1] );
 133     jarray inxArray =
 134         (jarray)(*env)->GetObjectField(env, gvdata, gvdIndicesFID);
 135     unsigned int* indices =
 136         (unsigned int*)(*env)->GetPrimitiveArrayCritical(env, inxArray, NULL);
 137     int prevCluster = -1;
 138     for (i = 0; i < glyphCount; i++) {
 139         int cluster = glyphInfo[i].cluster;
 140         if (direction == HB_DIRECTION_LTR) {
 141             // I need to understand what hb does when processing a substring
 142             // I expected the cluster index to be from the start of the text
 143             // to process.
 144             // Instead it appears to be from the start of the whole thing.
 145             indices[i+initialCount] = cluster;
 146         } else {
 147             indices[i+initialCount] = baseIndex + glyphCount -1 -i;
 148         }
 149     }
 150     (*env)->ReleasePrimitiveArrayCritical(env, inxArray, indices, 0);
 151     (*env)->SetIntField(env, gvdata, gvdCountFID, initialCount+glyphCount);
 152     return initialCount+glyphCount;
 153 }
 154 
 155 static float euclidianDistance(float a, float b)
 156 {
 157     float root;
 158     if (a < 0) {
 159         a = -a;
 160     }
 161 
 162     if (b < 0) {
 163         b = -b;
 164     }
 165 
 166     if (a == 0) {
 167         return b;
 168     }
 169 
 170     if (b == 0) {
 171         return a;
 172     }
 173 
 174     /* Do an initial approximation, in root */
 175     root = a > b ? a + (b / 2) : b + (a / 2);
 176 
 177     /* An unrolled Newton-Raphson iteration sequence */
 178     root = (root + (a * (a / root)) + (b * (b / root)) + 1) / 2;
 179     root = (root + (a * (a / root)) + (b * (b / root)) + 1) / 2;
 180     root = (root + (a * (a / root)) + (b * (b / root)) + 1) / 2;
 181 
 182     return root;
 183 }
 184 
 185 JDKFontInfo*
 186      createJDKFontInfo(JNIEnv *env,
 187                        jobject font2D,
 188                        jobject fontStrike,
 189                        jlong pScaler,
 190                        jlong pNativeFont,
 191                        jfloatArray matrix,
 192                        jboolean aat) {
 193 
 194 
 195     JDKFontInfo *fi = (JDKFontInfo*)malloc(sizeof(JDKFontInfo));
 196     if (!fi) {
 197        return NULL;
 198     }
 199     fi->env = env; // this is valid only for the life of this JNI call.
 200     fi->font2D = font2D;
 201     fi->fontStrike = fontStrike;
 202     fi->nativeFont = pNativeFont;
 203     fi->aat = aat;
 204     (*env)->GetFloatArrayRegion(env, matrix, 0, 4, fi->matrix);
 205     fi->xPtSize = euclidianDistance(fi->matrix[0], fi->matrix[1]);
 206     fi->yPtSize = euclidianDistance(fi->matrix[2], fi->matrix[3]);
 207 
 208     return fi;
 209 }
 210 
 211 
 212 #define TYPO_RTL 0x80000000
 213 
 214 JNIEXPORT jboolean JNICALL Java_sun_font_SunLayoutEngine_shape
 215     (JNIEnv *env, jclass cls,
 216      jobject font2D,
 217      jobject fontStrike,
 218      jfloatArray matrix,
 219      jlong pScaler,
 220      jlong pNativeFont,
 221      jboolean aat,
 222      jcharArray text,
 223      jobject gvdata,
 224      jint script,
 225      jint offset,
 226      jint limit,
 227      jint baseIndex,
 228      jobject startPt,
 229      jint flags,
 230      jint slot) {
 231 
 232      hb_buffer_t *buffer;
 233      hb_font_t* hbfont;
 234      jchar  *chars;
 235      jsize len;
 236      int glyphCount;
 237      hb_glyph_info_t *glyphInfo;
 238      hb_glyph_position_t *glyphPos;
 239      hb_direction_t direction = HB_DIRECTION_LTR;
 240      hb_feature_t *features =  NULL;
 241      int featureCount = 0;
 242      //int xs, ys;
 243 
 244      int i;
 245      unsigned int buflen;
 246 
 247      JDKFontInfo *jdkFontInfo =
 248          createJDKFontInfo(env, font2D, fontStrike,
 249                            pScaler, pNativeFont, matrix, aat);
 250      if (!jdkFontInfo) {
 251         return JNI_FALSE;
 252      }
 253      jdkFontInfo->env = env; // this is valid only for the life of this JNI call.
 254      jdkFontInfo->font2D = font2D;
 255      jdkFontInfo->fontStrike = fontStrike;
 256 
 257      hbfont = hb_jdk_font_create(jdkFontInfo, NULL);
 258 
 259      buffer = hb_buffer_create();
 260      hb_buffer_set_script(buffer, getHBScriptCode(script));
 261      hb_buffer_set_language(buffer,
 262                             hb_ot_tag_to_language(HB_OT_TAG_DEFAULT_LANGUAGE));
 263      if ((flags & TYPO_RTL) != 0) {
 264          direction = HB_DIRECTION_RTL;
 265      }
 266      hb_buffer_set_direction(buffer, direction);
 267 
 268 /*
 269      features = malloc(sizeof(hb_feature_t) * numFeatures);
 270      if (features) {
 271          hb_feature_from_string("kern", -1, &features[featureCount++]);
 272          hb_feature_from_string("liga", -1, &features[featureCount++]);
 273      }
 274 */
 275 
 276      chars = (*env)->GetCharArrayElements(env, text, NULL);
 277      len = (*env)->GetArrayLength(env, text);
 278 
 279      hb_buffer_add_utf16(buffer, chars, len, offset, limit-offset);
 280 
 281      hb_shape_full(hbfont, buffer, features, featureCount, 0);
 282      glyphCount = hb_buffer_get_length(buffer);
 283      glyphInfo = hb_buffer_get_glyph_infos(buffer, 0);
 284      glyphPos = hb_buffer_get_glyph_positions(buffer, &buflen);
 285      for (i = 0; i < glyphCount; i++) {
 286          int index = glyphInfo[i].codepoint;
 287          int xadv = (glyphPos[i].x_advance);
 288          int yadv = (glyphPos[i].y_advance);
 289      }
 290      // On "input" HB assigns a cluster index to each character in UTF-16.
 291      // On output where a sequence of characters have been mapped to
 292      // a glyph they are all mapped to the cluster index of the first character.
 293      // The next cluster index will be that of the first character in the
 294      // next cluster. So cluster indexes may 'skip' on output.
 295      // This can also happen if there are supplementary code-points
 296      // such that two UTF-16 characters are needed to make one codepoint.
 297      // In RTL text you need to count down.
 298      // So the following code tries to build the reverse map as expected
 299      // by calling code.
 300 
 301      storeGVData(env, gvdata, slot, baseIndex, startPt,
 302                  glyphCount, glyphInfo, glyphPos, direction);
 303 
 304      hb_buffer_destroy (buffer);
 305      hb_font_destroy(hbfont);
 306      free((void*)jdkFontInfo);
 307      if (features != NULL) free(features);
 308 
 309      return JNI_TRUE;
 310 }
 311