PDFlib

高度なPDFアプリケーションの開発を支援する定番プログラムライブラリー Supported by インフォテック株式会社

PDFlib TET サンプル集(クックブック)

本サンプルプログラムは、PDF テキスト抽出ライブラリーの実装である TET の基本的な機能を実際のプログラムで紹介したものです。

本サイトでダウンロードした TET は、一部機能の制限を除き、評価版として無償でお使いいただけます。

フォントの検索

PDFlib TET で、特定のフォントが PDF 文書のどこに使われているかを表示するサンプルプログラムです。ページ番号、位置、最初に見つかったテキストの最初の部分を表示します。ファイルが複数ある場合は、ページ番号の前にファイル名が表示されます。

フォントリストは、カンマで区切られたフォント名のリストです。-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 文書


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);
    }
}
(May 6, 2010 - June 11, 2015)