極限の技術:Visio PDF出力における「しおり」構造の再構築
Visio VBAを極めた者にとって、単なる`ExportAsFixedFormat`の呼び出しは「初歩」に過ぎない。しかし、エンタープライズレベルの要件、例えば数百ページに及ぶ設備図面や、複雑な階層構造を持つシステムインフラ図をPDF化する際、標準機能が生成する「ページ名が並ぶだけのしおり」は、もはや使い物にならない。
真のアーキテクトが求められるのは、図面内の特定のシェイプ(例えば「ビル名」や「ラック番号」)を動的に抽出し、PDFのメタデータ構造(アウトライン・ディクショナリ)へと再マッピングする技術だ。
本稿では、Visioの描画エンジンと外部PDFライブラリを高度に連携させ、標準機能の限界を突破する手法を詳説する。
—
1. 標準エンジンの限界と「外部介入」の必要性
Visioの`ExportAsFixedFormat`メソッドは、PDF/A準拠などの優れた点はあるが、しおり(ブックマーク)の生成ロジックはブラックボックス化されている。基本的には「ページ名」がそのまましおりになるだけであり、ページ内の階層構造を反映させることはできない。
これを制御するには、以下の2つのアプローチしかない。
1. Post-Process(後処理): PDFを出力した後、Adobe Acrobat APIやiTextSharp等を用いてアウトラインを書き換える。
2. Virtual Print(仮想印刷): PDFプリンタドライバを制御し、印刷命令の中にブックマーク制御コードを埋め込む。
現代的な自動化システムにおいて、最も堅牢なのは「後処理」だ。特に、Adobe Acrobat SDK(Acrobat Proが必要)を利用したCOM経由の操作は、Windows環境におけるデファクトスタンダードである。
—
2. オブジェクトのライフサイクルとメモリの厳密な管理
VBAからVisioとAcrobatという2つの巨大なCOMサーバを操作する場合、最も警戒すべきは「ゴーストプロセス」の発生だ。不適切な参照保持は、メモリリークを引き起こし、バッチ処理を数時間で停止させる。
‘ 参照設定: Adobe Acrobat 10.0 Type Library (もしくはそれ以降)
‘ 注意: Acrobat Readerでは不可。Pro版が必要。
Option Explicit
Public Sub ExportHighPerformancePDF()
Dim vsoApp As Visio.Application
Dim vsoDoc As Visio.Document
Dim pdfPath As String: pdfPath = “C:\Output\ComplexDiagram.pdf”
Set vsoApp = GetVisioInstance()
‘ ドキュメントのオープン(リードオンリー・非表示で高速化)
Set vsoDoc = vsoApp.Documents.OpenEx(“C:\Source\MasterTemplate.vsdx”, visOpenRO + visOpenHidden)
Try
‘ 1. 標準PDFエクスポート(まずはベースを生成)
‘ IntentはPrint(0)、OptimizationはOnScreen(1)を推奨
vsoDoc.ExportAsFixedFormat visFixedFormatPDF, pdfPath, visDocExIntentPrint, visDocExOptimizeOnScreen
‘ 2. PDFアウトラインの再構築(後述のコアロジック)
CustomizePDFBookmarks pdfPath, vsoDoc
Catch:
‘ エラー処理の実装
Finally:
‘ 決定的な解放プロセス
If Not vsoDoc Is Nothing Then vsoDoc.Close: Set vsoDoc = Nothing
‘ Visio本体を落とす場合は慎重に
End Try
End Sub
Private Function GetVisioInstance() As Visio.Application
On Error Resume Next
Set GetVisioInstance = GetObject(, “Visio.Application”)
If GetVisioInstance Is Nothing Then
Set GetVisioInstance = New Visio.Application
End If
End Function
—
3. Acrobat APIを用いたアウトラインの動的インジェクション
ここが本題だ。Visioのシェイプ情報を解析し、PDFの「ページ番号」と「座標」に紐付いたしおりを作成する。
Private Sub CustomizePDFBookmarks(ByVal pdfPath As String, ByRef vsoDoc As Visio.Document)
Dim acroApp As Object ‘ Acrobat.AcroApp
Dim avDoc As Object ‘ Acrobat.CAVGetDoc
Dim pdDoc As Object ‘ Acrobat.CApPDDoc
Dim jso As Object ‘ JavaScript Object (Acrobatの強力なバックドア)
Dim i As Integer
Set acroApp = CreateObject(“AcroExch.App”)
Set pdDoc = CreateObject(“AcroExch.PDDoc”)
‘ PDFファイルを開く
If pdDoc.Open(pdfPath) Then
‘ AcrobatのJSオブジェクトを取得(VBAの限界を超えるためのテクニック)
Set jso = pdDoc.GetJSObject
‘ 既存のしおりを全削除(クリーンな状態から構築)
‘ ※標準エクスポートが作ったゴミを排除する
jso.BookmarkRoot.remove
‘ Visioの各ページ、あるいは特定のシェイプを走査
Dim vsoPage As Visio.Page
For Each vsoPage In vsoDoc.Pages
If vsoPage.Background = 0 Then ‘ 背景ページを除外
‘ ページレベルのしおり追加
‘ jso.bookmarkRoot.createChild(名前, JSコード, インデックス)
Dim parentBM As Object
Set parentBM = jso.BookmarkRoot.createChild(vsoPage.Name, “this.pageNum = ” & (vsoPage.Index – 1), vsoPage.Index – 1)
‘ 【上級テクニック】ページ内の特定の重要シェイプを子しおりとして登録
‘ 例えば「機能ブロック」シェイプのみを抽出
Dim vsoShape As Visio.Shape
For Each vsoShape In vsoPage.Shapes
If vsoShape.CellExists(“User.IsBookmark”, visExistsAnywhere) Then
If vsoShape.CellsU(“User.IsBookmark”).ResultStr(“”) = “1” Then
‘ 子しおりを作成
parentBM.createChild vsoShape.Text, “this.pageNum = ” & (vsoPage.Index – 1), 0
End If
End If
Next vsoShape
End If
Next vsoPage
‘ 保存とクローズ
pdDoc.Save 1, pdfPath ‘ 1 = PDSaveFull
pdDoc.Close
End If
‘ 解放の儀式
Set jso = Nothing
Set pdDoc = Nothing
acroApp.Exit
Set acroApp = Nothing
End Sub
4. 実践における勘所:Win32 APIによるファイルロック待機
Visioの`ExportAsFixedFormat`は非同期的にファイルI/Oを完了させることがある。つまり、メソッドを抜けた直後にAcrobatでファイルを開こうとすると、「ファイルが使用中」でエラーになるリスクがある。
これを回避するために、Win32 APIを用いてファイルの書き込み完了を監視するコードを挟むのがプロの仕事だ。
Private Declare PtrSafe Sub Sleep Lib “kernel32” (ByVal dwMilliseconds As LongPtr)
Private Sub WaitForFileReady(filePath As String)
Dim fso As Object
Set fso = CreateObject(“Scripting.FileSystemObject”)
Dim startTime As Double
startTime = Timer
‘ 最大10秒待機
Do While Timer < startTime + 10
On Error Resume Next
Dim f As Integer: f = FreeFile
Open filePath For Binary Access Read Write Lock Read Write As #f
Close #f
If Err.Number = 0 Then Exit Do
On Error GoTo 0
Sleep 500 ' 0.5秒待機
DoEvents
Loop
End Sub
---
5. アーキテクトとしての結論
Visio VBAによるPDF出力制御の本質は、Visioという「描画エンジン」から、PDFという「構造化文書」へのセマンティックな変換にある。
1. Visio側では、`User-Defined Cells`を活用して、どのシェイプがメタデータ(しおり)として価値を持つかを定義しておく。
2. VBA側では、VisioのCOMツリーを舐めるように走査し、必要な情報をシリアライズする。
3. Acrobat APIを介して、生成されたPDFのバイナリ構造を操作し、インデックスを再構築する。
この一連の流れを、`Try-Catch-Finally`による厳密なオブジェクト管理下で実行することで、初めて「止まらない自動化システム」が完成する。レガシーなVBAを現代的なシステムインテグレーションの武器に変えるのは、こうした細部への執拗なまでのこだわりである。
