【テクニカル・上級編】【中級者向け】段落の「行間」を、文書内の画像サイズに合わせて自動的に拡張するマクロ – Word VBA解析バイブル

スポンサーリンク

【Word VBA極限の知見】画像サイズに完全追従する「行間自動拡張」アーキテクチャ

Wordでマニュアルや仕様書を作成しているとき、幾度となく直面する「画像の上下が切れる」という怪現象。
原因は単純だ。Wordの段落書式が「固定値」あるいは狭い「行間」に設定されている状態で、その行に高さ(Height)を持つインライン画像(文字列と共に配置)が挿入された際、Wordの描画エンジンが段落のボックスモデルを画像サイズに合わせて適切に再計算しない(あるいは設定された制約を優先する)ことに起因する。

GUIから「段落」ダイアログを開き、ちまちまと行間を「1行」から「倍数」や「最小値」に変更し、画像ごとに手動調整する? そんな泥臭いオペレーションは、プロフェッショナルな自動化エンジニアの辞書には存在しない。

今回は、文書内の全インライン画像を走査し、その高さを正確に算出した上で、所属する段落の行間をミリ秒単位で最適化する「行間自動拡張エンジン」の全貌を解説する。

1. Word描画エンジンの闇:なぜ画像が切れるのか

Wordの段落(`Paragraph`オブジェクト)と、その内部に存在するインライン画像(`InlineShape`オブジェクト)の関係性を見誤ると、マクロは容易に破綻する。

一般的に、VBAで画像サイズを取得する場合、以下のプロパティが存在する。

  • `InlineShape.Height` (現在の表示高さ)
  • `InlineShape.Width` (現在の表示幅)
  • `InlineShape.ScaleHeight` (スケール比率)

ここで致命的な罠となるのが、「行間(LineSpacing)と段落の間隔(SpaceBefore / SpaceAfter)」の概念の乖離である。
Wordの行間設定において、`wdLineSpaceMultiple` や `wdLineSpace1pt5` などの相対値を使っている場合、画像がそのフォントサイズ(Pt)を超える高さを持っていると、レイアウトエンジンは行の高さを拡張しきれず、結果として上下がクリッピング(切断)される。

これを防ぐためには、画像を内包する段落の行間モードを強制的に 「最小値(wdLineSpaceAtLeast)」 もしくは 「固定値(wdLineSpaceExactly)」 に変更し、行間ポイント数(LineSpacing)に「画像高さ + 余裕値(パディング)」を動的に流し込む必要がある。

2. 実装アーキテクチャ:堅牢性とパフォーマンスの極限追求

単に画像を探して行間を変えるだけのコードであれば初心者でも書ける。しかし、何百ページもあるレガシー文書を対象にした場合、オブジェクトの参照リーク、画面描画のチラつき、そして不要なUndoバッファの肥大化によるメモリ枯渇が発生する。

以下の極限最適化コードは、実務の現場で耐えうる堅牢性を持たせた完全版である。

Option Explicit

‘ ==============================================================================
‘ 処理名 : AutoExpandLineSpacingForImages
‘ 概要 : 文書内の全インライン画像を走査し、画像サイズに合わせて所属段落の行間を自動拡張する
‘ アーキテクチャ上の特徴:
‘ 1. ScreenUpdatingの完全制御による描画オーバーヘッドの排除
‘ 2. Undo記録の制限(または暗黙的グループ化)によるメモリ効率化
‘ 3. オブジェクトの明示的解放(Nothing代座)によるVBA特有のCOMメモリリーク防止
‘ ==============================================================================
Public Sub AutoExpandLineSpacingForImages()
Dim startTime As Double
startTime = Timer

‘ 1. パフォーマンス最適化の極み:描画停止と警告抑制
With Application
.ScreenUpdating = False
.DisplayAlerts = wdAlertsNone
.Calculation = wdCalculationManual
End With

On Error GoTo ErrorHandler

Dim doc As Document
Set doc = ActiveDocument

Dim ilShp As InlineShape
Dim targetPara As Paragraph
Dim requiredHeight As Single
Dim processedCount As Long
processedCount = 0

‘ パディング(上下の余白として確保するポイント数:例として4pt)
Const PADDING_PT As Single = 4#

‘ 2. インライン画像コレクションの高速イテレーション
Dim totalImages As Long
totalImages = doc.InlineShapes.Count

If totalImages = 0 Then
MsgBox “処理対象のインライン画像は存在しません。”, vbInformation, “情報”
GoTo Cleanup
End If

For Each ilShp In doc.InlineShapes
‘ 画像以外のオブジェクト(横線や数式フィールドなど)の誤爆を防ぐ
If ilShp.Type = wdInlineShapePicture Or _
ilShp.Type = wdInlineShapeLinkedPicture Or _
ilShp.Type = wdInlineShapeEmbeddedOLEObject Then

‘ 所属する段落を取得
Set targetPara = ilShp.Range.Paragraphs(1)

‘ 画像の高さ(ポイント単位)にパディングを加算
requiredHeight = ilShp.Height + PADDING_PT

