Core Text 編程指南

介紹html

原文連接: Core Text Programming Guideweb

重要:此文檔再也不更新,有關 Apple SDKs 的最新信息,訪問 documentation website.objective-c

Core Text 是一種用於處理字體和文本佈局的底層高級技術,自 Mac OS X v10.5 和 iOS 3.2 開始引入,你能夠從全部 iOS 及 OS X 的開發環境中使用其 API。swift

重要: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 中處理最底層的二維圖像生成。bash

Core Text 是爲高級框架中的文本佈局及字體處理功能提供支持的中間層,Quartz 則爲全部的文本和字體框架提供更爲底層的支持,Quartz 做用於文本的字形(CTGlyphInfo)和位置,而 Core Text 清楚如何爲字符匹配字體,在調用 Quartz 繪製文本以前,它會處理文本的樣式,字體規格和其餘屬性,Quartz 是獲取基本級別繪製字形的惟一方式。由於 Core Text 直接提供了 Quartz 可用數據,所以其文本繪製性能很高。app

若是客戶端不改變線程之間共享的任何參數(如 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對象是傳遞給UIView drawRect:方法的,而 OS X 版本drawRect:是傳遞給NSRect對象的。(OS X 中使用NSRectToCGRect函數可將傳入的NSRect對象轉換爲CGRect 做爲 Core Text 函數參數所需的對象。)ide

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 函數,例如CTFramesetterCreateWithAttributedStringCTFramesetterCreateFrame,而不是 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及其子類的實例。(你可能須要將一種類型轉換爲另外一種類型,以消除編譯器警告。)

attributes 是定義字符串中字符樣式的一組鍵值對,你能夠建立 CFDictionary 對象來保存要應用的 attributes,字符串中的字符按相同的 attributes 進行分組,稱爲字形組(CTRun)。在建立一個 AttributedString 時,可將 attributes 做爲參數傳遞。或者你也能夠將 attributeds 應用於已經存在的 CFMutableAttributedString 對象中,雖然 CFDictionaryRef 和 NSDictionary 能夠橋接,可是存儲在其中的某個對象可能不行。

CoreText 的運行時層次結構以下圖所示,頂部是 CTFramesetterRef,輸入 CFAttributedStringRef 和 CGPathRef,CTFramesetterRef 生成一個或多個 CTFrameRef,每一個 CTFrameRef 表示一個段落。

爲了生成 CTFrameRef,CTFramesetterRef 調用一個 CTTypesetterRef 對象,當 CTTypesetterRef 在CTFrameRef 中放置文本時,CTFramesetterRef 對 CTFrameRef 應用段落樣式(CTParagraphStyle),包括對齊、製表符、行間距、縮進、換行等屬性。CTTypesetterRef 將字符串轉換成 CTRun,並將其填充到 CTLine。

每一個 CTFrame 對象包含段落的 CTLine 對象。每一個 CTLine 對象表示一行文本。一個 CTFrame 對象可能只包含一個 CTLine 對象,也多是一組。CTLine 對象是在 CTFramesetterRef 建立 CTFrameRef 時被建立,CTLine 對象能夠直接繪製在圖形上下文中。

CTLine 包含一個 CTRun 數組,CTRun 是一組具備相同 attributes 和繪製方向的連續字形,CTTypesetterRef 在從 CFAttributedString、attributes 和 CTFont 生成 CTLine 時,同時生成 CTRuns。若是須要,CTRun 能夠將本身繪製在圖形上下文中,不過大多數時候,你不須要直接操做 CTRun。

字體對象

字體對象(CTFont)能夠幫助肯定字形之間的相對位置,並提供在圖形上下文繪製時使用的字體。Core Text 不透明類型 CTFont 是一個封裝了不少信息的具體字體實例。它的引用類型 CTFontRef 能夠橋接 UIFont 和 NSFont。當你建立一個 CTFont 對象,你一般須要指定(或使用默認)字號和轉換模型(transformation matrix),以提供 CTFont 具體的特徵。而後,你能夠在 CTFont 對象中查詢關於此字號下字體的許多信息,例如character-to-glyph mapping, encodings, font metric data, glyph data等。font metric data包括ascent, descent, leading, cap height, x-height等參數。glyph data包括bounding rectangle、glyph advance等參數。

以上參數沒法理解的可參考下圖,與原文無關。

font-metric-data

CTFont 對象是不可變的,所以它們能夠在多個操做,隊列或線程中同時使用。建立 CTFont 對象有許多方法。首選方法是使用字體描述CTFontCreateWithFontDescriptor。固然你也可使用其餘備選的API,這取決於你如今有什麼樣的數據。例如,你可使用字體的 PostScript 名稱(CTFontCreateWithName)或 CGFont(CTFontCreateWithGraphicsFont)。還有CTFontCreateUIFontForLanguage,它將引用你本地化應用中交互界面中的字體。

CTFont 引用提供了一種稱爲字體級聯(font cascading)的複雜的自動替換字體機制,該機制會在考慮字體特徵的同時,選擇適當的字體來替代缺失的字體。字體級聯基於級聯列表(cascading list),級聯列表是一個有序的字體描述(Font Descriptors)數組。有一個系統默認級聯列表(多態的,基於用戶的語言設置和當前字體的)和在字體建立時指定的字體級聯列表。使用字體描述中的信息,級聯機制能夠根據樣式和匹配字符匹配字體。CTFontCreateForString函數使用級聯列表來選擇合適的字體來編碼給定的字符串。要指定和檢索字體級聯列表,請使用kCTFontCascadeListAttribute屬性。

字體描述

字體描述(CTFontDescriptor)不透明類型提供了一種徹底由屬性字典描述字體的機制,以及一種易於使用的,用於構建新字體的字體匹配工具,你能夠從一個 CTFontDescriptor 中建立一個 CTFont,也能夠從一個 CTFont 中得到一個 CTFontDescriptor,你還能夠更改 CTFontDescriptor 並使用它來建立新的字體對象,或者建立 CTFontDescriptor 並指定部分屬性,例如family name、weight,就能夠找到系統中與之匹配的全部字體。CTFontDescriptorRef類型能夠和UIFontDescriptorNSFontDescriptor橋接。

使用 CTFontDescriptor,就無需處理複雜的轉換模型(transformation matrix),你能夠建立一個字體屬性字典,屬性包括PostScript name、font family、style,以及特徵(traits)(例如,粗體或斜體),用它建立 CTFontDescriptor 對象。然後使用 CTFontDescriptor 建立 CTFont 對象。CTFontDescriptor 能夠序列化並存儲,咱們能夠藉此持久化字體。下圖演示了字體系統使用 CTFontDescriptor 建立 CTFont 。

你能夠將 CTFontDescriptor 視爲對字體系統的查詢條件。建立具備不完整描述的 CTFontDescriptor,即在屬性字典中使用一個或幾個值,字體系統將從可用的字體中選擇最合適的字體。例如,若是你指定使用family查詢,而不指定standard faces(normal, bold, italic, bold italic),則會匹配family中全部 standard faces,可是若是指定kCTFontTraitsAttributekCTFontTraitBold,結果將收縮到符合bold trait的字體。系統經過CTFontDescriptorCreateMatchingFontDescriptors提供與查詢匹配的字體描述的完整列表。

在iOS 6.0及更高版本中,應用程序能夠按需使用CTFontDescriptorMatchFontDescriptorsWithProgressHandler下載安裝未安裝的可用字體。以這種方式下載的字體不會永久安裝,系統可能會在特定狀況下將其刪除。可供下載的字體在iOS 6:字體列表iOS 7:字體列表中做爲附加信息。DownloadFont 示例(在 iOS Developer Library 中)演示了這項技術。OS X 中不須要按需下載字體,由於全部可用字體已隨系統一塊兒安裝。

字體集合

字體集合是由一組 CTFontDescriptor 組成的單個對象。字體集合由 CTFontCollection 不透明類型表示。字體集合提供了字體枚舉、全局和自定義 CTFontCollection 訪問,以及訪問該 CTFontCollection 中 CTFontDescriptors 的功能。例如,你能夠經過CTFontCollectionCreateFromAvailableFonts建立系統中全部可用字體的CTFontCollection,並可使用該 CTFontCollection 獲取全部 CTFontDescriptors。

常見的文本佈局操做

本章介紹了一些常規文本佈局操做,以及如何使用 Core Text 編碼實現。

佈局一個段落

排版中最多見的操做之一是在任意大小的矩形區域內佈局多行的段落。Core Text 使此操做變得簡單,只須要幾行特定的 Core Text 的代碼。如要佈局段落,你須要得到繪製圖形上下文(CGContext),文本佈局路徑(CGPath)以及屬性字符串(CFAttributedString),文本佈局路徑則須要一個矩形路徑(CGReact)。這個例子中大多數代碼都須要建立和初始化上下文,路徑和字符串。完成此操做後,Core Text 只須要三行代碼便可完成佈局。

如下代碼顯示了段落是如何佈局的。此代碼能夠在UIViewNSView)的drawRect:中執行。

