Skip to content

Commit 211478d

Browse files
author
murrell
committed
add variable font support to quartz()
git-svn-id: https://svn.r-project.org/R/trunk@88317 00db46b3-68df-0310-9c12-caf00c1e9a41
1 parent acb48e8 commit 211478d

File tree

2 files changed

+125
-5
lines changed

2 files changed

+125
-5
lines changed

doc/NEWS.Rd

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@
5959
argument, which allows variable font axes to be specified,
6060
e.g., \code{c(wght = 100)}.
6161
62-
Only Cairo-based graphics devices (but not \code{cairo_pdf()})
63-
support variable font axes so far.
62+
Only the \code{quartz()} device and Cairo-based graphics devices
63+
(but not \code{cairo_pdf()}) support variable font axes so far.
6464
}
6565
}
6666

src/library/grDevices/src/devQuartz.c

Lines changed: 123 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -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

29612961
static 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+
30243131
void 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

Comments
 (0)