概要
PDFページ上の全テキストを抽出します。テキストはページ単位に以下の内容に編集します。
- 行単位の文字列
- 行単位の文字列の座標(計算で出したY座標のみ)
- ヘッダー(連続するページ上の最上部行。連続行有り)
- フッター(連続するページ上の最終行。連続行有り)
上記を配列で返します。全て計算で出しているので、期待しない結果が出る可能性が有ります。
詳細
PDFファイルからテキストと同時に取得できる情報は座標(X、Y)だけです。行(行番号)と言う情報は取得できません。英語ならば単語単位、日本語ならば1文字単位の座標(X、Y)で取得します。
当関数は、同じ行なのか、連続する1行のテキストなのかを座標を見て計算しています。行番号はページ上部からテキストの出現順に1,2,3行目としています。
ヘッダー、フッター以外の本文のみのテキスト検索にも使用できると思っています。
処理動作を変更する定数をいくつか備えていますが、最初はPDFファイルのパスだけを指定して、他はデフォルトで試して下さい。
座標はページ左下を基点とします。
抽出されるテキストの順番
プログラムでは画面上部から表示される順番でテキストの取得はできません。画面トップに表示さていたテキストが途中や最後の場合も有ります。同じ行内のテキストでも順番が前後する場合も有ります。これはPDFファイルの作成時の内容によって変わるみたいです。理由の詳細は不明です。
各テキストは画面表示上の座標(X、Y)を持っています。よって、テキストの順序が前後しても、画面表示では全く問題が出ません。
しかし、順番の前後するテキストをプログラムで扱うとなると問題が出てきます。この関数は座標を見て、画面表示の順(上から下へ、左から右へ)で各テキストを並び替えます。但し、並び替えると問題が出るケースもあります。表内のテキストや索引などのように横に行が複数存在する場合です。並び替えるとテキストのY座標が同じ場合は同じ行と判断してしまうからです。当関数では並び替えをしない指定も出来ます(非推奨!)。
同じ行のテキストと判断する条件
PDF上に以下のようなテキストが存在するとします。

これをプログラムで抽出すると、以下の青枠の単位で分割されて返されます。

「Acrobat 5.05」は「Acrobat」と「5.05」に分割されます。分割方法の指示や変更は出来ません。
当関数は同じ行のテキストと判断する条件を各テキスト横の中心座標の高さの差で行います。

各テキストの中心座標の Y 座標の差が、頁内のテキストの平均的な高さ(Y座標)の1/4の範囲に有れば、同じ行のテキストと判断します。1/4は経験則から出した独自の値です(変更可能:C_DEV 定数)。
ヘッダー、フッターと判断する条件
以下の条件を全て必要とします。
- 1行目から次ページと同じ位置に同じテキストが存在する時にヘッダーとします。最終行から次ページと同じ位置に同じテキストが存在する時にフッターとします。ヘッダーは下の行へ、フッターは上の行へ連続行の判断もします。
- 数字と記号以外のテキストが同じ。
変化するページ番号を無視する為の条件です。「年月日」の文字も無視します。テキストの横方向のX座標は見ません。 - 前後するページ上のテキストの中心座標Yの差がほぼ同じ。
テキストの中心座標Yの差は前ページのテキストの平均的な高さの1/4です(変更可能:C_DEV 定数)。 - 次ページの同じ位置に同じテキストが存在しなくても、ヘッダー行と判断した真下(直下)にある行もヘッダーと見なします。フッターも同様に、次ページの同じ位置に同じテキストが存在しなくても、フッターと判断した真上(直上)にある行もフッターとします。これは1ページ上にしか存在しないヘッダー、又はフッターも取り込む為の機能です。1行でもヘッダー、又はフッターが存在した時に機能します。
但し、この機能はデフォルトでは無効です。定数で有効に変更できます。 - ヘッダー、又はフッターと判断した行から離れた位置にある行は判断はしません。稀に本文のトップ行が同じ文字、同じ位置に有る場合があります。デフォルトではテキストの平均高さYの2.5倍以上離れていると無視します。フッターも同様です。
例:

「 XMPデータモデル」行は次ページ上の同じ位置に同じテキストが存在するので、ヘッダーと判断しました。
「プロパティ値」行は次ページの同じ位置にテキストが存在しなくてもヘッダーとして見ることが出来ます(4.の機能)。但し、デフォルでは、この機能は無効になっています。
「構造体」行はヘッダー行の「プロパティ値」から離れているので、ヘッダーの判断はしません(5.の機能)。
「4.」も「5.」もVBAソース内の定数で機能オン・オフに出来ます。
機能
- 指定したPDFの全テキストを抽出します。
- 抽出したテキストはY座標に従って並び替えます(推奨)。
並び替えをしない設定(bSortY = False)も出来ます。 - テキストのY座標をもとに1行のテキストに結合します。
結合する前にX座標の順にテキストを並び替えます(推奨)。
並び替えをしない設定(bSortX = False)も出来ます。 - 条件を満たした1行目をヘッダー、最終行をフッターとします。連続行も判断します。
形式
Public Function OutSquareRects( _
ByVal sInFilePathIn As String, _
ByRef sOutData() As PageLineZahyo) As Boolean
- 第1引数(sInFilePathIn):PDFのフルパスを指定します。
- 第2引数(sOutData):処理の結果です。
詳細は以下の「第2引数:sOutDataの詳細」を参照。
第2引数:sOutDataの詳細
処理結果が「sOutData() As PageLineZahyo」配列に返されます。
'処理の結果
Private Type PageLineZahyo
iLineCount As Long '行数:1~
sLineData(C_MAXLINE) As String '行のテキスト
iLineDataY(C_MAXLINE) As Long '行の中央Y座標
iHeaderCount As Long 'ヘッダー件数:1~
iFooterCount As Long 'フッター件数:1~
bHeaderFlag(C_MAXLINE) As Boolean 'Trueはヘッダー行
bFooterFlag(C_MAXLINE) As Boolean 'Trueはフッター行
'以下は作業用、デバッグ用
bHeaderST(C_MAXLINE) As Boolean 'Trueはヘッダー行・真下
bFooterST(C_MAXLINE) As Boolean 'Trueはフッター行・真上
iAveTextY As Long 'テキストの平均的な高さY
iTextCount As Long '座標を持ったテキスト数
End Type '注:文字数の合計では無い
- iCountLine:ページ内の行数。1は1行。-1、0はゼロ行。
- sLineData( ):ページ内の行データ。配列で返します。
- iLineDataY( ):行データの中心座標のY値。
- iHeaderCount:ヘッダーの数。0は無し。1~
- iFooterCount:フッターの数。0は無し。1~
- bHeaderFlag( ):ヘッダーの有無。sLineDataと同じ位置がTrueならヘッダー。見方は以下を参照。
- bFooterFlag( ):フッターの有無。sLineDataと同じ位置がTrueならフッター。見方は以下を参照。
上記以外は作業用、デバッグ用の変数です。