// 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);
複製代碼
// 獲取圖形上下文。
guard let 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." as CFString

// 建立一個最大長度爲0的可變屬性字符串
// kCFAllocatorDefault表示要一個內存分配器
// 0表示最大長度
guard let 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 as CFAttributedString)

// 建立一個frame
let frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, nil)

// 在給定的frame繪製上下文
CTFrameDraw(frame, context)
複製代碼

簡單的文字標籤

還有一種常見的排版操做是繪製單行文本以用做於用戶界面元素的標籤(label)。在 Core Text 中,這隻須要兩行代碼:一行建立具備 CFAttributedString 的 CTLine 對象,另外一行將 CTLine 繪製到圖形上下文中。

如下代碼展現瞭如何在UIViewNSViewdrawRect:方法中完成此操做。代碼中省略了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 context
let string: CFString = "Hello, World! I know nothing in the world that has as much power as a word" as CFString
let font: CTFont = CTFontCreateUIFontForLanguage(.label, 28, nil)!
let context: CGContext = UIGraphicsGetCurrentContext()!

// 1
// let attributes = [kCTFontAttributeName : font] as CFDictionary

// 2
let 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()
}

guard let attributes = CFDictionaryCreate(kCFAllocatorDefault, keyPointer, valuePointer, 1, nil, nil) else {
    debugPrint("attributes create fail")
    return
}

