Sibainu Relax Room

愛犬の柴犬とともに過ごす部屋

Android PDFの編集・保存・印刷

今日の柴犬です。最近の暑さでちょっとバテ気味です。ですが、藤の白い花の下で涼んで気持ちよさそうな顔しています。

WEBのみでは断片的で覚えにくいので最初に購入した Kotlin の本です。

今日のヌートリアです。最近1匹しか目にしません。駆除・襲われたのか分かりませんが心配です。

概要

タブレットから縦長の細長いシール紙に縦方向に横書きの PDF が作成できる。

その印刷ができる。

印刷する PDF がタブレットに表示できて印刷の内容が事前に確認できる。

タブレットから入力した文字が印刷できる。

そんなアプリを考えてみて形になりましたので記録します。

pdfFactory クラス

copy

    private class pdfFactory {

        File _file;
        public pdfFactory (){
            Log.d("pdfFactory","コンストラクタ0");
        }

        public pdfFactory (File file){
            Log.d("pdfFactory","コンストラクタ1");
            _file = file;
        }

        // メッソド
        public void generatePdf() {
            Context context = MainActivity.this;
            // PDFドキュメント生成用のクラスを作成
            PdfDocument pdfDocument = new PdfDocument();
            Resources r;
            PdfDocument.Page page;
            Canvas canvas;
            Matrix matrix = new Matrix();
            Paint text = new Paint();
            Paint paint1 = new Paint();
            Paint paint2 = new Paint();
            Paint paint3 = new Paint();
            int pages = 0;
            r=getResources();

            // A4横(72dpi)
            int a4X = 595;
            // A4縦(72dpi)
            int a4Y = 842;
            // roll1201横29mm(72dpi)
            int roll1201X=85;
            // roll1201縦90mm(72dpi)
            int roll1201Y=264;
            // 指定の縦横幅(ピクセル)でページを作る
            page = pdfDocument.startPage(new PdfDocument.PageInfo.Builder(roll1201X, roll1201Y, pages).create());
            // ページに書き込むためのキャンバスを取得する
            canvas = page.getCanvas();

            // 描画の座標系を左上を中心に右回り90度回転します
            canvas.rotate(90);
            // 描画の座標系の原点を移動します
            canvas.translate(0, -85);
            // ビットマップを描画します
            Bitmap bitmap = BitmapFactory.decodeResource(r, R.drawable.sample);
            int width = bitmap.getWidth();
            int height = bitmap.getHeight();
            canvas.drawBitmap(bitmap, new Rect(0, 0, width, height),
                new Rect(0, 0, width / 4, height / 4), null);

            // 文字を書き込む
            text.setTypeface(Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD_ITALIC));
            text.setColor(context.getColor(R.color.theme500));
            text.setTextSize(5);
            text.setTextAlign(Paint.Align.LEFT);
            canvas.drawText("テスト!!テスト!!!",85, 10, text);

            paint1.setTypeface(Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD_ITALIC));
            paint1.setColor(context.getColor(R.color.theme600));
            paint1.setTextSize(5);
            paint1.setTextAlign(Paint.Align.LEFT);
            String buf1 = tvTellop1.getText().toString() + ":" + etInput1.getText().toString();
            canvas.drawText(buf1,10, 12, paint1);

            paint2.setTypeface(Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD_ITALIC));
            paint2.setColor(context.getColor(R.color.theme700));
            paint2.setTextSize(5);
            paint2.setTextAlign(Paint.Align.LEFT);
            String buf2 = tvTellop2.getText().toString() + ":" + etInput2.getText().toString();
            // 描画の範囲は、x方向は 0 ⇒ roll1201Y y方向は 0 ⇒ roll1201X
            canvas.drawText(buf2,(roll1201Y - (int) paint2.measureText(buf2))/ 2, 30, paint2);

            paint3.setTypeface(Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD_ITALIC));
            paint3.setColor(context.getColor(R.color.theme800));
            paint3.setTextSize(5);
            paint3.setTextAlign(Paint.Align.LEFT);
            String buf3 = tvTellop3.getText().toString() + ":" + etInput3.getText().toString();
            // 描画の範囲は、x方向は 0 ⇒ roll1201Y y方向は 0 ⇒ roll1201X
            canvas.drawText(buf3, roll1201Y- (int) paint3.measureText(buf3), 60, paint3);

            // ページを閉じる
            pdfDocument.finishPage(page);
            try {
                // ファイルを開いてPDFを書き込む
                // 次の指定は不可  File path = Environment.getExternalStoragePublicDirectory(
                //              Environment.DIRECTORY_DOCUMENTS);
                //この保存先 /storage/emulated/0/Android/data/org.sibainu.relax.room.last1/files
                File path = getExternalFilesDir(null);
                path.mkdirs();
                _file = new File(path, getPicFileName());
                pdfDocument.writeTo(new FileOutputStream(_file));

                // PDF の描画
                drawPDF();
            } catch (IOException e) {
                e.printStackTrace();
            }
            pdfDocument.close();
        }

        protected String getPicFileName(){
            Calendar c = Calendar.getInstance();
            String s = c.get(Calendar.YEAR)
                + "_" + (c.get(Calendar.MONTH)+1)
                + "_" + c.get(Calendar.DAY_OF_MONTH)
                + "_" + c.get(Calendar.HOUR_OF_DAY)
                + "_" + c.get(Calendar.MINUTE)
                + "_" + c.get(Calendar.SECOND)
                + ".pdf";
            return s;
        }

        // メソッド
        public void drawPDF() throws RuntimeException {
            if (_file == null) {
                return;
            }

            try {
                // pdfを読み込み、1ページ目を取得
                ParcelFileDescriptor fd =
                    ParcelFileDescriptor.open(_file,
                                              ParcelFileDescriptor.MODE_READ_ONLY);
                PdfRenderer renderer = new PdfRenderer(fd);
                PdfRenderer.Page page = renderer.openPage(0);
                try {
                    ImageView view = findViewById(R.id.image);
                    // Bitmap の270度回転しますので描画の縦横を交換します
                    int drawHeight= view.getWidth();
                    int drawWidth = view.getHeight();
                    Log.d("test", "drawWidth=" + drawWidth +
                                       ", drawHeight=" + drawHeight);

                    float pdfWidth = page.getWidth();
                    float pdfHeight = page.getHeight();
                    Log.d("test", "pdfWidth=" + pdfWidth +
                                       ", pdfHeight=" + pdfHeight);

                    // PDFのサイズを基本にした縦横比を計算
                    float wRatio = drawWidth / pdfWidth;
                    float hRatio = drawHeight / pdfHeight;
                    Log.d("test", "wRatio=" + wRatio +
                                       ", hRatio=" + hRatio);

                    // Bitmap の最大サイズを計算します
                    if (wRatio <= hRatio) {
                        drawHeight = (int) Math.ceil(pdfHeight * wRatio);
                    } else {
                        drawWidth = (int) Math.ceil(pdfWidth * hRatio);
                    }
                    Log.d("test", "drawWidth=" + drawWidth +
                                      ", drawHeight=" + drawHeight);

                    // Bitmap生成して描画
                    Bitmap bitmap = Bitmap.createBitmap(drawWidth,
                                                    drawHeight,
                                                    Bitmap.Config.ARGB_8888);
                    page.render(bitmap, new Rect(0,
                                             0,
                                             drawWidth,
                                             drawHeight),
                                             null,
                                             PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY);

                    view.setImageBitmap(rotateBitmap(bitmap, 270));

                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    try {
                        if (fd != null) {
                            fd.close();
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    if (page != null) {
                        page.close();
                    }
                    if (renderer != null) {
                        renderer.close();
                    }
                }
            } catch(FileNotFoundException e) {
                Log.d("drawPDF","ファイルが開けられません");
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

        private Bitmap rotateBitmap(Bitmap targetBmp, int degrees) {
            int w = targetBmp.getWidth();
            int h = targetBmp.getHeight();

            Matrix mat = new Matrix();
            mat.setRotate(degrees);
            Bitmap responseBmp = Bitmap.createBitmap(targetBmp,0,0, w, h, mat,false);

            return responseBmp;
        }

    }

コンストラクタ

引数がないのと、引数の File があるのとを作ってみました。

        File _file;
        public pdfFactory (){
            Log.d("pdfFactory","コンストラクタ0");
        }

        public pdfFactory (File file){
            Log.d("pdfFactory","コンストラクタ1");
            _file = file;
        }

ボタンのクリック時のリスナーは次のようにしています。

    private class ClickListener implements View.OnClickListener {
        @Override
        public void onClick(View view) {

            //-----省略-----

            } else if (objid == R.id.button) {
                Log.d("generatePdf","onClock");
                pdfFactory pdffactory = new pdfFactory();
                pdffactory.generatePdf();
            }

generatePdf メソッド

Canvas を使って PDF の作成とファイル保存をしています。

保存先は getExternalFilesDir(null) として具体的な場所は /storage/emulated/0/Android/data/org.sibainu.relax.room.last1「アプリの名前空間」/files です。共有ではありませんので探すのに苦労するかもしれません。

文字列の方向は横、紙が縦長で縦方向ので変換が必要です。この例ですと、90mm×29mmの縦長のシール紙に長い方向で横書きをしようとしていますので、座標系を90度回転させて、原点を移動させています。

            // 描画の座標系を左上を中心に右回り90度回転します
            canvas.rotate(90);
            // 描画の座標系の原点を移動します
            canvas.translate(0, -85);

青色がとりあえず描く領域で、橙色のシート紙になる領域があり青色と橙色が一致するように変換させるというイメージです。

その変換は、最初は1の状態ですので青●の原点を中心に90度回転させます。すると2のようになり赤●が移動の目安になります。

移動の方向は、90度回転していますので y軸 のマイナス方向です。

赤●を青●に重なるように赤●を原点に移動(3のイメージ)させます。最終4の状態になります。

これは、移動のアルゴリズムですので描く前にしなければなりません。以降、青色の領域に書いた文字などは移動した後の状態で橙色に描かれます。

copy

       public void generatePdf() {
            Context context = MainActivity.this;
            // PDFドキュメント生成用のクラスを作成
            PdfDocument pdfDocument = new PdfDocument();
            Resources r;
            PdfDocument.Page page;
            Canvas canvas;
            Matrix matrix = new Matrix();
            Paint text = new Paint();
            Paint paint1 = new Paint();
            Paint paint2 = new Paint();
            Paint paint3 = new Paint();
            int pages = 0;
            r=getResources();

            // A4横(72dpi)
            int a4X = 595;
            // A4縦(72dpi)
            int a4Y = 842;
            // roll1201横29mm(72dpi)
            int roll1201X=85;
            // roll1201縦90mm(72dpi)
            int roll1201Y=264;
            // 指定の縦横幅(ピクセル)でページを作る
            page = pdfDocument.startPage(new PdfDocument.PageInfo.Builder(roll1201X, roll1201Y, pages).create());
            // ページに書き込むためのキャンバスを取得する
            canvas = page.getCanvas();

            // 描画の座標系を左上を中心に右回り90度回転します
            canvas.rotate(90);
            // 描画の座標系の原点を移動します
            canvas.translate(0, -85);
            // ビットマップを描画します
            Bitmap bitmap = BitmapFactory.decodeResource(r, R.drawable.sample);
            int width = bitmap.getWidth();
            int height = bitmap.getHeight();
            canvas.drawBitmap(bitmap, new Rect(0, 0, width, height),
                new Rect(0, 0, width / 4, height / 4), null);

            // 文字を書き込む
            text.setTypeface(Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD_ITALIC));
            text.setColor(context.getColor(R.color.theme500));
            text.setTextSize(5);
            text.setTextAlign(Paint.Align.LEFT);
            canvas.drawText("テスト!!テスト!!!",85, 10, text);

            paint1.setTypeface(Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD_ITALIC));
            paint1.setColor(context.getColor(R.color.theme600));
            paint1.setTextSize(5);
            paint1.setTextAlign(Paint.Align.LEFT);
            String buf1 = tvTellop1.getText().toString() + ":" + etInput1.getText().toString();
            canvas.drawText(buf1,10, 12, paint1);

            paint2.setTypeface(Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD_ITALIC));
            paint2.setColor(context.getColor(R.color.theme700));
            paint2.setTextSize(5);
            paint2.setTextAlign(Paint.Align.LEFT);
            String buf2 = tvTellop2.getText().toString() + ":" + etInput2.getText().toString();
            // 描画の範囲は、x方向は 0 ⇒ roll1201Y y方向は 0 ⇒ roll1201X
            canvas.drawText(buf2,(roll1201Y - (int) paint2.measureText(buf2))/ 2, 30, paint2);

            paint3.setTypeface(Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD_ITALIC));
            paint3.setColor(context.getColor(R.color.theme800));
            paint3.setTextSize(5);
            paint3.setTextAlign(Paint.Align.LEFT);
            String buf3 = tvTellop3.getText().toString() + ":" + etInput3.getText().toString();
            // 描画の範囲は、x方向は 0 ⇒ roll1201Y y方向は 0 ⇒ roll1201X
            canvas.drawText(buf3, roll1201Y- (int) paint3.measureText(buf3), 60, paint3);

            // ページを閉じる
            pdfDocument.finishPage(page);
            try {
                // ファイルを開いてPDFを書き込む
                // 次の指定は不可  File path = Environment.getExternalStoragePublicDirectory(
                //              Environment.DIRECTORY_DOCUMENTS);
                //この保存先 /storage/emulated/0/Android/data/org.sibainu.relax.room.last1/files
                File path = getExternalFilesDir(null);
                path.mkdirs();
                _file = new File(path, getPicFileName());
                pdfDocument.writeTo(new FileOutputStream(_file));

                // PDF の描画
                drawPDF();
            } catch (IOException e) {
                e.printStackTrace();
            }
            pdfDocument.close();
        }

        protected String getPicFileName(){
            Calendar c = Calendar.getInstance();
            String s = c.get(Calendar.YEAR)
                + "_" + (c.get(Calendar.MONTH)+1)
                + "_" + c.get(Calendar.DAY_OF_MONTH)
                + "_" + c.get(Calendar.HOUR_OF_DAY)
                + "_" + c.get(Calendar.MINUTE)
                + "_" + c.get(Calendar.SECOND)
                + ".pdf";
            return s;
        }

