PDFlib

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

しおりのコピー

1) はじめに

PDFlib 製品の1つである PDFlib+PDIを使用して、既存 PDF ファイルが持つしおりを取得し、新規 PDF ファイルに出力する方法をご紹介します。

2) しおりのコピーの方法

PDFlib+PDI には「しおりをコピーする」機能はありませんが、PDFlib+PDI に内蔵されている pCOS インターフェースを使って既存の PDF ファイルからしおりを取得し、PDFlib の機能で新規 PDF ファイルにしおりを作成することで、しおりのコピーを実現します。

しおりには様々な機能がありますのが、今回対象とするしおりは以下の機能をもつものです。

・ページを表示する
・階層のあるもの
・ページの特定位置を表示するもの

またしおりの文書内の移動には、「ページ番号を使用」する方法と、「移動先を利用」する方法がありますが、今回はどちらも実装しています。

基本的な処理の流れは下記のとおりです。(今回は、Javaを使用して記述しています。)


/*
 *しおりのコピー
 *
 *2019 Copyright (c) infoTek K.K. all rights reserved.
 *
 *必要な製品:PDFlib+PDI/PPS 9
 */

import java.util.HashMap;
import com.pdflib.PDFlibException;
import com.pdflib.pdflib;

class bookmark {
    public static void main(String[] args) {

        pdflib p = null;
        String optlist;

        try {
            /* PDF オブジェクトを生成 */
            p = new pdflib();
            p.set_option("errorpolicy=return");

            /* 新規PDF文書を開く */
            if (p.begin_document("new_bookmark.pdf", "") == -1) {
                throw new Exception("Error: " + p.get_errmsg());
            }

            /* 文書情報を設定 */
            p.set_info("Creator", "bookmark.java");
            p.set_info("Author", "infoTek");
            p.set_info("Title", "しおりのコピー");

            /* 既存 PDF 文書を開く */
            int doc = p.open_pdi_document("bookmark.pdf", "");
            if (doc == -1) {
                throw new Exception("Error: " + p.get_errmsg());
            }

            /* 既存 PDF 文書のぺージ数を pCOS 関数で取得する */
            int endpage = (int)p.pcos_get_number(doc, "length:pages");

            /* 既存 PDF 文書が複数ページがある場合、必要分繰り返す */
            for (int pageno = 1; pageno <= endpage; pageno++) {

                /* 既存 PDF ページを開く */
                int page = p.open_pdi_page(doc, pageno, "");
                if (page == -1) {
                    throw new Exception("Error: " + p.get_errmsg());
                }

                /* 新規 PDF ページをダミーのページサイズで作成する。 */
                p.begin_page_ext(10, 10, "");

                /* 新規 PDF ページ上に既存 PDF ページをインポートし、サイズを調整する */
                p.fit_pdi_page(page, 0, 0, "adjustpage");

                /* 既存 PDF ページを終了する */
                p.close_pdi_page(page);

                /* 新規 PDF ページを終了する*/
                p.end_page_ext("");
            }

            /* 全てのしおりをコピー */
            copy_bookmarks(p, doc);

            /* 既存 PDF 文書を終了する */
            p.close_pdi_document(doc);

            /* 新規 PDF 文書を終了する */
            p.end_document("");
        }
        catch (PDFlibException e) {
            System.err.println("PDFlib exception occurred in hello sample:");
            System.err.println("[" + e.get_errnum() + "] " + e.get_apiname() + ": " + e.get_errmsg());
        }
        catch (Exception e) {
            System.err.println(e);
        }
        finally {
            if (p != null) {
                p.delete();
            }
        }
    }

