書接前文: 從零開始, 開發一個 Web Office 套件 (1): 富文字編輯器
這是一個系列部落格, 最終目的是要做一個基於HTML Canvas 的, 類似於微軟 Office 的 Web Office 套件, 包括: 文件, 表格, 幻燈片... 等等.
對應的Github repo 地址: https://github.com/zhaokang555/canvas-text-editor
2.5 觀察一下幻燈片中的文字框
我們發現:
- 一個文字框中有若干行文字
- 一行文字中每個字元的大小, 樣式都可能不一樣
(廢話)- 但是, 上一篇文章中, 我們設定樣式, 是以行為物件的
- 任意一個單獨的字元, 都可以用滑鼠選中
(廢話)- 但是, 上一篇文章中, 我們計算文字包圍盒, 是以行為整體計算的
- 一行文字如果過長, 可以自動折行(預設行為, 可以修改)
- 當我調整文字框的寬度, 折行位置隨之改變
- 文字框的位置和寬高都是可以調整的.
為了將來我們的web slides中能夠用上CanvasTextEditor, 我們需要重構一下我們的程式碼.
2.6 重構Editor
- 修改
src/core/CanvasTextEditor.ts
, 為編輯器加上位置屬性, 寬高屬性, 樣式屬性:
- 修改
src/demo/App.tsx
, 給編輯器傳入位置資訊:
- 修改
src/core/CanvasTextEditor.ts
:
實現render函式, 渲染一個臨時邊框:
新增renderBorderCirclor函式, 渲染邊框上的8個圓形控制點:
- 最終效果:
2.7 設計Editor的整體架構
如下圖所示:
一張幻燈片(Slide)中可能有多個編輯器(Editor), 一個編輯器中可能有多個段落(Paragraph), 一個段落中可能有多行(SoftLine), 一行中可能有多個字元(Char).
2.8 自頂向下實現
接下來, 我們按照自頂向下的方式, 來一步步實現這個架構:
2.8.1 Paragraph
- 修改
src/core/CanvasTextEditor.ts
, 新增一個欄位 paragraphs:
其中, 每個Paragraph要接收這幾個引數:
chars: Char[]
- canvas context
- left
- top
- maxWidth
為什麼Paragraph的建構函式裡, 要直接接收Char列表, 而不是SoftLine列表呢?
因為一個SoftLine並不是真正的一行, 而是根據每個Char的寬度和Paragraph的maxWidth, 實時計算出來的.
我們以後將會實現這樣的feature: 如果使用者調整了Editor的大小, Paragraph的maxWidth隨之改變, 所有的SoftLine都會重新計算. 類似下圖一樣:
- 新建檔案
src/core/CanvasTextEditorParagraph.ts
如上圖, 在構造Paragraph時, 我們需要實現2個邏輯:
calcLayoutForSoftLines
:- 根據maxWidth, 將所有的Char進行分組, 得到softLines. (類似我們上一篇中的splitContentIntoLines方法)
- 根據每個softLine中的Chars的大小, 計算每個softLine的大小
calcLayout
:- 根據每個softLine的大小, 計算Paragraph的大小
然後, 我們來實現這兩個邏輯:
2.8.2 SoftLine
新建src/core/CanvasTextEditorSoftLine.ts
檔案, 並在其建構函式中, 計算傳入的所有chars的位置:
2.8.3 Char
- 新建
src/core/CanvasTextEditorChar.ts
檔案:
- 目前暫時先支援定製
color
和fontSize
兩個樣式:
- 另外, 要對外暴露
setPosition
方法, 方便在SoftLine中為每個Char設定位置:
2.8.4 刪除CanvasTextEditorText
由於之前src/core/CanvasTextEditorText.ts
中的邏輯現在已經分散到了Paragraph, SoftLine, Char中, 所以現在可以刪除這個檔案.
2.8.5 最終效果
2.9 行內文字底部對齊
截止到目前為止, 出現了一個小問題: 一行內不同大小的文字, 他們的縱向對齊方式, 是以頂部為基線的.
為了看得更清楚, 我們給每個字元加上輔助邊框和背景色, 修改src/core/CanvasTextEditorChar.ts
:
修改src/core/CanvasTextEditor.ts
, 再加上幾個漢字:
這樣, 可以更清晰地看出, 不同大小的文字是頂部對齊的:
為什麼會出現行內文字縱向頂部對齊呢? 因為我們之前為了方便, 將textBaseline
設定為了top
:
這樣設定之後, 包圍盒頂部座標 和 fillText(text, x, y)
中的y座標就相等了. 我們之前把它們統一記作top
.
現在, 我們不得不放棄之前的偷懶方式, 將兩者分別記錄:
- 將
fillText(text, x, y)
中的y
記作top
- 將包圍盒頂部座標記作
boundingBoxTop
修改src/core/CanvasTextEditorChar.ts
:
看一下效果:
2.10 再議textBaseLine
這次行內文字縱向對齊的問題解決了, 可是新的問題來了: 為什麼所有的文字整體上移了?
因為我們已經把textBaseLine
恢復成了預設值alphabetic
. 繪製文字的基線下移了, 且文字的座標(left, top)沒變, 所以相當於文字上移了.
為了解決這個問題, 我暫時想到了一種方法:
- 我們需要將每一行文字統一向下偏移一個長度
offsetY
- 每一行的
offsetY
, 取決於行內所有字元fontBoundingBoxAscent
的最大值 - 在渲染行內的每個字元時, 統一加上這個偏移值
接下來我們來實現, 修改src/core/CanvasTextEditorSoftLine.ts
:
效果:
文字上移的問題就解決了, 棒!
(未完待續)