coretext(转载http://www.padovo.com/blog/2013/01/31/study-coretext/)

Study CoreTextJAN 31ST, 2013 | COMMENTS前言好久没写日志,之前说过至少一天一篇,至了第二天就没开始做了。太忙了,有些疲于奔命,因为凉茶项目和阿拉丁项目的原因,自己有点力不从心了。引言学习CoreText,最初的想法是写一个杂志类的应用,因为对网易和zarca应用一些技术的疑问,所以,自己有了很强的兴趣欲和钻研欲,开始这段有点不顺的学习过程。难题1、对CGContextRef的CTM不理解,观念导致很多东西没有正确的理解。2、对NS的了解不多,一些文字绘制方面的座标系问题让自己很迷惑。3、对CoreText麻烦的API严重不适应。关于CTMCTM,Context Translate Matrix。 它是把要绘制的上下文以一个叫做Matrix的东西来表示,可以简单地想作,绘制的上下文的每一个点都映射在Matrix上,你在Matrix上的操作都会使得上下文上的点产生相应的变动。如放大、旋转、移动。在一般的教程里面,为了达到旋转或放大缩小的目的,一般都会先改变这个上下文,如:12345CGContextTranslateCTM(context, 0, self.bounds.size.height);CGContextScaleCTM(context, 1.0f, -1.0f);// some draw code// ....然后进行绘图操作。那么这个绘图操作是怎么做的呢?这个对Matrix的操作,为什么是放在前面而不是放在后面,为什么放在后面又没有效果呢?不是说改变Matrix就会改变上面的映射的所有点呢?这些常规的逻辑思维使得问题越发无法理解和解决。那么我们先从context来了解。一般情况也,我们总是认为context就是画布,所有的matrix旋转都是针对画布的旋转,虽然这样的理解是错误的,但是得到的结果却是正确的,但是如果在一些稍复杂的坐标系转换时,或者更改matrix时在之前或之后的理解时,这样理解就会得到难得理解的结果。其实context说的是绘画人所处的角度上下文。如下图,默认的情况下,绘画人的角度是正对着画布的:画布是白色的,而我则是在左上角用一个黄色的三角形来标识它的左上角,使用left top来标识context的左上角,而绘画人是黄色的圆形。要记着!!画布无论怎么样都是正对着屏幕的,它不会旋转,或者放大缩小,或者移动。那么为什么又看起来我放大了或者移动了呢?其实移动的是你的context,也就是你所处的context视角,我举个例子,比方说我要旋转180度在左上角写一个“abcdefg”。首先,我要先旋转180度:然后,我在左上角写上“abcdefg”:然后重置context:可以看到,我们改变context只是改变了自己面对画布的角度,而画布仍然是正对着屏幕的,自己始终以context的左上角为自己角度的左上角,而不是以画布的左上角为左上角,也就是说,这时绘画时的座标(0,0)是你旋转后context的left top,而不是画布 的左上角,记着这一点很重要。所以,在绘画的时候,其实是倒着画在了画布的右下角上。而重置context,则是把自己正对着画布而已。这也就说清了为什么是在使用matrix更改context之后进行绘图有效(把自己面对画布的角度先调整了),而不是在画了之后再调整(因为你都画完了,再调整自己的角度还有什么用?)。正确理解使用matrix更改context的方式很重要,因为这涉及到坐标系的问题,之后的CoreText相当讨论会讲到一个例子。关于NS座标系NS坐标系是以左下角为(0,0),与iOS的坐标系在Y上是相反的,所以,在iOS进行CoreText进行绘图或文字的时候,X方向是一致的,但是Y则是倒过来的。如下图:那么怎么办呢?想想,仔细看上面这张图,貌似像是正常方向的倒影,但是水平线却在最上面。嗯,挪下来,然后再反过来,看一下效果。如下图:效果:效果果然如图所示,好!!可是是不是就这样完了呢?不是,还有一个更为重要的问题,这个时候,进行了两次的转换matrix,context的left top在哪里呢?根据之前的理论,那得让自己先把自己向下移,然后把头倒过来,OK,这下明白了,这下画布的左下角变成了context的左上角,别的都没变。这时,当你在(20, 20)画一个长方形,其实就是画布的(20, canvas.height - 20 + rect.size.height)的位置上画了个长方形,而且是倒过来的。仔细想想这个,有趣的事情还有很多,因为按照自己看过本文之前的理论,可能会非常惊讶为什么得到的结果和自己想的不一样,一直以为是在(20, 20)处画一个长方形,结果却刚好相反,这就是没有理解context及matrix的正确含义所致。关于CoreText的API这种CoreFoundation式的API让我觉得很不适应,和一开始接触Objective-C一样,我在骂娘。不过一定要注意一点,就是使用Create函数建立的对象引用,必须要使用CFRelease掉。CoreText是什么?这是一个低级的API,它的数据源是NSAttributedString。它可以根据NSAttributedString的定义的每个range的subNSAttributedString的样式进行对字符串的渲染。可以这样说,这是一个富文本渲染器。为什么要用CoreText不是有了UIWebView了吗?为什么还要用CoreText呢?首先,UIWebView是一个很重量级的东西,占用大量的内存,加载缓慢。其次,UIWebView做不了一些东西,比如说分列显示,这点肯定webpage做不到(我做了这么久web,直到现在也想不到办法)。最后,UIWebView做出来的东西总是让人觉得不够cool,不知怎的,你总会发现用CoreText做的东西相当的有杂志感。那么为什么要用CoreText呢?其实很重要的一点是那个无用的UILabel控件,一方面是不能控制行高,另一个功能有限,特别难以控制多行文本,让人蛋疼的地方是,多行文本时,它竟然只能垂直居中。另,使用CoreText可以很好地做一些个性化的东西,比如可以使用动画,这一点UIWebView做不到。它能做一些很cool的东西,比方说,杂志,新闻类的应用。这是我为什么使用CoreText的原因。CoreText的概念。CTFramesetterRef按名字就知道,这是一个setter,一个属性设置器,它引用了一些有用的对像,诸如字体之类的,它的工作就是,生成一个CTFrameRef对象。CTFrameRef这是一个Frame对象,用于表示一个绘制区域,它是由CTFramesetterRef生成。CTLineRef表示要绘制的一行,这个概念可以不用理解,一般情况下我们不会使用到它,但是如果想做更为彻底的定制工作的话,那么就要去看apple的文档了。CTFrameRef对象包含了多个line对象。CTNodeRef表示要绘制的某个节点(subNSAttributedString),每line对象包含多个node对象,这个节点表示着不同格式的NSAttributedString对象的如何绘制。这个也可以不用理解,一般情况下我们也不会使用到它,不过要是用于在中间插入图片的话,这个就要考虑了。CoreText是不支持中间插入图片的,不过我们可以在读到特殊标记的node的时候,返回不同的行高和行宽,预留空间,在绘制完coretext之后,在这些个空间处绘制相应的图片。CoreText怎么使用呢?上面写了一堆概念性的东西,一下子还真是看不懂。那么怎么用呢?其实流程是这样的: 1、生成要绘制的NSAttributedString对象。 2、生成一个CTFramesetterRef对象,然后创建一个CGPath对象,这个Path对象用于表示可绘制区域坐标值、长宽。 3、使用上面生成的setter和path生成一个CTFrameRef对象,这个对象包含了这两个对象的信息(字体信息、坐标信息),它就可以使用CTFrameDraw方法绘制了。这里有一个demo代码:1234567891011121314151617181920212223- (void)_drawRectWithContext:(CGContextRef)context{  // generate NSAttributedString object    NSAttributedString *contentAttrString = [self _generateAttributeString];  // path    CGRect bounds = CGRectInset(self.bounds, 10.0f, 10.0f);    CGMutablePathRef path = CGPathCreateMutable();    CGPathAddRect(path, NULL, bounds);    // ------------------------ begin draw    // draw coretext    CTFramesetterRef framesetter        = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)(contentAttrString));    CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, NULL);    CTFrameDraw(frame, context);    CFRelease(frame);    CFRelease(path);    CFRelease(framesetter);    // ------------------------ end draw}NSAttributedString呢?一直都在说CoreText的事情,那么怎么使用NSAttributedString呢?怎么设置NSAttributedString的属性,怎么设计行高,怎么设置字体大小,样式?首先得会设置属性,调用NSAttributedString的setAttributes:range:方法就可以设置属性:123// set attributes to attributed string[attrString setAttributes:attributes                    range:NSMakeRange(0, attrString.length)];那么都有哪些属性呢?有下面这些属性:123456789101112131415const CFStringRef kCTCharacterShapeAttributeName;const CFStringRef kCTFontAttributeName;const CFStringRef kCTKernAttributeName;const CFStringRef kCTLigatureAttributeName;const CFStringRef kCTForegroundColorAttributeName;const CFStringRef kCTForegroundColorFromContextAttributeName;const CFStringRef kCTParagraphStyleAttributeName;const CFStringRef kCTStrokeWidthAttributeName;const CFStringRef kCTStrokeColorAttributeName;const CFStringRef kCTSuperscriptAttributeName;const CFStringRef kCTUnderlineColorAttributeName;const CFStringRef kCTUnderlineStyleAttributeName;const CFStringRef kCTVerticalFormsAttributeName;const CFStringRef kCTGlyphInfoAttributeName;const CFStringRef kCTRunDelegateAttributeName下面是说明:kCTCharacterShapeAttributeNameControls glyph selection. Value must be a CFNumberRef object. Default is value is 0 (disabled). A non-zero value is interpreted as Apple Type Services kCharacterShapeType selector + 1 (seefor selectors). For example, an attribute value of 1 corresponds to kTraditionalCharactersSelector.

kCTFontAttributeName

The font of the text to which this attribute applies. The value associated with this attribute must be a CTFont object. Default is Helvetica 12.

kCTKernAttributeName

The amount to kern the next character. The value associated with this attribute must be a CFNumber float. Default is standard kerning. The kerning attribute indicates how many points the following character should be shifted from its default offset as defined by the current character’s font in points: a positive kern indicates a shift farther away from and a negative kern indicates a shift closer to the current character. If this attribute is not present, standard kerning is used. If this attribute is set to 0.0, no kerning is done at all.

kCTLigatureAttributeName

The type of ligatures to use. The value associated with this attribute must be a CFNumber object. Default is an integer value of 1. The ligature attribute determines what kinds of ligatures should be used when displaying the string. A value of 0 indicates that only ligatures essential for proper rendering of text should be used. A value of 1 indicates that standard ligatures should be used, and 2 indicates that all available ligatures should be used. Which ligatures are standard depends on the script and possibly the font. Arabic text, for example, requires ligatures for many character sequences but has a rich set of additional ligatures that combine characters. English text has no essential ligatures, and typically has only two standard ligatures, those for “fi” and “fl”—all others are considered more advanced or fancy.

kCTForegroundColorAttributeName

The foreground color of the text to which this attribute applies. The value associated with this attribute must be a CGColor object. Default value is black.

kCTForegroundColorFromContextAttributeName

Sets a foreground color using the context’s fill color. Value must be a CFBooleanRef object. Default is false. The reason this exists is because an NSAttributedString object defaults to a black color if no color attribute is set. This forces Core Text to set the color in the context. This attribute allows developers to sidestep this, making Core Text set nothing but font information in the CGContext. If set, this attribute also determines the color used by kCTUnderlineStyleAttributeName, in which case it overrides the foreground color.

kCTParagraphStyleAttributeName

The paragraph style of the text to which this attribute applies. A paragraph style object is used to specify things like line alignment, tab rulers, writing direction, and so on. Value must be a CTParagraphStyle object. Default is an empty CTParagraphStyle object. See CTParagraphStyle Reference for more information.

kCTStrokeWidthAttributeName

The stroke width. Value must be a CFNumberRef object. Default value is 0.0, or no stroke. This attribute, interpreted as a percentage of font point size, controls the text drawing mode: positive values effect drawing with stroke only; negative values are for stroke and fill. A typical value for outlined text is 3.0.

kCTStrokeColorAttributeName

The stroke color. Value must be a CGColorRef object. Default is the foreground color.

kCTSuperscriptAttributeName

Controls vertical text positioning. Value must be a CFNumberRef object. Default is integer value 0. If supported by the specified font, a value of 1 enables superscripting and a value of -1 enables subscripting.

kCTUnderlineColorAttributeName

The underline color. Value must be a CGColorRef object. Default is the foreground color.

kCTUnderlineStyleAttributeName

The style of underlining, to be applied at render time, for the text to which this attribute applies. Value must be a CFNumber object. Default is kCTUnderlineStyleNone. Set a value of something other than kCTUnderlineStyleNone to draw an underline. In addition, the constants listed in “CTUnderlineStyleModifiers” can be used to modify the look of the underline. The underline color is determined by the text’s foreground color.

kCTVerticalFormsAttributeName

The orientation of the glyphs in the text to which this attribute applies. Value must be a CFBoolean object. Default is False. A value of False indicates that horizontal glyph forms are to be used; True indicates that vertical glyph forms are to be used.

kCTGlyphInfoAttributeName

The glyph info object to apply to the text associated with this attribute. Value must be a CTGlyphInfo object. The glyph specified by this CTGlyphInfo object is assigned to the entire attribute range, provided that its contents match the specified base string and that the specified glyph is available in the font specified by kCTFontAttributeName. See CTGlyphInfo Reference for more information.

kCTRunDelegateAttributeName

The run-delegate object to apply to an attribute range of the string. The value must be a CTRunDelegate object. The run delegate controls such typographic traits as glyph ascent, descent, and width. The values returned by the embedded run delegate apply to each glyph resulting from the text in that range. Because an embedded object is only a display-time modification, you should avoid applying this attribute to a range of text with complex behavior, such as text having a change of writing direction or having combining marks. It is thus recommended you apply this attribute to a range containing the single character U+FFFC. See CTRunDelegate Reference for more information.

上面所述的东西貌似只是说明了设置字体与样式,却没有行高、缩进之类的东西哦!!

嗯,不是没有,而是CoreText把它当成是段落样式来设置了,也就是说,要设置kCTParagraphStyleAttributeName的属性就行。kCTParagraphStyleAttributeName属性的值是一个CTParagraphStyle对象,你需要把你想要设置的段落属性放进这个对象就可以设置行高之类的东西:


kCTParagraphStyleSpecifierAlignment = 0,

kCTParagraphStyleSpecifierFirstLineHeadIndent = 1,

kCTParagraphStyleSpecifierHeadIndent = 2,

kCTParagraphStyleSpecifierTailIndent = 3,

kCTParagraphStyleSpecifierTabStops = 4,

kCTParagraphStyleSpecifierDefaultTabInterval = 5,

kCTParagraphStyleSpecifierLineBreakMode = 6,

kCTParagraphStyleSpecifierLineHeightMultiple = 7,

kCTParagraphStyleSpecifierMaximumLineHeight = 8,

kCTParagraphStyleSpecifierMinimumLineHeight = 9,

kCTParagraphStyleSpecifierLineSpacing = 10,          /* deprecated */

kCTParagraphStyleSpecifierParagraphSpacing = 11,

kCTParagraphStyleSpecifierParagraphSpacingBefore = 12,

kCTParagraphStyleSpecifierBaseWritingDirection = 13,

kCTParagraphStyleSpecifierMaximumLineSpacing = 14,

kCTParagraphStyleSpecifierMinimumLineSpacing = 15,

kCTParagraphStyleSpecifierLineSpacingAdjustment = 16,

kCTParagraphStyleSpecifierLineBoundsOptions = 17,

kCTParagraphStyleSpecifierCount

下面是说明:

kCTParagraphStyleSpecifierAlignment

The text alignment. Natural text alignment is realized as left or right alignment, depending on the line sweep direction of the first script contained in the paragraph. Type: CTTextAlignment. Default: kCTNaturalTextAlignment. Application: CTFramesetter.

kCTParagraphStyleSpecifierFirstLineHeadIndent

The distance, in points, from the leading margin of a frame to the beginning of the paragraph’s first line. This value is always nonnegative. Type: CGFloat. Default: 0.0. Application: CTFramesetter.

kCTParagraphStyleSpecifierHeadIndent

The distance, in points, from the leading margin of a text container to the beginning of lines other than the first. This value is always nonnegative. Type: CGFloat Default: 0.0 Application: CTFramesetter

kCTParagraphStyleSpecifierTailIndent

The distance, in points, from the margin of a frame to the end of lines. If positive, this value is the distance from the leading margin (for example, the left margin in left-to-right text). If 0 or negative, it’s the distance from the trailing margin. Type: CGFloat. Default: 0.0. Application: CTFramesetter.

kCTParagraphStyleSpecifierTabStops

The CTTextTab objects, sorted by location, that define the tab stops for the paragraph style. Type: CFArray of CTTextTabRef. Default: 12 left-aligned tabs, spaced by 28.0 points. Application: CTFramesetter, CTTypesetter.

kCTParagraphStyleSpecifierDefaultTabInterval

The documentwide default tab interval. Tabs after the last specified by kCTParagraphStyleSpecifierTabStops are placed at integer multiples of this distance (if positive). Type: CGFloat. Default: 0.0. Application: CTFramesetter, CTTypesetter.

kCTParagraphStyleSpecifierLineBreakMode

The mode that should be used to break lines when laying out the paragraph’s text. Type: CTLineBreakMode. Default: kCTLineBreakByWordWrapping. Application: CTFramesetter

kCTParagraphStyleSpecifierLineHeightMultiple

The line height multiple. The natural line height of the receiver is multiplied by this factor (if positive) before being constrained by minimum and maximum line height. Type: CGFloat. Default: 0.0. Application: CTFramesetter.

kCTParagraphStyleSpecifierMaximumLineHeight

The maximum height that any line in the frame will occupy, regardless of the font size or size of any attached graphic. Glyphs and graphics exceeding this height will overlap neighboring lines. A maximum height of 0 implies no line height limit. This value is always nonnegative. Type: CGFloat. Default: 0.0. Application: CTFramesetter.

kCTParagraphStyleSpecifierMinimumLineHeight

The minimum height that any line in the frame will occupy, regardless of the font size or size of any attached graphic. This value is always nonnegative. Type: CGFloat. Default: 0.0. Application: CTFramesetter.

kCTParagraphStyleSpecifierLineSpacing

The space in points added between lines within the paragraph (commonly known as leading). This value is always nonnegative. Type: CGFloat. Default: 0.0. Application: CTFramesetter.

kCTParagraphStyleSpecifierParagraphSpacing

The space added at the end of the paragraph to separate it from the following paragraph. This value is always nonnegative and is determined by adding the previous paragraph’s kCTParagraphStyleSpecifierParagraphSpacing setting and the current paragraph’s kCTParagraphStyleSpecifierParagraphSpacingBefore setting. Type: CGFloat. Default: 0.0. Application: CTFramesetter.

kCTParagraphStyleSpecifierParagraphSpacingBefore

The distance between the paragraph’s top and the beginning of its text content. Type: CGFloat. Default: 0.0. Application: CTFramesetter.

kCTParagraphStyleSpecifierBaseWritingDirection

The base writing direction of the lines. Type: CTWritingDirection. Default: kCTWritingDirectionNatural. Application: CTFramesetter, CTTypesetter.

kCTParagraphStyleSpecifierCount

The number of style specifiers. The purpose is to simplify validation of style specifiers

那么怎么编码呢?也就是这些属性怎么用呢?下面是一个demo:

NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] initWithString:_content];

