2011年4月30日土曜日

iTextで移動先の設定する方法

PDF間で特定の位置にリンクする場合、移動先名を設定しないとできません。

iTextで作成する場合、PdfDestinationで飛び先の位置を設定を予め作成したあとにそれぞれのページに追加します。 PdfContentByteもしくはPdfWriterのどちらかから追加できます。また一括でAddNamedDestinationsを使って一括に登録することが可能です。

次は新規文書に移動先を設定する例です。

using System;
using System.Drawing;
using iTextSharp.text;
using iTextSharp.text.pdf;
 
namespace ConsoleApplication1
{
    class Program
    {
        const float pt2mm = 72f / 25.4f;
 
        static void Main(string[] args)
        {
            string savePdf = "hogehoge.pdf";
            string loadPdf = "fugafuga.pdf";
 
            // 新規から作成
            string outputPath = @"hogehoge.pdf";
            Document pdoc = new Document(PageSize.A4);
            PdfWriter pw = PdfWriter.GetInstance(pdoc, new FileStream(outputPath, FileMode.Create));

            // 移動先の場所を設定
            PdfDestination dest = new PdfDestination(PdfDestination.XYZ, 105 * pt2mm, 148.5F * pt2mm, 0);
            
            // コンテンツの書き出し
            pdoc.Open();
            PdfContentByte pcb = pw.DirectContent;
            pcb.LocalDestination("name1", dest);        // 移動先名の作成
            Paragraph p = new Paragraph("Hello World");
            pdoc.Add(p);
            pcb.ClosePath();

            pdoc.NewPage();

            pw.AddNamedDestination("name2", 2, dest);   // 移動先名の作成
            pcb.ClosePath();
            pdoc.Close();
        }
    }
}

だが既存のPDFに追加する場合、新規から作成する場合と同じ設定を行ってもバグなのか分からないが追加することはできません。

仕方が無いので直接命令を書き込みます。
移動先の設定はCatalog→Names→Dests→Namesで取得できます。

PdfReader pdf = new PdfReader(@"hogehoge.pdf");
PdfDictionary root = pdf.Catalog;
PdfDictionary pDests = root.GetAsDict(PdfName.NAMES).GetAsDict(PdfName.DESTS);
PdfArray pDestNames = pDests.GetAsArray(PdfName.NAMES);
if (pDestNames == null)
{
    // 移動先の設定がない場合、作成する
    pDests.Put(PdfName.NAMES, new PdfArray());
    pDestNames = pDests.GetAsArray(PdfName.NAMES);
}

配列に移動先名、移動先の設定の順番に書き込みます。

PdfDestination dest = new PdfDestination(PdfDestination.XYZ, 0, 0, 0);
dest.AddPage(pdf.GetPageOrigRef(1));
pDestNames.Add(new PdfString("name1"));
pDestNames.Add(dest);

まとめると次のとおりになります。

using System;
using System.Drawing;
using iTextSharp.text;
using iTextSharp.text.pdf;
 
namespace ConsoleApplication1
{
    class Program
    {
        const float pt2mm = 72f / 25.4f;
 
        static void Main(string[] args)
        {
            string sourcePdf = @"hogehoge.pdf";
            string outputPath = @"fugafuga.pdf";

            PdfReader pdf = new PdfReader(sourcePdf);
            PdfStamper stamper = new PdfStamper(pdf, new FileStream(outputPath, FileMode.Create));

            // 既存PDFにあれこれ追加
            PdfContentByte cb = stamper.GetUnderContent(1);
            cb.BeginText();
            cb.SetFontAndSize(BaseFont.CreateFont(BaseFont.HELVETICA, BaseFont.WINANSI, BaseFont.EMBEDDED), 18);
            cb.ShowTextAligned(Element.ALIGN_LEFT, "Sample", 30, 600, 0);
            cb.EndText();

            // 移動先の生成
            PdfDestination dest = new PdfDestination(PdfDestination.XYZ, 0, 0, 0);
            if (dest.HasPage())
                dest.ArrayList[0] = pdf.GetPageOrigRef(2);
            else
                dest.AddPage(pdf.GetPageOrigRef(2));

            // 移動先を設定
            PdfDictionary root = pdf.Catalog;
            PdfDictionary pDests = root.GetAsDict(PdfName.NAMES).GetAsDict(PdfName.DESTS);
            PdfArray pDestNames = pDests.GetAsArray(PdfName.NAMES);
            if (pDestNames == null)
            {
                pDests.Put(PdfName.NAMES, new PdfArray());
                pDestNames = pDests.GetAsArray(PdfName.NAMES);
            }
            pDestNames.Add(new PdfString("name"));
            pDestNames.Add(dest);

            stamper.Close();
            pdf.Close();
        }
    }
}

2011年4月24日日曜日

FDKサンプルからスクリプトに移植1

