+// Apple's version of GCC automatically defines this symbol.
+// Since XWT is built with a "stock" version of GCC, we need to define
+// this manually to get the system header files to allow us to include them
+#define __APPLE_CPP__ 1
+
+// Standard GCC doesn't support precompiled headers,
+// meaning that it is WAY faster to just include the individual headers
+#include <CoreFoundation/CFString.h>
+#include <HIToolbox/MacWindows.h>
+#include <HIToolbox/MacApplication.h>
+#include <HIToolbox/Scrap.h>
+#include <HIToolbox/Appearance.h>
+#include <HIToolbox/CarbonEvents.h>
+#include <HIToolbox/HIView.h>
+#include <QD/ATSUnicode.h>
+
+#define min(a, b) ((a) < (b) ? (a) : (b))
+#define max(a, b) ((a) < (b) ? (b) : (a))
+
+#define FORMAT_RECT( x ) x.left, x.top, x.right, x.bottom
+#define PRINT_RECT( x ) fprintf( stderr, #x " = (%d, %d) (%d, %d)\n", x.left, x.top, x.right, x.bottom )
+#define PRINT_CGRECT( rect ) fprintf( stderr, #rect " = %f x %f @ (%f, %f)\n", rect.size.width, rect.size.height, rect.origin.x, rect.origin.y );
+
+
+static const EventTypeSpec CarbonSurfaceEventInfo[] = {
+ { kEventClassWindow, kEventWindowUpdate },
+ { kEventClassWindow, kEventWindowBoundsChanged },
+ { kEventClassWindow, kEventWindowActivated },
+ { kEventClassWindow, kEventWindowDeactivated },
+ { kEventClassWindow, kEventWindowClose },
+ { kEventClassWindow, kEventWindowZoomed },
+ { kEventClassWindow, kEventWindowExpanded },
+ { kEventClassWindow, kEventWindowCollapsed },
+
+
+ { kEventClassKeyboard, kEventRawKeyDown },
+ { kEventClassKeyboard, kEventRawKeyRepeat },
+ { kEventClassKeyboard, kEventRawKeyUp },
+ { kEventClassKeyboard, kEventRawKeyModifiersChanged },
+
+
+ { kEventClassMouse, kEventMouseDown },
+ { kEventClassMouse, kEventMouseUp },
+ { kEventClassMouse, kEventMouseMoved },
+ { kEventClassMouse, kEventMouseDragged },
+ { kEventClassMouse, kEventMouseExited },
+
+
+};
+
+static inline UniChar GetCharacterWithoutModifiers( EventRef rawKeyboardEvent )
+{
+ UInt32 keyCode;
+ // Get the key code from the raw key event
+ GetEventParameter( rawKeyboardEvent, kEventParamKeyCode, typeUInt32, NULL, sizeof( keyCode ), NULL, &keyCode );
+
+ // Get the current keyboard layout
+ // TODO: If this is a performance sink, we need to cache these values
+ SInt16 lastKeyLayoutID = GetScriptVariable( /*currentKeyScript*/ GetScriptManagerVariable(smKeyScript), smScriptKeys);
+ Handle uchrHandle = GetResource('uchr', lastKeyLayoutID);
+
+ // Translate the key press ignoring ctrl and option
+ UInt32 ignoredDeadKeys = 0;
+ UInt32 ignoredActualLength = 0;
+ UniChar unicodeKey = 0;
+
+ // We actually want the current shift key value
+ UInt32 modifiers = (GetCurrentEventKeyModifiers() & shiftKey) >> 8;
+ OSStatus err = UCKeyTranslate( reinterpret_cast<UCKeyboardLayout*>( *uchrHandle ), keyCode, kUCKeyActionDown,
+ /* modifierKeyState */ modifiers, LMGetKbdType(), kUCKeyTranslateNoDeadKeysMask, &ignoredDeadKeys,
+ /* buffer length */ 1,
+ /* actual length */ &ignoredActualLength,
+ /* string */ &unicodeKey );
+ assert( err == noErr );
+
+ return unicodeKey;
+}
+
+static CFStringRef JavaToCFString(java::lang::String* s) {
+ int len = min(1024, JvGetStringUTFLength(s));
+
+ char* buffer = (char*) malloc( len );
+ JvGetStringUTFRegion(s, 0, s->length(), buffer);
+
+ // Create a CFString from the UTF8 string
+ CFStringRef string = CFStringCreateWithBytes( NULL, (UInt8*) buffer, len, kCFStringEncodingUTF8, false );
+ assert( string != NULL );
+
+ free( buffer );
+ buffer = NULL;
+
+ return string;
+}
+
+static jstring CFToJavaString( CFStringRef s ) {
+ // TODO: I can probably use CFStringCreateExternalRepresentation
+ // First determine the size of the buffer required
+ const CFRange entireString = CFRangeMake( 0, CFStringGetLength( s ) );
+ CFIndex requiredSize = 0;
+ CFIndex charsConverted = CFStringGetBytes( s, entireString, kCFStringEncodingUTF8, 0, false, NULL, 0, &requiredSize );
+ assert( charsConverted == CFStringGetLength( s ) );
+
+ // Allocate the buffer, plus an additional byte for the null byte
+ UInt8* buffer = (UInt8*) malloc( requiredSize + 1 );
+ assert( buffer != NULL );
+
+ // Convert the string
+ charsConverted = CFStringGetBytes( s, entireString, kCFStringEncodingUTF8, 0, false, buffer, requiredSize, NULL );
+ assert( charsConverted == CFStringGetLength( s ) );
+
+ buffer[requiredSize] = '\0';
+
+ jstring string = JvNewStringUTF( (char*) buffer );
+ assert( string != NULL );
+
+ free( buffer );
+ buffer = NULL;
+
+ return string;
+}
+
+static jstring UniCharToXWTString( UniChar character )
+{
+ switch ( character ) {
+ case '\t': return JvNewStringLatin1("tab");
+ case kEscapeCharCode: return JvNewStringLatin1("escape");
+ case '\n':
+ case kEnterCharCode:
+ case kReturnCharCode:
+ return JvNewStringLatin1("enter");
+ case kBackspaceCharCode: return JvNewStringLatin1("back_space");
+ // escape == clear
+ //case kClearCharCode: return JvNewStringLatin1("clear");
+ //case VK_PAUSE: return JvNewStringLatin1("pause");
+ case kPageUpCharCode: return JvNewStringLatin1("page_up");
+ case kPageDownCharCode: return JvNewStringLatin1("page_down");
+ case kEndCharCode: return JvNewStringLatin1("end");
+ case kHomeCharCode: return JvNewStringLatin1("home");
+ case kLeftArrowCharCode: return JvNewStringLatin1("left");
+ case kUpArrowCharCode: return JvNewStringLatin1("up");
+ case kRightArrowCharCode: return JvNewStringLatin1("right");
+ case kDownArrowCharCode: return JvNewStringLatin1("down");
+ // TODO: Is help always the insert key in the mac world? on my keyboard it is
+ case kHelpCharCode: return JvNewStringLatin1("insert");
+ case kDeleteCharCode: return JvNewStringLatin1("delete");
+ // TODO: How can I determine if a numpad key was pressed?
+ //case VK_NUMPAD0: return JvNewStringLatin1("numpad0");
+ //case VK_NUMPAD1: return JvNewStringLatin1("numpad1");
+ //case VK_NUMPAD2: return JvNewStringLatin1("numpad2");
+ //case VK_NUMPAD3: return JvNewStringLatin1("numpad3");
+ //case VK_NUMPAD4: return JvNewStringLatin1("numpad4");
+ //case VK_NUMPAD5: return JvNewStringLatin1("numpad5");
+ //case VK_NUMPAD6: return JvNewStringLatin1("numpad6");
+ //case VK_NUMPAD7: return JvNewStringLatin1("numpad7");
+ //case VK_NUMPAD8: return JvNewStringLatin1("numpad8");
+ //case VK_NUMPAD9: return JvNewStringLatin1("numpad9");
+ // TODO: How do I get the function key?
+ case kFunctionKeyCharCode: return JvNewStringLatin1("f1");
+ //case VK_F2: return JvNewStringLatin1("f2");
+ //case VK_F3: return JvNewStringLatin1("f3");
+ //case VK_F4: return JvNewStringLatin1("f4");
+ //case VK_F5: return JvNewStringLatin1("f5");
+ //case VK_F6: return JvNewStringLatin1("f6");
+ //case VK_F7: return JvNewStringLatin1("f7");
+ //case VK_F8: return JvNewStringLatin1("f8");
+ //case VK_F9: return JvNewStringLatin1("f9");
+ //case VK_F10: return JvNewStringLatin1("f10");
+ //case VK_F11: return JvNewStringLatin1("f11");
+ //case VK_F12: return JvNewStringLatin1("f12");
+
+ default:
+ // Convert the character into a CFString, then into a java string
+ CFStringRef string = CFStringCreateWithCharactersNoCopy( NULL, &character, 1, kCFAllocatorNull );
+ assert( string != NULL );
+
+ jstring str = CFToJavaString( string );
+
+ CFRelease( string );
+ string = NULL;
+
+ return str;
+ }
+
+ // We should never get here
+ assert( false );
+ return NULL;
+}
+
+static void ModifierKeyTest( org::xwt::plat::Carbon$CarbonSurface* surface, UInt32 changedModifiers, UInt32 currentModifiers, UInt32 keyMask, const char* string )
+{
+ // If the modifier key changed
+ if ( changedModifiers & keyMask ) {
+ jstring str = JvNewStringLatin1( string );
+
+ // And it is currently down, dispatch a keypressed event
+ if ( currentModifiers & keyMask ) surface->KeyPressed( str );
+ // otherwise dispatch a key released event
+ else surface->KeyReleased( str );
+ }
+}
+
+// TODO: Do I have to make the event handlers "pascal", or use "NewEventHandlerUPP"?
+pascal static OSStatus CarbonSurfaceEventHandler( EventHandlerCallRef handler, EventRef event, void* data ) {
+ UInt32 eventClass = GetEventClass( event );
+ UInt32 eventKind = GetEventKind( event );
+ OSStatus result = eventNotHandledErr;
+ org::xwt::plat::Carbon$CarbonSurface* surface = (org::xwt::plat::Carbon$CarbonSurface*)data;
+
+ //JvSynchronize dummy( surface );
+
+ switch(eventClass) {
+ case kEventClassWindow:
+ switch(eventKind)
+ {
+ case kEventWindowUpdate:
+ {
+ //fprintf( stderr, "window update " );
+
+ // Create the graphics context
+ result = CreateCGContextForPort(GetWindowPort( (WindowRef) surface->window), (CGContextRef*) &surface->gc);
+ assert( result == noErr && surface->gc != NULL );
+
+ // Quartz uses the bottom left as its origin. Translate to use the top left.
+ Rect bounds;
+ GetWindowBounds( (WindowRef) surface->window, kWindowContentRgn, &bounds );
+ assert( result == noErr );
+ CGContextTranslateCTM( (CGContextRef) surface->gc, 0, bounds.bottom - bounds.top );
+ CGContextScaleCTM( (CGContextRef) surface->gc, 1, -1 );
+
+ // Convert the context to a CGRect with local coordinates
+ CGRect contentBounds = CGRectMake( 0, 0, bounds.right - bounds.left, bounds.bottom - bounds.top );
+
+ //PRINT_CGRECT( contentBounds );
+
+ // Get the invalid region
+ Rect globalInvalidRect;
+ result = GetWindowBounds( (WindowRef) surface->window, kWindowUpdateRgn, &globalInvalidRect );
+ assert( result == noErr );
+
+ CGRect invalidRect = CGRectMake( globalInvalidRect.left - bounds.left, globalInvalidRect.top - bounds.top, globalInvalidRect.right - globalInvalidRect.left, globalInvalidRect.bottom - globalInvalidRect.top );
+
+ //invalidRect = CGRectIntersection( invalidRect, contentBounds );
+
+ //PRINT_CGRECT( invalidRect );
+
+ BeginUpdate( (WindowRef) surface->window );
+
+ // Erase the region with white
+ // TODO: Draw the window background instead
+ //result = SetThemeWindowBackground( (WindowRef) surface->window, ThemeBrush inBrush, true );
+ CGContextSetRGBFillColor( (CGContextRef) surface->gc, 1.0, 1.0, 1.0, 1.0 );
+ CGContextFillRect( (CGContextRef) surface->gc, invalidRect );
+
+ //CGContextSetRGBFillColor( (CGContextRef) surface->gc, red, green, blue, 0.5 );
+ //CGContextFillRect( (CGContextRef) surface->gc, CGRectMake( 0, 0, (bounds.right - bounds.left), (bounds.bottom - bounds.top) ) );
+ // Add the invalid region to XWT's dirty regions
+ surface->Dirty( (jint) invalidRect.origin.x, (jint) invalidRect.origin.y, (jint) invalidRect.size.width, (jint) invalidRect.size.height );
+ // Then tell it to actually do the drawing
+ surface->blitDirtyScreenRegions();
+
+ EndUpdate( (WindowRef) surface->window );
+
+ // Free the context
+ CGContextFlush( (CGContextRef) surface->gc );
+ CGContextRelease( (CGContextRef) surface->gc );
+ surface->gc = NULL;
+ result = noErr;
+ break;
+ }
+
+ case kEventWindowBoundsChanged:
+ {
+ // We can't use the event parameter: it is the window bounds, not the content area bounds
+ HIRect currentBounds;
+ result = GetEventParameter( event, kEventParamCurrentBounds, typeHIRect, NULL, sizeof( currentBounds ), NULL, ¤tBounds );
+ assert( result == noErr );
+
+ // Get the event attributes to determine if size and/or origin have changed
+ UInt32 attributes;
+ result = GetEventParameter( event, kEventParamAttributes, typeUInt32, NULL, sizeof( attributes ), NULL, &attributes );
+ assert( result == noErr );
+
+ if ( attributes & kWindowBoundsChangeSizeChanged ) {
+ // If maximize is set, unset it
+ if ( attributes & kWindowBoundsChangeUserResize && surface->maximized ) {
+ //fprintf( stderr, "maximized = 0\n" );
+ surface->Maximized( false );
+ }
+
+ //fprintf( stderr, "size changed\n" );
+ surface->SizeChange( (jint) currentBounds.size.width, (jint) currentBounds.size.height );
+
+
+ }
+
+ if ( attributes & kWindowBoundsChangeOriginChanged ) {
+ //fprintf( stderr, "origin changed\n" );
+ surface->PosChange( (jint) currentBounds.origin.x, (jint) currentBounds.origin.y );
+ }
+
+ result = noErr;
+ break;
+ }
+
+ case kEventWindowActivated:
+ case kEventWindowDeactivated:
+ {
+ //fprintf( stderr, "focus = %d\n", (eventKind == kEventWindowActivated) );
+ surface->Focused( (eventKind == kEventWindowActivated) );
+ result = noErr;
+ break;
+ }
+
+ case kEventWindowZoomed:
+ {
+ // Toggle maximized whenever we recieve this event
+ //fprintf( stderr, "maximized: %d\n", ! surface->maximized );
+ surface->Maximized( ! surface->maximized );
+ result = noErr;
+ break;
+ }
+
+ case kEventWindowCollapsed:
+ {
+ //fprintf( stderr, "minimized true\n" );
+ surface->Minimized( true );
+ result = noErr;
+ break;
+ }
+
+ case kEventWindowExpanded:
+ {
+ //fprintf( stderr, "minimized false\n" );
+ surface->Minimized( false );
+ result = noErr;
+ break;
+ }
+
+ case kEventWindowClose:
+ {
+ //fprintf( stderr, "close\n" );
+ surface->Close();
+ result = noErr;
+ break;
+ }
+ default:
+ throw new java::lang::Error(JvNewStringLatin1("Unhandled Window Event"));
+ }
+ break;
+
+ case kEventClassMouse:
+ {
+ HIPoint where;
+ result = GetEventParameter( event, kEventParamMouseLocation, typeHIPoint, NULL, sizeof(where), NULL, &where );
+ assert( result == noErr );
+
+ // Create a hit test event to ask the standard window event handler where this press is
+ EventRef hitTest;
+ result = CreateEvent( NULL, kEventClassWindow, kEventWindowHitTest, 0, kEventAttributeNone, &hitTest );
+ assert( result == noErr );
+
+ result = SetEventParameter( hitTest, kEventParamMouseLocation, typeHIPoint, sizeof(where), &where );
+ assert( result == noErr );
+
+ result = SetEventParameter( hitTest, kEventParamDirectObject, typeWindowRef, sizeof(surface->window), &surface->window );
+ assert( result == noErr );
+
+ result = SendEventToEventTarget( hitTest, GetWindowEventTarget( (WindowRef) surface->window ) );
+ assert( result == noErr );
+
+ WindowDefPartCode part;
+ result = GetEventParameter( hitTest, kEventParamWindowDefPart, typeWindowDefPartCode, NULL, sizeof(part), NULL, &part );
+ assert( result == noErr );
+
+ ReleaseEvent( hitTest );
+ hitTest = NULL;
+
+ // ignore the event if it is not in the content region.
+ if ( part != wInContent )
+ {
+ return eventNotHandledErr;
+ }
+
+ // TODO: Can I get the content region's bounds as an HIRect?
+ Rect bounds;
+ GetWindowBounds( (WindowRef) surface->window, kWindowContentRgn, &bounds );
+ assert( result == noErr );
+ // Convert the context to a CGRect with local coordinates
+ CGRect contentBounds = CGRectMake( bounds.left, bounds.top, bounds.right - bounds.left, bounds.bottom - bounds.top );
+
+ // Now convert the point to content area relative coordinates:
+ where.x -= contentBounds.origin.x;
+ where.y -= contentBounds.origin.y;
+
+ switch ( eventKind )
+ {
+ case kEventMouseDown:
+ {
+ //fprintf( stderr, "mouse down (%f, %f)\n", where.x, where.y );
+ EventMouseButton mouseButton;
+ result = GetEventParameter( event, kEventParamMouseButton, typeMouseButton, NULL, sizeof(mouseButton), NULL, &mouseButton );
+ assert( result == noErr );
+
+ surface->Press( mouseButton );
+ result = eventNotHandledErr;
+ break;
+ }
+
+ case kEventMouseUp:
+ {
+ //fprintf( stderr, "mouse up (%f, %f)\n", where.x, where.y );
+ EventMouseButton mouseButton;
+ result = GetEventParameter( event, kEventParamMouseButton, typeMouseButton, NULL, sizeof(mouseButton), NULL, &mouseButton );
+ assert( result == noErr );
+
+ UInt32 clickCount;
+ result = GetEventParameter( event, kEventParamClickCount, typeUInt32, NULL, sizeof(clickCount), NULL, &clickCount );
+ assert( result == noErr );
+
+ surface->Release( mouseButton );
+
+ // deliver click/double click events
+ if ( clickCount == 1 ) {
+ //fprintf( stderr, "click (%f, %f)\n", where.x, where.y );
+ surface->Click( mouseButton );
+
+ //result = ActivateWindow( (WindowRef) surface->window, true );
+ //assert( result == noErr );
+
+ //ProcessSerialNumber psn = { 0, kCurrentProcess };
+ //result = SetFrontProcess( &psn );
+ //assert( result == noErr );
+ } else if ( clickCount == 2 ) {
+ //fprintf( stderr, "double click (%f, %f)\n", where.x, where.y );
+ surface->DoubleClick( mouseButton );
+ }
+
+ result = noErr;
+ break;
+ }
+
+ case kEventMouseMoved:
+ case kEventMouseDragged:
+ {
+ surface->Move( (jint) where.x, (jint) where.y );
+ result = noErr;
+ //fprintf( stderr, "mousemoved (%f, %f)\n", where.x, where.y );
+ break;
+ }
+
+ /*case kEventMouseExited:
+ {
+ // Move the mouse to (-1, -1) when it exits the window
+ surface->Move( -1, -1 );
+ result = noErr;
+ fprintf( stderr, "mouse exited\n" );
+ break;
+ }*/
+
+
+ /*case kEventMouseWheelMoved:
+ {
+ EventMouseWheelAxis inAxis;
+ result = inEvent.GetParameter<EventMouseWheelAxis>( kEventParamMouseWheelAxis, typeMouseWheelAxis, &inAxis );
+ assert( noErr == result );
+
+ SInt32 inDelta;
+ result = inEvent.GetParameter<SInt32>( kEventParamMouseWheelDelta, typeSInt32, &inDelta );
+ assert( noErr == result );
+
+ result = MouseWheelMoved( inAxis, inDelta, inKeyModifiers );
+ break;
+ }
+ */
+
+
+ // some other kind of Mouse event: This is (in my opinion) an error
+ default:
+ assert( false );
+ break;
+ }
+ break;
+ }
+
+
+ case kEventClassKeyboard:
+ switch (eventKind)
+ {
+ case kEventRawKeyDown:
+ case kEventRawKeyRepeat:
+ {
+ // TODO: This breaks international input
+ UniChar character = GetCharacterWithoutModifiers( event );
+ surface->KeyPressed( UniCharToXWTString( character ) );
+
+ result = noErr;
+ break;
+ }
+
+ case kEventRawKeyUp:
+ {
+ UniChar character = GetCharacterWithoutModifiers( event );
+ surface->KeyReleased( UniCharToXWTString( character ) );
+
+ result = noErr;
+ break;
+ }
+
+ case kEventRawKeyModifiersChanged:
+ {
+ static UInt32 previousModifiers = 0;
+ UInt32 currentModifiers = GetCurrentEventKeyModifiers();
+ UInt32 changedModifiers = previousModifiers ^ currentModifiers;
+
+ ModifierKeyTest( surface, changedModifiers, currentModifiers, shiftKey, "shift" );
+ ModifierKeyTest( surface, changedModifiers, currentModifiers, alphaLock, "caps_lock" );
+ ModifierKeyTest( surface, changedModifiers, currentModifiers, controlKey, "control" );
+ ModifierKeyTest( surface, changedModifiers, currentModifiers, optionKey, "alt" );
+ ModifierKeyTest( surface, changedModifiers, currentModifiers, kEventKeyModifierNumLockMask, "num_lock" );
+
+ previousModifiers = currentModifiers;
+ result = noErr;
+ break;
+ }
+
+ default:
+ assert( false );
+ break;
+ }
+ break;
+
+ default:
+ throw new java::lang::Error(JvNewStringLatin1("Unhandled Window Event"));
+ }
+
+ return result;
+}
+
+/** Creates a next layout from string and style. If unicodeBuffer is not null, the caller is responsable for releasing it. */
+static ATSUTextLayout CreateTextLayout( CFStringRef string, ATSUStyle style, CFStringRef* unicodeBuffer ) {
+ assert( string != NULL && unicodeBuffer != NULL && *unicodeBuffer == NULL && style != NULL );
+
+ // Create layout
+ ATSUTextLayout layout;
+ OSStatus result = ATSUCreateTextLayout( &layout );
+ assert( result == noErr && layout != NULL );
+
+ // Try to get the unicode buffer directly
+ CFIndex unicodeLength = CFStringGetLength( string );
+ assert( unicodeLength != 0 );
+ const UniChar* unicodeString = CFStringGetCharactersPtr( string );
+ if ( unicodeString == NULL )
+ {
+ // That failed. Allocate a buffer using CFAllocator so the CFString can own it
+ UniChar* buffer = (UniChar*) CFAllocatorAllocate( NULL, unicodeLength * sizeof( UniChar ), 0 );
+ assert( buffer != NULL );
+
+ // Get a copy of the text
+ CFStringGetCharacters( string, CFRangeMake( 0, unicodeLength ), buffer );
+
+ // Create a CFString which wraps and takes ownership of the buffer
+ *unicodeBuffer = CFStringCreateWithCharactersNoCopy( NULL, buffer, unicodeLength, NULL );
+ assert( *unicodeBuffer != NULL );
+
+ unicodeString = buffer;
+ }
+
+ assert( unicodeString != NULL );
+ //UniChar* unicode_string = malloc( sizeof( UniChar ) * unicode_length );
+ //CFStringGetCharacters( string, CFRangeMake( 0, unicode_length ), unicode_string );
+ ATSUSetTextPointerLocation( layout, unicodeString, kATSUFromTextBeginning, kATSUToTextEnd, unicodeLength );
+
+ // Set the style
+ result = ATSUSetRunStyle( layout, style, kATSUFromTextBeginning, kATSUToTextEnd );
+ assert( result == noErr );
+
+ // Using this setting makes rendering match TextEdit's (except for the antialiasing rules)
+ ATSLineLayoutOptions rendering = kATSLineFractDisable; //kATSLineUseDeviceMetrics;
+ ATSUAttributeTag tag = kATSULineLayoutOptionsTag;
+ ByteCount size = sizeof( rendering );
+ ATSUAttributeValuePtr value = &rendering;
+ result = ATSUSetLayoutControls( layout, 1, &tag, &size, &value );
+ assert( noErr == result );
+
+ // Allow ATSUI to use its default font fallbacks for Unicode. This means that the font may not
+ // match exactly, but it will display the characters if it can.
+ result = ATSUSetTransientFontMatching( layout, true );
+ assert( noErr == result );
+
+ return layout;
+}
+
+static void SetARGBFillColor( CGContextRef gc, jint argb )
+{
+ uint8_t* colour = (uint8_t*) &argb;
+ float alpha = colour[0] / 255.0;
+ float red = colour[1] / 255.0;
+ float green = colour[2] / 255.0;
+ float blue = colour[3] / 255.0;
+
+ CGContextSetRGBFillColor( gc, red, green, blue, alpha );
+}