‘ 3. 段落書式の動的書き換え
‘ 行間モードを「最小値」に設定し、画像が収まるよう強制拡張する
With targetPara
.LineSpacingRule = wdLineSpaceAtLeast
.LineSpacing = Application.PointsToPixels(requiredHeight, wdFormattingVisible) ‘ ※厳密にはポイント指定のため PointsToCentimeters 等ではなく直接Pt値を代入
‘ 正しいPt代入: .LineSpacing = requiredHeight
End With

‘ 正確なポイント代入の再適用(APIの型整合性を担保)
targetPara.LineSpacing = requiredHeight

processedCount = processedCount + 1

‘ ループ内でのメモリ解放
Set targetPara = Nothing
End If
Set ilShp = Nothing
Next ilShp

‘ 4. 計算結果のコミットと画面再描画
doc.Fields.Update ‘ 必要に応じてフィールド再計算

Dim elapsedTime As Double
elapsedTime = Timer – startTime

Application.ScreenUpdating = True
Application.DisplayAlerts = wdAlertsAll
Application.Calculation = wdCalculationAutomatic

MsgBox “処理完了: ” & processedCount & ” 件の画像を検知し、行間を最適化しました。” & vbCrLf & _
“実行時間: ” & Format(elapsedTime, “0.00”) & ” 秒”, vbInformation, “アーキテクチャ実行完了”
Exit Sub

ErrorHandler:
‘ 異常系ハンドリング:COMオブジェクトのゾンビ化を防ぐ
Application.ScreenUpdating = True
Application.DisplayAlerts = wdAlertsAll
Application.Calculation = wdCalculationAutomatic

MsgBox “予期せぬエラーが発生しました。” & vbCrLf & _
“Error 0x” & Hex(Err.Number) & “: ” & Err.Description, vbCritical, “致命的エラー”

Cleanup:
‘ 確実なメモリ解放
Set targetPara = Nothing
Set ilShp = Nothing
Set doc = Nothing
End Sub

3. チーフアーキテクトが解説するコードの急所

① `Application.ScreenUpdating` と `Calculation` の封じ込め

数百枚の画像を含む数MB規模のWordファイルを操作する場合、VBAからプロパティを変更するたびにWordは画面の再描画とレイアウトの再計算(Reflow)を走らせる。これが激しいパフォーマンス低下の元凶である。
冒頭で `ScreenUpdating = False` と `Calculation = wdCalculationManual` を明示し、全てのDOM操作(Document Object Model)をメモリ上で完結させた後に一括描画する手法は、大規模バッチ処理における鉄則である。

② `wdLineSpaceAtLeast` の採用理由

行間に「固定値(`wdLineSpaceExactly`)」を強制すると、万が一ユーザーがその行にフォントサイズの大きな文字を混在させた場合に、今度は文字の方が切れてしまうという本末転倒な事態に陥る。
「最低でも画像サイズ分の高さを確保し、それ以上の要素があれば自然に拡張する」という挙動を実現するために、`wdLineSpaceAtLeast`(最小値)の指定が唯一無二の解となる。

③ COMオブジェクトの明示的破棄(メモリリーク対策)

VBAはガベージコレクションの挙動が非決定的(Deterministicではない)である。特に `For Each` ループ内で `ilShp.Range.Paragraphs(1)` のようなプロパティチェーンを使用すると、背後で暗黙的なCOMインターフェイスの参照が生成され、ループが巨大化するにつれてメモリリークを引き起こす。
コード内で `Set targetPara = Nothing` や `Set ilShp = Nothing` をループの都度(あるいはプロシージャの脱出時に)明示的に記述しているのは、レガシー環境や長時間のバッチ実行でも安定稼働させるための防衛策である。

4. 発展:システム間連携・外部バッチとしての運用

このマクロを単なる「個人の便利ツール」で終わらせてはならない。
社内のドキュメント管理システム(DMS)や、PDF自動生成サーバーのパイプラインの一部として組み込む場合、Wordの外側(VBScriptやC#/.NETのCOM Interop)からこのマクロをサイレント実行することが求められる。

// C# (.NET Interop) から Word VBA マクロを安全に駆動する概念コード
Microsoft.Office.Interop.Word.Application wordApp = new Microsoft.Office.Interop.Word.Application();
wordApp.Visible = false;
Microsoft.Office.Interop.Word.Document doc = wordApp.Documents.Open(@”C:\Docs\Specification.docx”);

// マクロの安全な実行
wordApp.Run(“AutoExpandLineSpacingForImages”);

doc.Save();
doc.Close();
wordApp.Quit();

このように、Wordの不条理なレイアウト仕様をVBAのプログラムで完全に掌握し、機械的に補正する仕組みを構築しておけば、どれほど膨大な画像が挿入されようとも、印刷プレビューやPDF変換時の「画像の切断事故」を完全に根絶することが可能となる。

技術者たる者、GUIの奴隷になるな。コードで仕様をねじ伏せろ。

タイトルとURLをコピーしました