以前,「PDFとフォントの話」で,PDFの表示を正しいフォントで行うための,
FontConfig の設定調整方法を紹介しました.
しかし,この手法を実際の環境で活用するには,目視確認に手間がかかって大変です.
そこで,自動的に .fonts.conf に追加設定候補を追加する補助ツールを作りました.
前回同様,SWFTools を使って FontConfig の設定状況のログを出力し,
その結果を解析して,適切な追加設定を生成します.
前回同様,以下のような筆者の環境での,FontConfigの例を紹介します.
- Red Hat 4.1.2-50
- FontConfig 2.4.1
ツールはRubyで,内容は,以下のとおりです.
- 《 fc-chk.rb 》
#!/usr/bin/ruby def okdir( path ) return false if !(File.exist?(path)) return false if !(File.directory?(path)) return true end def usage puts "USAGE: fc-check.rb <SWFTools's Path> <Target Directory>" end def getBinPath( path ) return nil if !(okdir(path)) f = File.expand_path("bin",path) return nil if !(okdir(f)) f = File.expand_path("pdf2swf", f) return nil if !(File.exist?(f)) return f end def getFontPath( path ) return nil if !(okdir(path)) f = File.expand_path("fonts",path) return nil if !(okdir(f)) return f end def getLangPath( path ) return nil if !(okdir(path)) f = File.expand_path("share", path) return nil if !(okdir(f)) f = File.expand_path("xpdf", f) return nil if !(okdir(f)) f = File.expand_path("japanese", f) return nil if !(okdir(f)) return f end def getMapInfo( msg ) retArray = Array.new() msg.each_line {|ln| if (/\smaps\sto\s/ === ln || /\sFont\s.*\snot\sfound/ === ln) ln = ln.chomp retArray.push(ln) end } return retArray end def getFontNameByMapInfo( line ) retS = "" if (/\smaps\sto\s/ === line) /\sFont\s/ === $` retS = $' end if (/\sFont\s.*\snot\sfound/ === line) /\sFont\s/ === $& /\snot\sfound/ === $' retS = $` end retS = retS.strip() return retS end def isMapedSame( line ) retB = false return retB if !(/\smaps\sto\s/ === line) bName = $` aName = $' /\sFont\s/ === bName bName = $'; bName.strip; anArray = aName.split("/") aName = anArray.last anArray = aName.split("_mk") aName = anArray.first; aName.strip; retB = true if (aName == bName) return retB end def getFontSubstInfo( fontName, substInfo ) retArray = Array.new() exp = Regexp.union( fontName ) substInfo.each {|elem| elem.each {|n| (retArray.push(elem); break;) if ( exp === n ) } } return retArray if !(retArray.empty?) fnArray = fontName.split("-") exp = Regexp.union( fnArray.last ) substInfo.each {|elem| elem.each {|n| (retArray.push(elem); break;) if ( exp === n ) } } return retArray end def getSubstitureInfo( msg ) retArray = Array.new() elem = nil exp = Regexp.union("FcConfigSubstitute\sPattern","FcConfigSubstitute\sdonePattern") msg.each_line {|ln| (elem = Array.new();next;) if (exp === ln) if !(/^\t/ === ln) next if (nil==elem) elem.each {|l| (retArray.push(elem); break;) if (/^\tfile:/ === l) } elem = nil next end next if (nil==elem) ln = ln.chomp elem.push(ln) } return retArray end def printSubstInfo( subs ) subs.each {|elem| puts "--------" elem.each {|ln| break if (/charset/ === ln) puts ln } puts "--------" } end def printFontsConfXML( subs ) subs.each {|elem| orgF = "" orgS = "" orgN = "" newF = "" newS = "" elem.each {|ln| break if (/charset/ === ln) orgF = $' if (/family:\s/ === ln) orgS = $' if (/style:\s/ === ln) orgN = $' if (/fullname:\s/ === ln) } orgF.gsub!("(s)", ""); orgF.gsub!("(w)", ""); orgF.gsub!("\"",""); orgF.strip!; orgF.chomp!; orgS.gsub!("(s)", ""); orgS.gsub!("(w)", ""); orgS.gsub!("\"",""); orgS.strip!; orgS.chomp!; orgN.strip!; orgN.chomp!; if (nil == orgN || 0 == orgN.size ) orgN = "nil" else nArray = orgN.split("(s)"); orgN = nArray.first; orgN = orgN.gsub("(s)", ""); orgN = orgN.gsub("\"",""); end nArray = orgN.split("-"); if (2 == nArray.length) newF = nArray.first; newS = nArray.last; else nArray = orgF.split(" ") if (2 == nArray.length && "Regular" == orgS ) newF = nArray.first; newS = nArray.last; else newF = orgF.gsub(" ",""); newS = orgS.gsub(" ", ""); end end next if (orgF == newF && orgS == newS ) puts " <match target=\"scan\">" puts " <test name=\"family\" qual=\"any\">" puts(" <string>" + orgF + "</string>") puts " </test>" puts " <test name=\"style\" qual=\"any\">" puts(" <string>" + orgS + "</string>") puts " </test>" puts " <edit name=\"family\" mode=\"assign_replace\">" puts(" <string>" + newF + "</string>") puts " </edit>" puts " <edit name=\"style\" mode=\"assign_replace\">" puts(" <string>" + newS + "</string>") puts " </edit>" puts " </match>" } end def trimSubstInfo( subs ) retArray = Array.new() subs.each {|elem| next if (retArray.include?(elem)) retArray.push(elem) } return retArray end def chkFontConfig( modulePath, targetPath ) (usage; return;) if (nil==modulePath || nil==targetPath) (usage; return;) if !(okdir(targetPath)) binPath = getBinPath( modulePath ) (usage; return;) if (nil==binPath) fontPath = getFontPath( modulePath ) (usage; return;) if (nil==fontPath) langPath = getLangPath( modulePath ) (usage; return;) if (nil==langPath) ENV['FC_DEBUG'] = '1039' Dir.foreach( targetPath ) {|f| next if !(/[^.]/ === f) next if !(/.pdf$/ === f) retFile = f.gsub(".pdf",".swf") retFile = File.expand_path(retFile, targetPath) file = File.expand_path(f, targetPath) msg = `#{binPath} -vvv -s languagedir=#{langPath} -F #{fontPath} -T 4 -s flashversion=4 #{file} -o #{retFile} 2>&1` maps = getMapInfo(msg) # puts msg subs = getSubstitureInfo(msg) subs = trimSubstInfo(subs) #===== maps.each {|ln| fntname = getFontNameByMapInfo(ln) if (isMapedSame(ln)) puts( fntname + "\t\tis OK!!") else puts "========" puts ln puts "--------" sinf = getFontSubstInfo( fntname, subs ) printFontsConfXML( sinf ) puts "--------" # printSubstInfo( sinf ) puts "========" end } } end chkFontConfig(ARGV[0], ARGV[1])
例として,上記の「fc-chk.rb」を,1つのフォントだけで記述されたPDFに対して,
以下のように実行すると,
- 《 ツールの実行 》
-
> pwd; ls /home/hoge fc-chk.rb swftools PDFs > ls ./swftools bin fonts share > ls ./PDFs FreeStyle_Single-MinionProIt.pdf > ruby ./fc-chk.rb ./swftools ./PDFs > result.txt > pwd; ls fc-chk.rb swftools PDFs result.txt
となり,以下のような結果が出力されます.
- 《 result.txt 》
======== VERBOSE Font MinionPro-It maps to /home/hoge/swftools/fonts/MinionPro-Semibold_mk.ttf -------- <match target="scan"> <test name="family" qual="any"> <string>Minion Pro</string> </test> <test name="style" qual="any"> <string>Italic</string> </test> <edit name="family" mode="assign_replace"> <string>MinionPro</string> </edit> <edit name="style" mode="assign_replace"> <string>It</string> </edit> </match> -------- -------- family: "Minion Pro"(s) familylang: "en"(s) style: "Italic"(s) stylelang: "en"(s) fullname: "MinionPro-It"(s) "Minion Pro Ital"(s) fullnamelang: "en"(s) "en"(s) slant: 100(i)(s) weight: 80(i)(s) width: 100(i)(s) foundry: "adobe"(s) file: "/home/jetty/bpsr-swftools/fonts/MinionPro-It_mk.ttf"(s) index: 0(i)(s) outline: FcTrue(s) scalable: FcTrue(s) --------
これは,PDFファイル
-
/home/hoge/PDFs/FreeStyle_Single-MinionProIt.pdf
の内部で指定されているフォント名「MinionPro-It」と フォントファイルの関係が正しく認識できていないことを意味します. そこで,「result.txt」の,XML部分を,FontConfigの設定ファイル「~/.fonts.conf」に追加します.
- 《 ~/.fonts.conf 》
<?xml version="1.0"?> <!DOCTYPE fontconfig SYSTEM "fonts.dtd"> <fontconfig> ........ <match target="scan"> <test name="family" qual="any"> <string>Minion Pro</string> </test> <test name="style" qual="any"> <string>Italic</string> </test> <edit name="family" mode="assign_replace"> <string>MinionPro</string> </edit> <edit name="style" mode="assign_replace"> <string>It</string> </edit> </match> ........ </fontconfig>
そして,再度,以下のように実行すると,
- 《 ツールの再実行 》
-
> pwd; ls /home/hoge fc-chk.rb swftools PDFs > ls ./swftools bin fonts share > ls ./PDFs FreeStyle_Single-MinionProIt.pdf > ruby ./fc-chk.rb ./swftools ./PDFs > result2.txt > pwd; ls fc-chk.rb swftools PDFs result2.txt
以下のような結果が得られ,
- 《 result2.txt 》
MinionPro-It is OK!!
PDF内で指定されているフォント名に対して, フォントファイルを正しくマッピングするようになりました.
今回の例のように,単一のPDFファイルおよびフォントが対象の場合は,手動で作業してもたいしたことはありませんが,
通常,対象のPDFファイルおよびフォントは複数種類で大量にあるものなので,このようなツールで作業の効率化すると便利です.
みなさんも,日常の業務を効率化するためのツールをこまめに作る習慣を心掛けてはいかがでしょうか.