FM10のスクリプトはサンプルファイルがほとんど無く、リファレンスガイドすらない状態なので調査も兼ねてFDKサンプルのtextを移植してみました。
移植してみた雑感としては
  • 基本はFDKの仕様をオブジェクト化したという感じ(F_ObjHandleTの数値は各オブジェクトのidプロパティにある)
  • F_Printfに対応する命令がない?(今回はalertでごまかしてある)
  • alert(Javascript)とAlert(FrameMaker)とは違う
  • やっぱりポインタを気にすることないので非常に楽(特に文字列!)
XML読み書きのアプリケーションやDDE、COM、Win32APIを利用したいという場面以外ではFDKを使用するということはなくなるのではないだろうか。
var mMenu = app.GetNamedMenu("!MakerMainMenu");
var nMenu = mMenu.DefineAndAddMenu("APIMenu", "TextApi");

nMenu.DefineAndAddCommand(1, "GetTextCmd", "Get Text","");
nMenu.DefineAndAddCommand(2, "AddTextCmd","Insert Text","\\!GT");
nMenu.DefineAndAddCommand(3, "DeleteTextCmd", "Delete Text", "\\!IT");
nMenu.DefineAndAddCommand(4, "ColorTextCmd", "Color Text", "\\!DT");
nMenu.DefineAndAddCommand(5, "FontSizeCmd", "Change Font Size", "\\!CT");

UpdateMenus();

function Command(command)
{
    var doc = app.ActiveDoc;
    if(doc.id === 0)
        Alert("Please selected Document.", Constants.FF_ALERT_CONTINUE_WARN);

    switch(command)
    {
    /* Get text from selection. */
    case 1: 
        var tr = doc.TextSelection;
        
        if (tr.beg.obj.id === 0 || ((tr.beg.obj.id === tr.end.obj.id) && (tr.beg.offset === tr.end.offset)))
        {
            Alert("Please select some text and try again.", Constants.FF_ALERT_CONTINUE_WARN);
            break;
        }
        var textItems = doc.GetTextForRange(tr, Constants.FTI_String);
        alert(CreateStringFromTextItems(textItems));
        break;
        
    /* Add text. */
    case 2:
        var tr = doc.TextSelection;
        if (tr.beg.obj.id === 0)
        {
            Alert("Please select an insertion point and try again.", Constants.FF_ALERT_CONTINUE_WARN);
            break;
        }
        if (!((tr.beg.obj.id === tr.end.obj.id) && (tr.beg.offset === tr.end.offset)))
        {
            if (Alert("Do you wish to overwrite the selected text?", Constants.FF_ALERT_NO_DEFAULT))
                break;
        }
        doc.AddText(tr.beg, "The new CoffeeTool\011");
        break;

    /* Delete selected text. */
    case 3:
        var tr = doc.TextSelection;
        if (tr.beg.obj.id === 0 || ((tr.beg.obj.id === tr.end.obj.id) && (tr.beg.offset === tr.end.offset)))
        {
            Alert("Please select some text and try again.", Constants.FF_ALERT_CONTINUE_WARN);
            break;
        }

        doc.DeleteText(tr);
        break;

 /* Change the color of selected text to Red. */
 case 4:
        var tr = doc.TextSelection;
        if (tr.beg.obj.id === 0 || ((tr.beg.obj.id === tr.end.obj.id) && (tr.beg.offset === tr.end.offset)))
        {
            Alert("Please select some text and try again.", Constants.FF_ALERT_CONTINUE_WARN);
            break;
        }
        var color = doc.GetNamedObject(Constants.FO_Color, "Red");  // Color name is different in each language.
        var props = doc.GetTextProps(tr.beg);
        var i = GetPropIndex(props, Constants.FP_Color);
        props[i].propVal.obj = color;
        doc.SetTextProps(tr, props);
        break;

    /* Change the font size of the selected text to 30 points. */
    case 5:
        var tr = doc.TextSelection;
        if (tr.beg.obj.id === 0 || ((tr.beg.obj.id === tr.end.obj.id) && (tr.beg.offset === tr.end.offset)))
        {
            Alert("Please select some text and try again.", Constants.FF_ALERT_CONTINUE_WARN);
            break;
        }
        /* Allocate memory for the property list. */
        props = AllocatePropVals(1);
        /* Set up the properties. */
        props[0].propIdent.num = Constants.FP_FontSize;
        props[0].propVal.ival = 30 * 65536;
        props[0].propVal.valType = Constants.FT_Metric;
        /* Apply the property list to the text selection. */
        doc.SetTextProps(tr, props);
        break;
    }
}

/***************************************************************
* Create a string from an TextItems structure.
****************************************************************/
function CreateStringFromTextItems(textItems)
{
    var s = "";
    for (var i = 0; i < textItems.length; i++)
    {
        if (textItems[i].dataType === Constants.FTI_String)
        {
            s += textItems[i].sdata;
        }
    }
    
    return s;
}