// line space

CTParagraphStyleSetting lineSpaceSetting;

lineSpaceSetting.spec = kCTParagraphStyleSpecifierLineSpacing;

lineSpaceSetting.value = &_lineSpace;

lineSpaceSetting.valueSize = sizeof(float);

// indent

CTParagraphStyleSetting indentSetting;

indentSetting.spec = kCTParagraphStyleSpecifierFirstLineHeadIndent;

indentSetting.value = &_indent;

indentSetting.valueSize = sizeof(float);

// composite settings

CTParagraphStyleSetting settings[] = {

lineSpaceSetting,

indentSetting

};

CTParagraphStyleRef style = CTParagraphStyleCreate(settings, 2);

// build attributes

NSDictionary *attributes = @{(__bridge id)kCTParagraphStyleAttributeName: (__bridge id)style};

// set attributes to attributed string

[attrString setAttributes:attributes

range:NSMakeRange(0, attrString.length)];

分栏显示

这个东西是相当有趣的东东,可以使用CTFrameGetVisibleStringRange函数来计算指定frame绘制了多少字符,那么就可以另建一个frame把剩余的字符绘制进去:


// get current context and store it's state

CGContextRef context = UIGraphicsGetCurrentContext();

CGContextSaveGState(context);

// translate CTM for iOS