guard let 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 除了佈局每一列文本外,還爲每一列提供了字符串的子範圍。

如下代碼中的createColumnsWithColumnCount:方法接受列數做爲參數,並返回一個路徑數組,每一個路徑表明一列。

- (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;
}
複製代碼
func createColumns(withColumnCount columnCount: Int) -> CFArray? {
    let columnWidth = CGFloat(bounds.width / CGFloat(columnCount))
    var remainder = bounds

    // 橋接轉換
// var paths = [CGMutablePath]()
// for _ in 0 ..< columnCount {
// let (slice, remainded) = remainder.divided(atDistance: columnWidth, from: .minXEdge)
// let columnReact = slice.insetBy(dx: 8, dy: 15)
// remainder = remainded
// let path = CGMutablePath()
// path.addRect(columnReact, transform: .identity)
// paths.append(path)
// }
// return paths as CFArray

    // 指針建立
    let allocator = CFAllocatorGetDefault()?.takeUnretainedValue()
    let array = CFArrayCreateMutable(allocator, columnCount, nil)

    for _ in 0 ..< columnCount {
        let (slice, remainded) = remainder.divided(atDistance: columnWidth, from: .minXEdge)
        let columnReact = slice.insetBy(dx: 8, dy: 15)
        remainder = remainded
        let path = CGMutablePath()
        path.addRect(columnReact, transform: .identity)
        let manager = Unmanaged.passRetained(path)
        CFArrayAppendValue(array, manager.toOpaque())
    }
    return array
}
複製代碼

如下代碼在 UIView(NSView)的drawRect:中,該方法調用了createColumnsWithColumnCount,這個類包含一個 attributedString 屬性,須要你本身定義。

// 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);
 
}
複製代碼
guard let context = UIGraphicsGetCurrentContext() else { return }
let text = "replace that, use long long long text" as CFString
guard let 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)
guard let columPaths = self.createColumns(withColumnCount: 3) else {
    debugPrint("columPaths create fail")
    return
}
let pathCount = CFArrayGetCount(columPaths)
var startIndex = 0
for i in 0 ..< pathCount {
    guard let 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。這個示例還展現瞭如何在繪製以前使行居中。

此代碼能夠在UIViewNSView)的drawRect:方法中。未顯示代碼中使用的變量的初始化。

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;
複製代碼
guard let context = UIGraphicsGetCurrentContext() else { return }
let text = "Hello, World!Hello, World!Hello, World!" as CFString
let attributeds = [kCTFontAttributeName: CTFontCreateUIFontForLanguage(.label, 28, nil)!, kCTBackgroundColorAttributeName: UIColor.red.cgColor] as CFDictionary
guard let attributedString = CFAttributedStringCreate(kCFAllocatorDefault, text, attributeds) else { return }
context.translateBy(x: 0, y: self.bounds.size.height)
context.scaleBy(x: 1.0, y: -1.0)
let width: CGFloat = bounds.size.width / 1.5
let textPosition: CGPoint = .init(x: 0, y: 100)
// 使用CFAttributedString建立CTTypesetter
let typesetter = CTTypesetterCreateWithAttributedString(attributedString)
// 尋找從字符串開頭到給定寬度的換行符(這個寬度能夠繪製多少字)
var start = 0
let count = CTTypesetterSuggestLineBreak(typesetter, start, width.native)
// 根據返回的字數建立行
let line = CTTypesetterCreateLine(typesetter, .init(location: start, length: count))
let flush: CGFloat = 0.5
// 計算使行居中所需的偏移量
let penOffset = CTLineGetPenOffsetForFlush(line, flush, bounds.size.width.native)
// 根據計算的偏移量移動 textPosition 並繪製行。
context.textPosition = .init(x: textPosition.x.native + penOffset, y: textPosition.y.native)
CTLineDraw(line, context)
// 將索引移動到換行處
start += count
複製代碼