drawPDF メソッド

作成した PDF をスマホ・タブレット の画面に表示するメソッドです。

横長の ImageView の中に縦長の PDF を表示しようとしているので、ビットマップに変換して270度回転させて画像が最大になるようにしています。

変数 drawHeight drawWidth の初期値を ImageView の縦横を入れ替えた大きさにして、PDFの比率になるように drawHeight drawWidth を決めています。

copy

       public void drawPDF() throws RuntimeException {
            if (_file == null) {
                return;
            }

            try {
                // pdfを読み込み、1ページ目を取得
                ParcelFileDescriptor fd =
                    ParcelFileDescriptor.open(_file,
                                              ParcelFileDescriptor.MODE_READ_ONLY);
                PdfRenderer renderer = new PdfRenderer(fd);
                PdfRenderer.Page page = renderer.openPage(0);
                try {
                    ImageView view = findViewById(R.id.image);
                    // Bitmap を270度回転しますので描画の縦横を交換します
                    int drawHeight= view.getWidth();
                    int drawWidth = view.getHeight();
                    Log.d("test", "drawWidth=" + drawWidth +
                                       ", drawHeight=" + drawHeight);

                    float pdfWidth = page.getWidth();
                    float pdfHeight = page.getHeight();
                    Log.d("test", "pdfWidth=" + pdfWidth +
                                       ", pdfHeight=" + pdfHeight);

                    // PDFのサイズを基本にした縦横比を計算
                    float wRatio = drawWidth / pdfWidth;
                    float hRatio = drawHeight / pdfHeight;
                    Log.d("test", "wRatio=" + wRatio +
                                       ", hRatio=" + hRatio);

                    // Bitmap の最大サイズを計算します
                    if (wRatio <= hRatio) {
                        drawHeight = (int) Math.ceil(pdfHeight * wRatio);
                    } else {
                        drawWidth = (int) Math.ceil(pdfWidth * hRatio);
                    }
                    Log.d("test", "drawWidth=" + drawWidth +
                                      ", drawHeight=" + drawHeight);

                    // Bitmap生成して描画
                    Bitmap bitmap = Bitmap.createBitmap(drawWidth,
                                                    drawHeight,
                                                    Bitmap.Config.ARGB_8888);
                    page.render(bitmap, new Rect(0,
                                             0,
                                             drawWidth,
                                             drawHeight),
                                             null,
                                             PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY);

                    view.setImageBitmap(rotateBitmap(bitmap, 270));

                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    try {
                        if (fd != null) {
                            fd.close();
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    if (page != null) {
                        page.close();
                    }
                    if (renderer != null) {
                        renderer.close();
                    }
                }
            } catch(FileNotFoundException e) {
                Log.d("drawPDF","ファイルが開けられません");
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

        private Bitmap rotateBitmap(Bitmap targetBmp, int degrees) {
            int w = targetBmp.getWidth();
            int h = targetBmp.getHeight();

            Matrix mat = new Matrix();
            mat.setRotate(degrees);
            Bitmap responseBmp = Bitmap.createBitmap(targetBmp,0,0, w, h, mat,false);

            return responseBmp;
        }

保存状況

タブレットとPCをUSBで接続してファイルの保存状況を見てみます。

コードが出来上がるまでに大分ファイルを作りました。

保存したファイル

FireFox で保存したファイルを開いてみました。

このとおり縦長の縦方向に横書きの書式です。

タブレットの画面

タブレットでの表示は横長になっています。

コードで意図したとおりの表示になっています。

また、項目1~3までの入力が PDF にできるようにしています。入力どおりの表示になって印刷できるようにしています。

layout.activity_main

かなりの部分を変えましたので記録します。

copy

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/tvText1"
        android:layout_width="0dp"
        android:layout_height="60dp"
        android:layout_marginStart="8dp"
        android:text=""
        app:layout_constraintBottom_toTopOf="@+id/image"
        app:layout_constraintEnd_toStartOf="@+id/guideline3"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_begin="254dp" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_begin="0dp" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_percent="0.79" />

    <TextView
        android:id="@+id/textView01"
        android:layout_width="0dp"
        android:layout_height="60dp"
        android:layout_marginTop="8dp"
        android:layout_marginStart="8dp"
        android:text="01"
        android:clickable="true"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="@+id/guideline3"
        app:layout_constraintTop_toTopOf="@+id/guideline2" />

    <TextView
        android:id="@+id/textView02"
        android:layout_width="0dp"
        android:layout_height="60dp"
        android:layout_marginTop="8dp"
        android:layout_marginStart="8dp"
        android:text="02"
        android:clickable="true"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="1.0"
        app:layout_constraintStart_toStartOf="@+id/guideline3"
        app:layout_constraintTop_toBottomOf="@+id/textView01" />

    <TextView
        android:id="@+id/textView03"
        android:layout_width="0dp"
        android:layout_height="60dp"
        android:layout_marginTop="10dp"
        android:layout_marginStart="8dp"
        android:text="192.168.1.21"
        android:clickable="true"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="@+id/guideline3"
        app:layout_constraintTop_toBottomOf="@+id/textView02" />

    <TextView
        android:id="@+id/textView04"
        android:layout_width="0dp"
        android:layout_height="60dp"
        android:layout_marginStart="8dp"
        android:text="TextView"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="@+id/guideline3"
        app:layout_constraintTop_toTopOf="@+id/guideline" />

    <TextView
        android:id="@+id/tvTellop1"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:layout_marginStart="8dp"
        android:text="@string/tv_tellop1"
        android:textSize="11sp"
        app:layout_constraintEnd_toStartOf="@+id/guideline3"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@+id/guideline" />

    <EditText
        android:id="@+id/etInput1"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:layout_marginStart="8dp"
        android:inputType="text"
        android:text="@string/et_input1"
        android:textSize="11sp"
        app:layout_constraintEnd_toStartOf="@+id/guideline3"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/tvTellop1" />

    <TextView
        android:id="@+id/tvTellop2"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="15dp"
        android:layout_marginStart="8dp"
        android:text="@string/tv_tellop2"
        android:textSize="11sp"
        app:layout_constraintEnd_toStartOf="@+id/guideline3"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/etInput1" />

    <EditText
        android:id="@+id/etInput2"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:layout_marginStart="8dp"
        android:inputType="text"
        android:text="@string/et_input2"
        android:textSize="11sp"
        app:layout_constraintEnd_toStartOf="@+id/guideline3"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/tvTellop2" />

    <TextView
        android:id="@+id/tvTellop3"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="15dp"
        android:layout_marginStart="8dp"
        android:text="@string/tv_tellop3"
        android:textSize="11sp"
        app:layout_constraintEnd_toStartOf="@+id/guideline3"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/etInput2" />

    <EditText
        android:id="@+id/etInput3"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:layout_marginStart="8dp"
        android:inputType="text"
        android:textSize="11sp"
        android:text="@string/et_input3"
        app:layout_constraintEnd_toStartOf="@+id/guideline3"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/tvTellop3" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="1.0" />

    <Button
        android:id="@+id/button"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="Button"
        app:layout_constraintBottom_toBottomOf="@+id/guideline4"
        app:layout_constraintEnd_toStartOf="@+id/guideline3"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent" />

    <ImageView
        android:id="@+id/image"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toTopOf="@+id/guideline"
        app:layout_constraintEnd_toStartOf="@+id/guideline3"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/tvText1" />

</androidx.constraintlayout.widget.ConstraintLayout>

Design エディター

こんな感じの配置になります。

MainActivity.java

最後に MainActivity.java 全コードを記録します。

copy

package org.sibainu.relax.room.last1;

import androidx.annotation.UiThread;
import androidx.annotation.WorkerThread;
import androidx.appcompat.app.AppCompatActivity;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.graphics.pdf.PdfDocument;
import android.graphics.pdf.PdfRenderer;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

import com.brother.sdk.lmprinter.Channel;
import com.brother.sdk.lmprinter.NetworkSearchOption;
import com.brother.sdk.lmprinter.OpenChannelError;
import com.brother.sdk.lmprinter.PrintError;
import com.brother.sdk.lmprinter.PrinterDriver;
import com.brother.sdk.lmprinter.PrinterDriverGenerateResult;
import com.brother.sdk.lmprinter.PrinterDriverGenerator;
import com.brother.sdk.lmprinter.PrinterModel;
import com.brother.sdk.lmprinter.PrinterSearchResult;
import com.brother.sdk.lmprinter.PrinterSearcher;
import com.brother.sdk.lmprinter.setting.QLPrintSettings;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import java.io.FileNotFoundException;
import android.widget.ImageView;


import javax.net.ssl.HttpsURLConnection;

public class MainActivity extends AppCompatActivity {
    infoPost infopost;
    private TextView tvTellop1;
    private TextView tvTellop2;
    private TextView tvTellop3;
    private EditText etInput1;
    private EditText etInput2;
    private EditText etInput3;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        printerSearch();
        infopost = new infoPost("https://www.sibainu.org/",
                "uderstr",
                "idstr",
                "accessstr",
                "keystr");
        infopost.add("onCreate");

        ClickListener cl = new ClickListener();
        TextView tv;
        tv = findViewById(R.id.textView01);
        tv.setOnClickListener(cl);
        tv = findViewById(R.id.textView02);
        tv.setOnClickListener(cl);
        tv = findViewById(R.id.textView03);
        tv.setOnClickListener(cl);

        tvTellop1 = findViewById(R.id.tvTellop1);
        tvTellop2 = findViewById(R.id.tvTellop2);
        tvTellop3 = findViewById(R.id.tvTellop3);
        etInput1 = findViewById(R.id.etInput1);
        etInput2 = findViewById(R.id.etInput2);
        etInput3 = findViewById(R.id.etInput3);

        Button btn;
        btn =findViewById(R.id.button);
        btn.setOnClickListener(cl);
    }

    @Override
    protected void onDestroy(){
        super.onDestroy();
        infopost.add("onDestroy").postinfo();
    }

    private class ClickListener implements View.OnClickListener {
        @Override
        public void onClick(View view) {
            // クリックした TextView の値を取得
            int objid = view.getId();
            if (objid == R.id.textView01 ||
                    objid == R.id.textView02 ||
                    objid == R.id.textView03) {
                TextView tv;
                tv = findViewById(objid);
                String str = tv.getText().toString();

                //表示先の初期化
                tv = findViewById(R.id.tvText1);
                tv.setText("");

                //表示先の初期化
                tv = findViewById(R.id.textView04);
                tv.setText("");

                // 正規表現でマッチを確認
                Pattern p = Pattern.compile("[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}");
                Matcher m = p.matcher(str);
                if (m.find()) {
                    // IPアドレスの表示
                    tv.setText(str);
                    // プリントの実行
                    imagePrint(str, "mypdffile.pdf");
                }
            } else if (objid == R.id.button) {
                Log.d("generatePdf","onClock");
                pdfFactory pdffactory = new pdfFactory();
                pdffactory.generatePdf();
            }
        }
    }

    @UiThread
    private void printerSearch() {
        //クラス printerSearchTask の実体を作成
        printerSearchTask backgroundReceiver = new printerSearchTask(this);
        //executorService を作成
        ExecutorService executorService  = Executors.newSingleThreadExecutor();
        //executorService.submit の引数に実体を渡します
        //バックグラウンドで非同期処理が行われ戻り値が future に格納されます
        Future<PrinterSearchResult> future;
        future = executorService.submit(backgroundReceiver);
        PrinterSearchResult channels;
        try {
            //future から channels の配列を取り出します
            channels = future.get();
            if (channels != null) {

                TextView[] tvlist = {findViewById(R.id.textView01),
                        findViewById(R.id.textView02),
                        findViewById(R.id.textView03)};

                String modelname = "";
                String ipaddress = "";
                int i = 0;
                String[] printerList = new String[channels.getChannels().size()];

                for (Channel channel : channels.getChannels()) {
                    modelname = channel.getExtraInfo().get(Channel.ExtraInfoKey.ModelName);
                    ipaddress = channel.getChannelInfo();
                    printerList[i] = "プリンタ名: " + modelname + "\n" +
                            "IPアドレス: " + ipaddress;
                    if (i < tvlist.length ) {
                        tvlist[i].setText(printerList[i]);
                    }
                    i += 1;
                }

                TextView tv;
                tv = findViewById(R.id.tvText1);
                if (printerList.length > 0) {
                    tv.setText(String.join("\n", printerList));
                } else {
                    tv.setText("使用できるプリンタはありません");
                }
            }
        }
        catch(ExecutionException | InterruptedException ex) {
            Log.w("printerSearch catch", "非同期処理結果の取得で例外発生: ", ex);
        }
        finally {
            Log.d("printerSearch finally", "非同期処理完了");
            executorService.shutdown();
        }
    }

    public class printerSearchTask implements Callable<PrinterSearchResult> {
        PrinterSearchResult _channels;
        Context _context;
        //constructor です
        public printerSearchTask(Context context) {
            _context = context;
            Log.d("constructor", "printerSearchTask constructor");
        }
        // 非同期
        @WorkerThread
        @Override
        public PrinterSearchResult call() {
            try {
                NetworkSearchOption option = new NetworkSearchOption(5,
                        false);
                this._channels = PrinterSearcher.startNetworkSearch(_context,
                        option,
                        new Consumer<Channel>() {
                            @Override
                            public void accept(Channel channel) {
                            }
                        });
            } catch (Exception e) {
                Log.d("Error call catch", "PrinterSearchResult");
                e.printStackTrace();
            }
            return this._channels;
        }
    }

    private class infoPost {
        List<String> _info;
        String _urlstr;
        Map<String, String> postbody;
        public infoPost(String urlstr,
                        String userstr,
                        String idstr,
                        String accessstr,
                        String keystr){
            postbody =new HashMap<>();
            postbody.put("userstr",userstr);
            postbody.put("idstr",idstr);
            postbody.put("accessstr",accessstr);
            postbody.put("keystr",keystr);
            this._urlstr = urlstr;
            this._info = new ArrayList<>();
        }

        public infoPost add(String addstr) {
            this._info.add(addstr);
            return this;
        }

        @UiThread
        public void postinfo() {
            // List を配列に変換
            String[] ss = new String[_info.size()];
            _info.toArray(ss);

            // postbody に info データを追加
            String data;
            data = String.join("\n", ss);
            postbody.put("data",data);

            // 送信データを作成します。
            StringBuilder postData = new StringBuilder();
            // 連想配列をペアで処理
            try {
                for (Map.Entry<String, String> s : postbody.entrySet()) {
                    // 先頭が & にならないようにします。
                    if (postData.length() != 0) postData.append('&');
                    // エンコードして結合します。
                    postData.append(URLEncoder.encode(s.getKey(), "UTF-8"))
                        .append('=')
                        .append(URLEncoder.encode(s.getValue(), "UTF-8"));
                }

                requestPost(_urlstr, postData.toString());

            } catch(Exception e) {
                e.printStackTrace();
            }
        }

        @UiThread
        private void requestPost(String urlstr,
                                 String postbody) {
            //クラス requestPostTask の実体を作成
            requestPostTask backgroundReceiver = new requestPostTask(urlstr, postbody);
            //executorService を作成
            ExecutorService executorService  = Executors.newSingleThreadExecutor();
            //executorService.submit の引数に JsonPostHttp の実体を渡します
            //バックグラウンドで非同期処理が行われ戻り値が future に格納されます
            Future<String> future;
            future = executorService.submit(backgroundReceiver);
            String result = "";
            try {
                //future から戻り値を取り出します
                result = future.get();
                // テキストビューに表示
                TextView tv;
                tv = findViewById(R.id.tvText1);
                tv.setText(result);
            }
            catch(ExecutionException | InterruptedException ex) {
                Log.w("printerSearch catch", "非同期処理結果の取得で例外発生: ", ex);
            }
            finally {
                Log.d("printerSearch finally", "非同期処理完了");
                executorService.shutdown();
            }
        }

        public class requestPostTask implements Callable<String> {
            String _urlstr;
            String _postbody;
            //constructor です
            public requestPostTask(String urlstr,
                                   String postbody) {
                _urlstr = urlstr;
                _postbody = postbody;
                Log.d("constructor", "requestPostTask constructor");
            }
            // 非同期
            @WorkerThread
            @Override
            public String call() {
                String response = "";
                try {
                    // URLを設定します。
                    // 送信するデータをバイト配列に変換
                    byte[] postDataBytes = postbody.toString().getBytes("UTF-8");
                    // URLを設定します。
                    URL url = new URL(_urlstr);
                    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                    conn.setRequestMethod("POST");
                    conn.setDoOutput(true);
                    conn.setConnectTimeout(30000);

                    // 送信
                    conn.getOutputStream().write(postDataBytes);

                    // レスポンスを受け取る
                    int responseCode=conn.getResponseCode();
                    // 送信の結果
                    if (responseCode == HttpsURLConnection.HTTP_OK) {
                        String line;
                        BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
                        while ((line=br.readLine()) != null) {
                            response += line;
                        }
                    } else { response = "送信に不具合がありました"; }
                } catch (Exception e) {
                    response = "送信エラー: " + e.getMessage();
                }
                // future に渡す値
                return response;
            }
        }

    }

    @UiThread
    private void imagePrint(String ipaddress,
                            String filename) {
        //クラス requestPostTask の実体を作成
        imagePrintTask backgroundReceiver = new imagePrintTask(ipaddress, filename);
        //executorService を作成
        ExecutorService executorService  = Executors.newSingleThreadExecutor();
        //executorService.submit の引数に JsonPostHttp の実体を渡します
        //バックグラウンドで非同期処理が行われ戻り値が future に格納されます
        Future<String> future;
        future = executorService.submit(backgroundReceiver);
        String result = "";
        try {
            //future から戻り値を取り出します
            result = future.get();
            // テキストビューに表示
            TextView tv;
            tv = findViewById(R.id.tvText1);
            tv.setText(result);
            Log.d("imagePrint result", result);
        }
        catch(ExecutionException | InterruptedException ex) {
            Log.w("Error imagePrint catch", "非同期処理結果の取得で例外発生: ", ex);
        }
        finally {
            executorService.shutdown();
        }
    }

    private class imagePrintTask  implements Callable<String> {
        String _ipaddress;
        String _filename;
        public imagePrintTask(String ipaddress,
                              String filename) {
            _ipaddress = ipaddress;
            _filename = filename;
            Log.d("constructor", "imagePrintTask constructor");
        }
        // 非同期
        @WorkerThread
        @Override
        public String call() {
            // https://support.brother.com/g/s/es/htmldoc/mobilesdk/guide/getting-started/getting-started-android.html
            // https://support.brother.com/g/s/es/htmldoc/mobilesdk/guide/print-image.html
            // https://support.brother.com/g/s/es/htmldoc/mobilesdk/guide/discover-printer.html

            Channel channel = Channel.newWifiChannel(_ipaddress);

            PrinterDriverGenerateResult result = PrinterDriverGenerator.openChannel(channel);
            if (result.getError().getCode() != OpenChannelError.ErrorCode.NoError) {
                Log.e("Error OpenChannel", "Error - Open Channel: " + result.getError().getCode());
                // future に渡す値
                return "Error OpenChannel - IPアドレスのエラー";
            }

            File dir = getExternalFilesDir(null);
            File file = new File(dir, _filename);
            if (file == null){
                // future に渡す値
                return "Error File - イメージが取得できません";
            }

            PrinterDriver printerDriver = result.getDriver();
            String response = "";
            try {
                QLPrintSettings printSettings = new QLPrintSettings(PrinterModel.QL_820NWB);
                printSettings.setLabelSize(QLPrintSettings.LabelSize.RollW103);
                printSettings.setAutoCut(true);
                printSettings.setWorkPath(dir.toString());

                PrintError printError = printerDriver.printImage(file.toString(), printSettings);

                if (printError.getCode() != PrintError.ErrorCode.NoError) {
                    Log.d("Error Print", "Error - Print Image: " + printError.getCode());
                    response = "Error - Print Image";
                } else {
                    Log.d("Success Print", "Success - Print Image");
                    response = "Success - Print Image";
                }
            } catch(Exception e) {
                response = "Error Another catch - " + e.toString();
            } finally {
                printerDriver.closeChannel();
            }
            // future に渡す値
            return response;
        }
    }

    private class pdfFactory {

        File _file;
        public pdfFactory (){
            Log.d("pdfFactory","コンストラクタ0");
        }

        public pdfFactory (File file){
            Log.d("pdfFactory","コンストラクタ1");
            _file = file;
        }

        // メッソド
        public void generatePdf() {
            Context context = MainActivity.this;
            // PDFドキュメント生成用のクラスを作成
            PdfDocument pdfDocument = new PdfDocument();
            Resources r;
            PdfDocument.Page page;
            Canvas canvas;
            Matrix matrix = new Matrix();
            Paint text = new Paint();
            Paint paint1 = new Paint();
            Paint paint2 = new Paint();
            Paint paint3 = new Paint();
            int pages = 0;
            r=getResources();

            // A4横(72dpi)
            int a4X = 595;
            // A4縦(72dpi)
            int a4Y = 842;
            // roll1201横29mm(72dpi)
            int roll1201X=85;
            // roll1201縦90mm(72dpi)
            int roll1201Y=264;
            // 指定の縦横幅(ピクセル)でページを作る
            page = pdfDocument.startPage(new PdfDocument.PageInfo.Builder(roll1201X, roll1201Y, pages).create());
            // ページに書き込むためのキャンバスを取得する
            canvas = page.getCanvas();

            // 描画の座標系を左上を中心に右回り90度回転します
            canvas.rotate(90);
            // 描画の座標系の原点を移動します
            canvas.translate(0, -85);
            // ビットマップを描画します
            Bitmap bitmap = BitmapFactory.decodeResource(r, R.drawable.sample);
            int width = bitmap.getWidth();
            int height = bitmap.getHeight();
            canvas.drawBitmap(bitmap, new Rect(0, 0, width, height),
                new Rect(0, 0, width / 4, height / 4), null);

            // 文字を書き込む
            text.setTypeface(Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD_ITALIC));
            text.setColor(context.getColor(R.color.theme500));
            text.setTextSize(5);
            text.setTextAlign(Paint.Align.LEFT);
            canvas.drawText("テスト!!テスト!!!",85, 10, text);

            paint1.setTypeface(Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD_ITALIC));
            paint1.setColor(context.getColor(R.color.theme600));
            paint1.setTextSize(5);
            paint1.setTextAlign(Paint.Align.LEFT);
            String buf1 = tvTellop1.getText().toString() + ":" + etInput1.getText().toString();
            canvas.drawText(buf1,10, 12, paint1);

            paint2.setTypeface(Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD_ITALIC));
            paint2.setColor(context.getColor(R.color.theme700));
            paint2.setTextSize(5);
            paint2.setTextAlign(Paint.Align.LEFT);
            String buf2 = tvTellop2.getText().toString() + ":" + etInput2.getText().toString();
            // 描画の範囲は、x方向は 0 ⇒ roll1201Y y方向は 0 ⇒ roll1201X
            canvas.drawText(buf2,(roll1201Y - (int) paint2.measureText(buf2))/ 2, 30, paint2);

            paint3.setTypeface(Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD_ITALIC));
            paint3.setColor(context.getColor(R.color.theme800));
            paint3.setTextSize(5);
            paint3.setTextAlign(Paint.Align.LEFT);
            String buf3 = tvTellop3.getText().toString() + ":" + etInput3.getText().toString();
            // 描画の範囲は、x方向は 0 ⇒ roll1201Y y方向は 0 ⇒ roll1201X
            canvas.drawText(buf3, roll1201Y- (int) paint3.measureText(buf3), 60, paint3);

            // ページを閉じる
            pdfDocument.finishPage(page);
            try {
                // ファイルを開いてPDFを書き込む
                // 次の指定は不可  File path = Environment.getExternalStoragePublicDirectory(
                //              Environment.DIRECTORY_DOCUMENTS);
                //この保存先 /storage/emulated/0/Android/data/org.sibainu.relax.room.last1/files
                File path = getExternalFilesDir(null);
                Log.d("getExternalFilesDir",path.toString());
                path.mkdirs();
                _file = new File(path, getPicFileName());
                pdfDocument.writeTo(new FileOutputStream(_file));

                // PDF の描画
                drawPDF();
            } catch (IOException e) {
                e.printStackTrace();
            }
            pdfDocument.close();
        }

        protected String getPicFileName(){
            Calendar c = Calendar.getInstance();
            String s = c.get(Calendar.YEAR)
                + "_" + (c.get(Calendar.MONTH)+1)
                + "_" + c.get(Calendar.DAY_OF_MONTH)
                + "_" + c.get(Calendar.HOUR_OF_DAY)
                + "_" + c.get(Calendar.MINUTE)
                + "_" + c.get(Calendar.SECOND)
                + ".pdf";
            return s;
        }

        // メソッド
        public void drawPDF() throws RuntimeException {
            if (_file == null) {
                return;
            }

            try {
                // pdfを読み込み、1ページ目を取得
                ParcelFileDescriptor fd =
                    ParcelFileDescriptor.open(_file,
                                              ParcelFileDescriptor.MODE_READ_ONLY);
                PdfRenderer renderer = new PdfRenderer(fd);
                PdfRenderer.Page page = renderer.openPage(0);
                try {
                    ImageView view = findViewById(R.id.image);
                    // Bitmap の270度回転しますので描画の縦横を交換します
                    int drawHeight= view.getWidth();
                    int drawWidth = view.getHeight();
                    Log.d("test", "drawWidth=" + drawWidth +
                                       ", drawHeight=" + drawHeight);

                    float pdfWidth = page.getWidth();
                    float pdfHeight = page.getHeight();
                    Log.d("test", "pdfWidth=" + pdfWidth +
                                       ", pdfHeight=" + pdfHeight);

                    // PDFのサイズを基本にした縦横比を計算
                    float wRatio = drawWidth / pdfWidth;
                    float hRatio = drawHeight / pdfHeight;
                    Log.d("test", "wRatio=" + wRatio +
                                       ", hRatio=" + hRatio);

                    // Bitmap の最大サイズを計算します
                    if (wRatio <= hRatio) {
                        drawHeight = (int) Math.ceil(pdfHeight * wRatio);
                    } else {
                        drawWidth = (int) Math.ceil(pdfWidth * hRatio);
                    }
                    Log.d("test", "drawWidth=" + drawWidth +
                                      ", drawHeight=" + drawHeight);

                    // Bitmap生成して描画
                    Bitmap bitmap = Bitmap.createBitmap(drawWidth,
                                                    drawHeight,
                                                    Bitmap.Config.ARGB_8888);
                    page.render(bitmap, new Rect(0,
                                             0,
                                             drawWidth,
                                             drawHeight),
                                             null,
                                             PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY);

                    view.setImageBitmap(rotateBitmap(bitmap, 270));

                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    try {
                        if (fd != null) {
                            fd.close();
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    if (page != null) {
                        page.close();
                    }
                    if (renderer != null) {
                        renderer.close();
                    }
                }
            } catch(FileNotFoundException e) {
                Log.d("drawPDF","ファイルが開けられません");
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

        private Bitmap rotateBitmap(Bitmap targetBmp, int degrees) {
            int w = targetBmp.getWidth();
            int h = targetBmp.getHeight();

            Matrix mat = new Matrix();
            mat.setRotate(degrees);
            Bitmap responseBmp = Bitmap.createBitmap(targetBmp,0,0, w, h, mat,false);

            return responseBmp;
        }

    }
}

この件はここまでとします。