@@ -1451,7 +1451,7 @@ void* QuartzDevice_Create(void *_dev, QuartzBackend_t *def)
14511451    dev -> fillStroke       =  RQuartz_fillStroke ;
14521452    dev -> capabilities     =  RQuartz_capabilities ;
14531453    dev -> glyph            =  RQuartz_glyph ;
1454-     dev -> deviceVersion    =  R_GE_glyphs ;
1454+     dev -> deviceVersion    =  R_GE_fontVar ;
14551455
14561456    QuartzDesc  * qd  =  calloc (1 , sizeof (QuartzDesc ));
14571457    qd -> width       =  def -> width ;
@@ -2959,7 +2959,8 @@ static void RQuartz_fillStroke(SEXP path, int rule, const pGEcontext gc,
29592959}
29602960
29612961static  SEXP  RQuartz_capabilities (SEXP  capabilities ) { 
2962-     SEXP  patterns , clippingPaths , masks , compositing , transforms , paths ;
2962+     SEXP  patterns , clippingPaths , masks , compositing , transforms , paths ,
2963+         glyphs , variableFonts ;
29632964
29642965    PROTECT (patterns  =  allocVector (INTSXP , 3 ));
29652966    INTEGER (patterns )[0 ] =  R_GE_linearGradientPattern ;
@@ -3018,9 +3019,115 @@ static SEXP RQuartz_capabilities(SEXP capabilities) {
30183019    SET_VECTOR_ELT (capabilities , R_GE_capability_paths , paths );
30193020    UNPROTECT (1 );
30203021
3022+     PROTECT (glyphs  =  allocVector (INTSXP , 1 ));
3023+     INTEGER (glyphs )[0 ] =  1 ;
3024+     SET_VECTOR_ELT (capabilities , R_GE_capability_glyphs , glyphs );
3025+     UNPROTECT (1 );
3026+ 
3027+     PROTECT (variableFonts  =  allocVector (INTSXP , 1 ));
3028+     INTEGER (variableFonts )[0 ] =  1 ;
3029+     SET_VECTOR_ELT (capabilities , R_GE_capability_variableFonts , variableFonts );
3030+     UNPROTECT (1 );
3031+ 
30213032    return  capabilities ; 
30223033}
30233034
3035+ /* Core Text appears to localize variable font axis names (?!) 
3036+  * e.g., 'wght' -> "Weight" 
3037+  * So we need to do the same in order to perform comparisons. 
3038+  * Although there are calls for localizing various font names 
3039+  * and attribute names (e.g., CTFontCopyLocalizedName), I could 
3040+  * not see a call for localizing font axis names, so will just 
3041+  * use the registered names 
3042+  * https://learn.microsoft.com/en-us/typography/opentype/spec/dvaraxisreg#registered-axis-tags 
3043+  * This may fail on non-english locales. 
3044+  * For non-registered axes, it appears we get first char uppercase, but 
3045+  * remainder lower case (!), so we ignore case in the comparison. 
3046+  */ 
3047+ const  char  * registeredAxisNames [5 ] =  { "ital" , "opsz" , "slnt" , "wdth" , "wght"  };
3048+ const  char  * localAxisNames [5 ] =  { "Italic" , "Optical Size" , "Slant" , "Width" , "Weight"  };
3049+ static  int  axisNameMatch (const  char *  axisName , CFStringRef  axisDictName )
3050+ {
3051+     int  i , j , axisIndex  =  -1 , axisDictIndex  =  -1 ;
3052+     for  (i  =  0 ; i  <  5 ; i ++ ) {
3053+         if  (!strcmp (axisName , registeredAxisNames [i ])) 
3054+             axisIndex  =  i ;
3055+     }
3056+     if  (axisIndex  <  0 ) {
3057+         /* Custom axis */ 
3058+         CFStringRef  axisNameRef  =  
3059+             CFStringCreateWithCString (NULL , axisName , 
3060+                                       kCFStringEncodingASCII );
3061+         return  CFStringCompare (axisNameRef , axisDictName , 
3062+                                kCFCompareCaseInsensitive ) ==  
3063+             kCFCompareEqualTo ;
3064+     } else  {
3065+         /* Registered axis */ 
3066+         for  (j  =  0 ; j  <  5 ; j ++ ) {
3067+             CFStringRef  localNameRef  =  
3068+                 CFStringCreateWithCString (NULL , localAxisNames [j ], 
3069+                                           kCFStringEncodingASCII );
3070+             if  (CFStringCompare (localNameRef , axisDictName , 0 ) ==  
3071+                 kCFCompareEqualTo ) 
3072+                 axisDictIndex  =  j ;
3073+         }
3074+         return  axisIndex  ==  axisDictIndex ;
3075+     }
3076+ }
3077+ 
3078+ static  CTFontDescriptorRef  applyFontVar (CTFontRef  ctFont ,
3079+                                         CTFontDescriptorRef  ctFontDescriptor ,
3080+                                         SEXP  font ,
3081+                                         int  numVar ) 
3082+ {
3083+     int  i , j ;
3084+     CTFontDescriptorRef  curFontDescriptor , newFontDescriptor ;
3085+     curFontDescriptor  =  ctFontDescriptor ;
3086+     newFontDescriptor  =  ctFontDescriptor ;
3087+     /* Get the variation axis dictionary from the font */ 
3088+     CFArrayRef  ctFontVariationAxes  =  CTFontCopyVariationAxes (ctFont );
3089+     if  (ctFontVariationAxes ) {
3090+         CFIndex  numAxes  =  CFArrayGetCount (ctFontVariationAxes );
3091+         for  (i  =  0 ; i  <  numVar ; i ++ ) {
3092+             /* Find the relevant variation axis identifier from  
3093+                the dictionary */ 
3094+             const  char *  axisName  =  R_GE_glyphFontVarAxis (font , i );
3095+             CFNumberRef  axisID ;
3096+             int  found  =  0 ;
3097+             for  (j  =  0 ; j  <  numAxes ; j ++ ) {
3098+                 CFDictionaryRef  axisDict  =  
3099+                     CFArrayGetValueAtIndex (ctFontVariationAxes , j );
3100+                 CFStringRef  axisDictName  =  
3101+                     CFDictionaryGetValue (axisDict , 
3102+                                          kCTFontVariationAxisNameKey );
3103+                 if  (axisNameMatch (axisName , axisDictName )) {
3104+                     axisID  =  
3105+                         CFDictionaryGetValue (axisDict , 
3106+                                              kCTFontVariationAxisIdentifierKey );
3107+                     found  =  1 ;
3108+                 }
3109+             }
3110+             if  (found ) {
3111+                 CGFloat  axisValue  =  R_GE_glyphFontVarValue (font , i );
3112+                 /* Set the variation axis */ 
3113+                 newFontDescriptor  =  
3114+                     CTFontDescriptorCreateCopyWithVariation (curFontDescriptor ,
3115+                                                             axisID ,
3116+                                                             axisValue );
3117+                 /* If we did not just modify the original font descriptor, 
3118+                  * release the font descriptor that we just modified. 
3119+                  */ 
3120+                 if  (curFontDescriptor  !=  ctFontDescriptor ) {
3121+                     CFRelease (curFontDescriptor );
3122+                 }
3123+                 curFontDescriptor  =  newFontDescriptor ;
3124+             }
3125+         }
3126+         CFRelease (ctFontVariationAxes );
3127+     }
3128+     return  newFontDescriptor ;
3129+ }
3130+ 
30243131void  RQuartz_glyph (int  n , int  * glyphs , double  * x , double  * y , 
30253132                   SEXP  font , double  size ,
30263133                   int  colour , double  rot , pDevDesc  dd )
@@ -3047,13 +3154,26 @@ void RQuartz_glyph(int n, int *glyphs, double *x, double *y,
30473154        CTFontManagerCreateFontDescriptorsFromURL (cfFontURL );
30483155    CFRelease (cfFontURL );
30493156    if  (cfFontDescriptors ) {
3157+         CTFontDescriptorRef  ctFontDescriptor ;
3158+         ctFontDescriptor  =  
3159+             (CTFontDescriptorRef ) CFArrayGetValueAtIndex (cfFontDescriptors , 0 );
30503160        /* NOTE: that the font needs an inversion (in y) matrix 
30513161           because the device has an inversion in user space  
30523162           (for bitmap devices anyway) */ 
30533163        CGAffineTransform  trans  =  CGAffineTransformMakeScale (1.0 , -1.0 );
30543164        if  (rot  !=  0.0 ) trans  =  CGAffineTransformRotate (trans , rot /180. * M_PI );
30553165        CTFontRef  ctFont  =  
3056-             CTFontCreateWithFontDescriptor ((CTFontDescriptorRef ) CFArrayGetValueAtIndex (cfFontDescriptors , 0 ), size , & trans );
3166+             CTFontCreateWithFontDescriptor (ctFontDescriptor , size , & trans );
3167+ 
3168+         /* Apply font variations, if any */ 
3169+         int  numVar  =  R_GE_glyphFontNumVar (font );
3170+         if  (numVar  >  0 ) {
3171+             ctFontDescriptor  =  applyFontVar (ctFont , ctFontDescriptor , 
3172+                                             font , numVar );
3173+             CFRelease (ctFont );
3174+             ctFont  =  CTFontCreateWithFontDescriptor (ctFontDescriptor , 
3175+                                                     size , & trans );
3176+         }
30573177
30583178        CGColorSpaceRef  cs  =  CGColorSpaceCreateWithName (kCGColorSpaceSRGB );
30593179        CGFloat  fillColor [] =  { R_RED (colour )/255.0 , 
0 commit comments