一步一步教你写pdf文件

一步一步教你写PDF文件


    PDF作为一种跨平台的文件格式,越来越受到用户的欢迎。现在除了adobe官方提供的pdflib有很多第三方的库可以实现PDF的文件创建、修改、格式转换。PDF文档采用的是二进制和文本混排的方式。近期项目需要,对pdf文件的结构做了一些研究。最终,领导决定采用第三方库,没能用得上。在此,跟大家分享一下,算是抛砖引玉。关于pdf文件的结构网上有很多介绍,在此不在赘述,请参看

       pdf文件可以包含文本、图片、视频、动画、3D数据等,本人就拿最简单的文本举例。那么我们就从“Hello, world!"开始吧。本例,截图如下:

     由于pdf文件的解析中需要依靠交叉引用表,交叉引用表记录的是每一个obj相对于文件始点的位置(以后称“地址”),我们可以写一个函数,求出当前文件的位置。

long getObjLocation(FILE* pFile)
{
     int fseekres = fseek(pFile,0,SEEK_CUR);
     return ftell(pFile);
}

      随着ISO标准的变化,pdf的文件格式也有一些调整。文件开始的地方标明文件对应的版本格式。

 void writeHeader(FILE* pFile,vector<long>& pdfLocation)
{
     fwrite("%PDF-1.7\n\n",1,strlen("%PDF-1.7\n\n"),pFile);

     pdfLocation.push_back(getObjLocation(pFile));

 }

   解析pdf文件时,首先会从入口点开始。

void writeCatalog(FILE* pFile,vector<long>& pdfLocation)

     fwrite("1 0 obj  % entry point\n<<\n  /Type /Catalog\n  /Pages   3 0 R\n>>

               \nendobj\n\n",

            1,

            strlen("1 0 obj  % entry point\n<<\n  /Type /Catalog\n  /Pages 3 0 R\n>>

            \nendobj\n\n"),

            pFile);
      pdfLocation.push_back(getObjLocation(pFile));
}

       pdf可以有很多页,每一页的页面大小、内容可以各不相同,本例中只有一页

void writePages(FILE* pFile,vector<long>& pdfLocation)
{
   fwrite("3 0 obj\n<<\n  /Type /Pages\n  /MediaBox [ 0 0 200 200 ]\n 

            /Count 1\n  /Kids [ 4 0 R ]\n>>\nendobj\n\n",

           1,

           strlen("3 0 obj\n<<\n  /Type /Pages\n  /MediaBox [ 0 0 200 200 ]\n 

           /Count 1\n  /Kids [ 4 0 R ]\n>>\nendobj\n\n"),pFile);
          pdfLocation.push_back(currentLocation(pFile));
}

        创建page。

void writePage(FILE* pFile,vector<long>& pdfLocation)
{
     fwrite("4 0 obj\n<<\n  /Type /Page\n  /Parent 3 0 R\n  /Resources <<\n/Font  <<

               \n/F1 5 0 R \n>>\n  >>\n  /Contents 6 0 R\n>>\nendobj\n\n",

               1,
              strlen("4 0 obj\n<<\n  /Type /Page\n  /Parent 2 0 R\n  /Resources <<

              \n/Font <<\n/F1 5 0 R \n>>\n  >>\n  /Contents 6 0 R\n>>\nendobj\n\n"),

             pFile);
     pdfLocation.push_back(getObjLocation(pFile));
}

     写入文本内容。

void writeContents(FILE* pFile,vector<long>& pdfLocation)
{
    char txtContent[512];
    sprintf_s(txtContent,512,"6 0 obj  %% page content\n<<\n  /Length % d\n>>

     \nstream\nBT\n70 50 TD\n/F1 12 Tf\n(Hello, world!)nET\nendstream\nendobj\n\n",

     strlen("\nBT\n70 50 TD\n/F1 12 Tf\n(Hello, world!) Tj\nET\n"));
    fwrite(txtContent,1,strlen(txtContent),pFile);
    pdfLocation.push_back(getObjLocation(pFile)); 
}

     定义字体。 

void writeFont(FILE* pFile,vector<long>& pdfLocation)
{
    fwrite("5 0 obj\n<<\n  /Type /Font\n  /Subtype /Type1\n  /BaseFont

               /Times- Roman\n>>\nendobj\n\n",

               1,
               strlen("5 0 obj\n<<\n  /Type /Font\n  /Subtype /Type1\n  /BaseFont

               /Times-Roman\n>>\nendobj\n\n"),

             pFile);
    pdfLocation.push_back(getObjLocation(pFile));
}

       创建交叉引用表。习惯上从第0个obj开始,第0个不存在,标记为删除。根据情况交叉引用表可以不止一个。
void writeXref(FILE* pFile,const vector<long> pdfLocation)
{
     char* xrefIndexAndNum = new char[64];
     memset(xrefIndexAndNum,0x0,64);
     sprintf_s(xrefIndexAndNum,64,"xref\n0 %d\n",pdfLocation.size());
     fwrite(xrefIndexAndNum,1,strlen(xrefIndexAndNum),pFile);
    delete [] xrefIndexAndNum;
    xrefIndexAndNum = NULL;
    fwrite("0000000000 65535 f\n",1,strlen("0000000000 65535 f\n"),pFile);
    vector<long>::const_iterator iter = pdfLocation.begin(); 
    for(size_t i = 0;i < pdfLocation.size() -1;i++)
   {
     writeObjXref(pFile,pdfLocation[i]);  
   }
}

//单个obj的引用地址

void writeObjXref(FILE* pFile,long objRef)
{
     char *temp = new char[30];
    sprintf_s(temp,30,"%010d 00000 n \n",objRef); 
    fwrite(temp,1,strlen(temp),pFile); 
    delete [] temp;
    temp = NULL;

     文件尾的写法跟版本的关系不大,指明obj的的个数、入口点、交叉引用表的地址。

void writeTraile(FILE* pFile,const vector<long> pdfLocation)
{
   char* tempArrChar = new char[256];
    sprintf_s(tempArrChar,256,
   "trailer\n<</Size %d/Root 1 0 R>>\nstartxref\n%ld\n%%%%EOF\n",
    pdfLocation.size(),
    pdfLocation[pdfLocation.size() -1]);
    fwrite(tempArrChar,1,strlen(tempArrChar),pFile); 
    delete [] tempArrChar;
    tempArrChar = NULL;
}

     最后调用以上函数,创建一个完整的pdf文件。大家可以用记事本打开生产的pdf文件,可以看到pdf文件的组织形式。

void writePdf(const char *filename)
{
   FILE* pdfFile = NULL;
   fopen_s(&pdfFile,filename,"wb");
   vector<long> pdfLocation;//存储obj的引用地址和交叉引用表的地址
   writeHeader(pdfFile,pdfLocation);  
   writeCatalog(pdfFile,pdfLocation);
   writePages(pdfFile,pdfLocation); 
   writePage(pdfFile,pdfLocation); 
   writeFont(pdfFile,pdfLocation);
   writeContents(pdfFile,pdfLocation);  
   writeXref(pdfFile,pdfLocation);

    writeTraile(pdfFile,pdfLocation); 
    fclose(pdfFile);
   pdfFile = NULL;
}

      目前市面上已经有很多成熟的第三方库,基本能满足大家的需求。鄙人在此抛砖引玉,只是出于帮助大家一起理解pdf文件结构的目的,不鼓励重复造轮子。匆忙写就,如有不当之处,欢迎拍砖。

 

ps:本文采用C++/CLI 托管代码,简单修改可用于C#。为了排版需要,代码中插入了很多空格,请不要简单拷贝运行。

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值