PDFとフォントの話(活用編)

以前,「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ファイルおよびフォントは複数種類で大量にあるものなので,このようなツールで作業の効率化すると便利です.

みなさんも,日常の業務を効率化するためのツールをこまめに作る習慣を心掛けてはいかがでしょうか.

biz-Stream詳細情報  biz-Stream資料請求

超高速!!高機能!! Web帳票ソリューション biz-Stream

オンデマンドかつリアルタイムにビジネスドキュメントを生成する帳票ソリューション