Home | History | Annotate | Download | only in apple
      1 // Copyright 2014 PDFium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 // Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com
      6 
      7 #include "core/include/fxcrt/fx_ext.h"
      8 #include "core/include/fxge/fx_freetype.h"
      9 #include "core/include/fxge/fx_ge.h"
     10 #include "core/src/fxge/agg/include/fx_agg_driver.h"
     11 #include "core/src/fxge/dib/dib_int.h"
     12 #include "core/src/fxge/ge/text_int.h"
     13 
     14 #if _FXM_PLATFORM_ == _FXM_PLATFORM_APPLE_
     15 #include "apple_int.h"
     16 #include "core/include/fxge/fx_ge_apple.h"
     17 #ifndef CGFLOAT_IS_DOUBLE
     18 #error Expected CGFLOAT_IS_DOUBLE to be defined by CoreGraphics headers
     19 #endif
     20 
     21 void* CQuartz2D::createGraphics(CFX_DIBitmap* pBitmap) {
     22   if (!pBitmap) {
     23     return NULL;
     24   }
     25   CGBitmapInfo bmpInfo = kCGBitmapByteOrder32Little;
     26   switch (pBitmap->GetFormat()) {
     27     case FXDIB_Rgb32:
     28       bmpInfo |= kCGImageAlphaNoneSkipFirst;
     29       break;
     30     case FXDIB_Argb:
     31     default:
     32       return NULL;
     33   }
     34   CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
     35   CGContextRef context = CGBitmapContextCreate(
     36       pBitmap->GetBuffer(), pBitmap->GetWidth(), pBitmap->GetHeight(), 8,
     37       pBitmap->GetPitch(), colorSpace, bmpInfo);
     38   CGColorSpaceRelease(colorSpace);
     39   return context;
     40 }
     41 void CQuartz2D::destroyGraphics(void* graphics) {
     42   if (graphics) {
     43     CGContextRelease((CGContextRef)graphics);
     44   }
     45 }
     46 void* CQuartz2D::CreateFont(const uint8_t* pFontData, FX_DWORD dwFontSize) {
     47   CGDataProviderRef pDataProvider =
     48       CGDataProviderCreateWithData(NULL, pFontData, (size_t)dwFontSize, NULL);
     49   if (NULL == pDataProvider) {
     50     return NULL;
     51   }
     52   CGFontRef pCGFont = CGFontCreateWithDataProvider(pDataProvider);
     53   CGDataProviderRelease(pDataProvider);
     54   return pCGFont;
     55 }
     56 void CQuartz2D::DestroyFont(void* pFont) {
     57   CGFontRelease((CGFontRef)pFont);
     58 }
     59 void CQuartz2D::setGraphicsTextMatrix(void* graphics, CFX_Matrix* matrix) {
     60   if (!graphics || !matrix) {
     61     return;
     62   }
     63   CGContextRef context = (CGContextRef)graphics;
     64   CGFloat ty = CGBitmapContextGetHeight(context) - matrix->f;
     65   CGContextSetTextMatrix(
     66       context, CGAffineTransformMake(matrix->a, matrix->b, matrix->c, matrix->d,
     67                                      matrix->e, ty));
     68 }
     69 FX_BOOL CQuartz2D::drawGraphicsString(void* graphics,
     70                                       void* font,
     71                                       FX_FLOAT fontSize,
     72                                       FX_WORD* glyphIndices,
     73                                       CGPoint* glyphPositions,
     74                                       int32_t charsCount,
     75                                       FX_ARGB argb,
     76                                       CFX_Matrix* matrix) {
     77   if (!graphics) {
     78     return FALSE;
     79   }
     80   CGContextRef context = (CGContextRef)graphics;
     81   CGContextSetFont(context, (CGFontRef)font);
     82   CGContextSetFontSize(context, fontSize);
     83   if (matrix) {
     84     CGAffineTransform m = CGContextGetTextMatrix(context);
     85     m = CGAffineTransformConcat(
     86         m, CGAffineTransformMake(matrix->a, matrix->b, matrix->c, matrix->d,
     87                                  matrix->e, matrix->f));
     88     CGContextSetTextMatrix(context, m);
     89   }
     90   int32_t a, r, g, b;
     91   ArgbDecode(argb, a, r, g, b);
     92   CGContextSetRGBFillColor(context, r / 255.f, g / 255.f, b / 255.f, a / 255.f);
     93   CGContextSaveGState(context);
     94 #if CGFLOAT_IS_DOUBLE
     95   CGPoint* glyphPositionsCG = new CGPoint[charsCount];
     96   for (int index = 0; index < charsCount; ++index) {
     97     glyphPositionsCG[index].x = glyphPositions[index].x;
     98     glyphPositionsCG[index].y = glyphPositions[index].y;
     99   }
    100 #else
    101   CGPoint* glyphPositionsCG = (CGPoint*)glyphPositions;
    102 #endif
    103   CGContextShowGlyphsAtPositions(context, (CGGlyph*)glyphIndices,
    104                                  glyphPositionsCG, charsCount);
    105 #if CGFLOAT_IS_DOUBLE
    106   delete[] glyphPositionsCG;
    107 #endif
    108   CGContextRestoreGState(context);
    109   return TRUE;
    110 }
    111 void CQuartz2D::saveGraphicsState(void* graphics) {
    112   if (graphics) {
    113     CGContextSaveGState((CGContextRef)graphics);
    114   }
    115 }
    116 void CQuartz2D::restoreGraphicsState(void* graphics) {
    117   if (graphics) {
    118     CGContextRestoreGState((CGContextRef)graphics);
    119   }
    120 }
    121 static CGContextRef createContextWithBitmap(CFX_DIBitmap* pBitmap) {
    122   if (!pBitmap || pBitmap->IsCmykImage() || pBitmap->GetBPP() < 32) {
    123     return NULL;
    124   }
    125   CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Little;
    126   if (pBitmap->HasAlpha()) {
    127     bitmapInfo |= kCGImageAlphaPremultipliedFirst;
    128   } else {
    129     bitmapInfo |= kCGImageAlphaNoneSkipFirst;
    130   }
    131   CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    132   CGContextRef context = CGBitmapContextCreate(
    133       pBitmap->GetBuffer(), pBitmap->GetWidth(), pBitmap->GetHeight(), 8,
    134       pBitmap->GetPitch(), colorSpace, bitmapInfo);
    135   CGColorSpaceRelease(colorSpace);
    136   return context;
    137 }
    138 CFX_QuartzDeviceDriver::CFX_QuartzDeviceDriver(CGContextRef context,
    139                                                int32_t deviceClass) {
    140   m_saveCount = 0;
    141   _context = context;
    142   _deviceClass = deviceClass;
    143   CGContextRetain(_context);
    144   CGRect r = CGContextGetClipBoundingBox(context);
    145   _width = FXSYS_round(r.size.width);
    146   _height = FXSYS_round(r.size.height);
    147   _renderCaps = FXRC_SOFT_CLIP | FXRC_BLEND_MODE | FXRC_ALPHA_PATH |
    148                 FXRC_ALPHA_IMAGE | FXRC_BIT_MASK | FXRC_ALPHA_MASK;
    149   if (_deviceClass != FXDC_DISPLAY) {
    150   } else {
    151     CGImageRef image = CGBitmapContextCreateImage(_context);
    152     if (image) {
    153       _renderCaps |= FXRC_GET_BITS;
    154       _width = CGImageGetWidth(image);
    155       _height = CGImageGetHeight(image);
    156       CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(image);
    157       if (kCGImageAlphaPremultipliedFirst == alphaInfo ||
    158           kCGImageAlphaPremultipliedLast == alphaInfo ||
    159           kCGImageAlphaOnly == alphaInfo) {
    160         _renderCaps |= FXRC_ALPHA_OUTPUT;
    161       }
    162     }
    163     CGImageRelease(image);
    164   }
    165   CGAffineTransform ctm = CGContextGetCTM(_context);
    166   CGContextSaveGState(_context);
    167   m_saveCount++;
    168   if (ctm.d >= 0) {
    169     CGFloat offset_x, offset_y;
    170     offset_x = ctm.tx;
    171     offset_y = ctm.ty;
    172     CGContextTranslateCTM(_context, -offset_x, -offset_y);
    173     CGContextConcatCTM(_context, CGAffineTransformMake(1, 0, 0, -1, offset_x,
    174                                                        _height + offset_y));
    175   }
    176   _foxitDevice2User = CGAffineTransformIdentity;
    177   _user2FoxitDevice = CGAffineTransformInvert(_foxitDevice2User);
    178 }
    179 CFX_QuartzDeviceDriver::~CFX_QuartzDeviceDriver() {
    180   CGContextRestoreGState(_context);
    181   m_saveCount--;
    182   for (int i = 0; i < m_saveCount; ++i) {
    183     CGContextRestoreGState(_context);
    184   }
    185   if (_context) {
    186     CGContextRelease(_context);
    187   }
    188 }
    189 int CFX_QuartzDeviceDriver::GetDeviceCaps(int capsID) {
    190   switch (capsID) {
    191     case FXDC_DEVICE_CLASS: {
    192       return _deviceClass;
    193     }
    194     case FXDC_PIXEL_WIDTH: {
    195       return _width;
    196     }
    197     case FXDC_PIXEL_HEIGHT: {
    198       return _height;
    199     }
    200     case FXDC_BITS_PIXEL: {
    201       return 32;
    202     }
    203     case FXDC_RENDER_CAPS: {
    204       return _renderCaps;
    205     }
    206     default: { return 0; }
    207   }
    208 }
    209 CFX_Matrix CFX_QuartzDeviceDriver::GetCTM() const {
    210   CGAffineTransform ctm = CGContextGetCTM(_context);
    211   return CFX_Matrix(ctm.a, ctm.b, ctm.c, ctm.d, ctm.tx, ctm.ty);
    212 }
    213 void CFX_QuartzDeviceDriver::SaveState() {
    214   CGContextSaveGState(_context);
    215   m_saveCount++;
    216 }
    217 void CFX_QuartzDeviceDriver::RestoreState(FX_BOOL isKeepSaved) {
    218   CGContextRestoreGState(_context);
    219   if (isKeepSaved) {
    220     CGContextSaveGState(_context);
    221   } else {
    222     m_saveCount--;
    223   }
    224 }
    225 FX_BOOL CFX_QuartzDeviceDriver::SetClip_PathFill(const CFX_PathData* pathData,
    226                                                  const CFX_Matrix* matrix,
    227                                                  int fillMode) {
    228   SaveState();
    229   CGAffineTransform m = CGAffineTransformIdentity;
    230   if (matrix) {
    231     m = CGAffineTransformMake(matrix->GetA(), matrix->GetB(), matrix->GetC(),
    232                               matrix->GetD(), matrix->GetE(), matrix->GetF());
    233   }
    234   m = CGAffineTransformConcat(m, _foxitDevice2User);
    235   CGContextConcatCTM(_context, m);
    236   setPathToContext(pathData);
    237   RestoreState(FALSE);
    238   if ((fillMode & 3) == FXFILL_WINDING) {
    239     CGContextClip(_context);
    240   } else {
    241     CGContextEOClip(_context);
    242   }
    243   return TRUE;
    244 }
    245 FX_FLOAT CFX_QuartzDeviceDriver::getLineWidth(
    246     const CFX_GraphStateData* graphState,
    247     CGAffineTransform ctm) {
    248   FX_FLOAT lineWidth = graphState->m_LineWidth;
    249   if (graphState->m_LineWidth <= 0.f) {
    250     CGSize size;
    251     size.width = 1;
    252     size.height = 1;
    253     CGSize temp = CGSizeApplyAffineTransform(size, ctm);
    254     CGFloat x = 1 / temp.width;
    255     CGFloat y = 1 / temp.height;
    256     lineWidth = x > y ? x : y;
    257   }
    258   return lineWidth;
    259 }
    260 FX_BOOL CFX_QuartzDeviceDriver::SetClip_PathStroke(
    261     const CFX_PathData* pathData,
    262     const CFX_Matrix* matrix,
    263     const CFX_GraphStateData* graphState) {
    264   SaveState();
    265   CGAffineTransform m = CGAffineTransformIdentity;
    266   if (matrix) {
    267     m = CGAffineTransformMake(matrix->GetA(), matrix->GetB(), matrix->GetC(),
    268                               matrix->GetD(), matrix->GetE(), matrix->GetF());
    269   }
    270   m = CGAffineTransformConcat(m, _foxitDevice2User);
    271   CGContextConcatCTM(_context, m);
    272   FX_FLOAT lineWidth = getLineWidth(graphState, m);
    273   setStrokeInfo(graphState, 0xFF000000, lineWidth);
    274   setPathToContext(pathData);
    275   CGContextReplacePathWithStrokedPath(_context);
    276   RestoreState(FALSE);
    277   CGContextClip(_context);
    278   return TRUE;
    279 }
    280 static CGBlendMode GetCGBlendMode(int blend_type) {
    281   CGBlendMode mode = kCGBlendModeNormal;
    282   switch (blend_type) {
    283     case FXDIB_BLEND_NORMAL:
    284       mode = kCGBlendModeNormal;
    285       break;
    286     case FXDIB_BLEND_MULTIPLY:
    287       mode = kCGBlendModeMultiply;
    288       break;
    289     case FXDIB_BLEND_SCREEN:
    290       mode = kCGBlendModeScreen;
    291       break;
    292     case FXDIB_BLEND_OVERLAY:
    293       mode = kCGBlendModeOverlay;
    294       break;
    295     case FXDIB_BLEND_DARKEN:
    296       mode = kCGBlendModeDarken;
    297       break;
    298     case FXDIB_BLEND_LIGHTEN:
    299       mode = kCGBlendModeLighten;
    300       break;
    301     case FXDIB_BLEND_COLORDODGE:
    302       mode = kCGBlendModeColorDodge;
    303       break;
    304     case FXDIB_BLEND_COLORBURN:
    305       mode = kCGBlendModeColorBurn;
    306       break;
    307     case FXDIB_BLEND_HARDLIGHT:
    308       mode = kCGBlendModeHardLight;
    309       break;
    310     case FXDIB_BLEND_SOFTLIGHT:
    311       mode = kCGBlendModeSoftLight;
    312       break;
    313     case FXDIB_BLEND_DIFFERENCE:
    314       mode = kCGBlendModeDifference;
    315       break;
    316     case FXDIB_BLEND_EXCLUSION:
    317       mode = kCGBlendModeExclusion;
    318       break;
    319     case FXDIB_BLEND_HUE:
    320       mode = kCGBlendModeHue;
    321       break;
    322     case FXDIB_BLEND_SATURATION:
    323       mode = kCGBlendModeSaturation;
    324       break;
    325     case FXDIB_BLEND_COLOR:
    326       mode = kCGBlendModeColor;
    327       break;
    328     case FXDIB_BLEND_LUMINOSITY:
    329       mode = kCGBlendModeLuminosity;
    330       break;
    331     default:
    332       mode = kCGBlendModeNormal;
    333       break;
    334   }
    335   return mode;
    336 }
    337 FX_BOOL CFX_QuartzDeviceDriver::DrawPath(const CFX_PathData* pathData,
    338                                          const CFX_Matrix* matrix,
    339                                          const CFX_GraphStateData* graphState,
    340                                          FX_DWORD fillArgb,
    341                                          FX_DWORD strokeArgb,
    342                                          int fillMode,
    343                                          int alpha_flag,
    344                                          void* pIccTransform,
    345                                          int blend_type) {
    346   SaveState();
    347   CGBlendMode mode = GetCGBlendMode(blend_type);
    348   if (mode != kCGBlendModeNormal) {
    349     CGContextSetBlendMode(_context, mode);
    350   }
    351   CGAffineTransform m = CGAffineTransformIdentity;
    352   if (matrix) {
    353     m = CGAffineTransformMake(matrix->GetA(), matrix->GetB(), matrix->GetC(),
    354                               matrix->GetD(), matrix->GetE(), matrix->GetF());
    355   }
    356   m = CGAffineTransformConcat(m, _foxitDevice2User);
    357   CGContextConcatCTM(_context, m);
    358   int pathMode = 0;
    359   if (graphState && strokeArgb) {
    360     CGContextSetMiterLimit(_context, graphState->m_MiterLimit);
    361     FX_FLOAT lineWidth = getLineWidth(graphState, m);
    362     setStrokeInfo(graphState, strokeArgb, lineWidth);
    363     pathMode |= 4;
    364   }
    365   if (fillMode && fillArgb) {
    366     setFillInfo(fillArgb);
    367     if ((fillMode & 3) == FXFILL_WINDING) {
    368       pathMode |= 1;
    369     } else if ((fillMode & 3) == FXFILL_ALTERNATE) {
    370       pathMode |= 2;
    371     }
    372   }
    373   setPathToContext(pathData);
    374   if (fillMode & FXFILL_FULLCOVER) {
    375     CGContextSetShouldAntialias(_context, false);
    376   }
    377   if (pathMode == 4) {
    378     CGContextStrokePath(_context);
    379   } else if (pathMode == 1) {
    380     CGContextFillPath(_context);
    381   } else if (pathMode == 2) {
    382     CGContextEOFillPath(_context);
    383   } else if (pathMode == 5) {
    384     CGContextDrawPath(_context, kCGPathFillStroke);
    385   } else if (pathMode == 6) {
    386     CGContextDrawPath(_context, kCGPathEOFillStroke);
    387   }
    388   RestoreState(FALSE);
    389   return TRUE;
    390 }
    391 FX_BOOL CFX_QuartzDeviceDriver::FillRect(const FX_RECT* rect,
    392                                          FX_ARGB fillArgb,
    393                                          int alphaFlag,
    394                                          void* iccTransform,
    395                                          int blend_type) {
    396   CGBlendMode mode = GetCGBlendMode(blend_type);
    397   if (mode != kCGBlendModeNormal) {
    398     CGContextSetBlendMode(_context, mode);
    399   }
    400   CGRect rect_fx =
    401       CGRectMake(rect->left, rect->top, rect->Width(), rect->Height());
    402   CGRect rect_usr = CGRectApplyAffineTransform(rect_fx, _foxitDevice2User);
    403   int32_t a, r, g, b;
    404   ArgbDecode(fillArgb, a, r, g, b);
    405   CGContextSetRGBFillColor(_context, r / 255.f, g / 255.f, b / 255.f,
    406                            a / 255.f);
    407   CGContextFillRect(_context, rect_usr);
    408   if (mode != kCGBlendModeNormal) {
    409     CGContextSetBlendMode(_context, kCGBlendModeNormal);
    410   }
    411   return TRUE;
    412 }
    413 FX_BOOL CFX_QuartzDeviceDriver::DrawCosmeticLine(FX_FLOAT x1,
    414                                                  FX_FLOAT y1,
    415                                                  FX_FLOAT x2,
    416                                                  FX_FLOAT y2,
    417                                                  FX_DWORD argb,
    418                                                  int alphaFlag,
    419                                                  void* iccTransform,
    420                                                  int blend_type) {
    421   CGBlendMode mode = GetCGBlendMode(blend_type);
    422   if (mode != kCGBlendModeNormal) {
    423     CGContextSetBlendMode(_context, mode);
    424   }
    425   CGPoint pt =
    426       CGPointApplyAffineTransform(CGPointMake(x1, y1), _foxitDevice2User);
    427   x1 = pt.x;
    428   y1 = pt.y;
    429   pt = CGPointApplyAffineTransform(CGPointMake(x2, y2), _foxitDevice2User);
    430   x2 = pt.x;
    431   y2 = pt.y;
    432   int32_t a, r, g, b;
    433   ArgbDecode(argb, a, r, g, b);
    434   CGContextSetRGBStrokeColor(_context, r / 255.f, g / 255.f, b / 255.f,
    435                              a / 255.f);
    436   CGContextMoveToPoint(_context, x1, y1);
    437   CGContextAddLineToPoint(_context, x2, y2);
    438   CGContextStrokePath(_context);
    439   if (mode != kCGBlendModeNormal) {
    440     CGContextSetBlendMode(_context, kCGBlendModeNormal);
    441   }
    442   return TRUE;
    443 }
    444 FX_BOOL CFX_QuartzDeviceDriver::GetClipBox(FX_RECT* rect) {
    445   CGRect r = CGContextGetClipBoundingBox(_context);
    446   r = CGRectApplyAffineTransform(r, _user2FoxitDevice);
    447   rect->left = FXSYS_floor(r.origin.x);
    448   rect->top = FXSYS_floor(r.origin.y);
    449   rect->right = FXSYS_ceil(r.origin.x + r.size.width);
    450   rect->bottom = FXSYS_ceil(r.origin.y + r.size.height);
    451   return TRUE;
    452 }
    453 FX_BOOL CFX_QuartzDeviceDriver::GetDIBits(CFX_DIBitmap* bitmap,
    454                                           int32_t left,
    455                                           int32_t top,
    456                                           void* pIccTransform,
    457                                           FX_BOOL bDEdge) {
    458   if (FXDC_PRINTER == _deviceClass) {
    459     return FALSE;
    460   }
    461   if (bitmap->GetBPP() < 32) {
    462     return FALSE;
    463   }
    464   if (!(_renderCaps | FXRC_GET_BITS)) {
    465     return FALSE;
    466   }
    467   CGPoint pt = CGPointMake(left, top);
    468   pt = CGPointApplyAffineTransform(pt, _foxitDevice2User);
    469   CGAffineTransform ctm = CGContextGetCTM(_context);
    470   pt.x *= FXSYS_fabs(ctm.a);
    471   pt.y *= FXSYS_fabs(ctm.d);
    472   CGImageRef image = CGBitmapContextCreateImage(_context);
    473   if (NULL == image) {
    474     return FALSE;
    475   }
    476   CGFloat width = (CGFloat)bitmap->GetWidth();
    477   CGFloat height = (CGFloat)bitmap->GetHeight();
    478   if (width + pt.x > _width) {
    479     width -= (width + pt.x - _width);
    480   }
    481   if (height + pt.y > _height) {
    482     height -= (height + pt.y - _height);
    483   }
    484   CGImageRef subImage = CGImageCreateWithImageInRect(
    485       image, CGRectMake(pt.x, pt.y, width, height));
    486   CGContextRef context = createContextWithBitmap(bitmap);
    487   CGRect rect = CGContextGetClipBoundingBox(context);
    488   CGContextClearRect(context, rect);
    489   CGContextDrawImage(context, rect, subImage);
    490   CGContextRelease(context);
    491   CGImageRelease(subImage);
    492   CGImageRelease(image);
    493   if (bitmap->HasAlpha()) {
    494     for (int row = 0; row < bitmap->GetHeight(); row++) {
    495       uint8_t* pScanline = (uint8_t*)bitmap->GetScanline(row);
    496       for (int col = 0; col < bitmap->GetWidth(); col++) {
    497         if (pScanline[3] > 0) {
    498           pScanline[0] = (pScanline[0] * 255.f / pScanline[3] + .5f);
    499           pScanline[1] = (pScanline[1] * 255.f / pScanline[3] + .5f);
    500           pScanline[2] = (pScanline[2] * 255.f / pScanline[3] + .5f);
    501         }
    502         pScanline += 4;
    503       }
    504     }
    505   }
    506   return TRUE;
    507 }
    508 FX_BOOL CFX_QuartzDeviceDriver::SetDIBits(const CFX_DIBSource* pBitmap,
    509                                           FX_ARGB argb,
    510                                           const FX_RECT* srcRect,
    511                                           int dest_left,
    512                                           int dest_top,
    513                                           int blendType,
    514                                           int alphaFlag,
    515                                           void* iccTransform) {
    516   SaveState();
    517   CGFloat src_left, src_top, src_width, src_height;
    518   if (srcRect) {
    519     src_left = srcRect->left;
    520     src_top = srcRect->top;
    521     src_width = srcRect->Width();
    522     src_height = srcRect->Height();
    523   } else {
    524     src_left = src_top = 0;
    525     src_width = pBitmap->GetWidth();
    526     src_height = pBitmap->GetHeight();
    527   }
    528   CGAffineTransform ctm = CGContextGetCTM(_context);
    529   CGFloat scale_x = FXSYS_fabs(ctm.a);
    530   CGFloat scale_y = FXSYS_fabs(ctm.d);
    531   src_left /= scale_x;
    532   src_top /= scale_y;
    533   src_width /= scale_x;
    534   src_height /= scale_y;
    535   CGRect rect_fx = CGRectMake(dest_left, dest_top, src_width, src_height);
    536   CGRect rect_usr = CGRectApplyAffineTransform(rect_fx, _foxitDevice2User);
    537   CGContextBeginPath(_context);
    538   CGContextAddRect(_context, rect_usr);
    539   CGContextClip(_context);
    540   rect_usr.size =
    541       CGSizeMake(pBitmap->GetWidth() / scale_x, pBitmap->GetHeight() / scale_y);
    542   rect_usr = CGRectOffset(rect_usr, -src_left, -src_top);
    543   CG_SetImageTransform(dest_left, dest_top, src_width, src_height, &rect_usr);
    544   CFX_DIBitmap* pBitmap1 = NULL;
    545   if (pBitmap->IsAlphaMask()) {
    546     if (pBitmap->GetBuffer()) {
    547       pBitmap1 = (CFX_DIBitmap*)pBitmap;
    548     } else {
    549       pBitmap1 = pBitmap->Clone();
    550     }
    551     if (NULL == pBitmap1) {
    552       RestoreState(FALSE);
    553       return FALSE;
    554     }
    555     CGDataProviderRef pBitmapProvider = CGDataProviderCreateWithData(
    556         NULL, pBitmap1->GetBuffer(),
    557         pBitmap1->GetPitch() * pBitmap1->GetHeight(), NULL);
    558     CGColorSpaceRef pColorSpace = CGColorSpaceCreateDeviceGray();
    559     CGBitmapInfo bitmapInfo = kCGImageAlphaNone | kCGBitmapByteOrderDefault;
    560     CGImageRef pImage = CGImageCreate(
    561         pBitmap1->GetWidth(), pBitmap1->GetHeight(), pBitmap1->GetBPP(),
    562         pBitmap1->GetBPP(), pBitmap1->GetPitch(), pColorSpace, bitmapInfo,
    563         pBitmapProvider, NULL, true, kCGRenderingIntentDefault);
    564     CGContextClipToMask(_context, rect_usr, pImage);
    565     CGContextSetRGBFillColor(_context, FXARGB_R(argb) / 255.f,
    566                              FXARGB_G(argb) / 255.f, FXARGB_B(argb) / 255.f,
    567                              FXARGB_A(argb) / 255.f);
    568     CGContextFillRect(_context, rect_usr);
    569     CGImageRelease(pImage);
    570     CGColorSpaceRelease(pColorSpace);
    571     CGDataProviderRelease(pBitmapProvider);
    572     if (pBitmap1 != pBitmap) {
    573       delete pBitmap1;
    574     }
    575     RestoreState(FALSE);
    576     return TRUE;
    577   }
    578   if (pBitmap->GetBPP() < 32) {
    579     pBitmap1 = pBitmap->CloneConvert(FXDIB_Rgb32);
    580   } else {
    581     if (pBitmap->GetBuffer()) {
    582       pBitmap1 = (CFX_DIBitmap*)pBitmap;
    583     } else {
    584       pBitmap1 = pBitmap->Clone();
    585     }
    586   }
    587   if (NULL == pBitmap1) {
    588     RestoreState(FALSE);
    589     return FALSE;
    590   }
    591   if (pBitmap1->HasAlpha()) {
    592     if (pBitmap1 == pBitmap) {
    593       pBitmap1 = pBitmap->Clone();
    594       if (!pBitmap1) {
    595         RestoreState(FALSE);
    596         return FALSE;
    597       }
    598     }
    599     for (int row = 0; row < pBitmap1->GetHeight(); row++) {
    600       uint8_t* pScanline = (uint8_t*)pBitmap1->GetScanline(row);
    601       for (int col = 0; col < pBitmap1->GetWidth(); col++) {
    602         pScanline[0] = (uint8_t)(pScanline[0] * pScanline[3] / 255.f + .5f);
    603         pScanline[1] = (uint8_t)(pScanline[1] * pScanline[3] / 255.f + .5f);
    604         pScanline[2] = (uint8_t)(pScanline[2] * pScanline[3] / 255.f + .5f);
    605         pScanline += 4;
    606       }
    607     }
    608   }
    609   CGContextRef ctx = createContextWithBitmap(pBitmap1);
    610   CGImageRef image = CGBitmapContextCreateImage(ctx);
    611   int blend_mode = blendType;
    612   if (FXDIB_BLEND_HARDLIGHT == blendType) {
    613     blend_mode = kCGBlendModeSoftLight;
    614   } else if (FXDIB_BLEND_SOFTLIGHT == blendType) {
    615     blend_mode = kCGBlendModeHardLight;
    616   } else if (blendType >= FXDIB_BLEND_NONSEPARABLE &&
    617              blendType <= FXDIB_BLEND_LUMINOSITY) {
    618     blend_mode = blendType - 9;
    619   } else if (blendType > FXDIB_BLEND_LUMINOSITY || blendType < 0) {
    620     blend_mode = kCGBlendModeNormal;
    621   }
    622   CGContextSetBlendMode(_context, (CGBlendMode)blend_mode);
    623   CGContextDrawImage(_context, rect_usr, image);
    624   CGImageRelease(image);
    625   CGContextRelease(ctx);
    626   if (pBitmap1 != pBitmap) {
    627     delete pBitmap1;
    628   }
    629   RestoreState(FALSE);
    630   return TRUE;
    631 }
    632 FX_BOOL CFX_QuartzDeviceDriver::StretchDIBits(const CFX_DIBSource* pBitmap,
    633                                               FX_ARGB argb,
    634                                               int dest_left,
    635                                               int dest_top,
    636                                               int dest_width,
    637                                               int dest_height,
    638                                               const FX_RECT* clipRect,
    639                                               FX_DWORD flags,
    640                                               int alphaFlag,
    641                                               void* iccTransform,
    642                                               int blend_type) {
    643   SaveState();
    644   if (clipRect) {
    645     CGContextBeginPath(_context);
    646     CGRect rect_clip = CGRectMake(clipRect->left, clipRect->top,
    647                                   clipRect->Width(), clipRect->Height());
    648     rect_clip = CGRectApplyAffineTransform(rect_clip, _foxitDevice2User);
    649     CGContextAddRect(_context, rect_clip);
    650     CGContextClip(_context);
    651   }
    652   CGRect rect = CGRectMake(dest_left, dest_top, dest_width, dest_height);
    653   rect = CGRectApplyAffineTransform(rect, _foxitDevice2User);
    654   if (FXDIB_BICUBIC_INTERPOL == flags) {
    655     CGContextSetInterpolationQuality(_context, kCGInterpolationHigh);
    656   } else if (FXDIB_DOWNSAMPLE == flags) {
    657     CGContextSetInterpolationQuality(_context, kCGInterpolationNone);
    658   } else {
    659     CGContextSetInterpolationQuality(_context, kCGInterpolationMedium);
    660   }
    661   CG_SetImageTransform(dest_left, dest_top, dest_width, dest_height);
    662   CFX_DIBitmap* pBitmap1 = NULL;
    663   if (pBitmap->IsAlphaMask()) {
    664     if (pBitmap->GetBuffer()) {
    665       pBitmap1 = (CFX_DIBitmap*)pBitmap;
    666     } else {
    667       pBitmap1 = pBitmap->Clone();
    668     }
    669     if (NULL == pBitmap1) {
    670       RestoreState(FALSE);
    671       return FALSE;
    672     }
    673     CGDataProviderRef pBitmapProvider = CGDataProviderCreateWithData(
    674         NULL, pBitmap1->GetBuffer(),
    675         pBitmap1->GetPitch() * pBitmap1->GetHeight(), NULL);
    676     CGColorSpaceRef pColorSpace = CGColorSpaceCreateDeviceGray();
    677     CGBitmapInfo bitmapInfo = kCGImageAlphaNone | kCGBitmapByteOrderDefault;
    678     CGImageRef pImage = CGImageCreate(
    679         pBitmap1->GetWidth(), pBitmap1->GetHeight(), pBitmap1->GetBPP(),
    680         pBitmap1->GetBPP(), pBitmap1->GetPitch(), pColorSpace, bitmapInfo,
    681         pBitmapProvider, NULL, true, kCGRenderingIntentDefault);
    682     CGContextClipToMask(_context, rect, pImage);
    683     CGContextSetRGBFillColor(_context, FXARGB_R(argb) / 255.f,
    684                              FXARGB_G(argb) / 255.f, FXARGB_B(argb) / 255.f,
    685                              FXARGB_A(argb) / 255.f);
    686     CGContextFillRect(_context, rect);
    687     CGImageRelease(pImage);
    688     CGColorSpaceRelease(pColorSpace);
    689     CGDataProviderRelease(pBitmapProvider);
    690     if (pBitmap1 != pBitmap) {
    691       delete pBitmap1;
    692     }
    693     RestoreState(FALSE);
    694     return TRUE;
    695   }
    696   if (pBitmap->GetBPP() < 32) {
    697     pBitmap1 = pBitmap->CloneConvert(FXDIB_Rgb32);
    698   } else {
    699     if (pBitmap->GetBuffer()) {
    700       pBitmap1 = (CFX_DIBitmap*)pBitmap;
    701     } else {
    702       pBitmap1 = pBitmap->Clone();
    703     }
    704   }
    705   if (NULL == pBitmap1) {
    706     RestoreState(FALSE);
    707     return FALSE;
    708   }
    709   if (pBitmap1->HasAlpha()) {
    710     if (pBitmap1 == pBitmap) {
    711       pBitmap1 = pBitmap->Clone();
    712       if (!pBitmap1) {
    713         RestoreState(FALSE);
    714         return FALSE;
    715       }
    716     }
    717     for (int row = 0; row < pBitmap1->GetHeight(); row++) {
    718       uint8_t* pScanline = (uint8_t*)pBitmap1->GetScanline(row);
    719       for (int col = 0; col < pBitmap1->GetWidth(); col++) {
    720         pScanline[0] = (uint8_t)(pScanline[0] * pScanline[3] / 255.f + .5f);
    721         pScanline[1] = (uint8_t)(pScanline[1] * pScanline[3] / 255.f + .5f);
    722         pScanline[2] = (uint8_t)(pScanline[2] * pScanline[3] / 255.f + .5f);
    723         pScanline += 4;
    724       }
    725     }
    726   }
    727   CGContextRef ctx = createContextWithBitmap(pBitmap1);
    728   CGImageRef image = CGBitmapContextCreateImage(ctx);
    729   CGContextDrawImage(_context, rect, image);
    730   CGImageRelease(image);
    731   CGContextRelease(ctx);
    732   if (pBitmap1 != pBitmap) {
    733     delete pBitmap1;
    734   }
    735   RestoreState(FALSE);
    736   return TRUE;
    737 }
    738 FX_BOOL CFX_QuartzDeviceDriver::CG_DrawGlypRun(int nChars,
    739                                                const FXTEXT_CHARPOS* pCharPos,
    740                                                CFX_Font* pFont,
    741                                                CFX_FontCache* pCache,
    742                                                const CFX_Matrix* pGlyphMatrix,
    743                                                const CFX_Matrix* pObject2Device,
    744                                                FX_FLOAT font_size,
    745                                                FX_DWORD argb,
    746                                                int alpha_flag,
    747                                                void* pIccTransform) {
    748   if (nChars == 0) {
    749     return TRUE;
    750   }
    751   CQuartz2D& quartz2d =
    752       ((CApplePlatform*)CFX_GEModule::Get()->GetPlatformData())->_quartz2d;
    753   if (!pFont->GetPlatformFont()) {
    754     if (pFont->GetPsName() == CFX_WideString::FromLocal("DFHeiStd-W5")) {
    755       return FALSE;
    756     }
    757     pFont->SetPlatformFont(
    758         quartz2d.CreateFont(pFont->GetFontData(), pFont->GetSize()));
    759     if (!pFont->GetPlatformFont()) {
    760       return FALSE;
    761     }
    762   }
    763   CFX_FixedBufGrow<FX_WORD, 32> glyph_indices(nChars);
    764   CFX_FixedBufGrow<CGPoint, 32> glyph_positions(nChars);
    765   for (int i = 0; i < nChars; i++) {
    766     glyph_indices[i] = pCharPos[i].m_ExtGID;
    767     glyph_positions[i].x = pCharPos[i].m_OriginX;
    768     glyph_positions[i].y = pCharPos[i].m_OriginY;
    769   }
    770   CFX_Matrix text_matrix;
    771   if (pObject2Device) {
    772     text_matrix.Concat(*pObject2Device);
    773   }
    774   CGAffineTransform matrix_cg =
    775       CGAffineTransformMake(text_matrix.a, text_matrix.b, text_matrix.c,
    776                             text_matrix.d, text_matrix.e, text_matrix.f);
    777   matrix_cg = CGAffineTransformConcat(matrix_cg, _foxitDevice2User);
    778   CGContextSetTextMatrix(_context, matrix_cg);
    779   CGContextSetFont(_context, (CGFontRef)pFont->GetPlatformFont());
    780   CGContextSetFontSize(_context, FXSYS_fabs(font_size));
    781   int32_t a, r, g, b;
    782   ArgbDecode(argb, a, r, g, b);
    783   CGContextSetRGBFillColor(_context, r / 255.f, g / 255.f, b / 255.f,
    784                            a / 255.f);
    785   SaveState();
    786   if (pGlyphMatrix) {
    787     CGPoint origin = CGPointMake(glyph_positions[0].x, glyph_positions[0].y);
    788     origin = CGPointApplyAffineTransform(origin, matrix_cg);
    789     CGContextTranslateCTM(_context, origin.x, origin.y);
    790     CGAffineTransform glyph_matrix = CGAffineTransformMake(
    791         pGlyphMatrix->a, pGlyphMatrix->b, pGlyphMatrix->c, pGlyphMatrix->d,
    792         pGlyphMatrix->e, pGlyphMatrix->f);
    793     if (_foxitDevice2User.d < 0) {
    794       glyph_matrix = CGAffineTransformInvert(glyph_matrix);
    795     }
    796     CGContextConcatCTM(_context, glyph_matrix);
    797     CGContextTranslateCTM(_context, -origin.x, -origin.y);
    798   }
    799   CGContextShowGlyphsAtPositions(_context, (CGGlyph*)glyph_indices,
    800                                  glyph_positions, nChars);
    801   RestoreState(FALSE);
    802   return TRUE;
    803 }
    804 FX_BOOL CFX_QuartzDeviceDriver::DrawDeviceText(int nChars,
    805                                                const FXTEXT_CHARPOS* pCharPos,
    806                                                CFX_Font* pFont,
    807                                                CFX_FontCache* pCache,
    808                                                const CFX_Matrix* pObject2Device,
    809                                                FX_FLOAT font_size,
    810                                                FX_DWORD color,
    811                                                int alpha_flag,
    812                                                void* pIccTransform) {
    813   if (NULL == pFont || NULL == _context) {
    814     return FALSE;
    815   }
    816   FX_BOOL bBold = pFont->IsBold();
    817   if (!bBold && pFont->GetSubstFont() &&
    818       pFont->GetSubstFont()->m_Weight >= 500 &&
    819       pFont->GetSubstFont()->m_Weight <= 600) {
    820     return FALSE;
    821   }
    822   SaveState();
    823   CGContextSetTextDrawingMode(_context, kCGTextFillClip);
    824   FX_BOOL ret = FALSE;
    825   int32_t i = 0;
    826   while (i < nChars) {
    827     if (pCharPos[i].m_bGlyphAdjust || font_size < 0) {
    828       if (i > 0) {
    829         ret = CG_DrawGlypRun(i, pCharPos, pFont, pCache, NULL, pObject2Device,
    830                              font_size, color, alpha_flag, pIccTransform);
    831         if (!ret) {
    832           RestoreState(FALSE);
    833           return ret;
    834         }
    835       }
    836       const FXTEXT_CHARPOS* char_pos = pCharPos + i;
    837       CFX_Matrix glphy_matrix;
    838       if (font_size < 0) {
    839         glphy_matrix.Concat(-1, 0, 0, -1, 0, 0);
    840       }
    841       if (char_pos->m_bGlyphAdjust) {
    842         glphy_matrix.Concat(
    843             char_pos->m_AdjustMatrix[0], char_pos->m_AdjustMatrix[1],
    844             char_pos->m_AdjustMatrix[2], char_pos->m_AdjustMatrix[3], 0, 0);
    845       }
    846       ret = CG_DrawGlypRun(1, char_pos, pFont, pCache, &glphy_matrix,
    847                            pObject2Device, font_size, color, alpha_flag,
    848                            pIccTransform);
    849       if (!ret) {
    850         RestoreState(FALSE);
    851         return ret;
    852       }
    853       i++;
    854       pCharPos += i;
    855       nChars -= i;
    856       i = 0;
    857     } else {
    858       i++;
    859     }
    860   }
    861   if (i > 0) {
    862     ret = CG_DrawGlypRun(i, pCharPos, pFont, pCache, NULL, pObject2Device,
    863                          font_size, color, alpha_flag, pIccTransform);
    864   }
    865   RestoreState(FALSE);
    866   return ret;
    867 }
    868 void CFX_QuartzDeviceDriver::setStrokeInfo(const CFX_GraphStateData* graphState,
    869                                            FX_ARGB argb,
    870                                            FX_FLOAT lineWidth) {
    871   if (NULL == graphState) {
    872     return;
    873   }
    874   CGContextSetLineWidth(_context, lineWidth);
    875   CGLineCap cap;
    876   switch (graphState->m_LineCap) {
    877     case CFX_GraphStateData::LineCapRound: {
    878       cap = kCGLineCapRound;
    879       break;
    880     }
    881     case CFX_GraphStateData::LineCapSquare: {
    882       cap = kCGLineCapSquare;
    883       break;
    884     }
    885     case CFX_GraphStateData::LineCapButt:
    886     default: { cap = kCGLineCapButt; }
    887   }
    888   CGContextSetLineCap(_context, cap);
    889   CGLineJoin join;
    890   switch (graphState->m_LineJoin) {
    891     case CFX_GraphStateData::LineJoinRound: {
    892       join = kCGLineJoinRound;
    893       break;
    894     }
    895     case CFX_GraphStateData::LineJoinBevel: {
    896       join = kCGLineJoinBevel;
    897       break;
    898     }
    899     case CFX_GraphStateData::LineJoinMiter:
    900     default: { join = kCGLineJoinMiter; }
    901   }
    902   CGContextSetLineJoin(_context, join);
    903   if (graphState->m_DashCount) {
    904 #if CGFLOAT_IS_DOUBLE
    905     CGFloat* dashArray = new CGFloat[graphState->m_DashCount];
    906     for (int index = 0; index < graphState->m_DashCount; ++index) {
    907       dashArray[index] = graphState->m_DashArray[index];
    908     }
    909 #else
    910     CGFloat* dashArray = (CGFloat*)graphState->m_DashArray;
    911 #endif
    912     CGContextSetLineDash(_context, graphState->m_DashPhase, dashArray,
    913                          graphState->m_DashCount);
    914 #if CGFLOAT_IS_DOUBLE
    915     delete[] dashArray;
    916 #endif
    917   }
    918   int32_t a, r, g, b;
    919   ArgbDecode(argb, a, r, g, b);
    920   CGContextSetRGBStrokeColor(_context, r / 255.f, g / 255.f, b / 255.f,
    921                              a / 255.f);
    922 }
    923 void CFX_QuartzDeviceDriver::setFillInfo(FX_ARGB argb) {
    924   int32_t a, r, g, b;
    925   ArgbDecode(argb, a, r, g, b);
    926   CGContextSetRGBFillColor(_context, r / 255.f, g / 255.f, b / 255.f,
    927                            a / 255.f);
    928 }
    929 void CFX_QuartzDeviceDriver::setPathToContext(const CFX_PathData* pathData) {
    930   int32_t count = pathData->GetPointCount();
    931   FX_PATHPOINT* points = pathData->GetPoints();
    932   CGContextBeginPath(_context);
    933   for (int32_t i = 0; i < count; i++) {
    934     switch (points[i].m_Flag & FXPT_TYPE) {
    935       case FXPT_MOVETO:
    936         CGContextMoveToPoint(_context, points[i].m_PointX, points[i].m_PointY);
    937         break;
    938       case FXPT_LINETO:
    939         CGContextAddLineToPoint(_context, points[i].m_PointX,
    940                                 points[i].m_PointY);
    941         break;
    942       case FXPT_BEZIERTO: {
    943         CGContextAddCurveToPoint(_context, points[i].m_PointX,
    944                                  points[i].m_PointY, points[i + 1].m_PointX,
    945                                  points[i + 1].m_PointY, points[i + 2].m_PointX,
    946                                  points[i + 2].m_PointY);
    947         i += 2;
    948       }
    949     }
    950     if (points[i].m_Flag & FXPT_CLOSEFIGURE) {
    951       CGContextClosePath(_context);
    952     }
    953   }
    954 }
    955 void CFX_QuartzDeviceDriver::CG_SetImageTransform(int dest_left,
    956                                                   int dest_top,
    957                                                   int dest_width,
    958                                                   int dest_height,
    959                                                   CGRect* rect) {
    960   int flip_y = _foxitDevice2User.d * dest_height < 0 ? 1 : -1;
    961   int flip_x = _foxitDevice2User.a * dest_width > 0 ? 1 : -1;
    962   if (flip_y < 0 || flip_x < 0) {
    963     if (dest_height < 0) {
    964       dest_height = -dest_height;
    965       dest_top -= dest_height;
    966     }
    967     CGRect rt = CGRectApplyAffineTransform(
    968         CGRectMake(dest_left, dest_top, dest_width, dest_height),
    969         _foxitDevice2User);
    970     CGFloat offset_x = (rt.origin.x) + rt.size.width / 2.f,
    971             offset_y = (rt.origin.y) + rt.size.height / 2.f;
    972     CGAffineTransform transform = CGAffineTransformIdentity;
    973     transform = CGAffineTransformConcat(
    974         transform, CGAffineTransformMake(1, 0, 0, 1, -offset_x, -offset_y));
    975     transform = CGAffineTransformConcat(
    976         transform, CGAffineTransformMake(flip_x, 0, 0, flip_y, 0, 0));
    977     transform = CGAffineTransformConcat(
    978         transform, CGAffineTransformMake(1, 0, 0, 1, offset_x, offset_y));
    979     CGContextConcatCTM(_context, transform);
    980     if (rect) {
    981       *rect = CGRectApplyAffineTransform(*rect, transform);
    982     }
    983   }
    984 }
    985 void CFX_QuartzDeviceDriver::ClearDriver() {
    986   if (NULL == _context) {
    987     return;
    988   }
    989   for (int i = 0; i < m_saveCount; ++i) {
    990     CGContextRestoreGState(_context);
    991   }
    992   m_saveCount = 0;
    993   if (_context) {
    994     CGContextRelease(_context);
    995   }
    996 }
    997 CFX_QuartzDevice::CFX_QuartzDevice() {
    998   m_bOwnedBitmap = FALSE;
    999   m_pContext = NULL;
   1000 }
   1001 CFX_QuartzDevice::~CFX_QuartzDevice() {
   1002   if (m_pContext) {
   1003     CGContextRelease(m_pContext);
   1004   }
   1005   if (m_bOwnedBitmap) {
   1006     delete GetBitmap();
   1007   }
   1008 }
   1009 CGContextRef CFX_QuartzDevice::GetContext() {
   1010   return m_pContext;
   1011 }
   1012 FX_BOOL CFX_QuartzDevice::Attach(CGContextRef context, int32_t nDeviceClass) {
   1013   if (m_pContext) {
   1014     CGContextRelease(m_pContext);
   1015   }
   1016   m_pContext = context;
   1017   CGContextRetain(m_pContext);
   1018   IFX_RenderDeviceDriver* pDriver =
   1019       new CFX_QuartzDeviceDriver(m_pContext, nDeviceClass);
   1020   SetDeviceDriver(pDriver);
   1021   return TRUE;
   1022 }
   1023 FX_BOOL CFX_QuartzDevice::Attach(CFX_DIBitmap* pBitmap) {
   1024   SetBitmap(pBitmap);
   1025   m_pContext = createContextWithBitmap(pBitmap);
   1026   if (NULL == m_pContext) {
   1027     return FALSE;
   1028   }
   1029   IFX_RenderDeviceDriver* pDriver =
   1030       new CFX_QuartzDeviceDriver(m_pContext, FXDC_DISPLAY);
   1031   SetDeviceDriver(pDriver);
   1032   return TRUE;
   1033 }
   1034 FX_BOOL CFX_QuartzDevice::Create(int32_t width,
   1035                                  int32_t height,
   1036                                  FXDIB_Format format) {
   1037   if ((uint8_t)format < 32) {
   1038     return FALSE;
   1039   }
   1040   CFX_DIBitmap* pBitmap = new CFX_DIBitmap;
   1041   if (!pBitmap->Create(width, height, format)) {
   1042     delete pBitmap;
   1043     return FALSE;
   1044   }
   1045   m_bOwnedBitmap = TRUE;
   1046   return Attach(pBitmap);
   1047 }
   1048 #endif  // _FXM_PLATFORM_  == _FXM_PLATFORM_APPLE_
   1049