ARTText.m 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. /*
  2. * Copyright (c) Facebook, Inc. and its affiliates.
  3. *
  4. * This source code is licensed under the MIT license found in the
  5. * LICENSE file in the root directory of this source tree.
  6. */
  7. #import <React/ARTText.h>
  8. #import <CoreText/CoreText.h>
  9. @implementation ARTText
  10. - (void)setAlignment:(CTTextAlignment)alignment
  11. {
  12. [self invalidate];
  13. _alignment = alignment;
  14. }
  15. static void ARTFreeTextFrame(ARTTextFrame frame)
  16. {
  17. if (frame.count) {
  18. // We must release each line before freeing up this struct
  19. for (int i = 0; i < frame.count; i++) {
  20. CFRelease(frame.lines[i]);
  21. }
  22. free(frame.lines);
  23. free(frame.widths);
  24. }
  25. }
  26. - (void)setTextFrame:(ARTTextFrame)frame
  27. {
  28. if (frame.lines != _textFrame.lines) {
  29. ARTFreeTextFrame(_textFrame);
  30. }
  31. [self invalidate];
  32. _textFrame = frame;
  33. }
  34. - (void)dealloc
  35. {
  36. ARTFreeTextFrame(_textFrame);
  37. }
  38. - (void)renderLayerTo:(CGContextRef)context
  39. {
  40. ARTTextFrame frame = self.textFrame;
  41. if ((!self.fill && !self.stroke) || !frame.count) {
  42. return;
  43. }
  44. // to-do: draw along a path
  45. CGTextDrawingMode mode = kCGTextStroke;
  46. if (self.fill) {
  47. if ([self.fill applyFillColor:context]) {
  48. mode = kCGTextFill;
  49. } else {
  50. for (int i = 0; i < frame.count; i++) {
  51. CGContextSaveGState(context);
  52. // Inverse the coordinate space since CoreText assumes a bottom-up coordinate space
  53. CGContextScaleCTM(context, 1.0, -1.0);
  54. CGContextSetTextDrawingMode(context, kCGTextClip);
  55. [self renderLineTo:context atIndex:i];
  56. // Inverse the coordinate space back to the original before filling
  57. CGContextScaleCTM(context, 1.0, -1.0);
  58. [self.fill paint:context];
  59. // Restore the state so that the next line can be clipped separately
  60. CGContextRestoreGState(context);
  61. }
  62. if (!self.stroke) {
  63. return;
  64. }
  65. }
  66. }
  67. if (self.stroke) {
  68. CGContextSetStrokeColorWithColor(context, self.stroke);
  69. CGContextSetLineWidth(context, self.strokeWidth);
  70. CGContextSetLineCap(context, self.strokeCap);
  71. CGContextSetLineJoin(context, self.strokeJoin);
  72. ARTCGFloatArray dash = self.strokeDash;
  73. if (dash.count) {
  74. CGContextSetLineDash(context, 0, dash.array, dash.count);
  75. }
  76. if (mode == kCGTextFill) {
  77. mode = kCGTextFillStroke;
  78. }
  79. }
  80. CGContextSetTextDrawingMode(context, mode);
  81. // Inverse the coordinate space since CoreText assumes a bottom-up coordinate space
  82. CGContextScaleCTM(context, 1.0, -1.0);
  83. for (int i = 0; i < frame.count; i++) {
  84. [self renderLineTo:context atIndex:i];
  85. }
  86. }
  87. - (void)renderLineTo:(CGContextRef)context atIndex:(int)index
  88. {
  89. ARTTextFrame frame = self.textFrame;
  90. CGFloat shift;
  91. switch (self.alignment) {
  92. case kCTTextAlignmentRight:
  93. shift = frame.widths[index];
  94. break;
  95. case kCTTextAlignmentCenter:
  96. shift = (frame.widths[index] / 2);
  97. break;
  98. default:
  99. shift = 0;
  100. break;
  101. }
  102. // We should consider snapping this shift to device pixels to improve rendering quality
  103. // when a line has subpixel width.
  104. CGContextSetTextPosition(context, -shift, -frame.baseLine - frame.lineHeight * index);
  105. CTLineRef line = frame.lines[index];
  106. CTLineDraw(line, context);
  107. }
  108. @end