はじめに

PDFファイルから透明テキストレイヤーを抽出する際、「テキストの順序が元のPDFと異なってしまう」という問題に直面しました。本記事では、この問題の原因と、JavaScriptとPythonそれぞれでの解決策について解説します。誤っている点もあるかもしれませんが、参考になりましたら幸いです。

PDFの透明テキストとは

PDFの透明テキストレイヤーは、PDFファイル内に埋め込まれた検索可能なテキスト情報です。OCR処理されたPDFや、デジタル生成されたPDFには、この透明テキストレイヤーが含まれており、以下のような機能を実現しています:

  • テキスト検索
  • コピー&ペースト
  • スクリーンリーダーによる読み上げ
  • 機械翻訳

問題:テキストの順序が乱れる理由

PDFの内部構造

PDFファイルは、テキストを「コンテンツストリーム」という形式で保存しています。このストリームには、テキストとその位置情報が含まれていますが、必ずしも読む順序で格納されているわけではありません。

[[[PD:::Fxxx===131000000,,,yyy===243000000,,,===""""""]]]

一般的な抽出方法の問題点

多くのPDF処理ライブラリは、以下のような手順でテキストを抽出します:

  1. コンテンツストリームからテキストと位置情報を取得
  2. 座標でソート (上から下、左から右)
  3. ソート結果を出力

この「座標でソート」する処理が、テキスト順序の乱れを引き起こす主な原因です。

具体的な問題例

  • 縦書きと横書きの混在 :日本語文書でよく見られる
  • 複数カラムレイアウト :新聞や雑誌形式
  • 図表の挿入 :本文の流れを分断する要素
  • ヘッダー・フッター :ページをまたぐ要素

解決策:言語別アプローチ

JavaScript (PDF.js) での解決策

PDF.jsは、Mozillaが開発したJavaScriptベースのPDFレンダリングライブラリです。

順序を保持する実装