フッターを見る時は iCountLine の値で配列の最後が判断できます。
戻り値
- True : 正常
- False : エラー。第一引数のファイルがPDFで無い等。
動作検証した環境
- Windows 10 64bit Pro
- Adobe Acrobat XI Pro (バージョン 11.0.23)
- MS Office - Excel 2007 - VBA
関数のソース - VBA
参照設定が2つ必要です。
Download:
/p1164_Get-HeaderFooter.zip ( 50.2 KB )
001 Option Explicit
002
003 '**************************************************
004 '
005 ' Create : 2023/03/07
006 ' Update : 2026/03/22
007 '
008 ' ★の値はテストで出した結果。
009 '**************************************************
010
011 '全体で使用
012 Private Const C_MAXLINE As Long = 200 'ページ中の最大行数
013 Private Const C_DEV As Long = 4 '差計算時の分母
014
015 'Sort_TextData で使用
016 Private Const C1_Y_FIX As Boolean = True 'Y座標の修正
017 Private Const C1_SA As Long = 3 '★差の範囲 Y座標
018 Private Const C1_SORTX As Boolean = True 'X軸ソートの実行
019 Private Const C1_SORTY As Boolean = True 'Y軸ソートの実行
020
021 'Get_Header_Footer で使用
022 '行間が長い行はチェック対象から外す。
023 'True:外す False:外さない
024 Private Const C2_LONG_HD As Boolean = False 'ヘッダー
025 Private Const C2_LONG_FT As Boolean = False 'フッター
026 Private Const C2_LONG_BAI = 2.5 '★行(高さ)の倍率
027 '真下、真上の行はヘッダー、フッターに取り込む
028 'True:取り込む False:取り込まない
029 Private Const C2_SHORT_HD As Boolean = True 'ヘッダーの真下
030 Private Const C2_SHORT_FT As Boolean = True 'フッターの真上
031 Private Const C2_SHORT_BAI = 1.3 '★行(高さ)の倍率
032
033 '処理の結果
034 Private Type PageLineZahyo
035 iLineCount As Long '行数:1~
036 sLineData(C_MAXLINE) As String '行のテキスト
037 iLineDataY(C_MAXLINE) As Long '行の中央Y座標
038 iHeaderCount As Long 'ヘッダー件数:1~
039 iFooterCount As Long 'フッター件数:1~
040 bHeaderFlag(C_MAXLINE) As Boolean 'Trueはヘッダー行
041 bFooterFlag(C_MAXLINE) As Boolean 'Trueはフッター行
042
043 '以下は作業用、デバッグ用
044 bHeaderST(C_MAXLINE) As Boolean 'Trueは真下のヘッダー行
045 bFooterST(C_MAXLINE) As Boolean 'Trueは真上のフッター行
046 iAveTextY As Long 'テキストの平均的な高さY
047 iTextCount As Long '座標を持ったテキスト数
048 End Type '注:文字数の合計では無い
049
050 'PDFファイル内のテキストとその座標(一時作業用)
051 Private Type TextZahyo
052 sText As String 'テキスト(編集済み)
053 sTextOrg As String 'テキスト(オリジナル)
054 '※Trime前の状態
055 sTop As String '上部 座標
056 sBottom As String '下部 座標
057 sLeft As String '左 座標
058 sRight As String '右 座標
059 iLineNo As Long '行番号
060 iCenterX As Long '中心座標 X
061 iCenterY As Long '中心座標 Y
062 End Type
063
064 'Acrobat JavaScriptから取得のQuads座標
065 Private Enum JsZahyo
066 iTop = 1
067 iBottom = 5
068 iLeft = 0
069 iRight = 2
070 End Enum
071 '※ 0[Left] 1[Top] 2[Right] 3[Top]
072 '※ 4[Left] 5[Bottom] 6[Right] 7[Bottom]
073
074 Private msMsg As String 'メッセージ
075 ' VBA RunTime Error 対応用
076 Private miErrorNo As Long 'Err.Number の内容
077 Private msErrorMsg As String 'Err.Description の内容
078
079 '**************************************************
080 '
081 ' 関数を呼び出すテスト用
082 '
083 ' Create : 2023/03/07
084 '
085 '**************************************************
086 Sub Main_Test()
087
088 '▼メモリ上のAcrobatプロセスを確認する
089 If iCheckAcrobat() > 0 Then
090 msMsg = "Acrobatが動いています。" _
091 & vbCrLf & "処理を中断します。" & vbCrLf _
092 & vbCrLf & "この後でAcrobatの終了を試みます。"
093 MsgBox msMsg, vbCritical, "実行の中断"
094 'Acrobat OLEの終了を試みる
095 Call EndAcrobat
096 Exit Sub
097 End If
098
099 Dim bRet As Boolean '関数の戻り値
100 Dim sInFilePathIn As String 'PDFファイル
101 Dim sOutData() As PageLineZahyo 'テキストと座標
102
103 'PDF内の全テキストと座標を取得
104 sInFilePathIn = ThisWorkbook.Path & "\Test22.pdf"
105 bRet = OutSquareRects(sInFilePathIn, sOutData)
106 If bRet = False Then Exit Sub
107
108 '=====================================
109 '結果をテキスト出力し、メモ帳も起動する
110 Call WriteLog(sInFilePathIn, sOutData)
111 End Sub
112
113 '**************************************************
114 '
115 ' PDF内の全テキストを抽出し、行データを作成する。
116 ' ヘッダー、フッターの情報も作成する。
117 '
118 ' Create : 2023/02/28
119 ' Update : 2026/03/22
120 '
121 ' 引数1 : sInFilePathIn As String (IN)
122 ' 入力するPDFファイルのフルパス
123 '
124 ' 引数2 : sOutData() As PageLineZahyo (OUT)
125 ' PDFファイル内の
126 ' ・行とそのY座標
127 ' ・ヘッダー、フッター
128 '
129 ' 戻り値 : True 正常
130 ' False エラー、PDFが無い、等
131 '
132 ' 参照設定:
133 ' Acrobat (Adobe Acrobat **.* Type Library)
134 ' AFormAut 1.0 Type Library
135 '
136 '**************************************************
137 Public Function OutSquareRects( _
138 ByVal sInFilePathIn As String, _
139 ByRef sOutData() As PageLineZahyo) As Boolean
140
141 'On Error GoTo Err_OutSquareRects:
142 OutSquareRects = True
143 Dim start As Double: start = Timer
144
145 Dim i1 As Long
146 Dim i2 As Long
147 Dim iPageNo As Long
148 Dim iPageEnd As Long
149 Dim bRet As Boolean
150
151 Dim sAJS As String
152 Dim sReturn As String
153 Dim sJsText As String
154 Dim sJsTextOrg As String
155 Dim sJsWk2() As String
156 Dim sJsWk3() As String
157 Dim sJsQuads() As String
158
159 Dim sTextDT() As TextZahyo '作業用
160
161 'テキストの座標を取得するAcrobat JavScript
162 Const sAcrobatJavaScript = _
163 "var sOut='';" & _
164 "var numWords = this.getPageNumWords(@P);" & _
165 "for ( var j = 0; j < numWords; j++) {" & _
166 " nthWord = this.getPageNthWord(@P,j,false);" & _
167 " aQuads = this.getPageNthWordQuads(@P,j);" & _
168 " sOut=sOut + j + '\b' + nthWord + " & _
169 "'\b' + aQuads + '\t';" & _
170 "}" & _
171 "event.value=sOut;"
172 '※下記の特殊文字はデータの区切りに使用
173 ' \b:バックスペース
174 ' \t:タブ
175
176 If Dir$(sInFilePathIn, vbNormal) = "" Then
177 MsgBox sInFilePathIn & vbCrLf & _
178 "ファイルが存在しない。", _
179 vbOKOnly + vbCritical, "実行エラー"
180 OutSquareRects = False
181 Exit Function
182 End If
183
184 'Acrobatオブジェクトの定義&作成
185 Dim objAcroApp As New Acrobat.AcroApp
186 Dim objAcroAVDoc As New Acrobat.AcroAVDoc
187 Dim objAcroPDDoc As New Acrobat.AcroPDDoc
188 Dim objAFormApp As New AFORMAUTLib.AFormApp
189 Dim objAFormFields As AFORMAUTLib.Fields
190
191 '前回のエラー終了時の事前対応
192 objAcroApp.CloseAllDocs
193 objAcroApp.Hide '稀にデスクトップに表示されるので隠す
194
195 'PDFファイルを開く
196 If objAcroAVDoc.Open(sInFilePathIn, "") = False Then
197 MsgBox "Open出来ません。" & vbCrLf & sInFilePathIn, _
198 vbOKOnly + vbCritical, "実行エラー"
199 OutSquareRects = False
200 GoTo Skip_OutSquareRects_END:
201 End If
202
203 Application.Cursor = xlWait 'マウスポインターを砂時計
204 Set objAcroPDDoc = objAcroAVDoc.GetPDDoc
205 iPageEnd = objAcroPDDoc.GetNumPages - 1
206 ' Set objAFormApp = CreateObject("AFormAut.App")
207 Set objAFormFields = objAFormApp.Fields
208 Set objAcroPDDoc = objAcroAVDoc.GetPDDoc
209
210 'ページ単位のテキストデータ
211 ReDim sOutData(iPageEnd) As PageLineZahyo
212
213 For iPageNo = 0 To iPageEnd
214
215 DoEvents 'CPUを一時的に返す
216 '頁単位で全文字列+座標を抽出
217
218 'Acrobat JavaScriptの編集
219 sAJS = sAcrobatJavaScript
220 sAJS = Replace(sAJS, "@P", iPageNo)
221 'Acrobat JavaScript の実行
222 sReturn = objAFormFields.ExecuteThisJavascript(sAJS)
223 sJsWk2 = Split(sReturn, vbTab)
224
225 Debug.Print "Page=" & iPageNo + 1 & _
226 " Start-Time=" & Time & " Count=" & _
227 UBound(sJsWk2) + 1
228
229 '結果の編集
230 i2 = -1
231 If UBound(sJsWk2) >= 0 Then
232 ReDim sTextDT(UBound(sJsWk2)) As TextZahyo
233 End If
234
235 For i1 = 0 To UBound(sJsWk2)
236 If sJsWk2(i1) = "" Then Exit For
237
238 sJsWk3 = Split(sJsWk2(i1), vbBack)
239 sJsQuads = Split(sJsWk3(2), ",")
240 sJsText = sJsWk3(1)
241 sJsText = Replace(sJsText, vbCr, "")
242 sJsText = Replace(sJsText, vbLf, "")
243 sJsTextOrg = sJsText
244 sJsText = Trim$(sJsText)
245 If sJsText <> "" Then
246 i2 = i2 + 1
247 With sTextDT(i2)
248 .sText = sJsText
249 .sTextOrg = sJsTextOrg
250 .sTop = sJsQuads(JsZahyo.iTop)
251 .sBottom = sJsQuads(JsZahyo.iBottom)
252 .sLeft = sJsQuads(JsZahyo.iLeft)
253 .sRight = sJsQuads(JsZahyo.iRight)
254 '初期化
255 .iLineNo = -1
256 .iCenterX = -1
257 .iCenterY = -1
258 End With
259 End If
260 Next i1
261
262 sOutData(iPageNo).iTextCount = i2
263 If i2 >= 0 Then
264 ReDim Preserve sTextDT(i2) As TextZahyo
265
266 '▼全テキストの[平均の高さ]と[中心座標]を求める
267 bRet = EditYobiData(iPageNo, sOutData(), sTextDT)
268 If bRet = False Then
269 OutSquareRects = False
270 Exit For
271 End If
272
273 '▼テキストの中心座標を基に全テキストのソートを行う
274 bRet = Sort_TextData(iPageNo, sOutData(), sTextDT)
275 If bRet = False Then
276 OutSquareRects = False
277 Exit For
278 End If
279
280 '▼テキストデータから行データを作成
281 bRet = MargTextLine(iPageNo, sOutData(), sTextDT)
282 If bRet = False Then
283 OutSquareRects = False
284 Exit For
285 End If
286 End If
287
288 Next iPageNo
289
290 '▼ヘッダー、フッターを抽出
291 bRet = Get_Header_Footer(sOutData)
292 ' If bRet = False Then Exit Sub
293
294 Application.Cursor = xlDefault 'マウスポインターを戻す
295 'PDFファイルを閉じる
296 If objAcroAVDoc.Close(False) = False Then
297 MsgBox "AVDocオブジェクトはClose出来ませんでした", _
298 vbOKOnly + vbCritical, "実行エラー"
299 OutSquareRects = False
300 End If
301
302 Skip_OutSquareRects_END:
303
304 On Error Resume Next 'これ以降は強制実行
305
306 Application.Cursor = xlDefault 'マウスポインターを戻す
307 '変更しないで閉じます。
308 bRet = objAcroAVDoc.Close(False)
309 'Acrobatアプリケーションの終了
310 objAcroApp.Hide
311 objAcroApp.Exit
312 'オブジェクトの強制開放
313 Set objAFormFields = Nothing
314 Set objAFormApp = Nothing
315 Set objAcroPDDoc = Nothing
316 Set objAcroAVDoc = Nothing
317 Set objAcroApp = Nothing
318
319 Debug.Print "Total Time = " & Timer - start & _
320 " PageCnt = " & iPageEnd + 1
321
322 Exit Function
323
324 Err_OutSquareRects:
325 MsgBox "処理は以下の理由で中断しました。" & vbCrLf & _
326 vbCrLf & Err.Number & vbCrLf & Err.Description, _
327 vbOKOnly + vbCritical, "OutSquareRects:実行エラー"
328 OutSquareRects = False
329 GoTo Skip_OutSquareRects_END:
330 End Function
331
332 '**************************************************
333 '
334 ' データの中間加工
335 '
336 ' ・全テキストの平均・高さを求める
337 ' ・全テキストの中心座標を求める
338 '
339 ' Create : 2023/03/07
340 '
341 ' ※ココで求める値は整数値で十分
342 '
343 '**************************************************
344 Private Function EditYobiData( _
345 ByVal iPageNo As Long, _
346 ByRef sOutData() As PageLineZahyo, _
347 ByRef sTextDT() As TextZahyo) As Boolean
348
349 EditYobiData = True
350
351 Dim i1 As Long
352 Dim iAll As Long 'テキストの高さの集計
353 Dim iHi As Long '計算用作業
354
355 iAll = 0
356 For i1 = 0 To UBound(sTextDT)
357 With sTextDT(i1)
358 iHi = Round(Val(.sTop) - Val(.sBottom))
359 'テキストの高さの集計
360 iAll = iAll + iHi
361 'テキストの中心座標
362 .iCenterY = Round(Val(.sTop) - (iHi / 2))
363 iHi = Val(.sRight) - Val(.sLeft)
364 .iCenterX = Round(Val(.sRight) - (iHi / 2))
365 '初期化
366 .iLineNo = -1
367 End With
368 Next i1
369
370 With sOutData(iPageNo)
371 If .iTextCount = -1 Then
372 .iAveTextY = -1
373 Else
374 'ページ単位のテキストの平均的な高さ
375 .iAveTextY = Round(iAll / (.iTextCount + 1))
376 End If
377 End With
378
379 End Function
380
381 '**************************************************
382 '
383 ' テキストの中心座標を基に全テキストをソートする
384 '
385 ' Create : 2023/03/08
386 ' Update : 2023/03/12
387 '
388 ' 注:(C1_Y_FIX=True)で行間(座標Y)が小さい場合(差:3)は
389 ' 先頭行と同じ座標Yに強制修正する。同じ行内でも
390 ' 高さが少し異なるテキストを同じ行とする為の処理です。
391 ' このようなテキストは非常に多い。
392 '**************************************************
393 Private Function Sort_TextData( _
394 ByVal iPageNo As Long, _
395 ByRef sOutData() As PageLineZahyo, _
396 ByRef sTextDT() As TextZahyo) As Boolean
397
398 Sort_TextData = True
399
400 Dim iSa As Long '計算の差
401 Dim i1 As Long
402 Dim i2 As Long
403 Dim iEndInx As Long
404 Dim w_sText As String 'テキスト
405 Dim w_sTextOrg As String 'テキスト(オリジナル)
406 Dim w_sTop As String '上部 座標
407 Dim w_sBottom As String '下部 座標
408 Dim w_sLeft As String '左 座標
409 Dim w_sRight As String '右 座標
410 Dim w_iLineNo As Long '行番号
411 Dim w_iCenterX As Long '中心座標 X
412 Dim w_iCenterY As Long '中心座標 Y
413 Dim iLineSeqNo As Long '行番号
414 Dim iHeightAve As Long 'テキストの平均的な高さ
415
416 iEndInx = sOutData(iPageNo).iTextCount
417
418 '▼Y軸座標でソート
419 If C1_SORTY Then
420 For i1 = 0 To iEndInx - 1
421 For i2 = i1 + 1 To iEndInx
422 If sTextDT(i1).iCenterY < sTextDT(i2).iCenterY Then
423 w_sText = sTextDT(i1).sText
424 w_sTextOrg = sTextDT(i1).sTextOrg
425 w_sTop = sTextDT(i1).sTop
426 w_sBottom = sTextDT(i1).sBottom
427 w_sLeft = sTextDT(i1).sLeft
428 w_sRight = sTextDT(i1).sRight
429 w_iCenterX = sTextDT(i1).iCenterX
430 w_iCenterY = sTextDT(i1).iCenterY
431
432 sTextDT(i1).sText = sTextDT(i2).sText
433 sTextDT(i1).sTextOrg = sTextDT(i2).sTextOrg
434 sTextDT(i1).sTop = sTextDT(i2).sTop
435 sTextDT(i1).sBottom = sTextDT(i2).sBottom
436 sTextDT(i1).sLeft = sTextDT(i2).sLeft
437 sTextDT(i1).sRight = sTextDT(i2).sRight
438 sTextDT(i1).iCenterX = sTextDT(i2).iCenterX
439 sTextDT(i1).iCenterY = sTextDT(i2).iCenterY
440
441 sTextDT(i2).sText = w_sText
442 sTextDT(i2).sTextOrg = w_sTextOrg
443 sTextDT(i2).sTop = w_sTop
444 sTextDT(i2).sBottom = w_sBottom
445 sTextDT(i2).sLeft = w_sLeft
446 sTextDT(i2).sRight = w_sRight
447 sTextDT(i2).iCenterX = w_iCenterX
448 sTextDT(i2).iCenterY = w_iCenterY
449 End If
450 Next i2
451 Next i1
452 '微妙な座標Y誤差の修正
453 If C1_Y_FIX Then
454 For i1 = 0 To iEndInx - 1
455 iSa = Abs(sTextDT(i1).iCenterY - _
456 sTextDT(i1 + 1).iCenterY)
457 If (0 < iSa) And (iSa <= C1_SA) Then
458 sTextDT(i1 + 1).iCenterY = _
459 sTextDT(i1).iCenterY
460 End If
461 Next i1
462 End If 'End IF (C1_Y_FIX)
463 End If 'End If (bSortY)
464
465 '▼行番号を追加します。Y座標で判断する。
466 If sOutData(iPageNo).iTextCount = -1 Then
467 sOutData(iPageNo).iLineCount = -1
468 Else
469 iLineSeqNo = 1
470 iHeightAve = Round(sOutData(iPageNo).iAveTextY / C_DEV)
471 '※下記にしたら影響するのは本文のみ。
472 ' iHeightAve = 0
473 For i1 = 0 To iEndInx - 1
474 sTextDT(i1).iLineNo = iLineSeqNo
475 For i2 = i1 + 1 To iEndInx
476 If Abs((sTextDT(i1).iCenterY - _
477 sTextDT(i2).iCenterY)) <= iHeightAve Then
478 '行番号を設定
479 sTextDT(i2).iLineNo = iLineSeqNo
480 Else
481 i1 = i2 - 1
482 iLineSeqNo = iLineSeqNo + 1
483 Exit For
484 End If
485 Next i2
486 Next i1
487 If sTextDT(iEndInx).iLineNo = -1 Then
488 sTextDT(iEndInx).iLineNo = iLineSeqNo
489 End If
490 sOutData(iPageNo).iLineCount = iLineSeqNo
491 End If
492
493 '▼行番号単位でX軸ソート
494 If C1_SORTX Then
495 If sOutData(iPageNo).iTextCount <> -1 Then
496 iLineSeqNo = -1
497 For i1 = 0 To iEndInx - 1
498 For i2 = i1 + 1 To iEndInx
499 If sTextDT(i1).iLineNo <> sTextDT(i2).iLineNo _
500 Then Exit For
501 If sTextDT(i1).iCenterX > sTextDT(i2).iCenterX Then
502 w_sText = sTextDT(i1).sText
503 w_sTextOrg = sTextDT(i1).sTextOrg
504 w_sTop = sTextDT(i1).sTop
505 w_sBottom = sTextDT(i1).sBottom
506 w_sLeft = sTextDT(i1).sLeft
507 w_sRight = sTextDT(i1).sRight
508 w_iLineNo = sTextDT(i1).iLineNo
509 w_iCenterX = sTextDT(i1).iCenterX
510 w_iCenterY = sTextDT(i1).iCenterY
511
512 sTextDT(i1).sText = sTextDT(i2).sText
513 sTextDT(i1).sTextOrg = sTextDT(i2).sTextOrg
514 sTextDT(i1).sTop = sTextDT(i2).sTop
515 sTextDT(i1).sBottom = sTextDT(i2).sBottom
516 sTextDT(i1).sLeft = sTextDT(i2).sLeft
517 sTextDT(i1).sRight = sTextDT(i2).sRight
518 sTextDT(i1).iLineNo = sTextDT(i2).iLineNo
519 sTextDT(i1).iCenterX = sTextDT(i2).iCenterX
520 sTextDT(i1).iCenterY = sTextDT(i2).iCenterY
521
522 sTextDT(i2).sText = w_sText
523 sTextDT(i2).sTextOrg = w_sTextOrg
524 sTextDT(i2).sTop = w_sTop
525 sTextDT(i2).sBottom = w_sBottom
526 sTextDT(i2).sLeft = w_sLeft
527 sTextDT(i2).sRight = w_sRight
528 sTextDT(i2).iLineNo = w_iLineNo
529 sTextDT(i2).iCenterX = w_iCenterX
530 sTextDT(i2).iCenterY = w_iCenterY
531 End If
532 Next i2
533 Next i1
534 End If
535 End If 'End If (bSortX)
536
537 End Function
538
539 '**************************************************
540 '
541 ' 行データを作成
542 '
543 ' Create : 2023/03/07
544 '
545 '**************************************************
546 Private Function MargTextLine( _
547 ByVal iPageNo As Long, _
548 ByRef sOutData() As PageLineZahyo, _
549 ByRef sTextDT() As TextZahyo) As Boolean
550 MargTextLine = True
551
552 Dim i1 As Long
553 Dim i2 As Long
554
555 '初期化
556 For i1 = 0 To UBound(sOutData(iPageNo).sLineData)
557 sOutData(iPageNo).sLineData(i1) = ""
558 sOutData(iPageNo).iLineDataY(i1) = -1
559 Next i1
560
561 '行番号単位に行テキストを作成
562 For i1 = 0 To UBound(sTextDT)
563 i2 = sTextDT(i1).iLineNo - 1
564 With sOutData(iPageNo)
565 .sLineData(i2) = _
566 .sLineData(i2) & sTextDT(i1).sTextOrg
567 .iLineDataY(i2) = sTextDT(i1).iCenterY
568 End With
569 Next i1
570
571 End Function
572
573 '**************************************************
574 '
575 ' ヘッダー、フッターの判断
576 '
577 ' Create : 2023/03/07
578 ' Update : 2023/03/12
579 '
580 ' ヘッダー、フッターの抽出条件:
581 ' 文字列+連続頁+前後ページでの高さ(Y)が同じ
582 '
583 ' C2_LONG_HD,C2_LONG_FTをTrueにすると行間が長い行は
584 ' 比較対象から外す。(推奨)
585 '
586 '**************************************************
587 Private Function Get_Header_Footer( _
588 ByRef sOutData() As PageLineZahyo) As Boolean
589
590 Get_Header_Footer = True
591
592 Dim iPageNo As Long 'ページ番号
593 Dim iNextPg As Long '次のページ番号
594 Dim i1 As Long
595 Dim i2 As Long
596 Dim iHeaderEnd As Long 'ヘッダー最終処理の位置
597 Dim iFooterEnd As Long 'フッター最終処理の位置
598 Dim iSa As Long '差
599
600 '初期化
601 For iPageNo = 0 To UBound(sOutData)
602 With sOutData(iPageNo)
603 For i1 = 0 To C_MAXLINE
604 .bHeaderFlag(i1) = False
605 .bFooterFlag(i1) = False
606 .bHeaderST(i1) = False 'デバッグ用
607 .bFooterST(i1) = False 'デバッグ用
608 Next i1
609 End With
610 Next iPageNo
611
612 '■ヘッダー、フッター
613 For iPageNo = 0 To UBound(sOutData)
614 If sOutData(iPageNo).iLineCount < 0 Then _
615 GoTo Next_Get_Header_Footer:
616
617 iNextPg = iPageNo + 1
618
619 '▼ヘッダーの編集
620 iHeaderEnd = -1
621 '当ページと次ページとの比較
622 If iPageNo < UBound(sOutData) Then
623 iSa = Round(sOutData(iPageNo).iAveTextY / C_DEV)
624 For i1 = 0 To sOutData(iPageNo).iLineCount - 1
625 If (DeleteText(sOutData(iPageNo).sLineData(i1)) = _
626 DeleteText(sOutData(iNextPg).sLineData(i1))) _
627 And Abs(sOutData(iPageNo).iLineDataY(i1) - _
628 sOutData(iNextPg).iLineDataY(i1)) <= iSa _
629 Then
630 If C2_LONG_HD And (i1 > 0) Then
631 '1行上との行間が大きい時はヘッダーにしない。
632 If Abs(sOutData(iPageNo).iLineDataY(i1 - 1) - _
633 sOutData(iPageNo).iLineDataY(i1)) > _
634 (sOutData(iPageNo).iAveTextY * _
635 C2_LONG_BAI) Then
636 Exit For
637 End If
638 End If
639 sOutData(iPageNo).bHeaderFlag(i1) = True
640 sOutData(iNextPg).bHeaderFlag(i1) = True
641 iHeaderEnd = i1
642 Else
643 Exit For
644 End If
645 Next i1
646 End If
647 For i1 = sOutData(iPageNo).iLineCount - 1 To 0 Step -1
648 If sOutData(iPageNo).bHeaderFlag(i1) Then
649 iHeaderEnd = i1
650 Exit For
651 End If
652 Next i1
653
654 'ヘッダーの真下の近い行はヘッダーとする
655 If C2_SHORT_HD And (iHeaderEnd <> -1) Then
656 '真下の行はヘッダーとして取り込む
657 With sOutData(iPageNo)
658 iSa = .iAveTextY * C2_SHORT_BAI
659 For i2 = (iHeaderEnd + 1) To (.iLineCount - 1)
660 If Abs(.iLineDataY(i2 - 1) - _
661 .iLineDataY(i2)) <= iSa Then
662 .bHeaderFlag(i2) = True
663 .bHeaderST(i2) = True
664 Else
665 Exit For
666 End If
667 Next i2
668 End With
669 End If
670
671 '▼フッターの編集
672 iFooterEnd = -1
673 If iPageNo < UBound(sOutData) Then
674 i2 = sOutData(iNextPg).iLineCount - 1
675 '当ページと次ページとの比較
676 If (i2 >= 0) And (iPageNo < UBound(sOutData)) Then
677 iSa = Round(sOutData(iPageNo).iAveTextY / C_DEV)
678 For i1 = sOutData(iPageNo).iLineCount - 1 To 0 Step -1
679 If i1 <= iHeaderEnd Then Exit For 'ヘッダーに達した
680
681 If (DeleteText(sOutData(iPageNo).sLineData(i1)) = _
682 DeleteText(sOutData(iNextPg).sLineData(i2))) _
683 And Abs(sOutData(iPageNo).iLineDataY(i1) - _
684 sOutData(iNextPg).iLineDataY(i2)) <= iSa _
685 Then
686 sOutData(iPageNo).bFooterFlag(i1) = True
687 sOutData(iNextPg).bFooterFlag(i2) = True
688 iFooterEnd = i1
689 If C2_LONG_FT And (i1 > 0) Then
690 '1行上との行間が大きい時は「次は」比較しない。
691 If Abs(sOutData(iPageNo).iLineDataY(i1 - 1) - _
692 sOutData(iPageNo).iLineDataY(i1)) > _
693 (sOutData(iPageNo).iAveTextY * C2_LONG_BAI) Then
694 Exit For
695 End If
696 End If
697 End If
698 Next i1
699 End If
700 End If
701 For i1 = 0 To sOutData(iPageNo).iLineCount - 1
702 If sOutData(iPageNo).bFooterFlag(i1) Then
703 iFooterEnd = i1
704 Exit For
705 End If
706 Next i1
707 'フッターの真上の近い行はフッターとして取り込む
708 If C2_SHORT_FT And (iFooterEnd <> -1) Then
709 With sOutData(iPageNo)
710 iSa = .iAveTextY * C2_SHORT_BAI
711 For i1 = iFooterEnd To 1 Step -1
712 If Abs(.iLineDataY(i1) - _
713 .iLineDataY(i1 - 1)) <= iSa Then
714 .bFooterFlag(i1 - 1) = True
715 .bFooterST(i1 - 1) = True
716 Else
717 Exit For
718 End If
719 Next i1
720 End With
721 End If
722
723 Next_Get_Header_Footer:
724 Next iPageNo
725
726 '■ヘッダー、フッターの数を数える
727 For iPageNo = 0 To UBound(sOutData)
728 With sOutData(iPageNo)
729 .iHeaderCount = 0
730 For i1 = 0 To C_MAXLINE
731 If .bHeaderFlag(i1) Then
732 .iHeaderCount = .iHeaderCount + 1
733 End If
734 Next i1
735 .iFooterCount = 0
736 For i1 = 0 To C_MAXLINE
737 If .bFooterFlag(i1) Then
738 .iFooterCount = .iFooterCount + 1
739 End If
740 Next i1
741 End With
742 Next iPageNo
743
744 End Function
745
746 '**************************************************
747 '
748 ' ヘッダーとフッターからページ番号と日付に当たる部分を
749 ' 削除する。比較の対象から外すためです。
750 '
751 ' Create : 2023/03/07
752 ' Update : 2023/03/08
753 '
754 ' ※[年月日]を入れれば日付を外して比較が出来る
755 '**************************************************
756 Private Function DeleteText( _
757 ByVal sText As String) As String
758
759 Const D_DATA = "0123456789年月日" & _
760 "#./*-=+!""$%&'()~|`[]{}@*:;<>?_\/,"
761 Dim i1 As Long
762 Dim sW As String
763 Dim iCnt As Long
764
765 '連続する空白は1つにする
766 sText = Replace(sText, " ", " ")
767 sText = Replace(sText, " ", " ")
768
769 For i1 = 1 To Len(D_DATA)
770 sW = Mid$(D_DATA, i1, 1)
771 sText = Replace(sText, sW, "")
772 Next i1
773
774 DeleteText = Trim$(sText)
775
776 End Function
777
778 '**************************************************
779 '
780 ' 処理結果をテキスト出力する。メモ帳も起動する。
781 '
782 ' ※デバッグ用ロジック
783 '
784 ' Create : 2023/03/07
785 ' Update : 2023/03/11
786 '
787 '**************************************************
788 Private Sub WriteLog( _
789 ByVal sInFilePathIn As String, _
790 ByRef sOutData() As PageLineZahyo)
791
792 Dim sLogFilePath As String
793 Dim iFileNo As Long
794 Dim iPageNo As Long
795 Dim i1 As Long
796 Dim i2 As Long
797 Dim iLineC As Long
798 Dim iTextC As Long
799
800 Dim sPage As String
801 Dim iPageL As Long
802 Dim sPrint As String
803 Dim iSeq As Long
804
805 '実行中の当ファイルが格納されたフォルダのパス
806 sLogFilePath = ThisWorkbook.Path & "\" 'Excel
807 iFileNo = FreeFile()
808 sLogFilePath = ThisWorkbook.Path & "\" & "\Log-" & _
809 Format(Date, "yyyy-mmdd-") & _
810 Format(Time, "hhmm-ss") & ".txt"
811 Open sLogFilePath For Append As #iFileNo
812
813 '見出し
814 Print #iFileNo, "Log-File = " & sLogFilePath
815 Print #iFileNo, "IN-PDF = " & sInFilePathIn
816
817 'ヘッダー、フッターの一覧
818 For iPageNo = 0 To UBound(sOutData)
819 With sOutData(iPageNo)
820 sPage = "Page=(" & (iPageNo + 1) & "/" & _
821 (UBound(sOutData) + 1) & ") "
822 iPageL = Len(sPage)
823 'ヘッダー出力
824 iSeq = 1
825 For i2 = 0 To C_MAXLINE
826 If .bHeaderFlag(i2) Then
827 sPrint = sPage & iSeq & _
828 ".Header(" & .sLineData(i2) & ")" & _
829 "Y=" & .iLineDataY(i2)
830 Print #iFileNo, sPrint
831 sPage = Space(iPageL)
832 iSeq = iSeq + 1
833 End If
834 Next i2
835 'フッター出力
836 iSeq = 1
837 For i2 = 0 To C_MAXLINE
838 If .bFooterFlag(i2) Then
839 sPrint = sPage & iSeq & _
840 ".Footer(" & .sLineData(i2) & ")" & _
841 "Y=" & .iLineDataY(i2)
842 Print #iFileNo, sPrint
843 sPage = Space(iPageL)
844 iSeq = iSeq + 1
845 End If
846 Next i2
847 If Trim(sPage) <> "" Then
848 Print #iFileNo, sPage
849 End If
850 End With
851 Next iPageNo
852 Print #iFileNo, vbCrLf & _
853 "***************************" & vbCrLf
854
855 '各ページの作業用値の一覧
856 iLineC = 0: iTextC = 0
857 For iPageNo = 0 To UBound(sOutData)
858 With sOutData(iPageNo)
859 Print #iFileNo, _
860 "Page=(" & (iPageNo + 1) & "/" & _
861 (UBound(sOutData) + 1) & ")" & _
862 " 行数=" & .iLineCount & _
863 " テキスト数=" & .iTextCount & _
864 " テキスト平均Y高さ=" & .iAveTextY & _
865 " ヘッダー数=" & .iHeaderCount & _
866 " フッター数= " & .iFooterCount
867 If (iLineC < .iLineCount) Then iLineC = .iLineCount
868 If (iTextC < .iTextCount) Then iTextC = .iTextCount
869 End With
870 Next iPageNo
871 Print #iFileNo, vbCrLf & " 最大行数=" & _
872 iLineC & " 最大テキスト数=" & iTextC
873 Print #iFileNo, vbCrLf & "***************************"
874
875 '行データ
876 For iPageNo = 0 To UBound(sOutData)
877 Print #iFileNo, vbCrLf & "======================="
878 With sOutData(iPageNo)
879 sPage = "Page=(" & (iPageNo + 1) & "/" & _
880 (UBound(sOutData) + 1) & ") "
881 iPageL = Len(sPage)
882 Print #iFileNo, vbCrLf & sPage & _
883 " テキスト数=" & .iTextCount & _
884 " 行数=" & .iLineCount & _
885 " テキスト平均Y高さ=" & .iAveTextY & _
886 " ヘッダー数=" & .iHeaderCount & _
887 " フッター数= " & .iFooterCount
888 sPage = Space(iPageL)
889 iPageL = Len(sPage)
890
891 'ヘッダー出力
892 iSeq = 1
893 For i2 = 0 To C_MAXLINE
894 If .bHeaderFlag(i2) Then
895 sPrint = sPage & iSeq & _
896 ".Header(" & .sLineData(i2) & ")" & _
897 "Y=" & .iLineDataY(i2) & _
898 IIf(.bHeaderST(i2), " S*", "")
899 Print #iFileNo, sPrint
900 sPage = Space(iPageL)
901 iSeq = iSeq + 1
902 End If
903 Next i2
904
905 'フッター出力
906 iSeq = 1
907 For i2 = 0 To C_MAXLINE
908 If .bFooterFlag(i2) Then
909 sPrint = sPage & iSeq & _
910 ".Footer(" & .sLineData(i2) & ")" & _
911 "Y=" & .iLineDataY(i2) & _
912 IIf(.bFooterST(i2), " S*", "")
913 Print #iFileNo, sPrint
914 sPage = Space(iPageL)
915 iSeq = iSeq + 1
916 End If
917 Next i2
918 Print #iFileNo, ""
919
920 'テキスト行
921 For i2 = 0 To .iLineCount - 1
922 Print #iFileNo, "p" & (iPageNo + 1) & " " & _
923 (i2 + 1) & "/" & .iLineCount & _
924 " Text=(" & .sLineData(i2) & ")" & _
925 "Y=" & .iLineDataY(i2)
926 Next i2
927 End With
928 Next iPageNo
929 Print #iFileNo, vbCrLf & "*** EOF ***"
930 Close #iFileNo
931
932 'メモ帳を起動
933 Dim ReturnValue
934 ReturnValue = Shell("NOTEPAD.EXE " & sLogFilePath, 1)
935 End Sub
936
937 '*********************************************
938 ' メモリ上のAcrobatプロセスを数える。
939 '*********************************************
940 Private Function iCheckAcrobat() As Long
941 'On Error GoTo skip_iCheckAcrobat: 'VBA実行時エラー
942 Dim items As Object
943 Set items = CreateObject("WbemScripting.SWbemLocator") _
944 .ConnectServer.ExecQuery( _
945 "Select * From Win32_Process " & _
946 "Where Name = 'Acrobat.exe'")
947 '1以上はプロセス有り、0は無し
948 iCheckAcrobat = items.Count
949 ' Exit Function
950 'skip_iCheckAcrobat:
951 ' miErrorNo = Err.Number
952 ' msErrorMsg = Err.Description
953 End Function
954
955 '*********************************************
956 ' メモリ上に残ったAcrobatプロセスを
957 ' 正常に終了させる為だけの処理です。
958 '*********************************************
959 Sub EndAcrobat()
960 On Error Resume Next
961 Dim objAcroApp As New Acrobat.AcroApp
962 Dim objAcroAVDoc As New Acrobat.AcroAVDoc
963 Dim objAcroPDDoc As New Acrobat.AcroPDDoc
964 Dim objAFormApp As New AFORMAUTLib.AFormApp
965 Dim objAFormField As New AFORMAUTLib.Field
966 Dim objAFormFields As AFORMAUTLib.Fields
967
968 objAcroApp.CloseAllDocs
969 objAcroApp.Hide
970 objAcroApp.Exit
971
972 Set objAFormFields = Nothing
973 Set objAFormField = Nothing
974 Set objAFormApp = Nothing
975 Set objAcroPDDoc = Nothing
976 Set objAcroAVDoc = Nothing
977 Set objAcroApp = Nothing
978
979 '上記の実行後にメモリ上から消える、はず。
980 MsgBox "Acrobat OLEの終了を試みました。" & _
981 vbCrLf & "10秒後にメモリから消えたのを" & _
982 vbCrLf & "タスクマネージャーで確認して下さい。", _
983 vbSystemModal + vbInformation, "お知らせ"
984 End Sub
テキストの並び替え関連の定数
テキストをY座標をもとに並び替えます。したくない時は C1_SORTY をFlaseに変更します。行単位(=座標Yが同じ)でテキストのX座標で並び替えます。したくない時は C1_SORTX をFlaseに変更します。Y座標での並び替えを行うときに、同時にY座標の微妙な誤差(Private Const C1_SA As Long = 3)以内ならば同じ行の先頭のテキストのY座標値に強制的に変更しています。変更したくない時は C1_Y_FIX をFalseに変更します。
Private Const C1_Y_FIX As Boolean = True 'Y座標の修正
Private Const C1_SA As Long = 3 '★差の範囲 Y座標
Private Const C1_SORTX As Boolean = True 'X軸ソートの実行
Private Const C1_SORTY As Boolean = True 'Y軸ソートの実行
ヘッダー、フッター関連の定数
ヘッダーと判断した行の真下(直下)にある行もヘッダーと見なします。したくない場合は以下の C2_SHORT_HD 定数をFalseに変更します。フッターも同様です。フッター行の真上(直上)ある行もヘッダーと見なします。したくない場合はC2_SHORT_FT 定数をFalseにします。
C2_SHORT_BAI 定数は真下、真上と判断する時の計算倍率です。テキストの高さの平均値にこの倍率を掛けて、計算します。この値を大きくすると真下、真上と判断する行が増えます。大きすぎると本文の内容を拾ってしまいます。計算の詳細はVBAソースを御覧ください。
Private Const C2_SHORT_HD As Boolean = True 'ヘッダーの真下
Private Const C2_SHORT_FT As Boolean = True 'フッターの真上
Private Const C2_SHORT_BAI = 1.3 '★行(高さ)の倍率
ヘッダーと判断した行、又はフッターと判断した行と一定以上に離れているテキストは、ヘッダー、又はフッターの判断はしない方がイイです。以下の定数でTrueを設定すると、これが機能します。離れていても判断(チェック)して欲しい時はFalseにします。デフォルトはFalseで、離れていてもヘッダー、フッターの判断をします。
C2_LONG_BAI 定数は離れる行数の最大倍率(2.5行)です。この値を大きくするとチェック対象の行が増えます。
'行間が長い行はチェック対象から外す。
'True:外す False:外さない
Private Const C2_LONG_HD As Boolean = False 'ヘッダー
Private Const C2_LONG_FT As Boolean = False 'フッター
Private Const C2_LONG_BAI = 2.5 '★行(高さ)の倍率
上記のC2_SHORT_BAI 定数とC2_LONG_BAI 定数は非常に重要です。抽出漏れが有ると感じた時は迷わず値を大きくして見て下さい。
その他の定数
1ページの行数の最大は200(0~200)です。C_MAXLINEで指定してます。行の判断の計算値の分母はC_DEVで4をデフォルトにしています。
Private Const C_MAXLINE As Long = 200 'ページ中の最大行数
Private Const C_DEV As Long = 4 '差計算時の分母
当関数の評価手順
当関数付属の出力テキストファイルでフッターとヘッダーの判断結果を確認します。できるだけ種類の異なるPDFを数種類用意します。
- 最初は入力PDFを指定するだけで、定数は全てデフォルトにします。
- 次に以下の定数のTrueをFalseに、FalseをTrueに変更して再実行します。
Private Const C2_SHORT_HD As Boolean = True
Private Const C2_SHORT_FT As Boolean = True
Private Const C2_LONG_HD As Boolean = False
Private Const C2_LONG_FT As Boolean = False - 定数をもとに戻し、以下の値を少しずつ大きくしてみます。
Private Const C2_SHORT_BAI = 1.3
Private Const C2_LONG_BAI = 2.5
C2_LONG_BAI定数はC2_LONG_HDとC2_LONG_FT をTrueにしないと確認できません。 - 時間があれば、今度は値を小さくしてみます。
出力したテキストファイルはWinMerge 日本語版等でテキスト比較を行うと簡単に違いを確認できます。
備考
下記は処理結果をテキストファイルへ出力する確認用のロジックです。メモ帳も起動します。必要なくなったら、WriteLog 処理も削除して下さい。
Call WriteLog(sInFilePathIn, sOutData)
ヘッダー、フッターに関する情報が不要の方は、OutSquareRects関数内に有る以下の部分を削除して下さい。少しは処理速度が上がります。
bRet = Get_Header_Footer(sOutData)
参照
その他
- 関数内部にはデバッグ用のロジックも一部残してあります。
サイト管理者の技術メモ
これ以降はサイト管理者が当ページを管理するための技術メモです。公開用には書いていません。よって、見る必要は無いです。
関数内の数値「-1」の扱い
デバッグ時に使います。未処理だと実行エラーを発生されるようにしています。また、データが存在しなかった時の判断にも利用しています。
「Microsoft Print to PDF」と「Adobe PDF」との違い
MS Office に入っている文書をPDFにする「Microsoft Print to PDF」とAdobe Acrobatに付いている「Adobe PDF」との違いが当関数のテストで分かった。
「Microsoft Print to PDF」で作ったPDFは同じ行でもテキストの座標Yが異なるケースがかなり有る。Y座標値「2」程度だが、当関数でこれを処理すると別の行になってしまいます。「Adobe PDF」で作成したPDFは元の文書が同じ行ならば座標Yは同じになります。画面表示するPDF上のテキストは同じ箇所に有るように見えるが、これをプログラムで扱うとなると別の行の文字列になってしまうのは困る。中心座標Y値が「2」しか違わないのを修正する「座標Y誤差自動修正機能」なるものを追加します。
Y座標誤差自動修正機能
「座標Y軸ソート」をしたときに、直前・直後テキストの前後のY座標の差が有り、その差が2以内の場合は、直前のY座標の値を直後のテキストのY座標に修正する。これが発生した時は再度「座標Y軸ソート」を行う必要が有る。 <ー勘違いしてました。再ソートは不要でした。
Y軸ソート時の中心座標Yでの強制修正:Sort_TextData関数内
同じ行の判断は中心座標Yでソートした後にC_DEVの定数を使って判断してします。このソートを行うと座標Yの値によってはテキストが行内の本来の場所とは異なる場所(前後)に入ってしまうケースが出ている。それをカバーする為にソート後に「座標Y誤差の自動修正」ロジックを追加した。座標Yの差が2以下ならば前のテキストの座標Yを直後のテキストの座標Yにセットする。