應用段落樣式

applyParaStyle方法實現了一個將段落樣式應用於attributed string。該方法接收字體,字號和行間距等參數,行間距會增長或減小line之間的距離。

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;
}
複製代碼
func applyParaStyle(fontName: CFString, pointSize: CGFloat, plainText: String, lineSpaceInc: CGFloat) -> NSAttributedString {
    // 建立字體以肯定高度
    let font = CTFontCreateWithName(fontName, pointSize, nil)
    // 計算lineSpacing
    var lineSpace = (CTFontGetLeading(font) + lineSpaceInc) * 2
    // 建立段落樣式
    let valueSize = MemoryLayout<CGFloat>.stride
    var setting =  CTParagraphStyleSetting(spec: .lineSpacingAdjustment, valueSize: valueSize, value: &lineSpace)
    let paragtaphStyle = CTParagraphStyleCreate(&setting, 1)
    // 建立attributed string。設置字體和段落樣式。
    let attrString = NSAttributedString(string: plainText, attributes: [
        .font: font,
        .paragraphStyle: paragtaphStyle,
        ])
    return attrString
}
複製代碼

如下代碼調用applyParaStyle,該方法建立純文本字符串,使用applyParaStyle方法建立具備段落屬性的attributed string,而後建立 framesetter 和 frame,並繪製 frame。

- (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中初始化上下文。
guard let 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 string
let 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" as CFString, pointSize: 24, plainText: string, lineSpaceInc: 50)
// 根據attributed string建立framesetter
let framesetter = CTFramesetterCreateWithAttributedString(attString as CFAttributedString)
// 建立要填充的區域的路徑
let path = CGPath(rect: rect, transform: nil)
// 建立繪製區域
let frame = CTFramesetterCreateFrame(framesetter, .init(location: 0, length: 0), path, nil)
// 繪製
CTFrameDraw(frame, context)
複製代碼

在 OS X中,NSViewdrawRect:方法接收一個 NSRec 對象,可是CGPathCreateWithRect方法須要一個 CGRect 對象。所以,必須使用下面的方法將 NSRect 對象轉換爲 CGRect 對象:

CGRect myRect = NSRectToCGRect([self bounds]);
複製代碼

此外,在 OS X 中,獲取圖形上下文的方式不一樣,不須要翻轉座標。

在非矩形區域繪製文本

在非矩形區域中繪製文本的難點在於描述非矩形路徑。如下代碼的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);
}
複製代碼
// 建立一個環形路徑
func addSquashedDonut(path: CGMutablePath, transform: CGAffineTransform, rect: CGRect) {
    let width = rect.size.width
    let height = rect.size.height
    let radiusH: CGFloat = width / 3.0
    let radiusV: CGFloat = height / 3.0
    path.move(to: .init(x: rect.origin.x, y: rect.origin.y + height - radiusV), transform: transform)
    path.addQuadCurve(to: .init(x: rect.origin.x + radiusH, y: rect.origin.y + height), control: .init(x: rect.origin.x, y: rect.origin.y + height), transform: transform)
    path.addLine(to: .init(x: rect.origin.x + width - radiusH, y: rect.origin.y + height), transform: transform)
    path.addQuadCurve(to: .init(x: rect.origin.x + width, y: rect.origin.y + height - radiusV), control: .init(x: rect.origin.x + width, y: rect.origin.y + height), transform: transform)
    path.addLine(to: .init(x: rect.origin.x + width, y: rect.origin.y + radiusV), transform: transform)
    path.addQuadCurve(to: .init(x: rect.origin.x + width - radiusH, y: rect.origin.y), control: .init(x: rect.origin.x + width, y: rect.origin.y), transform: transform)
    path.addLine(to: .init(x: rect.origin.x + radiusH, y: rect.origin.y), transform: transform)
    path.addQuadCurve(to: .init(x: rect.origin.x, y: rect.origin.y + radiusV), control: .init(x: rect.origin.x, y: rect.origin.y), transform: transform)
    path.closeSubpath()
    path.addEllipse(in: .init(x: rect.origin.x + width / 2.0 - width / 5.0, y: rect.origin.y + height / 2.0 - height / 5.0, width: width / 5.0 * 2.0, height: height / 5.0 * 2.0), transform: transform)
}

