반응형



Android에서 PdfBox 라이브러리를 사용하여 이미지와 텍스트를 PDF로 저장하는 예제입니다.


최초작성 - 2019. 2. 23




다음 글에서 작성했던 앱의 내용을 PDF로 저장하도록 수정해보았습니다.


Android ScrollView 예제 - ImageView와 TextView를 함께 스크롤하는 방법

https://webnautes.tistory.com/1303



A4용지 한장 분량이 넘어가는 것은 고려하지 않았습니다.  

다음처럼 A4용지 너비를 넘어가는 긴 한줄의 문장을 여러 줄로 나누어 출력되도록 작성되어 있습니다.

123456789012345 67890123456 7890123 45678 901234567 8901233333333 3456789012 3456789 01234 567890



다음처럼 예제가 동작합니다.

1. 상단에 보이는 SAVE 버튼을 클릭하면 잠시 기다리라는 메시지가 보입니다.


 



2. PDF로 저장완료 된 후 저장된 위치를 알려줍니다. 탐색기에서 해당 경로로 이동하면 저장된 PDF 파일을 확인할 수 있습니다.


   




3. PDF 파일을 열어보면 다음처럼 보입니다.





다음 과정을 통해 예제를 테스트 해볼 수 있습니다.


1. build.gradle에  PdfBox 라이브러리를 추가합니다.  Android API 19 이상을 사용할 것을 권장하고 있습니다.


dependencies {
   implementation fileTree(dir: 'libs', include: ['*.jar'])
   implementation 'com.android.support:appcompat-v7:28.0.0'
   implementation 'com.android.support.constraint:constraint-layout:1.1.3'
   implementation 'com.tom_roush:pdfbox-android:1.8.10.1'
   testImplementation 'junit:junit:4.12'
   androidTestImplementation 'com.android.support.test:runner:1.0.2'
   androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}




2. AndroidManifest.xml 매니페스트 파일에 외부 저장소 접근 권한을 추가합니다.


<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
   package="com.tistory.webnautes.scrollview_test">
   
   <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
   
   <application




3. activity_main.xml 레이아웃 파일에 버튼을 추가합니다. 버튼 클릭시 PDF로 저장하도록 합니다.


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:gravity="center"
   android:orientation="vertical">

   <Button
       android:id="@+id/button_save"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:text="Save" />

   <ScrollView




4. 프로젝트 창을 Project 뷰로 변경하고  main 폴더 아래에 assets 폴더를 생성하고 확장자가 ttf인 폰트를 복사해줍니다.



5. MainActivity.java에 다음처럼 코드를 수정합니다.

pdfbox는 저수준 API라서 수작업으로 좌표를 지정해줘야 합니다. 왼쪽 아래가 (0,0) 입니다.


package com.tistory.webnautes.scrollview_test;

