2015年9月30日水曜日

PDF の中身をオンデマンド修正する

ちょっと PDF ファイルをオンザフライで修正する案件が起こりました。理由は:

  • Subset だとフォント名が細工されるので、無理矢理訂正したい。
  • 四角形と円形の線が細くて、しかも半分隠れていた。RD を設定、Rect と Border を増加したい。 

その時のノウハウです。

基本的な流れとしては、
  • PdfReader で PDF を読み込みます。
  • 次に PdfReader がメモリー上に保持しているデータ構造を巡回して、必要個所を書き換えます。
  • その結果を PdfCopy で書き込んでもらう、という方針を取ります。。。

iText で大量の PDF をマージする
http://d.hatena.ne.jp/ocs/20110520/1305866658

PdfReader で読み込んだ後、copy.GetImportedPage(reader, i); と、copy.AddPage(page); で、コピーすれば良いようです。

参照が発生する場合は、AddToBody で追加した参照を、PdfReader から読んできたページの辞書にセット。

AP.Put(PdfName.N, copy.AddToBody(objN).IndirectReference);

PdfCopy を継承して細工します。CopyDictionary をオーバーライドすれば、事足りると思います。

  class AltPdfCopy : PdfCopy {
    public AltPdfCopy(Document document, Stream os)
      : base(document, os) {

    }

    protected override PdfDictionary CopyDictionary(PdfDictionary __inp, bool keepStruct, bool directRootKids) {
      var newd = base.CopyDictionary(__inp, keepStruct, directRootKids);
      ...
      return newd;
    }
  }

全部を晒すことはできませんが、フォント名の加工は次のようにしました。

  protected override PdfDictionary CopyDictionary(PdfDictionary __inp, bool keepStruct, bool directRootKids) {
    var newd = base.CopyDictionary(__inp, keepStruct, directRootKids);
    if (newd.GetAsName(PdfName.TYPE) == PdfName.FONTDESCRIPTOR) {
      PdfName FontName = newd.GetAsName(PdfName.FONTNAME);
      String a = enc.GetString(FontName.GetBytes());
      if (Regex.IsMatch(a, "/[A-Z]{6}\\+")) {
        newd.Put(PdfName.FONTNAME, new PdfName(enc.GetBytes("/" + a.Substring(1 + 6 + 1))));
        IsMod |= true;
      }
    }
    ...
  }

2015年9月29日火曜日

埋め込みフォントについて、Adobe Reader に警告を受ける

Adobe Reader に、埋め込みフォント…を抽出できません。一部の文字を正しくひょ宇治できない場合や、印刷できない場合があります、と警告を受けました。

PDFXplorer で見た場合、このような感じです。

PDF の仕様書によると、BaseFont はポストスクリプトフォント名を書かないといけないらしく、明らかに仕様に反した動きに思えます。

しかし、iTextSharp では、Subset フォントに対して、6文字のアルファベットとプラス記号を足すらしく。。。

目下、対策を検討中。

2015年9月19日土曜日

注釈の外観辞書を作成する

外観辞書とは「注釈の外観(見た目)を定義している辞書」とのことです。注釈の数だけ存在します。

具体的には /AP /N の辞書です。

仕様は、
Portable document format — Part 1 - Adobe
  12 Interactive Features
    12.5 Annotations
      12.5.5 Appearance Streams で詳しく触れられています。

PDFXplorer で確認すると、このようになっています:



サンプル PDF はこちら
 
この外観辞書で表現している外観です:


外観辞書がないとどうなる?
  • Adobe Reader などでは裏で作って表示するようです。印刷しても、きちんと印刷されます。
  • PDF 印刷専用ソフトなどでは、印刷されない場合有り ← これが問題

そこで、外観辞書を自作する必要がでてきます。

画像一枚をベタで描画する注釈のサンプルです:
http://itext.2136553.n4.nabble.com/Stamp-annotations-td2150068.html

/// <summary>
/// CreateStampAnnotation
/// </summary>
/// <remarks>
/// How to compute width and height in pt.:
/// 
/// ```cs
/// var w = image.Width * 72f / image.DpiX;
/// var h = image.Height * 72f / image.DpiY;
/// ```
/// 
/// How to use:
/// 
/// ```cs
/// pdfStamper.AddAnnotation(CreateStampAnnotation(...), 1);
/// ```
/// </remarks>
/// <param name="writer">pdfStamper.Writer or such.</param>
/// <param name="annotContents">The body part of annotation tooltip.</param>
/// <param name="stampHintName">Such as "Approved" suitable for file name.</param>
/// <param name="stampAuthor">The bolded headline part of annotation tooltip suitable for author or title.</param>
/// <param name="stampTitle">The invisible title can be confirmed at annotation property.</param>
public static PdfAnnotation CreateStampAnnotation(
    PdfWriter writer,
    string annotContents,
    string stampHintName,
    string stampAuthor,
    string stampTitle,
    iTextSharp.text.Image image,
    iTextSharp.text.Rectangle rect,
    int annotFlags = PdfAnnotation.FLAGS_PRINT
)
{
    var annot = PdfAnnotation.CreateStamp(writer, rect, annotContents, stampHintName);
    {
        {
            // "/N" is a normal appearance should be stored in appearance dictionary.
            var N = PdfAppearance.CreateAppearance(writer, image.Width, image.Height);
            image.SetAbsolutePosition(0, 0);
            N.AddImage(image);
            new PdfStream(N.ToPdf(N.PdfWriter));
            annot.SetAppearance(PdfName.N, N);
        }
        if (stampAuthor != null)
        {
            annot.Title = stampAuthor;
        }
        annot.Name = Guid.NewGuid().ToString();
        annot.Flags = annotFlags;
        if (stampTitle != null)
        {
            annot.Put(new PdfName("Subj"), new PdfString(stampTitle, PdfString.TEXT_UNICODE));
        }
    }
    return annot;
}