func paths() -> [CGMutablePath] {
    let path = CGMutablePath()
    var bounds = self.bounds
    bounds = bounds.insetBy(dx: 10, dy: 10)
    addSquashedDonut(path: path, transform: .identity, rect: bounds)
    return [path]
}

func f() {
    // 在iOS中初始化上下文。
    guard let 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
    let 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."
    guard let attString = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0) else { return }
    CFAttributedStringReplaceString(attString, .init(location: 0, length: 0), string as CFString)
    let rgbColorSpace = CGColorSpaceCreateDeviceRGB()
    var components: [CGFloat] = [1.0, 0.0, 0.0, 0.8]
    guard let red = CGColor(colorSpace: rgbColorSpace, components: &components) else { return }
    // 將前13個字符設置爲紅色
    CFAttributedStringSetAttribute(attString, .init(location: 0, length: 13), kCTForegroundColorAttributeName, red)
    let framesetter = CTFramesetterCreateWithAttributedString(attString)
    let paths = self.paths()
    var startIndex = 0
    for path in paths {
        // 路徑的背景色設置爲黃色
        context.setFillColor(UIColor.yellow.cgColor)
        context.addPath(path)
        context.fillPath()
        context.drawPath(using: .stroke)
        let frame = CTFramesetterCreateFrame(framesetter, .init(location: startIndex, length: 0), path, nil)
        CTFrameDraw(frame, context)
        let frameRange = CTFrameGetVisibleStringRange(frame)
        startIndex += frameRange.length
    }
}
複製代碼

常見字體操做

本章介紹了一些常見的字體處理操做,並展現瞭如何使用 Core Text 編碼實現。這些操做在 iOS 和 OS X 上是相同的。

建立字體描述

下面的示例函數根據字體名稱和字號建立字體描述。

CTFontDescriptorRef CreateFontDescriptorFromName(CFStringRef postScriptName,
                                                 CGFloat size)
{
   return CTFontDescriptorCreateWithNameAndSize(postScriptName, size);
}
複製代碼
func CreateFontDescriptorFromName(postScriptName: CFString, size: CGFloat) -> CTFontDescriptor {
    return CTFontDescriptorCreateWithNameAndSize(postScriptName, size)
}
複製代碼

下面的示例函數根據 font family 和字體特徵建立字體描述。

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);
複製代碼
let familyName = "Papyrus"
let symbolicTraits: CTFontSymbolicTraits = .traitCondensed
let size: CGFloat = 24.0

var attributes: [AnyHashable : Any] = [:]
attributes[kCTFontFamilyNameAttribute] = familyName
// attributes字典中包含traits字典
// 本例中只指定字體特徵。
var traits: [AnyHashable : Any] = [:]
traits[kCTFontSymbolicTrait] = NSNumber(value: symbolicTraits.rawValue)
attributes[kCTFontTraitsAttribute] = traits
attributes[kCTFontSizeAttribute] = NSNumber(value: Float(size))
let descriptor = CTFontDescriptorCreateWithAttributes(attributes as CFDictionary)
print(descriptor)
複製代碼

根據字體描述建立字體

如下代碼展現瞭如何建立字體描述並使用它建立字體。當調用CTFontCreateWithFontDescriptor時,一般會傳給 matrix 參數NULL,以指定默認(identity)矩陣。CTFontCreateWithFontDescriptor的 size 和 matrix 會覆蓋字體描述中的值,除非字體描述未指定 size 和 matrix。

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);
複製代碼
let fontAttributes = [
    kCTFontFamilyNameAttribute: "Courier",
    kCTFontStyleNameAttribute: "Bold",
    kCTFontSizeAttribute: NSNumber(value: 16.0)
] as CFDictionary
// 建立字體描述
let descriptor = CTFontDescriptorCreateWithAttributes(fontAttributes)
// 根據字體描述建立字體
let font = CTFontCreateWithFontDescriptor(descriptor, 0.0, nil)
print(font)
複製代碼

