PDFlib TET で、特定のフォントが PDF 文書のどこに使われているかを表示するサンプルプログラムです。ページ番号、位置、最初に見つかったテキストの最初の部分を表示します。ファイルが複数ある場合は、ページ番号の前にファイル名が表示されます。
フォントリストは、カンマで区切られたフォント名のリストです。-ignorefonts および -includefonts が指定されていない場合は、全てのフォントが含まれます。-ignorefonts が指定されている場合、指定されたフォントを除いて表示します。-includefonts が指定されている場合は、フォントリストで指定されたフォントのみ表示します。
アプリケーションは、Adobe Acrobat と同様に左上隅を座標系の基点として出力しています。これは、左下隅を基点とする、PDF のデフォルトの座標系とは異なります。PDF のデフォルト座標系を使用する場合は、USE_ACROBAT_COORDINATES 変数に false をセットします。
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.text.NumberFormat;
import java.util.Iterator;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.TreeSet;
import com.pdflib.TET;
import com.pdflib.TETException;
/**
* PDFで指定したフォントが使われている場所を特定し、ページ番号、位置、テキストの最初の
* 部分を出力する。
*
* 使用方法: フォントの検索
* [ -ignorefonts <font list> | -includefonts <font list> ] <PDF document>
*
* <font list> はカンマで分割されたフォント名である。
* -ignorefonts も -includefonts もどちらも指定されない場合は、全てのフォントが対象となる。
* -ignorefonts が指定された場合、そのフォントを除いた全てが含まれる。
* -includefonts が指定された場合、そのフォントのみ含まれる。
*
* アプリケーションは Adobe Acrobat と同様に左上隅を座標の基点とする。これは左下隅を座標の
* 基点とするPDF座標とは異なる。PDF座標を使用する場合は、USE_ACROBAT_COORDINATES に"false"
* を指定する。
*
* Acrobat では、カーソル座標を次のように表示できる。
*
* カーソル座標の表示
* →Acrobat 7/8: [表示 | ナビゲーションパネル | 情報]
* →Acrobat 9: [表示 | カーソル座標]
* 単位の選択
* →Acrobat 7/8/9: [編集 | 環境設定 | (分類) | 単位とガイド | ページと規定の単位]
* →Acrobat 7/8 では、情報パネルのオプションを使うことができる。
*
* 必要な製品 : TET 4
*
* 必要なデータ: PDF 文書
*
* @version $Id: font_finder.java,v 1.10 2008/11/20 08:06:40 stm Exp $
*/
public class font_finder {
/**
* グローバルオプションリスト。 相対パス名を指定
*/
private static final String GLOBAL_OPTLIST = "searchpath={../resource/cmap "
+ "../resource/glyphlist ../input}";
/**
* 文書のオプションリスト
*/
private static final String DOC_OPTLIST = "";
/**
* ページのオプションリスト。
* フォントが変わるまでは改行しないようにするため、セパレーターを定義する
*/
private static final String PAGE_OPTLIST = "granularity=page "
+ "contentanalysis={lineseparator=U+0000 zoneseparator=U+0000}";
/**
* System.out に送られるエンコーディング。
* 例えば、コマンドウィンドウでデフォルトエンコーディングと異なる文字コードを
*指定することがする
*/
private static final String OUTPUT_ENCODING =
System.getProperty("file.encoding");
/**
* OUTPUT_ENCODING にて指定されたエンコーディングで System.out より出力
*/
private static PrintStream out;
/**
* 出力に含めないフォントを識別するための、コマンドラインフラグ
*/
private static final String IGNORE_OPT = "-ignorefonts";
/**
* 出力に含めるフォントを識別するための、コマンドラインフラグ
*/
private final static String INCLUDE_OPT = "-includefonts";
/**
* ファイルが複数あり、ファイル名が前に付加される場合のテキストの最大長
*/
private final static int MAX_TEXT_LENGTH_MULTI_FILE = 25;
/**
* ファイル名が付加されない場合の、テキストの最大長
*/
private final static int MAX_TEXT_LENGTH_SINGLE_FILE = 40;
/**
* 左上隅を基点とするAcrobat座標系、または左下隅を基点とするPDFデフォルト座標系を
* 使用する
*/
private static final boolean USE_ACROBAT_COORDINATES = true;
/**
* 指定されたフォントのみ出力する。null値の場合、全てのフォントが含まれる
*/
private Set<String> includedFonts;
/**
* 指定されたフォントを除いて出力する。null値の場合、フォントは除かれない
*/
private Set<String> ignoredFonts;
/**
* 入力文書名
*/
private String filename;
/**
* 座標(x,y)を出力するためのフォーマット
*/
private NumberFormat coordFormat;
/**
* ファイルが複数ある時、各行でファイル名を出力する
*/
private boolean prependFilenames;
/**
* @param filename
* 入力文書のファイル名
* @param fontsToInclude
* 出力に含めるフォント(おそらくnull値)
* @param fontsToIgnore
* 出力に含めないフォント(おそらくnull値)
* @param prependFilenames
* 各行でファイル名を前に付加する
*/
private font_finder(String filename, Set<String> fontsToInclude, Set<String> fontsToIgnore,
boolean prependFilenames) {
this.filename = filename;
this.includedFonts = fontsToInclude;
this.ignoredFonts = fontsToIgnore;
this.prependFilenames = prependFilenames;
this.coordFormat = NumberFormat.getInstance();
coordFormat.setMinimumFractionDigits(0);
coordFormat.setMaximumFractionDigits(2);
}
/**
* フォント検索の処理を実行
*/
private void execute() {
TET tet = null;
int pageno = 0;
try {
tet = new TET();
tet.set_option(GLOBAL_OPTLIST);
final int doc = tet.open_document(filename, DOC_OPTLIST);
if (doc == -1) {
System.err.println("Error " + tet.get_errnum() + " in "
+ tet.get_apiname() + "(): " + tet.get_errmsg());
}
else {
/*
* PDF文書のページ分ループする
*/
final int n_pages = (int) tet.pcos_get_number(doc,
"length:pages");
for (pageno = 1; pageno <= n_pages; ++pageno) {
process_page(tet, doc, pageno);
}
tet.close_document(doc);
}
}
catch (TETException e) {
if (pageno == 0) {
System.err.println("Error " + e.get_errnum() + " in "
+ e.get_apiname() + "(): " + e.get_errmsg() + "\n");
}
else {
System.err.println("Error " + e.get_errnum() + " in "
+ e.get_apiname() + "() on page " + pageno + ": "
+ e.get_errmsg() + "\n");
}
}
finally {
tet.delete();
}
}
/**
* ページから、同じフォントを使用しているテキストのまとまりを取得する
*
* @param tet
* TET オブジェクト
* @param doc
* TET ドキュメントハンドル
* @param pageno
* 処理をするページ
*
* @throws TETException
* TET API によるエラー
*/
private void process_page(TET tet, final int doc, int pageno)
throws TETException {
final int page = tet.open_page(doc, pageno, PAGE_OPTLIST);
if (page == -1) {
System.err.println("Error " + tet.get_errnum() + " in "
+ tet.get_apiname() + "(): " + tet.get_errmsg());
}
else {
/*
* ページからテキストを取得し、同じフォントを使用しているテキストの
* まとまりで分割する
*/
for (String text = tet.get_text(page); text != null; text = tet
.get_text(page)) {
process_char_info(tet, doc, pageno, page, text);
}
if (tet.get_errnum() != 0) {
System.err.println("Error " + tet.get_errnum() + " in "
+ tet.get_apiname() + "(): " + tet.get_errmsg());
}
tet.close_page(page);
}
}
/**
* ページ上の文字情報を処理し、その結果を出力する
*
* @param tet
* TET オブジェクト
* @param doc
* TET ドキュメントハンドル
* @param pageno
* ページ番号
* @param page
* TET ページハンドル
* @param text
* ページ上のテキスト
*
* @throws TETException
*/
private void process_char_info(TET tet, final int doc, int pageno,
final int page, String text) throws TETException {
int currentFontId = -1;
int currentStringStart = 0;
int currentStringEnd = 0;
double xPos = 0;
double yPos = 0;
/*
* Acrobat座標に変換するため、ページの高さを取得する
*/
final double pageHeight = tet.pcos_get_number(doc,
"pages[" + (pageno - 1) + "]/height");
for (int ci = tet.get_char_info(page); ci != -1; ci = tet
.get_char_info(page), currentStringEnd += 1) {
int newFontId = tet.fontid;
if (newFontId != currentFontId) {
if (currentFontId != -1) {
// テキスト情報を出力する
String fontName = tet.pcos_get_string(doc, "fonts["
+ currentFontId + "]/name");
if (includeFontInOutput(fontName)) {
if (USE_ACROBAT_COORDINATES) {
yPos = pageHeight - yPos;
}
/*
* コマンドラインで複数のファイルを指定した場合、ファイル名が
* 出力される
*/
if (prependFilenames) {
out.print(filename + ", ");
}
out.print("page " + pageno);
out.print(" at (" + coordFormat.format(xPos)
+ " " + coordFormat.format(yPos) + "), ");
out.print("font " + fontName + ":");
int textLength = currentStringEnd - currentStringStart;
int displayLength = Math.min(
prependFilenames
? MAX_TEXT_LENGTH_MULTI_FILE
: MAX_TEXT_LENGTH_SINGLE_FILE,
textLength);
out.print(text.substring(currentStringStart,
currentStringStart + displayLength));
if (textLength > displayLength) {
out.print("...");
}
out.println();
}
}
currentFontId = newFontId;
currentStringStart = currentStringEnd;
xPos = tet.x;
yPos = tet.y;
}
}
}
/**
* 出力にフォントを含めるかどうか
*
* @param fontName
* チェックするフォント名
*
* @return 出力されるべきフォントの場合はtrue
*/
private boolean includeFontInOutput(String fontName) {
return (includedFonts == null && ignoredFonts == null)
|| (includedFonts != null && includedFonts.contains(fontName))
|| (ignoredFonts != null && !ignoredFonts.contains(fontName));
}
/**
* カンマで区切られたフォントリストを出力する
*
* @param fonts
* リストとして出力するためのフォントを設定
*/
private static void print_font_list(Set<String> fonts) {
Iterator<String> i = fonts.iterator();
int pos = 0;
while (i.hasNext()) {
if (pos > 0) {
out.print(", ");
}
String fontName = (String) i.next();
out.print(fontName);
}
}
/**
* フォント名リストを分解し、それらからフォント名のセットを生成する
*
* @param fontList
* カンマで区切られたフォントリスト
*
* @return フォントリストの要素を含む集合
*/
private static Set<String> parse_font_list(String fontList) {
Set<String> retval = new TreeSet<String>();
StringTokenizer tokenizer = new StringTokenizer(fontList, ",");
while (tokenizer.hasMoreTokens()) {
retval.add((String) tokenizer.nextElement());
}
return retval;
}
/**
* メインプログラム
*
* @param args
* コマンドライン引数
*
* @throws UnsupportedEncodingException
* サポートされていないエンコードの場合に例外を送出
*/
public static void main(String[] args) throws UnsupportedEncodingException {
System.out.println("Using output encoding \"" + OUTPUT_ENCODING + "\"");
out = new PrintStream(System.out, true, OUTPUT_ENCODING);
Set<String> fontsToInclude = null;
Set<String> fontsToIgnore = null;
int i;
for (i = 0; i < args.length; i += 1) {
if (args[i].equals(IGNORE_OPT)) {
i += 1;
if (i < args.length && fontsToIgnore == null
&& fontsToInclude == null) {
fontsToIgnore = parse_font_list(args[i]);
}
else {
usage();
}
}
else if (args[i].equals(INCLUDE_OPT)) {
i += 1;
if (i < args.length && fontsToIgnore == null
&& fontsToInclude == null) {
fontsToInclude = parse_font_list(args[i]);
}
else {
usage();
}
}
else {
break;
}
}
// 少なくとも1つの項目は、入力ファイルとして指定されていなければならない
if (i < args.length) {
/*
* "included fonts"および"ignored fonts"を記述したヘッダー
*/
out.print("included fonts: ");
if (fontsToInclude == null) {
out.print("all except ignored fonts");
}
else {
print_font_list(fontsToInclude);
}
out.println();
out.print("ignored fonts: ");
if (fontsToIgnore == null) {
out.print("none");
}
else {
print_font_list(fontsToIgnore);
}
out.println();
/*
* 入力ファイルが複数ある場合は、各行にファイル名を付加する
*/
boolean printFilenames = args.length - i > 1;
for (; i < args.length; i += 1) {
font_finder f = new font_finder(args[i], fontsToInclude,
fontsToIgnore, printFilenames);
f.execute();
}
}
else {
usage();
}
}
private static void usage() {
System.err.println("usage: font_finder [ -ignorefonts | "
+ " -includefonts ] ...");
System.exit(1);
}
}