?CoreText是一個高效處理字符和字形轉換和進行文字排版的框架,API基于C語言。
常見的CoreText類介紹
(1)、CFAttributedStringRef
屬性字符串,用于存儲需要繪制的文字字符和字符屬性
(2)、CTFramesetterRef
framesetter對應的類型是 CTFramesetter,通過CFAttributedStringRef進行初始化,它作為CTFrame對象的生產工廠,負責根據path生產對應的CTFrame;
(3)、CTFrame
CTFrame是可以通過CTFrameDraw函數直接繪制到context上的,當然你可以在繪制之前,操作CTFrame中的CTLine,進行一些參數的微調;
(3)、CTLine
在CTFrame內部是由多個CTLine來組成的,每個CTLine代表一行;可以看做Core Text繪制中的一行的對象 通過它可以獲得當前行的line ascent,line descent ,line leading,還可以獲得Line下的所有Glyph Runs;
(4)、CTRun
或者叫做 Glyph Run,每個CTLine又是由多個CTRun組成的,每個CTRun代表一組顯示風格一致的文本,是一組共享想相同attributes(屬性)的字形的集合體;
渲染流程
- 當我們需要排版時,可以對字符串設置各種格式,生成NSAttributeString;
- 然后用NSAttributeString去創建CTFramesetter類,
- CTFramesetter會處理排版信息,然后生成排版后的結果CTFrame;
- CTFrame是一段或者多段文本,每段文本又由多行文字組成,每行的表示為CTLine;
- CTLine是一行文本,每行文本由多個CTRun組成,CTRun是一小段連續的字形;
- CTTypeSetter負責上下文相關排版處理,比如說換行,每個CTFrame中都會有一個CTTypeSetter; 他們之間的關系圖如下:
總的來說,CTFramesetter是生成CTFrame的工廠類,初始化參數是attributed string,會在內部創建CTTypesetter并進行實際的排版;
CTLine類似每一行的文字,CTRun是一行中具有相同屬性的連續字形,比如說“我正在分享閱讀器”,就會由三個CTRun組成,分別是“我正在”、“分享”、“閱讀器”(因為“分享”兩個字加粗了,否則就會是一個CTRun)。
?CoreText的使用流程:
- 使用core text就是有一個要顯示的string,
- 然后定義這個string每個部分的樣式生成富文本attributedString
- 由富文本生成 CTFramesetter
- CTFramesetter得到CTFrame
- 使用繪制(CTFrameDraw)CTFrame?
關鍵函數介紹
由富文本字符串得到CTFramesetter
- CTFramesetterCreateWithAttributedString(att as CFAttributedString)
-
CFAttributedString是NSAttributedString的CF對象,可以直接強轉;
CTFramesetterRef CTFramesetterCreateWithAttributedString( CFAttributedStringRef string );
CTFramesetter包含了富文本字符串的布局信息和相關屬性,供后續的繪制操作使用。最主要的作用就是生成下面的CTFrame。
通過調用 CTFramesetterCreateWithAttributedString
函數,可以將富文本字符串轉換為 Core Text 的布局對象,為后續的繪制操作提供所需的文本排版和屬性信息。這樣,你就可以使用 Core Text 提供的更多功能來自定義文本的布局、字體、顏色等,并實現高度定制化的文本渲染效果。
CTFramesetterRef
對象并不直接進行繪制操作,它只包含了文本布局的信息。要將文本繪制到圖形上下文中,還需要使用 CTFrameDraw
函數創建并繪制 CTFrameRef
對象。
生成CTFrame
- CTFramesetterCreateFrame(framesetter, CFRangeMake(pageStart, 0), path, nil)
使用 CTFramesetterRef
對象、文本范圍、路徑和其他參數創建一個 CTFrameRef
對象,
CTFrame是排版數據,可直接通過重寫View的drawRect方法渲染到頁面上
framesetter
:上面創建的CTFramesetterRef
stringRange
:要使用的文本范圍,即?CFRange
?結構體。- 可以通過設置?
CFRangeMake
?參數來確定要使用的富文本字符串的起始位置和長度 - 如果范圍的長度部分設置為0,比如CFRangeMake(location, 0),則會盡可能的填滿CTFrame,將繼續添加行,直到文本或空間用完。
- 可以通過設置?
path
:繪制文本的路徑,即?CGPathRef
?類型對象。- 路徑定義了文本應該在畫布上的布局方式和區域。
- 一般傳渲染View的bounds即可
frameAttributes
:可選的附加屬性字典,提供額外的布局控制和屬性設置。
計算分頁
- CTFrameGetVisibleStringRange(frame)
CTFrameGetVisibleStringRange
函數的作用是獲取給定文本框架(CTFrame
)中可見的文本范圍。可見的范圍是指在當前文本框架大小和路徑下實際可見的文本部分。
返回值: CTFrameGetVisibleStringRange
函數返回一個 CFRange
結構體,表示給定文本框架中可見的文本范圍。該范圍包括起始位置(location
)和長度(length
)信息。
比如原文有1W字,當前的frame只能顯示200字,那么返回的Range就是(0,200),下一頁在從200的基礎上進行計算,比如第二頁算出為(200,430),在下一頁就從430開始計算,如此循環就可計算出這1W字需要分多少頁,并且每頁內容的CTFrame都已生成。
通過上面的介紹,把這幾個函數連起來,就是數據準備階段的核心方法:
- 根據txt內容生成String -> 在由String生成富文本-> 由富文本生成framesetter,
- 根據頁面大小計算生成單頁的CTFrame
- CTFrame獲取當前Frame有效的文字顯示范圍,下一頁的location累加,循環計算分頁,保存得到每頁的內容范圍和每頁的CTFrame
func createCTFrame(contentStr: String) {let range = NSMakeRange(0, contentStr.count)let att = NSMutableAttributedString(string: contentStr)att.addAttribute(.foregroundColor, value: UIColor.lightGray, range: range)att.addAttribute(.font, value: UIFont.systemFont(ofSize: 22), range: range)let framesetter = CTFramesetterCreateWithAttributedString(att as CFAttributedString)let path = CGPath(rect: self.readView.bounds, transform: nil)var pageStart = 0var frameArray: [CTFrame] = []var i: Int = 0repeat {let frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(pageStart, 0), path, nil)let pageRange = CTFrameGetVisibleStringRange(frame)let beginIndex = contentStr.index(contentStr.startIndex, offsetBy: pageRange.location)let endIndex = contentStr.index(beginIndex, offsetBy: pageRange.length)let onePage = String(contentStr[beginIndex..<endIndex])pageStart = pageRange.location + pageRange.lengthprint("第\(i)頁" ,pageRange, onePage)i+=1frameArray.append(frame)} while(pageStart < contentStr.count )self.frameArray = frameArray}
渲染方法
-
CTFrameDraw(frame, ctx)
渲染的核心方法,CTFrameDraw
方法的作用是將指定的文本框架對象繪制到圖形上下文中,實現文本的可視化呈現。
具體做法:在繼承UIView的子類中,重寫drawrect方法,里面最重要的一行就是CTFrameDraw(frame, ctx),即可完成渲染:
/// 繪制
override func draw(_ rect: CGRect) {guard let frame = frameRef, let ctx = UIGraphicsGetCurrentContext() else {return}ctx.textMatrix = CGAffineTransform.identityctx.translateBy(x: 0, y: bounds.size.height)ctx.scaleBy(x: 1.0, y: -1.0)CTFrameDraw(frame, ctx)
}
除了?CTFrameDraw , 還想要對文本內容有更精細的控制,可以使用CTLineDraw,CTRunDraw
-
void?CTLineDraw(CTLineRef line,CGContextRef context )
-
void CTRunDraw(?CTRunRef run, CGContextRef context,CFRange range )?
CTLineDraw
函數繪制的是單行文本,需要在CGContext中設置好position,在圖文混排時,可以用到。
CTRunDraw
函數繪制的是單個文本運行,YYText使用的渲染方法就是CTRunDraw,對于控制特別精細的可以但是CTRun控制渲染。
以下是在學習的過程中找到的資料:
CoreText的基礎知識了解:
CoreText實戰講解,手把手教你實現圖文、點擊高亮、自定義截斷功能 - 簡書
文字排版入門—— 排版基礎、CoreText和圖文混排-騰訊云開發者社區-騰訊云
iOS 基于CoreText的排版引擎 - 簡書
比較完整的txt閱讀器demo:
iOS: .txt 小說閱讀器功能開發的 5 個老套路 - 掘金
套路繼續, .txt 小說閱讀器功能開發 - 掘金
最簡版demo: 使用coretext計算分頁并渲染,上面demo的功能多,導致核心邏輯淹沒在業務代碼中,找起來麻煩,所以做了一個只展示核心原理的最簡demo :?
博客園系列文章:
https://www.cnblogs.com/summer-blog/p/6030641.html
https://www.cnblogs.com/summer-blog/p/6030885.html
https://www.cnblogs.com/summer-blog/p/6044118.html
https://www.cnblogs.com/summer-blog/p/6402664.html
比較精細的閱讀器思路,頁面行高重排,目前我們還用不到
我在七貓做閱讀器——排版篇
從基礎的各種CoreText渲染,到頁面之間切換動畫都有獨立的demo,最后有一個把CoreText渲染+頁面切換集成在一起的demo?
小說閱讀器的設計和實現 - 簡書
閱讀器多種翻頁的設計與實現 - 簡書?
GitHub - loyinglin/LearnCoreText