PDFlib

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

リンクのコピー

1) はじめに

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

なお PDFlib 10 ではページ上のインタラクティブ要素の取り込みに対応したため、リンクを維持したままページを取り込むことができます。

2) リンクのコピーの方法

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

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

・同文書内に移動するリンク
・URL に移動するリンク

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

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


/*
 * リンク注釈のコピー
 *
 * 2021 Copyright (c) infoTek K.K. all rights reserved.
 *
 * 必要な製品:PDFlib+PDI/PPS 9
 */

import com.pdflib.PDFlibException;
import com.pdflib.pdflib;

class linkannots {
    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_linkannots.pdf", "") == -1) {
                throw new Exception("Error: " + p.get_errmsg());
            }

            /* 文書情報を設定 */
            p.set_info("Creator", "annotation.java");
            p.set_info("Author", "infoTek");
            p.set_info("Title", "リンクのコピー");

            /* 既存 PDF 文書を開く */
            int doc = p.open_pdi_document("linkannots.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");

                /* ページ上のリンク注釈をコピーする */
                copy_linkannots(p, doc, pageno);

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

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

            /* 既存 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_linkannots(pdflib p, int doc, int pageno) throws PDFlibException {

        /* pageno ページ上の「注釈」の数を pCOS 関数で取得する */
        String pagepath = "pages[" + (pageno - 1) + "]";
        int annotscount = (int)p.pcos_get_number(doc, "length:" + pagepath + "/annots");
        if (annotscount == 0) {
            return;
        }

        /* 既存 PDF 文書内の「注釈」分だけ繰り返す */
        for (int i = 0; i < annotscount; i++) {

            String path = pagepath + "/annots[" + i + "]";

            /* 注釈のうち、Subtype が "Link" のもののみを対象にする */
            if (!(p.pcos_get_string(doc, path + "/Subtype").equals("Link"))) {
                continue;
            }

            String optlist;

            /* リンク先の取得
             * リンク注釈は Action (/A) または Dest (/Dest) のいずれかを持っており、
             * /A エントリがある場合は Action を実行し、ない場合は /Dest エントリの内容に移動する
             */
            double pcosid = p.pcos_get_number(doc, "pcosid:" + path + "/A");
            if (pcosid != -1) {

                /* Action を実行
                 * 本サンプルでは GoTo, URI のみをサポートする
                 */
                int action;
                switch (p.pcos_get_string(doc, path + "/A/S")) {
                    case "GoTo":
                        /* 同文書内に移動するアクションを実行する
                         * Dest パスは "/A/D"
                         */
                        optlist = create_dest(p, doc, path, "/A/D");
                        action = p.create_action("GoTo", optlist);
                        optlist = "action={activate={" + action + "}}";
                        break;
                    case "URI":
                        // ウェブページへの移動
                        String url = p.pcos_get_string(doc, path + "/A/URI");
                        optlist = "url={" + url + "}";
                        action = p.create_action("URI", optlist);
                        optlist = "action={activate={" + action + "}}";
                        break;
                    default:
                        // 本サンプルでは扱わない
                        continue;
                }
            } else {
                /* Dest 先に移動する
                 * Dest パスは "/Dest"
                 */
                optlist = create_dest(p, doc, path, "/Dest");
            }

            /* リンクの座標を取得 */
            double llx = p.pcos_get_number(doc, path + "/Rect[0]");
            double lly = p.pcos_get_number(doc, path + "/Rect[1]");
            double urx = p.pcos_get_number(doc, path + "/Rect[2]");
            double ury = p.pcos_get_number(doc, path + "/Rect[3]");

            /* Link 注釈を作成する */
            optlist += " linewidth={0}";
            p.create_annotation(llx, lly, urx, ury, "Link", optlist);
        }
    }

    public static String create_dest(pdflib p, int doc, String path, String subpath) throws PDFlibException {

        // 注釈の移動先ページを取得
        int destpage = (int) p.pcos_get_number(doc,  path + "/destpage");

        String optlist, destoptlist = "";

        /* 移動先の型ごとに処理を分ける
         * 配列("array") の場合、指定された移動先に移動する
         * 文字列("string") の場合、指定された名前付き移動先を作成して移動する
         */
        switch (p.pcos_get_string(doc, "type:" + path + subpath)) {

            /* 指定された移動先に移動する場合 */
            case "array":

                // 指定された移動先を optlist で再現
                destoptlist = parse_dest(p, doc, path + subpath);
                destoptlist += " page={" + destpage + "}";
                optlist = "destination={" + destoptlist + "}";
                break;

            /* 指定された名前付き移動先を作成して移動する場合 */
            case "string":

                /* 移動先名と、該当の移動先情報を取得 */
                String destpath = "";
                String destname = p.pcos_get_string(doc, path + subpath);
                int destcount = (int) p.pcos_get_number(doc, "length:names/Dests");
                for (int i = 0; i < destcount; i++) {
                    String names_key = p.pcos_get_string(doc, "names/Dests[" + i + "].key");
                    if (destname.equals(names_key)) {
                        destpath = "names/Dests[" + i + "]/D";
                        break;
                    }
                }
                /* 名前付き移動先を、新規 PDF 文書のページ上に作成する */
                destoptlist = parse_dest(p, doc, destpath);
                destoptlist += " page={" + destpage + "}";
                p.add_nameddest(destname, destoptlist);
                // 作成した名前付き移動先に移動するよう設定
                optlist = "destname={" + destname + "}";
                break;

            default:

                optlist = "destination={page={" + destpage + "}}";
        }

        return optlist;
    }

    public static String parse_dest(pdflib p, int doc, String path) throws PDFlibException {

        String optlist, type;
        double top, bottom, right, left, zoom;

        /* 移動先配列を解析し、PDFlib で必要なオプションに変換する
         *
         * [0] はページオブジェクトへの参照ですが、今回は使用しない
         * [1] はページの表示方法を表す文字列で、これにより配列のサイズが異なる
         * ・XYZ: left, top, zoom に従った固定されたページ表示 ([page /XYZ left top zoom])
         * ・Fit: ページ全体が入るように表示 ([page /Fit])
         * ・FitH: ページ幅に合わせて表示 ([page /FitH top])
         * ・FitV: ページ高に合わせて表示 ([page /FitV left])
         * ・FitR: 指定された四角形に合わせて表示 ([page /FitR left bottom right top])
         * ・FitB: ページ領域 (ArtBox) に合わせて表示 ([page /FitB])
         * ・FitBH: ページ領域 (ArtBox) の高さに合わせて表示 ([page /FitBH top])
         * ・FitBW: ページ領域 (ArtBox) の幅に合わせて表示 ([page /FitBW left])
         *
         * 画面の表示方法によって、画面表示の y 座標を格納しているプロパティが異なり、
         * また、destinationに設定する移動先オプションが異なるので、処理を分岐する
         */

        /* 画面の表示方法を取得・分岐 */
        switch (p.pcos_get_string(doc, path + "[1]")) {
            case "XYZ":
                optlist = "type={fixed}";
                type = p.pcos_get_string(doc, "type:" + path + "[2]");
                if (type.equals("number")) {
                    left = p.pcos_get_number(doc, path + "[2]");
                    if (left < 0) left = 0.0;
                    optlist += " left={" + left + "}";
                }
                type = p.pcos_get_string(doc, "type:" + path + "[3]");
                if (type.equals("number")) {
                    top = p.pcos_get_number(doc, path + "[3]");
                    if (top < 0) top = 0.0;
                    optlist += " top={" + top + "}";
                }
                type = p.pcos_get_string(doc, "type:" + path + "[4]");
                if (type.equals("number")) {
                    zoom = p.pcos_get_number(doc, path + "[4]");
                    optlist += " zoom={" + zoom + "}";
                }
                break;
            case "Fit":
                optlist = "type={fitwindow}";
                break;
            case "FitH":
                optlist = "type={fitwidth}";
                type = p.pcos_get_string(doc, "type:" + path + "[2]");
                if (type.equals("number")) {
                    top = p.pcos_get_number(doc, path + "[2]");
                    if (top < 0) top = 0.0;
                    optlist += " top={" + top + "}";
                }
                break;
            case "FitV":
                optlist = "type={fitheight}";
                type = p.pcos_get_string(doc, "type:" + path + "[2]");
                if (type.equals("number")) {
                    left = p.pcos_get_number(doc, path + "[2]");
                    if (left < 0) left = 0.0;
                    optlist += " top={" + left + "}";
                }
                break;
            case "FitR":
                optlist = "type={fitrect}";
                type = p.pcos_get_string(doc, "type:" + path + "[2]");
                if (type.equals("number")) {
                    left = p.pcos_get_number(doc, path + "[2]");
                    if (left < 0) left = 0.0;
                    optlist += " left={" + left + "}";
                }
                type = p.pcos_get_string(doc, "type:" + path + "[3]");
                if (type.equals("number")) {
                    bottom = p.pcos_get_number(doc, path + "[3]");
                    if (bottom < 0) bottom = 0.0;
                    optlist += " bottom={" + bottom + "}";
                }
                type = p.pcos_get_string(doc, "type:" + path + "[4]");
                if (type.equals("number")) {
                    right = p.pcos_get_number(doc, path + "[4]");
                    if (right < 0) right = 0.0;
                    optlist += " right={" + right + "}";
                }
                type = p.pcos_get_string(doc, "type:" + path + "[5]");
                if (type.equals("number")) {
                    top = p.pcos_get_number(doc, path + "[5]");
                    if (top < 0) top = 0.0;
                    optlist += " top={" + top + "}";
                }
                break;
            case "FitB":
                optlist = "type={fitvisible}";
                break;
            case "FitBH":
                optlist = "type={fitvisiblewidth}";
                type = p.pcos_get_string(doc, "type:" + path + "[2]");
                if (type.equals("number")) {
                    top = p.pcos_get_number(doc, path + "[2]");
                    if (top < 0) top = 0.0;
                    optlist += " top={" + top + "}";
                }
                break;
            case "FitBW":
                optlist = "type={fitvisibleheight}";
                type = p.pcos_get_string(doc, "type:" + path + "[2]");
                if (type.equals("number")) {
                    left = p.pcos_get_number(doc, path + "[2]");
                    if (left < 0) left = 0.0;
                    optlist += " left={" + left + "}";
                }
                break;
            default:
                optlist = "";
        }

        return optlist;
    }
}

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 製品をお試しください。

関連ページ

  • リンク注釈

    ページ上にリンク注釈を作成するサンプルです。

  • Web リンク

    ページ上に Web リンクを作成するサンプルです。

  • リンク先

    pCOS インターフェースで、PDF 文書に含まれるリンクとその移動先を表示するサンプルです。

(Jul 13, 2021 - Dec 6, 2021)