PageRenderTime 43ms CodeModel.GetById 14ms RepoModel.GetById 1ms app.codeStats 0ms

/Core/Source/DTCoreTextGlyphRun.m

https://gitlab.com/Mr.Tomato/DTCoreText
Objective C | 539 lines | 401 code | 114 blank | 24 comment | 52 complexity | 8e3851b1e733edccd2a987ba3c457cdf MD5 | raw file
  1. //
  2. // DTCoreTextGlyphRun.m
  3. // DTCoreText
  4. //
  5. // Created by Oliver Drobnik on 1/25/11.
  6. // Copyright 2011 Drobnik.com. All rights reserved.
  7. //
  8. #import "DTCoreTextGlyphRun.h"
  9. #import "DTCoreTextLayoutLine.h"
  10. #import "DTTextAttachment.h"
  11. #import "DTCoreTextConstants.h"
  12. #import "DTCoreTextParagraphStyle.h"
  13. #import "DTCoreTextFunctions.h"
  14. #import "NSDictionary+DTCoreText.h"
  15. #import "DTWeakSupport.h"
  16. #import "DTLog.h"
  17. @implementation DTCoreTextGlyphRun
  18. {
  19. CTRunRef _run;
  20. CGRect _frame;
  21. CGFloat _offset; // x distance from line origin
  22. CGFloat _ascent;
  23. CGFloat _descent;
  24. CGFloat _leading;
  25. CGFloat _width;
  26. BOOL _writingDirectionIsRightToLeft;
  27. BOOL _isTrailingWhitespace;
  28. NSInteger _numberOfGlyphs;
  29. const CGPoint *_glyphPositionPoints;
  30. DT_WEAK_VARIABLE DTCoreTextLayoutLine *_line; // retain cycle, since these objects are retained by the _line
  31. DT_WEAK_VARIABLE NSDictionary *_attributes; // weak because it is owned by _run IVAR
  32. NSArray *_stringIndices;
  33. DTTextAttachment *_attachment;
  34. BOOL _hyperlink;
  35. BOOL _didCheckForAttachmentInAttributes;
  36. BOOL _didCheckForHyperlinkInAttributes;
  37. BOOL _didCalculateMetrics;
  38. BOOL _didDetermineTrailingWhitespace;
  39. }
  40. - (id)initWithRun:(CTRunRef)run layoutLine:(DTCoreTextLayoutLine *)layoutLine offset:(CGFloat)offset
  41. {
  42. self = [super init];
  43. if (self)
  44. {
  45. _run = run;
  46. CFRetain(_run);
  47. _offset = offset;
  48. _line = layoutLine;
  49. }
  50. return self;
  51. }
  52. - (void)dealloc
  53. {
  54. if (_run)
  55. {
  56. CFRelease(_run);
  57. }
  58. }
  59. #ifndef COVERAGE
  60. // exclude method from coverage testing
  61. - (NSString *)description
  62. {
  63. return [NSString stringWithFormat:@"<%@ glyphs=%ld %@>", [self class], (long)[self numberOfGlyphs], NSStringFromCGRect(_frame)];
  64. }
  65. #endif
  66. #pragma mark - Drawing
  67. - (void)drawInContext:(CGContextRef)context
  68. {
  69. if (!_run || !context)
  70. {
  71. return;
  72. }
  73. CGAffineTransform textMatrix = CTRunGetTextMatrix(_run);
  74. if (CGAffineTransformIsIdentity(textMatrix))
  75. {
  76. CTRunDraw(_run, context, CFRangeMake(0, 0));
  77. }
  78. else
  79. {
  80. CGPoint pos = CGContextGetTextPosition(context);
  81. // set tx and ty to current text pos according to docs
  82. textMatrix.tx = pos.x;
  83. textMatrix.ty = pos.y;
  84. CGContextSetTextMatrix(context, textMatrix);
  85. CTRunDraw(_run, context, CFRangeMake(0, 0));
  86. // restore identity
  87. CGContextSetTextMatrix(context, CGAffineTransformIdentity);
  88. }
  89. }
  90. - (void)drawDecorationInContext:(CGContextRef)context
  91. {
  92. // get the scaling factor of the current translation matrix
  93. CGAffineTransform ctm = CGContextGetCTM(context);
  94. CGFloat contentScale = MAX(ctm.a, -ctm.d); // needed for rounding operations
  95. if (contentScale<1 || contentScale>2)
  96. {
  97. contentScale = 2;
  98. }
  99. CGFloat smallestPixelWidth = 1.0f/contentScale;
  100. DTColor *backgroundColor = [self.attributes backgroundColor];
  101. // -------------- Line-Out, Underline, Background-Color
  102. BOOL drawStrikeOut = [[_attributes objectForKey:DTStrikeOutAttribute] boolValue];
  103. BOOL drawUnderline = [[_attributes objectForKey:(id)kCTUnderlineStyleAttributeName] boolValue];
  104. if (drawStrikeOut||drawUnderline||backgroundColor)
  105. {
  106. // calculate area covered by non-whitespace
  107. CGRect lineFrame = _line.frame;
  108. // LTR line frames include trailing whitespace in width
  109. // we need to subtract it so that we don't highlight/underline it
  110. if (!_line.writingDirectionIsRightToLeft)
  111. {
  112. lineFrame.size.width -= _line.trailingWhitespaceWidth;
  113. }
  114. // exclude trailing whitespace so that we don't underline too much
  115. CGRect runStrokeBounds = CGRectIntersection(lineFrame, self.frame);
  116. NSInteger superscriptStyle = [[_attributes objectForKey:(id)kCTSuperscriptAttributeName] integerValue];
  117. switch (superscriptStyle)
  118. {
  119. case 1:
  120. {
  121. runStrokeBounds.origin.y -= _ascent * 0.47f;
  122. break;
  123. }
  124. case -1:
  125. {
  126. runStrokeBounds.origin.y += _ascent * 0.25f;
  127. break;
  128. }
  129. default:
  130. break;
  131. }
  132. if (backgroundColor)
  133. {
  134. CGRect backgroundColorRect = CGRectIntegral(CGRectMake(runStrokeBounds.origin.x, lineFrame.origin.y, runStrokeBounds.size.width, lineFrame.size.height));
  135. CGContextSetFillColorWithColor(context, backgroundColor.CGColor);
  136. CGContextFillRect(context, backgroundColorRect);
  137. }
  138. if (drawStrikeOut || drawUnderline)
  139. {
  140. BOOL didDrawSomething = NO;
  141. CGContextSaveGState(context);
  142. CTFontRef usedFont = (__bridge CTFontRef)([_attributes objectForKey:(id)kCTFontAttributeName]);
  143. CGFloat fontUnderlineThickness;
  144. if (usedFont)
  145. {
  146. fontUnderlineThickness = CTFontGetUnderlineThickness(usedFont) * smallestPixelWidth;
  147. }
  148. else
  149. {
  150. fontUnderlineThickness = smallestPixelWidth;
  151. }
  152. CGFloat usedUnderlineThickness = DTCeilWithContentScale(fontUnderlineThickness, contentScale);
  153. CGContextSetLineWidth(context, usedUnderlineThickness);
  154. if (drawStrikeOut)
  155. {
  156. CGFloat y;
  157. if (usedFont)
  158. {
  159. CGFloat strokePosition = CTFontGetXHeight(usedFont)/(CGFloat)2.0;
  160. y = DTRoundWithContentScale(runStrokeBounds.origin.y + _ascent - strokePosition, contentScale);
  161. }
  162. else
  163. {
  164. y = DTRoundWithContentScale((runStrokeBounds.origin.y + self.frame.size.height/2.0f + 1), contentScale);
  165. }
  166. if ((int)(usedUnderlineThickness/smallestPixelWidth)%2) // odd line width
  167. {
  168. y += smallestPixelWidth/2.0f; // shift down half a pixel to avoid aliasing
  169. }
  170. CGContextMoveToPoint(context, runStrokeBounds.origin.x, y);
  171. CGContextAddLineToPoint(context, runStrokeBounds.origin.x + runStrokeBounds.size.width, y);
  172. didDrawSomething = YES;
  173. }
  174. // only draw underlines if Core Text didn't draw them yet
  175. if (drawUnderline && !DTCoreTextDrawsUnderlinesWithGlyphs())
  176. {
  177. CGFloat y;
  178. // use lowest underline position of all glyph runs in same line
  179. CGFloat underlinePosition = [_line underlineOffset];
  180. y = DTRoundWithContentScale(_line.baselineOrigin.y + underlinePosition - fontUnderlineThickness/2.0f, contentScale);
  181. if ((int)(usedUnderlineThickness/smallestPixelWidth)%2) // odd line width
  182. {
  183. y += smallestPixelWidth/2.0f; // shift down half a pixel to avoid aliasing
  184. }
  185. CGContextMoveToPoint(context, runStrokeBounds.origin.x, y);
  186. CGContextAddLineToPoint(context, runStrokeBounds.origin.x + runStrokeBounds.size.width, y);
  187. didDrawSomething = YES;
  188. }
  189. if (didDrawSomething)
  190. {
  191. CGContextStrokePath(context);
  192. }
  193. CGContextRestoreGState(context); // restore antialiasing
  194. }
  195. }
  196. }
  197. - (CGPathRef)newPathWithGlyphs
  198. {
  199. CTFontRef font = (__bridge CTFontRef)[self.attributes objectForKey:(id)kCTFontAttributeName];
  200. if (!font)
  201. {
  202. DTLogError(@"CTFont missing on %@", self);
  203. return NULL;
  204. }
  205. const CGGlyph *glyphs = CTRunGetGlyphsPtr(_run);
  206. const CGPoint *positions = CTRunGetPositionsPtr(_run);
  207. CGMutablePathRef mutablePath = CGPathCreateMutable();
  208. for (NSUInteger i = 0; i < CTRunGetGlyphCount(_run); i++)
  209. {
  210. CGGlyph glyph = glyphs[i];
  211. CGPoint position = positions[i];
  212. CGAffineTransform glyphTransform = CTRunGetTextMatrix(_run);
  213. glyphTransform = CGAffineTransformScale(glyphTransform, 1, -1);
  214. CGPathRef glyphPath = CTFontCreatePathForGlyph(font, glyph, &glyphTransform);
  215. CGAffineTransform posTransform = CGAffineTransformMakeTranslation(position.x, position.y);
  216. CGPathAddPath(mutablePath, &posTransform, glyphPath);
  217. CGPathRelease(glyphPath);
  218. }
  219. return mutablePath;
  220. }
  221. #pragma mark - Calculations
  222. - (void)calculateMetrics
  223. {
  224. // calculate metrics
  225. @synchronized(self)
  226. {
  227. if (!_didCalculateMetrics)
  228. {
  229. _width = (CGFloat)CTRunGetTypographicBounds((CTRunRef)_run, CFRangeMake(0, 0), &_ascent, &_descent, &_leading);
  230. _didCalculateMetrics = YES;
  231. }
  232. }
  233. }
  234. - (CGRect)frameOfGlyphAtIndex:(NSInteger)index
  235. {
  236. if (!_didCalculateMetrics)
  237. {
  238. [self calculateMetrics];
  239. }
  240. if (!_glyphPositionPoints)
  241. {
  242. // this is a pointer to the points inside the run, thus no retain necessary
  243. _glyphPositionPoints = CTRunGetPositionsPtr(_run);
  244. }
  245. if (!_glyphPositionPoints || index >= self.numberOfGlyphs)
  246. {
  247. return CGRectNull;
  248. }
  249. CGPoint glyphPosition = _glyphPositionPoints[index];
  250. CGRect rect = CGRectMake(_line.baselineOrigin.x + glyphPosition.x, _line.baselineOrigin.y - _ascent, _offset + _width - glyphPosition.x, _ascent + _descent);
  251. if (index < self.numberOfGlyphs-1)
  252. {
  253. rect.size.width = _glyphPositionPoints[index+1].x - glyphPosition.x;
  254. }
  255. return rect;
  256. }
  257. // TODO: fix indices if the stringRange is modified
  258. - (NSArray *)stringIndices
  259. {
  260. if (!_stringIndices)
  261. {
  262. const CFIndex *indices = CTRunGetStringIndicesPtr(_run);
  263. NSInteger count = self.numberOfGlyphs;
  264. NSMutableArray *array = [NSMutableArray arrayWithCapacity:count];
  265. NSInteger i;
  266. for (i = 0; i < count; i++)
  267. {
  268. [array addObject:[NSNumber numberWithInteger:indices[i]]];
  269. }
  270. _stringIndices = array;
  271. }
  272. return _stringIndices;
  273. }
  274. // bounds of an image encompassing the entire run
  275. - (CGRect)imageBoundsInContext:(CGContextRef)context
  276. {
  277. return CTRunGetImageBounds(_run, context, CFRangeMake(0, 0));
  278. }
  279. // range of the characters from the original string
  280. - (NSRange)stringRange
  281. {
  282. if (!_stringRange.length)
  283. {
  284. CFRange range = CTRunGetStringRange(_run);
  285. _stringRange = NSMakeRange(range.location + _line.stringLocationOffset, range.length);
  286. }
  287. return _stringRange;
  288. }
  289. - (void)fixMetricsFromAttachment
  290. {
  291. if (self.attachment)
  292. {
  293. if (!_didCalculateMetrics)
  294. {
  295. [self calculateMetrics];
  296. }
  297. _descent = 0;
  298. _ascent = self.attachment.displaySize.height;
  299. }
  300. }
  301. - (BOOL)isTrailingWhitespace
  302. {
  303. if (_didDetermineTrailingWhitespace)
  304. {
  305. return _isTrailingWhitespace;
  306. }
  307. BOOL isTrailing;
  308. if (_line.writingDirectionIsRightToLeft)
  309. {
  310. isTrailing = (self == [[_line glyphRuns] objectAtIndex:0]);
  311. }
  312. else
  313. {
  314. isTrailing = (self == [[_line glyphRuns] lastObject]);
  315. }
  316. if (isTrailing)
  317. {
  318. if (!_didCalculateMetrics)
  319. {
  320. [self calculateMetrics];
  321. }
  322. // this is trailing whitespace if it matches the lines's trailing whitespace
  323. if (_line.trailingWhitespaceWidth >= _width)
  324. {
  325. _isTrailingWhitespace = YES;
  326. }
  327. }
  328. _didDetermineTrailingWhitespace = YES;
  329. return _isTrailingWhitespace;
  330. }
  331. #pragma mark Properites
  332. - (NSInteger)numberOfGlyphs
  333. {
  334. if (!_numberOfGlyphs)
  335. {
  336. _numberOfGlyphs = CTRunGetGlyphCount(_run);
  337. }
  338. return _numberOfGlyphs;
  339. }
  340. - (NSDictionary *)attributes
  341. {
  342. if (!_attributes)
  343. {
  344. _attributes = (__bridge NSDictionary *)CTRunGetAttributes(_run);
  345. }
  346. return _attributes;
  347. }
  348. - (DTTextAttachment *)attachment
  349. {
  350. if (!_attachment)
  351. {
  352. if (!_didCheckForAttachmentInAttributes)
  353. {
  354. _attachment = [self.attributes objectForKey:NSAttachmentAttributeName];
  355. _didCheckForAttachmentInAttributes = YES;
  356. }
  357. }
  358. return _attachment;
  359. }
  360. - (BOOL)isHyperlink
  361. {
  362. if (!_hyperlink)
  363. {
  364. if (!_didCheckForHyperlinkInAttributes)
  365. {
  366. _hyperlink = [self.attributes objectForKey:DTLinkAttribute]!=nil;
  367. _didCheckForHyperlinkInAttributes = YES;
  368. }
  369. }
  370. return _hyperlink;
  371. }
  372. - (CGRect)frame
  373. {
  374. if (!_didCalculateMetrics)
  375. {
  376. [self calculateMetrics];
  377. }
  378. return CGRectMake(_line.baselineOrigin.x + _offset, _line.baselineOrigin.y - _ascent, _width, _ascent + _descent);
  379. }
  380. - (CGFloat)width
  381. {
  382. if (!_didCalculateMetrics)
  383. {
  384. [self calculateMetrics];
  385. }
  386. return _width;
  387. }
  388. - (CGFloat)ascent
  389. {
  390. if (!_didCalculateMetrics)
  391. {
  392. [self calculateMetrics];
  393. }
  394. return _ascent;
  395. }
  396. - (CGFloat)descent
  397. {
  398. if (!_didCalculateMetrics)
  399. {
  400. [self calculateMetrics];
  401. }
  402. return _descent;
  403. }
  404. - (CGFloat)leading
  405. {
  406. if (!_didCalculateMetrics)
  407. {
  408. [self calculateMetrics];
  409. }
  410. return _leading;
  411. }
  412. - (BOOL)writingDirectionIsRightToLeft
  413. {
  414. CTRunStatus status = CTRunGetStatus(_run);
  415. return (status & kCTRunStatusRightToLeft)!=0;
  416. }
  417. @synthesize frame = _frame;
  418. @synthesize numberOfGlyphs = _numberOfGlyphs;
  419. @synthesize attributes = _attributes;
  420. @synthesize ascent = _ascent;
  421. @synthesize descent = _descent;
  422. @synthesize leading = _leading;
  423. @synthesize attachment = _attachment;
  424. @synthesize writingDirectionIsRightToLeft = _writingDirectionIsRightToLeft;
  425. @end