diff options
Diffstat (limited to 'src/libui_sdl/libui/darwin/drawtext.m')
-rw-r--r-- | src/libui_sdl/libui/darwin/drawtext.m | 655 |
1 files changed, 655 insertions, 0 deletions
diff --git a/src/libui_sdl/libui/darwin/drawtext.m b/src/libui_sdl/libui/darwin/drawtext.m new file mode 100644 index 0000000..c376536 --- /dev/null +++ b/src/libui_sdl/libui/darwin/drawtext.m @@ -0,0 +1,655 @@ +// 6 september 2015 +#import "uipriv_darwin.h" + +// TODO +#define complain(...) implbug(__VA_ARGS__) + +// TODO double-check that we are properly handling allocation failures (or just toll free bridge from cocoa) +struct uiDrawFontFamilies { + CFArrayRef fonts; +}; + +uiDrawFontFamilies *uiDrawListFontFamilies(void) +{ + uiDrawFontFamilies *ff; + + ff = uiNew(uiDrawFontFamilies); + ff->fonts = CTFontManagerCopyAvailableFontFamilyNames(); + if (ff->fonts == NULL) + implbug("error getting available font names (no reason specified) (TODO)"); + return ff; +} + +int uiDrawFontFamiliesNumFamilies(uiDrawFontFamilies *ff) +{ + return CFArrayGetCount(ff->fonts); +} + +char *uiDrawFontFamiliesFamily(uiDrawFontFamilies *ff, int n) +{ + CFStringRef familystr; + char *family; + + familystr = (CFStringRef) CFArrayGetValueAtIndex(ff->fonts, n); + // toll-free bridge + family = uiDarwinNSStringToText((NSString *) familystr); + // Get Rule means we do not free familystr + return family; +} + +void uiDrawFreeFontFamilies(uiDrawFontFamilies *ff) +{ + CFRelease(ff->fonts); + uiFree(ff); +} + +struct uiDrawTextFont { + CTFontRef f; +}; + +uiDrawTextFont *mkTextFont(CTFontRef f, BOOL retain) +{ + uiDrawTextFont *font; + + font = uiNew(uiDrawTextFont); + font->f = f; + if (retain) + CFRetain(font->f); + return font; +} + +uiDrawTextFont *mkTextFontFromNSFont(NSFont *f) +{ + // toll-free bridging; we do retain, though + return mkTextFont((CTFontRef) f, YES); +} + +static CFMutableDictionaryRef newAttrList(void) +{ + CFMutableDictionaryRef attr; + + attr = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + if (attr == NULL) + complain("error creating attribute dictionary in newAttrList()()"); + return attr; +} + +static void addFontFamilyAttr(CFMutableDictionaryRef attr, const char *family) +{ + CFStringRef cfstr; + + cfstr = CFStringCreateWithCString(NULL, family, kCFStringEncodingUTF8); + if (cfstr == NULL) + complain("error creating font family name CFStringRef in addFontFamilyAttr()"); + CFDictionaryAddValue(attr, kCTFontFamilyNameAttribute, cfstr); + CFRelease(cfstr); // dictionary holds its own reference +} + +static void addFontSizeAttr(CFMutableDictionaryRef attr, double size) +{ + CFNumberRef n; + + n = CFNumberCreate(NULL, kCFNumberDoubleType, &size); + CFDictionaryAddValue(attr, kCTFontSizeAttribute, n); + CFRelease(n); +} + +#if 0 +TODO +// See http://stackoverflow.com/questions/4810409/does-coretext-support-small-caps/4811371#4811371 and https://git.gnome.org/browse/pango/tree/pango/pangocoretext-fontmap.c for what these do +// And fortunately, unlike the traits (see below), unmatched features are simply ignored without affecting the other features :D +static void addFontSmallCapsAttr(CFMutableDictionaryRef attr) +{ + CFMutableArrayRef outerArray; + CFMutableDictionaryRef innerDict; + CFNumberRef numType, numSelector; + int num; + + outerArray = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); + if (outerArray == NULL) + complain("error creating outer CFArray for adding small caps attributes in addFontSmallCapsAttr()"); + + // Apple's headers say these are deprecated, but a few fonts still rely on them + num = kLetterCaseType; + numType = CFNumberCreate(NULL, kCFNumberIntType, &num); + num = kSmallCapsSelector; + numSelector = CFNumberCreate(NULL, kCFNumberIntType, &num); + innerDict = newAttrList(); + CFDictionaryAddValue(innerDict, kCTFontFeatureTypeIdentifierKey, numType); + CFRelease(numType); + CFDictionaryAddValue(innerDict, kCTFontFeatureSelectorIdentifierKey, numSelector); + CFRelease(numSelector); + CFArrayAppendValue(outerArray, innerDict); + CFRelease(innerDict); // and likewise for CFArray + + // these are the non-deprecated versions of the above; some fonts have these instead + num = kLowerCaseType; + numType = CFNumberCreate(NULL, kCFNumberIntType, &num); + num = kLowerCaseSmallCapsSelector; + numSelector = CFNumberCreate(NULL, kCFNumberIntType, &num); + innerDict = newAttrList(); + CFDictionaryAddValue(innerDict, kCTFontFeatureTypeIdentifierKey, numType); + CFRelease(numType); + CFDictionaryAddValue(innerDict, kCTFontFeatureSelectorIdentifierKey, numSelector); + CFRelease(numSelector); + CFArrayAppendValue(outerArray, innerDict); + CFRelease(innerDict); // and likewise for CFArray + + CFDictionaryAddValue(attr, kCTFontFeatureSettingsAttribute, outerArray); + CFRelease(outerArray); +} +#endif + +// Named constants for these were NOT added until 10.11, and even then they were added as external symbols instead of macros, so we can't use them directly :( +// kode54 got these for me before I had access to El Capitan; thanks to him. +#define ourNSFontWeightUltraLight -0.800000 +#define ourNSFontWeightThin -0.600000 +#define ourNSFontWeightLight -0.400000 +#define ourNSFontWeightRegular 0.000000 +#define ourNSFontWeightMedium 0.230000 +#define ourNSFontWeightSemibold 0.300000 +#define ourNSFontWeightBold 0.400000 +#define ourNSFontWeightHeavy 0.560000 +#define ourNSFontWeightBlack 0.620000 +static const CGFloat ctWeights[] = { + // yeah these two have their names swapped; blame Pango + [uiDrawTextWeightThin] = ourNSFontWeightUltraLight, + [uiDrawTextWeightUltraLight] = ourNSFontWeightThin, + [uiDrawTextWeightLight] = ourNSFontWeightLight, + // for this one let's go between Light and Regular + // we're doing nearest so if there happens to be an exact value hopefully it's close enough + [uiDrawTextWeightBook] = ourNSFontWeightLight + ((ourNSFontWeightRegular - ourNSFontWeightLight) / 2), + [uiDrawTextWeightNormal] = ourNSFontWeightRegular, + [uiDrawTextWeightMedium] = ourNSFontWeightMedium, + [uiDrawTextWeightSemiBold] = ourNSFontWeightSemibold, + [uiDrawTextWeightBold] = ourNSFontWeightBold, + // for this one let's go between Bold and Heavy + [uiDrawTextWeightUltraBold] = ourNSFontWeightBold + ((ourNSFontWeightHeavy - ourNSFontWeightBold) / 2), + [uiDrawTextWeightHeavy] = ourNSFontWeightHeavy, + [uiDrawTextWeightUltraHeavy] = ourNSFontWeightBlack, +}; + +// Unfortunately there are still no named constants for these. +// Let's just use normalized widths. +// As far as I can tell (OS X only ships with condensed fonts, not expanded fonts; TODO), regardless of condensed or expanded, negative means condensed and positive means expanded. +// TODO verify this is correct +static const CGFloat ctStretches[] = { + [uiDrawTextStretchUltraCondensed] = -1.0, + [uiDrawTextStretchExtraCondensed] = -0.75, + [uiDrawTextStretchCondensed] = -0.5, + [uiDrawTextStretchSemiCondensed] = -0.25, + [uiDrawTextStretchNormal] = 0.0, + [uiDrawTextStretchSemiExpanded] = 0.25, + [uiDrawTextStretchExpanded] = 0.5, + [uiDrawTextStretchExtraExpanded] = 0.75, + [uiDrawTextStretchUltraExpanded] = 1.0, +}; + +struct closeness { + CFIndex index; + CGFloat weight; + CGFloat italic; + CGFloat stretch; + CGFloat distance; +}; + +// Stupidity: CTFont requires an **exact match for the entire traits dictionary**, otherwise it will **drop ALL the traits**. +// We have to implement the closest match ourselves. +// Also we have to do this before adding the small caps flags, because the matching descriptors won't have those. +CTFontDescriptorRef matchTraits(CTFontDescriptorRef against, uiDrawTextWeight weight, uiDrawTextItalic italic, uiDrawTextStretch stretch) +{ + CGFloat targetWeight; + CGFloat italicCloseness, obliqueCloseness, normalCloseness; + CGFloat targetStretch; + CFArrayRef matching; + CFIndex i, n; + struct closeness *closeness; + CTFontDescriptorRef current; + CTFontDescriptorRef out; + + targetWeight = ctWeights[weight]; + switch (italic) { + case uiDrawTextItalicNormal: + italicCloseness = 1; + obliqueCloseness = 1; + normalCloseness = 0; + break; + case uiDrawTextItalicOblique: + italicCloseness = 0.5; + obliqueCloseness = 0; + normalCloseness = 1; + break; + case uiDrawTextItalicItalic: + italicCloseness = 0; + obliqueCloseness = 0.5; + normalCloseness = 1; + break; + } + targetStretch = ctStretches[stretch]; + + matching = CTFontDescriptorCreateMatchingFontDescriptors(against, NULL); + if (matching == NULL) + // no matches; give the original back and hope for the best + return against; + n = CFArrayGetCount(matching); + if (n == 0) { + // likewise + CFRelease(matching); + return against; + } + + closeness = (struct closeness *) uiAlloc(n * sizeof (struct closeness), "struct closeness[]"); + for (i = 0; i < n; i++) { + CFDictionaryRef traits; + CFNumberRef cfnum; + CTFontSymbolicTraits symbolic; + + closeness[i].index = i; + + current = CFArrayGetValueAtIndex(matching, i); + traits = CTFontDescriptorCopyAttribute(current, kCTFontTraitsAttribute); + if (traits == NULL) { + // couldn't get traits; be safe by ranking it lowest + // LONGTERM figure out what the longest possible distances are + closeness[i].weight = 3; + closeness[i].italic = 2; + closeness[i].stretch = 3; + continue; + } + + symbolic = 0; // assume no symbolic traits if none are listed + cfnum = CFDictionaryGetValue(traits, kCTFontSymbolicTrait); + if (cfnum != NULL) { + SInt32 s; + + if (CFNumberGetValue(cfnum, kCFNumberSInt32Type, &s) == false) + complain("error getting symbolic traits in matchTraits()"); + symbolic = (CTFontSymbolicTraits) s; + // Get rule; do not release cfnum + } + + // now try weight + cfnum = CFDictionaryGetValue(traits, kCTFontWeightTrait); + if (cfnum != NULL) { + CGFloat val; + + // LONGTERM instead of complaining for this and width and possibly also symbolic traits above, should we just fall through to the default? + if (CFNumberGetValue(cfnum, kCFNumberCGFloatType, &val) == false) + complain("error getting weight value in matchTraits()"); + closeness[i].weight = val - targetWeight; + } else + // okay there's no weight key; let's try the literal meaning of the symbolic constant + // LONGTERM is the weight key guaranteed? + if ((symbolic & kCTFontBoldTrait) != 0) + closeness[i].weight = ourNSFontWeightBold - targetWeight; + else + closeness[i].weight = ourNSFontWeightRegular - targetWeight; + + // italics is a bit harder because Core Text doesn't expose a concept of obliqueness + // Pango just does a g_strrstr() (backwards case-sensitive search) for "Oblique" in the font's style name (see https://git.gnome.org/browse/pango/tree/pango/pangocoretext-fontmap.c); let's do that too I guess + if ((symbolic & kCTFontItalicTrait) != 0) + closeness[i].italic = italicCloseness; + else { + CFStringRef styleName; + BOOL isOblique; + + isOblique = NO; // default value + styleName = CTFontDescriptorCopyAttribute(current, kCTFontStyleNameAttribute); + if (styleName != NULL) { + CFRange range; + + // note the use of the toll-free bridge for the string literal, since CFSTR() *can* return NULL + range = CFStringFind(styleName, (CFStringRef) @"Oblique", kCFCompareBackwards); + if (range.location != kCFNotFound) + isOblique = YES; + CFRelease(styleName); + } + if (isOblique) + closeness[i].italic = obliqueCloseness; + else + closeness[i].italic = normalCloseness; + } + + // now try width + // TODO this does not seem to be enough for Skia's extended variants; the width trait is 0 but the Expanded flag is on + // TODO verify the rest of this matrix (what matrix?) + cfnum = CFDictionaryGetValue(traits, kCTFontWidthTrait); + if (cfnum != NULL) { + CGFloat val; + + if (CFNumberGetValue(cfnum, kCFNumberCGFloatType, &val) == false) + complain("error getting width value in matchTraits()"); + closeness[i].stretch = val - targetStretch; + } else + // okay there's no width key; let's try the literal meaning of the symbolic constant + // LONGTERM is the width key guaranteed? + if ((symbolic & kCTFontExpandedTrait) != 0) + closeness[i].stretch = 1.0 - targetStretch; + else if ((symbolic & kCTFontCondensedTrait) != 0) + closeness[i].stretch = -1.0 - targetStretch; + else + closeness[i].stretch = 0.0 - targetStretch; + + CFRelease(traits); + } + + // now figure out the 3-space difference between the three and sort by that + for (i = 0; i < n; i++) { + CGFloat weight, italic, stretch; + + weight = closeness[i].weight; + weight *= weight; + italic = closeness[i].italic; + italic *= italic; + stretch = closeness[i].stretch; + stretch *= stretch; + closeness[i].distance = sqrt(weight + italic + stretch); + } + qsort_b(closeness, n, sizeof (struct closeness), ^(const void *aa, const void *bb) { + const struct closeness *a = (const struct closeness *) aa; + const struct closeness *b = (const struct closeness *) bb; + + // via http://www.gnu.org/software/libc/manual/html_node/Comparison-Functions.html#Comparison-Functions + // LONGTERM is this really the best way? isn't it the same as if (*a < *b) return -1; if (*a > *b) return 1; return 0; ? + return (a->distance > b->distance) - (a->distance < b->distance); + }); + // and the first element of the sorted array is what we want + out = CFArrayGetValueAtIndex(matching, closeness[0].index); + CFRetain(out); // get rule + + // release everything + uiFree(closeness); + CFRelease(matching); + // and release the original descriptor since we no longer need it + CFRelease(against); + + return out; +} + +// Now remember what I said earlier about having to add the small caps traits after calling the above? This gets a dictionary back so we can do so. +CFMutableDictionaryRef extractAttributes(CTFontDescriptorRef desc) +{ + CFDictionaryRef dict; + CFMutableDictionaryRef mdict; + + dict = CTFontDescriptorCopyAttributes(desc); + // this might not be mutable, so make a mutable copy + mdict = CFDictionaryCreateMutableCopy(NULL, 0, dict); + CFRelease(dict); + return mdict; +} + +uiDrawTextFont *uiDrawLoadClosestFont(const uiDrawTextFontDescriptor *desc) +{ + CTFontRef f; + CFMutableDictionaryRef attr; + CTFontDescriptorRef cfdesc; + + attr = newAttrList(); + addFontFamilyAttr(attr, desc->Family); + addFontSizeAttr(attr, desc->Size); + + // now we have to do the traits matching, so create a descriptor, match the traits, and then get the attributes back + cfdesc = CTFontDescriptorCreateWithAttributes(attr); + // TODO release attr? + cfdesc = matchTraits(cfdesc, desc->Weight, desc->Italic, desc->Stretch); + + // specify the initial size again just to be safe + f = CTFontCreateWithFontDescriptor(cfdesc, desc->Size, NULL); + // TODO release cfdesc? + + return mkTextFont(f, NO); // we hold the initial reference; no need to retain again +} + +void uiDrawFreeTextFont(uiDrawTextFont *font) +{ + CFRelease(font->f); + uiFree(font); +} + +uintptr_t uiDrawTextFontHandle(uiDrawTextFont *font) +{ + return (uintptr_t) (font->f); +} + +void uiDrawTextFontDescribe(uiDrawTextFont *font, uiDrawTextFontDescriptor *desc) +{ + // TODO +} + +// text sizes and user space points are identical: +// - https://developer.apple.com/library/mac/documentation/TextFonts/Conceptual/CocoaTextArchitecture/TypoFeatures/TextSystemFeatures.html#//apple_ref/doc/uid/TP40009459-CH6-51627-BBCCHIFF text points are 72 per inch +// - https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CocoaDrawingGuide/Transforms/Transforms.html#//apple_ref/doc/uid/TP40003290-CH204-SW5 user space points are 72 per inch +void uiDrawTextFontGetMetrics(uiDrawTextFont *font, uiDrawTextFontMetrics *metrics) +{ + metrics->Ascent = CTFontGetAscent(font->f); + metrics->Descent = CTFontGetDescent(font->f); + metrics->Leading = CTFontGetLeading(font->f); + metrics->UnderlinePos = CTFontGetUnderlinePosition(font->f); + metrics->UnderlineThickness = CTFontGetUnderlineThickness(font->f); +} + +struct uiDrawTextLayout { + CFMutableAttributedStringRef mas; + CFRange *charsToRanges; + double width; +}; + +uiDrawTextLayout *uiDrawNewTextLayout(const char *str, uiDrawTextFont *defaultFont, double width) +{ + uiDrawTextLayout *layout; + CFAttributedStringRef immutable; + CFMutableDictionaryRef attr; + CFStringRef backing; + CFIndex i, j, n; + + layout = uiNew(uiDrawTextLayout); + + // TODO docs say we need to use a different set of key callbacks + // TODO see if the font attribute key callbacks need to be the same + attr = newAttrList(); + // this will retain defaultFont->f; no need to worry + CFDictionaryAddValue(attr, kCTFontAttributeName, defaultFont->f); + + immutable = CFAttributedStringCreate(NULL, (CFStringRef) [NSString stringWithUTF8String:str], attr); + if (immutable == NULL) + complain("error creating immutable attributed string in uiDrawNewTextLayout()"); + CFRelease(attr); + + layout->mas = CFAttributedStringCreateMutableCopy(NULL, 0, immutable); + if (layout->mas == NULL) + complain("error creating attributed string in uiDrawNewTextLayout()"); + CFRelease(immutable); + + uiDrawTextLayoutSetWidth(layout, width); + + // unfortunately the CFRanges for attributes expect UTF-16 codepoints + // we want graphemes + // fortunately CFStringGetRangeOfComposedCharactersAtIndex() is here for us + // https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Strings/Articles/stringsClusters.html says that this does work on all multi-codepoint graphemes (despite the name), and that this is the preferred function for this particular job anyway + backing = CFAttributedStringGetString(layout->mas); + n = CFStringGetLength(backing); + // allocate one extra, just to be safe + layout->charsToRanges = (CFRange *) uiAlloc((n + 1) * sizeof (CFRange), "CFRange[]"); + i = 0; + j = 0; + while (i < n) { + CFRange range; + + range = CFStringGetRangeOfComposedCharactersAtIndex(backing, i); + i = range.location + range.length; + layout->charsToRanges[j] = range; + j++; + } + // and set the last one + layout->charsToRanges[j].location = i; + layout->charsToRanges[j].length = 0; + + return layout; +} + +void uiDrawFreeTextLayout(uiDrawTextLayout *layout) +{ + uiFree(layout->charsToRanges); + CFRelease(layout->mas); + uiFree(layout); +} + +void uiDrawTextLayoutSetWidth(uiDrawTextLayout *layout, double width) +{ + layout->width = width; +} + +struct framesetter { + CTFramesetterRef fs; + CFMutableDictionaryRef frameAttrib; + CGSize extents; +}; + +// TODO CTFrameProgression for RTL/LTR +// TODO kCTParagraphStyleSpecifierMaximumLineSpacing, kCTParagraphStyleSpecifierMinimumLineSpacing, kCTParagraphStyleSpecifierLineSpacingAdjustment for line spacing +static void mkFramesetter(uiDrawTextLayout *layout, struct framesetter *fs) +{ + CFRange fitRange; + CGFloat width; + + fs->fs = CTFramesetterCreateWithAttributedString(layout->mas); + if (fs->fs == NULL) + complain("error creating CTFramesetter object in mkFramesetter()"); + + // TODO kCTFramePathWidthAttributeName? + fs->frameAttrib = NULL; + + width = layout->width; + if (layout->width < 0) + width = CGFLOAT_MAX; + // TODO these seem to be floor()'d or truncated? + fs->extents = CTFramesetterSuggestFrameSizeWithConstraints(fs->fs, + CFRangeMake(0, 0), + fs->frameAttrib, + CGSizeMake(width, CGFLOAT_MAX), + &fitRange); // not documented as accepting NULL +} + +static void freeFramesetter(struct framesetter *fs) +{ + if (fs->frameAttrib != NULL) + CFRelease(fs->frameAttrib); + CFRelease(fs->fs); +} + +// LONGTERM allow line separation and leading to be factored into a wrapping text layout + +// TODO reconcile differences in character wrapping on platforms +void uiDrawTextLayoutExtents(uiDrawTextLayout *layout, double *width, double *height) +{ + struct framesetter fs; + + mkFramesetter(layout, &fs); + *width = fs.extents.width; + *height = fs.extents.height; + freeFramesetter(&fs); +} + +// Core Text doesn't draw onto a flipped view correctly; we have to do this +// see the iOS bits of the first example at https://developer.apple.com/library/mac/documentation/StringsTextFonts/Conceptual/CoreText_Programming/LayoutOperations/LayoutOperations.html#//apple_ref/doc/uid/TP40005533-CH12-SW1 (iOS is naturally flipped) +// TODO how is this affected by the CTM? +static void prepareContextForText(CGContextRef c, CGFloat cheight, double *y) +{ + CGContextSaveGState(c); + CGContextTranslateCTM(c, 0, cheight); + CGContextScaleCTM(c, 1.0, -1.0); + CGContextSetTextMatrix(c, CGAffineTransformIdentity); + + // wait, that's not enough; we need to offset y values to account for our new flipping + *y = cheight - *y; +} + +// TODO placement is incorrect for Helvetica +void doDrawText(CGContextRef c, CGFloat cheight, double x, double y, uiDrawTextLayout *layout) +{ + struct framesetter fs; + CGRect rect; + CGPathRef path; + CTFrameRef frame; + + prepareContextForText(c, cheight, &y); + mkFramesetter(layout, &fs); + + // oh, and since we're flipped, y is the bottom-left coordinate of the rectangle, not the top-left + // since we are flipped, we subtract + y -= fs.extents.height; + + rect.origin = CGPointMake(x, y); + rect.size = fs.extents; + path = CGPathCreateWithRect(rect, NULL); + + frame = CTFramesetterCreateFrame(fs.fs, + CFRangeMake(0, 0), + path, + fs.frameAttrib); + if (frame == NULL) + complain("error creating CTFrame object in doDrawText()"); + CTFrameDraw(frame, c); + CFRelease(frame); + + CFRelease(path); + + freeFramesetter(&fs); + CGContextRestoreGState(c); +} + +// LONGTERM provide an equivalent to CTLineGetTypographicBounds() on uiDrawTextLayout? + +// LONGTERM keep this for later features and documentation purposes +#if 0 + w = CTLineGetTypographicBounds(line, &ascent, &descent, NULL); + // though CTLineGetTypographicBounds() returns 0 on error, it also returns 0 on an empty string, so we can't reasonably check for error + CFRelease(line); + + // LONGTERM provide a way to get the image bounds as a separate function later + bounds = CTLineGetImageBounds(line, c); + // though CTLineGetImageBounds() returns CGRectNull on error, it also returns CGRectNull on an empty string, so we can't reasonably check for error + + // CGContextSetTextPosition() positions at the baseline in the case of CTLineDraw(); we need the top-left corner instead + CTLineGetTypographicBounds(line, &yoff, NULL, NULL); + // remember that we're flipped, so we subtract + y -= yoff; + CGContextSetTextPosition(c, x, y); +#endif + +static CFRange charsToRange(uiDrawTextLayout *layout, int startChar, int endChar) +{ + CFRange start, end; + CFRange out; + + start = layout->charsToRanges[startChar]; + end = layout->charsToRanges[endChar]; + out.location = start.location; + out.length = end.location - start.location; + return out; +} + +#define rangeToCFRange() charsToRange(layout, startChar, endChar) + +void uiDrawTextLayoutSetColor(uiDrawTextLayout *layout, int startChar, int endChar, double r, double g, double b, double a) +{ + CGColorSpaceRef colorspace; + CGFloat components[4]; + CGColorRef color; + + // for consistency with windows, use sRGB + colorspace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB); + components[0] = r; + components[1] = g; + components[2] = b; + components[3] = a; + color = CGColorCreate(colorspace, components); + CGColorSpaceRelease(colorspace); + + CFAttributedStringSetAttribute(layout->mas, + rangeToCFRange(), + kCTForegroundColorAttributeName, + color); + CGColorRelease(color); // TODO safe? +} |