13 #include <OpenGL/CGLMacro.h>
15 #include <ApplicationServices/ApplicationServices.h>
20 "title" :
"VuoImageText",
24 "ApplicationServices.framework"
90 i->lineCounts =
nullptr;
91 i->lineBounds =
nullptr;
92 i->lineWidthsExcludingTrailingWhitespace =
nullptr;
93 i->lineXOrigins =
nullptr;
94 i->charAdvance =
nullptr;
105 if (value->lineCounts)
106 free(value->lineCounts);
107 if (value->lineBounds)
108 free(value->lineBounds);
109 if (value->lineWidthsExcludingTrailingWhitespace)
110 free(value->lineWidthsExcludingTrailingWhitespace);
111 if (value->lineXOrigins)
112 free(value->lineXOrigins);
113 if (value->charAdvance)
114 free(value->charAdvance);
133 std::vector<VuoImage> imagesToRelease;
141 double lastUsed = item->second.second;
146 imagesToRelease.push_back(item->second.first.first);
155 for (std::vector<VuoImage>::iterator item = imagesToRelease.begin(); item != imagesToRelease.end(); ++item)
167 extern "C" void __attribute__((constructor)) VuoImageTextCache_preinit(
void)
181 VuoImageTextCache_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, DISPATCH_TIMER_STRICT, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
195 extern "C" void __attribute__((destructor)) VuoImageTextCache_fini(
void)
238 bool includeTrailingWhiteSpace,
241 CGColorSpaceRef *colorspace,
243 CGAffineTransform *outTransform)
245 CFStringRef fontNameCF = NULL;
248 fontNameCF = CFStringCreateWithCString(NULL, font.
fontName, kCFStringEncodingUTF8);
250 *ctFont = CTFontCreateWithName(fontNameCF ? fontNameCF : CFSTR(
""), font.pointSize * backingScaleFactor, NULL);
253 CFRelease(fontNameCF);
255 CGFloat colorComponents[4] = {font.color.r, font.color.g, font.color.b, font.color.a};
256 *colorspace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
257 *cgColor = CGColorCreate(*colorspace, colorComponents);
259 unsigned long underline = font.underline ? kCTUnderlineStyleSingle : kCTUnderlineStyleNone;
260 CFNumberRef underlineNumber = CFNumberCreate(NULL, kCFNumberCFIndexType, &underline);
262 float kern = (font.
characterSpacing - 1) * font.pointSize * backingScaleFactor;
263 CFNumberRef kernNumber = CFNumberCreate(NULL, kCFNumberFloatType, &kern);
266 CGContextRef cgContext = CGBitmapContextCreate(NULL, 1, 1, 8, 4, *colorspace, kCGImageAlphaPremultipliedLast);
268 *outTransform = CGAffineTransformScale(CGAffineTransformMakeRotation(-rotation), 1., verticalScale);
272 CGMutablePathRef path = CGPathCreateMutable();
273 CGPathAddRect(path, NULL, CGRectMake(0, 0, wrapWidthPixels, INFINITY));
276 CFStringRef cfText = CFStringCreateWithCStringNoCopy(NULL, text, kCFStringEncodingUTF8, kCFAllocatorNull);
277 CFIndex characterCount = CFStringGetLength(cfText);
278 CFStringRef keys[] = { kCTFontAttributeName, kCTForegroundColorAttributeName, kCTUnderlineStyleAttributeName, kCTKernAttributeName };
279 CFTypeRef values[] = { *ctFont, *cgColor, underlineNumber, kernNumber };
280 CFDictionaryRef attr = CFDictionaryCreate(NULL, (
const void **)&keys, (
const void **)&values,
sizeof(keys) /
sizeof(keys[0]), NULL, NULL);
281 CFAttributedStringRef attrString = CFAttributedStringCreate(NULL, cfText, attr);
282 CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(attrString);
283 CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0,0), path, NULL);
284 CFArrayRef ctLines = CTFrameGetLines(frame);
286 CFIndex lineCount = CFArrayGetCount(ctLines);
287 if (text[strlen(text) - 1] ==
'\n')
291 double ascent = CTFontGetAscent(*ctFont);
292 double descent = CTFontGetDescent(*ctFont);
293 double leading = CTFontGetLeading(*ctFont);
295 double lineHeight = ascent + descent + leading;
296 CGRect bounds = CGRectMake(0, 0, 0, 0);
297 CGRect* lineBounds = (CGRect *)malloc(
sizeof(CGRect) * lineCount);
298 unsigned int* lineCounts = (
unsigned int*) malloc(
sizeof(
unsigned int) * lineCount);
299 textImageData->lineWidthsExcludingTrailingWhitespace = (
VuoReal *)malloc(
sizeof(
VuoReal) * lineCount);
300 textImageData->lineXOrigins = (
VuoReal *)malloc(
sizeof(
VuoReal) * lineCount);
303 for (CFIndex i = 0; i < lineCount; ++i)
306 CTLineRef ctLine =
nullptr;
307 if (i < CFArrayGetCount(ctLines))
308 ctLine = (CTLineRef) CFArrayGetValueAtIndex(ctLines, i);
310 CFRange stringRange = ctLine ? CTLineGetStringRange(ctLine) : CFRangeMake(0,0);
311 lineCounts[i] = stringRange.length;
314 CGFloat secondaryOffset;
315 CGFloat previousOffset = ctLine ? CTLineGetOffsetForStringIndex(ctLine, stringRange.location, &secondaryOffset ) : 0;
317 for(CFIndex index = stringRange.location; index < stringRange.location + stringRange.length; index++)
319 CGFloat offset = CTLineGetOffsetForStringIndex(ctLine,
MIN(stringRange.location + stringRange.length, index + 1), &secondaryOffset );
320 charAdvance[index] = (
VuoReal) (offset - previousOffset);
321 previousOffset = offset;
324 CGRect lineImageBounds = ctLine ? CTLineGetImageBounds(ctLine, cgContext) : CGRectZero;
325 double width = CGRectGetWidth(lineImageBounds);
326 textImageData->lineWidthsExcludingTrailingWhitespace[i] = lineImageBounds.size.width;
327 textImageData->lineXOrigins[i] = lineImageBounds.origin.x;
328 if (includeTrailingWhiteSpace && ctLine)
329 width += CTLineGetTrailingWhitespaceWidth(ctLine);
330 lineBounds[i] = CGRectMake(CGRectGetMinX(lineImageBounds), lineHeight * i - ascent, width, lineHeight);
333 if (CGRectGetMinX(lineBounds[i]) < CGRectGetMinX(bounds))
334 bounds.origin.x = CGRectGetMinX(lineBounds[i]);
335 if (CGRectGetMinY(lineBounds[i]) < CGRectGetMinY(bounds))
336 bounds.origin.y = CGRectGetMinY(lineBounds[i]);
337 if (CGRectGetMaxX(lineBounds[i]) > CGRectGetMaxX(bounds))
338 bounds.size.width += CGRectGetMaxX(lineBounds[i]) - CGRectGetMaxX(bounds);
342 bounds.size.height += lineHeight;
344 bounds.size.height += lineHeight * font.
lineSpacing;
348 const unsigned int AA_STROKE_PAD = 2;
350 CGRect transformedBounds = CGRectApplyAffineTransform(bounds, *outTransform);
351 CGPoint transformedTopLeft = CGPointApplyAffineTransform(bounds.origin, *outTransform);
352 CGPoint transformedTopRight = CGPointApplyAffineTransform(CGPointMake(bounds.origin.x+bounds.size.width, bounds.origin.y), *outTransform);
353 CGPoint transformedBottomRight = CGPointApplyAffineTransform(CGPointMake(bounds.origin.x+bounds.size.width, bounds.origin.y+bounds.size.height), *outTransform);
354 CGPoint transformedBottomLeft = CGPointApplyAffineTransform(CGPointMake(bounds.origin.x, bounds.origin.y+bounds.size.height), *outTransform);
356 unsigned int width = ceil(CGRectGetWidth(transformedBounds)) + AA_STROKE_PAD;
357 unsigned int height = ceil(CGRectGetHeight(transformedBounds)) + AA_STROKE_PAD;
359 textImageData->width = width;
360 textImageData->height = height;
361 textImageData->lineHeight = lineHeight;
362 textImageData->bounds =
VuoRectangle_make(CGRectGetMidX(bounds), CGRectGetMidY(bounds), CGRectGetWidth(bounds), CGRectGetHeight(bounds));
363 textImageData->transformedBounds =
VuoRectangle_make(CGRectGetMidX(transformedBounds), CGRectGetMidY(transformedBounds), CGRectGetWidth(transformedBounds), CGRectGetHeight(transformedBounds));
364 textImageData->transformedCorners[0] = (VuoPoint2d){(float)(transformedTopLeft.x - transformedBounds.origin.x), (float)(transformedTopLeft.y - transformedBounds.origin.y)};
365 textImageData->transformedCorners[1] = (VuoPoint2d){(float)(transformedTopRight.x - transformedBounds.origin.x), (float)(transformedTopRight.y - transformedBounds.origin.y)};
366 textImageData->transformedCorners[2] = (VuoPoint2d){(float)(transformedBottomRight.x - transformedBounds.origin.x), (float)(transformedBottomRight.y - transformedBounds.origin.y)};
367 textImageData->transformedCorners[3] = (VuoPoint2d){(float)(transformedBottomLeft.x - transformedBounds.origin.x), (float)(transformedBottomLeft.y - transformedBounds.origin.y)};
368 textImageData->lineCount = lineCount;
369 textImageData->lineCounts = lineCounts;
371 textImageData->charAdvance = charAdvance;
372 textImageData->charCount = characterCount;
373 textImageData->horizontalAlignment = font.alignment;
375 for (
int i = 0; i < lineCount; i++)
377 CGRect bb = CGRectApplyAffineTransform(lineBounds[i], *outTransform);
378 textImageData->lineBounds[i] =
VuoRectangle_make(CGRectGetMidX(bb), CGRectGetMidY(bb), CGRectGetWidth(bb), CGRectGetHeight(bb));
384 CGContextRelease(cgContext);
387 CFRelease(attrString);
388 CFRelease(framesetter);
392 CFRelease(kernNumber);
393 CFRelease(underlineNumber);
407 VuoPoint2d corners[4];
409 VuoImage_makeText(text, font, backingScaleFactor, verticalScale, rotation, wrapWidth, corners);
411 double minX =
MIN(corners[0].x,
MIN(corners[1].x,
MIN(corners[2].x, corners[3].x)));
412 double minY =
MIN(corners[0].y,
MIN(corners[1].y,
MIN(corners[2].y, corners[3].y)));
413 double maxX =
MAX(corners[0].x,
MAX(corners[1].x,
MAX(corners[2].x, corners[3].x)));
414 double maxY =
MAX(corners[0].y,
MAX(corners[1].y,
MAX(corners[2].y, corners[3].y)));
433 CGColorSpaceRef colorspace;
435 CGAffineTransform transform;
436 CFArrayRef ctLines =
VuoImageText_createCTLines(text, font, backingScaleFactor, verticalScale, rotation, INFINITY, includeTrailingWhiteSpace, &ctFont, &cgColor, &colorspace, textData, &transform);
438 CGColorRelease(cgColor);
439 CGColorSpaceRelease(colorspace);
454 double w = textBounds.size.x * s;
455 double h = textBounds.size.y * s;
458 size.x = (w / windowSize.x) * 2;
459 size.y = size.x * (h / w);
464 #define ScreenToGL(x) (((x) / screenWidthInPixels) * 2.)
473 CFStringRef fontNameCF = NULL;
476 fontNameCF = CFStringCreateWithCString(NULL, font.
fontName, kCFStringEncodingUTF8);
478 CTFontRef ctFont = CTFontCreateWithName(fontNameCF ? fontNameCF : CFSTR(
""), font.pointSize * backingScaleFactor, NULL);
481 CFRelease(fontNameCF);
483 double ascent = CTFontGetAscent(ctFont);
484 double descent = CTFontGetDescent(ctFont);
485 double leading = CTFontGetLeading(ctFont);
486 VuoReal lineHeight = ascent + descent + leading;
502 double w = textData->width * scale;
503 double h = textData->height * scale;
505 textData->width = (w / screenWidthInPixels) * 2;
506 textData->height = textData->width * (h / w);
508 textData->lineHeight =
ScreenToGL(textData->lineHeight * scale);
513 for (
int i = 0; i < textData->lineCount; i++)
519 for (
int n = 0; n < textData->charCount; n++)
521 float advance = textData->charAdvance[n] * scale;
522 textData->charAdvance[n] =
ScreenToGL(advance);
538 if(textData->horizontalAlignment == VuoHorizontalAlignment_Left)
540 x = -textData->width * .5;
544 if(textData->horizontalAlignment == VuoHorizontalAlignment_Center)
545 x = textData->lineBounds[lineIndex].size.x * -.5;
547 x = textData->width * .5 - textData->lineBounds[lineIndex].size.x;
551 return VuoPoint2d_make(x, ((textData->lineCount - lineIndex) * textData->lineHeight) - textData->lineHeight - (textData->height * .5));
564 unsigned int lineIndex = 0;
566 unsigned int lineStart = 0;
568 while (lineIndex < textData->lineCount && charIndex >= lineStart + textData->lineCounts[lineIndex])
570 lineStart += textData->lineCounts[lineIndex];
574 if(lineIndex >= textData->lineCount)
576 lineIndex = textData->lineCount - 1;
577 lineStart -= textData->lineCounts[lineIndex];
580 if(lineStartCharIndex != NULL)
581 *lineStartCharIndex = lineStart;
591 charIndex =
MAX(0,
MIN(textData->charCount, charIndex));
594 unsigned int lineStart = 0;
599 for(
int n = lineStart; n < charIndex; n++)
600 pos.x += textData->charAdvance[n];
602 if( lineIndex != NULL )
603 *lineIndex = lineIdx;
614 std::vector<VuoRectangle> rects;
616 unsigned int lineStart = 0;
621 for(
int n = lineStart; n < selectionStartIndex; n++)
622 origin.x += textData->charAdvance[n];
626 unsigned int curIndex = selectionStartIndex;
627 unsigned int textLength = textData->charCount;
629 while(curIndex < selectionStartIndex + selectionLength)
631 width += curIndex < textLength ? textData->charAdvance[curIndex] : 0;
634 if( curIndex >= lineStart + textData->lineCounts[lineIndex] || curIndex >= selectionStartIndex + selectionLength )
636 float w = fmax(.01, width);
637 float h = textData->lineHeight;
638 rects.push_back(
VuoRectangle_make(origin.x + (w * .5), origin.y + (h * .5), w, h) );
640 lineStart += textData->lineCounts[lineIndex];
643 if(lineIndex >= textData->lineCount)
651 *lineCount = rects.size();
653 std::copy(rects.begin(), rects.end(), copy);
663 unsigned int charIndex = 0;
664 unsigned int index =
MIN(textData->lineCount, lineIndex);
665 for(
unsigned int i = 0; i < index; i++)
666 charIndex += textData->lineCounts[i];
676 unsigned int lineIndex;
679 *charactersRemaining = (textData->lineCounts[lineIndex] - (index - lineBegin));
680 float width = textData->lineBounds[lineIndex].size.x - begin.x;
697 if(h == VuoHorizontalAlignment_Left)
698 point.x -= textData->width * .5;
699 else if(h == VuoHorizontalAlignment_Right)
700 point.x += textData->width * .5;
702 if(v == VuoVerticalAlignment_Bottom)
703 point.y -= textData->height * .5;
704 else if(v == VuoVerticalAlignment_Top)
705 point.y += textData->height * .5;
707 for(
int r = 0; r < textData->lineCount; r++)
709 bool lastLine = r == textData->lineCount - 1;
715 if(point.y > lineOrigin.y || lastLine)
718 if(point.x < lineOrigin.x)
722 else if(point.x > (lineOrigin.x + lineBounds.size.x))
723 return index + textData->lineCounts[r] - (lastLine ? 0 : 1);
725 for(
int i = 0; i < textData->lineCounts[r]; i++)
727 float advance = textData->charAdvance[index + i];
729 if(point.x < lineOrigin.x + advance * .5)
732 lineOrigin.x += advance;
735 return index + textData->lineCounts[r] + (lastLine ? 1 : 0);
738 index += textData->lineCounts[r];
742 return textData->charCount;
757 if (font.pointSize < 0.00001
758 || verticalScale < 0.00001)
762 static dispatch_once_t initCache = 0;
763 dispatch_once(&initCache, ^ {
766 VuoFontClass fc(font, backingScaleFactor, verticalScale, rotation, wrapWidth);
774 VuoImage image = e->second.first.first;
776 memcpy(outCorners, &e->second.first.second[0],
sizeof(VuoPoint2d)*4);
788 CGColorSpaceRef colorspace;
792 CGAffineTransform transform;
793 CFArrayRef ctLines =
VuoImageText_createCTLines(text, font, backingScaleFactor, verticalScale, rotation, wrapWidth,
false, &ctFont, &cgColor, &colorspace, textData, &transform);
798 CGContextRef cgContext = CGBitmapContextCreate(NULL, textData->width, textData->height, 8, textData->width * 4, colorspace, kCGImageAlphaPremultipliedLast);
820 CGContextSetTextMatrix(cgContext, CGAffineTransformMakeScale(1.0, -1.0));
823 memcpy(outCorners, textData->transformedCorners,
sizeof(VuoPoint2d)*4);
826 CGContextTranslateCTM(cgContext, textData->transformedCorners[0].x, textData->transformedCorners[0].y);
829 CGContextTranslateCTM(cgContext, fabs(cos(rotation)), fabs(sin(rotation)));
832 CGContextConcatCTM(cgContext, transform);
837 CFIndex lineCount = CFArrayGetCount(ctLines);
838 for (CFIndex i = 0; i < lineCount; ++i)
840 CTLineRef ctLine = (CTLineRef)CFArrayGetValueAtIndex(ctLines, i);
842 float textXPosition = -(bounds.center.x - (bounds.size.x * .5));
843 if (font.alignment == VuoHorizontalAlignment_Center)
844 textXPosition += (bounds.size.x - textData->lineWidthsExcludingTrailingWhitespace[i] - textData->lineXOrigins[i]) / 2.;
845 else if (font.alignment == VuoHorizontalAlignment_Right)
846 textXPosition += bounds.size.x - textData->lineWidthsExcludingTrailingWhitespace[i] - textData->lineXOrigins[i];
848 float textYPosition = -(bounds.center.y - (bounds.size.y * .5));
849 textYPosition += textData->lineHeight * i * font.
lineSpacing;
851 CGContextSetTextPosition(cgContext, textXPosition, textYPosition);
852 CTLineDraw(ctLine, cgContext);
868 VuoImage image =
VuoImage_makeFromBuffer(CGBitmapContextGetData(cgContext), GL_RGBA, textData->width, textData->height, VuoImageColorDepth_8, ^(
void *buffer) { CGContextRelease(cgContext); });
871 CGColorSpaceRelease(colorspace);
872 CGColorRelease(cgColor);
887 VuoImage cachedImage = it->second.first.first;
889 memcpy(outCorners, &it->second.first.second[0],
sizeof(VuoPoint2d)*4);
897 memcpy(&c[0], textData->transformedCorners,
sizeof(VuoPoint2d)*4);
899 (*VuoImageTextCache)[descriptor] = e;