iOS 上利用 fallback 機制為不同語言的文字 (script) 設定字型,從而使得文字混排更為優雅

沁雪幽蘭發表於2018-12-16

iOS 上利用 fallback 機制為不同語言的文字 (script) 設定字型,從而使得文字混排更為優雅

如果您是一位 WEB 開發者,相信您對 CSS 的 font-family 屬性一定不會陌生。通常我們會為 font-family 屬性設定一長串的字型(家族)列表,就像這樣的:

.text { font-family: Menlo, Monaco, Consolas, "Courier New", monospace, Arial, "Microsoft YaHei", "黑體", "宋體", sans-serif; }
複製程式碼

有人肯定會問,為什麼要這麼設定啊?如果你足夠細心,你一定會發現當你瀏覽該網頁時英文字型和中文字型並不是同一種字型,如果你在不同的作業系統甚至不同的電腦上看到網頁所呈現的字型也不一樣。這又是為什麼呢?

此外,在您使用 Microsoft Word 進行段落排版的時候,你會發現 Word 可以自動對中文樣式應用中文字型,對英文樣式應用英文字型。這又是如何做到的呢?

在實際的排版需求中,為了好看,我們通常會需要針對不同語言的文字 (script) 進行不同的字型設定,以達到最佳的視覺效果。當然,對於我們來說,最常見的還是中英文混排。通常設計人員給出的設計稿非常漂亮,可是中英文使用了不同的字型,我們開發人員該如何高保真地還原設計稿的原始設計呢?

iOS 上利用 fallback 機制為不同語言的文字 (script) 設定字型,從而使得文字混排更為優雅

很顯然,對於 WEB 開發者來說,已經有了很好的解決方案,而對於其他客戶端的同學來說,這估計應該就有點犯難了,我們通常會告訴設計人員:“系統不支援” 抑或 “做不了” 之類的話。那事實上,到底能不能做到呢?答案是肯定的。

iOS 上利用 fallback 機制為不同語言的文字 (script) 設定字型,從而使得文字混排更為優雅

要實現這個,我們先來了解一個概念:fallback 機制。

這種機制通常是首先規定拉丁字母、西裡爾字母、希臘字母等西文的字型,然後以一定的順序分別規定阿拉伯字母、天城文(例如印度文)、日文、韓文、簡體中文、繁體中文等文字的字型。文字引擎會盡量用優先的字型來顯示當前遇到的字元,如果 fallback 列表中最靠前的字型不支援此字元,就向後找(這就是所謂 fallback)。CSS 的 font-family 屬性基本也是這樣工作的。

Windows 其實也是類似這樣的。目前 Windows 7 的西文字型是 Segoe UI,簡體中文字型是微軟雅黑,繁體中文字型是微軟正黑,日文字型是 Meiryo,天城文字型是 Mangal…… 所以你根本不需要這樣的軟體,你的作業系統已經是這樣了。

因為中日韓漢字共享 Unicode code point,所以當一個字(比如「直」這個字)同時受到簡體中文、繁體中文、日文、韓文字型的支援,作業系統會根據當前的 locale 以及系統語言列表的順序選擇最優先的書寫系統的字型。

iOS 上利用 fallback 機制為不同語言的文字 (script) 設定字型,從而使得文字混排更為優雅

然而,很少談及移動作業系統上的字型 fallback 機制,更別說實現了,還好我經過不斷查詢,找到一些蛛絲馬跡。由於本人是一名 iOS 開發者,所以我們以 iOS 為例,來看看它的實現方式。

通常,我們使用如下的方式建立一個字型:

let systemFont = UIFont.systemFont(ofSize: fontSize)
複製程式碼

顯然,如果您還這樣建立,肯定無法實現我們的目標。那應該怎麼做呢?我們需要使用到 UIFontDescriptor,程式碼如下:

public extension UIFont {
    convenience init(names: [String], size: CGFloat) {
        
        if names.first != nil {
            let mainFontName = names.first!
            
            let descriptors = names.map { UIFontDescriptor(fontAttributes: [.name: $0]) }
            
            let attributes: [UIFontDescriptor.AttributeName: Any] = [
                UIFontDescriptor.AttributeName.cascadeList: descriptors,
                UIFontDescriptor.AttributeName.name: mainFontName,
                UIFontDescriptor.AttributeName.size: size,
                ]
            
            let customFontDescriptor: UIFontDescriptor = UIFontDescriptor(fontAttributes: attributes)
            self.init(descriptor: customFontDescriptor, size: size)
        }
        else{
            let systemFont = UIFont.systemFont(ofSize: size)
            let systemFontDescriptor: UIFontDescriptor = systemFont.fontDescriptor
            self.init(descriptor: systemFontDescriptor, size: size)
        }
    }
    
    convenience init(families: [String], size: CGFloat, weight: UIFont.Weight = .regular) {
        
        if families.first != nil {
            let mainFontFamily = families.first!
            let descriptors = families.map { UIFontDescriptor(fontAttributes: [.family: $0]) }
            let traits = [UIFontDescriptor.TraitKey.weight: weight]
            
            let attributes: [UIFontDescriptor.AttributeName: Any] = [
                UIFontDescriptor.AttributeName.cascadeList: descriptors,
                UIFontDescriptor.AttributeName.family: mainFontFamily,
                UIFontDescriptor.AttributeName.size: size,
                UIFontDescriptor.AttributeName.traits: traits
            ]
            
            let customFontDescriptor: UIFontDescriptor = UIFontDescriptor(fontAttributes: attributes)
            self.init(descriptor: customFontDescriptor, size: size)
        }
        else{
            let systemFont = UIFont.systemFont(ofSize: size, weight: weight)
            let systemFontDescriptor: UIFontDescriptor = systemFont.fontDescriptor
            self.init(descriptor: systemFontDescriptor, size: size)
        }
    }
}
複製程式碼

呼叫示例:

let text = "これは日本語文章と Roman Text の混植文章です。美しいヒラギノと San Francisco で日本語とローマ字を書きます。System Font のフォントメトリクスには獨自の調整が入っています。\n\nあのイーハトーヴォの\nすきとおった風、\n夏でも底に冷たさをもつ青いそら、\nうつくしい森で飾られたモーリオ市、\n郊外のぎらぎらひかる草の波。\n祇辻飴葛蛸鯖鰯噌庖箸\n底辺直卿蝕薩化\nABCDEFGHIJKLM\nabcdefghijklm\n1234567890iClockᴹᴵᴺᴵClockªMINI"
let font = UIFont(families: ["Lucida Grande", "Baskerville", "Apple SD Gothic Neo"], size: 20, weight: .medium)
//let font = UIFont(names: ["LucidaGrande", "Baskerville", "AppleSDGothicNeo-Thin"], size: 20)
label.font = font
label.text = text
複製程式碼

其中 family 和 font name 這兩個別搞混了,不知道怎麼在 iOS 上看字型的 family 和 font name 的可以下載這個 App:字型預覽(itunes.apple.com/cn/app/字型預覽…)

如果是自定義字型,也可以在同一個wifi的區域網中將字型上傳到 App 裡檢視。

演示動畫:

iOS 上利用 fallback 機制為不同語言的文字 (script) 設定字型,從而使得文字混排更為優雅

iOS 上利用 fallback 機制為不同語言的文字 (script) 設定字型,從而使得文字混排更為優雅

Github原始碼: github.com/pcjbird/fbC…

參考連結:

是否有一種軟體能夠對不同語言的文字指定不同的字型?

www.zhihu.com/question/20…

Fallback to pure Japanese font. No more Chinese font in Japanese text on iOS apps.

github.com/usagimaru/F…

相關文章