a}sycc}rPnoo)eDcnnr};tFgsise;u.fetttttxywhrjuteue::iensnTtmorxdiceesrntiitgo使txxd:tthhritte{ee:tdoCCrimm:enooet..irnndetttieettTmrret使dxeee.aameTtnnxsnn.merttttssw.xa(rffihtc)==,oode;trrtiTatmmhgewe[[,hxax45ttit]]WtC,,iotpnhatOgerend.teg.rei(ttpTeaemgxset.)Cmoa{npt(eintte(m);=>{

ポイント

  • getTextContent()メソッドは、PDFの内部構造に忠実な順序でテキストを返す
  • 配列のインデックスが元の順序を表現
  • 座標による再ソートを行わない

Python (PyMuPDF) での解決策

PyMuPDF(fitz)は、MuPDFライブラリのPythonバインディングです。

順序を保持する実装

idmepforedfdtxoootcrcfr.ia=p#r#ieyctcaaflilztfgwseo_ie_nelstt_1t2otp#ft:tde#ezi:e:teaoee(x.dxxgrxxp)Ptoxrtrtettay_p,aa__bigMwew=wdtlf==euinp_ieo_Pt(aptcxcbriDhpgaettklf\adF_degxsoonwxofet=icr'_,r_i..=nk.tdpngsp.llfijeteaeta[tgiiofoxertetrg]eennritx(hn_iexteelntp)utp.t(_slip(dme(g_"itpinapfex)edtneanega_rt:tiyxne_egpa(_cpbt_t_eat"ttelitet_tete."o=nexeth(exg)cxtxe)dxtek"lt.tx:ot(t=."isstc""(=gn+t.s))d"ee=ra):ib0t.ipcl:(gsppto"ep(e"clta)n)k#i(n:dsn".("esgl,spei"atn[,n(e]s"_)["tt:],ee)xx:[tt]")),:"")

ポイント

  • get_text("text")は、PDFのコンテンツストリーム順序を保持
  • get_text("dict")で詳細な構造情報を取得可能
  • 座標ベースのソートを避ける

Python (pdfplumber) の問題点

pdfplumberは人気のあるPythonライブラリですが、デフォルトで座標ベースの処理を行います:

#iwmipptdohfrfptpoldrupfmdpp#tbflaeepugexrlmextubtmeir=brnae.cproptapd_geften.e.(pxepatxdg(tfe)r_sap:catt_ht)exats()pdf#:

実装比較表

特徴PDF.js (JavaScript)PyMuPDF (Python)pdfplumber (Python)
コンテンツストリーム順序保持
座標情報の取得
処理速度
メモリ使用量
日本語対応
ブラウザ対応

実践例:ハイブリッドアプローチ

順序保持と座標情報の両方を活用する実装例:

JavaScript実装

c}lac}a}g}g}soseesntyct}rtr}trshnoh)eTe)TePticniotxywh)teti}r;etDrsssre::ie;uxufexuFu.et.ixdirtrrttrTctxtgtiitgnBn(euBnetettei:tthhyMtryxoxrexnee:ttP[aunOttrtaxtaimm:ho.trrhE(IctIlt..iis.hnbiix)ttCtIetttisi...gsteWoenmrret.ttaayi.r{minmd.aametihb.ntasttsesnn.meoisx-aechextssw.xns(lxt=Mn=:rffiht(.a-aOtoet,oodeI)t..rIr[ttirrtiteybydt]a=enmmhge{x.;ee{;dxd[[,hmt-xrmaate45tsI;(stwCx]];tb);aao,,,e.(inmy{ptts)ae]gpn.<eats)g.o5eir){.ttge({em(tsaT.,emxabtp)C(o(=ni>ttee{nmt,()i;ndex)=>({

Python実装

classddddeeeePffffDFsedfdrg#rg#rT_exoooeeeeeeiltcrctttttxnfr.u_u_uti.a=p#tifcrtrtrEttcaetolnenenx_etfgxeroxxt_x_ietmsststsr(twt___biee_o_eas_izndilf(lbrblceit.uino)fytyftlthomcdcb._e_.ofe_p,teklftpdotr)mmexooeo(kre::senp=icrxsseixt(a=nktieygt=apgp.lf_tl=i_ddea0tgioiiflni[afgeenrto.aat]t_iexteentmleapn.t(ss}im(eb_m(ag_"ipe)tssxdossteedtnaleetarehntiynfml_dl)u_cpb.'''''_fixefmttelitoptbfsi)t:r,ee."oneraeboin:e(rxg)cxigxonzdmspatekltgetxtees-edt(t=.i_i'''''x,xlfe"(=gnin:::::[f_(d"eeta+')pdib0t.elpssss=b:aocl:(gm_appppbtcto"esigaaaa1oh)"clt.nennnnx):)k#i(ad_....':sn"pengggg]"espxueeee[,spe'mtttt1"an:,((((][,nd"""",]s(itbfs)["{teboix:],exonz[)mtxte':[_""""b]i,,,,b)no:d"["0xe"]")'x)))],,,,[0]#))[x0,y0,x1,y1]

ベストプラクティス

1. 用途に応じた選択

defcieehflloiiouffss#r#r#reeeueue__tststecueueuxar_r_rtsncncnreaaa"s"s"c=oepeht=royii=s=bo"g=i=rnfiti_un"i"dmlaloc"ellanot__y_nhtoobtoeruaedxdtsn(te_etu_rad_ss"n"eeeax_altcryracsashice"st):"i::on":

2. エラーハンドリング

a}syt}}nrcyci}c}rccrofo)eaoef{ncrnr;ttntus(oeseucsunt!ntttrhorctsuCunlntteoriIr(eiexlntDnie.[oxteetre]ntC.[m!err;Cow]simorsona;tsroantr=e;)rftenm(een(t.{'Ent'esTxt.Nxtetiotrxr=tC.taetoicamennetwsxtcxTatelteinurxtftdato.ec(ptuistpaent(iagxde'ogetm(ne.Cisc)gon.ifenfda{ttpi:iTeal'lengt)extee;dt.'r:Ci)('ot;i,nettmeeesmrn.rtl=o(e>r)n);g{;th===0){

3. パフォーマンス最適化

#defedtfdPxooooDtctrcFra.a=lsebfycc_tnaoiltfpadtreo_iar_clsltgtihpp#tb#pdeaze_d_aaeaa(r.sixtggxtgb)godeeetceaep=x=x_ht_eti==_=cpnlimsdthd(enixdpeN_fpnn=oaxot(d(r(icgtnepfdas[n[esexd_ont]p..tfpcgaragas_a)erageppt(tnetpah0_g__et),ieitnhd(ded,txsxx(ot]ttbt+a(eaar"xtlbtttc_a_e)hptix_acdtsghx"ie_,)zsse,ie=zn1bed0a,_)ti:ctdhox_t)sa:ilz_ep)a:ges)

トラブルシューティング

よくある問題と解決策

  1. 日本語の文字化け
#teUxTtF-=8page.get_text("text").encode('utf-8',errors='ignore').decode('utf-8')
  1. 縦書きテキストの処理
a}sycc}rPnoo)eDcnnr};tFgsise;u.fetttttxywhrjuteue::iensnTtmorxdiceesrntiitgo使txxd:tthhritte{ee:tdoCCrimm:enooet..irnndetttieettTmrret使dxeee.aameTtnnxsnn.merttttssw.xa(rffihtc)==,oode;trrtiTatmmhgewe[[,hxax45ttit]]WtC,,iotpnhatOgerend.teg.rei(ttpTeaemgxset.)Cmoa{npt(eintte(m);=>{

0 3. メモリ不足

a}sycc}rPnoo)eDcnnr};tFgsise;u.fetttttxywhrjuteue::iensnTtmorxdiceesrntiitgo使txxd:tthhritte{ee:tdoCCrimm:enooet..irnndetttieettTmrret使dxeee.aameTtnnxsnn.merttttssw.xa(rffihtc)==,oode;trrtiTatmmhgewe[[,hxax45ttit]]WtC,,iotpnhatOgerend.teg.rei(ttpTeaemgxset.)Cmoa{npt(eintte(m);=>{

1

まとめ

PDFの透明テキスト抽出において、順序保持は重要な課題です。主なポイントは:

  1. 問題の理解 :座標ベースのソートが順序を乱す主因
  2. 適切なライブラリ選択 :PDF.js(JavaScript)やPyMuPDF(Python)を使用
  3. 実装方法 :コンテンツストリームの順序を維持する
  4. ハイブリッドアプローチ :順序と座標情報の両方を活用

これらの知識を活用することで、より正確で信頼性の高いPDFテキスト抽出システムを構築できます。

参考資料