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 可以寄給
我,如果以上筆記有疏漏錯誤的地方歡迎指正 ?

Blog Redesigned

最近又把 Blog 整頓了一番,目標是改用靜態網頁。使用靜態網頁搭配一些網頁
樣板程式不禁讓人想起最初使用 MT 來寫 Blog 的時候,不同的是現在有像
Disqus 或是 IntenseDebate 這樣的動態留言服務,讓網頁可以完全
的只提供靜態內容,不用多一個 cgi 程式來提供留言功能,這隱然成為最近流行
的網頁設計方法之一。

轉換的過程中學到一些新東西,紀錄於此。

網頁編譯器

現在網頁是用
IkiWiki
產生的,它是大師 Joey Hess 的作品,大師稱它是一個
wiki compiler
可以透過各種規則把網頁的原始碼 (可能是 markdown 的格式) 轉成 HTML 網頁。
因為強大的 inline 規則以及各式各樣的外掛程式,也有許多人把 ikiwiki 當作
Blog 的平台。

轉換的過程其實滿順利的,因為舊的 Blog 都是以 Markdown 寫成,換到
ikiwiki 的 Markdown 引擎只需要一點點修改。要感謝 WordPress 可以把資料
庫匯出成 XML 格式,轉換只需要簡單的 XSLT 就完成了,我用的
XSL 是參考這裡修改,去掉
Comment 以及更新 meta的格式而成。

改用文字編輯器來寫 Blog 後,會習慣在 72 行的地方換行,但是在瀏覽器中只
要換行就會多一個空白,以前寫網頁的時候遇到中文都要小心的換行才不會讓不
該空白的地方出現空白,也不能打開 Emacs 的 auto-fill 模式。拜 ikiwiki
強大的外掛程式架構以及 Perl 的 Unicode 正規表示式支援,寫個
外掛把不該空白的地方黏起來只要幾分鐘
時間!而 Blog 原始碼則維持容易閱讀的格式。

screenshot

HTML5

既然都換了 Blog 系統,也打算換新的版面,乾脆就升級到網頁標準的新世代
HTML5。在收集 HTML5 相關資訊時發現了 HTML5 Boilerplate 這個網頁樣
板計畫,包含了許多目前 HTML5 的 Best Practice,基於這個樣板設計網頁讓
你馬上就 HTML5 Ready ☺

我認為 HTML5 最有趣的一點是帶有語意的新標籤,如 <article>,
<section> 等等,搭配新的大綱演算法可以自動產生大綱,運用得當
的話,對於機器或是身體不便的人都能更方便的取得資訊。

ikiwiki 原本就有支援實驗性的 HTML5 模式,但是預設的樣板不是很完美,例如
多層嵌套的 <section><nav><article>,都會產生多餘的大綱節
點,若只是需要群組與排版功能的話,最好還是使用 <div> 就好。如我現在
用的 template 長得就像這樣:

    <article>
        <header>
            <h1>TITLE</h1>
            <time>TIME</time>
        </header>
        <div class="content">
            CONTENT
        </div>
        <footer></footer>
    </article>

這裡推薦使用 HTML 5 Outliner 來檢查網頁的大綱,或是用專門的
Chrome 擴展,如果網頁設計無誤,應該會看到如下乾淨的大綱:

screenshot

Web Fonts

使用網路字體可以讓網頁在不同的作業系統、不同的瀏覽器上看起來都是一樣的,
現在有很多提供網路字體的服務,像是 Google 也推出
Google Font Directory,甚至 Fonts.com 還有免費的中文網路字體,
參考 Type Is Beautiful測試頁,速度還不錯呢。

我現在用的是從 Font Squirrel 下載的自由字體,這個網站上不但有大
量的自由字體可以挑選,還有預先整理好的 @font-face Kits 可以直接使用,
或是用 @font-face Generator 產生最佳化的網路字體。

結語

