文字編輯器中文字斷行及排版演算法研究
袁永福2013/4/15
文字編輯器是一種非常複雜的圖形軟體,涉及到的很多開發技巧和軟體結構都是傳統的資料庫程式開發中所從未應用的,因此掌握相關技術的人是非常的少的。在其中文字斷行及排版演算法是編輯器開發中的核心演算法之一。如果沒有掌握這個演算法,那只能在開源軟體的基礎上小打小鬧了。
文字排版大致分為以下幾個步驟:
計算文件容器的客戶區寬度。比如設定的紙張寬度減去左頁邊距和右頁邊距的寬度。這裡的文件容器不僅僅指大的正文區域,還包括單元格、文字框之類的文件結構。
斷行,也就是將各個字元從左到右,從上到下的依次放置在文件容器中。產生一行行文字,實現一種流式排版。
行內排版,也就是在文件行中進行字元排版,特別是為了完成文件內容兩邊對齊功能。
分頁。
■■測量字元大小
說到測量字元,就涉及到等寬字型和比例字型的概念了。等寬字型就是使用該字型繪製字元,字元的寬度是一樣的,比如「宋體」,它就是等寬字型,用它來測量和繪製字母「w」和「i」,其寬度是一樣的。比例字型就是使用該字型測量和繪製字元,其寬度是不一樣的,比如「times new roman」,用它來測量字母「w」和「i」,其寬度是不一樣的。
對於等寬字型,可以事先測量乙個字元的寬度,比如「w」,則以後遇到其他字元就使用這個已經測量好的寬度;而對於比例字型,則需要進行實時的測量。
不過一般來說,對於等寬字型和比例字型,中文符號的寬度還是一致的。因此可以實現測量乙個中文字元的寬度,以後遇到中文字元就採用這個事先測好的寬度。
這裡帶來乙個問題,如何判斷乙個字元是否為中文字元,那就需要參照gb3212,gbk等計算機字符集的標準來判斷了。一般來說unicode編碼範圍從19968至40869的字元為中文字元,當然為了進一步的優化,可以知道一些全形符號,它們的寬度也等於中文字元。
比如對於字型「wingdings」,所有的字元在這個字型中完全變味了,就表示乙個個特定形狀的符號,判斷是否是中文就毫無意義了;另外對於條碼字型也有這種情況。
不過解析字型二進位制檔案資訊還是要花掉不少時間的,比如對於宋體,其字型檔名simsun.ttc,檔案大小15mb,含28762個字元輪廓資訊。但分析所得的結果資訊量很小,只有1424 位元組,為此需要將分析結果儲存在乙個臨時檔案中,下次就無需分析這個字型二進位制檔案了。
■■斷行
測量完字元的大小後,編輯器程式開始在記憶體中構造排版物件模型,不斷的將字元填充到最後乙個文件行,若文件行的字元寬度和加上準備新增的字元的寬度大於文件容器客戶區寬度時,就進行斷行,另起一行開始填充字元。
不過也存在提前斷行的情況。為了盡量保證連續的英文本母字元和阿拉伯數字之間不能出現斷行,這樣會導致同乙個邏輯上密切相關的單詞被拆散放在兩行了。因此遇到這種情況需要提前斷行。
當然這樣的操作也不是絕對的,比如遇到連續的超級長的「單詞」時,比如100個連續字元「a」,雖然基本上沒有實際意義,但這是一種必需考慮的邊界條件,很容易導致程式執行錯誤。因此在提前斷行時需要進行這樣的判斷,若真的出現這種情況,那就取消提前斷行。
※前置標點和後置標點
不能出現在行尾的符號稱為前置標點,例如「([¨·ˇˉ―‖』」…∶、。〃々〉》」』】〕〗!"'),.:;?]`|}~¢」。
比如乙個文字行內容為「?張三李四王五【」,這就是一種不和規範的文字行,需要避免這種情況。
在進行文字斷行時,若這個文件行的最後乙個字元是前置標點時,需要進行提前斷行;如果斷行後第乙個要排版的字元為後置標點時,也需要進行提前斷行。
在進行斷行的時候,對於段落符號要進行一些特殊處理。段落符號本身是有一定的寬度的,但當文件行要執行斷行時,參與計算時的寬度就可以當做零了。
在排版的程式設計實踐中,筆者採用堆疊的方式實現斷行。首先將所有要排版的字元壓入乙個堆疊中,然後迴圈從堆疊中peek獲得乙個字元元素,然後試圖新增到當前文件行中,若文件行剩餘空間足夠容納新字元,則將該新字元新增到文件行中,同時堆疊執行pop操作。若文件行剩餘空間不夠,則不執行pop操作,新建乙個文件行,從而開始新的迴圈。如果出現提前斷行,則需要將當前文件行中的若干個字元元素移出來,並壓入堆疊中等著下一次迴圈中使用。
※停止行
使用者在編輯的時候會頻繁的輸入字元,這就使得程式頻繁的進行文件排版操作。當文件內容比較多,比如上萬個字元時,進行整個文件範圍的字元排版及重新繪製使用者介面可能要花上幾百毫秒的,這樣就導致使用者輸入字元時編輯器反應遲鈍。
為此在使用者編輯錄入的時候,需要進行文件內容的部分區域的文字排版,而其他區域的排版就不要動了。為此在程式設計中採用了一種技巧來減輕排版的工作量,筆者稱之為停止行技巧。
■■行內排版
文字斷行完成後,需要進行行內排版。
文件行中各個字元的寬度之和不大可能正好等於文件容器的客戶區寬度。兩者會有空白差。
由於中文字元和英文本元寬度不一樣,對於不等寬字型,各個英文本元、數字字元等寬度還不一樣。使得各個文字行的字元寬度之和是不一樣的,使得各個文件行右邊緣是參差不齊的。這樣比較嚴重的影響美觀。
為此需要將文件行的寬度拉長成文件容器客戶區寬度,由此會額外的製造出不少空白,此時需要將這些空白比較均勻的分攤到各個字元上。此處是比較均勻的分攤,但不是完全均勻,是有一定的分布演算法的。
為此要分攤由於文字兩邊對齊而造成的額外空間時,首先要對文件行的字元進行分組,然後將額外的空白平均分攤到字元組上。
例如對於文字「dcwriter電子病歷文字編輯器。」,其分組為「[dcwriter][電][子][病][歷][文][本][編][輯][器][。]」,其中一對方括號之間就是一組字元,這樣就分成11組。如果額外的空白寬度為20個單位,則需要將空白平均分攤到這些字元組上面,最後一組不分攤,於是前面10組分配得到20÷(11-1)=2個單位的空白寬度。在排版時將這10個2單位的空白寬度插入到字元組之間,這樣就能拉長文件行的寬度正好等於文件容器的客戶區寬度。
■■分頁
分頁本質上說就是計算分頁線的位置。其過程如下
首先計算出標準頁的高度,也就是紙張高度減去上下頁邊距的值,還需要考慮到頁首頁尾的修正量。
設定當前分頁線的位置,也就是上乙個分頁線的位置加上標準頁高。
遍歷文件行,若分頁線的位置在文件行中間,說明該行文字被分割到兩頁中,此時將分頁線的位置向上移動,使得分頁線在當前文件行的上邊緣和上乙個文件行下邊緣的中間。
在進行分頁時,也需要判斷很多邊界條件,比如當某個文件行非常高,比如中間放置了乙個超高的,使得這個文件行的高度大於標準頁高,此時就不能隨便移動分頁線的位置了。
另外當文件中有**時,則需要深入到**單元格內部進行修正分頁線位置的操作,這是一種遞迴操作。
在電子病歷業務中有著繼續列印的功能,在筆者的實現中,續打位置實際上就算是一種特殊的分頁線,這樣就能避免在續打時文字被分割列印的情況。
文字斷行和排版演算法是非常複雜的,即使筆者經過長期的重構再重構,優化再優化,也還是花費了一萬多行的c#**來實現這個功能,而且還有不少地方仍然需要優化。
文字編輯器
include stdio.h include string.h include stdlib.h define len sizeof struct linetable struct linetable main 輸出並加入行號 display struct linetable head else ...
文字編輯器
text editer文字編輯器 data ref container tec type ref to cl gui custom container.data ref edit type ref to cl gui textedit.data text line 85 work area it t...
文字編輯器
uedit notepad mybase 筆記類工具 sublime 列模式操作很牛叉 sublime 快捷鍵 常用類操作模式 1.選擇編輯的行,ctrl l,就可以操作了。2.按住shiftr 鍵盤,滑鼠右鍵選擇欲選擇內容。ctrl l 選擇整行 按住 繼續選擇下行 ctrl kk 從游標處刪除至...