この後にテキストから行データを作成する処理(関数:MargTextLine)で、前後のテキストの座標Y値がテキストの平均高さの1/4かの判断を行っている。同一行の判断のために。コレはコレで必要な処理、と思う。
微妙な誤差は強制修正が必要だが、ある程度の差は通常の計算値として通常に使用する必要が有る。上記はそうゆう話。
使えない画像
せっかく作ったのに、どこの文章にも使えない画像。

以上。

管理人様
おっしゃる通り、修正データは後ろに追加されて行くようで、末尾のフッターを抽出する方法を検討しておりました。
こちらのサンプルを活用すれば、目的の最終ページのフッター判断もできそうです。
テストして利用させていただきたいと思います。
実用的なコードと解説をありがとうございます。
ND さんへ。
連絡が届いて安心しました。
コチラのテストでは「ヘッダー」の判断方法がイマイチのPDFが少し出ています。精度を上げる方法を検討中です。機能的に不要な場合は定数で「機能しない」設定も入れる予定です。公開はかなり早い時期になります。
ここのページの更新日時等のチェックをお願いします。
<2023年3月9日 16:02 追加>
必要と思われる機能は追加完了。後日、PDFを見ながら再&再テストを行う。(疲
管理人様
コードの更新ありがとうございました。
Microsoft Print to PDFとの違いや、座標誤差など、
PDFのテキストを正確に取得するためには、
かなりの知識と労力が必要だと感じました。
必要な箇所を見極めて、取り込んでいきたいと思っております。
お手数をおかけしておりますが、引き続き、
よろしくお願いいたします。
ND さんへ。
手持ちの種類の異なる15種類ぐらいのPDFと、テスト用に作ったPDFで確認しましたが、タブン、世間には「トンデモナイ形をした文書」が存在していると思っています。
その時は行単位(+Y座標)でのテキストが存在するので、何とかなるでしょう。
「作業中」は取ります。
管理人様
大変お手数をおかけしました。
フッター処理の部分を中心に、参考にさせていただきます。
フッター抽出できるようになれば、
確認作業の効率がかなりよくなります。
ありがとうございました。
ND さんへ
期待に添えることを願ってます。 v(^_^)
<10:11 追加>
今回の件で1年前の問題点を解決できる目処が立ちました。
本文のみの検索サンプルを作れるかもしれません。先にどの様な検索仕様(オプション等も含め)にするかを考える必要が有りますが。