目的
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
Image may be NSFW.
Clik here to view.
生成用ソースコード
usingSystem;usingSystem.Collections;usingSystem.Collections.Generic;usingSystem.IO;usingSystem.Text;usingSystem.Text.RegularExpressions;classMyPdfGenerator{classIndirectObjInfo{publicintobjId;publicintgenId;publicintpos;publicIndirectObjInfo(intoId,intgId,intp){objId=oId;genId=gId;pos=p;}}Stream_st;Dictionary<int,IndirectObjInfo>_indirectObjs;int_largestObjId;int_numberOfObjId;// objId = 0 を除いた個数int_xrefPos;publicMyPdfGenerator(Streamst){_st=st;_indirectObjs=newDictionary<int,IndirectObjInfo>();_largestObjId=-1;_numberOfObjId=0;_xrefPos=-1;}voidWriteStringToStream(strings){byte[]buf=Encoding.ASCII.GetBytes(s);_st.Write(buf,0,buf.Length);}publicvoidWriteHeader(){if(_st.Position!=0){thrownewException("Position error.");}WriteStringToStream("%PDF-1.4\n");/*
WriteStringToStream("%");
byte[] tmp = new byte[]{0xe2,0xe3,0xcf,0xd3};
_st.Write(tmp,0,tmp.Length);
WriteStringToStream("\n");
*/}publicvoidWriteIndirectObj(intobjId,stringdictPart){WriteIndirectObj(objId,dictPart,null);}publicvoidWriteIndirectStreamObj(intobjId,stringstreamData){WriteIndirectStreamObj(objId,Encoding.ASCII.GetBytes(streamData));}publicvoidWriteIndirectStreamObj(intobjId,byte[]streamData){stringdictPart="<</Length "+streamData.Length.ToString()+">>";WriteIndirectObj(objId,dictPart,streamData);}voidWriteIndirectObj(intobjId,stringdictPart,byte[]streamData){if(objId<=0){thrownewException("ObjId must be non-zero positive number");}intpos=(int)_st.Position;StringBuildersb=newStringBuilder(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,newIndirectObjInfo(objId,0,pos));// Addメソッドを使用することで、キーが重複すると例外を投げてくれる_numberOfObjId++;}publicvoidWriteXref(){if(_numberOfObjId!=_largestObjId){thrownewException("Failed.");}_xrefPos=(int)_st.Position;StringBuildersb=newStringBuilder(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(inti=1;i<=_largestObjId;i++){sb.Append(_indirectObjs[i].pos.ToString("D10"));sb.Append(" 00000 n \n");}WriteStringToStream(sb.ToString());}publicvoidWriteTrailer(introotObjId){StringBuildersb=newStringBuilder(50);sb.Append("trailer\n<<");sb.Append("/Size "+(_numberOfObjId+1).ToString()+" ");sb.Append("/Root "+rootObjId.ToString()+" 0 R");sb.Append(">>\n");WriteStringToStream(sb.ToString());}publicvoidWriteStartXref(){StringBuildersb=newStringBuilder(20);sb.Append("startxref\n");sb.Append(_xrefPos.ToString());sb.Append("\n");WriteStringToStream(sb.ToString());}publicvoidWriteEof(){WriteStringToStream("%%EOF\n");}}classPdfGenTest{[STAThread]staticvoidMain(string[]args){varst=newFileStream("testout.pdf",FileMode.Create);// over writevarpg=newMyPdfGenerator(st);pg.WriteHeader();pg.WriteIndirectObj(1,"<</Type/Catalog /Pages 2 0 R>>");// [1] Root Objectpg.WriteIndirectObj(2,"<</Type/Pages /Kids [4 0 R] /Count 1>>");// [2] Pages Objectpg.WriteIndirectStreamObj(3,@"1. 0. 0. 1. 50. 720. cm BT /F0 36 Tf (Hello, world!) Tj ET");// [3] Contentspg.WriteIndirectObj(4,"<</Type/Page /Parent 2 0 R /Resources 5 0 R /MediaBox [0 0 595 842] /Contents 3 0 R>>");// [4] Page Objectpg.WriteIndirectObj(5,"<</Font <</F0 <</Type /Font /BaseFont /Times-Roman /Subtype /Type1 >> >> >>");// [5] Resourcespg.WriteXref();pg.WriteTrailer(1);pg.WriteStartXref();pg.WriteEof();}}