本文翻译整理自:Core Text Programming Guide(Updated: 2014-09-17
https://developer.apple.com/library/archive/documentation/StringsTextFonts/Conceptual/CoreText_Programming/Introduction/Introduction.html#//apple_ref/doc/uid/TP40005533
文章目录
- 一、关于 Core Text
- 1、概览
- 核心文本布局文本
- 您可以使用核心文本管理字体
- 2、先决条件
- 3、另见
- 二、核心文本概述
- 1、Core Text是一个基于C的平台中立API
- 2、核心文本对象是C语言不透明类型
- 核心文本不透明类型
- 字体对象
- 字体描述符
- 字体集合
- 三、通用文本布局操作
- 1、布置一个段落
- 2、简单文本标签
- 3、柱状布局
- 4、手动断线
- 5、应用段落样式
- 6、在非矩形区域中显示文本
- 四、常用字体操作
- 1、创建字体描述符
- 2、从字体描述符创建字体
- 3、创建相关字体
- 4、序列化字体
- 5、从序列化数据创建字体
- 6、更改字距
- 7、获取字符的字形
一、关于 Core Text
Core Text是一种用于布局文本和处理字体的高级低级技术。
Mac OS X v10.5和3.2iOS中引入的Core Text API可从所有OS X和iOS环境中访问。
重要提示: Core Text适用于必须在低级别进行文本布局和字体处理的开发人员,例如布局引擎的开发人员。
如果可能,您应该使用更高级别的框架开发您的应用程序——即在iOS中使用Text Kit(参见 文本编程指南iOS )或OS X中的Cocoa文本系统(参见 Cocoa文本架构指南 )。
Core Text是这些文本系统的底层技术,因此它们共享其速度和效率。
此外,Text Kit和Cocoa文本系统提供富文本编辑、全功能页面布局引擎和其他基础设施,如果您的应用程序单独使用Core Text,则需要提供这些基础设施。
1、概览
Core Text适用于需要与Core Graphics框架(Quartz)相关的低级文本处理技术的应用程序。
如果您直接使用Quartz并且需要绘制一些文本,请使用Core Text。
例如,如果您有自己的页面布局引擎——您有一些文本并且您知道它需要在视图中的位置——您可以使用Core Text生成字形并将它们相对定位,并具有精细排版的所有功能,例如字距调整、连字符、换行、断字和对齐。
核心文本布局文本
Core Text生成字形(来自字符代码和字体数据),并在字形运行中相对定位它们。
它将字形运行分解为行,并将行组装成多行框架(例如段落)。
Core Text还提供字形和布局相关数据,例如字形位置以及行和框架的测量。
它处理字符属性和段落样式,包括各种类型的标签样式和定位。
相关章节: 核心文本概述、常见文本布局操作
您可以使用核心文本管理字体
Core Text字体API提供字体、字体集合、字体描述符以及对字体数据的轻松访问。
它还提供对多种主字体、字体变体、字体级联和字体链接的支持。
Core Text提供了Quartz的替代方案,用于将您自己的字体加载到当前进程中,即字体激活。
相关章节: 常用字体操作
2、先决条件
要充分利用本文档,您应该了解文本系统和问题,并且应该知道如何使用Core Foundation不透明类型。
有关Core Foundation的信息,请参阅 Core Foundation设计概念 。
3、另见
除了本文档之外,还有几个文档涵盖了Core Text的更具体方面或描述了Core Text使用的软件服务。
- 核心文本参考集合 为核心文本布局和字体API提供完整的参考信息。
- CoreTextPageViewer (在iOS开发人员库中)展示了如何使用核心文本来显示大型文本。
- DownloadFont (在iOS开发人员库中)演示了如何按需下载字体。
- CoreTextRTF (在Mac Developer Library中)展示了如何使用Core Text在Cocoa应用程序的窗口中布局和绘制RTF内容。
- 使用Core Text with Cocoa沿着路径绘图 (在Mac开发人员库中)展示了如何使用Core Text沿着曲线布局和绘制字形。
- 核心基础设计概念 和 核心基础框架参考 描述了核心基础,一个为核心文本使用的常见数据类型和基本软件服务提供抽象的框架。
以下章节(在iOS开发人员库中)描述了iOS中的Text Kit:
- 绘图和管理文本中的 文本编程指南iOS 描述了iOS中的应用程序级文本处理系统。
- 有关与核心文本和其他文本系统相关的排版概念的信息,请参阅 iOS文本编程指南 中的排版概念。
以下文档(在Mac开发者库中)提供了描述OS X中Cocoa文本系统的留档入口点:
- Cocoa文本体系结构指南 介绍了Cocoa文本系统。
- 文本布局编程指南 描述了Cocoa文本布局引擎。
二、核心文本概述
Core Text是一种用于布局文本和处理字体的高级低级技术。
Core Text直接与Core Graphics(CG)(也称为Quartz)一起工作,后者是高速图形渲染引擎,在OS X和iOS中处理最低级别的二维成像。
重要提示: Core Text专为开发更高级别的文本处理框架而设计。
一般应用程序开发人员应在iOS中使用Text Kit(参见 文本编程指南iOS )或OS X中的Cocoa文本系统(参见 Cocoa文本架构指南 )。
Core Text介于更高级别框架提供的文本布局和字体支持以及Quartz为所有文本和字体框架提供的低级功能之间。
Quartz框架作用于字形及其位置。
Core Text知道字符如何映射到字体,并在调用Quartz渲染文本之前考虑有关样式、字体指标和其他属性的信息。
Quartz是在基本级别绘制字形的唯一方法,并且由于Core Text以Quartz直接可用的形式提供所有数据,因此结果是高性能的文本渲染。
多线程:如果客户端不改变任何参数,例如线程之间共享的属性字符串,则可以同时从多个线程调用Core Text函数。
1、Core Text是一个基于C的平台中立API
核心文本API在iOS和OS X上几乎相同,尽管OS X版本提供了一组更丰富的字体管理API,包括可变的字体集合。
但是,在平台之间移植代码时,必须考虑UIKit和AppKit之间的差异。
例如,您必须有一个Quartz图形上下文来渲染核心文本生成的字形,并且您在每个平台上获得的图形上下文不同。
您在iOS中绘制的视图在iOS中是UIView
子类,在OS X中是NSView
子类别。
您应该注意,CGRect
对象被传递到UIView``drawRect:
方法中,而drawRect:
的OS X版本被传递到一个NSRect
对象中。
(您可以使用OS X中的NSRectToCGRect
函数将传入的NSRect
对象转换为核心文本函数参数所需的`CGRect’对象。)
由UIView
函数UIGraphicsGetCurrentContext
返回的图形上下文相对于未经修改的Quartz图形上下文是翻转的(也就是说,UIView
返回的上下文的原点在左上角),因此您必须在iOS中而不是在OS X中重新翻转图形上下文。
有关此技术的代码示例,请参见例2-1。
核心文本尽可能使用系统数据类型和服务,并且您可以使用与OS X和iOS中的其他核心框架相关的相同约定。
例如,核心文本对许多输入和输出参数使用核心基础对象,因此您可以将它们存储在核心基础集合类中。
核心文本处理的其他对象,如CGPath
对象,由核心图形框架提供。
2、核心文本对象是C语言不透明类型
为了快速和简单,OS X和iOS中的许多低级库都是用普通C编写的。
使用Core Text时,您使用一组C函数,例如CTFramesetterCreateWithAttributedString
和CTFramesetterCreateFrame
,而不是Objective-C类和方法。
核心文本不透明类型
核心文本布局引擎通常使用属性字符串(CFAttributedStringRef
)和图形路径(CGPathRef
)。
属性字符串对象封装支持显示文本的字符串,并包含定义字符串中字符样式方面的属性(或“属性”),例如字体和颜色。
核心文本中的排版机制使用属性字符串中的信息来执行字符到字形的转换。
图形路径定义文本帧的形状。
在OS X v10.7和iOS3.2及更高版本中,路径可以是非矩形的。
cft引用类型,CFAttributedStringRef
,与它的Foundation对应物NSAttributedString
是免费桥接的。
这意味着Core Foundation类型在函数或方法调用中可以与桥接的Foundation对象互换。
因此,在一个方法中,您可以看到一个NSAttributedString *
参数,您可以传入一个CFAttributedStringRef
,而在一个函数中,您可以看到一个CFAttributedStringRef
参数,您可以传入一个NSAttributedString
实例。
(您可能需要将一种类型转换为另一种类型以抑制编译器警告。)这也适用于NSAttributedString
的具体子类。
这些属性是定义字符串中字符的样式特征的键值对,这些字符被分组在共享相同属性的范围内。
属性本身被传递到属性字符串中,并使用CFDicizard对象从中检索。
要将样式应用于字形运行(CTRun对象),请创建一个CFDicizard对象来保存要应用的属性,然后创建一个属性字符串,将字典作为参数传递。
或者,您可以将属性应用于已经存在的CFMutableAttributedString对象。
尽管CFDictionaryRef
和NSDictionary
是免费桥接的,但存储在字典中的单个属性对象可能不是。
运行时的核心文本对象形成层次结构,如图1-1所示。
该层次结构的顶部是框架设置器对象(CTFramesetterRef
)。
以属性字符串和图形路径作为输入,框架设置器生成一个或多个文本帧(CTFrameRef
)。
每个CTFrame对象代表一个段落。
图1-1 Core Text布局引擎的架构
为了生成框架,排帧器调用排字器对象(CTTypesetterRef
)。
当它在框架中布置文本时,排帧器会对其应用段落样式,包括对齐、制表符、行距、缩进和换行模式等属性。
排字器将属性字符串中的字符转换为字形,并将这些字形放入填充文本框架的行中。
每个CTFrame对象都包含段落的行(CTLine)对象。
每个行对象代表一行文本。
CTFrame对象可能只包含一个长的CTLine对象,也可能包含一组行。
行对象由排字员在框架设置操作期间创建,并且像框架一样,可以将自己直接绘制到图形上下文中。
每个CTLine对象都包含一个字形运行(CTRun)对象数组。
字形运行是一组共享相同属性和方向的连续字形。
排字器在从字符串、属性和字体对象生成行时创建字形运行。
这意味着一行由一个或多个字形运行构成。
如果需要,字形运行可以将自己绘制到图形上下文中,尽管大多数客户端不需要直接与字形运行交互。
字体对象
字体有助于相对于彼此排列字形,并用于在图形上下文中绘图时建立当前字体。
Core Text字体不透明类型CTFont是一个特定的字体实例,它封装了大量信息。
它的引用类型CTFontRef
是免费桥接的UIFont
在iOS和NSFont
在OS X中。
当您创建CTFont对象时,您通常会指定(或使用默认)点大小和转换矩阵,这会给出字体实例的特定特征。
然后,您可以查询字体对象以获取有关该特定点大小的字体的各种信息,例如字符到字形的映射、编码、字体度量数据和字形数据等。
字体度量是诸如上升、下降、领先、上限高度、x高度等参数。
字形数据包括边界矩形和字形前进等参数。
Core Text字体对象是不可变的,因此它们可以被多个操作、工作队列或线程同时使用。
有许多方法可以创建字体对象。
首选方法是从字体描述符使用CTFontCreateWithFontDescriptor
。
您还可以使用许多转换API,具体取决于您必须从什么开始。
例如,您可以使用字体的PostScript名称(CTFontCreateWithName
)或Core Graphics字体引用(CTFontCreateWithGraphicsFont
)。
还有CTFontCreateUIFontForLanguage
,它为您正在使用的本地化中的应用程序的用户界面字体创建引用。
Core Text字体引用提供了一种复杂的自动字体替换机制,称为字体级联,它选择适当的字体来替换缺失的字体,同时考虑字体特征。
字体级联基于级联列表,级联列表是有序字体描述符的数组。
有一个系统默认级联列表(它是多态的,基于用户的语言设置和当前字体)和一个在字体创建时指定的字体级联列表。
使用字体描述符中的信息,级联机制可以根据样式匹配字体以及匹配字符。
CTFontCreateForString
函数使用级联列表来选择适当的字体来编码给定的字符串。
要指定和检索字体级联列表,请使用kCTFontCascadeListAttribute
属性。
字体描述符
由CTFontDescriptor不透明类型表示的字体描述符提供了一种完全根据属性字典描述字体的机制,以及用于构建新字体的易于使用的字体匹配工具。
您可以从字体描述符中创建字体对象,也可以从字体对象中获取描述符,还可以更改描述符并使用它来创建新的字体对象。
您可以通过创建字体描述符来部分描述字体,例如,仅使用家族名称或权重,然后可以在系统上找到与给定特征匹配的所有字体。
CTFontDescriptorRef
类型免费桥接到iOS中的UIFontDescriptor
和OS X中的NSFontDescriptor
。
您无需处理复杂的转换矩阵,而是可以创建字体属性字典,其中包括PostScript名称、字体系列和样式以及特征(例如,粗体或斜体字)等属性作为CTFontDescriptor对象。
您可以使用字体描述符创建CTFont对象。
字体描述符可以序列化并存储在文档中,以提供字体的持久性。
图1-2说明了使用字体描述符创建特定字体实例的字体系统。
图1-2从字体描述符创建字体
您可以将字体描述符视为对字体系统的查询。
您可以创建一个规范不完整的字体描述符,也就是说,在属性字典中只有一个或几个值,系统将从可用的字体中选择最合适的字体。
例如,如果您使用描述符对具有标准面孔(正常、粗体、斜体、粗体斜体字)的家族名称进行查询,不指定任何特征将匹配家族中的所有面孔,但是如果您指定一个特征字典,其kCTFontTraitsAttribute
为kCTFontTraitBold
,结果将从整个家族进一步缩小到满足粗体特征的成员。
系统可以通过CTFontDescriptorCreateMatchingFontDescriptors
.
在iOS6.0及更高版本中,应用程序可以按需下载未使用CTFontDescriptorMatchFontDescriptorsWithProgressHandler
功能安装的可用字体。
以这种方式下载的字体不会永久安装,系统可能会在某些情况下将其删除。
可供下载的字体列在“附加信息”中iOS6:字体列表和iOS7:字体列表。
下载字体 (在iOS开发者库中)演示了下载技术。
在OS X中不需要按需下载字体,因为所有可用字体都随系统安装。
字体集合
字体集合是作为单个对象的字体描述符组。
字体集合由CTFontCollection不透明类型表示。
字体集合提供了字体枚举、访问全局和自定义字体集合以及访问包含该集合的字体描述符的功能。
例如,您可以通过调用CTFontCollectionCreateFromAvailableFonts
来创建系统中所有可用字体的字体集合,并且可以使用该集合来获取所有成员字体描述符的数组。
三、通用文本布局操作
本章描述了一些常见的文本布局操作,并展示了如何使用Core Text执行它们。
本章包含以下带有代码列表的操作:
- 布置一个段落
- 简单文本标签
- 柱状布局
- 手动断线
- 应用段落样式
- 在非矩形区域中显示文本
1、布置一个段落
排版中最常见的操作之一是在任意大小的矩形区域内布局多行段落。
Core Text使此操作变得简单,只需要几行Core Text特定的代码。
要布局段落,您需要一个图形上下文来绘制,一个矩形路径来提供文本布局的区域,以及一个属性字符串。
本例中的大部分代码都需要创建和初始化上下文、路径和字符串。
完成后,Core Text只需要三行代码来进行布局。
在[例2-1] 中的代码显示了一个段落是如何布局的,这段代码可以驻留在UIView
子类(OS X中的NSView
子类)的drawRect:
方法中。
例2-1排版一个简单的段落
// Initialize a graphics context in iOS.
CGContextRef context = UIGraphicsGetCurrentContext();
// Flip the context coordinates, in iOS only.
CGContextTranslateCTM(context, 0, self.bounds.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
// Initializing a graphic context in OS X is different:
// CGContextRef context =
// (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
// Set the text matrix.
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
// Create a path which bounds the area where you will be drawing text.
// The path need not be rectangular.
CGMutablePathRef path = CGPathCreateMutable();
// In this simple example, initialize a rectangular path.
CGRect bounds = CGRectMake(10.0, 10.0, 200.0, 200.0);
CGPathAddRect(path, NULL, bounds );
// Initialize a string.
CFStringRef textString = CFSTR("Hello, World! I know nothing in the world that has as much power as a word. Sometimes I write one, and I look at it, until it begins to shine.");
// Create a mutable attributed string with a max length of 0.
// The max length is a hint as to how much internal storage to reserve.
// 0 means no hint.
CFMutableAttributedStringRef attrString =
CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
// Copy the textString into the newly created attrString
CFAttributedStringReplaceString (attrString, CFRangeMake(0, 0),
textString);
// Create a color that will be added as an attribute to the attrString.
CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
CGFloat components[] = { 1.0, 0.0, 0.0, 0.8 };
CGColorRef red = CGColorCreate(rgbColorSpace, components);
CGColorSpaceRelease(rgbColorSpace);
// Set the color of the first 12 chars to red.
CFAttributedStringSetAttribute(attrString, CFRangeMake(0, 12),
kCTForegroundColorAttributeName, red);
// Create the framesetter with the attributed string.
CTFramesetterRef framesetter =
CTFramesetterCreateWithAttributedString(attrString);
CFRelease(attrString);
// Create a frame.
CTFrameRef frame = CTFramesetterCreateFrame(framesetter,
CFRangeMake(0, 0), path, NULL);
// Draw the specified frame in the given context.
CTFrameDraw(frame, context);
// Release the objects we used.
CFRelease(frame);
CFRelease(path);
CFRelease(framesetter);
2、简单文本标签
另一个常见的排版操作是绘制单行文本以用作用户界面元素的标签。
在Core Text中,这只需要两行代码:一行用于创建带有CF属性字符串的行对象,另一行用于将该行绘制到图形上下文中。
例2-2显示了如何在UIView
或NSView
子类的drawRect:
方法中完成此操作。
该例省略了纯文本字符串、字体和图形上下文的初始化,以及本文档其他例中显示的操作。
它显示了如何创建属性字典并使用它来创建属性字符串。
(字体创建显示在创建字体描述符和从字体描述符创建字体中。)
例2-2排版一个简单的文本标签
CFStringRef string; CTFontRef font; CGContextRef context;
// Initialize the string, font, and context
CFStringRef keys[] = { kCTFontAttributeName };
CFTypeRef values[] = { font };
CFDictionaryRef attributes =
CFDictionaryCreate(kCFAllocatorDefault, (const void**)&keys,
(const void**)&values, sizeof(keys) / sizeof(keys[0]),
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
CFAttributedStringRef attrString =
CFAttributedStringCreate(kCFAllocatorDefault, string, attributes);
CFRelease(string);
CFRelease(attributes);
CTLineRef line = CTLineCreateWithAttributedString(attrString);
// Set text position and draw the line into the graphics context
CGContextSetTextPosition(context, 10.0, 10.0);
CTLineDraw(line, context);
CFRelease(line);
3、柱状布局
在多列中布局文本是另一种常见的排版操作。
严格来说,Core Text本身一次只布局一列,不计算列大小或位置。
您可以在调用Core Text以在您计算的路径区域内布局文本之前执行这些操作。
在此示例中,Core Text除了在每列中布局文本外,还为每列提供文本字符串内的子范围。
例[2-3] 中的createColumnsWithColumnCount:
方法接受要绘制的列数作为参数,并返回一个路径数组,每列一个路径。
[例2-4] 包括一个drawRect:
method的实现,它调用本地createColumnsWithColumnCount
方法,该方法在此例中首先定义。
此代码位于UIView
子类(OS X中的NSView
子类)中。
该子类包括一个attributedString
,此处未显示该属性,但在此例中调用其访问器以返回要布局的属性字符串。
例2-3将视图划分为列
- (CFArrayRef)createColumnsWithColumnCount:(int)columnCount
{
int column;
CGRect* columnRects = (CGRect*)calloc(columnCount, sizeof(*columnRects));
// Set the first column to cover the entire view.
columnRects[0] = self.bounds;
// Divide the columns equally across the frame's width.
CGFloat columnWidth = CGRectGetWidth(self.bounds) / columnCount;
for (column = 0; column < columnCount - 1; column++) {
CGRectDivide(columnRects[column], &columnRects[column],
&columnRects[column + 1], columnWidth, CGRectMinXEdge);
}
// Inset all columns by a few pixels of margin.
for (column = 0; column < columnCount; column++) {
columnRects[column] = CGRectInset(columnRects[column], 8.0, 15.0);
}
// Create an array of layout paths, one for each column.
CFMutableArrayRef array =
CFArrayCreateMutable(kCFAllocatorDefault,
columnCount, &kCFTypeArrayCallBacks);
for (column = 0; column < columnCount; column++) {
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, columnRects[column]);
CFArrayInsertValueAtIndex(array, column, path);
CFRelease(path);
}
free(columnRects);
return array;
}
Listing 2-4 Performing columnar text layout
// Override drawRect: to draw the attributed string into columns.
// (In OS X, the drawRect: method of NSView takes an NSRect parameter,
// but that parameter is not used in this listing.)
- (void)drawRect:(CGRect)rect
{
// Initialize a graphics context in iOS.
CGContextRef context = UIGraphicsGetCurrentContext();
// Flip the context coordinates in iOS only.
CGContextTranslateCTM(context, 0, self.bounds.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
// Initializing a graphic context in OS X is different:
// CGContextRef context =
// (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
// Set the text matrix.
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
// Create the framesetter with the attributed string.
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(
(CFAttributedStringRef)self.attributedString);
// Call createColumnsWithColumnCount function to create an array of
// three paths (columns).
CFArrayRef columnPaths = [self createColumnsWithColumnCount:3];
CFIndex pathCount = CFArrayGetCount(columnPaths);
CFIndex startIndex = 0;
int column;
// Create a frame for each column (path).
for (column = 0; column < pathCount; column++) {
// Get the path for this column.
CGPathRef path = (CGPathRef)CFArrayGetValueAtIndex(columnPaths, column);
// Create a frame for this column and draw it.
CTFrameRef frame = CTFramesetterCreateFrame(
framesetter, CFRangeMake(startIndex, 0), path, NULL);
CTFrameDraw(frame, context);
// Start the next frame at the first character not visible in this frame.
CFRange frameRange = CTFrameGetVisibleStringRange(frame);
startIndex += frameRange.length;
CFRelease(frame);
}
CFRelease(columnPaths);
CFRelease(framesetter);
}
4、手动断线
在Core Text中,您通常不需要手动换行,除非您有特殊的断字过程或类似的要求。
排版器自动执行换行。
或者,Core Text使您能够准确指定每行文本的换行位置。
[例2-5] 显示了如何创建排版器,排版器使用的对象,并直接使用排版器来查找适当的换行并手动创建排版线。
此示例还显示了如何在绘图前居中。
此代码可能在UIView
子类(OS X中的NSView
子类)的drawRect:
方法中。
例2-5执行手动换行
double width; CGContextRef context; CGPoint textPosition; CFAttributedStringRef attrString;
// Initialize those variables.
// Create a typesetter using the attributed string.
CTTypesetterRef typesetter = CTTypesetterCreateWithAttributedString(attrString);
// Find a break for line from the beginning of the string to the given width.
CFIndex start = 0;
CFIndex count = CTTypesetterSuggestLineBreak(typesetter, start, width);
// Use the returned character count (to the break) to create the line.
CTLineRef line = CTTypesetterCreateLine(typesetter, CFRangeMake(start, count));
// Get the offset needed to center the line.
float flush = 0.5; // centered
double penOffset = CTLineGetPenOffsetForFlush(line, flush, width);
// Move the given text drawing position by the calculated offset and draw the line.
CGContextSetTextPosition(context, textPosition.x + penOffset, textPosition.y);
CTLineDraw(line, context);
// Move the index beyond the line break.
start += count;
5、应用段落样式
例2-6实现了一个将段落样式应用于属性字符串的函数。
该函数接受字体名称、点大小和增加或减少文本行间距的行距作为参数。
该函数由[例2-7] 中的代码调用,该代码创建一个纯文本字符串,使用applyParaStyle
函数使用给定的段落属性创建一个属性字符串,然后创建一个框架和框架,并绘制框架。
例2-6应用段落样式
NSAttributedString* applyParaStyle(
CFStringRef fontName , CGFloat pointSize,
NSString *plainText, CGFloat lineSpaceInc){
// Create the font so we can determine its height.
CTFontRef font = CTFontCreateWithName(fontName, pointSize, NULL);
// Set the lineSpacing.
CGFloat lineSpacing = (CTFontGetLeading(font) + lineSpaceInc) * 2;
// Create the paragraph style settings.
CTParagraphStyleSetting setting;
setting.spec = kCTParagraphStyleSpecifierLineSpacing;
setting.valueSize = sizeof(CGFloat);
setting.value = &lineSpacing;
CTParagraphStyleRef paragraphStyle = CTParagraphStyleCreate(&setting, 1);
// Add the paragraph style to the dictionary.
NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:
(__bridge id)font, (id)kCTFontNameAttribute,
(__bridge id)paragraphStyle,
(id)kCTParagraphStyleAttributeName, nil];
CFRelease(font);
CFRelease(paragraphStyle);
// Apply the paragraph style to the string to created the attributed string.
NSAttributedString* attrString = [[NSAttributedString alloc]
initWithString:(NSString*)plainText
attributes:attributes];
return attrString;
}
在[例2-7] 中,样式字符串用于创建帧表,代码使用帧表创建帧并绘制帧。
例2-7绘制样式段落
- (void)drawRect:(CGRect)rect {
// Initialize a graphics context in iOS.
CGContextRef context = UIGraphicsGetCurrentContext();
// Flip the context coordinates in iOS only.
CGContextTranslateCTM(context, 0, self.bounds.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
// Set the text matrix.
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
CFStringRef fontName = CFSTR("Didot Italic");
CGFloat pointSize = 24.0;
CFStringRef string = CFSTR("Hello, World! I know nothing in the world that has
as much power as a word. Sometimes I write one,
and I look at it, until it begins to shine.");
// Apply the paragraph style.
NSAttributedString* attrString = applyParaStyle(fontName, pointSize, string, 50.0);
// Put the attributed string with applied paragraph style into a framesetter.
CTFramesetterRef framesetter =
CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attrString);
// Create a path to fill the View.
CGPathRef path = CGPathCreateWithRect(rect, NULL);
// Create a frame in which to draw.
CTFrameRef frame = CTFramesetterCreateFrame(
framesetter, CFRangeMake(0, 0), path, NULL);
// Draw the frame.
CTFrameDraw(frame, context);
CFRelease(frame);
CGPathRelease(path);
CFRelease(framesetter);
}
在OS X中,NSView
drawRect:
方法接收NSRect
参数,但CGPathCreateWithRect
函数需要CGRect
参数。
因此,必须使用以下函数调用将NSRect
对象转换为CGRect
对象:
CGRect myRect = NSRectToCGRect([self bounds]);
此外,在OS X中,您获得的图形上下文不同,并且不会翻转其坐标,如[例2-7] 中的注释所示。
6、在非矩形区域中显示文本
在非矩形区域中显示文本的难点是描述非矩形路径。
[例2-8] 中的AddSquashedDonutPath
函数返回一个甜甜圈形状的路径。
一旦有了路径,只需调用通常的Core Text函数来应用属性并绘制。
例2-8在非矩形路径中显示文本
// Create a path in the shape of a donut.
static void AddSquashedDonutPath(CGMutablePathRef path,
const CGAffineTransform *m, CGRect rect)
{
CGFloat width = CGRectGetWidth(rect);
CGFloat height = CGRectGetHeight(rect);
CGFloat radiusH = width / 3.0;
CGFloat radiusV = height / 3.0;
CGPathMoveToPoint( path, m, rect.origin.x, rect.origin.y + height - radiusV);
CGPathAddQuadCurveToPoint( path, m, rect.origin.x, rect.origin.y + height,
rect.origin.x + radiusH, rect.origin.y + height);
CGPathAddLineToPoint( path, m, rect.origin.x + width - radiusH,
rect.origin.y + height);
CGPathAddQuadCurveToPoint( path, m, rect.origin.x + width,
rect.origin.y + height,
rect.origin.x + width,
rect.origin.y + height - radiusV);
CGPathAddLineToPoint( path, m, rect.origin.x + width,
rect.origin.y + radiusV);
CGPathAddQuadCurveToPoint( path, m, rect.origin.x + width, rect.origin.y,
rect.origin.x + width - radiusH, rect.origin.y);
CGPathAddLineToPoint( path, m, rect.origin.x + radiusH, rect.origin.y);
CGPathAddQuadCurveToPoint( path, m, rect.origin.x, rect.origin.y,
rect.origin.x, rect.origin.y + radiusV);
CGPathCloseSubpath( path);
CGPathAddEllipseInRect( path, m,
CGRectMake( rect.origin.x + width / 2.0 - width / 5.0,
rect.origin.y + height / 2.0 - height / 5.0,
width / 5.0 * 2.0, height / 5.0 * 2.0));
}
// Generate the path outside of the drawRect call so the path is calculated only once.
- (NSArray *)paths
{
CGMutablePathRef path = CGPathCreateMutable();
CGRect bounds = self.bounds;
bounds = CGRectInset(bounds, 10.0, 10.0);
AddSquashedDonutPath(path, NULL, bounds);
NSMutableArray *result =
[NSMutableArray arrayWithObject:CFBridgingRelease(path)];
return result;
}
- (void)drawRect:(CGRect)rect
{
[super drawRect:rect];
// Initialize a graphics context in iOS.
CGContextRef context = UIGraphicsGetCurrentContext();
// Flip the context coordinates in iOS only.
CGContextTranslateCTM(context, 0, self.bounds.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
// Set the text matrix.
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
// Initialize an attributed string.
CFStringRef textString = CFSTR("Hello, World! I know nothing in the world that
has as much power as a word. Sometimes I write one, and I look at it,
until it begins to shine.");
// Create a mutable attributed string.
CFMutableAttributedStringRef attrString =
CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
// Copy the textString into the newly created attrString.
CFAttributedStringReplaceString (attrString, CFRangeMake(0, 0), textString);
// Create a color that will be added as an attribute to the attrString.
CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
CGFloat components[] = { 1.0, 0.0, 0.0, 0.8 };
CGColorRef red = CGColorCreate(rgbColorSpace, components);
CGColorSpaceRelease(rgbColorSpace);
// Set the color of the first 13 chars to red.
CFAttributedStringSetAttribute(attrString, CFRangeMake(0, 13),
kCTForegroundColorAttributeName, red);
// Create the framesetter with the attributed string.
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(attrString);
// Create the array of paths in which to draw the text.
NSArray *paths = [self paths];
CFIndex startIndex = 0;
// In OS X, use NSColor instead of UIColor.
#define GREEN_COLOR [UIColor greenColor]
#define YELLOW_COLOR [UIColor yellowColor]
#define BLACK_COLOR [UIColor blackColor]
// For each path in the array of paths...
for (id object in paths) {
CGPathRef path = (__bridge CGPathRef)object;
// Set the background of the path to yellow.
CGContextSetFillColorWithColor(context, [YELLOW_COLOR CGColor]);
CGContextAddPath(context, path);
CGContextFillPath(context);
CGContextDrawPath(context, kCGPathStroke);
// Create a frame for this path and draw the text.
CTFrameRef frame = CTFramesetterCreateFrame(framesetter,
CFRangeMake(startIndex, 0), path, NULL);
CTFrameDraw(frame, context);
// Start the next frame at the first character not visible in this frame.
CFRange frameRange = CTFrameGetVisibleStringRange(frame);
startIndex += frameRange.length;
CFRelease(frame);
}
CFRelease(attrString);
CFRelease(framesetter);
}
四、常用字体操作
本章介绍了一些常见的字体处理操作,并展示了如何使用Core Text对它们进行编码。
这些操作在iOS和OS X上是相同的。
本章包含以下带有代码列表的操作:
- 创建字体描述符
- 从字体描述符创建字体
- 创建相关字体
- 序列化字体
- 从序列化数据创建字体
- 更改字距
- 获取字符的字形
1、创建字体描述符
例3-1中的示例函数从指定PostScript字体名称和点大小的参数值创建字体描述符。
例3-1根据名称和点大小创建字体描述符
CTFontDescriptorRef CreateFontDescriptorFromName(CFStringRef postScriptName,
CGFloat size)
{
return CTFontDescriptorCreateWithNameAndSize(postScriptName, size);
}
例3-2中的示例函数根据字体系列名称和字体特征创建字体描述符。
例3-2从系列和特征创建字体描述符
NSString* familyName = @"Papyrus";
CTFontSymbolicTraits symbolicTraits = kCTFontTraitCondensed;
CGFloat size = 24.0;
NSMutableDictionary* attributes = [NSMutableDictionary dictionary];
[attributes setObject:familyName forKey:(id)kCTFontFamilyNameAttribute];
// The attributes dictionary contains another dictionary, the traits dictionary,
// which in this example specifies only the symbolic traits.
NSMutableDictionary* traits = [NSMutableDictionary dictionary];
[traits setObject:[NSNumber numberWithUnsignedInt:symbolicTraits]
forKey:(id)kCTFontSymbolicTrait];
[attributes setObject:traits forKey:(id)kCTFontTraitsAttribute];
[attributes setObject:[NSNumber numberWithFloat:size]
forKey:(id)kCTFontSizeAttribute];
CTFontDescriptorRef descriptor =
CTFontDescriptorCreateWithAttributes((CFDictionaryRef)attributes);
CFRelease(descriptor);
2、从字体描述符创建字体
例3-3显示了如何创建字体描述符并使用它来创建字体。
当调用CTFontCreateWithFontDescriptor
时,通常为矩阵参数传递NULL
以指定默认(单位)矩阵。
CTFontCreateWithFontDescriptor
的大小和矩阵(第二和第三)参数覆盖字体描述符中指定的任何参数,除非它们未指定(大小为0.0
,矩阵为NULL
)。
例3-3从字体描述符创建字体
NSDictionary *fontAttributes =
[NSDictionary dictionaryWithObjectsAndKeys:
@"Courier", (NSString *)kCTFontFamilyNameAttribute,
@"Bold", (NSString *)kCTFontStyleNameAttribute,
[NSNumber numberWithFloat:16.0],
(NSString *)kCTFontSizeAttribute,
nil];
// Create a descriptor.
CTFontDescriptorRef descriptor =
CTFontDescriptorCreateWithAttributes((CFDictionaryRef)fontAttributes);
// Create a font using the descriptor.
CTFontRef font = CTFontCreateWithFontDescriptor(descriptor, 0.0, NULL);
CFRelease(descriptor);
3、创建相关字体
将现有字体转换为相关或相似的字体通常很有用。
例3-4中的示例函数展示了如何根据函数调用传递的布尔参数的值将字体设为粗体或非粗体。
如果当前字体系列没有请求的样式,则函数返回NULL
。
例3-4更改字体的特征
CTFontRef CreateBoldFont(CTFontRef font, Boolean makeBold)
{
CTFontSymbolicTraits desiredTrait = 0;
CTFontSymbolicTraits traitMask;
// If requesting that the font be bold, set the desired trait
// to be bold.
if (makeBold) desiredTrait = kCTFontBoldTrait;
// Mask off the bold trait to indicate that it is the only trait
// to be modified. As CTFontSymbolicTraits is a bit field,
// could change multiple traits if desired.
traitMask = kCTFontBoldTrait;
// Create a copy of the original font with the masked trait set to the
// desired value. If the font family does not have the appropriate style,
// returns NULL.
return CTFontCreateCopyWithSymbolicTraits(font, 0.0, NULL, desiredTrait, traitMask);
}
在例3-8中的示例函数将给定字体转换为另一个字体系列中的类似字体,如果可能的话保留特征。
它可能返回NULL
。
大小参数传入0.0
,矩阵参数传入NULL
会保留原始字体的大小。
例3-5将字体转换为另一个系列
CTFontRef CreateFontConvertedToFamily(CTFontRef font, CFStringRef family)
{
// Create a copy of the original font with the new family. This call
// attempts to preserve traits, and may return NULL if that is not possible.
// Pass in 0.0 and NULL for size and matrix to preserve the values from
// the original font.
return CTFontCreateCopyWithFamily(font, 0.0, NULL, family);
}
4、序列化字体
在例3-6中的示例函数展示了如何创建XML数据来序列化可以嵌入到文档中的字体。
或者,最好使用NSArchiver
。
这只是完成此任务的一种方法,但它保留了以后重新创建确切字体所需的字体中的所有数据。
例3-6序列化字体
CFDataRef CreateFlattenedFontData(CTFontRef font)
{
CFDataRef result = NULL;
CTFontDescriptorRef descriptor;
CFDictionaryRef attributes;
// Get the font descriptor for the font.
descriptor = CTFontCopyFontDescriptor(font);
if (descriptor != NULL) {
// Get the font attributes from the descriptor. This should be enough
// information to recreate the descriptor and the font later.
attributes = CTFontDescriptorCopyAttributes(descriptor);
if (attributes != NULL) {
// If attributes are a valid property list, directly flatten
// the property list. Otherwise we may need to analyze the attributes
// and remove or manually convert them to serializable forms.
// This is left as an exercise for the reader.
if (CFPropertyListIsValid(attributes, kCFPropertyListXMLFormat_v1_0)) {
result = CFPropertyListCreateXMLData(kCFAllocatorDefault, attributes);
}
}
}
return result;
}
5、从序列化数据创建字体
例3-7中的示例函数展示了如何从扁平化的XML数据创建字体引用。
它展示了如何取消扁平化字体属性并使用这些属性创建字体。
例3-7从序列化数据创建字体
CTFontRef CreateFontFromFlattenedFontData(CFDataRef iData)
{
CTFontRef font = NULL;
CFDictionaryRef attributes;
CTFontDescriptorRef descriptor;
// Create our font attributes from the property list.
// For simplicity, this example creates an immutable object.
// If you needed to massage or convert certain attributes
// from their serializable form to the Core Text usable form,
// do it here.
attributes =
(CFDictionaryRef)CFPropertyListCreateFromXMLData(
kCFAllocatorDefault,
iData, kCFPropertyListImmutable, NULL);
if (attributes != NULL) {
// Create the font descriptor from the attributes.
descriptor = CTFontDescriptorCreateWithAttributes(attributes);
if (descriptor != NULL) {
// Create the font from the font descriptor. This sample uses
// 0.0 and NULL for the size and matrix parameters. This
// causes the font to be created with the size and/or matrix
// that exist in the descriptor, if present. Otherwise default
// values are used.
font = CTFontCreateWithFontDescriptor(descriptor, 0.0, NULL);
}
}
return font;
}
6、更改字距
默认情况下启用连词和字距调整。
要禁用,请将kCTKernAttributeName
属性设置为0
。
例3-8将绘制的前几个字符的kern大小设置为大数字。
例3-8设置字距
// Set the color of the first 13 characters to red
// using a previously defined red CGColor object.
CFAttributedStringSetAttribute(attrString, CFRangeMake(0, 13),
kCTForegroundColorAttributeName, red);
// Set kerning between the first 18 chars to be 20
CGFloat otherNum = 20;
CFNumberRef otherCFNum = CFNumberCreate(NULL, kCFNumberCGFloatType, &otherNum);
CFAttributedStringSetAttribute(attrString, CFRangeMake(0,18),
kCTKernAttributeName, otherCFNum);
7、获取字符的字形
例3-9展示了如何为具有单一字体的字符串中的字符获取字形。
大多数时候,您应该只使用CTLine对象来获取此信息,因为一种字体可能不会对整个字符串进行编码。
此外,简单的字符到字形映射不会为复杂的脚本获得正确的外观。
如果您尝试为字体显示特定的Unicode字符,这种简单的字形映射可能是合适的。
例3-9获取字符的字形
void GetGlyphsForCharacters(CTFontRef font, CFStringRef string)
{
// Get the string length.
CFIndex count = CFStringGetLength(string);
// Allocate our buffers for characters and glyphs.
UniChar *characters = (UniChar *)malloc(sizeof(UniChar) * count);
CGGlyph *glyphs = (CGGlyph *)malloc(sizeof(CGGlyph) * count);
// Get the characters from the string.
CFStringGetCharacters(string, CFRangeMake(0, count), characters);
// Get the glyphs for the characters.
CTFontGetGlyphsForCharacters(font, characters, glyphs, count);
// Do something with the glyphs here. Characters not mapped by this font will be zero.
// ...
// Free the buffers
free(characters);
free(glyphs);
}
2024-06-16(日)