目的
pdfのフォーマットが公式の資料だけだと読み解けないところがあるので、PDFファイルを作って Adobe Reader でどうなるか見てみたいと思った。
ので、PDFファイルのリファレンステーブルとかめんどうな部分の生成を補助するコードを書いてみた。
参考サイト
indirect objectの中身はほぼこちらを参考にさせていただきました。
https://itchyny.hatenablog.com/entry/2015/09/16/100000
サンプルPDF
※改行コードは \n (Lf) (0x0A) としてASCIIで保存してください。byte位置のテーブル情報を含んでいるため、byteがずれると正常なPDFになりません。念のため%%EOF
の末尾にも改行コードをいれてください。
(今回はASCII文字しか含んでいないので、UTF-8またはShift_JISでも可)
%PDF-1.4
1 0 obj<</Type/Catalog /Pages 2 0 R>>
endobj
2 0 obj<</Type/Pages /Kids [4 0 R] /Count 1>>
endobj
3 0 obj<</Length 58>>stream
1. 0. 0. 1. 50. 720. cm BT /F0 36 Tf (Hello, world!) Tj ET
endstream
endobj
4 0 obj<</Type/Page /Parent 2 0 R /Resources 5 0 R /MediaBox [0 0 595 842] /Contents 3 0 R>>
endobj
5 0 obj<</Font <</F0 <</Type /Font /BaseFont /Times-Roman /Subtype /Type1 >> >> >>
endobj
xref
0 6
0000000000 65535 f
0000000009 00000 n
0000000054 00000 n
0000000107 00000 n
0000000211 00000 n
0000000311 00000 n
trailer
<</Size 6 /Root 1 0 R>>
startxref
401
%%EOF
生成用ソースコード
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
class MyPdfGenerator
{
class IndirectObjInfo
{
public int objId;
public int genId;
public int pos;
public IndirectObjInfo(int oId, int gId, int p)
{
objId = oId;
genId = gId;
pos = p;
}
}
Stream _st;
Dictionary<int,IndirectObjInfo> _indirectObjs;
int _largestObjId;
int _numberOfObjId; // objId = 0 を除いた個数
int _xrefPos;
public MyPdfGenerator(Stream st)
{
_st = st;
_indirectObjs = new Dictionary<int,IndirectObjInfo>();
_largestObjId = -1;
_numberOfObjId = 0;
_xrefPos = -1;
}
void WriteStringToStream(string s)
{
byte[] buf = Encoding.ASCII.GetBytes(s);
_st.Write(buf, 0, buf.Length);
}
public void WriteHeader()
{
if (_st.Position!=0){throw new Exception("Position error.");}
WriteStringToStream("%PDF-1.4\n");
/*
WriteStringToStream("%");
byte[] tmp = new byte[]{0xe2,0xe3,0xcf,0xd3};
_st.Write(tmp,0,tmp.Length);
WriteStringToStream("\n");
*/
}
public void WriteIndirectObj(int objId, string dictPart)
{
WriteIndirectObj(objId, dictPart, null);
}
public void WriteIndirectStreamObj(int objId, string streamData)
{
WriteIndirectStreamObj(objId, Encoding.ASCII.GetBytes(streamData));
}
public void WriteIndirectStreamObj(int objId, byte[] streamData)
{
string dictPart = "<</Length "+ streamData.Length.ToString() +">>";
WriteIndirectObj(objId, dictPart, streamData);
}
void WriteIndirectObj(int objId, string dictPart, byte[] streamData)
{
if (objId<=0) {throw new Exception("ObjId must be non-zero positive number");}
int pos = (int)_st.Position;
StringBuilder sb = new StringBuilder(20+dictPart.Length);
sb.Append(objId.ToString());
sb.Append(" 0 obj");
sb.Append(dictPart);
WriteStringToStream(sb.ToString());
sb.Clear();
if (streamData != null) {
WriteStringToStream("stream\n");
_st.Write(streamData, 0, streamData.Length);
WriteStringToStream("\nendstream");
}
sb.Append("\n");
sb.Append("endobj\n");
WriteStringToStream(sb.ToString());
if (_largestObjId < objId) {
_largestObjId = objId;
}
_indirectObjs.Add(objId, new IndirectObjInfo(objId,0,pos)); // Addメソッドを使用することで、キーが重複すると例外を投げてくれる
_numberOfObjId++;
}
public void WriteXref()
{
if ( _numberOfObjId != _largestObjId ) {
throw new Exception("Failed.");
}
_xrefPos = (int)_st.Position;
StringBuilder sb = new StringBuilder(20+(_numberOfObjId+1)*20);
sb.Append("xref\n");
sb.Append("0 "); sb.Append((_numberOfObjId+1).ToString()); sb.Append("\n");
sb.Append("0000000000 65535 f \n");
for ( int i=1 ; i <= _largestObjId ; i++) {
sb.Append(_indirectObjs[i].pos.ToString("D10"));
sb.Append(" 00000 n \n");
}
WriteStringToStream(sb.ToString());
}
public void WriteTrailer(int rootObjId)
{
StringBuilder sb = new StringBuilder(50);
sb.Append("trailer\n<<");
sb.Append("/Size " + (_numberOfObjId+1).ToString() + " ");
sb.Append("/Root " + rootObjId.ToString()+" 0 R");
sb.Append(">>\n");
WriteStringToStream(sb.ToString());
}
public void WriteStartXref()
{
StringBuilder sb = new StringBuilder(20);
sb.Append("startxref\n");
sb.Append(_xrefPos.ToString());
sb.Append("\n");
WriteStringToStream(sb.ToString());
}
public void WriteEof()
{
WriteStringToStream("%%EOF\n");
}
}
class PdfGenTest
{
[STAThread]
static void Main(string[] args)
{
var st = new FileStream("testout.pdf", FileMode.Create); // over write
var pg = new MyPdfGenerator(st);
pg.WriteHeader();
pg.WriteIndirectObj(1,"<</Type/Catalog /Pages 2 0 R>>"); // [1] Root Object
pg.WriteIndirectObj(2,"<</Type/Pages /Kids [4 0 R] /Count 1>>"); // [2] Pages Object
pg.WriteIndirectStreamObj(3, @"1. 0. 0. 1. 50. 720. cm BT /F0 36 Tf (Hello, world!) Tj ET"); // [3] Contents
pg.WriteIndirectObj(4,"<</Type/Page /Parent 2 0 R /Resources 5 0 R /MediaBox [0 0 595 842] /Contents 3 0 R>>"); // [4] Page Object
pg.WriteIndirectObj(5,"<</Font <</F0 <</Type /Font /BaseFont /Times-Roman /Subtype /Type1 >> >> >>"); // [5] Resources
pg.WriteXref();
pg.WriteTrailer(1);
pg.WriteStartXref();
pg.WriteEof();
}
}