『TextLayout』Font 與大小計算

sdt1005 7年前發布 | 9K 次閱讀 iOS開發 移動開發

前端作為一個展示平臺,打交道最多的就是文字和圖形。其實,文字也是一種圖形。在查閱資料后,大概總結了下:字符布局范圍,文字繪制到屏幕上的流程,自定義 inputView等。

環境信息

macOS 10.12.4

Xcode 8.4

iOS 10.4

字體

glyph

字形,估計用英文還要好理解一點:symbol。每個字,都有各種各樣的字形,如「A」:

那么,是否代表著字母和字形之間,有某種對應關系呢?這也不盡然。

Ligature

連體。如果說,字母和字形是一對多的關系,其實也不對,因為還會存在連體的情況。這使得一個字形,也對應這多個字母:

計算機存儲字符的方式為「數字 – 字符編碼」的映射表。而 iOS 與 macOS 平臺下,均使用 Unicode 編碼。它獨立于平臺,語言等存在,解決了計算機系統中,各種編碼方案之間的沖突。除此之外,還提供了應該如何處理上下文,如何斷句換行,如何在不同語言間排版,如何格式化數字、時間等解決方案。

typeface

字體。font 和 typeface 翻譯過來都是字體,所以為什么說中文理解更麻煩呢。來看看英文解釋:

  • typeface: a particular design of type
  • font: a set of type of particular face and size

所以,font 其實是 typeface 和 size 的組合。我們在初始化 UIFont 的時候就能看出,需要指定 font name(這是之后要介紹的 font family),還需要指定 font size。當然,font 也不只是包含這兩個信息,接下來提到的 typestyle 也是其中之一。

typestyle

字體樣式。一種字體可能會提供多種不同的樣式。如,斜體、粗體等。

font family

即同一種字體,不同樣式的組合。如,宋體+粗體,宋體+細體,宋體+斜體,它們均為宋體,但是又有著不同的樣式,整個組合形式,即宋體的 font family。

綜合以上的概念,可以得出如下公式:

字體布局

將文字渲染到界面的過程,即是將 text 生成 glyph,通過 text layout 排版到 text view 的過程。對于英文來說,從 text view 的左上角開始排版,到達右邊界后,另起一行,直到布局到右下角,結束。

平時經常需要計算文字大小,用于符合設計圖要求。但是,文字的范圍,行間距這些到底是什么?有沒有更簡便的計算方式?先來看看字符之間都有哪些間隙:

圖中標的名字,都對應著 UIFont 的屬性:

@property(nonatomic, readonly) CGFloat ascender;
@property(nonatomic, readonly) CGFloat descender;
@property(nonatomic, readonly) CGFloat capHeight;
@property(nonatomic, readonly) CGFloat xHeight;
@property(nonatomic, readonly) CGFloat lineHeight;
@property(nonatomic, readonly) CGFloat leading;

所以,要計算一行文本的高度,可以直接調用 lineHeight 。而實際調用 UILabel 計算出來的高度,等于 ceil(font.lineHeight) ,這也是計算方法內部,做的優化。

下面這個圖,是單個字母的布局規則,通過它來認識布局中的其他元素:

metrics

單位長度。對于橫向布局的字符來說,布局系統會給一個單位間距,也就是途中看到的 Advance width。也就是從 origin 點,到 glyph 真正渲染的距離,這也是與下一個 glyph 之間的間距。在這里,左間距叫做 left-side bearing,又間距叫做 right-side bearing。而縱向排版,則是用 ascent 與 descent 表示,他們分別代表頂部與底部和 origin 距離。Bounding box 即是真正渲染出來,用戶能看到的部分。

kerning

字間距。默認情況下,橫向排版就是一個字接一個字,但是很多時候,為了好看,我們會調整字間距。給 NSAttibutedString 設置對應的 NSKernAttributeName 即可。

leading

行間距。這個應該很好理解了,從之前的圖可以看出:

字體大小計算

通過對布局的基本介紹,大致能知道 glyph 的布局范圍。那么,我們再來看看平時用得最多的文字范圍計算。在我接觸到的項目中,幾乎都有類似字體范圍計算的 category,目前我司的是這樣的:

@interface UILabel (STExtension)

- (CGSize)st_size;
- (CGSize)st_sizeWithMaxsize:(CGSize)size;

@end

不僅如此,還有 NSString 的,還有 NSAttributedString 的。而散落在工程中的,還有各種各樣的計算方法:

// NSStirngDrawing.h
- (CGRect)boundingRectWithSize:options:attributes:context:
- (CGSize)sizeWithAttributes:

// UILabel.h
- (CGRect)textRectForBounds:limitedToNumberOfLines:

// UIView.h
- (CGSize)sizeThatFits

// UIFont.h
@property(nonatomic, readonly) CGFloat lineHeight;

官方提供的頂層接口就有好幾個,那么,應該用哪個,哪個更為準確呢?我們一一試驗一下。

經過查看調用棧,最終的調用方法分別為:

  • NSStirng : boundingRectWithSize:options:attributes:context:
  • NSAttributedString : boundingRectWithSize:options:context:
  • UIFont : lineHeight

而 UI 控件則是在這些方法上進行向上取整,以保證渲染效率。除此之外, UILabel 的 textRectForBounds:limitedToNumberOfLines: 方法實在有趣,不僅可以自行判斷是計算 text 還是 attributedText,而且還能給定高度。也就是說,當文本 < limitedLines 時,返回文本自身高度,而超過時,則返回最大高度。

封裝

根據日常用到的計算場景,我重新封裝了 category,下面是主要修改:

  • 提供單行文本的高度計算。直接 ceil(font.lineHeight) 。
  • 提供指定行數的文本的高度計算,與單行類似。
  • 給 NSString 、 NSSAttributedString 提供指定文本行數的計算方式,其中,在 NSString 的 + load 方法中初始化靜態 UILabel ,并直接調用 label 的相關方法進行計算。

完整代碼可以查看 repo 的 UIFont+STSize , UILabel+STSize , NSString+STSize , NSSAttibutedString+STSize 。

問題

當 NSAttributedString 的有 firstLineHeadIndent 時,也就是首行縮進屬性,計算會有問題。具體情況如下:

attributtedText.string = @”abcabcabc…(1000)…abc”;

numberOfLines = 0;

firstLineHeadIndent = 20;

maxSize = CGSizeMake(10, HUGE);

調用 UILabel 的 textRectForBounds:limitedToNumberOfLines: 方法,能正確獲得 size。

但是,當 attributtedText.string 太短,只能顯示一行時,返回的 size 大小卻只包含 text 的 size,而沒有加上 firstLineHeadIndent。

也就是說,當 text 只夠顯示一行文本時,如果有首行縮進,就會出問題,所以在使用時,還是要小心。

 

來自:http://www.saitjr.com/ios/textlayout-font-and-size.html

 

 本文由用戶 sdt1005 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!