13 #include <OpenGL/CGLMacro.h>
15 #include <ApplicationServices/ApplicationServices.h>
20 "title" :
"VuoImageText",
24 "ApplicationServices.framework"
126 std::vector<VuoImage> imagesToRelease;
134 double lastUsed = item->second.second;
139 imagesToRelease.push_back(item->second.first.first);
148 for (std::vector<VuoImage>::iterator item = imagesToRelease.begin(); item != imagesToRelease.end(); ++item)
160 extern "C" void __attribute__((constructor)) VuoImageTextCache_preinit(
void)
174 VuoImageTextCache_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, DISPATCH_TIMER_STRICT, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
231 bool includeTrailingWhiteSpace,
234 CGColorSpaceRef *colorspace,
236 CGAffineTransform *outTransform)
238 CFStringRef fontNameCF = NULL;
241 fontNameCF = CFStringCreateWithCString(NULL, font.
fontName, kCFStringEncodingUTF8);
243 *ctFont = CTFontCreateWithName(fontNameCF ? fontNameCF : CFSTR(
""), font.pointSize * backingScaleFactor, NULL);
246 CFRelease(fontNameCF);
248 CGFloat colorComponents[4] = {font.color.r, font.color.g, font.color.b, font.color.a};
249 *colorspace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
250 *cgColor = CGColorCreate(*colorspace, colorComponents);
252 unsigned long underline = font.underline ? kCTUnderlineStyleSingle : kCTUnderlineStyleNone;
253 CFNumberRef underlineNumber = CFNumberCreate(NULL, kCFNumberCFIndexType, &underline);
255 float kern = (font.
characterSpacing - 1) * font.pointSize * backingScaleFactor;
256 CFNumberRef kernNumber = CFNumberCreate(NULL, kCFNumberFloatType, &kern);
259 CGContextRef cgContext = CGBitmapContextCreate(NULL, 1, 1, 8, 4, *colorspace, kCGImageAlphaPremultipliedLast);
261 *outTransform = CGAffineTransformScale(CGAffineTransformMakeRotation(-rotation), 1., verticalScale);
265 CGMutablePathRef path = CGPathCreateMutable();
266 CGPathAddRect(path, NULL, CGRectMake(0, 0, wrapWidthPixels, INFINITY));
269 CFStringRef cfText = CFStringCreateWithCStringNoCopy(NULL, text, kCFStringEncodingUTF8, kCFAllocatorNull);
270 CFIndex characterCount = CFStringGetLength(cfText);
271 CFStringRef keys[] = { kCTFontAttributeName, kCTForegroundColorAttributeName, kCTUnderlineStyleAttributeName, kCTKernAttributeName };
272 CFTypeRef values[] = { *ctFont, *cgColor, underlineNumber, kernNumber };
273 CFDictionaryRef attr = CFDictionaryCreate(NULL, (
const void **)&keys, (
const void **)&values,
sizeof(keys) /
sizeof(keys[0]), NULL, NULL);
274 CFAttributedStringRef attrString = CFAttributedStringCreate(NULL, cfText, attr);
275 CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(attrString);
276 CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0,0), path, NULL);
277 CFArrayRef ctLines = CTFrameGetLines(frame);
279 CFIndex lineCount = CFArrayGetCount(ctLines);
282 double ascent = CTFontGetAscent(*ctFont);
283 double descent = CTFontGetDescent(*ctFont);
284 double leading = CTFontGetLeading(*ctFont);
286 double lineHeight = ascent + descent + leading;
287 CGRect bounds = CGRectMake(0, 0, 0, 0);
288 CGRect* lineBounds = (CGRect *)malloc(
sizeof(CGRect) * lineCount);
289 unsigned int* lineCounts = (
unsigned int*) malloc(
sizeof(
unsigned int) * lineCount);
294 for (CFIndex i = 0; i < lineCount; ++i)
296 CTLineRef ctLine = (CTLineRef) CFArrayGetValueAtIndex(ctLines, i);
297 CFRange stringRange = CTLineGetStringRange(ctLine);
298 lineCounts[i] = stringRange.length;
301 CGFloat secondaryOffset;
302 CGFloat previousOffset = CTLineGetOffsetForStringIndex(ctLine, stringRange.location, &secondaryOffset );
304 for(CFIndex index = stringRange.location; index < stringRange.location + stringRange.length; index++)
306 CGFloat offset = CTLineGetOffsetForStringIndex(ctLine,
MIN(stringRange.location + stringRange.length, index + 1), &secondaryOffset );
307 charAdvance[index] = (
VuoReal) (offset - previousOffset);
308 previousOffset = offset;
311 CGRect lineImageBounds = CTLineGetImageBounds(ctLine, cgContext);
312 double width = CGRectGetWidth(lineImageBounds);
314 textImageData->
lineXOrigins[i] = lineImageBounds.origin.x;
315 if (includeTrailingWhiteSpace)
316 width += CTLineGetTrailingWhitespaceWidth(ctLine);
317 lineBounds[i] = CGRectMake(CGRectGetMinX(lineImageBounds), lineHeight * i - ascent, width, lineHeight);
320 if (CGRectGetMinX(lineBounds[i]) < CGRectGetMinX(bounds))
321 bounds.origin.x = CGRectGetMinX(lineBounds[i]);
322 if (CGRectGetMinY(lineBounds[i]) < CGRectGetMinY(bounds))
323 bounds.origin.y = CGRectGetMinY(lineBounds[i]);
324 if (CGRectGetMaxX(lineBounds[i]) > CGRectGetMaxX(bounds))
325 bounds.size.width += CGRectGetMaxX(lineBounds[i]) - CGRectGetMaxX(bounds);
329 bounds.size.height += lineHeight;
331 bounds.size.height += lineHeight * font.
lineSpacing;
335 const unsigned int AA_STROKE_PAD = 2;
337 CGRect transformedBounds = CGRectApplyAffineTransform(bounds, *outTransform);
338 CGPoint transformedTopLeft = CGPointApplyAffineTransform(bounds.origin, *outTransform);
339 CGPoint transformedTopRight = CGPointApplyAffineTransform(CGPointMake(bounds.origin.x+bounds.size.width, bounds.origin.y), *outTransform);
340 CGPoint transformedBottomRight = CGPointApplyAffineTransform(CGPointMake(bounds.origin.x+bounds.size.width, bounds.origin.y+bounds.size.height), *outTransform);
341 CGPoint transformedBottomLeft = CGPointApplyAffineTransform(CGPointMake(bounds.origin.x, bounds.origin.y+bounds.size.height), *outTransform);
343 unsigned int width = ceil(CGRectGetWidth(transformedBounds)) + AA_STROKE_PAD;
344 unsigned int height = ceil(CGRectGetHeight(transformedBounds)) + AA_STROKE_PAD;
346 textImageData->
width = width;
347 textImageData->
height = height;
349 textImageData->
bounds =
VuoRectangle_make(CGRectGetMidX(bounds), CGRectGetMidY(bounds), CGRectGetWidth(bounds), CGRectGetHeight(bounds));
350 textImageData->
transformedBounds =
VuoRectangle_make(CGRectGetMidX(transformedBounds), CGRectGetMidY(transformedBounds), CGRectGetWidth(transformedBounds), CGRectGetHeight(transformedBounds));
351 textImageData->
transformedCorners[0] = (VuoPoint2d){(float)(transformedTopLeft.x - transformedBounds.origin.x), (float)(transformedTopLeft.y - transformedBounds.origin.y)};
352 textImageData->
transformedCorners[1] = (VuoPoint2d){(float)(transformedTopRight.x - transformedBounds.origin.x), (float)(transformedTopRight.y - transformedBounds.origin.y)};
353 textImageData->
transformedCorners[2] = (VuoPoint2d){(float)(transformedBottomRight.x - transformedBounds.origin.x), (float)(transformedBottomRight.y - transformedBounds.origin.y)};
354 textImageData->
transformedCorners[3] = (VuoPoint2d){(float)(transformedBottomLeft.x - transformedBounds.origin.x), (float)(transformedBottomLeft.y - transformedBounds.origin.y)};
359 textImageData->
charCount = characterCount;
362 for (
int i = 0; i < lineCount; i++)
364 CGRect bb = CGRectApplyAffineTransform(lineBounds[i], *outTransform);
371 CGContextRelease(cgContext);
374 CFRelease(attrString);
375 CFRelease(framesetter);
379 CFRelease(kernNumber);
380 CFRelease(underlineNumber);
394 VuoPoint2d corners[4];
396 VuoImage_makeText(text, font, backingScaleFactor, verticalScale, rotation, wrapWidth, corners);
398 double minX =
MIN(corners[0].x,
MIN(corners[1].x,
MIN(corners[2].x, corners[3].x)));
399 double minY =
MIN(corners[0].y,
MIN(corners[1].y,
MIN(corners[2].y, corners[3].y)));
400 double maxX =
MAX(corners[0].x,
MAX(corners[1].x,
MAX(corners[2].x, corners[3].x)));
401 double maxY =
MAX(corners[0].y,
MAX(corners[1].y,
MAX(corners[2].y, corners[3].y)));
420 CGColorSpaceRef colorspace;
422 CGAffineTransform transform;
423 CFArrayRef ctLines =
VuoImageText_createCTLines(text, font, backingScaleFactor, verticalScale, rotation, INFINITY, includeTrailingWhiteSpace, &ctFont, &cgColor, &colorspace, textData, &transform);
425 CGColorRelease(cgColor);
426 CGColorSpaceRelease(colorspace);
441 double w = textBounds.size.x * s;
442 double h = textBounds.size.y * s;
445 size.x = (w / windowSize.x) * 2;
446 size.y = size.x * (h / w);
451 #define ScreenToGL(x) (((x) / screenWidthInPixels) * 2.)
460 CFStringRef fontNameCF = NULL;
463 fontNameCF = CFStringCreateWithCString(NULL, font.
fontName, kCFStringEncodingUTF8);
465 CTFontRef ctFont = CTFontCreateWithName(fontNameCF ? fontNameCF : CFSTR(
""), font.pointSize * backingScaleFactor, NULL);
468 CFRelease(fontNameCF);
470 double ascent = CTFontGetAscent(ctFont);
471 double descent = CTFontGetDescent(ctFont);
472 double leading = CTFontGetLeading(ctFont);
473 VuoReal lineHeight = ascent + descent + leading;
489 double w = textData->
width * scale;
490 double h = textData->
height * scale;
492 textData->
width = (w / screenWidthInPixels) * 2;
500 for (
int i = 0; i < textData->
lineCount; i++)
506 for (
int n = 0; n < textData->
charCount; n++)
527 x = -textData->
width * .5;
532 x = textData->
lineBounds[lineIndex].size.x * -.5;
551 unsigned int lineIndex = 0;
553 unsigned int lineStart = 0;
555 while(charIndex >= lineStart + textData->
lineCounts[lineIndex])
567 if(lineStartCharIndex != NULL)
568 *lineStartCharIndex = lineStart;
581 unsigned int lineStart = 0;
586 for(
int n = lineStart; n < charIndex; n++)
589 if( lineIndex != NULL )
590 *lineIndex = lineIdx;
601 std::vector<VuoRectangle> rects;
603 unsigned int lineStart = 0;
608 for(
int n = lineStart; n < selectionStartIndex; n++)
613 unsigned int curIndex = selectionStartIndex;
614 unsigned int textLength = textData->
charCount;
616 while(curIndex < selectionStartIndex + selectionLength)
618 width += curIndex < textLength ? textData->
charAdvance[curIndex] : 0;
621 if( curIndex >= lineStart + textData->
lineCounts[lineIndex] || curIndex >= selectionStartIndex + selectionLength )
623 float w = fmax(.01, width);
625 rects.push_back(
VuoRectangle_make(origin.x + (w * .5), origin.y + (h * .5), w, h) );
638 *lineCount = rects.size();
640 std::copy(rects.begin(), rects.end(), copy);
650 unsigned int charIndex = 0;
651 unsigned int index =
MIN(textData->
lineCount, lineIndex);
652 for(
unsigned int i = 0; i < index; i++)
663 unsigned int lineIndex;
666 *charactersRemaining = (textData->
lineCounts[lineIndex] - (index - lineBegin));
667 float width = textData->
lineBounds[lineIndex].size.x - begin.x;
684 if(h == VuoHorizontalAlignment_Left)
685 point.x -= textData->
width * .5;
686 else if(h == VuoHorizontalAlignment_Right)
687 point.x += textData->
width * .5;
689 if(v == VuoVerticalAlignment_Bottom)
690 point.y -= textData->
height * .5;
691 else if(v == VuoVerticalAlignment_Top)
692 point.y += textData->
height * .5;
694 for(
int r = 0; r < textData->
lineCount; r++)
696 bool lastLine = r == textData->
lineCount - 1;
702 if(point.y > lineOrigin.y || lastLine)
705 if(point.x < lineOrigin.x)
709 else if(point.x > (lineOrigin.x + lineBounds.size.x))
710 return index + textData->
lineCounts[r] - (lastLine ? 0 : 1);
712 for(
int i = 0; i < textData->
lineCounts[r]; i++)
716 if(point.x < lineOrigin.x + advance * .5)
719 lineOrigin.x += advance;
722 return index + textData->
lineCounts[r] + (lastLine ? 1 : 0);
744 if (font.pointSize < 0.00001
745 || verticalScale < 0.00001)
749 static dispatch_once_t initCache = 0;
750 dispatch_once(&initCache, ^ {
753 VuoFontClass fc(font, backingScaleFactor, verticalScale, rotation, wrapWidth);
761 VuoImage image = e->second.first.first;
763 memcpy(outCorners, &e->second.first.second[0],
sizeof(VuoPoint2d)*4);
775 CGColorSpaceRef colorspace;
779 CGAffineTransform transform;
780 CFArrayRef ctLines =
VuoImageText_createCTLines(text, font, backingScaleFactor, verticalScale, rotation, wrapWidth,
false, &ctFont, &cgColor, &colorspace, textData, &transform);
785 CGContextRef cgContext = CGBitmapContextCreate(NULL, textData->
width, textData->
height, 8, textData->
width * 4, colorspace, kCGImageAlphaPremultipliedLast);
807 CGContextSetTextMatrix(cgContext, CGAffineTransformMakeScale(1.0, -1.0));
816 CGContextTranslateCTM(cgContext, fabs(cos(rotation)), fabs(sin(rotation)));
819 CGContextConcatCTM(cgContext, transform);
824 CFIndex lineCount = CFArrayGetCount(ctLines);
825 for (CFIndex i = 0; i < lineCount; ++i)
827 CTLineRef ctLine = (CTLineRef)CFArrayGetValueAtIndex(ctLines, i);
829 float textXPosition = -(bounds.center.x - (bounds.size.x * .5));
830 if (font.alignment == VuoHorizontalAlignment_Center)
832 else if (font.alignment == VuoHorizontalAlignment_Right)
835 float textYPosition = -(bounds.center.y - (bounds.size.y * .5));
838 CGContextSetTextPosition(cgContext, textXPosition, textYPosition);
839 CTLineDraw(ctLine, cgContext);
858 CGColorSpaceRelease(colorspace);
859 CGColorRelease(cgColor);
874 VuoImage cachedImage = it->second.first.first;
876 memcpy(outCorners, &it->second.first.second[0],
sizeof(VuoPoint2d)*4);
886 (*VuoImageTextCache)[descriptor] = e;