Not Again, MingLiu!

長話短說,從 FreeType 2.4.4 (2010-11-28 release) 開始,只要編譯的時候有
啟用 BCI (Byte Code Interpreter) ,新細明體就不應該會破掉了,只是因為一
些原因 (下面詳述),一直到 2.4.5 (2011-06-24 release) 才算是完全解決。

這個問題牽扯到許多有趣知識,所以我決定作個筆記以免以後忘掉。

TrueType

首先是一些名詞解釋,TrueType 是常見的字型 (font) 格式,由 Apple 發展,
後來被 Microsoft 採用並發揚光大;雖然沒有經過任何國際組織的標準化,在當
時儼然已經成為業界標準 (de facto standard)。

TrueType 使用的檔案格式又稱為 sfnt 格式,由許多不同的表格 (table) 組
成,每個表格由四個字的標籤 (tag) 表示,其中我們最關心的是 glyf 這個表
格,因為所有的字符 (glyph) 都儲存在這個表格內。每個字符僅僅紀錄了形狀,
要知道某個編碼的某個字元 (character) 是對應到哪個形狀,還要透過 cmap
這個表格來查詢;也有一個字沒有相對應的字符,或是沒有任何字對應到某個字
符的情況。

參考資料:

Hinting (Instructing)

TrueType 是一種外框字型 (Outline Font),也就是說每一個字符都是以向量圖
形的方式呈現,理論上可以在各種不同解析度下呈現相同的形狀。

DejaVu.tiff O Rasterize
M M Rasterize

但是越是在低解析度的時候,由於可用的像素較少,越容易遇到邊界的表達能力
問題,如右圖的 M 字,若沒有對齊像素點的話,呈現出來的圖案就會比較瘦長
且左右不對稱,如果稍微向右移動並加寬一點,呈現出來的圖案就會比較接近原
本向量圖形所預期的形狀;這個動作正是 Hinting 中的對齊格線 (grid
fitting) 功能。

在 TrueType 檔案中,描述 Hinting 的方式其實是儲存在字型的 fpgm
prep 表格中的小程式,或是內嵌在字符中。看起來像是這樣:

    PUSHB[000]   # Push one byte onto the stack.
    3            # Value for loop variable.
    SLOOP[]      # Set the loop count to 3.
    PUSHB[002]   # Push three bytes onto the stack.
    5            # Point number.
    46           # Point number.
    43           # Point number.
    ALIGNRP[]    # Align the points 5, 46 and 43 with rp0 (point 8). This will
                 # align the height of the two lower serifs. 

使用這個小程式的方法是 Apple 的專利,但是因為 TrueType 一開始只是業界標
準,Apple 並沒有在規格書裡面提及專利的相關資訊,造成後來使用 FreeType
啟用 BCI (Byte Code Interpreter) 可能會侵犯專利的問題。(相關專利已經在 2010 年 4 月全數過期)

對每個字符做 Hinting 是個耗時耗力的工作,就算是只有數百個字符的西文字
型也要花上數倍於繪圖的時間來做 Hinting,因此除了少數高品質的字型外,多
數的字型是沒有 Hinting 的。而隨著顯示裝置的解析度越來越高,對於
Hinting 的需求也越來越少,在較新的作業系統上可能會忽略部份 Hinting 的
資訊或是完全關閉 Hinting。而在自由軟體的世界中,大多使用 FreeType
的 Auto-Hinting 技術來支援解析度較低的顯示裝置。

參考資料:

MingLiu

細明體 (MingLiu) 與新細明體 (PMingLiu) 是華康 (DynaLab) 設計的字型,內
建於 Microsoft 的作業系統中,使用非常廣泛。華康的字型使用了特殊的組字
方式,似乎是為了從自有的字型資料轉換到 TrueType 才會使用這樣的方法。

首先,定義一組筆劃的字符,只用來組字,不對應到任何的編碼:

PMingLiu Strokes

然後在每一個個別的字符中用參照的方式把筆劃匯入,這時筆劃間的相對位置是
亂的,最後再用 Hinting 的功能把筆劃移動到對的位置去:

PMingLiu uni65B0
PMingLiu Hinting

這樣濫用 Hinting 的方式,也導致如果不開啟 Hinting 就會看到破碎的字符:

PMingLiu Broken Glyph

為什麼說是濫用?因為只要沒有打開 Hinting 就會顯示錯誤,表示用錯地方了;
有些時候是不需要,或是沒辦法使用 Hinting 的。

參考資料:

Tricky Fonts