建立相似字體

將一個已經存在的字體轉換爲相關或相似字體很是實用。如下代碼中的示例函數展現瞭如何利用函數調用傳入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);
}
複製代碼
func CreateBoldFont(font: CTFont, makeBold: Bool) -> CTFont? {
    // CTFontSymbolicTraits是一個OptionSet(選擇集合),若是須要,能夠指定多個特徵

    // 須要修改的trait集合(至關於keys)
    let traitMask: CTFontSymbolicTraits = [.boldTrait]
    // traitMask中trait的值,二者結合能夠增長或去除trait(至關於values)
    var desiredTrait = CTFontSymbolicTraits.init(rawValue: 0)

    // 若是要求字體加粗,設置trait爲bold
    if makeBold {
        desiredTrait = .boldTrait
    }

    // 建立原始字體的副本,如無匹配trait的字體,返回nil
    return CTFontCreateCopyWithSymbolicTraits(font, 0.0, nil, 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);
}
複製代碼
func CreateFontConvertedToFamily(font: CTFont, family: CFString) -> CTFont? {
    return CTFontCreateCopyWithFamily(font, 0, nil, family)
}
複製代碼

字體序列化

如下代碼中的示例函數展現瞭如何建立一個 XML,並使用其序列化一個能夠在文檔中使用的字體。或者你也可使用NSArchiver完成一樣的效果。這只是將建立一個確切字體所須要的數據進行存儲的一種方法。

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;
}
複製代碼
func CreateFlattenedFontData(font: CTFont) -> Unmanaged<CFData>? {
    // 根據字體獲取字體描述
    let descriptor = CTFontCopyFontDescriptor(font)
    // 根據字體描述獲取屬性字典
    let attributes = CTFontDescriptorCopyAttributes(descriptor)

    if CFPropertyListIsValid(attributes, .xmlFormat_v1_0) {
        // 若是屬性列表有效,可直接將其序列化
        return CFPropertyListCreateXMLData(kCFAllocatorDefault, attributes)
    }
    else {
        // 不然可能須要分析其中某個屬性,將其丟棄或轉爲可序列化的數據類型
    }
    return nil
}
複製代碼

字體反序列化

如下代碼中的示例函數展現瞭如何從 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;
}
複製代碼
func CreateFontFromFlattenedFontData(data: CFData) -> CTFont? {
    let immutable = CFPropertyListMutabilityOptions.mutableContainers.rawValue
    guard let attributesUnm = CFPropertyListCreateFromXMLData(kCFAllocatorDefault, data, immutable, nil) else {
        return nil
    }
    let attributes = attributesUnm.takeRetainedValue() as! CFDictionary
// defer {
// attributesUnm.release()
// }
    let descriptor = CTFontDescriptorCreateWithAttributes(attributes)
    let font = CTFontCreateWithFontDescriptor(descriptor, 0, nil)
    return font
}
複製代碼

修改字距

連字(Ligatures)和字距默認是開啓的,可經過將kCTKernAttributeName設置爲 0 禁用,如下代碼爲繪製的前幾個字符設置了較大的字距。

// 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." as CFString)
CFAttributedStringSetAttribute(attributedString, .init(location: 0, length: 0), kCTForegroundColorAttributeName, UIColor.red.cgColor)
var num: CGFloat = 20
let cfNum = CFNumberCreate(kCFAllocatorNull, .cgFloatType, &num)
CFAttributedStringSetAttribute(attributedString, .init(location: 0, length: 18), kCTKernAttributeName, cfNum)
複製代碼

從字符獲取字形

如下代碼展現瞭如何從一個只有一個字體的stringcharacters中獲取字形(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);
}
複製代碼
func GetGlyphsForCharacters(font: CTFont, string: CFString) {
    let count = CFStringGetLength(string)
    let characters = UnsafeMutablePointer<UniChar>.allocate(capacity: count)
    defer {
        characters.deinitialize(count: count)
        characters.deallocate()
    }
    let glyphs = UnsafeMutablePointer<CGGlyph>.allocate(capacity: count)
    defer {
        glyphs.deinitialize(count: count)
        glyphs.deallocate()
    }
    CFStringGetCharacters(string, .init(location: 0, length: count), characters)
    CTFontGetGlyphsForCharacters(font, characters, glyphs, count)
    print("characters: \(characters.pointee)")
    print("glyphs: \(glyphs.pointee)")
}
複製代碼
相關文章
相關標籤/搜索