CreateAppearance と SetAppearance という物を初めて知りました。

2015年8月10日月曜日

文字列の回転

ColumnText.ShowTextAligned 等に指定する rotation の単位・方向を知らなかったので、テストして動作確認しました。

PDF なので、用紙は左下原点です。


2015年7月9日木曜日

注釈の外観辞書で、透明度を設定できない

ページの方のRESOURCES辞書に入ってしまうので、注釈のコンテンツからは参照できない模様。

注釈のRESOURCES辞書に入れないといけないが、、、そんな方法は用意されていないみたいで、自分で辞書を作って、コンテンツを作文して対応しました。

参考コード、例:
 var a = PdfAnnotation.CreateSquareCircle(wr1, pi.GetPDFRect(tx.Bounds), "", true);

 a.Put(PdfName.IC, pi.GetPdfArray(tx.Fore));//interior color
 a.Flags = PdfAnnotation.FLAGS_PRINT;
 {
  PdfDictionary objAP = new PdfDictionary();
  {
   PdfContentByte wr = new PdfContentByte(wr1);
   var bcFore = pi.GetBaseColor(tx.Fore);
   bcFore = new BaseColor(bcFore.R, bcFore.G, bcFore.B, (int)(255 * 0.3));

   var rc1 = pi.GetPDFRect(tx.Bounds);

   //PdfGState gs1 = new PdfGState();
   //gs1.FillOpacity = 0.3f;

   wr.SaveState();
   //wr.SetGState(gs1);
   wr.InternalBuffer.Append(" /GS1 gs ");
   wr.SetColorFill(bcFore);
   wr.SetLineWidth(0);
   wr.Rectangle(rc1.Left, rc1.Bottom, rc1.Width, rc1.Height);
   wr.Fill();
   wr.RestoreState();

   var bbox = pi.GetPDFRect(tx.Bounds);
   PdfStream objN = new PdfStream(wr.ToPdf(wr.PdfWriter));
   wr.Reset();
   objN.Put(PdfName.TYPE, PdfName.XOBJECT);
   objN.Put(PdfName.SUBTYPE, PdfName.FORM);
   objN.Put(PdfName.FORMTYPE, new PdfNumber(1));
   objN.Put(PdfName.BBOX, new PdfArray(new float[] { bbox.Left, bbox.Bottom, bbox.Right, bbox.Top }));
   {
    PdfDictionary objRes = new PdfDictionary();
    {
     PdfArray objProcSet = new PdfArray();
     {
      objProcSet.Add(PdfName.PDF);
     }
     objRes.Put(PdfName.PROCSET, objProcSet);
    }
    {
     PdfDictionary objExtGState = new PdfDictionary();
     {
      PdfDictionary objGS1 = new PdfDictionary();
      objGS1.Put(PdfName.ca, new PdfNumber(0.3f));

      objExtGState.Put(new PdfName("GS1"), objGS1);
     }
     objRes.Put(PdfName.EXTGSTATE, objExtGState);
    }
    objN.Put(PdfName.RESOURCES, objRes);
   }
   objAP.Put(PdfName.N, wr1.AddToBody(objN).IndirectReference);

  }
  a.Flags = PdfAnnotation.FLAGS_PRINT;
  a.Put(PdfName.AP, objAP);
  a.Put(PdfName.CA, new PdfNumber(0.3f));
 }
 wr1.AddAnnotation(a);

FreeTextCalloutのRD

PDF注釈の「引き出し線ツール」

回転ができます。これがややこしい。。。

RDは、回転した後の矩形で指定します。

[   ←  ↓   →   ↑   ]

+-----------------+
|        | 4      |
|        V        |
|   +---------+ 3 |
|-->|         |<--|
| 1 +---------+   |
|        A        |
|      2 |        |
+-----------------+

[ 1 2 3 4 ] の順で、DeflateRectする量を指定。

アノーテーションの例:

iTextSharp.text.Rectangle 座標系

左下が原点
右が+x
上が+y

new iTextSharp.text.Rectangle(llx, lly, urx, ury);

new iTextSharp.text.Rectangle(左下x, 左下y, 右上x, 右上y);

+y

A
|
|     +------------B
|     |            |
|     |            |
|     |            |
|     A------------+
|
0-------------------------> +x
A = (llx, llyB = (urx, ury)


(lower left X, lower left Y) - (upper right X, upper right Y)


単位はポイント pt です。1pt0.352777m参考

A4 の場合、約 595x842 pt ⇔ 210x297 mm