除了新細明體,華康還有一些字型也是使用類似的方式組字,同樣很多人使用的
標楷體就是其中之一,還有一些日本字型也是濫用 Hinting 的功能來繪製字符,
所以 FreeType 其實有內建黑名單來對付這樣的字型,遇到了就強制開啟
Hinting。一開始這個名單是用字型名稱來列表的:

    static const char trick_names[TRICK_NAMES_COUNT]
                                 [TRICK_NAMES_MAX_CHARACTERS + 1] =
    {
      "DFKaiSho-SB",     /* dfkaisb.ttf */
      "DFKaiShu",
      "DFKai-SB",        /* kaiu.ttf */
      "HuaTianKaiTi?",   /* htkt2.ttf */
      "HuaTianSongTi?",  /* htst3.ttf */
      "MingLiU",         /* mingliu.ttf & mingliu.ttc */
      "PMingLiU",        /* mingliu.ttc */
      "MingLi43",        /* mingli.ttf */
    };

但是這樣還不夠,因為很多 PDF 輸出軟體會把字型的名稱亂改:

    % pdffonts ~/pdf31yNwCi71d.pdf
    name                            type              emb sub uni object ID
    ------------------------------- ----------------- --- --- --- ---------
    AAAAAA+TimesNewRoman            TrueType          yes yes yes      9  0
    AAAAAB+___                      TrueType          yes yes yes     16  0
    AAAAAC+___                      TrueType          yes yes yes     17  0
    AAAAAE+____                     TrueType          yes yes yes     18  0
    AAAAAD+____                     TrueType          yes yes yes     19  0