這篇是心得筆記的性質,因為如果不紀錄一下可能很快就忘了…有許多細節都省
略過去,若是要深入探討則每個主題都可以發展成長篇大論,需要更詳細資料的
網友可以參考引用的連結。

一些眉眉角角的東西希望之後有機會可以再單獨拿出來分享囉 ☻

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

Xorg 1.4 XInput Hotplug

Xorg 1.4 開始支援 hal based 的 xinput hotplug,也就是說滑鼠跟鍵盤這些設備可以拔來拔去而不用在 xorg.conf 裡面設定,再加上越來越多 driver 可以 auto-configuration,xorg.conf 裡面的東西越來越少了。

xorg 的 evdev driver 是利用 linux kernel 的 evdev 支援,來使用滑鼠、鍵盤等多種設備,我的 Logitech V450 就一定要用 evdev 才能支援所有的按鍵。

無奈 Debian Sid 中的 Xorg 以及 evdev 都非常的新,但是一些升級的配套措施跟文件跟不上,導致最近發生許多問題,相關的 bug report 有 #443292#442316

今天終於把滑鼠鍵盤都設定好了,關鍵在於 hal 的設定跟 gnome 的設定。

首先是設定 hal,因為 hal 預設只設定了使用 evdev,但是沒有顧慮到非使用 us layout 的人,導致升級之後鍵盤 layout 大亂,目前暫時取消了這項設定,我們可以從 /usr/share/doc/hal/examples/10-x11-input.fdi 把設定撿回
/etc/hal/fdi/policy/,內容如下:

<?xml version="1.0" encoding="ISO-8859-1"?>
<deviceinfo version="0.2">
  <device>
    <!-- FIXME: Support tablets too. -->
    <match key="info.capabilities" contains="input.mouse">
      <merge key="input.x11_driver" type="string">mouse</merge>
      <match key="/org/freedesktop/Hal/devices/computer:system.kernel.name"
              string="Linux">
        <merge key="input.x11_driver" type="string">evdev</merge>
      </match>
    </match>
  
    <match key="info.capabilities" contains="input.keys">
      <!-- If we're using Linux, we use evdev by default (falling back to
            keyboard otherwise). -->
      <merge key="input.x11_driver" type="string">keyboard</merge>
      <match key="/org/freedesktop/Hal/devices/computer:system.kernel.name"
              string="Linux">
        <merge key="input.x11_driver" type="string">evdev</merge>
        <merge key="input.xkb.rules" type="string">xorg</merge>
        <merge key="input.xkb.model" type="string">evdev</merge>
        <merge key="input.xkb.layout" type="string">dvorak</merge>
      </match>
    </match>
  
  </device>
</deviceinfo>

其中關於鍵盤 layout、model、rules 的設定是我加上去的。滑鼠不需更動即可 hotplug 使用。

接下來關鍵的地方是,如果你使用 gnome 的話,記得把 keyboard model 改成 evdev,如圖:

gnome keyboard setting

最後,xorg.conf 就可以清乾淨了:

Section "Device"
        Identifier      "intel"
        Driver          "intel"
        Option          "AccelMethod"     "XAA"
        Option          "XAANoOffScreenPixmaps" "True"
EndSection
  
Section "InputDevice"
        Identifier      "Synaptics Touchpad"
        Driver          "synaptics"
        Option          "CorePointer"
        Option          "Device"                "/dev/input/event9"
        Option          "Protocol"              "auto-dev"
        Option          "SHMConfig"             "on"
        Option          "LeftEdge"              "1100"
        Option          "RightEdge"             "5800"
        Option          "TopEdge"               "1600"
        Option          "BottomEdge"    "4200"
        Option          "HorizEdgeScroll" "on"
EndSection
  
Section "Monitor"
        Identifier      "Configured Monitor"
        Option          "DPMS"
        DisplaySize     330 200
EndSection
  
Section "Screen"
        Identifier      "Default Screen"
        Monitor         "Configured Monitor"
        DefaultDepth    24
        SubSection "Display"
                Modes           "1280x768" "1024x768"
        EndSubSection
EndSection