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 という物を初めて知りました。