    public static void copy_bookmarks(pdflib p, int doc) throws PDFlibException {

        /*既存 PDF 文書内の「しおり」の数を pCOS 関数で取得する*/
        int bookmarkcount = (int)p.pcos_get_number(doc, "length:bookmarks");
        if (bookmarkcount == 0) {
            System.out.println("No bookmarks found");
            return;
        }

        /*既存 PDF 文書内の「移動先」の数を pCOS 関数で取得する*/
        int destcount = (int)p.pcos_get_number(doc, "length:names/Dests");

        /*pCOS ID としおりハンドルを紐づけるハッシュマップ*/
        HashMap<Integer, Integer> shiori = new HashMap<Integer, Integer>();

        /*既存 PDF 文書内の「しおり」が複数がある場合、必要分繰り返す*/
        for (int i = 0; i < bookmarkcount; i++) {

            String bookmark_path = "bookmarks[" + i + "]";
            String optlist;
            double y, zoom;

            /*しおりの階層構造を作成する*/
            int level = (int)p.pcos_get_number(doc, bookmark_path + "/level");
            int parent_pcosid = (int)p.pcos_get_number(doc, "pcosid:" + bookmark_path + "/Parent");
            int parent = 0;
            if (level != 0 && shiori.containsKey(parent_pcosid)) {
                parent = shiori.get(parent_pcosid);
            }

            /*「しおり」のページ移動の方法
            /*①ページ番号を使用 or ②移動先を使用 ごとに処理を分ける*/
            /*①:bookmarks 疑似オブジェクトのプロパティ /A/D の型が配列("array")*/
            /*②:bookmarks 疑似オブジェクトのプロパティ /A/D の型が文字列("string")*/
            switch (p.pcos_get_string(doc, "type:" + bookmark_path + "/A/D")) {

                /*ページ番号を使用して移動先を設定する場合*/
                case "array":

                    /*移動先のページ番号を取得*/
                    int dest_page = (int)p.pcos_get_number(doc, bookmark_path + "/destpage");
                    optlist = "open={true} parent={" + parent + "}";

                    /*
                     *・100%表示:names/Dests 疑似オブジェクトのプロパティ /D[1] → FitR
                     *・ページレベルにズーム:names/Dests 疑似オブジェクトのプロパティ /D[1] → Fit
                     *・幅に合わせる:names/Dests 疑似オブジェクトのプロパティ /D[1] → FitH
                     *・描画領域の幅に合わせる:names/Dests 疑似オブジェクトのプロパティ /D[1] → FitBH
                     *・倍率を設定:names/Dests 疑似オブジェクトのプロパティ /D[1] → XYZ
                     */
                    /*画面の表示方法によって、画面表示の y 座標を格納しているプロパティが異なり、
                     *また、destinationに設定する移動先オプションが異なるので、処理を分岐する*/

                    /*画面の表示方法を取得・分岐*/
                    switch (p.pcos_get_string(doc, bookmark_path + "/A/D[1]")) {
                        case "FitR":
                            y = p.pcos_get_number(doc, bookmark_path + "/A/D[5]");
                            if (y < 0) y = 0;
                            optlist += " destination={page={" + dest_page + "} top={" + y + "} zoom={1} type={fixed}}";
                            break;
                        case "Fit":
                            optlist += " destination={page={" + dest_page + "} type={fitwindow}}";
                            break;
                        case "FitH":
                            y = p.pcos_get_number(doc, bookmark_path + "/A/D[2]");
                            if (y < 0) y = 0;
                            optlist += " destination={page={" + dest_page + "} top={" + y + "} type={fitwidth}}";
                            break;
                        case "FitBH":
                            y = p.pcos_get_number(doc, bookmark_path + "/A/D[2]");
                            if (y < 0) y = 0;
                            optlist += " destination={page={" + dest_page + "} top={" + y + "} type={fitvisiblewidth}}";
                            break;
                        case "XYZ":
                            y = p.pcos_get_number(doc, bookmark_path + "/A/D[3]");
                            if (y < 0) y = 0;
                            zoom = p.pcos_get_number(doc, bookmark_path + "/A/D[4]");
                            optlist += " destination={page={" + dest_page + "} top={" + y + "} zoom={" + zoom + "} type={fixed}}";
                            break;
                        default:
                            optlist += " destination={page={" + dest_page + "} type={fitwindow}}";
                    }
                    break;

                    /*移動先を使用して移動先を設定する場合*/
                case "string":

                    String destname;
                    String path = "";

                    /*移動先名と、該当の移動先情報を取得*/
                    destname = p.pcos_get_string(doc, bookmark_path + "/A/D");
                    for (int j = 0; j < destcount; j++) {
                        String names_key = p.pcos_get_string(doc, "names/Dests[" + j + "].key");
                        if (destname.equals(names_key)) {
                            path = "names/Dests[" + j + "]";
                            break;
                        }
                    }

                    /*
                     *・100%表示:names/Dests 疑似オブジェクトのプロパティ /D[1] → FitR
                     *・ページレベルにズーム:names/Dests 疑似オブジェクトのプロパティ /D[1] → Fit
                     *・幅に合わせる:names/Dests 疑似オブジェクトのプロパティ /D[1] → FitH
                     *・描画領域の幅に合わせる:names/Dests 疑似オブジェクトのプロパティ /D[1] → FitBH
                     *・倍率を設定:names/Dests 疑似オブジェクトのプロパティ /D[1] → XYZ
                     */
                    /*画面の表示方法によって、画面表示の y 座標を格納しているプロパティが異なり、
                     *また、destinationに設定する移動先オプションが異なるので、処理を分岐する*/

                    /*ページ番号を取得する*/
                    dest_page = (int)p.pcos_get_number(doc, path + "/destpage");
                    optlist = "page={" + dest_page + "}";

                    /*画面の表示方法を取得・分岐*/
                    switch (p.pcos_get_string(doc, path + "/D[1]")) {
                        case "FitR":
                            y = p.pcos_get_number(doc, path + "/D[5]");
                            if (y < 0) y = 0;
                            optlist += " top={" + y + "} zoom={1} type={fixed}";
                            break;
                        case "Fit":
                            optlist += " type={fitwindow}";
                            break;
                        case "FitH":
                            y = p.pcos_get_number(doc, path + "/D[2]");
                            if (y < 0) y = 0;
                            optlist += " top={" + y + "} type={fitwidth}";
                            break;
                        case "FitBH":
                            y = p.pcos_get_number(doc, path + "/D[2]");
                            if (y < 0) y = 0;
                            optlist += " top={" + y + "} type={fitvisiblewidth}";
                            break;
                        case "XYZ":
                            y = p.pcos_get_number(doc, path + "/D[3]");
                            if (y < 0) y = 0;
                            zoom = p.pcos_get_number(doc, path + "/D[4]");
                            optlist += " top={" + y + "} zoom={" + zoom + "} type={fixed}";
                            break;
                        default:
                            optlist += " type={fitwindow}";
                    }

                    /*名前付き移動先を、新規 PDF 文書のページ上に作成する*/
                    p.add_nameddest(destname, optlist);
                    optlist = "destname={" + destname + "} open={true} parent={" + parent + "}";
                    break;

                default:
                    dest_page = (int)p.pcos_get_number(doc, bookmark_path + "/destpage");
                    optlist = "destination={page={" + dest_page + "}} open={true} parent={" + parent + "}";
            }

            /*「しおり」のタイトルを取得する*/
            String title = p.pcos_get_string(doc, bookmark_path + "/Title");

            /*しおりの作成・しおりハンドルを pCOS ID と紐づけるため取得する*/
            int handle = p.create_bookmark(title, optlist);
            int pcosid = (int) p.pcos_get_number(doc, "pcosid:" + bookmark_path);
            shiori.put(pcosid, handle);
        }
    }
}