CGContextTranslateCTM(context, 0, self.bounds.size.height);

CGContextScaleCTM(context, 1, -1);

// generate attributed string

NSAttributedString *attrString = [self _generateAttributedString];

// Draw code start -------------------------------------------------------------------------------------------------

CGRect bounds = CGRectInset(self.bounds, 25.0f, 25.0f);

float columnWidth = (bounds.size.width - 30.0f) / 2.0f;

float columnHeight = bounds.size.height;

CTFramesetterRef framesetter

= CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)(attrString));

int location = 0;

// ------------- first column

CGRect firstColumnRect = CGRectMake(bounds.origin.x, bounds.origin.y, columnWidth, columnHeight);

CGMutablePathRef firstColumnPath = CGPathCreateMutable();

CGPathAddRect(firstColumnPath, NULL, firstColumnRect);

CTFrameRef firstColumnFrame =

CTFramesetterCreateFrame(framesetter, CFRangeMake(location, 0), firstColumnPath, NULL);

CFRange firstColumnStringRange = CTFrameGetVisibleStringRange(firstColumnFrame);

CTFrameDraw(firstColumnFrame, context);

// recalculate the location for next frame.

location = firstColumnStringRange.length;

// ------------- second column

CGRect secondColumnRect =

CGRectMake(bounds.origin.x + 30 + columnWidth, bounds.origin.y, columnWidth, columnHeight);

CGMutablePathRef secondColumnPath = CGPathCreateMutable();

CGPathAddRect(secondColumnPath, NULL, secondColumnRect);

CTFrameRef secondColumnFrame =

CTFramesetterCreateFrame(framesetter,

CFRangeMake(location, 0),

secondColumnPath, NULL);

CTFrameDraw(secondColumnFrame, context);

// release

CFRelease(firstColumnPath);

CFRelease(firstColumnFrame);

CFRelease(secondColumnPath);

CFRelease(secondColumnFrame);

CFRelease(framesetter);

// Draw code end  -------------------------------------------------------------------------------------------------

// restore current context

CGContextRestoreGState(context);

总结

总的来说,CoreText是相当的麻烦的,所以,必须要对相关的代码进行一定的抽象。

这篇日志1月份就写了,直到现在才补完。一直害怕把这些内容忘记,于是就翻了下之前写好的测试代码,复习了一下,然后写完它。

好了,今天收工。

2013-3-1 智品公司

Posted by Kut.Zhang Jan 31st, 2013  iOS

你可能感兴趣的:(coretext(转载http://www.padovo.com/blog/2013/01/31/study-coretext/))