src/java.desktop/macosx/native/libawt_lwawt/awt/CTextPipe.m
changeset 47216 71c04702a3d5
parent 26751 70bac69b37c9
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.desktop/macosx/native/libawt_lwawt/awt/CTextPipe.m	Tue Sep 12 19:03:39 2017 +0200
@@ -0,0 +1,708 @@
+/*
+ * Copyright (c) 2011, 2013, 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.
+ */
+
+//  Native side of the Quartz text pipe, paints on Quartz Surface Datas.
+//  Interesting Docs : /Developer/Documentation/Cocoa/TasksAndConcepts/ProgrammingTopics/FontHandling/FontHandling.html
+
+#import "sun_awt_SunHints.h"
+#import "sun_lwawt_macosx_CTextPipe.h"
+#import "sun_java2d_OSXSurfaceData.h"
+
+#import <JavaNativeFoundation/JavaNativeFoundation.h>
+
+#import "CoreTextSupport.h"
+#import "QuartzSurfaceData.h"
+#include "AWTStrike.h"
+
+/* Use THIS_FILE when it is available. */
+#ifndef THIS_FILE
+    #define THIS_FILE __FILE__
+#endif
+
+static const CGAffineTransform sInverseTX = { 1, 0, 0, -1, 0, 0 };
+
+
+#pragma mark --- CoreText Support ---
+
+
+// Translates a Unicode into a CGGlyph/CTFontRef pair
+// Returns the substituted font, and places the appropriate glyph into "glyphRef"
+CTFontRef JavaCT_CopyCTFallbackFontAndGlyphForUnicode
+(const AWTFont *font, const UTF16Char *charRef, CGGlyph *glyphRef, int count) {
+    CTFontRef fallback = JRSFontCreateFallbackFontForCharacters((CTFontRef)font->fFont, charRef, count);
+    if (fallback == NULL)
+    {
+        // use the original font if we somehow got duped into trying to fallback something we can't
+        fallback = (CTFontRef)font->fFont;
+        CFRetain(fallback);
+    }
+
+    CTFontGetGlyphsForCharacters(fallback, charRef, glyphRef, count);
+    return fallback;
+}
+
+// Translates a Java glyph code int (might be a negative unicode value) into a CGGlyph/CTFontRef pair
+// Returns the substituted font, and places the appropriate glyph into "glyph"
+CTFontRef JavaCT_CopyCTFallbackFontAndGlyphForJavaGlyphCode
+(const AWTFont *font, const jint glyphCode, CGGlyph *glyphRef)
+{
+    // negative glyph codes are really unicodes, which were placed there by the mapper
+    // to indicate we should use CoreText to substitute the character
+    if (glyphCode >= 0)
+    {
+        *glyphRef = glyphCode;
+        CFRetain(font->fFont);
+        return (CTFontRef)font->fFont;
+    }
+
+    UTF16Char character = -glyphCode;
+    return JavaCT_CopyCTFallbackFontAndGlyphForUnicode(font, &character, glyphRef, 1);
+}
+
+// Breakup a 32 bit unicode value into the component surrogate pairs
+void JavaCT_BreakupUnicodeIntoSurrogatePairs(int uniChar, UTF16Char charRef[]) {
+    int value = uniChar - 0x10000;
+    UTF16Char low_surrogate = (value & 0x3FF) | LO_SURROGATE_START;
+    UTF16Char high_surrogate = (((int)(value & 0xFFC00)) >> 10) | HI_SURROGATE_START;
+    charRef[0] = high_surrogate;
+    charRef[1] = low_surrogate;
+}
+
+
+
+/*
+ * Callback for CoreText which uses the CoreTextProviderStruct to feed CT UniChars
+ * We only use it for one-off lines, and don't attempt to fragment our strings
+ */
+const UniChar *Java_CTProvider
+(CFIndex stringIndex, CFIndex *charCount, CFDictionaryRef *attributes, void *refCon)
+{
+    // if we have a zero length string we can just return NULL for the string
+    // or if the index anything other than 0 we are not using core text
+    // correctly since we only have one run.
+    if (stringIndex != 0)
+    {
+        return NULL;
+    }
+
+    CTS_ProviderStruct *ctps = (CTS_ProviderStruct *)refCon;
+    *charCount = ctps->length;
+    *attributes = ctps->attributes;
+    return ctps->unicodes;
+}
+
+
+/*
+ *    Gets a Dictionary filled with common details we want to use for CoreText when we are interacting
+ *    with it from Java.
+ */
+static NSDictionary* ctsDictionaryFor(const NSFont *font, BOOL useFractionalMetrics)
+{
+    NSNumber *gZeroNumber = [NSNumber numberWithInt:0];
+    NSNumber *gOneNumber = [NSNumber numberWithInt:1];
+
+    return [NSDictionary dictionaryWithObjectsAndKeys:
+             font, NSFontAttributeName,
+             gOneNumber,  (id)kCTForegroundColorFromContextAttributeName,
+             useFractionalMetrics ? gZeroNumber : gOneNumber, @"CTIntegerMetrics", // force integer hack in CoreText to help with Java's integer assumptions
+             gZeroNumber, NSLigatureAttributeName,
+             gZeroNumber, NSKernAttributeName,
+             nil];
+}
+
+// Itterates though each glyph, and if a transform is present for that glyph, apply it to the CGContext, and strike the glyph.
+// If there is no per-glyph transform, just strike the glyph. Advances must also be transformed on-the-spot as well.
+void JavaCT_DrawGlyphVector
+(const QuartzSDOps *qsdo, const AWTStrike *strike, const BOOL useSubstituion, const int uniChars[], const CGGlyph glyphs[], CGSize advances[], const jint g_gvTXIndicesAsInts[], const jdouble g_gvTransformsAsDoubles[], const CFIndex length)
+{
+    CGPoint pt = { 0, 0 };
+
+    // get our baseline transform and font
+    CGContextRef cgRef = qsdo->cgRef;
+    CGAffineTransform ctmText = CGContextGetTextMatrix(cgRef);
+
+    BOOL saved = false;
+
+    CGAffineTransform invTx = CGAffineTransformInvert(strike->fTx);
+
+    NSInteger i;
+    for (i = 0; i < length; i++)
+    {
+        CGGlyph glyph = glyphs[i];
+        int uniChar = uniChars[i];
+        // if we found a unichar instead of a glyph code, get the fallback font,
+        // find the glyph code for the fallback font, and set the font on the current context
+        if (uniChar != 0)
+        {
+            CTFontRef fallback;
+            if (uniChar > 0xFFFF) {
+                UTF16Char charRef[2];
+                JavaCT_BreakupUnicodeIntoSurrogatePairs(uniChar, charRef);
+                CGGlyph glyphTmp[2];
+                fallback = JavaCT_CopyCTFallbackFontAndGlyphForUnicode(strike->fAWTFont, (const UTF16Char *)&charRef, (CGGlyph *)&glyphTmp, 2);
+                glyph = glyphTmp[0];
+            } else {
+                const UTF16Char u = uniChar;
+                fallback = JavaCT_CopyCTFallbackFontAndGlyphForUnicode(strike->fAWTFont, &u, (CGGlyph *)&glyph, 1);
+            }
+            if (fallback) {
+                const CGFontRef cgFallback = CTFontCopyGraphicsFont(fallback, NULL);
+                CFRelease(fallback);
+
+                if (cgFallback) {
+                    if (!saved) {
+                        CGContextSaveGState(cgRef);
+                        saved = true;
+                    }
+                    CGContextSetFont(cgRef, cgFallback);
+                    CFRelease(cgFallback);
+                }
+            }
+        } else {
+            if (saved) {
+                CGContextRestoreGState(cgRef);
+                saved = false;
+            }
+        }
+
+        // if we have per-glyph transformations
+        int tin = (g_gvTXIndicesAsInts == NULL) ? -1 : (g_gvTXIndicesAsInts[i] - 1) * 6;
+        if (tin < 0)
+        {
+            CGContextShowGlyphsAtPoint(cgRef, pt.x, pt.y, &glyph, 1);
+        }
+        else
+        {
+            CGAffineTransform tx = CGAffineTransformMake(
+                                                         (CGFloat)g_gvTransformsAsDoubles[tin + 0], (CGFloat)g_gvTransformsAsDoubles[tin + 2],
+                                                         (CGFloat)g_gvTransformsAsDoubles[tin + 1], (CGFloat)g_gvTransformsAsDoubles[tin + 3],
+                                                         0, 0);
+
+            CGPoint txOffset = { (CGFloat)g_gvTransformsAsDoubles[tin + 4], (CGFloat)g_gvTransformsAsDoubles[tin + 5] };
+
+            txOffset = CGPointApplyAffineTransform(txOffset, invTx);
+
+            // apply the transform, strike the glyph, can change the transform back
+            CGContextSetTextMatrix(cgRef, CGAffineTransformConcat(ctmText, tx));
+            CGContextShowGlyphsAtPoint(cgRef, txOffset.x + pt.x, txOffset.y + pt.y, &glyph, 1);
+            CGContextSetTextMatrix(cgRef, ctmText);
+
+            // transform the measured advance for this strike
+            advances[i] = CGSizeApplyAffineTransform(advances[i], tx);
+            advances[i].width += txOffset.x;
+            advances[i].height += txOffset.y;
+        }
+
+        // move our next x,y
+        pt.x += advances[i].width;
+        pt.y += advances[i].height;
+
+    }
+    // reset the font on the context after striking a unicode with CoreText
+    if (saved) {
+        CGContextRestoreGState(cgRef);
+    }
+}
+
+// Using the Quartz Surface Data context, draw a hot-substituted character run
+void JavaCT_DrawTextUsingQSD(JNIEnv *env, const QuartzSDOps *qsdo, const AWTStrike *strike, const jchar *chars, const jsize length)
+{
+    CGContextRef cgRef = qsdo->cgRef;
+
+    AWTFont *awtFont = strike->fAWTFont;
+    CGFloat ptSize = strike->fSize;
+    CGAffineTransform tx = strike->fFontTx;
+
+    NSFont *nsFont = [NSFont fontWithName:[awtFont->fFont fontName] size:ptSize];
+
+    if (ptSize != 0) {
+        CGFloat invScale = 1 / ptSize;
+        tx = CGAffineTransformConcat(tx, CGAffineTransformMakeScale(invScale, invScale));
+        CGContextConcatCTM(cgRef, tx);
+    }
+
+    CGContextSetTextMatrix(cgRef, CGAffineTransformIdentity); // resets the damage from CoreText
+
+    NSString *string = [NSString stringWithCharacters:chars length:length];
+    /*
+       The calls below were used previously but for unknown reason did not 
+       render using the right font (see bug 7183516) when attribString is not 
+       initialized with font dictionary attributes.  It seems that "options" 
+       in CTTypesetterCreateWithAttributedStringAndOptions which contains the 
+       font dictionary is ignored.
+
+    NSAttributedString *attribString = [[NSAttributedString alloc] initWithString:string];
+
+    CTTypesetterRef typeSetterRef = CTTypesetterCreateWithAttributedStringAndOptions((CFAttributedStringRef) attribString, (CFDictionaryRef) ctsDictionaryFor(nsFont, JRSFontStyleUsesFractionalMetrics(strike->fStyle)));
+    */
+    NSAttributedString *attribString = [[NSAttributedString alloc]
+        initWithString:string
+        attributes:ctsDictionaryFor(nsFont, JRSFontStyleUsesFractionalMetrics(strike->fStyle))];
+    
+    CTTypesetterRef typeSetterRef = CTTypesetterCreateWithAttributedString((CFAttributedStringRef) attribString);
+
+    CFRange range = {0, length};
+    CTLineRef lineRef = CTTypesetterCreateLine(typeSetterRef, range);
+
+    CTLineDraw(lineRef, cgRef);
+
+    [attribString release];
+    CFRelease(lineRef);
+    CFRelease(typeSetterRef);
+}
+
+
+/*----------------------
+    DrawTextContext is the funnel for all of our CoreText drawing.
+    All three JNI apis call through this method.
+ ----------------------*/
+static void DrawTextContext
+(JNIEnv *env, QuartzSDOps *qsdo, const AWTStrike *strike, const jchar *chars, const jsize length, const jdouble x, const jdouble y)
+{
+    if (length == 0)
+    {
+        return;
+    }
+
+    qsdo->BeginSurface(env, qsdo, SD_Text);
+    if (qsdo->cgRef == NULL)
+    {
+        qsdo->FinishSurface(env, qsdo);
+        return;
+    }
+
+    CGContextRef cgRef = qsdo->cgRef;
+
+
+    CGContextSaveGState(cgRef);
+    JRSFontSetRenderingStyleOnContext(cgRef, strike->fStyle);
+
+    // we want to translate before we transform (scale or rotate) <rdar://4042541> (vm)
+    CGContextTranslateCTM(cgRef, x, y);
+
+    AWTFont *awtfont = strike->fAWTFont; //(AWTFont *)(qsdo->fontInfo.awtfont);
+    NSCharacterSet *charSet = [awtfont->fFont coveredCharacterSet];
+
+    JavaCT_DrawTextUsingQSD(env, qsdo, strike, chars, length);   // Draw with CoreText
+
+    CGContextRestoreGState(cgRef);
+
+    qsdo->FinishSurface(env, qsdo);
+}
+
+#pragma mark --- Glyph Vector Pipeline ---
+
+/*-----------------------------------
+    Glyph Vector Pipeline
+
+    doDrawGlyphs() has been separated into several pipelined functions to increase performance,
+    and improve accountability for JNI resources, malloc'd memory, and error handling.
+
+    Each stage of the pipeline is responsible for doing only one major thing, like allocating buffers,
+    aquiring transform arrays from JNI, filling buffers, or striking glyphs. All resources or memory
+    acquired at a given stage, must be released in that stage. Any error that occurs (like a failed malloc)
+    is to be handled in the stage it occurs in, and is to return immediatly after freeing it's resources.
+
+-----------------------------------*/
+
+static JNF_CLASS_CACHE(jc_StandardGlyphVector, "sun/font/StandardGlyphVector");
+
+// Checks the GlyphVector Java object for any transforms that were applied to individual characters. If none are present,
+// strike the glyphs immediately in Core Graphics. Otherwise, obtain the arrays, and defer to above.
+static inline void doDrawGlyphsPipe_checkForPerGlyphTransforms
+(JNIEnv *env, QuartzSDOps *qsdo, const AWTStrike *strike, jobject gVector, BOOL useSubstituion, int *uniChars, CGGlyph *glyphs, CGSize *advances, size_t length)
+{
+    // if we have no character substitution, and no per-glyph transformations - strike now!
+    static JNF_MEMBER_CACHE(jm_StandardGlyphVector_gti, jc_StandardGlyphVector, "gti", "Lsun/font/StandardGlyphVector$GlyphTransformInfo;");
+    jobject gti = JNFGetObjectField(env, gVector, jm_StandardGlyphVector_gti);
+    if (gti == 0)
+    {
+        if (useSubstituion)
+        {
+            // quasi-simple case, substitution, but no per-glyph transforms
+            JavaCT_DrawGlyphVector(qsdo, strike, TRUE, uniChars, glyphs, advances, NULL, NULL, length);
+        }
+        else
+        {
+            // fast path, straight to CG without per-glyph transforms
+            CGContextShowGlyphsWithAdvances(qsdo->cgRef, glyphs, advances, length);
+        }
+        return;
+    }
+
+    static JNF_CLASS_CACHE(jc_StandardGlyphVector_GlyphTransformInfo, "sun/font/StandardGlyphVector$GlyphTransformInfo");
+    static JNF_MEMBER_CACHE(jm_StandardGlyphVector_GlyphTransformInfo_transforms, jc_StandardGlyphVector_GlyphTransformInfo, "transforms", "[D");
+    jdoubleArray g_gtiTransformsArray = JNFGetObjectField(env, gti, jm_StandardGlyphVector_GlyphTransformInfo_transforms); //(*env)->GetObjectField(env, gti, g_gtiTransforms);
+    if (g_gtiTransformsArray == NULL) {
+        return;
+    } 
+    jdouble *g_gvTransformsAsDoubles = (*env)->GetPrimitiveArrayCritical(env, g_gtiTransformsArray, NULL);
+    if (g_gvTransformsAsDoubles == NULL) {
+        (*env)->DeleteLocalRef(env, g_gtiTransformsArray);
+        return;
+    } 
+
+    static JNF_MEMBER_CACHE(jm_StandardGlyphVector_GlyphTransformInfo_indices, jc_StandardGlyphVector_GlyphTransformInfo, "indices", "[I");
+    jintArray g_gtiTXIndicesArray = JNFGetObjectField(env, gti, jm_StandardGlyphVector_GlyphTransformInfo_indices);
+    jint *g_gvTXIndicesAsInts = (*env)->GetPrimitiveArrayCritical(env, g_gtiTXIndicesArray, NULL);
+    if (g_gvTXIndicesAsInts == NULL) {
+        (*env)->ReleasePrimitiveArrayCritical(env, g_gtiTransformsArray, g_gvTransformsAsDoubles, JNI_ABORT);
+        (*env)->DeleteLocalRef(env, g_gtiTransformsArray);
+        (*env)->DeleteLocalRef(env, g_gtiTXIndicesArray);
+        return;
+    }
+    // slowest case, we have per-glyph transforms, and possibly glyph substitution as well
+    JavaCT_DrawGlyphVector(qsdo, strike, useSubstituion, uniChars, glyphs, advances, g_gvTXIndicesAsInts, g_gvTransformsAsDoubles, length);
+
+    (*env)->ReleasePrimitiveArrayCritical(env, g_gtiTransformsArray, g_gvTransformsAsDoubles, JNI_ABORT);
+    (*env)->ReleasePrimitiveArrayCritical(env, g_gtiTXIndicesArray, g_gvTXIndicesAsInts, JNI_ABORT);
+
+    (*env)->DeleteLocalRef(env, g_gtiTransformsArray);
+    (*env)->DeleteLocalRef(env, g_gtiTXIndicesArray);
+}
+
+// Retrieves advances for translated unicodes
+// Uses "glyphs" as a temporary buffer for the glyph-to-unicode translation
+void JavaCT_GetAdvancesForUnichars
+(const NSFont *font, const int uniChars[], CGGlyph glyphs[], const size_t length, CGSize advances[])
+{
+    // cycle over each spot, and if we discovered a unicode to substitute, we have to calculate the advance for it
+    size_t i;
+    for (i = 0; i < length; i++)
+    {
+        UniChar uniChar = uniChars[i];
+        if (uniChar == 0) continue;
+
+        CGGlyph glyph = 0;
+        const CTFontRef fallback = JRSFontCreateFallbackFontForCharacters((CTFontRef)font, &uniChar, 1);
+        if (fallback) {
+            CTFontGetGlyphsForCharacters(fallback, &uniChar, &glyph, 1);
+            CTFontGetAdvancesForGlyphs(fallback, kCTFontDefaultOrientation, &glyph, &(advances[i]), 1);
+            CFRelease(fallback);
+        }
+
+        glyphs[i] = glyph;
+    }
+}
+
+// Fills the glyph buffer with glyphs from the GlyphVector object. Also checks to see if the glyph's positions have been
+// already caculated from GlyphVector, or we simply ask Core Graphics to make some advances for us. Pre-calculated positions
+// are translated into advances, since CG only understands advances.
+static inline void doDrawGlyphsPipe_fillGlyphAndAdvanceBuffers
+(JNIEnv *env, QuartzSDOps *qsdo, const AWTStrike *strike, jobject gVector, CGGlyph *glyphs, int *uniChars, CGSize *advances, size_t length, jintArray glyphsArray)
+{
+    // fill the glyph buffer
+    jint *glyphsAsInts = (*env)->GetPrimitiveArrayCritical(env, glyphsArray, NULL);
+    if (glyphsAsInts == NULL) {
+        return;
+    }
+
+    // if a glyph code from Java is negative, that means it is really a unicode value
+    // which we can use in CoreText to strike the character in another font
+    size_t i;
+    BOOL complex = NO;
+    for (i = 0; i < length; i++)
+    {
+        jint code = glyphsAsInts[i];
+        if (code < 0)
+        {
+            complex = YES;
+            uniChars[i] = -code;
+            glyphs[i] = 0;
+        }
+        else
+        {
+            uniChars[i] = 0;
+            glyphs[i] = code;
+        }
+    }
+
+    (*env)->ReleasePrimitiveArrayCritical(env, glyphsArray, glyphsAsInts, JNI_ABORT);
+
+    // fill the advance buffer
+    static JNF_MEMBER_CACHE(jm_StandardGlyphVector_positions, jc_StandardGlyphVector, "positions", "[F");
+    jfloatArray posArray = JNFGetObjectField(env, gVector, jm_StandardGlyphVector_positions);
+    jfloat *positions = NULL;
+    if (posArray != NULL) {
+        // in this case, the positions have already been pre-calculated for us on the Java side
+        positions = (*env)->GetPrimitiveArrayCritical(env, posArray, NULL);
+        if (positions == NULL) {
+            (*env)->DeleteLocalRef(env, posArray);
+        }
+    }
+    if (positions != NULL) {
+        CGPoint prev;
+        prev.x = positions[0];
+        prev.y = positions[1];
+
+        // <rdar://problem/4294061> take the first point, and move the context to that location
+        CGContextTranslateCTM(qsdo->cgRef, prev.x, prev.y);
+
+        CGAffineTransform invTx = CGAffineTransformInvert(strike->fFontTx);
+
+        // for each position, figure out the advance (since CG won't take positions directly)
+        size_t i;
+        for (i = 0; i < length - 1; i++)
+        {
+            size_t i2 = (i+1) * 2;
+            CGPoint pt;
+            pt.x = positions[i2];
+            pt.y = positions[i2+1];
+            pt = CGPointApplyAffineTransform(pt, invTx);
+            advances[i].width = pt.x - prev.x;
+            advances[i].height = -(pt.y - prev.y); // negative to translate to device space
+            prev.x = pt.x;
+            prev.y = pt.y;
+        }
+
+        (*env)->ReleasePrimitiveArrayCritical(env, posArray, positions, JNI_ABORT);
+        (*env)->DeleteLocalRef(env, posArray);
+    }
+    else
+    {
+        // in this case, we have to go and calculate the positions ourselves
+        // there were no pre-calculated positions from the glyph buffer on the Java side
+        AWTFont *awtFont = strike->fAWTFont;
+        CTFontGetAdvancesForGlyphs((CTFontRef)awtFont->fFont, kCTFontDefaultOrientation, glyphs, advances, length);
+
+        if (complex)
+        {
+            JavaCT_GetAdvancesForUnichars(awtFont->fFont, uniChars, glyphs, length, advances);
+        }
+    }
+
+    // continue on to the next stage of the pipe
+    doDrawGlyphsPipe_checkForPerGlyphTransforms(env, qsdo, strike, gVector, complex, uniChars, glyphs, advances, length);
+}
+
+// Obtains the glyph array to determine the number of glyphs we are dealing with. If we are dealing a large number of glyphs,
+// we malloc a buffer to hold the glyphs and their advances, otherwise we use stack allocated buffers.
+static inline void doDrawGlyphsPipe_getGlyphVectorLengthAndAlloc
+(JNIEnv *env, QuartzSDOps *qsdo, const AWTStrike *strike, jobject gVector)
+{
+    static JNF_MEMBER_CACHE(jm_StandardGlyphVector_glyphs, jc_StandardGlyphVector, "glyphs", "[I");
+    jintArray glyphsArray = JNFGetObjectField(env, gVector, jm_StandardGlyphVector_glyphs);
+    jsize length = (*env)->GetArrayLength(env, glyphsArray);
+
+    if (length == 0)
+    {
+        // nothing to draw
+        (*env)->DeleteLocalRef(env, glyphsArray);
+        return;
+    }
+
+    if (length < MAX_STACK_ALLOC_GLYPH_BUFFER_SIZE)
+    {
+        // if we are small enough, fit everything onto the stack
+        CGGlyph glyphs[length];
+        int uniChars[length];
+        CGSize advances[length];
+        doDrawGlyphsPipe_fillGlyphAndAdvanceBuffers(env, qsdo, strike, gVector, glyphs, uniChars, advances, length, glyphsArray);
+    }
+    else
+    {
+        // otherwise, we should malloc and free buffers for this large run
+        CGGlyph *glyphs = (CGGlyph *)malloc(sizeof(CGGlyph) * length);
+        int *uniChars = (int *)malloc(sizeof(int) * length);
+        CGSize *advances = (CGSize *)malloc(sizeof(CGSize) * length);
+
+        if (glyphs == NULL || uniChars == NULL || advances == NULL)
+        {
+            (*env)->DeleteLocalRef(env, glyphsArray);
+            [NSException raise:NSMallocException format:@"%s-%s:%d", THIS_FILE, __FUNCTION__, __LINE__];
+            if (glyphs)
+            {
+                free(glyphs);
+            }
+            if (uniChars)
+            {
+                free(uniChars);
+            }
+            if (advances)
+            {
+                free(advances);
+            }
+            return;
+        }
+
+        doDrawGlyphsPipe_fillGlyphAndAdvanceBuffers(env, qsdo, strike, gVector, glyphs, uniChars, advances, length, glyphsArray);
+
+        free(glyphs);
+        free(uniChars);
+        free(advances);
+    }
+
+    (*env)->DeleteLocalRef(env, glyphsArray);
+}
+
+// Setup and save the state of the CGContext, and apply any java.awt.Font transforms to the context.
+static inline void doDrawGlyphsPipe_applyFontTransforms
+(JNIEnv *env, QuartzSDOps *qsdo, const AWTStrike *strike, jobject gVector, const jfloat x, const jfloat y)
+{
+    CGContextRef cgRef = qsdo->cgRef;
+    CGContextSetFontSize(cgRef, 1.0);
+    CGContextSetFont(cgRef, strike->fAWTFont->fNativeCGFont);
+    CGContextSetTextMatrix(cgRef, CGAffineTransformIdentity);
+
+    CGAffineTransform tx = strike->fFontTx;
+    tx.tx += x;
+    tx.ty += y;
+    CGContextConcatCTM(cgRef, tx);
+
+    doDrawGlyphsPipe_getGlyphVectorLengthAndAlloc(env, qsdo, strike, gVector);
+}
+
+
+#pragma mark --- CTextPipe JNI ---
+
+
+/*
+ * Class:     sun_lwawt_macosx_CTextPipe
+ * Method:    doDrawString
+ * Signature: (Lsun/java2d/SurfaceData;JLjava/lang/String;DD)V
+ */
+JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CTextPipe_doDrawString
+(JNIEnv *env, jobject jthis, jobject jsurfacedata, jlong awtStrikePtr, jstring str, jdouble x, jdouble y)
+{
+    QuartzSDOps *qsdo = (QuartzSDOps *)SurfaceData_GetOps(env, jsurfacedata);
+    AWTStrike *awtStrike = (AWTStrike *)jlong_to_ptr(awtStrikePtr);
+
+JNF_COCOA_ENTER(env);
+
+    jsize len = (*env)->GetStringLength(env, str);
+
+    if (len < MAX_STACK_ALLOC_GLYPH_BUFFER_SIZE) // optimized for stack allocation <rdar://problem/4285041>
+    {
+        jchar unichars[len];
+        (*env)->GetStringRegion(env, str, 0, len, unichars);
+        JNF_CHECK_AND_RETHROW_EXCEPTION(env);
+
+        // Draw the text context
+        DrawTextContext(env, qsdo, awtStrike, unichars, len, x, y);
+    }
+    else
+    {
+        // Get string to draw and the length
+        const jchar *unichars = JNFGetStringUTF16UniChars(env, str);
+
+        // Draw the text context
+        DrawTextContext(env, qsdo, awtStrike, unichars, len, x, y);
+
+        JNFReleaseStringUTF16UniChars(env, str, unichars);
+    }
+
+JNF_COCOA_RENDERER_EXIT(env);
+}
+
+
+/*
+ * Class:     sun_lwawt_macosx_CTextPipe
+ * Method:    doUnicodes
+ * Signature: (Lsun/java2d/SurfaceData;J[CIIFF)V
+ */
+JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CTextPipe_doUnicodes
+(JNIEnv *env, jobject jthis, jobject jsurfacedata, jlong awtStrikePtr, jcharArray unicodes, jint offset, jint length, jfloat x, jfloat y)
+{
+    QuartzSDOps *qsdo = (QuartzSDOps *)SurfaceData_GetOps(env, jsurfacedata);
+    AWTStrike *awtStrike = (AWTStrike *)jlong_to_ptr(awtStrikePtr);
+
+JNF_COCOA_ENTER(env);
+
+    // Setup the text context
+    if (length < MAX_STACK_ALLOC_GLYPH_BUFFER_SIZE) // optimized for stack allocation
+    {
+        jchar copyUnichars[length];
+        (*env)->GetCharArrayRegion(env, unicodes, offset, length, copyUnichars);
+        JNF_CHECK_AND_RETHROW_EXCEPTION(env);
+        DrawTextContext(env, qsdo, awtStrike, copyUnichars, length, x, y);
+    }
+    else
+    {
+        jchar *copyUnichars = malloc(length * sizeof(jchar));
+        if (!copyUnichars) {
+            [JNFException raise:env as:kOutOfMemoryError reason:"Failed to malloc memory to create the glyphs for string drawing"];
+        }
+
+        @try {
+            (*env)->GetCharArrayRegion(env, unicodes, offset, length, copyUnichars);
+            JNF_CHECK_AND_RETHROW_EXCEPTION(env);
+            DrawTextContext(env, qsdo, awtStrike, copyUnichars, length, x, y);
+        } @finally {
+            free(copyUnichars);
+        }
+    }
+
+JNF_COCOA_RENDERER_EXIT(env);
+}
+
+/*
+ * Class:     sun_lwawt_macosx_CTextPipe
+ * Method:    doOneUnicode
+ * Signature: (Lsun/java2d/SurfaceData;JCFF)V
+ */
+JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CTextPipe_doOneUnicode
+(JNIEnv *env, jobject jthis, jobject jsurfacedata, jlong awtStrikePtr, jchar aUnicode, jfloat x, jfloat y)
+{
+    QuartzSDOps *qsdo = (QuartzSDOps *)SurfaceData_GetOps(env, jsurfacedata);
+    AWTStrike *awtStrike = (AWTStrike *)jlong_to_ptr(awtStrikePtr);
+
+JNF_COCOA_ENTER(env);
+
+    DrawTextContext(env, qsdo, awtStrike, &aUnicode, 1, x, y);
+
+JNF_COCOA_RENDERER_EXIT(env);
+}
+
+/*
+ * Class: sun_lwawt_macosx_CTextPipe
+ * Method: doDrawGlyphs
+ * Signature: (Lsun/java2d/SurfaceData;JLjava/awt/font/GlyphVector;FF)V
+ */
+JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CTextPipe_doDrawGlyphs
+(JNIEnv *env, jobject jthis, jobject jsurfacedata, jlong awtStrikePtr, jobject gVector, jfloat x, jfloat y)
+{
+    QuartzSDOps *qsdo = (QuartzSDOps *)SurfaceData_GetOps(env, jsurfacedata);
+    AWTStrike *awtStrike = (AWTStrike *)jlong_to_ptr(awtStrikePtr);
+
+JNF_COCOA_ENTER(env);
+
+    qsdo->BeginSurface(env, qsdo, SD_Text);
+    if (qsdo->cgRef == NULL)
+    {
+        qsdo->FinishSurface(env, qsdo);
+        return;
+    }
+
+    CGContextSaveGState(qsdo->cgRef);
+    JRSFontSetRenderingStyleOnContext(qsdo->cgRef, JRSFontGetRenderingStyleForHints(sun_awt_SunHints_INTVAL_FRACTIONALMETRICS_ON, sun_awt_SunHints_INTVAL_TEXT_ANTIALIAS_ON));
+
+    doDrawGlyphsPipe_applyFontTransforms(env, qsdo, awtStrike, gVector, x, y);
+
+    CGContextRestoreGState(qsdo->cgRef);
+
+    qsdo->FinishSurface(env, qsdo);
+
+JNF_COCOA_RENDERER_EXIT(env);
+}