:從 `\shp\pict` 內(nèi)嵌圖像到 Pandoc Image 節(jié)點的完整鏈路)
Pandoc RTF 讀取器圖片解析實戰(zhàn)從\shp\pict內(nèi)嵌圖像到 Pandoc Image 節(jié)點的完整鏈路【免費下載鏈接】pandocUniversal markup converter項目地址: https://gitcode.com/gh_mirrors/pa/pandoc本文以 pandoc 倉庫中的命令測試用例 test/command/10145.md 為切入點完整拆解 Pandoc 的 RTF 讀取器Text.Pandoc.Readers.RTF如何解析 RTF 文檔中通過 Shape 對象\shp\shpinst包裹、以\pict\jpegblip內(nèi)嵌的 JPEG 圖片并輸出為帶尺寸屬性的 PandocImage節(jié)點。讀完本文你將理解 RTF 圖片的十六進(jìn)制編碼方式、twips 尺寸單位到英寸的換算規(guī)則、圖片文件的 SHA-1 命名機(jī)制以及 Pandoc 命令測試Command Test的編寫與運(yùn)行方法。測試用例速覽一個內(nèi)嵌 JPEG 圖片的 RTF 文檔test/command/10145.md是 pandoc 的 golden test金標(biāo)準(zhǔn)測試之一它給定一段 RTF 輸入要求pandoc的 RTF 讀取器將其轉(zhuǎn)換為 Pandoc 的 native內(nèi)部 AST 文本表示格式并與預(yù)期的 native 輸出逐字符比對。% pandoc -f rtf -t native {\rtf1\ansi\deff0{\fonttbl{\f0 \fswiss Helvetica;}{\f1 \fmodern Courier;}} ... ^D [ Para [ Image ( , [] , [ ( width , 0.2777777777777778in ) , ( height , 0.3055555555555556in ) ] ) [ Str image ] ( 9ea9761885249909bcd8a610706eef51b54dc351.jpg , ) ] , Para [ Str moon ] ]這個用例雖然只有 24 行卻覆蓋了 RTF 圖片解析的幾個關(guān)鍵難點Shape 包裝結(jié)構(gòu)、圖片二進(jìn)制數(shù)據(jù)的十六進(jìn)制表示、圖片顯示尺寸的換算、以及圖片資源的持久化命名。下面逐層展開。RTF 輸入解剖Shape 與 Pict 的嵌套結(jié)構(gòu)RTF 中內(nèi)嵌圖片有兩種常見形式直接使用{\pict ...}圖片對象或使用文本框 / 形狀包裝的{\shp ...}結(jié)構(gòu)。本用例屬于后者其核心片段為{\pard \ql \f0 \sa0 \li0 \fi0 {\shp{\*\shpinst\shpwr2\shpwrk3\shpbypara\shpbyignore \shptop1320\shpbottom7745\shpbxcolumn\shpbxignore\shpleft0\shpright9638 {\sp{\sn shapeType}{\sv 75}} {\sp{\sn wzDescription}{\sv }} {\sp{\sn wzName}{\sv }} {\sp{\sn pib}{\sv {\pict\jpegblip\picw20\pich22\picwgoal400\pichgoal440 ffd8ffe000104a464946...ffd9}}}}} \par} {\pard \ql \f0 \sa0 \li0 \fi0 moon\par}各要素的作用如下RTF 控制字 / 組含義在本用例中的值{\shp ...}Shape 對象組包裹形狀及其屬性外層容器\shpinstShape 實例說明\*表示可選目的地形狀實例\shpwr2文字環(huán)繞方式環(huán)繞環(huán)繞形狀\shptop1320/\shpbottom7745形狀在頁面中的垂直位置twips1320 / 7745\shpleft0/\shpright9638形狀在頁面中的水平位置twips0 / 9638{\sp{\sn pib}{\sv ...}}形狀屬性對\sn為屬性名\sv為屬性值pib表示“圖片二進(jìn)制數(shù)據(jù)”圖片數(shù)據(jù)載體{\pict\jpegblip ...}圖片對象jpegblip聲明圖片類型為 JPEG內(nèi)嵌 JPEG\picw20/\pich22圖片原始像素寬高20 × 22 px\picwgoal400/\pichgoal440圖片顯示尺寸twips1 英寸 1440 twips400 × 440 twipsffd8ffe0...ffd9JPEG 文件的十六進(jìn)制字節(jié)流以ffd8起始、ffd9結(jié)尾圖片數(shù)據(jù)其中{\sp{\sn pib}{\sv {\pict...}}}是理解整個用例的關(guān)鍵\sn pib聲明該屬性是“圖片二進(jìn)制數(shù)據(jù)”\sv的值是一個\pict對象。Pandoc 讀取器只有在pib屬性下才真正提取圖片內(nèi)容。期望輸出解讀Pandoc Image 節(jié)點轉(zhuǎn)換得到的 native AST 是一個包含兩個塊Block的文檔[ Para [ Image ( -- 元素 id 為空 , [] -- 無 class , [ ( width , 0.2777777777777778in ) -- 寬 400/1440 英寸 , ( height , 0.3055555555555556in ) -- 高 440/1440 英寸 ] ) [ Str image ] -- 替代文本alt ( 9ea9761885249909bcd8a610706eef51b54dc351.jpg , ) -- src 與 title ] , Para [ Str moon ] -- 段落中的普通文本 ]值得注意的三個細(xì)節(jié)尺寸換算picwgoal400twips 除以 14401 英寸的 twips 數(shù)得到0.2777777777777778inpichgoal440得到0.3055555555555556in。這個換算關(guān)系與源碼中fromIntegral w / 1440的計算完全一致。alt 文本圖片的替代文本被固定為字符串image因此輸出為[Str image]。資源命名圖片被存儲為以 SHA-1 哈希命名、.jpg擴(kuò)展名的媒體文件。用例中的文件名9ea9761885249909bcd8a610706eef51b54dc351.jpg正是內(nèi)嵌 JPEG 字節(jié)流的 SHA-1 摘要。源碼實現(xiàn)原理RTF 讀取器的圖片處理鏈路入口與注冊RTF 讀取器在 src/Text/Pandoc/Readers/RTF.hs 中實現(xiàn)其公開接口為readRTF :: ReaderOptions - a - m Pandoc第 48-57 行。該讀取器在 src/Text/Pandoc/Readers.hs 中注冊為(rtf, TextReader readRTF)即通過pandoc -f rtf即可調(diào)用。讀取過程分為兩步詞法分析Tokenizer將 RTF 文本切分為 token 流token 類型包括控制字ControlWord如\pict、控制符號ControlSymbol如\{、十六進(jìn)制字節(jié)HexVals對應(yīng)\xx、未格式化文本UnformattedText和分組Grouped。語義處理processTok對 token 流逐個處理維護(hù)一個屬性棧Properties與文檔狀態(tài)RTFState最終產(chǎn)出Blocks。\shp與\sp組的分流在processTok中與形狀相關(guān)的 token 有專門分支{\shp ...}組第 481-482 行Grouped (Tok _ (ControlWord shp _) : toks)直接進(jìn)入組內(nèi)繼續(xù)處理屬性對{\sp{\sn 名字}{\sv 值}}第 483-490 行當(dāng)\sn的值為pib時對\sv組內(nèi)的 token即{\pict...}繼續(xù)處理其它屬性名如shapeType、wzDescription、wzName則被忽略遇到{\pict ...}組第 496-497 行時調(diào)用handlePict完成圖片數(shù)據(jù)收集。也就是說讀取器只在pib屬性中發(fā)現(xiàn)圖片形狀的位置、環(huán)繞等布局信息\shptop、\shpwr2等不會進(jìn)入 Pandoc AST——這符合 Pandoc 面向內(nèi)容而非版式的設(shè)計。handlePict收集圖片元數(shù)據(jù)與字節(jié)流handlePict第 1143-1180 行通過getPictData對\pict組內(nèi)的 token 做左折疊提取以下字段RTF 控制字存入字段說明emfblip/pngblip/jpegblippicType圖片類型枚舉分別對應(yīng) EMF / PNG / JPEGpicw/pichpicWidth/picHeight原始像素尺寸picwgoal/pichgoalpicWidthGoal/picHeightGoal目標(biāo)顯示尺寸twips\binN二進(jìn)制數(shù)據(jù)picBytes二進(jìn)制模式picBinary True連續(xù)十六進(jìn)制文本picData文本模式每兩個字符解碼為一個字節(jié)在本用例中JPEG 數(shù)據(jù)以連續(xù)十六進(jìn)制文本形式直接出現(xiàn)在\pict組內(nèi)如ffd8ffe000104a46...因此走UnformattedText分支累積到picData隨后通過T.chunksOf 2分組、hexToWord逐對解碼為字節(jié)串。解碼完成后handlePict根據(jù)picType決定 MIME 類型與擴(kuò)展名Just Emfblip - (Just image/x-emf, .emf) Just Pngblip - (Just image/png, .png) Just Jpegblip - (Just image/jpeg, .jpg)隨后調(diào)用insertMedia來自Text.Pandoc.Class將解碼后的字節(jié)流存入文檔媒體包文件名由 SHA-1 哈希生成let pictname show (hashWith SHA1 $ BL.toStrict bytes) ext insertMedia pictname (Just mt) bytes這正是期望輸出中9ea9761885249909bcd8a610706eef51b54dc351.jpg的來源對圖片字節(jié)串計算 SHA-1轉(zhuǎn)成十六進(jìn)制字符串后追加.jpg擴(kuò)展名。媒體入庫后gImage屬性被設(shè)置為該圖片同時addText image寫入固定 alt 文本。尺寸換算與 AST 生成圖片最終在addFormatting第 352-384 行中轉(zhuǎn)換為 PandocImage節(jié)點。當(dāng)文本帶有g(shù)Image屬性時構(gòu)造let attr (, [], (case picWidthGoal pict of Nothing - [] Just w - [(width, tshow (fromIntegral w / 1440 :: Double) in)]) (case picHeightGoal pict of Nothing - [] Just h - [(height, tshow (fromIntegral h / 1440 :: Double) in)])) in B.imageWith attr (picName pict) . B.text可以看到picwgoal/pichgoal除以 1440 后以英寸in為單位寫入width/height屬性picName即 SHA-1 文件名作為圖片srcalt 文本取自前面的[Str image]。整條鏈路與用例的期望輸出一一對應(yīng)。命令測試框架這種用例如何被運(yùn)行test/command/10145.md屬于 pandoc 的命令測試Command Test。測試運(yùn)行器位于 test/Tests/Command.hs其格式約定如下第 13-31 行代碼塊第一行以%開頭之后是要執(zhí)行的命令后續(xù)若干行為傳給命令的標(biāo)準(zhǔn)輸入stdin輸入以獨立一行^D結(jié)束之后的行是期望的標(biāo)準(zhǔn)輸出若期望 stderr則每行以2前綴若期望非零退出碼末行以加狀態(tài)碼。測試發(fā)現(xiàn)邏輯第 83 行會掃描test/command目錄下所有.md文件并逐一解析其中的命令塊實際執(zhí)行時execTest用test-pandoc --emulate模擬pandoc命令行將真實輸出與期望輸出做 golden 比對。因此10145.md既是回歸測試也是一份“RTF 圖片解析行為”的可執(zhí)行文檔。運(yùn)行該用例的方式需先構(gòu)建項目當(dāng)前倉庫使用 cabal / stack 管理構(gòu)建與測試命令詳見 INSTALL.md# 方式一運(yùn)行全部測試包含 test/command 下的用例 cabal test # 方式二只運(yùn)行命令行測試組 cabal test --test-options-p Command若某用例失敗Tests.Command會輸出期望與實際的 diff便于定位讀取器行為變化。手動驗證把測試用例變成可復(fù)現(xiàn)的實驗無需修改倉庫即可用已構(gòu)建的 pandoc 復(fù)現(xiàn)本用例# 將 10145.md 中 % 行之后的 RTF 內(nèi)容保存為 10145.rtf pandoc -f rtf -t native 10145.rtf預(yù)期輸出與test/command/10145.md中^D之后的內(nèi)容一致。若改用其它輸出格式例如-t html5圖片會以data:URI 或外部媒體文件形式輸出取決于是否使用--self-contained等選項而-t markdown則會看到image{width0.2777777777777778in height0.3055555555555556in}形式的圖片語法。此外MANUAL.txt 中記錄了 RTF 作為輸入/輸出格式的支持情況第 280、364 行以及 RTF 輸出模板變量fontsize第 3619-3622 行如12pt可供進(jìn)一步查閱 RTF 相關(guān)的格式選項。小結(jié)通過test/command/10145.md這一個用例可以完整掌握 Pandoc RTF 讀取器處理內(nèi)嵌圖片的機(jī)制結(jié)構(gòu)識別\shp→\sp{\sn pib}→\pict的嵌套路徑是讀取器定位圖片數(shù)據(jù)的入口數(shù)據(jù)解碼\pict內(nèi)的十六進(jìn)制文本按兩字符一組解碼為原始字節(jié)\binN二進(jìn)制模式同樣支持尺寸換算\picwgoal/\pichgoaltwips除以 1440 得到英寸寫入Image節(jié)點的width/height屬性資源命名圖片字節(jié)流的 SHA-1 摘要作為文件名擴(kuò)展名由jpegblip/pngblip/emfblip類型決定測試機(jī)制命令測試以% 命令 輸入 ^D 期望輸出的格式沉淀行為規(guī)范是理解 Pandoc 各讀取器細(xì)節(jié)的最佳學(xué)習(xí)材料。如果你需要調(diào)試或擴(kuò)展 RTF 圖片解析邏輯核心代碼集中在 src/Text/Pandoc/Readers/RTF.hs 的handlePict、addFormatting與processTok三處配合 test/command 下的命令用例即可快速驗證?!久赓M下載鏈接】pandocUniversal markup converter項目地址: https://gitcode.com/gh_mirrors/pa/pandoc創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考