今回はstreamの中身を読んで、そこに画面表示するテキストがあったらそれを抜き出す。
PDFでのテキスト描画は以下のような形になっている。
このほかにも何通りかテキスト描画の表記方法があるが、今回はこの形式のみを処理対象とした。
またテキストのエンコーディングもshift-jis決め打ちとして処理している。
BT /F2 20.00 Tf ET //フォント名とフォントサイズ BT 320.88 354.34 Td (その者。のちに・・・) Tj ET //描画位置xyとテキスト
まずは抜き出したテキストを保存するするクラスを作成します。
「プロジェクト」メニューかあら「クラスの追加」を選択し、「PDFPage.cs」というクラスを追加し、以下のようにします。
テキスト情報(フォント名、フォントサイズ、表示座標、テキスト)をリスト化して"ページ"として保持するだけのクラスです。
次にPDFTextReaderにstreamが来たらそれを読んで、情報を抜き出す処理を追加します。
本来であればオブジェクトを1つずつ読み解いてアクセスするべきものですが、何も気にせずに読み込んでいます。
実行すると、List
かなりいい加減ですがこれでPDFからの情報読み込み処理は完了です。
using System.Collections.Generic; namespace Pdf2Pdf { class PDFPosition { public float fX { get { return _fX; } set { _fX = value; } } float _fX = 0; public float fY { get { return _fY; } set { _fY = value; } } float _fY = 0; public PDFPosition() { } public PDFPosition(float x, float y) { _fX = x; _fY = y; } } class PDFText : PDFPosition { public string strText { get { return _strText; } set { _strText = value; } } string _strText = ""; public string strFont { get { return _strFont; } set { _strFont = value; } } string _strFont = ""; public float fPoint { get { return _fPoint; } set { _fPoint = value; } } float _fPoint = 0; public PDFText() { } public PDFText(float x, float y, string strText) : base(x, y) { _strText = strText; } public PDFText(string strFont, float fPoint, float x, float y, string strText) : base(x, y) { _strFont = strFont; _fPoint = fPoint; _strText = strText; } } class PDFPage { public List<object> Items { get { return _Items; } set { _Items = value; } } List<object> _Items = new List<object>(); public PDFPage() { } } }■PDFTextReader.cs
//トレーラー if (strObj == "trailer") { //トレーラーの情報は読み取らない //トレーラーはPDF末尾に配置されるから、これで読み込み終わり break; } } //オブジェクトの読み込み { byte[] data; string strAttribute; bool ret = ReadObject(br, strObj, out strAttribute, out data); if (ret && data != null && data.Length > 0) { //オブジェクトの処理 PDFPage page = new PDFPage(); //コンテンツの中身はテキストだと決めてかかってる。本当は駄目。 //フォントが埋め込まれてたらそのフォントのバイナリに対してまでこれが呼ばれてしまう AnalyzeContents(data, page); //情報があったら、"ページ"として追加する if (page.Items.Count > 0) _listPages.Add(page); } if (ret == false) { Debug.Assert(false); } } } } return true; } public List<PDFPage> Pages { get { return _listPages; } } List<PDFPage> _listPages = new List<PDFPage>(); /// <summary> /// テキスト情報の取り出し /// /// 取り出したテキスト情報はPDFPageへ格納する /// /// shift-jisのみ対応 /// ( ) で囲まれたテキストのみを処理し、¥表記や、<>表記のテキストには対応しない /// </summary> void AnalyzeContents(byte[] pcbContents, PDFPage page) { byte[] data; //エスケープシーケンスを外す //「文章」以外の部分も処理することになるけど気にしない using (MemoryStream ms1 = new MemoryStream(pcbContents)) using (BinaryReader br = new BinaryReader(ms1)) using (MemoryStream ms2 = new MemoryStream()) { while (ms1.Position != ms1.Length) { byte tmp = br.ReadByte(); if (tmp != 0x5c) //「\\」 { ms2.WriteByte(tmp); continue; } tmp = br.ReadByte(); ms2.WriteByte(tmp); } ms2.Flush(); data = ms2.ToArray(); } using (MemoryStream ms = new MemoryStream(data)) using (PDFRawReader pr = new PDFRawReader(ms)) { //"小説家になろう"の縦書きPDFはshift-jis Encoding enc = Encoding.GetEncoding("shift_jis"); string strFontObj = ""; float fFontPoint = 0; while (true) { if (ms.Position == ms.Length) break; string strLine = pr.ReadLine(enc, false); LogOut(strLine); //フォントobject名とフォントサイズの取得 { Regex re = new Regex(@"BT /(.+) ([\d\.]+) Tf", RegexOptions.IgnoreCase); Match m = re.Match(strLine); while (m.Success) { try { strFontObj = m.Groups[1].Value; string strSize = m.Groups[2].Value; fFontPoint = float.Parse(strSize); } catch (Exception) { strFontObj = ""; fFontPoint = 0; } m = m.NextMatch(); } } //テキストの取得 { //表示座標とテキストだけ抜き出す // ( ) で囲まれたテキストのみを処理し、¥表記や、<>表記のテキストには対応しない Regex re = new Regex(@"BT ([\d\.]+) ([\d\.]+) Td \((.+)\) Tj ET", RegexOptions.IgnoreCase); Match m = re.Match(strLine); while (m.Success) { try { string strX = m.Groups[1].Value; string strY = m.Groups[2].Value; string strText = m.Groups[3].Value; float x = float.Parse(strX); float y = float.Parse(strY); PDFText text = new PDFText(strFontObj, fFontPoint, x, y, strText); page.Items.Add(text); } catch (Exception) { } m = m.NextMatch(); } } } } }