遇到這樣的情況就破功了,所以很多人在看 PDF 的時候會遇到字破掉的問題,我
先在中提出可能的解法,後來發現 Suzuki Toshiya 在 2010-11-22 的時候加
入用 cvt/fpgm/prep 表格的加總檢查來比對黑名單,因為這三個表格是只要是
有使用 Hinting 的字型都會有,也不太會改變的。

    static const tt_sfnt_id_rec sfnt_id[TRICK_SFNT_IDS_NUM_FACES]
                                       [TRICK_SFNT_IDS_PER_FACE] = {
      ...
      { /* MingLiU 1995 */
        { 0x05bcf058, 0x000002e4 }, /* cvt  */
        { 0x28233bf1, 0x000087c4 }, /* fpgm */
        { 0xa344a1ea, 0x000001e1 }  /* prep */
      },
      { /* MingLiU 1996- */
        { 0x05bcf058, 0x000002e4 }, /* cvt  */
        { 0x28233bf1, 0x000087c4 }, /* fpgm */
        { 0xa344a1eb, 0x000001e1 }  /* prep */
      },
      ...

只是原先這個方法只有在字型沒有名稱的時候才會啟用,我加了 patch 改成無條
件檢查黑名單,從此就應該不會有漏網之魚了。

參考資料:

Sign Extension

只是後來我在我的機器上面仍然發現某些 PDF 仍然顯示破碎字型,難道是加總
檢查也會出錯?因為牽扯到加總的問題,第一個想到的就是可能跟機器的整
數長度有關,立刻改用 32-bit 的環境測試同一個 PDF,果然就好了。但是計
算檢查碼的演算法很簡單,也沒有溢位的疑慮,那是哪裡出了問題?好在 IRC
上有大大給了提示:

    kanru> hum.. checksum *: 0xa344a1eb
    kanru> checksum #: 0xffffffffa344a1eb
    j5g> kanru: 哈,因為 init checksum = 0xffffffff, sign extension 變
                成 0xffffffffffffffff?
    j5g> 然後之後都只處理後面 32bit
    kanru> j5g: 不過有可能是 font 裡面是錯的
    kanru> 0xffffffffa344a1eb 是直接從 font table 讀出來的
    j5g> 喔,那就是 0xa344a1eb sign extended to 0xffffffffa344a1eb

原來是 FreeType 在讀入字型資訊的時候,強制把 int 轉型為 uint 結果發生
sign extension 錯誤,之後比對的時候就跟著錯了。送了 patch 之後,應該天
下太平了吧?

參考資料:

PDF correctness

有人問說:那為什麼不乾脆隨時打開 Hinting 就好?據我所知目前的自由 PDF
Viewer 都是預設關閉 Hinting 的,除了上述的理由外,我猜大概是因為
Hinting 有可能大幅度的改變一個字符的寬度,如下圖:

FtDiff DejaVu

可以發現三種顯示方式的寬度都不一樣,而 PDF 是一種接近印刷品質的檔案格
式,有可能在輸出 PDF 的時候已經丟失動態改變寬度的能力,kern 的修正也已
經沒辦法修改,這時候如過還讓 Hinting 去改變字符的寬度的話,很有可能得到
不理想的結果。不過以上只是我的猜想,還沒有找各 PDF Viewer 的作者證實。
至於封閉的商業 PDF Viewer 又是怎麼做的就更不得而知了。

結論:快升級到最新的 FreeType 吧!如果還有遇到顯示錯誤的 PDF 可以寄給
我,如果以上筆記有疏漏錯誤的地方歡迎指正 ?

Understanding UUID

通用唯一識別碼 (Universally Unique IDentifier, UUID) 或是全域唯一識別
碼 (Globally Unique IDentifier, GUID) 是一個 128 bits 的整數,並保證其
在時間與空間的分佈都是獨一無二的。UUID 由開放軟體基金會(OSF) 標準化後
用在他們的 DCE 系統上,後來在微軟的 COM 系統上發揚光大。除此之外在許多
地方也都可以看到 UUID 的身影,如 Linux 上的分割表/區塊裝置就是以 UUID
來標示,或是RSS 的 <guid> 標籤也可以使用 UUID,實際上 UUID 是標準的
URN 表示法之一,你可以在任何需要標示單一物件的地方使用 UUID。

UUID 的文字形式為一個 8-4-4-4-12 的十六進位表示,共有 16 個 bytes,有
人說使用 UUID 不方便人類辨識,但了解 UUID 的組成後你還是可以從這個表示
法看出一些端倪來。本文參考的是 IETF 版本的 RFC4122。

UUID

UUID 共有四個版本,第 13 個字元的位置就是表示版本號。第一種是以時間和網
路卡號組成,時間是以一百奈秒為單位,網路卡號理論上是不會重複的,再加上
clock_seq 這個每次開機重設一次的亂數欄位,就算時間回朔了也不會重複,
代號是 1。第二種和第四種是以命名空間加上一個 hash 組成的,分別可以使
用MD5 或是 SHA1 演算法,算出來後就填到空位中,代號是 35。第三種
是全亂數組成,代號是 4

因此我們可以在不同的情境選用不同的 UUID,也可以從 UUID 看出版本跟時間等
資訊,如 4ef17586-f187-11df-8xxx-xxxxxxxxxxxx 看到第三個區塊是
11df 就可以知道是最近產生的以時間卡號為基礎的 UUID,時間
1dff1874ef17586 解出來就是 2010-11-16 13:42:09.173031.0 UTC。而
7c0fdbe4-1b09-4278-9fc9-5f0c6a1f2ae2 就是純亂數的 UUID,沒有任何
意義。

uuid-el

在轉換 Blog 到 ikiwiki 的時候,為了要整合 Disqus 系統,每篇都需要一個唯
一的識別碼,這樣 Disqus 就只認 UUID 而不是網址,將來網址變了也可以對得
上,而此 ID 也可以用在 RSS, ATOM 上面。研究了 RFC 4122 後,用 elisp 寫
了產生 UUID 的工具,uuid-el,使用只需要:

(require 'uuid)
;; Generate UUIDv1
(uuid-1)
;; Generate UUIDv4
(uuid-4)
;; Generate UUIDv3, v5
(uuid-3 uuid-ns-url "http://example.com")
(uuid-4 uuid-ns-url "http://example.com")

搭配以下 snippet 就可以快速展開需要的 metadata:

# -*- mode: snippet -*-
# name: meta
# key: meta
# --
\[[!meta title="$1"]]
\[[!meta guid="${1:$(uuid-urn (uuid-5 uuid-ns-kanru text))}"]]
\[[!meta date="${2:`(current-time-string)`}"]]
$0

最後補上影片:

下一集:用 ffmpeg 製作桌面錄影

TOP 20 Space-Hungry Packages

Arch Linux 與 Debian 皆可以在安裝的時候選擇最小安裝,究竟哪一個
Distribution 在使用一段時間之後最佔空間呢? 剛安裝完的時候似乎是 Arch
比較小一點,但是因為 Debian 套件切的比較細,所以最後可能是 Debian 會比
較小。

當初在試用 Arch 的時候寫了小程式來畫出前 20 個最佔空間的套件,並依此來
瘦身,結果如下:

Arch Linux Package Size TOP 20
Debian Package Size TOP 20

兩邊因為安裝的套件種類不同因此無從比較,Arch 的 TeXLive 因為沒有切所以
會比 Debian 上較大一點,Debian 上的 ghc6 則是為了測試 xmonad 裝的,
Haskell 本身做出來的執行檔就頗大,ghc6 需要 369MB 實在不意外…

底下是製作圖片的程式,實際上是用 Google Chart API 畫的:

    #!/usr/bin/python
    # -*- coding: utf-8 -*-
    from pygooglechart import StackedHorizontalBarChart, Axis
    from subprocess import *
     
    TITLE="Debian Package Size TOP 20"
    PKGSIZE_PROG="./dpkgsize"
    OUTPUT="debian-top20pkg.png"
     
    chart = StackedHorizontalBarChart(930, 320)
    chart.set_bar_width(10)
    chart.set_title(TITLE)
     
    size_data = []
    name_data = []
     
    for ln in Popen([PKGSIZE_PROG], stdout=PIPE).stdout:
        size, name = ln.split()
        size_data.append(int(size))
        name_data.append(name)
     
    chart.add_data(size_data[:20])
     
    max_size = size_data[20]
    min_size = size_data[0]
    label = map(lambda x: str(x / 1024)+"MB",
                xrange(min_size, max_size, (max_size-min_size)/10))
    label.reverse()
     
    rev_name_data = name_data[:20]
    rev_name_data.reverse()
    chart.set_axis_labels(Axis.LEFT, rev_name_data)
    chart.set_axis_labels(Axis.BOTTOM, label)
     
    print(chart.get_url())
    chart.download(OUTPUT)

PKGSIZE_PROG 代換成自己 Distro 的就可以了:

Debian:

    #!/bin/sh
    dpkg-query -W -f='${Installed-Size} ${Package}\n'|sort -nr

Arch:

    #!/bin/sh
    pacman -Qi|awk '/^Installed Size/{print int($4), name} /^Name/{name=$3}'|sort -nr

Display VCS info in prompts

通常 VCS (Version Control System) 都會在工作目錄底下藏一些特殊目錄用來儲存資訊,如 .svn .git .hg 等,因此我們可以在 shell prompt 上動一些手腳,當我們進到 VCS 工作目錄時,自動顯示相關的訊息在 prompt 上。

而 zsh-beta 4.3.6-dev-0+20080921-1 內建了由 Frank Terbeck 開發的 vcs_info 子系統,可以自動偵測 bzr, cdv, CVS, darcs, git, hg, mtn, p4, svk, svn, tla 等多種 VCS,並設定相關的環境變數可以顯示在 prompt 上。

zsh prompt

我的 prompt 設定如下

autoload -Uz vcs_info && vcs_info
zstyle ':vcs_info:*' actionformats \
'%F{5}(%f%s%F{5})%F{3}-%F{5}[%F{2}%b%F{3}|%F{1}%a%F{5}]%f '
zstyle ':vcs_info:*' formats       \
'%F{5}(%f%s%F{5})%F{3}-%F{5}[%F{2}%b%F{5}]%f '
zstyle ':vcs_info:(sv[nk]|bzr):*' branchformat '%b%F{1}:%F{3}%r'
precmd () { vcs_info }
EXITCODE="%(?..%?%1v )"
JOBS="%(1j.%j .)"
local BLUE="%{^[[1;34m%}"
local RED="%{^[[1;31m%}"
local GREEN="%{^[[1;32m%}"
local CYAN="%{^[[1;36m%}"
local YELLOW="%{^[[1;33m%}"
local NO_COLOUR="%{^[[0m%}"
PROMPT="${RED}${EXITCODE}${CYAN}${JOBS}${YELLOW}%* ${RED}%n${BLUE}@%m:${GREEN}%40<...<%B%~%b%<<"'${vcs_info_msg_0_}'"
${YELLOW}%# ${NO_COLOUR}"

詳細的設定請參閱 zshcontrib(3) GATHERING INFORMATION FROM VERSION CONTROL SYSTEMS

Emacs 使用 Xft

Emacs 22 有新的 gtk 介面以及更進步的 unicode 支援,而使用 xft 來顯示字體的部份程式則進了 emacs-unicode-2 這個 branch,據說這會是 Emacs 23 的候選之一。

從 CVS 編譯好 emacs 23 之後,照著一些網路上的教學,的確讓英文的部份變成了 Xft 顯示的漂亮字型,但是中文部份仍然是使用醜醜的 bitmap (X font, 因為我沒有安裝中文字型所需要的相關檔案),網路上也甚少提及如何設定中文的 Xft 字型,大部份都還是用 X font。

其實很簡單,只要:

  1. 設定與設使用 Xft backend,把以下加入 ~/.Xresources 中
Emacs.FontBackend: xft
  1. 在 ~/.emacs 中指定使用的中文字型
(set-default-font "Bitstream Vera Sans Mono-14")
(set-fontset-font (frame-parameter nil 'font)
        'han '("cwTeXHeiBold" . "unicode-bmp"))
  1. 啟動 emacs
emacs-snapshot --enable-font-backend

“–enable-font-backend” 一定要加,因為預設沒有開啟除了 X font 以外其他任何 backend。

Screenshot:

emacs-unicode-2-xft

Update: 先前設定字型的部份,英文的設定會蓋掉中文的部份,現在已經修正。