کار با PDF در جاوا: آموزش Apache PDFBox

این مقاله توسط جناب آقای امید نژادعباسی تدوین شده و برای انتشار در اختیار جاواکاپ قرار گرفتهاست.
Apache PDFBox یکی از کتابخانههای متنباز بنیاد Apache است که به زبان جاوا نوشته شده و برای ساخت یا ویرایش فایلهای pdf مورد استفاده قرار میگیرد. با این کتابخانه میتوان کارهایی از قبیل افزودن و استخراج متن و عکس، ویرایش اطلاعات فایل، افزودن کامنت، هایلایت کردن متن, وصل کردن دو یا چند فایل، افزودن یا حذف مجوزها مثل مجوز چاپ کردن فایل، افزودن کد جاوااسکریپت، رمزگذاری فایل، افزودن امضا، تبدیل فایل به عکس و HTML و… را انجام داد.
PDFBox از چهار جز اصلی تشکیلشدهاست که هر کدام در یک فایل jar جداگانه قرار دارند که عبارتند از: PDFBox و FontBox و XmpBox و Preflight.
- بین این بستهها مهمترین آنها PDFBox است که تمام کلاسهای کاربردی برای پردازش pdf در آن قرار دارند.
- FontBox اجازه استفاده از فونت های مختلف را میدهد.
- از XmpBox برای کنترل متادیتای XMP فایلها استفاده میشود.
- با کلاس های Preflight میتوان چک کرد که آیا فایل pdf از استاندارد PDF/A پیروی می کند یا خیر.
در بیشتر مواقع شما فقط به PDFBox و FontBox نیاز دارید که هر دو آنها در فایل pdfbox-app قرار داده شدهاند و میتوانید آنها را از سایت رسمی دانلود کنید.
در این مطلب سعی داریم با چند مثال به طور مختصر با چند تا از کلاسهای این کتابخانه آشنا شویم.
مثال ۱: ساخت صفحه جدید حاوی متن و عکس و اضافه کردن به یک فایل جدید
در این مثال میخواهیم یک فایل pdf بارگذاری کنیم و یک صفحه حاوی یک متن Hello World! دقیقا در وسط و یک لوگوی جاوا در زیر متن به آن اضافه کنیم. خروجی به شکل زیر خواهد بود:
کلاس PDDocument نشان دهنده فایلهای pdf است. می توانید با استفادهاز new از آن شی جدیدی بسازید ولی ما در اینجا از متد استاتیک load همان کلاس استفاده می کنیم تا فایل موجود قبلی را بارگذاری کنیم:
1 | PDDocument pdDocument = PDDocument.load(new File(“file.pdf”)); |
حالا برای اضافه کردن صفحه باید از کلاس PDPage شی بسازیم:
1 2 | PDPage page1 = new PDPage(PDRectangle.A4); pdDocument.addPage(page1); |
اگر از متد سازنده بدون پارامتر یعنی PDFPage() استفاده میکردیم اندازه صفحه ۸٫۵ *۱۱ اینچ میشد که اندازه صفحات نامههای اداری در آمریکا است. در این جا از ثابت PDRectangle.A4 استفاده کردیم که برابر با اندازه صفحه A4 است. سپس با متد add از شی PDDocumentمان، صفحه را به انتهای فایل اضافه میکنیم. جالب است بدانید حتی بعد از اضافه کردن صفحه به فایل می توانید آنرا ویرایش کنید. این قابلیت به شما اجازه میدهد تا صفحههای موجود قبلی را هم ویرایش کنید.
برای جایگذاری متن در صفحات باید با نحوه مختصاتدهی آشنا باشیم. نقطه (۰ و ۰) در هر صفحه گوشه پایین سمت چپ آن صفحه است و برای متن یا تصویر هم نقاط مختصات مکان گوشه پایینی سمت چپ آنها را مشخص میکند. برای پیدا کردن وسط صفحه به اندازه کل صفحه نیاز داریم:
1 2 | float pageHeight = page1.getMediaBox().getUpperRightY(); float pageWidth = page1.getMediaBox().getUpperRightX(); |
متد getMediaBox شی PDRectanlge را برمیگرداند که نشاندهندهٔ کل محدوده فیزیکی صفحه است. بعد متد های *getUpperRight مختصات گوشه راست بالایی را برمیگردانند که همان عرض و طول صفحه هم میباشند. حالا چند متغیر دیگر هم تعریف میکنیم:
1 2 3 | String text = "Hello World!"; PDFont font = PDType1Font.TIMES_ROMAN; float fontSize = 48f; |
همان طور که از اسم متغیر ها معلوم است قرار است text را با فونت TIMES_ROMAN و با سایز ۴۸ که بر حسب ۱/۷۲ اینچ است بنویسیم. برای نوشتن متن در وسط باید عرض نهایی آن را هم بدانیم. هر فونت ممکن است اندازه مختلفی داشته باشد. به خاطر همین در کلاس PDFont یک متد تعریف شده است که می توان با آن عرض رشته را حساب کرد:
1 | float textWidth = font.getStringWidth(text) /1000*(fontSize); |
البته این متد مقدار واقعی عرض را برنمیگرداند بلکه بر حسب یک هزارم هر واحدصفحه یعنی ۱/۷۲ اینچ برمیگرداند. پس ابتدا آن را تقسیم بر ۱۰۰۰ کردیم بعد حاصل ضرب آن با اندازه فونت عرض واقعی را به ما میدهد.
1 | PDPageContentStream contentStream=new PDPageContentStream(pdDocument, page1); |
در مرحله بعد برای وارد کردن محتوا باید یک شی از کلاس PDPageContentStream بسازیم. به متد سازنده آن باید صفحه مورد نظر و فایل pdf که میخواهیم روی آن بنویسیم را پاس کنیم. قبل از نوشتن متن هم باید متد beginText را فرا بخوانیم. بعد فونت مورد استفاده و اندازه آن را تنظیم میکنیم:
1 2 | contentStream.beginText(); contentStream.setFont(font, fontSize); |
کتابخانه PDFBox هیچ امکانی برای تنظیم جای متن مثلا درج متن در وسط صفحه ندارد. حتی اگر متن شما از صفحه خارج شود خط جدیدی ایجاد نمیکند و همه این پیچیدگیها بر عهده برنامهنویس است. حالا برای مختصات متن Hello World! مان به صورت زیر عمل میکنیم:
1 2 | float textX = (pageWidth/2) - (textWidth/2); float textY = (pageHeight/2) - (fontSize/2); |
سعی کنید به این نوع مختصات دهی عادت کنید! حالا که این کار را کردیم یک خط جدید در آن نقطه شروع میکنیم و با متد showText متنمان را مینویسیم و با متد endText به نوشتن متن پایان میدهیم:
1 2 3 | contentStream.newLineAtOffset(textX, textY); contentStream.showText(text); contentStream.endText(); |
حال برای اضافه کردن عکس به صفحه می توان از متد drawImage استفاده کرد که یک شی از جنس PDImageXObject نیاز دارد:
1 2 | PDImageXObject javaLogo=PDImageXObject.createFromFile(("java.png"), pdDocument); float imgSquareSize=200 |
متغیر imgSquareSize را هم برای اندازه عکس استفاده خواهیمکرد چون ممکن است اندازه واقعی عکس خیلی بزرگ باشد. باید از همان روشی برای پیدا کردن وسط صفحه استفاده کنیم که برای متن به کار بردیم. برای محور yها به اندازه imgSquareSize پایین میرویم تا تصویرْ درست زیر متن بیافتد:
1 2 | contentStream.drawImage(javaLogo, (pageWidth/2) - (imgSquareSize/2), textY-imgSquareSize,imgSquareSize, imgSquareSize); contentStream.close(); |
وقتی کارمان با contentStream تمام شد آنرا میبندیم. برای اتمام کار باید فایل را ذخیره کنیم:
1 2 | pdDocument.save("new.pdf"); pdDocument.close(); |
کد کامل مربوط به این برنامه را اینجا می توانید ملاحظه کنید.
مثال ۲: رمز گذاری و ویرایش مجوزهای فایل
همانطور که گفتیم میتوان با PDFBox مجوزهای فایل را تغییر داد. در این مثال سعی داریم مجوز چاپ کردن را از کاربر بگیریم. برای این کار ابتدا با استفاده از متد load فایل را بارگذاری میکنیم:
1 | PDDocument pdDocument = PDDocument.load(new File("new.pdf")); |
کلاس AccessPermission مجوزهای فایل را در خود نگه میدارد. از آن یک شی میسازیم و مجوز پرینت گرفتن آن را لغو میکنیم:
1 2 | AccessPermission access=new AccessPermission(); access.setCanPrint(false); |
ولی ما این مجوز دسترسی را مستقیما به فایل اعمال نمیکنیم بلکه باید آنرا به شی StandardProtectionPolicy که خط مشی استفاده از فایل را مشخص میکند پاس میکنیم. این کلاس فقط یک متد سازنده دارد:
1 | StandardProtectionPolicy policy=new StandardProtectionPolicy("123", "465", access); |
پارامتر اول گذرواژه متعلق به صاحب فایل است. اگر با این گذرواژه فایل را باز کنید تمام مجوز ها را (حتی اگر غیر فعال هم باشند) دارید. پارامتر دوم گذرواژهای است که شما به کاربر فایل میدهید و پارامتر سوم هم شی AccessPermissionمان هست. بعد با متد protect فایل را رمزگذاری میکنیم. سپس باید آنرا ذخیره کنیم:
1 2 | pdDocument.protect(policy); pdDocument.save("new.pdf"); |
اگر سعی کنید این فایل را دوباره با متد load باز کنید باید رمز فایل را هم به عنوان پارامتر پاس کنید.
کد کامل این قسمت را میتوانید در این لینک ببینید.
مثال ۳: وصل کردن چند فایل به هم
وصلکردن فایل در PDFBox به راحتی انجام میشود. در کد زیر فرض کنید متغیر list حاوی تمامی فایلهایی است که می خواهیم به هم وصل کنیم. با اجرای کد زیر فایلی با نام merged ساخته میشود که نتیجه اتصال همه فایلها به همان ترتیبی که در لیست اولیه قرار دارند خواهد بود.
1 2 3 4 5 6 | List<String> filesToMerge=new ArrayList<>(); PDFMergerUtility merger = new PDFMergerUtility(); for(String file:filesToMerge) merger.addSource(file); merger.setDestinationFileName("merged.pdf"); merger.mergeDocuments(); |
سخن نهایی
در این مطلب مثالهایی از کار با PDFBox را دیدیم. این کتابخانه همانطور که دیدید بسیار قدرتمند است و برای بسیار از نیازها پاسخگوی نیاز برنامهنویسان است.
البته کتابخانه های دیگری هم مثل IText برای کار با فایلهای پیدیاف وجود دارد ولی به نظر نویسنده هیچ یک به کاملی PDFBox نیستند. برای مثال با توجه به مجوز استفاده خاص، از iText فقط میتوانید در پروژههای متنباز استفاده کنید. میتوانید برای مقایسه بیشتر بین کتابخانههای کار با فایل PDf جاوا به این مطلب رجوع کنید.
با سلام آیا با این توابع میتوان pdf را تبدیل به ورد کرد؟
نه !
با PdfBox امکان انجام این کار وجود ندارد. برای این کار می توانید از کتاب خانه Apose.PDF استفاده کنید که این کار را با دو خط کد هم برای شما انجام می دهد:
https://docs.aspose.com/pdf/java/convert-pdf-to-doc-and-docx/
برای دیدن مثال های بیشتر از تبدیل بین انواع قالب های دیگر به این مطلب مراجعه کنید:
https://www.baeldung.com/pdf-conversions-java