نحوه ذخیره شدن متن در فایلهای PDF
حتما نیاز است پیشنیاز فوق را یکبار مطالعه کنید تا علت خروجیهای متفاوتی را که در ادامه ملاحظه خواهید نمود، بهتر مشخص شوند. همچنین فایل PDF ایی که مورد بررسی قرار خواهد گرفت، همان فایلی است که توسط متد writePdf ذکر شده در پیشنیاز تهیه شده است.
دو کلاس متفاوت برای استخراج متن از فایلهای PDF در iTextSharp وجود دارند:
الف) SimpleTextExtractionStrategy
using System.Diagnostics; using System.IO; using iTextSharp.text; using iTextSharp.text.pdf; using iTextSharp.text.pdf.parser; namespace TestReaders { class Program { private static void readPdf1() { var reader = new PdfReader("test.pdf"); int intPageNum = reader.NumberOfPages; for (int i = 1; i <= intPageNum; i++) { var text = PdfTextExtractor.GetTextFromPage(reader, i, new SimpleTextExtractionStrategy()); File.WriteAllText("page-" + i + "-text.txt", text); } reader.Close(); } static void Main(string[] args) { readPdf1(); } } }
Test ld Wor llo He Hello People
ب) LocationTextExtractionStrategy
همان مثال قبل را درنظر بگیرید، اینبار به شکل زیر:
private static void readPdf2() { var reader = new PdfReader("test.pdf"); int intPageNum = reader.NumberOfPages; for (int i = 1; i <= intPageNum; i++) { var text = PdfTextExtractor.GetTextFromPage(reader, i, new LocationTextExtractionStrategy()); File.WriteAllText("page-" + i + "-text.txt", text); } reader.Close(); }
Test Hello World Hello People
استخراج متون فارسی از فایلهای PDF توسط iTextSharp
روشهای فوق با PDFهای فارسی هم کار میکنند اما خروجی حاصل آن مفهوم نیست و نیاز به پردازش ثانوی دارد. ابتدا مثال زیر را درنظر بگیرید:
static void writePdf2() { using (var document = new Document(PageSize.A4)) { var writer = PdfWriter.GetInstance(document, new FileStream("test.pdf", FileMode.Create)); document.Open(); FontFactory.Register("c:\\windows\\fonts\\tahoma.ttf"); var tahoma = FontFactory.GetFont("tahoma", BaseFont.IDENTITY_H); ColumnText.ShowTextAligned( canvas: writer.DirectContent, alignment: Element.ALIGN_CENTER, phrase: new Phrase("تست میشود", tahoma), x: 100, y: 100, rotation: 0, runDirection: PdfWriter.RUN_DIRECTION_RTL, arabicOptions: 0); } Process.Start("test.pdf"); }
ﺩﻮﺷﻲﻣ ﺖﺴﺗ
private static void readPdf2() { var reader = new PdfReader("test.pdf"); int intPageNum = reader.NumberOfPages; for (int i = 1; i <= intPageNum; i++) { var text = PdfTextExtractor.GetTextFromPage(reader, i, new LocationTextExtractionStrategy()); text = Encoding.UTF8.GetString(Encoding.UTF8.GetBytes(text)); File.WriteAllText("page-" + i + "-text.txt", text, Encoding.UTF8); } reader.Close(); }
ﺩﻮﺷﻲﻣ ﺖﺴﺗ
using System; using System.Collections.Generic; using System.Drawing; using System.Linq; using System.Runtime.InteropServices; using System.Security; namespace TestReaders { [SuppressUnmanagedCodeSecurity] class GdiMethods { [DllImport("GDI32.dll")] public static extern bool DeleteObject(IntPtr hgdiobj); [DllImport("gdi32.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern uint GetCharacterPlacement(IntPtr hdc, string lpString, int nCount, int nMaxExtent, [In, Out] ref GcpResults lpResults, uint dwFlags); [DllImport("GDI32.dll")] public static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj); } [StructLayout(LayoutKind.Sequential)] struct GcpResults { public uint lStructSize; [MarshalAs(UnmanagedType.LPTStr)] public string lpOutString; public IntPtr lpOrder; public IntPtr lpDx; public IntPtr lpCaretPos; public IntPtr lpClass; public IntPtr lpGlyphs; public uint nGlyphs; public int nMaxFit; } public class UnicodeCharacterPlacement { const int GcpReorder = 0x0002; GCHandle _caretPosHandle; GCHandle _classHandle; GCHandle _dxHandle; GCHandle _glyphsHandle; GCHandle _orderHandle; public Font Font { set; get; } public string Apply(string lines) { if (string.IsNullOrWhiteSpace(lines)) return string.Empty; return Apply(lines.Split('\n')).Aggregate((s1, s2) => s1 + s2); } public IEnumerable<string> Apply(IEnumerable<string> lines) { if (Font == null) throw new ArgumentNullException("Font is null."); if (!hasUnicodeText(lines)) return lines; var graphics = Graphics.FromHwnd(IntPtr.Zero); var hdc = graphics.GetHdc(); try { var font = (Font)Font.Clone(); var hFont = font.ToHfont(); var fontObject = GdiMethods.SelectObject(hdc, hFont); try { var results = new List<string>(); foreach (var line in lines) results.Add(modifyCharactersPlacement(line, hdc)); return results; } finally { GdiMethods.DeleteObject(fontObject); GdiMethods.DeleteObject(hFont); font.Dispose(); } } finally { graphics.ReleaseHdc(hdc); graphics.Dispose(); } } void freeResources() { _orderHandle.Free(); _dxHandle.Free(); _caretPosHandle.Free(); _classHandle.Free(); _glyphsHandle.Free(); } static bool hasUnicodeText(IEnumerable<string> lines) { return lines.Any(line => line.Any(chr => chr >= '\u00FF')); } void initializeResources(int textLength) { _orderHandle = GCHandle.Alloc(new int[textLength], GCHandleType.Pinned); _dxHandle = GCHandle.Alloc(new int[textLength], GCHandleType.Pinned); _caretPosHandle = GCHandle.Alloc(new int[textLength], GCHandleType.Pinned); _classHandle = GCHandle.Alloc(new byte[textLength], GCHandleType.Pinned); _glyphsHandle = GCHandle.Alloc(new short[textLength], GCHandleType.Pinned); } string modifyCharactersPlacement(string text, IntPtr hdc) { var textLength = text.Length; initializeResources(textLength); try { var gcpResult = new GcpResults { lStructSize = (uint)Marshal.SizeOf(typeof(GcpResults)), lpOutString = new String('\0', textLength), lpOrder = _orderHandle.AddrOfPinnedObject(), lpDx = _dxHandle.AddrOfPinnedObject(), lpCaretPos = _caretPosHandle.AddrOfPinnedObject(), lpClass = _classHandle.AddrOfPinnedObject(), lpGlyphs = _glyphsHandle.AddrOfPinnedObject(), nGlyphs = (uint)textLength, nMaxFit = 0 }; var result = GdiMethods.GetCharacterPlacement(hdc, text, textLength, 0, ref gcpResult, GcpReorder); return result != 0 ? gcpResult.lpOutString : text; } finally { freeResources(); } } } }
در اینجا برای اصلاح متد readPdf2 خواهیم داشت:
private static void readPdf2() { var reader = new PdfReader("test.pdf"); int intPageNum = reader.NumberOfPages; for (int i = 1; i <= intPageNum; i++) { var text = PdfTextExtractor.GetTextFromPage(reader, i, new LocationTextExtractionStrategy()); text = Encoding.UTF8.GetString(Encoding.UTF8.GetBytes(text)); text = new UnicodeCharacterPlacement { Font = new System.Drawing.Font("Tahoma", 12) }.Apply(text); File.WriteAllText("page-" + i + "-text.txt", text, Encoding.UTF8); } reader.Close(); }
سؤال: آیا این روش با تمام PDFهای فارسی کار میکند؟
پاسخ: خیر! همانطور که در پیشنیاز مطلب جاری عنوان شد، در یک حالت خاص، PDF writer میتواند شماره Glyphها را کاملا عوض کرده و در فایل PDF نهایی ثبت کند. خروجی حاصل در برنامه Adobe reader خوانا است، چون نمایش را بر اساس اطلاعات هندسی Glyphها انجام میدهد؛ اما خروجی متنی آن به نوعی obfuscated است چون مثلا حرف A آن به کاراکتر مرسوم دیگری نگاشت شده است.