WordのVBAでフォントの存在チェック
MS Wordを使っていて、作成したのと別の環境で表示したときと比べてレイアウトが崩れてしまう原因のひとつにフォントがあります。ドキュメント内で指定されているフォントがないと、実際とは違うフォントで線画されるため、意図したフォントで表示されないだけでなく場合によってレイアウトが崩れたりします。
そこでフォントをチェックするツールをVBAで作成しようというのが今回の話です。すこし古いOffice2013 Word環境でコーディングしましたが、最新の2019での稼働が確認できました。
開発の表示と参照設定
まず環境の構築をします。Wordの開発タブが表示されていない場合は「開発タブを出します」「ファイル」→「オプション」から「リボンのユーザー設定」から右側のタブの「開発」にチェックを入れます。
次に表示した開発タブから「Visual Vasic」を選択し表示された画面の「ツール」から「参照設定」を開き「Microsoft Scripting Runtime」と「Microsoft XML, V6.0」を選択します。前者はファイル操作用、後者はXML読み込み用のライブラリとなっています。画像では選択されて上段にきていますが、選択されていない際は下の方に該当の項目が存在します。
メインとなるコード
先にメインとなるコードを紹介します。このコードはおおむね次のように流れていきます。
-
まず、使っている環境で利用で利用できるフォントをリストアップします。VBAで既存のフォントを確認するには、「Application.FontNames」を使います。
これで得られるフォント名の中には@から始まるものがあります。これらは縦書き用のフォントなので今回は取得対象外としています。
-
次にチェックするドキュメントのフォントを取得します。詳しくは後述しますが、WordドキュメントをZIPファイルとして展開した際に出力されるXMLを読み込むことで利用しているフォントのリストを取得しています。
-
最後にふたつのフォントリストを照らし合わせて不足している場合にメッセージに表示します。
ソースは次のようになっています。この関数の引数としてチェックしたいWordドキュメントを渡します。
checkFonts
Private Sub checkFonts(doc As Document)
Dim strFonts() As String
Dim strDocFonts() As String
Dim i, j As Integer
Dim intFontCnt As Integer
Dim blnExist As Boolean
Dim strMsg As String
Dim fs As New FileSystemObject
Dim strWorkDir As String
'既存フォント取得
intFontCnt = 0
For i = 1 To Application.FontNames.Count
If Left(Application.FontNames.Item(i), 1) <> "@" Then
intFontCnt = intFontCnt + 1
End If
Next i
ReDim strFonts(intFontCnt - 1)
For i = 1 To Application.FontNames.Count
If Left(Application.FontNames.Item(i), 1) <> "@" Then
strFonts(i - 1) = Application.FontNames.Item(i)
End If
Next i
'ドキュメントのフォント取得
'一時フォルダで作業します
strWorkDir = fs.GetSpecialFolder(TemporaryFolder) & "\get-fonts-work"
'ワークフォルダ作成(スクラップ&ビルド)
If fs.FolderExists(strWorkDir) Then
fs.DeleteFolder strWorkDir
End If
fs.CreateFolder strWorkDir
'サブフォルダ
fs.CreateFolder strWorkDir & "\extract"
'ファイルをワーク領域にコピー
fs.CopyFile doc.Path & "\" & doc.Name, strWorkDir & "\temp.zip", True
'ファイルを展開
unzip strWorkDir & "\temp.zip", strWorkDir & "\extract"
'XMLからフォント取得
strDocFonts = getDocFonts(strWorkDir & "\extract" & "\word\fontTable.xml")
'比較
For i = 0 To UBound(strDocFonts)
blnExist = False
For j = 0 To UBound(strFonts)
If strDocFonts(i) = strFonts(j) Then
blnExist = True
Exit For
End If
Next j
If blnExist = False Then
strMsg = strMsg & vbCrLf & strDocFonts(i)
End If
Next i
'ワークフォルダ片付け
fs.DeleteFolder strWorkDir
Set fs = Nothing
If strMsg = "" Then
Debug.print "すべてのフォントが利用できます"
Else
strMsg = doc.Name & "内の次のフォントが利用できません" & strMsg
Debug.print strMsg
End If
End Sub
ドキュメント内で利用しているフォントを取得
ドキュメント内で利用しているフォントを取得する方法はWord:「使用されているフォント 一覧を表示する方法」を参考にさせていただきました。
一度ZIPファイルとして展開して、中にあるフォント一覧のあるXMLファイルを読み込みます。この時一時フォルダを作業スペースとして利用しました。
まず調べたいファイルを一時フォルダにコピーします。
コピーしたファイルを展開します。これにはPowerShellを利用しています。この方法はExcel作業をVBAで効率化:「VBAでZIP圧縮と解凍を行う」を参考にさせていただきました。
またWScriptの操作ではQuiita:「VBAからPowerShellを起動し、任意コマンドを実行する」を参考にさせていただきました。
unzip
Private Function unzip(strSource As String, strTarget As String)
Dim objWSH As Object
Dim blnWait As Boolean
Dim intVisibleType As Integer '0:off 1:on
Dim strCommand As String
blnWait = True
intVisibleType = 0
strCommand = "Expand-Archive -Path " & strSource & " -DestinationPath " & strTarget & " -Force"
Set objWSH = CreateObject("WScript.Shell")
objWSH.Run "powershell -NoLogo -ExecutionPolicy RemoteSigned -Command " & strCommand, intVisibleType, blnWait
End FunctionEnd Sub
XMLをParse
先の参考サイトにあったようにWordファイルをZIPファイルとみなして展開して出てくる¥word¥fontTable.xmlを読み込みます。中身は割とシンプルなのでXMLでparseしないでテキストで読み込んでもいいかなとコードを書いた後で思いました。
XMLのパースについてはプログラマー向けEXCEL活用術ブログ:「【VBA】ノード名を指定せずにXMLをパースする」を参考にさせていただきました。
この関数の引数にはfontTable.xmlのパスを渡します。
getDocFonts
Private Function getDocFonts(strPath As String) As String()
Dim XMLDocument As New MSXML2.DOMDocument60
Dim nodeFonts As IXMLDOMNode
Dim node As IXMLDOMNode
Dim intCnt As Integer
Dim strFonts() As String
XMLDocument.async = False
XMLDocument.Load strPath
If (XMLDocument.parseError.ErrorCode <> 0) Then
'XMLParseエラー
getDocFonts = strFonts
Exit Function
End If
Set nodeFonts = XMLDocument.ChildNodes(1)
intCnt = 0
'fontsの子ノードがfont
For Each node In nodeFonts.ChildNodes
If node.Attributes(0).NodeValue <> "" Then
intCnt = intCnt + 1
End If
Next node
ReDim strFonts(intCnt - 1)
intCnt = 0
For Each node In nodeFonts.ChildNodes
If node.Attributes(0).NodeValue <> "" Then
strFonts(intCnt) = node.Attributes(0).NodeValue
intCnt = intCnt + 1
End If
Next node
Set node = Nothing
Set nodeFonts = Nothing
getDocFonts = strFonts
End Function
どのファイルを開いてもコードが使えるように
コードを記述したファイルを使うたびにオープンするのは何となくスマートではありません。なので、このコードをWordの共通データに保存します。
「開発」「マクロ」のウインドウを開き、中段にある「マクロの保存先」を「Normal.dotm(全文書対象のテンプレート)」に切り替え右側の「構成内容変更」ボタンを押します。
開いたウインドウの左側がマクロを作成したファイル、右側が共通のテンプレートとなります。右側の一覧から共通にさせたいマクロが含まれるモジュールや、フォームを右側にコピーします。
一度登録したものを削除したい場合は、右側の一覧の中から対象のモジュールやフォームを選択した状態で削除を押します。
コピーした後は「閉じる」を押して終了させてください。これでどのファイルからもマクロを呼び出せるようになりました。
参考にさせていただきましたサイトの皆様ありがとうございました