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