123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125 |
- /*
- * Copyright (c) Facebook, Inc. and its affiliates.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- */
- #import <React/ARTText.h>
- #import <CoreText/CoreText.h>
- @implementation ARTText
- - (void)setAlignment:(CTTextAlignment)alignment
- {
- [self invalidate];
- _alignment = alignment;
- }
- static void ARTFreeTextFrame(ARTTextFrame frame)
- {
- if (frame.count) {
- // We must release each line before freeing up this struct
- for (int i = 0; i < frame.count; i++) {
- CFRelease(frame.lines[i]);
- }
- free(frame.lines);
- free(frame.widths);
- }
- }
- - (void)setTextFrame:(ARTTextFrame)frame
- {
- if (frame.lines != _textFrame.lines) {
- ARTFreeTextFrame(_textFrame);
- }
- [self invalidate];
- _textFrame = frame;
- }
- - (void)dealloc
- {
- ARTFreeTextFrame(_textFrame);
- }
- - (void)renderLayerTo:(CGContextRef)context
- {
- ARTTextFrame frame = self.textFrame;
- if ((!self.fill && !self.stroke) || !frame.count) {
- return;
- }
- // to-do: draw along a path
- CGTextDrawingMode mode = kCGTextStroke;
- if (self.fill) {
- if ([self.fill applyFillColor:context]) {
- mode = kCGTextFill;
- } else {
- for (int i = 0; i < frame.count; i++) {
- CGContextSaveGState(context);
- // Inverse the coordinate space since CoreText assumes a bottom-up coordinate space
- CGContextScaleCTM(context, 1.0, -1.0);
- CGContextSetTextDrawingMode(context, kCGTextClip);
- [self renderLineTo:context atIndex:i];
- // Inverse the coordinate space back to the original before filling
- CGContextScaleCTM(context, 1.0, -1.0);
- [self.fill paint:context];
- // Restore the state so that the next line can be clipped separately
- CGContextRestoreGState(context);
- }
- if (!self.stroke) {
- return;
- }
- }
- }
- if (self.stroke) {
- CGContextSetStrokeColorWithColor(context, self.stroke);
- CGContextSetLineWidth(context, self.strokeWidth);
- CGContextSetLineCap(context, self.strokeCap);
- CGContextSetLineJoin(context, self.strokeJoin);
- ARTCGFloatArray dash = self.strokeDash;
- if (dash.count) {
- CGContextSetLineDash(context, 0, dash.array, dash.count);
- }
- if (mode == kCGTextFill) {
- mode = kCGTextFillStroke;
- }
- }
- CGContextSetTextDrawingMode(context, mode);
- // Inverse the coordinate space since CoreText assumes a bottom-up coordinate space
- CGContextScaleCTM(context, 1.0, -1.0);
- for (int i = 0; i < frame.count; i++) {
- [self renderLineTo:context atIndex:i];
- }
- }
- - (void)renderLineTo:(CGContextRef)context atIndex:(int)index
- {
- ARTTextFrame frame = self.textFrame;
- CGFloat shift;
- switch (self.alignment) {
- case kCTTextAlignmentRight:
- shift = frame.widths[index];
- break;
- case kCTTextAlignmentCenter:
- shift = (frame.widths[index] / 2);
- break;
- default:
- shift = 0;
- break;
- }
- // We should consider snapping this shift to device pixels to improve rendering quality
- // when a line has subpixel width.
- CGContextSetTextPosition(context, -shift, -frame.baseLine - frame.lineHeight * index);
- CTLineRef line = frame.lines[index];
- CTLineDraw(line, context);
- }
- @end
|