import android.Manifest;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Environment;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import com.tom_roush.pdfbox.pdmodel.PDDocument;
import com.tom_roush.pdfbox.pdmodel.PDPage;
import com.tom_roush.pdfbox.pdmodel.PDPageContentStream;
import com.tom_roush.pdfbox.pdmodel.common.PDRectangle;
import com.tom_roush.pdfbox.pdmodel.font.PDFont;
import com.tom_roush.pdfbox.pdmodel.font.PDType0Font;
import com.tom_roush.pdfbox.pdmodel.graphics.image.LosslessFactory;
import com.tom_roush.pdfbox.pdmodel.graphics.image.PDImageXObject;
import com.tom_roush.pdfbox.util.PDFBoxResourceLoader;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {
   private static final String TAG = "webnautes" ;
   private File root;
   private AssetManager assetManager;
   private ImageView imageView;
   private TextView textView;
   private PDFont font;


   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);

       imageView = (ImageView)findViewById(R.id.imageview);
       textView = (TextView)findViewById(R.id.textview);

       imageView.setImageResource(R.drawable.moon);


       // 테스트용 텍스트를 생성합니다.
       String text = "123456789012345 67890123456 7890123 45678 901234567 8901233333333 3456789012 3456789 01234 567890";
       textView.setText(text);


       Button button_save = (Button) findViewById(R.id.button_save);
       button_save.setOnClickListener(new View.OnClickListener() {
           public void onClick(View v) {
               final SaveTask saveTask = new SaveTask();
               saveTask.execute();
           }
       });

   }

   @Override
   protected void onStart() {
       super.onStart();
       setup();
   }


   private void setup() {

       PDFBoxResourceLoader.init(getApplicationContext());
       root = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
       assetManager = getAssets();

       if (ContextCompat.checkSelfPermission(MainActivity.this,
               Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED)
       {

           ActivityCompat.requestPermissions(MainActivity.this,
                   new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
       }
   }



   public String createPdf() {
       PDDocument document = new PDDocument();
       PDPage page = new PDPage();
       document.addPage(page);


       try{
           font = PDType0Font.load(document, assetManager.open("NanumBarunGothicLight.ttf"));
       }
       catch (IOException e){
           Log.e(TAG, "폰트를 읽을 수 없습니다.", e);
       }

       PDPageContentStream contentStream;

       try {
           contentStream = new PDPageContentStream( document, page, true, true);

           Drawable drawable = imageView.getDrawable();
           Bitmap bitmap = ((BitmapDrawable)drawable).getBitmap();

           int image_width = bitmap.getWidth();
           int image_height = bitmap.getHeight();
           int A4_width = (int) PDRectangle.A4.getWidth();
           int A4_height = (int) PDRectangle.A4.getHeight();

           float scale = (float) (A4_width/(float)image_width*0.8);

           int image_w = (int) (bitmap.getWidth() * scale);
           int image_h = (int) (bitmap.getHeight() * scale);

           Bitmap resized = Bitmap.createScaledBitmap(bitmap, image_w, image_h, true);
           PDImageXObject pdImage = LosslessFactory.createFromImage(document, resized);


           float x_pos = page.getCropBox().getWidth();
           float y_pos = page.getCropBox().getHeight();

           float x_adjusted = (float) (( x_pos - image_w ) * 0.5 + page.getCropBox().getLowerLeftX());
           float y_adjusted = (float) ((y_pos - image_h) * 0.9 + page.getCropBox().getLowerLeftY());

           contentStream.drawImage(pdImage, x_adjusted, y_adjusted, image_w, image_h);


           int text_width = 470;
           int text_left = 70;

           String textN = textView.getText().toString();
           int fontSize = 17;
           float leading = 1.5f * fontSize;

           List<String> lines = new ArrayList<String>();
           int lastSpace = -1;
           for (String text : textN.split("\n"))
           {
               while (text.length() > 0) {
                   int spaceIndex = text.indexOf(' ', lastSpace + 1);
                   if (spaceIndex < 0)
                       spaceIndex = text.length();
                   String subString = text.substring(0, spaceIndex);
                   float size = fontSize * font.getStringWidth(subString) / 1000;
                   if (size > text_width) {
                       if (lastSpace < 0)
                           lastSpace = spaceIndex;
                       subString = text.substring(0, lastSpace);
                       lines.add(subString);
                       text = text.substring(lastSpace).trim();
                       lastSpace = -1;
                   } else if (spaceIndex == text.length()) {
                       lines.add(text);
                       text = "";
                   } else {
                       lastSpace = spaceIndex;
                   }
               }
           }

           contentStream.beginText();
           contentStream.setFont(font, fontSize);
           contentStream.newLineAtOffset(text_left, y_adjusted-20);

           for (String line: lines)
           {
               contentStream.showText(line);
               contentStream.newLineAtOffset(0, -leading);
           }
           contentStream.endText();
           contentStream.close();

           String path = root.getAbsolutePath() + "/테스트.pdf";

           document.save(path);
           document.close();

           return path;

       } catch (IOException e) {
           Log.e(TAG, "Exception thrown while creating PDF", e);
       }

       return "error";
   }


   class SaveTask extends AsyncTask<Void, Void, String> {

       @Override
       protected String doInBackground(Void... voids) {

           String path = createPdf();
           return path;
       }

       @Override
       protected void onPreExecute() {
           super.onPreExecute();

           Toast.makeText(MainActivity.this, "잠시 기다리세요.", Toast.LENGTH_SHORT).show();

       }

       @Override
       protected void onPostExecute(String path) {
           super.onPostExecute(path);

           Toast.makeText(MainActivity.this, path+"에 PDF 파일로 저장했습니다.", Toast.LENGTH_LONG).show();
       }

   }
}





참고

https://github.com/TomRoush/PdfBox-Android


https://stackoverflow.com/questions/49958604/draw-image-at-mid-position-using-pdfbox-java


https://stackoverflow.com/questions/19635275/how-to-generate-multiple-lines-in-pdf-using-apache-pdfbox

반응형

문제 발생시 지나치지 마시고 댓글 남겨주시면 가능한 빨리 답장드립니다.

도움이 되셨다면 토스아이디로 후원해주세요.
https://toss.me/momo2024


제가 쓴 책도 한번 검토해보세요 ^^

+ Recent posts