文本pdf转epub的一点体会

文本pdf,指的是相对扫描版的pdf而言,可以拷贝文本。文本pdf又有人称为矢量pdf或者印前pdf。这里不包括那种经过ocr处理的所谓双层pdf,即看上去是扫描的图像,但又可以拷贝文本的那种pdf。

pdf不适合在电纸书上看,因为无法调整字体以及根据屏幕大小自动调整排版,除非是大屏(8吋以上),否则最多只能勉强横着看。所以想把pdf转成epub。

epub实质上是打包的html。所以需要提取出pdf中的文本和图片,然后组合成html,再制作元数据文件.ncx和.opf等,压缩成zip就可以了。

转换的大致步骤可分为:1.提取文本和图片 2.根据章节拆分组合成多个html 3.制作元数据文件 4.打包成zip。

提取pdf的文本有多种方法。1. Acrobat有将pdf另存为txt或html的功能,其它类似的软件如pdf-XChange等也有此功能。2.直接打开pdf,然后Ctrl-A, Ctrl-C 3.用代码实现

这三种方法不能绝对说哪种最好。第1种方法比较方便,但是发现有的pdf转换后会丢失文本,不清楚什么原因,也许跟字体有关。第2种方法也很方便,但是会保留原书的排版比如换行等,需要手工或者编程调整。不过,碰到过一本书,用第1种方法提取文本有缺失,用第2种方法却没有,同样不清楚原因。第3种方法最麻烦,但也最灵活。

使用itextsharp,大致的代码是

PdfReader reader = new PdfReader("foo.pdf");
int pages = reader.NumberOfPages;
var st = new SimpleTextExtractionStrategy();
for (int i = 1; i <= pages ; i++) 
{
          string str = PdfTextExtractor.GetTextFromPage(reader, i, st);//每一页的文本
}

可以根据自己的需要实现ITextExtractionStrategy这个接口(下文会提到),实现特殊的处理。

提取图片,大致也有3种方法。1.Acrobat有将pdf另存为图片的功能。2.其他软件如pdfToy,pdf补丁丁等也有类似功能。3.编程实现。

同样不能说哪种方法一定最好。第1种方法比较方便,但是某些特殊的文本和图片组合成的“图片”难以批量导出(下文会讨论),而且很多图片是不需要的,要手工或者编程删除。第2种方法的处理速度较快,尤其是批量处理时。但是转换图片的选项不如直接用Acrobat丰富。比如Acrobat可以另存为单色的png,而pdfToy和pdf补丁丁都没有这个选项。其他软件没有试过。第3种方法比较麻烦,但是最灵活,可以处理上面说的特殊图片。

处理一本象棋书时,发现棋图不是单纯的图片,而是文本和图片组合成的(用Acrobat的Edit Text & Images就可以发现),怎么将棋图提取出来呢?

也许有人马上会想到截屏,但截屏有个很大的缺点,就是除非是高清显示屏(我还无福消受),否则缺省的分辨率只有96dpi,太低了。另外,还有个坐标的问题。

开始想先确定图片的坐标位置,然后将整个页面另存为图片,然后根据坐标再去抠图。发现每张棋图下固定有“九八七六五四三二一”的文字,而这个字符串只出现在棋图下面,所以确定了这个字符串的坐标,就可以得到棋图的坐标,代码是:

    public class MyTextExtractionStrategy : ITextExtractionStrategy 
    {
        public void BeginTextBlock()
        {
        }

        public void EndTextBlock()
        {
        }    

        public void RenderImage(ImageRenderInfo info)
        {

        }

        StringBuilder sb = new StringBuilder();
        private string preTxt = null;
        private string prepreTxt = null;
        private string fontName = "";
        private float left = 0;
        private float bottom = 0;

        //上面三个是接口的方法,可以留空,下面这个方法返回页面的文本
        public virtual String GetResultantText()
        {
            string result = sb.ToString();
            sb.Length = 0;
            return result;
        }

        public void RenderText(TextRenderInfo info)
        {
            var txt = info.GetText();

            if (txt == "") 
            {
                Vector curBaseline = info.GetBaseline().GetStartPoint();
                Vector topRight = info.GetAscentLine().GetEndPoint();
                iTextSharp.text.Rectangle rect = new iTextSharp.text.Rectangle(curBaseline[Vector.I1], curBaseline[Vector.I2], topRight[Vector.I1], topRight[Vector.I2]);
                left = rect.Bottom;
                bottom = rect.Left;
            }
            else if (txt == "" && preTxt == "" && prepreTxt == "")
            {
                sb.Append(left.ToString() + "," + bottom.ToString() + "|");
            }
            if (prepreTxt == null)
            {
                prepreTxt = txt;
            }
            else
            {
                if (preTxt == null)
                {
                    preTxt = txt;
                }
                else
                {
                    prepreTxt = preTxt;
                    preTxt = txt;
                }
            }
        }
    }    

实际上不需要检测“九八七六五四三二一”,只要检测到“九八七”三个连续的字符,就可以断定上面就是图片,用left和bottom记录图片左下角的坐标x,y。因为每张棋图的长度和宽度都是一样的,所以不需要每次都检测,调试后把值直接写在后续的处理代码里。

但是,这个取得的图片坐标,仅仅是pdf自己定义的坐标,和截图所需要的屏幕坐标完全是两回事。如何转化?查了不少资料,有个软件提供了api,但需要付钱。自己试了下,找不到合适的方法(其实是由于对pdf理解不深)。最后偶然找到pdf的cropbox资料,才豁然开朗。于是先把pdf的每一页提取出来,保存为单独的pdf文件0001.pdf, 0002.pdf, 0003.pdf......,然后用下面的代码截图:

PdfReader reader = new PdfReader("foo.pdf");
int serail = 1;
string fileName = "0001.pdf";
int pages = reader.NumberOfPages; var st = new MyTextExtractionStrategy(); for (int i = 1; i <= pages ; i++) { string str = PdfTextExtractor.GetTextFromPage(reader, i, st); if (!String.IsNullOrWhiteSpace(str))
continue;
string[] coordinates = str.Split('|');
foreach (string item in coordinates)
{
string[] leftBottom = item.Split(',');
                PdfReader reader2 = new PdfReader(fileName);
float left = float.Parse(leftBottom[0]);
float bottom = float.Parse(
leftBottom[1]);
                    PdfRectangle rect = new PdfRectangle(left, bottom - 162.075f, left + 120.627f, bottom);//长度和宽度数据预先调试好的
            PdfDictionary pageDict = reader.GetPageN(1);
                  pageDict.Put(PdfName.CROPBOX, rect);
string serialStr = String.Format("{0:0000}crop.pdf", serial);
PdfStamper stamper = new PdfStamper(reader2, File.Open(serialStr, FileMode.Create, FileAccess.Write));
stamper.Close();
reader2.Close();
serial++;
fileName = String.Format("{0:0000}.pdf", serial);
}
}

得到截图后的pdf,再批量转成图片,就得到了所需要的结果。批量转换使用Acrobat的Action功能,虽然速度很慢,但是可以选择单色png。(待续)

posted @ 2023-02-05 02:44  平静寄居者  阅读(464)  评论(0编辑  收藏  举报