3) インタラクティブ要素のコピーの注意点

PDFlib+PDI で既存の PDF ページからインポートできるのは、ページのコンテンツ部分のみです。リンク、しおり、注釈、フォームフィールドなどの、インタラクティブ要素は、インポートされません。

既存 PDF に、どのようなインタラクティブ要素が含まれているか、あらかじめ分かっている場合は、PDFlib+PDI に含まれる pCOS インターフェース の機能により、その情報を取得して PDFlib で新規の PDF に生成することが可能です。

4) pCOS パスによるインタラクティブ要素の取得

PDFlib 製品に含まれている pCOS インターフェースには、pCOSパスと呼ばれる PDF からページコンテンツ以外の属性情報を取得する擬似オブジェクトがあります。これを使用してインタラクティブ要素を取得することができます。

pCOS パスを使用したインタラクティブ要素取得のサンプルプログラムついては、ダウンロードページまたは、PDFlib pCOS サンプル集(クックブック)で Java、PHP のソースを公開していますのでご覧ください。

なお、pCOS パスが用意されていないインタラクティブ要素を取得したい場合、PDFの仕様に沿って取得することはできますが、PDF の仕様に関する知識が必要になります。

技術情報では、PDF の構造を分析し、出力するツール 「PDFinfo」 を公開しています。PDF にどんなインタラクティブ要素が含まれているか知りたい。pCOSパスの用意がないものについて、キーワードとなる情報を取得したい。といった場合にお役立てください。

5) 新規PDF へのインタラクティブ要素の生成

上記で取得したインタラクティブ要素を、PDFlibを使用して新規 PDF 上に生成します。サンプルプログラムについては、 ダウンロードページまたは、 PDFlib サンプル集(クックブック)で Java、PHP のソースを公開していますのでご覧ください。

6) おわりに

PDFlib 製品はご購入前でも、ダウンロードページからダウンロードし、評価版として試用することができます。評価版の状態でも、いくつかの制限を除き製品版と同様にお使いいただけますので、ぜひ PDFlib 製品をお試しください。

関連ページ

  • PDF にしおりの階層を作成

    新規 PDF にいくつかのレベルにネストされたしおりを作成するサンプルプログラムです。

  • PDF からしおりを抽出

    pCOS インターフェースで既存 PDF から全てのしおりを抽出するサンプルプログラムです。

  • PDFlib+PDI と TET を使用したしおりの作成

    既存 PDF を取り込み、新規でPDF を生成します。その際既存 PDF から、指定したフォントとフォントサイズでテキストを抽出し、それをもとに新しいしおりを生成します。

(Jul 03, 2019 - Jun 23, 2021)