Core Text 是一種用於處理字型和文字佈局的底層高階技術,自 Mac OS X v10.5 和 iOS 3.2 開始引入,你可以從所有 iOS 及 OS X 的開發環境中使用其 API。
重要:Core Text 是為一些必須處理底層字型處理和文字佈局的開發者準備,如無必要,你應該使用 TextKit(Text Programming Guide for iOS)、CocoaText(Cocoa Text Architecture Guide)等框架開發你的 App 或 Mac 應用。Core Text 是以上兩種文字框架的底層實現,因此它們的速度和效率是共享的。除此之外,以上兩種文字框架提供了富文字編輯及頁面佈局引擎。如果你的 App 只使用 Core Text,則需要為其提供其他的基礎實現。
省略了一些摘要和相關文件,基本後面都會提到,有需要看原文。
複製程式碼
概述
Core Text 直接與 Core Graphics(Quartz)協調作業。Quartz 是一個圖形渲染引擎,可以在 iOS 及 OS X 中處理最底層的二維影象生成。
Core Text 是為高階框架中的文字佈局及字型處理功能提供支援的中間層,Quartz 則為所有的文字和字型框架提供更為底層的支援,Quartz 作用於文字的字形(CTGlyphInfo)和位置,而 Core Text 清楚如何為字元匹配字型,在呼叫 Quartz 繪製文字之前,它會處理文字的樣式,字型規格和其他屬性,Quartz 是獲取基本級別繪製字形的唯一方式。因為 Core Text 直接提供了 Quartz 可用資料,因此其文字繪製效能很高。
如果客戶端不改變執行緒之間共享的任何引數(如 attributed strings),則可以同時從多個執行緒呼叫 Core Text 函式。
Core Text 是基於 C 的跨平臺 API
Core Text API 在 iOS 和 OS X 上幾乎相同,不過 OS X 版本上提供了更加豐富的字型管理 API,包括可變字型集合。雖然 API 相差不多,但是 UIKit 和 AppKit 之間存在差異,如在平臺之間移植程式碼時需要考慮到這些差異。例如,你需要一個 Quartz 圖形上下文來繪製 Core Text 生成的字形,而你在每個平臺上獲得的圖形上下文並非一樣。因為 iOS 中繪製的檢視是UIView,OS X 中則是NSView。你需要知道的是CGRect物件是傳遞給UIViewdrawRect:方法的,而 OS X 版本drawRect:是傳遞給NSRect物件的。(OS X 中使用NSRectToCGRect函式可將傳入的NSRect物件轉換為CGRect 作為 Core Text 函式引數所需的物件。)
由UIView函式UIGraphicsGetCurrentContext返回的圖形上下文相對於未經修改的 Quartz 圖形上下文(UIView返回的圖形上下文,原點在左上角)進行了翻轉,因此在 iOS 中你需要翻轉 Quartz 圖形上下文,而在 OS X 中則不必如此,有關此技術的示例程式碼,參閱佈局一個段落小節。
Core Text 使用了與 OS X 和 iOS 中其他核心框架相同的約定,並且儘可能的使用系統資料型別和服務,舉例來說,Core Text 中許多入參和返回值是 Core Foundation 物件。因此你可以將它們儲存在 Core Foundation 集合類中,Core Text 使用的其他物件,如CGPath物件,實際由 Core Graphics 提供支援。
Core Text 物件是 C 的不透明型別
為了速度和簡潔性,iOS 和 OS X 中的許多底層庫使用 C 編寫。因此使用 Core Text 時,你需要使用使用 C 函式,例如CTFramesetterCreateWithAttributedString,CTFramesetterCreateFrame,而不是 OC 的類與方法。
Core Tex 不透明型別
Core Text 的佈局作業通常需要由屬性字串(CFAttributedStringRef)和圖形路徑(CGPathRef)共同完成,CFAttributedStringRef 包含需要繪製的字串、字元的樣式屬性(如顏色和字型)。Core Text 中的排版機制使用其中的資訊,完成字元到字形的轉換。
CGPathRef 定義了文字繪製區域的形狀。在 OS X 10.7 和 iOS 3.2 及更高版本中,路徑可以是非矩形的。
CFAttributedString 的引用型別 CFAttributedStringRef 可與 NSAttributedString 橋接,這意味著在函式中,你可以在 Core Foundation 型別和橋接型別間進行轉換。因此,在看到引數為NSAttributedString *的方法中,可以傳入CFAttributedStringRef,在看到引數為CFAttributedStringRef的函式中,可以傳入NSAttributedString及其子類的例項。(你可能需要將一種型別轉換為另一種型別,以消除編譯器警告。)
排版中最常見的操作之一是在任意大小的矩形區域內佈局多行的段落。Core Text 使此操作變得簡單,只需要幾行特定的 Core Text 的程式碼。如要佈局段落,你需要獲得繪製圖形上下文(CGContext),文字佈局路徑(CGPath)以及屬性字串(CFAttributedString),文字佈局路徑則需要一個矩形路徑(CGReact)。這個例子中大多數程式碼都需要建立和初始化上下文,路徑和字串。完成此操作後,Core Text 只需要三行程式碼即可完成佈局。
// 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);
複製程式碼
// 獲取圖形上下文。guardlet context = UIGraphicsGetCurrentContext() else { return }
// 翻轉上下文座標,僅iOS。
context.translateBy(x: 0, y: self.bounds.size.height)
context.scaleBy(x: 1.0, y: -1.0)
// 設定文字繪製的矩形
context.textMatrix = .identity
// 建立一個繪製文字的區域的路徑 不必是矩形let path = CGMutablePath()
// 初始化一個矩形路徑。let bounds = CGRect(x: 10.0, y: 10.0, width: 200.0, height: 200.0)
path.addRect(bounds, transform: .identity)
// 初始化一個字串let textString = "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."asCFString// 建立一個最大長度為0的可變屬性字串// kCFAllocatorDefault表示要一個記憶體分配器// 0表示最大長度guardlet attrString = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0) else { return }
// 將textString複製到新建立的attrString中CFAttributedStringReplaceString(attrString, CFRangeMake(0, 0), textString)
// 建立一個將新增到attrString的顏色屬性。let rgbColorSpace = CGColorSpaceCreateDeviceRGB()
var components: [CGFloat] = [1.0, 0.0, 0.0, 0.8]
let red = CGColor(colorSpace: rgbColorSpace, components: &components)
// 將前12個字元的顏色設定為紅色.CFAttributedStringSetAttribute(attrString, CFRangeMake(0, 12), kCTForegroundColorAttributeName, red)
// 使用屬性字串建立framesetter。let framesetter = CTFramesetterCreateWithAttributedString(attrString asCFAttributedString)
// 建立一個framelet frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, nil)
// 在給定的frame繪製上下文CTFrameDraw(frame, context)
複製程式碼
簡單的文字標籤
還有一種常見的排版操作是繪製單行文字以用作於使用者介面元素的標籤(label)。在 Core Text 中,這隻需要兩行程式碼:一行建立具有 CFAttributedString 的 CTLine 物件,另一行將 CTLine 繪製到圖形上下文中。
以下程式碼展示瞭如何在UIView或NSView的drawRect:方法中完成此操作。程式碼中省略了plain text string、font、graphics context等已在本文件其他小節中展示過的操作,展示瞭如何建立屬性字典並用其建立attributed string。(字型建立展示在建立字型描述和根據字型描述建立字型小節。)
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);
複製程式碼
// 初始化 string, font, and contextlet string: CFString = "Hello, World! I know nothing in the world that has as much power as a word"asCFStringlet font: CTFont = CTFontCreateUIFontForLanguage(.label, 28, nil)!
let context: CGContext = UIGraphicsGetCurrentContext()!
// 1// let attributes = [kCTFontAttributeName : font] as CFDictionary// 2let key = UnsafeMutablePointer<CFString>.allocate(capacity: 1)
key.initialize(to: kCTFontAttributeName)
let keyPointer = unsafeBitCast(key, to: UnsafeMutablePointer<UnsafeRawPointer?>.self)
defer {
keyPointer.deinitialize(count: 1)
keyPointer.deallocate()
}
let value = UnsafeMutablePointer<CTFont>.allocate(capacity: 1)
value.initialize(to: font)
let valuePointer = unsafeBitCast(value, to: UnsafeMutablePointer<UnsafeRawPointer?>.self)
defer {
valuePointer.deinitialize(count: 1)
valuePointer.deallocate()
}
guardlet attributes = CFDictionaryCreate(kCFAllocatorDefault, keyPointer, valuePointer, 1, nil, nil) else {
debugPrint("attributes create fail")
return
}
guardlet attributeString = CFAttributedStringCreate(kCFAllocatorDefault, string, attributes) else {
debugPrint("attributeString create fail")
return
}
let line = CTLineCreateWithAttributedString(attributeString)
context.textPosition = CGPoint(x: 100, y: 100)
CTLineDraw(line, context)
複製程式碼
多列布局
還有一種常見的排版操作是在多個列中佈局文字。嚴格的講,Core Text 本身一次只顯示一列,並不計算列的尺寸或位置。不過在呼叫 Core Text 佈局文字之前,計算列的路徑區域,可以使文字在列中繪製,以此達到多列的文字,在這個示例中,Core Text 除了佈局每一列文字外,還為每一列提供了字串的子範圍。
// 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);
}
複製程式碼
guardlet context = UIGraphicsGetCurrentContext() else { return }
let text = "replace that, use long long long text"asCFStringguardlet attributedString = CFAttributedStringCreate(kCFAllocatorDefault, text, nil) else {
debugPrint("create attributedString fail")
return
}
context.translateBy(x: 0, y: self.bounds.size.height)
context.scaleBy(x: 1.0, y: -1.0)
context.textMatrix = .identity
let framesetter = CTFramesetterCreateWithAttributedString(attributedString)
guardlet columPaths = self.createColumns(withColumnCount: 3) else {
debugPrint("columPaths create fail")
return
}
let pathCount = CFArrayGetCount(columPaths)
var startIndex = 0for i in0 ..< pathCount {
guardlet pointer = CFArrayGetValueAtIndex(columPaths, i) else {
debugPrint("columPaths index \(i) load fail")
continue
}
let path = Unmanaged<CGMutablePath>.fromOpaque(pointer)
let frame = CTFramesetterCreateFrame(framesetter, .init(location: startIndex, length: 0), path.takeUnretainedValue(), nil)
CTFrameDraw(frame, context)
startIndex += CTFrameGetVisibleStringRange(frame).length
path.release()
}
複製程式碼
手動換行
在Core Text中,除非你有特殊的斷字過程或類似的需求,否則不需要手動換行。framesetter可以自動換行。不過Core Text 也可以讓你準確的指定在哪裡中斷一行文字。以下程式碼展示瞭如何建立 typesetter 以及直接使用 typesetter 查詢換行符並手動建立 typeset line。這個示例還展示瞭如何在繪製之前使行居中。
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;
CFIndexcount = 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;
複製程式碼
- (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);
}
複製程式碼
// 在iOS中初始化上下文。guardlet context = UIGraphicsGetCurrentContext() else { return }
// 僅在iOS中翻轉上下文座標
context.translateBy(x: 0, y: self.bounds.size.height)
context.scaleBy(x: 1.0, y: -1.0)
// 設定文字矩陣(就是設定字元繪製的方向,以免字元上下或左右翻轉,因為在iOS上Core Text和Core Graphicsz座標系不同)
context.textMatrix = .identity
// 建立有段落樣式的attributed stringlet string = "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."let attString = applyParaStyle(fontName: "Didot-Italic"asCFString, pointSize: 24, plainText: string, lineSpaceInc: 50)
// 根據attributed string建立framesetterlet framesetter = CTFramesetterCreateWithAttributedString(attString asCFAttributedString)
// 建立要填充的區域的路徑let path = CGPath(rect: rect, transform: nil)
// 建立繪製區域let frame = CTFramesetterCreateFrame(framesetter, .init(location: 0, length: 0), path, nil)
// 繪製CTFrameDraw(frame, context)
複製程式碼
在非矩形區域中繪製文字的難點在於描述非矩形路徑。以下程式碼的AddSquashedDonutPath方法返回一個環形路徑。有了路徑後,只需呼叫常用的 Core Text 函式即可應用屬性並繪製。
// 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);
}
複製程式碼
將一個已經存在的字型轉換為相關或類似字型非常實用。以下程式碼中的示例函式展示瞭如何利用函式呼叫傳入Boolean值使字型加粗或取消加粗。如果當前 font family 沒有要求的 font,函式返回NULL。
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);
}
複製程式碼
以下程式碼中的示例函式將傳入一個給定的字型,返回另一個 font family 中相似的字型,如果可能,保留原字型的 trait。這個函式可能返回NULL。將 size 傳入0.0,matrix 傳入NULL,可以使返回的字型 size 等同於原字型。
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);
}
複製程式碼
CFDataRefCreateFlattenedFontData(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;
}
複製程式碼
以下程式碼中的示例函式展示瞭如何從 XML 資料中反序列出字型的屬性字典,並利用屬性字典建立一個字型引用。
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;
}
複製程式碼
funcCreateFontFromFlattenedFontData(data: CFData) -> CTFont? {
let immutable = CFPropertyListMutabilityOptions.mutableContainers.rawValue
guardlet attributesUnm = CFPropertyListCreateFromXMLData(kCFAllocatorDefault, data, immutable, nil) else {
returnnil
}
let attributes = attributesUnm.takeRetainedValue() as! CFDictionary// defer {// attributesUnm.release()// }let descriptor = CTFontDescriptorCreateWithAttributes(attributes)
let font = CTFontCreateWithFontDescriptor(descriptor, 0, nil)
return font
}
複製程式碼
// 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);
複製程式碼
let attributedString = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0)
CFAttributedStringReplaceString(attributedString, .init(location: 0, length: 0), "Hello, World! I know nothing in the world that has as much power as a word."asCFString)
CFAttributedStringSetAttribute(attributedString, .init(location: 0, length: 0), kCTForegroundColorAttributeName, UIColor.red.cgColor)
var num: CGFloat = 20let cfNum = CFNumberCreate(kCFAllocatorNull, .cgFloatType, &num)
CFAttributedStringSetAttribute(attributedString, .init(location: 0, length: 18), kCTKernAttributeName, cfNum)
複製程式碼
從字元獲取字形
以下程式碼展示瞭如何從一個只有一個字型的string的characters中獲取字形(glyphs),大部分情況下,你應該從 CTLine 中獲取這些資訊,因為string中可能包含不止一種字型。此外,對於比較複雜的文字繪製而言,簡單的character to glyphs不能得到預期的外觀,如果你希望使用一種字型,顯示特定的Unicode字元(Characters),這種字元到字形的對映是適合的。
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);
}
複製程式碼