翻開一本日本小說,您首先會注意到文字是沿著直欄向下延伸,而非橫向跨越行,並且直欄是從紙張的右邊緣向左前進。從小讀直書長大的讀者會覺得橫排文字有些冷冰冰。工程上的問題在於,PDF 與幾乎所有的數位文字系統一樣,是圍繞著從左到右延伸的水平基準線建置的,而內容串流中並沒有「改為向下書寫此段落」的概念。因此,當 Delphi 應用程式必須為台灣、日本或韓國讀者產生證書、詩歌、標牌或傳統格式的法律文件時,版面配置必須手動組合:一個字置於下一個字下方,一欄置於前一欄의 左側
HotPDF 提供了一個開關,可以為您處理逐字的例行計算。您設定的字型帶有一個 IsVertical 旗標,一旦開啟,單次 TextOut 呼叫就會將整個字串堆疊成一個垂直欄,而不是沿著基準線延伸。欄位位置、從右到左的順序,以及一個默默發揮重要作用的字形替換,是本頁面其餘部分將會處理的內容

此開關位於 SetFont
垂直排版(vertical layout)不是頁面或文件的屬性。它是您繪圖所用字型的屬性,您可以使用 SetFont 的第五個引數來開啟它:
// SetFont(FontName, FontStyle, Size, FontCharset, IsVertical)
// The 5th argument flips the current font into vertical mode.
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, DEFAULT_CHARSET, False); // horizontal
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, DEFAULT_CHARSET, True); // vertical
因為旗標依附於字型,您只需再次呼叫 SetFont 並使用不同的最後一個引數,即可在水平和垂直書寫之間進行切換。一個頁面可以在頂部包含一個水平標題,在下方包含直書的正文文字,而 HotPDF 會針對每個字型物件(而非每個頁面)將這兩種模式分開。這就是為什麼混合版面配置是可能的,而無需在您這邊進行任何特殊的模式處理:垂直 SetFont 之後的每個 TextOut 都會進行堆疊,水平之後的每個 TextOut 都會沿著基準線延伸,且最後一個 SetFont 勝出
第四個引數是 Windows 字元集,與水平呼叫所使用的相同。傳入 DEFAULT_CHARSET 讓系統按字串解析字形,這點很重要,因為垂直文件通常會混合不同的文字語系。關於字型的一切依然適用:它必須安裝在建置電腦上,而且您幾乎總是需要設定 FontEmbedding := True,以便檔案在未安裝 Arial Unicode MS 的讀取器上也能呈現相同的中日韓(CJK)字形
一次 TextOut 呼叫就是一欄
啟用垂直字型後,TextOut 呼叫不再從給定點向側邊展開其字串。它將第一個字元放在頂部,然後將其餘字元直接向下放置,每個字形按照字型的高度遞進。您傳入的 X 決定了直欄;您傳入的 Y 決定了直欄頂部的起始位置。因此,要配置一段真實的文字,您需要為每一欄發出一次 TextOut,並在呼叫之間將 X 向左移步,因為中日韓(CJK)的直欄是從右向左閱讀的
var
Pdf: THotPDF;
const
ColTop = 760; // y of the first glyph in every column (points)
ColGap = 28; // horizontal distance between columns
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'VerticalText.pdf';
Pdf.FontEmbedding := True; // embed the CJK face for portable rendering
Pdf.BeginDoc;
Pdf.CurrentPage.Size := psA4;
// A horizontal heading first, in the ordinary writing mode.
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 16, DEFAULT_CHARSET, False);
Pdf.CurrentPage.TextOut(60, 800, 0, 'Tang poem, vertical layout');
// Switch the font into vertical mode; every TextOut below now stacks.
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 18, DEFAULT_CHARSET, True);
// Columns advance right to left, so X decreases with each call.
Pdf.CurrentPage.TextOut(520, ColTop, 0, '床前明月光');
Pdf.CurrentPage.TextOut(520 - ColGap, ColTop, 0, '疑是地上霜');
Pdf.CurrentPage.TextOut(520 - ColGap * 2, ColTop, 0, '舉頭望明月');
Pdf.CurrentPage.TextOut(520 - ColGap * 3, ColTop, 0, '低頭思故鄉');
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
這首詩閱讀起來正如預期:最右邊的直欄最先,自上而下,然後視線跳向左側的下一欄。字串本身沒有任何內容來編碼該順序。您需要在 X 座標中對其進行編碼,在每次呼叫中遞減它們。如果弄錯了方向,詩句就會反過來,這是將水平版面配置移植到垂直排版時最常見的單一錯誤
座標:向下延伸、自右向左
有端有兩個軸在起作用,且它們拉向相反的方向,因此值得保持精確。HotPDF 從頁面的左下角開始量測(以點為單位),Y 軸向上增加。因此,垂直直欄開始於較高的 Y 座標(靠近紙張頂部),隨著 HotPDF 為每個字形減去行高,字元會從該處向下延伸。您只需為每一欄設定一次起始 Y 座標,元件就會處理向下延伸的動作
水平軸是您手動控制的軸。每一直欄都位於其專屬的 X 座標上,且後續的直欄會移動到較小的 X值,因為閱讀順序是自右向左。一個明智的規律是選擇最右邊直欄的 X 座標,然後為隨後的每一次呼叫減去一個固定的直欄間距,就像範例中對 ColGap 所做的那樣。間距由您來選擇;太窄相鄰直欄會接觸,太寬版塊看起來會很稀疏。對於 12 到 18 點的正文文字,比字型大小稍大一點的間距閱讀起來最舒服
必須改變形狀的字形
少數幾個字元不僅僅是其水平型態的旋轉複本;當文字轉為垂直時,必須對它們進行替換。HotPDF 為您處理的字元是日文長音符號 ー (U+30FC),這是在「コーヒー」等片假名單字中出現的長母音長條。當水平繪製時,它是基準線上的短破折號。如果您將這同一個字形堆疊到直欄中,它會橫向平躺在直欄上,這是錯誤的:在直書日文中,該記號會變成連接它所坐落的兩個字元的垂直筆劃。HotPDF 在垂直路徑中偵測 U+30FC,並將其轉譯為垂直線(U+007C),這樣長音符號就會指向正確的方向,而不需要您進行任何工作
這項單一替換涵蓋了大多數簡單實作會出錯的情況,但值得瞭解的是,更通用的問題在此之外還有所加深。完整的垂直排印(vertical typography)還會將拉丁字母和西方標點符號旋轉 90 度、移動小假名(small kana),並將括號和逗號重新定位為其垂直形式,而這一切的完整實現存在於字型的 OpenType 垂直功能中,而非固定規則中。當字型攜帶這些功能時,HotPDF 可以取用它們:垂直替換字形(vert 和 vrt2 GSUB 功能)和垂直字距調整(vkrn 和 vpal GPOS 尋找)是選擇性啟用的,只有在作用中字型確實定義了它們時,才會沿著垂直路徑套用。對於像 Arial Unicode MS 這樣單一廣泛覆蓋字型中的混合中日韓(CJK)文字,內建的 U+30FC 處理加上均勻的行高步進,就足以產生正確、易讀的直欄;當您改用專為精細垂直排版而設計的字型,且想要其原生的假名定位和字形間距時,OpenType 功能就顯得非常重要
在單一頁面上混合語系與方向
真實的文件很少是純粹的。直書日文頁面可能會包含水平英文說明、沿著底部的頁碼,或者在日文旁邊垂直排版的一塊韓文。因為垂直旗標是字型級的開關,您只需藉由交替 SetFont 呼叫即可組合這些內容,而無需管理任何頁面範圍的狀態。設定水平字型,寫入頁首和頁碼(folio),設定垂直字型,配置直欄,再設定水平字型寫入頁尾。每個區域都會採用最近一次 SetFont 的模式,因此唯一需要的規範是在變更方向時隨時呼叫它
混合語系時要規劃的一個細節:中文、日文和韓文意符(ideographs)接近正方形,且以均勻的步長進行堆疊,但在垂直直欄中嵌入的拉丁文字段並不具有那種統一的遞進。如果您在其他為直書的文字中需要幾個拉丁單字,請深思熟慮決定它們應該旋轉以沿著直欄向下延伸,還是直立設定為簡短的水平插入段,並使用其專屬的 TextOut 定位該片段,而不是讓它採用針對意符設計的垂直步長。將混合段視為其專屬的定位問題可以保持直欄的節奏完整
這些片段很小,且它們的組合是可預測的。在 SetFont 中開啟垂直旗標,從固定的頂部 Y 座標為每一欄發出一次 TextOut,並在呼叫之間遞減 X 以便直欄從右向左閱讀。內嵌字型以便中日韓(CJK)字形在傳送到讀者電腦時能夠存活,並讓元件處理 U+30FC 長音符號,以及在字型支援的情況下處理 OpenType 垂直功能。從那裡開始,將版面配置投入生產主要就是算術運算:從測量的區塊寬度中導出直欄 X 位置、將長段落拆分為適合頁面高度的直欄,並為水平頁首和頁碼保留空間
關於它所建立的更廣泛文字和字型介面(包括水平 TextOut 約定和 Unicode 字型註冊),請參閱多語言 Hello World 範例和 TextOut 範例。這裡顯示的 SetFont 垂直開關和 TextOut 呼叫均屬於適用於 Delphi 和 C++Builder 的 HotPDF 元件的一部分