Android PDFの編集・保存・印刷

今日の柴犬です。最近の暑さでちょっとバテ気味です。ですが、藤の白い花の下で涼んで気持ちよさそうな顔しています。
2024年2月26日現在
WEBのみでは断片的で覚えにくいので最初に購入した Kotlin の本です。
今日のヌートリアです。最近1匹しか目にしません。駆除・襲われたのか分かりませんが心配です。
概要
タブレットから縦長の細長いシール紙に縦方向に横書きの PDF が作成できる。
その印刷ができる。
印刷する PDF がタブレットに表示できて印刷の内容が事前に確認できる。
タブレットから入力した文字が印刷できる。
そんなアプリを考えてみて形になりましたので記録します。

pdfFactory クラス
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の状態になります。
これは、移動のアルゴリズムですので描く前にしなければなりません。以降、青色の領域に書いた文字などは移動した後の状態で橙色に描かれます。
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 を決めています。
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
かなりの部分を変えましたので記録します。
<?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 全コードを記録します。
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;
}
}
}
この件はここまでとします。