◆はじめに

▲本書の内容

本書『Java実例プログラミング　事務処理編』は、Javaプログラミング入門書（初級者対象）です。本書に掲載したプログラムの「MakeCSV1」および「PrintCSV1」は、姉妹編の「描画処理編」に掲載した「GCreator」よりも簡単平易な内容です。ただし、Javaの言語としての文法規則の説明は、省略しています。「入門」ではありますが、Javaに関しての初歩的知識が読者に既にあるものとします。 Javaプログラミングの解説に際し、著者が開発した実際に動くプログラム（「MakeCSV1」および「PrintCSV1」）のソース・コードを公開し、そのソース・コード内の大量の注釈文によって、プログラミング・テクニックを実践的に解説する形式とします。そのため、日本語の注釈文を埋め込んだソース・コードを読んでいただくことになります。

本書が公開するソース・コードのプログラムは、メイン・システムの「MakeCSV1」とサブ・システムの「PrintCSV1」との、二種類から構成されます。「MakeCSV1」は、見積もり金額計算を想定した金額計算プログラムで、計算結果は、「CSVデータ・ファイル」および「罫線描画命令書ファイル」として保存されます。「PrintCSV1」は、「CSV形式のデータ」を受け取り、さらに「独自形式の罫線描画命令書」を受け取り、罫線入りの計算明細書をプリンターから紙にプリント・アウトします。「MakeCSV1」から「PrintCSV1」へのデータ伝達は、「ファイル経由の間接伝達」と「メモリー上で文字列コピーする直接伝達」とが可能ですが、作業が簡単で操作間違いのない直接伝達を基本とします。なお、CSV（comma separated values）とは、データ項目がコンマで区切られたテキスト形式のデータ・ベース規格のことで、ファイル拡張子が「CSV」になります。一方、「独自形式の罫線描画命令書」とは、罫線の位置や長さを指定し、罫線区画された各欄にデータを嵌め込む指定を行なうテキスト形式の命令書で、ファイル拡張子は独自の「pcc」になります。

「MakeCSV1」は、キーボードから文字入力を受け付ける「JTextField」と、表形式でデータを一括して取りまとめる「JTable」（DefaultTableModel）と、警告表示を行なう「JTextArea」とを、操作画面の各所に配置し、必要な計算処理を行なうプログラムです。プログラムを作動させる前に、CSVファイル形式で、「取引先データ・ベース」（Customer.csv）と「商品データ・ベース」（Item.csv）とをテキスト・エディターで作成しておく必要があります。別途、見本となる「取引先データ・ベース」（Customer.csv）と「商品データ・ベース」（Item.csv）とを用意してあります。「取引先データ・ベース」は、取引先コード、取引先名、基準掛率、この３項目からなります。「商品データ・ベース」は、商品コード、品名、定価、消費税率、この４項目からなります。「JTextArea」の所定欄に取引先コードが入力され確定（エンター・キー打鍵）されると、「取引先データ・ベース」をシーケンシャル・サーチで検索し、取引先名および基準掛率を所定欄に自動的に書き込む仕組みです。同様に、「JTextArea」の所定欄に商品コードが入力され確定されると、「商品データ・ベース」をシーケンシャル・サーチで検索し、品名、定価、消費税率、これらを所定欄に自動的に書き込みます。取引先コードおよび商品コードが予め設定されていない臨時取り引きの場合、コードを空欄のまま確定すれば、その空欄に「コードなし」を意味する「**」が自動で書き込まれ、コード以外のデータを所定欄に手動でキーボード入力することになります。発行年月日欄は、空欄のまま確定すれば、その日の年月日が自動的に書き込まれます。「年月日」ではなく、独自形式の「発行番号」の書き込みでも構いません。

「PrintCSV1」は、姉妹編の「描画処理編」に記載した「GCreator」を極度に簡略にし、表作成専用のコマンド（命令）を追加し、複数ページ対応にしたものです。ただし、両者のコマンドは、似ているものの互換性はなく、それぞれ独自のコマンド体系になっています。「GCreator」は、ピクセル単位で座標数値を扱い、「PrintCSV」は、ミリ・メートル単位で座標数値を扱います。単位を統一することも出来ましたが、それぞれの利便性を優先しました。そもそも、コマンド体系の一部が似ているだけで全体的に大きく違うため、互換性を断念しました。「PrintCSV1」は、「MakeCSV1」に付属するサブ・システムですが、単独で実行することも可能です。サブ・システムとして実行する場合、表示された罫線入りの「計算明細書」の内容を確認し、それで問題なければ、メニューの「ファイル」から「画像印刷」を選んで、紙へのプリント・アウトを実行します。「PrintCSV1」を単独で実行する場合、メニューの「見本」を選んで、使い方のサンプルを参考にしつつ、「PrintCSV1Manual.txt」も参照して下さい。

著者が開発した描画プログラム「MakeCSV1」および「PrintCSV1」は、ソース・コードの形で提供されるため、読者によって私的に機能拡張することができます。ソース・コードは、機能別に別ファイルに分割されているため、理解しやすく整理されています。大量の説明コメントが、ソース・コードに埋め込まれているため、Javaの初歩的知識さえあれば、理解可能です。もし、理解しにくい部分があれば、「とにかく、そのような手続きで上手く行くのだ」と理解するしかありません。 Javaプログラミングに関しては、その難しさの本質が、Java付属のライブラリーの膨大さにあり、そのブラック・ボックスの内部構造の難解さにあります。ブラック・ボックスは、その内部構造を詳細に知ろうとしても、その趣旨からして困難で、その外形的な使い方を知るだけで満足するしかありません。そのため、使い方のパターンを憶えることも、ブラック・ボックス的理解の一つであって、ブラック・ボックスの内部にまで深く踏み込むのは、上級者の範囲になります。

▲本書の構成

本書は、第一部の「解説編」と第二部の「ソース・コード編」との二部構成です。

「解説編」では、「MakeCSV1」および「PrintCSV1」の機能説明や使用法説明を概略的に行ないます。「解説編」では、「MakeCSV1」および「PrintCSV1」の稼働状態をスクリーン・ショットしたものを挿絵に用います。ただし、解説それ自体は、簡素なもので、必要最小限とします。詳しい解説は、ソース・コード内にコメント（注釈）として埋め込んでいます。つまり、既にJavaに関する基礎知識のある読者は、ソース・コードを読むことにより、解説も読む形になるわけです。

「ソース・コード編」では、必要なソース・コードの全部を公開します。「ソース・コード編」は、「MakeCSV1」および「PrintCSV1」さらに「共用ライブラリー」から成り立っています。ソース・コード以外に「MakeCSV1Manual.txt」や「PrintCSV1Manual.txt」など、各種関連ファイルも添付しています。これら関連ファイルは、プログラムの内部から参照され使用される必須のデータ・ファイルであって、プログラムの稼働に必要なものです。

ソース・コードの中に、個別具体的な解説をコメント形式で解説する方法を採用しているため、「ソース・コード編」では、ソース・コードの中以外に、詳しい解説はありません。ソース・コードの所定位置で、その場にふさわしい解説を行ないますので、ソース・コードを丹念に読んでください。ソース・コードは、トリッキーな書き方やテクニカルな書き方を一切していません。最も基本的で分かりやすい方法により、記述効率が悪かろうと、愚直に記述しています。そのため、複雑に込み入った部分が殆どなく、読みやすくなっています。

「PrintCSV1」の具体的な使用法に関しては、「メニュー・バー」の中に「見本」の項目を入れ、命令書の書き方の見本を何種類か提示しています。「PrintCSV1」を「MakeCSV1」のサブ・システムではなく、単独で実行する場合、この「見本」が参考になります。基礎的な命令の記述法は、この「見本」を実行して、その振る舞いを見たり、命令書の一部を書き替えて、その振る舞いの変化を実験的に試して、理解していただくことになります。「MakeCSV1」は、操作方法が極めて簡単であり、操作コマンドも存在しないため、「メニュー・バー」の中に「見本」の項目は、入っていません。「MakeCSV1Manual.txt」で使い方を理解して下さい。

▲補足説明

EPUBファイルの閲覧には、EPUBリーダー・ソフトの「calibre」などを利用して下さい。なお、EPUBファイルの別の閲覧方法としては、EPUBファイルの拡張子「epub」を「zip」に書き替え、 zip圧縮を解凍（復元）すれば、複数のXHTMLファイルになるため、インターネット・ブラウザで閲覧できます。「XHTML」は、「HTML」のタグ記述規則を厳格にしたものに過ぎず、基本的に同じものです。解凍されたファイル群から目次ファイルである「ch02nav.xhtml」を最初に開くと、普通に閲覧できます。

また、EPUBビューア（「calibre」など）やインターネット・ブラウザ（「グーグル・クローム」など）の機能に依存しますが、そのソフトにクリップボードへのテキスト・コピーができる機能があれば、この機能を使って、 XHTMLのエスケープ・シーケンス文字が除去されたソース・コードを、テキスト・エディタにペーストすることで、簡単に取得できます。

■第一部　解説編

◆第一章　MakeCSV1の構成ファイル解説

MakeCSV1は、ソース・コード・ファイルおよび補助テキスト・ファイルとして、次に示すファイルが必要です。これらは、「ソース・コード編」でその全てを公開しています。なお、他にも必要となるディレクトリー（フォルダー）を書き加えます。これらのディレクトリー名およびファイル名は、ソース・コードの中でも使われているため、変更できません。

/user/java/MakeCSV1/src/new　【ソース・ファイル・ディレクトリー】
　MakeCSV1.java
　　Swingの各種部品を集めて操作と表示の画面を構築する
　MakeCSV1DataBase.java
　　取引先コードまたは商品コードで、それぞれのデータベースを検索する
　MakeCSV1InputLis.java
　　テキスト・フィールドでのデータ入力を受け取り、機能の実現または委託する
　MakeCSV1MenuLis.java
　　メニュー操作での要求を受け取り、機能の実現または委託する
　MakeCSV1ToolLis.java
　　ボタン操作での要求を受け取り、データ・テーブルに関する必要な処理を行なう
/user/java/Lib/src/new 　　【共用ライブラリー・ファイル・ディレクトリー】
　SystemMessage.java
　　StackTraceMonitorおよびGCreator.javaと協調動作する
　StackTraceMonitor.java
　　Java実行環境が出力するエラー・メッセージを横取りして別途表示する
　SingleFileFilter.java
　　ファイル選択の補助を行なう
　Util.java
　　ユーティリティーの提供（余分な機能も含む）
　TextIO.java
　　テキスト・ファイルの入出力を行なう（余分な機能も含む）
/user/java/MakeCSV1/sys　　【MakeCSV1システム・ファイル・ディレクトリー】
　MakeCSV1Manual.txt
　　MakeCSV1の入力およびデータ処理の仕様書
　Costomer.csv
　　取引先データベース（取引先コード、取引先名、基準掛率）
　Item.csv
　　商品データベース（商品コード、商品名、定価、消費税率）
/user/java/MakeCSV1/csv　　【データ・ファイル・ディレクトリー(csv)】
/user/java/MakeCSV1/pcc　　【印刷命令文書ファイル・ディレクトリー(pcc)】
/user/class　　　　　　　　【クラス・ファイル・ディレクトリー(class)】
/user/bat　　　　　　　　　【バッチ・ファイル・ディレクトリー(bat)】

◆第二章　MakeCSV1の操作法解説

以下に、MakeCSV1の稼働状態の実例をスクリーン・ショットで示し、操作法の概要を説明します。

上の図は、データ入力している途中の状態です。まずは、MakeCSV1の入力画面構成（Swingの配置）を説明します。一段目は、タイトル・バーであって、左端に「MakeCSV1 Ver.1.0」と表示しているだけです。無論、右端には、ウィンドウ操作ボタン（最大化ボタンや終了ボタンなど）があります。二段目は、メニュー・バーであって、各種機能を配置しています。左端から、「ファイル」「消去」「ヘルプ」のメニューがあります。三段目は、ツール・バー（ボタンを配置）であって、左から順に「選択解除」「全行削除」「一行削除」「一行記憶」「一行挿入」「空行追加」「計算検証」のボタンあります。これらのボタンには、直下にあるテーブルを行単位で編集する機能および計算結果を検証する機能を割り当てています。四段目は、最も広い画面領域を占めていて、各種データを表形式でまとめたテーブルを配置しています。テーブルの各欄は、左から順に、「品番」「品名」「単価」「数量」「金額」「税率」「税込」、となっています。五段目は、四段目に付属する合計欄のテーブルです。六段目は、入力欄で、左から順に、「取引先コード」「取引先名」「基準掛率」「年月日」、となっています。七段目は、入力欄で、左から順に、「品番」「品名」「定価」「税率」、となっています。八段目は、入力欄で、左から順に、「実効掛率」「単価」「数量」「金額」「税込金額」、となっています。九段目は、「メッセージ・エリア」で、エラー・メッセージや作業報告メッセージを表示します。

さらに詳しい機能や動作の仕様は、「MakeCSV1Manual.txt」を参照して下さい。

メニュー・バーの「メニュー」および「メニュー・アイテム」の詳細は、以下のようになります。

ファイル
　　開く
　　保存
　　印刷
消去
　　警告文消去
ヘルプ
　　使用法

◆第三章　PrintCSV1の構成ファイル解説

PrintCSV1は、ソース・コード・ファイルおよび補助テキスト・ファイルとして、次に示すファイルが必要です。これらは、「ソース・コード編」でその全てを公開しています。なお、他にも必要となるディレクトリー（フォルダー）を書き加えます。これらのディレクトリー名およびファイル名は、ソース・コードの中でも使われているため、変更できません。

/user/java/PrintCSV1/src/new　【ソース・ファイル・ディレクトリー】
　PrintCSV1.java
　　Swingの各種部品を集めて表示および操作の画面を構築する
　PrintCSV1AnalyzeEngine.java
　　作表コマンドを分析し、機能実現を支援する
　PrintCSV1CanvasEngine.java
　　一覧表の描画処理を行なう
　PrintCSV1PrintEngine.java
　　印刷を実行する
　PrintCSV1MenuLis.java
　　メニュー操作報告を受け取り、機能を実現または委託する
　PrintCSV1ButtonLis.java
　　ボタン操作報告を受け取り、機能を実現または委託する
　PrintCSV1FileIO.java
　　ファイル入出力を支援する
/user/java/PrintCSV1/sys 　【PrintCSV1システム・ファイル・ディレクトリー】
　PrintCSV1Manual.txt
　　PrintCSV1の作表コマンド仕様書
/user/java/Lib/src/new 　　【共用ライブラリー・ファイル・ディレクトリー】
　SystemMessage.java
　StackTraceMonitor.java
　SingleFileFilter.java
　Util.java
　TextIO.java
/user/java/MakeCSV1/csv　　【データ・ファイル・ディレクトリー(csv)】
/user/java/MakeCSV1/pcc　　【印刷命令文書ファイル・ディレクトリー(pcc)】
/user/class　　　　　　　　【クラス・ファイル・ディレクトリー(class)】
/user/bat　　　　　　　　　【バッチ・ファイル・ディレクトリー(bat)】

◆第四章　PrintCSV1の操作法解説

以下に、PrintCSV1の稼働状態の実例をスクリーン・ショットで示し、操作法の概要を説明します。

PrintCSV1をMakeCSV1のサブ・システムとして使用する限り、「メニュー」の「画像印刷」を選択するだけで、プリンターのデバイス・ドライバーに制御が移行し、印刷設定に関わるポップアップ画面が表示されますので、印刷設定を行なった後、「印刷実行」の機能を選択すれば、罫線入り「明細書」の印刷が実行されます。

PrintCSV1を単独で起動する場合、書式文（「作表コマンド」を使ったマクロ・プログラム）を自分で作成することになります。書式文の書き方は、「メニュー」の「見本」を参考にして下さい。「作表コマンド」の詳しい機能や動作の仕様は、「PrintCSV1Manual.txt」を参照して下さい。罫線入り作表に関する各部名称は、「メニュー」の「NUL 各部名称説明」を選択することで、図解説明が表示されます。

メニュー・バーの「メニュー」および「メニュー・アイテム」の詳細は、以下のようになります。

ファイル
　　データ開く（UTF-8）
　　データ保存（UTF-8）
　　書式文開く（UTF-8）
　　書式文保存（UTF-8）
　　画像印刷
消去
　　データ消去
　　書式文消去
　　警告文消去
　　全消去
見本
　　CSV 罫線標準用法
　　CSV 罫線特殊用法
　　NUL 各部名称説明
　　NUL 印刷試験A4
　　NUL カード印刷
　　NUL フォント・テスト１
　　NUL フォント・テスト２
表示
　　表示：x6
　　表示：x5
　　表示：x4
　　表示：x3 標準設定
　　表示：x2
　　表示：x1
ヘルプ
　　使用法

■第二部　ソース・コード編

「Windows11」から、テキスト・ファイルの入出力において、文字コードのデフォルトが、「Shift_JIS」から「UTF-8」になりました。正確に言えば、「Windows10」であっても、「Windows10 21H2」から「UTF-8」に変更されています。なお、バージョン番号の「21H2」は、「2021年下半期」の意味です。「Windows10」であれ「Windows11」であれ、「UTF-8」がデフォルトの環境である場合、この点に注意が必要です。「UTF-8」がOSレベルで標準仕様となる今後は、プログラム・ソース・コード（.java）も命令文書（.pcc）も付属のシステム・テキスト・ファイルも、特別な理由がない限り、全て「UTF-8」で保存することが望まれます。各種テキスト・エディターには、文字コードを指定してファイル保存する機能があるはずです。

なお、コンピューターのストレージ（HDDやSSD）にJavaの実行環境（JDK18）がインストール済みであり、 「javac.exe」や「java.exe」が実行できるよう、また、拡張子が「class」のファイルにアクセスできるよう、ＯＳに対してシステム環境変数（「PATH」および「CLASSPATH」さらに「JAVA_HOME」）が適切に設定されているものとします。ただし、当面必要なシステム環境変数は、「PATH」および「CLASSPATH」の設定だけです。なお、「PATH」は、Windows上で動作する多くのプログラムが必要とする性質のもので、「javac.exe」や「java.exe」が動作するよう、「PATH」のリストに対して、さらに追加することになります。また、「JAVA_HOME」は、他のプログラムが必要とすることもあるため、念のために設定するものです。環境変数の「PATH」および「CLASSPATH」は、バッチ・ファイルの中でその都度指定することも可能ですが、「JAVA_HOME」は、そうではないため、ＯＳの「コントロール パネル」を通じて、これら全部を指定することを推奨します。

システム環境変数設定の一例（各自が中身を調整すること）
「PATH」 　　　―――　「c:\Progrem Files\java\jdk-18.0.2.1\bin」を含むこと
「CLASSPATH」　―――　「c:\user\class」
「JAVA_HOME」　―――　「c:\Progrem Files\java\jdk-18.0.2.1」

システム環境変数が適切に設定済みであることを条件に、Windows11の環境下でコンパイルするバッチ・ファイルの一例（参考）を、以下に示します。見落としやすいピリオド記号に、注意して下さい。この一例は、「Shift_JIS」で記述されたソース・コードをコンパイルする場合です。ソース・コードが「UTF-8」で記述されていれば、エンコードの指定部分を「UTF-8」に書き換えるか削除して下さい。

まずは、ライブラリーのコンパイル。

rem 共用ライブラリーのコンパイル
c:
del \user\class\SystemMessage*.class
del \user\class\StackTraceMonitor*.class
del \user\class\SingleFileFilter*.class
del \user\class\Util*.class
del \user\class\TextIO*.class
cd \user\java\Lib\src\new
javac -d \user\class -sourcepath . -encoding Shift_JIS SystemMessage.java
javac -d \user\class -sourcepath . -encoding Shift_JIS StackTraceMonitor.java
javac -d \user\class -sourcepath . -encoding Shift_JIS SingleFileFilter.java
javac -d \user\class -sourcepath . -encoding Shift_JIS Util.java
javac -d \user\class -sourcepath . -encoding Shift_JIS TextIO.java
pause

続いて、PrintCSV1のコンパイル。

rem PrintCSV1のコンパイル
c:
del \user\class\PrintCSV1*.class
cd \user\java\PrintCSV1\src\new
javac -d \user\class -sourcepath . -encoding Shift_JIS PrintCSV1.java
pause

コンパイル済みのプログラムに対して、Windows11の環境下でPrintCSV1を実行するバッチ・ファイルの一例を、以下に示します。

rem PrintCSV1の実行
c:
cd \user\java\PrintCSV1
java PrintCSV1
pause

さらに続いて、MakeCSV1のコンパイル。

rem MakeCSV1のコンパイル
c:
del \user\class\MakeCSV1*.class
cd \user\java\MakeCSV1\src\new
javac -d \user\class -sourcepath . -encoding Shift_JIS MakeCSV1.java
pause

コンパイル済みのプログラムに対して、Windows11の環境下でMakeCSV1を実行するバッチ・ファイルの一例を、以下に示します。

rem MakeCSV1の実行
c:
cd \user\java\MakeCSV1
java MakeCSV1
pause

なお、実行環境としては、以下の条件で安定動作することを確認しています。

CPU:     intel Core i3-1005G1
MEMORY:  8GB
STRAGE:  117GB SSD
DISPLAY: 1920x1080px (set up 150%)
OS:      Windows11 Home
JDK:     JDK18

◆MakeCSV1.java

//▼▼MakeCSV1.java
//帳票用 CSV データ作成（見積書、請求書など）
//作表印刷処理は、サブ・システム（PrintCSV1）の呼び出しにて行なう

//■■TEST ENVIRONMENT
//CPU:     intel Core i3-1005G1
//MEMORY:  8GB
//STRAGE:  117GB SSD
//DISPLAY: 1920×1080 px (setup 150%: 1280×720 px)
//OS:      Windows11 Home
//JDK:     JDK18
//「タスク バーを自動的に隠す」に設定

//なお、一般的なディスプレイの解像度は、以下の通り。
//8K（7680×4320 dot）
//4K（3840×2160）
//WQHD（2560×1440）
//FHD（1920×1080）
//WXGA++(1600×900）
//HD（1280×720）
//ただし、
//FHD（1920×1080）が最多であり事実上の標準である。
//OSのセットアップ条件で、ソフトウェア上の解像度（拡大縮小度）が変わる。

/* display image
+----------------------------------------------------------------+
|   TITLE BAR        （プログラム名を配置）                      |
+----------------------------------------------------------------+
|   MENU  BAR        （メニューを配置）                          |
+----------------------------------------------------------------+
|   TOOL  BAR        （ボタンを配置）                            |
+----------------------------------------------------------------+
|   TABLE PANEL      （細目テーブルを配置）                      |
|                                                                |
|                                                                |
| - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  |
|                    （合計テーブルを配置）                      |
|                                                                |
+----------------------------------------------------------------+
|   INPUT PANEL-1    （テキスト・フィールドを配置）              |
+----------------------------------------------------------------+
|   INPUT PANEL-2    （テキスト・フィールドを配置）              |
+----------------------------------------------------------------+
|   INPUT PANEL-3    （テキスト・フィールドを配置）              |
+----------------------------------------------------------------+
|   MESSAGE PANEL    （テキスト・エリアを配置）                  |
+----------------------------------------------------------------+
*/

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;
import javax.swing.border.*;
import java.util.*;
import java.io.*;

//■■フレーム定義
public class MakeCSV1 extends JFrame implements SystemMessage
{
    //■メンバー変数
    MakeCSV1 f;
    StackTraceMonitor st;
    Container pane;
    MakeCSV1DataBase db;
    MakeCSV1MenuLis menuLis;
    MakeCSV1ToolLis toolLis;
    MakeCSV1InputLis inputLis;

    JPanel   tablePanel, inputPanel1, inputPanel2, inputPanel3, messagePanel;
    JMenuBar menuBar;
    JToolBar toolBar;
    JTable   table, table2;
    JTextField customerCode, customerName, customerRate, date;
    JTextField itemCode, itemName, basePrice, realRate, unitPrice, quantity,
        sumPrice, taxRate, taxedPrice;
    DefaultTableModel tModel, tModel2;
    JTextArea  messageArea;

    int t1Width; //細目表(px)
    int t1Height;
    int t2Width; //合計表(px)
    int t2Height;
    int mLines; //メッセージ欄（行数）
    int mCols; //メッセージ欄（字数）

    File dataFile = null;
    File idFile = null;
    File formFile = null;
    final String dataExt = "csv"; //データファイルの拡張子（標準設定）
    final String formExt = "pcc"; //PrintCSV1書式定義ファイルの拡張子
    final String manual = "./sys/MakeCSV1Manual.txt"; //使用法マニュアル
    static String printDir;

    //■エントリーポイント
    public static void main(String args[])
    {
        if(args.length==1)
            printDir = args[0];
        else
            printDir = ".";
        //●画面構築部の生成
        new MakeCSV1();
    }

    //■コンストラクタ
    MakeCSV1()
    {
        super("MakeCSV1  Ver.1.0");
        pane = getContentPane();
        f = this;
        (st = new StackTraceMonitor(this)).start();
          //スタックトレースの表示をメッセージ・エリアに変更する。
          //スタックトレースを標準エラー出力のままにしたい場合、
          //この一行をコメントにして無効にする。

        //●固有機能部の生成
        db = new MakeCSV1DataBase(this);

        //●要求受信部の生成（リスナー）
        menuLis = new MakeCSV1MenuLis(this);
        toolLis = new MakeCSV1ToolLis(this);
        inputLis = new MakeCSV1InputLis(this);

        //●プログラムの開始と終了
        addWindowListener(new WindowAdapter()
        {
            public void windowActivated(WindowEvent e)
            { //フレームのアクティブ化
            }
        });
        setDefaultCloseOperation(EXIT_ON_CLOSE);

        //●スウィングで表示される文字数や行数や大きさ。
        //それぞれの実行環境に合わせて数値を調整すること。
        //下記の数値設定は、簡易設定。
        if(getToolkit().getScreenSize().width >= 1280 &&
           getToolkit().getScreenSize().height >= 720)
        {
            t1Width = 1230; //細目表(px)
            t1Height = 330;
            t2Width = 1230; //合計表(px)
            t2Height = 20;
            mLines = 5; //メッセージ欄（行数）
            mCols = 136; //メッセージ欄（字数）
         }
         else
         {
            t1Width = 800; //細目表(px)
            t1Height = 220;
            t2Width = 800; //合計表(px)
            t2Height = 20;
            mLines = 4; //メッセージ欄（行数）
            mCols = 88; //メッセージ欄（字数）
         }

        //●全体レイアウト
        setJMenuBar(menuBar = new JMenuBar());
        pane = getContentPane();
        Box box1 = Box.createVerticalBox(); //ボックス・レイアウト
        pane.add(box1);
        box1.add(toolBar     = new JToolBar());
        box1.add(tablePanel  = new JPanel());
        box1.add(inputPanel1 = new JPanel());
        box1.add(inputPanel2 = new JPanel());
        box1.add(inputPanel3 = new JPanel());
        box1.add(messagePanel= new JPanel());

        //●MENU BAR のレイアウト
        JMenu menu;
        addA(menuBar, menu = new JMenu("ファイル"));
        addB(menu, new JMenuItem("開く"), menuLis, "Load");
        addB(menu, new JMenuItem("保存"), menuLis, "Save");
        addB(menu, new JMenuItem("印刷"), menuLis, "Print");
        addA(menuBar, menu = new JMenu("消去"));
        addB(menu, new JMenuItem("警告文消去"), menuLis, "Erase");
        addA(menuBar, menu = new JMenu("ヘルプ"));
        addB(menu, new JMenuItem("使用法"), menuLis, "Usage");

        //●TOOL BAR のレイアウト
        //JTable を行単位で編集するボタン等の配置
        toolBar.setFloatable(false);            //ツールバーの位置固定
        toolBar.setBackground(new Color(255,255,100));    //背景色黄色
        toolBar.setLayout(new FlowLayout(FlowLayout.CENTER));//中央寄せ
        toolBar.setMaximumSize(new Dimension(Short.MAX_VALUE, 10));
        addB(toolBar, new JButton("選択解除"), toolLis, "clearSelection");
        addB(toolBar, new JButton("全行削除"), toolLis, "clear");
        addB(toolBar, new JButton("一行削除"), toolLis, "cut");
        addB(toolBar, new JButton("一行記憶"), toolLis, "copy");
        addB(toolBar, new JButton("一行挿入"), toolLis, "paste");
        addB(toolBar, new JButton("空行追加"), toolLis, "append");
        addB(toolBar, new JButton("計算検証"), toolLis, "verify");

        //●TABLE PANEL のレイアウト
        //▼品目逐次追加記録表
        tablePanel.setBackground(new Color(255,255,150));

        String headerName[] =   //テーブルの見出し
            {"品番","品名","単価","数量","金額","税率","税込"};
        tModel = new DefaultTableModel(headerName, 0); //データ格納
        table = new JTable();   //テーブル生成
        table.setModel(tModel); //データ格納オブジェクトの設定
        table.setRowHeight(20); //行の高さ pixel
        table.setPreferredScrollableViewportSize(
            new Dimension(t1Width,t1Height)); //表示範囲 pixel
        table.setAutoscrolls(true);  //自動スクロールを指定

        DefaultTableColumnModel cModel =  //全体カラム管理を取得
            (DefaultTableColumnModel)table.getColumnModel();
        for(int i=0; i<7; i++) //全カラムに対し
        {
            TableColumn col = cModel.getColumn(i); //個別カラム管理
            col.setCellRenderer(new Ren(this)); //カラム毎に表現方法を指定
        }
        JTableHeader header = table.getTableHeader(); //ヘッダ管理
        header.setResizingAllowed(true);   //カラム巾の変更操作可
        header.setReorderingAllowed(false); //カラム順の変更操作不可
        JScrollPane scroll = new JScrollPane(
            table,
            JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
            JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);

        //▼合計記録表
        String headerName2[] =   //テーブルの見出し
            {"","","","","合計金額","","税込総額"};
        tModel2 = new DefaultTableModel(headerName2, 0); //データ格納
        table2 = new JTable();   //テーブル生成
        table2.setModel(tModel2); //データ格納オブジェクトの設定
        table2.setRowHeight(20); //行の高さ pixcel
        table2.setPreferredScrollableViewportSize(
            new Dimension(t2Width,t2Height)); //表示範囲pixel
        table.setAutoscrolls(false);  //

        DefaultTableColumnModel cModel2 =  //全体カラム管理オブジェクト取得
            (DefaultTableColumnModel)table2.getColumnModel();
        for(int i=0; i<7; i++) //全カラムに対し
        {
            TableColumn col = cModel2.getColumn(i); //個別カラム管理
            col.setCellRenderer(new Ren(this)); //カラム毎に表現方法を指定
        }
        JTableHeader header2 = table2.getTableHeader(); //ヘッダ管理
        header2.setResizingAllowed(true);   //カラム巾の変更操作可
        header2.setReorderingAllowed(false); //カラム順の変更操作不可
        JScrollPane scroll2 = new JScrollPane(
            table2,
            JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
            JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);

        Box box2 = Box.createVerticalBox(); //ボックス・レイアウト
        tablePanel.add(box2);
        box2.add(scroll);
        box2.add(scroll2);

        String data[] =
            {"Total","","","","","","",""};
        tModel2.addRow(data);

        //●INPUT PANEL-1 のレイアウト
        inputPanel1.setBackground(new Color(100,255,255));
        Box box3 = Box.createHorizontalBox(); //ボックス・レイアウト
        inputPanel1.add(box3);

        box3.add(new JLabel("取引先コード"));
        box3.add(customerCode = new JTextField(10));
        customerCode.addActionListener(inputLis);
        customerCode.setActionCommand("cCode");
        Util.kanjiIn(customerCode, false); //漢字変換なし

        box3.add(new JLabel("　取引先名"));
        box3.add(customerName = new JTextField(25));
        customerName.addActionListener(inputLis);
        customerName.setActionCommand("cName");
        Util.kanjiIn(customerName, true); //漢字変換モード

        box3.add(new JLabel("　基準掛率"));
        box3.add(customerRate = new JTextField(10));
        customerRate.addActionListener(inputLis);
        customerRate.setActionCommand("cRate");
        Util.kanjiIn(customerRate, false); //漢字変換なし

        box3.add(new JLabel("　年月日"));
        box3.add(date = new JTextField(15));
        date.addActionListener(inputLis);
        date.setActionCommand("date");
        Util.kanjiIn(date, false); //漢字変換なし

        //●INPUT PANEL-2 のレイアウト
        //商品１件分の入力（前半）
        inputPanel2.setBackground(new Color(150,255,255));
        Box box4 = Box.createHorizontalBox(); //ボックス・レイアウト
        inputPanel2.add(box4);

        box4.add(new JLabel("品番"));
        addC(box4, itemCode = new JTextField(10), inputLis, "iCode");
        Util.kanjiIn(itemCode, false); //漢字変換なし

        box4.add(new JLabel("　品名"));
        addC(box4, itemName = new JTextField(25), inputLis, "iName");
        Util.kanjiIn(itemName, true); //漢字変換モード

        box4.add(new JLabel("　定価"));
        addC(box4, basePrice = new JTextField(10), inputLis, "bPrice");
        Util.kanjiIn(basePrice, false); //漢字変換なし

        box4.add(new JLabel("　税率"));
        addC(box4, taxRate = new JTextField(5), inputLis, "tRate");
        Util.kanjiIn(taxRate, false); //漢字変換なし

        //●INPUT PANEL-3 のレイアウト
        //商品１件分の入力（後半）
        inputPanel3.setBackground(new Color(200,255,255));
        Box box5 = Box.createHorizontalBox(); //ボックス・レイアウト
        inputPanel3.add(box5);

        box5.add(new JLabel("　実効掛率"));
        addC(box5, realRate = new JTextField(5), inputLis, "rRate");
        Util.kanjiIn(realRate, false); //漢字変換なし

        box5.add(new JLabel("　単価"));
        addC(box5, unitPrice = new JTextField(10), inputLis, "uPrice");
        Util.kanjiIn(unitPrice, false); //漢字変換なし

        box5.add(new JLabel("　数量"));
        addC(box5, quantity = new JTextField(10), inputLis, "quantity");
        Util.kanjiIn(quantity, false); //漢字変換なし

        box5.add(new JLabel("　金額"));
        addC(box5, sumPrice = new JTextField(15), inputLis, "sPrice");
        Util.kanjiIn(sumPrice, false); //漢字変換なし

        box5.add(new JLabel("　税込金額"));
        addC(box5, taxedPrice = new JTextField(15), inputLis, "tPrice");
        Util.kanjiIn(taxedPrice, false); //漢字変換なし

        //●MESSAGE PANEL のレイアウト
        messageArea = new JTextArea(mLines,mCols);
        messageArea.setBackground(new Color(230,230,255));
        messageArea.setMargin(new Insets(3, 3, 3, 3)); //内部余白
        JScrollPane scroll3 = new JScrollPane(
            messageArea,
            JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
            JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
        messageArea.setAutoscrolls(true);
        messageArea.setEditable(false); //編集禁止
        messagePanel.add(scroll3);
        messagePanel.setBackground(new Color(255,150,150));

        //●その他の処理
        pack();
        setVisible(true);
        customerCode.requestFocus();

        GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
        int screenW = gd.getDisplayMode().getWidth();
        int screenH = gd.getDisplayMode().getHeight();
        message("hardware screen size = " + screenW + " × " + screenH + " px");
        //上記スクリーン・サイズは、ハードウェアとしての本来の数値。

        screenW = getToolkit().getScreenSize().width;
        screenH = getToolkit().getScreenSize().height;
        message("software screen size = " + screenW + " × " + screenH + " px");
        //上記スクリーン・サイズは、OSでセットアップされたソフトウェアでの数値。
    }

    //■システムメッセージの表示
    public void message(String s)
    {
        int len = messageArea.getText().length();
        messageArea.select(len, len); //自動スクロールを実行
        messageArea.append(s + "\n");
    }

    //■コンテナーに JMenu を配置
    public void addA(Container cont, AbstractButton b)
    {
        cont.add(b);
    }
    //■コンテナーに JButton, JMenuItem を配置
    public void addB(Container cont, AbstractButton b,
        ActionListener lis, String command)
    {
        cont.add(b);
        if(lis!=null)
            b.addActionListener(lis);
        if(command!=null)
            b.setActionCommand(command);
    }
    //■レイアウト・ボックスに JTextField を配置
    public void addC(Box box, JTextField field,
        ActionListener lis, String command)
    {
        box.add(field);
        if(lis!=null)
            field.addActionListener(lis);
        if(command!=null)
            field.setActionCommand(command);
    }

    //■JTable の表示形式を変更
    class Ren extends DefaultTableCellRenderer
    { //このスーパークラスは JLabel
        MakeCSV1 f = null;
        Ren(MakeCSV1 frame)
        {
            f = frame;
        }
        //▼セル内情報が表示されるたびに呼び出されるメソッド
        public Component getTableCellRendererComponent(
            JTable table, Object value, boolean isSelected,
            boolean hasFocus, int row, int col)
        {
            setValue(value);        //セルに文字列表示
            if(isSelected && (table==f.table))
                setBackground(Color.lightGray); //セルが選択行ならグレー
            else
                setBackground(Color.white);     //それ以外は白
            if(col>=2 && col<=6)
                setHorizontalAlignment(RIGHT);  //数値カラムは右寄せ
            else
                setHorizontalAlignment(LEFT);   //その他は左寄せ
            return this; //変更した表示オブジェクト（拡張JLabel）を返す
        }
    }
}

◆MakeCSV1MenuLis.java

//▼▼MakeCSV1MenuLis.java
//▼▼リスナー部・メニューバー区画対応

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.io.*;

//■■JMenuItemのイベントリスナー
class MakeCSV1MenuLis implements ActionListener
{
    //■メンバー変数
    MakeCSV1 f;

    //■コンストラクタ
    MakeCSV1MenuLis(MakeCSV1 frame)
    {
        f = frame;
    }

    //■JMenuItemのイベントハンドラ
    public void actionPerformed(ActionEvent event)
    {
        String com = event.getActionCommand();
        if(com.equals("Load"))          //データ読込
            f.db.load();
        else if(com.equals("Save"))     //データ保存
            f.db.save();
        else if(com.equals("Erase"))     //警告消去
            f.messageArea.setText("");
        else if(com.equals("Print"))    //印刷
        {
            f.db.total();
            PrintCSV1 pCSV = new PrintCSV1(f, f.st);
            pCSV.canvas.setDef(f.db.makeCommands());
            pCSV.dArea.setText(f.db.getTableData());
        }
        else if(com.equals("Usage"))    //使用法表示
            Util.textBrowseCCP(f, new File(f.manual));
    }
}

◆MakeCSV1ToolLis.java

//▼▼MakeCSV1ToolLis.java
//▼▼ツールバー区画対応リスナー

import java.awt.event.*;

//■■JButtonのイベントリスナー
class MakeCSV1ToolLis implements ActionListener
{
    //■メンバー変数
    private MakeCSV1 f;
    private final int cols = 7; //テーブルのカラム数
    private String[] buff = new String[cols]; //テーブル１行分を一時記憶
    private String[] buff2 = new String[cols]; //テーブル１行分の空行

    //■コンストラクタ
    MakeCSV1ToolLis(MakeCSV1 frame)
    {
        f = frame;
    }

    //■JButtonのイベントハンドラ
    public void actionPerformed(ActionEvent event)
    {
        String com = event.getActionCommand();
        for(int i=0; i<cols; i++) //空行の中身を設定
            buff2[i] = new String("");

        if(com.equals("clear")) //テーブルの全行削除、取引先のクリア
        {
            while(0<f.tModel.getRowCount())
                f.tModel.removeRow(0);
            f.db.total();
            f.customerCode.setText(""); //取引先のクリア
            f.customerName.setText("");
            f.customerRate.setText("");
            f.date.setText("");
        }
        else if(com.equals("clearSelection")) //テーブルの全行削除、取引先のクリア
        {
                f.table.clearSelection();
                f.db.total();
        }
        else if(com.equals("cut")) //テーブルの行削除と記憶
        {
            int row = f.table.getSelectedRow(); //選択状態の行の位置を取得
            if(row>=0 && row<f.table.getRowCount()) //行位置が適性範囲なら
            {
                for(int i=0; i<cols; i++) //選択状態の行を一時記憶
                    buff[i] = new String((String)f.table.getValueAt(row, i));
                f.tModel.removeRow(row); //行削除
                f.table.clearSelection();
                f.db.total();
            }
        }
        else if(com.equals("copy")) //テーブルの行記憶
        {
            int row = f.table.getSelectedRow();
            if(row>=0 && row<f.table.getRowCount())
            {
                for(int i=0; i<cols; i++)
                    buff[i] = new String((String)f.table.getValueAt(row, i));
                f.table.clearSelection();
            }
        }
        else if(com.equals("paste")) //テーブルに行挿入
        {
            int row = f.table.getSelectedRow();
            if(row>=0 && row<f.table.getRowCount())
            {
                f.tModel.insertRow(row, buff); //行挿入
                f.table.clearSelection();
                f.db.total();
            }
            else
            {
                f.tModel.addRow(buff);
                f.db.total();
            }
        }
        else if(com.equals("append")) //テーブルに空行挿入
        {
            int row = f.table.getSelectedRow();
            if(row>=0 && row<f.table.getRowCount())
            {
                f.tModel.insertRow(row, buff2); //行挿入
                f.table.clearSelection();
            }
            else
                f.tModel.addRow(buff2);
        }
        else if(com.equals("verify")) //計算検証
        //品番や品名の検証はせず、明細表全体の数値計算合理性を検証
        {
            f.db.total();
            f.db.verify();
        }
    }
}

◆MakeCSV1InputLis.java

//▼▼MakeCSV1InputLis.java
//インプット区画対応リスナー

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;
import java.io.*;
import java.math.*;
import java.util.*;

//■■JTextFieldのイベントリスナー
class MakeCSV1InputLis implements ActionListener
{
    //■メンバー変数
    MakeCSV1 f;
    String [] cRec = new String[3]; //登録取引先データ
        //取引先コード・取引先名・基準掛率
    String [] iRec = new String[4]; //登録商品データ
        //品番・品名・定価・税率
    String [] iCSV = new String[7]; //帳票作成用データ
        //品番・品名・単価・数量・金額・税率・税込金額

    //■コンストラクタ
    MakeCSV1InputLis(MakeCSV1 frame)
    {
        f = frame;
    }

    //■JTextFieldのイベント・ハンドラ
    public void actionPerformed(ActionEvent event)
    {
        String com = event.getActionCommand();
        // 以下は、取引先および発行日の入力
        // JTextField customerCode, customerName, customerRate, date,
        if(com.equals("cCode"))
        { //●取引先コード customerCode
            if(f.customerCode.getText().trim().equals("") ||
               f.customerCode.getText().trim().equals("**") ) //コード未登録
            {
                f.customerCode.setText("**");
                f.customerName.setText("");
                f.customerRate.setText("**");
                f.date.setText("");
                f.customerName.requestFocus();
            }
            else
            {
                try
                {
                    cRec = f.db.getRecord(
                        "customer.csv",f.customerCode.getText().trim());
                    f.customerName.setText(cRec[1]); //取引先名のセット
                    f.customerRate.setText(cRec[2]); //基準掛率のセット
                    f.date.setText("");
                    f.customerName.requestFocus();
                }
                catch(Exception e)
                {
                    f.customerName.setText(""); //取引先名のセット
                    f.customerRate.setText(""); //基準掛率のセット
                    f.date.setText(""); //年月日のセット
                    f.message("取引先データベース検索エラー："+e);
                    f.customerCode.requestFocus();
                }
            }
        }
        else if(com.equals("cName"))
        { //●取引先名 customerName
            if(f.customerName.getText().trim().equals(""))
                f.customerName.requestFocus();  //再入力の要求
            else
                f.customerRate.requestFocus(); //名称を確認するか、名称を書き込む
        }
        else if(com.equals("cRate"))
        { //●取引先基準掛率％ customerRate
            if(f.customerRate.equals("") || f.customerRate.equals("**"))
                f.customerRate.setText("**");
            else
                f.date.requestFocus(); //数値を承認するか、「**」を書き込む
        }
        else if(com.equals("date"))
        { //●年月日 date
            if(f.date.getText().trim().equals("") ||
               f.date.getText().trim().equals("**") ) //
            {
                GregorianCalendar calen = new GregorianCalendar();
                int year  = calen.get(Calendar.YEAR);
                int month = calen.get(Calendar.MONTH)+1;
                int day   = calen.get(Calendar.DAY_OF_MONTH);
                String Month = "";
                String Day   = "";
                if(month<10)
                    Month = "0" + month;
                else
                    Month = "" + month;
                if(day<10)
                    Day = "0" + day;
                else
                    Day = "" + day;
                f.date.setText( ""+year+"."+Month+"."+Day); //年月日のセット
                f.date.requestFocus(); //確認要求
            }
            else
                f.itemCode.requestFocus(); //日付を承認するか、日付を書き込む
        }

        // 以下は、商品データのインプット
        //itemCode, itemName, taxRate, basePrice, realRate, unitPrice, quantity,
        else if(com.equals("iCode"))
        { //●商品コード itemCode
            if( f.itemCode.getText().trim().equals("") ||
                f.itemCode.getText().trim().equals("**")) //コード未登録
            {
                f.itemCode.setText("**");  //商品コードなしの場合（別注品や規格外品）
                f.basePrice.setText("**"); //定価なしの場合（別注品や規格外品）
                f.realRate.setText("**");  //掛率なしの場合（単価個別設定）
                f.itemName.requestFocus();
            }
            else
            {
                try
                {
                    iRec = f.db.getRecord(
                        "item.csv",f.itemCode.getText().trim());
                    f.itemName.setText(iRec[1]);
                    f.basePrice.setText(iRec[2]);
                    f.taxRate.setText(iRec[3]);
                    f.realRate.setText(f.customerRate.getText());
                    f.realRate.requestFocus();
                }
                catch(Exception e)
                {
                    f.itemName.setText("");
                    f.message("商品データベース検索エラー："+e);
                }
            }
        }
        else if(com.equals("iName"))
        { //●商品名 itemName
            if(f.itemName.getText().trim().equals(""))
                f.itemName.requestFocus();  //再入力要求
            else
                f.taxRate.requestFocus(); //次に進む
        }
        else if(com.equals("tRate"))
        { //●消費税の税率％ taxRate
            if(f.taxRate.getText().trim().equals(""))
                f.taxRate.requestFocus();  //再入力要求
            else
                f.realRate.requestFocus(); //次に進む
        }

        else if(com.equals("bPrise"))
        { //●定価（メーカー希望小売価格） basePrice
            if(f.basePrice.getText().trim().equals(""))
                f.basePrice.requestFocus();  //再入力要求
            else
                f.realRate.requestFocus(); //次に進む
        }
        else if(com.equals("rRate"))
        { //●実効掛率％ realRate
            String s = f.realRate.getText().trim();
            if(s.equals("") || s.equals("**") )
            {
                f.realRate.setText("**");
                s="**";
            }
            try
            {
                int iRate = Integer.parseInt(f.realRate.getText().trim());
                int iPrice = Integer.parseInt(iRec[2].trim()); //定価データ取得
                f.unitPrice.setText("" + (iPrice*iRate)/100 );
                f.unitPrice.requestFocus(); //次に進む
            }
            catch(Exception e)
            {
                if(s.trim().equals("**"))
                    f.unitPrice.requestFocus(); //単価入力とみなし、次に進む
                else
                {
                    f.realRate.setText("");
                    f.realRate.requestFocus();  //再入力要求
                }
            }
        }
        else if(com.equals("uPrice"))
        { //●単価 unitPrice
            try
            {
                Integer.parseInt(f.unitPrice.getText().trim()); //数値チェック
                f.quantity.requestFocus(); //次に進む
            }
            catch(Exception e)
            {
                f.unitPrice.setText("");
                f.unitPrice.requestFocus(); //再入力要求
            }
        }
        else if(com.equals("quantity"))
        { //●数量 quantity
            try
            {
                Integer.parseInt(f.quantity.getText().trim()); //数値チェック
            }
            catch(NumberFormatException e) //整数以外
            {
                f.quantity.setText("");
                f.quantity.requestFocus(); //再入力要求
            }
            long l_unitPrice =0;
            long l_quantity =0;
            double d_taxRate = 0.0;
            try
            {
                //金額計算、税込金額計算
                l_unitPrice = Long.parseLong(f.unitPrice.getText().trim());
                l_quantity  = Long.parseLong(f.quantity.getText().trim());
                f.sumPrice.setText( ""+ (l_quantity * l_unitPrice));
                d_taxRate = (Double.parseDouble(f.taxRate.getText().trim()))/100;
                f.taxedPrice.setText( ""+ (int)(l_quantity * l_unitPrice *(1+d_taxRate)));
                f.taxedPrice.requestFocus(); //次に進む
            }
            catch(NumberFormatException e) //整数以外
            {
                 f.sumPrice.setText("");
                 f.quantity.setText("");
                 f.quantity.requestFocus(); //数量再入力要求
            }
        }
        else if(com.equals("tPrice"))
        { //●税込 taxedPrice（確認のエンター・キーが押される）
            int row = f.table.getRowCount(); //現在のデータ件数を取得
            Rectangle r = f.table.getCellRect(row, 0, false);
                                                //オブジェクト形式変換
            if(r != null)
                f.table.scrollRectToVisible(r); //現在の行にスクロール
            //▼テーブルへの追加登録
            iCSV[0] = f.itemCode.getText().trim();    //品番
            iCSV[1] = f.itemName.getText().trim();    //品名
            iCSV[2] = f.unitPrice.getText().trim();   //単価
            iCSV[3] = f.quantity.getText().trim();    //数量
            iCSV[4] = f.sumPrice.getText().trim();    //金額
            iCSV[5] = f.taxRate.getText().trim();     //税率
            iCSV[6] = f.taxedPrice.getText().trim();  //税込
            f.table.clearSelection();
            f.tModel.addRow(iCSV); //データ１件追加
            //▼入力欄テキスト・フィールドのクリア
            f.itemCode.setText("");    //品番
            f.itemName.setText("");    //品名
            f.basePrice.setText("");   //定価（対外非公表）
            f.realRate.setText("");    //掛率（対外非公表）
            f.unitPrice.setText("");   //単価
            f.quantity.setText("");    //数量
            f.sumPrice.setText("");    //金額
            f.taxRate.setText("");     //税率
            f.taxedPrice.setText("");  //税込
            f.db.total();              //合計計算
            f.itemCode.requestFocus(); //次の段階に進む
        }
    }
}

◆MakeCSV1DataBase.java

//▼▼MakeCSV1DataBase.java
//▼▼データベース操作とテーブルデータ操作

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;
import java.io.*;
import java.util.*;

class MakeCSV1DataBase
{
    //■メンバー変数
    MakeCSV1 f;
    String rec[] = null;

    //■コンストラクタ
    MakeCSV1DataBase(MakeCSV1 frame)
    {
        f = frame;
    }

    //■データ読込み
    //データファイル(.csv)を分解し、JTableにデータを入れる。
    //それ以外の関連情報は印刷制御情報(.pcc)ファイルから得て、
    //JTextFieldに入れる。
    void load()
    {
        JFileChooser fileChooser = new JFileChooser("./csv");
        SingleFileFilter filter = new SingleFileFilter(f.dataExt);
        fileChooser.setFileFilter(filter);
        int ret = fileChooser.showOpenDialog(f.pane);
        f.dataFile = fileChooser.getSelectedFile();
        if(ret == JFileChooser.APPROVE_OPTION)
        {
            String s = f.dataFile.getName();
            String baseName = s.substring(0,s.indexOf("."));
            f.formFile = new File("./pcc/" + baseName + ".pcc");
        }
        else
        {
            f.message("読込中止");
            return;
        }
        BufferedReader reader = null;
        BufferedReader reader2 = null;
        try
        {
            reader = new BufferedReader(new FileReader(f.dataFile)); //読込む用意
            reader2 = new BufferedReader(new FileReader(f.formFile)); //読込む用意
            try
            {
                String line = "";
                String [] rowData = new String[7];
                while(0<f.tModel.getRowCount())
                    f.tModel.removeRow(0);
                for (;;) //データ読取り
                {
                    line = reader.readLine();
                    if(line == null) //終端超越
                        break;
                    else if(line.startsWith("******"))
                    {
                        total();
                        break;
                    }
                    rowData = Util.csv2Array(line,7); //CSVを分解
                    //品番、品名、単価、数量、金額、税率、税込
                    if(!line.startsWith("******"))
                        f.tModel.addRow(rowData); //Jtableにデータ１件追加
                }
                for(;;) //書式定義読取り
                {
                    String define = reader2.readLine();
                    if(define == null) //終端超越
                        break;
                    StringTokenizer st = new StringTokenizer(define,"()\"[] 殿");
                    String method = st.nextToken().trim();

                    if(method.equals("Subtitle"))
                    {
                        String s = st.nextToken().trim(); //顧客コード
                        f.customerCode.setText(s);  //顧客コード
                        f.customerName.setText(st.nextToken().trim());  //顧客名
                        if(s.equals("**") || s.equals(""))
                        {
                            f.customerCode.setText("**");
                            f.customerRate.setText("**");
                        }
                        else
                        {
                            String [] cRec = new String[3]; //登録取引先データ
                            cRec = f.db.getRecord(
                                "customer.csv",s);
                            f.customerRate.setText(cRec[2]); //標準掛率のセット
                        }
                    }
                    else if(method.equals("Number"))
                        f.date.setText(st.nextToken().trim());          //日付
                }
            }
            catch(IOException e)
            {
                f.message("読取エラー：" +e);
            }
        }
        catch(FileNotFoundException e)
        {
            f.message("オープンエラー："+e);
        }
        closeBR(reader);
        closeBR(reader2);
    } //load()

    void closeBR(BufferedReader r)
    {
        try
        {
            if(r!=null)
                r.close();
        }
        catch(IOException e)
        {
            f.message("クローズエラー："+e);
        }
    }

    //■データ保存
    //JTableのセル情報は合計行も付加し、CSV形式で保存される。
    //それ以外の関連情報は印刷制御情報に含め、別ファイルに保存される。
    void save()
    {
        JFileChooser fileChooser = new JFileChooser("./csv");
        SingleFileFilter filter = new SingleFileFilter(f.dataExt);
        fileChooser.setFileFilter(filter);
        int ret = fileChooser.showSaveDialog(f.pane);
        f.dataFile = fileChooser.getSelectedFile();
        if(ret == JFileChooser.APPROVE_OPTION)
        {
            String s = f.dataFile.getName();
            String baseName = s.substring(0,s.indexOf("."));
            f.formFile = new File("./pcc/" + baseName +".pcc");
        }
        else
        {
            f.message("保存中止");
            return;
        }
        BufferedWriter writer = null;
        BufferedWriter writer2 = null;
        try
        {
            writer = new BufferedWriter(new FileWriter(f.dataFile)); //書込む用意
            writer2 = new BufferedWriter(new FileWriter(f.formFile)); //書込む用意
            try
            {
                writer.write(getTableData());
                writer2.write(makeCommands());
            }
            catch(IOException e)
            {
                f.message("書込エラー：" +e);
            }
            finally
            {
                writer.close();
                writer2.close();
            }
        }
        catch(FileNotFoundException e)
        {
            f.message("オープンエラー："+e);
        }
        catch(IOException e)
        {
            f.message("クローズエラー："+e);
        }
        closeBW(writer, f.dataFile.getName());
        closeBW(writer2, f.formFile.getName());
    } //save()

    String getTableData()
    {
      int rows = f.table.getRowCount();
      String s="";
      for(int i=0; i<rows; i++) //JTable１行を分解
      {
          s = s + (String)f.table.getValueAt(i,0) + ",";   //品番
          s = s + (String)f.table.getValueAt(i,1) + ",";   //品名
          s = s + (String)f.table.getValueAt(i,2) + ",";   //単価
          s = s + (String)f.table.getValueAt(i,3) + ",";   //数量
          s = s + (String)f.table.getValueAt(i,4) + ",";   //金額
          s = s + (String)f.table.getValueAt(i,5) + ",";   //税率
          s = s + (String)f.table.getValueAt(i,6) + "\n";  //税込
      }
      s = s + "******,";                               //品番
      s = s + "合計,";                                 //品名
      s = s + ",";                                     //単価
      s = s + ",";                                     //数量
      s = s + (String)f.table2.getValueAt(0,4) + ",";  //金額合計
      s = s + ",";                                     //税率
      s = s + (String)f.table2.getValueAt(0,6) +"\n";  //税込合計
      return s;
    }

    String makeCommands()
    {
      String s ="";
      s = s + "Paper(\"A4\");\n";
      s = s + "DataType(\"CSV\");\n";
      s = s + "LineWidth(0.3);\n";
      s = s + "FontSize(12);\n";
      s = s + "FontBold(true);\n";
      s = s + "Margin(15,15);\n";
      s = s + "PageHeader(25);\n";
      s = s + "Number(\"" + f.date.getText() + "\");\n";
      s = s + "PageNumber(true);\n";
      s = s + "Title(\"計算明細書\");\n";
      s = s + "Subtitle([" +
          f.customerCode.getText() + "]" +
          f.customerName.getText() + "殿);\n";
      s = s + "TableHeader(10);\n";
      s = s + "Row(9,25);\n";
      s = s + "Column(20,L,\"品番\");\n";
      s = s + "Column(55,L,\"品名\");\n";
      s = s + "Column(20,N0,\"単価\");\n";
      s = s + "Column(20,N0,\"数量\");\n";
      s = s + "Column(25,N0,\"金額\");\n";
      s = s + "Column(15,N0,\"税率\");\n";
      s = s + "Column(25,N0,\"税込\");\n";
      s = s + "Footnote(\"【日本総合商事】\");\n";
      return s;
    }
    void closeBW(BufferedWriter w, String s)
    {
        try
        {
            if(w!=null)
                w.close();
            f.message(s + " に書込ました");
        }
        catch(IOException e)
        {
            f.message("クローズエラー："+e);
        }
    }

    String [] getRecord(String type, String code)
    {
        if(type.equals("customer.csv"))
        { //取引先データ・ベースを検索
            return Util.searchCSV("./sys/customer.csv", code, 3, 0);
        }
        else if(type.equals("item.csv"))
        { //商品データ・ベースを検索
            code = code.toUpperCase(); //小文字が混じれば、大文字に変換。
            f.itemCode.setText(code);
            return Util.searchCSV("./sys/item.csv", code, 4, 0);
        }
        return null;
    }

    //■合計計算
    void total()
    {
        int rows = f.table.getRowCount();
        String s1 = "", s2 = "";
        int total1 = 0, total2 = 0;
        try
        {
            for(int i=0; i<rows; i++) //合計計算
            {
                s1 = (String)f.table.getValueAt(i,4);  //金額
                total1 += Integer.parseInt(s1.trim()); //金額合計

                s2 = (String)f.table.getValueAt(i,6);  //税込
                total2 += Integer.parseInt(s2.trim()); //税込合計
            }
            f.tModel2.setValueAt(Integer.toString(total1),0,4);
            f.tModel2.setValueAt(Integer.toString(total2),0,6);
        }
        catch(Exception e)
        {
            f.message("「合計計算」で数値表現エラー。");
        }
    }

    //■計算検証
    void verify()
    { //単価、数量、金額、税率、税込、これらの相互整合性のみを検証
      //unitPrice, quantity, sumPrice, taxRate, taxedPrice;
        f.message("数値検証開始。");
        int uPriceX, quantityX, sPriceX, tRateX, tPriceX;
        String iCodeX="", iNameX="";
        int rows = f.table.getRowCount();
        int i=0;
        try
        {
            for(i=0; i<rows; i++) //各行を計算検証
            {
                iCodeX    = (String)f.table.getValueAt(i,0);                   //品番
                iNameX    = (String)f.table.getValueAt(i,1);                   //品名
                uPriceX   = Integer.parseInt((String)f.table.getValueAt(i,2)); //単価
                quantityX = Integer.parseInt((String)f.table.getValueAt(i,3)); //数量
                sPriceX   = Integer.parseInt((String)f.table.getValueAt(i,4)); //金額
                tRateX    = Integer.parseInt((String)f.table.getValueAt(i,5)); //税率
                tPriceX   = Integer.parseInt((String)f.table.getValueAt(i,6)); //税込

                if(uPriceX*quantityX != sPriceX)
                {
                    f.message(">>異常あり：" + (i+1) + "行目" +
                        iCodeX + iNameX + "で、数値計算エラー");
                }
                else if((int)(sPriceX*(1+tRateX/100.0)) != tPriceX)
                {
                    f.message(">>異常あり：" + (i+1) + "行目" +
                        iCodeX + iNameX + "で、数値計算エラー");
                }
            }
        }
        catch(Exception e)
        {
            f.message(">>異常あり：" + (i+1) + "行目" +
                iCodeX + iNameX + "で、数値表現エラー");
        }
        f.message("数値検証終了。");
    }
}

◆Costomer.csv

1001,東京商事,70
1002,大阪商会,60
1003,名古屋企画,65
1004,神戸商事,55

◆Item.csv

AB0001,鉛筆10ダース入,5000,10
AB0002,ノート10冊入,2000,10
AB0003,ボールペン50本入,5000,10
AB0004,消しゴム50個入,8000,10
CD0001,サバ缶詰50缶入,7000,8
CD0002,米穀10kg入,4000,8
CD0003,海苔10束入,8000,8
CD0004,小麦粉10袋入,3000,8

◆MakeCSV1Manual.txt

■■帳票作成システム MakeCSV1 の使い方

■データ記入欄への記入の仕方
●INPUT PANEL-1
【取引先コード欄】
　　取引先データベースに登録されている取引先コードを書き込む。
　　取引先コードがない場合、空欄のまま確定することで、「**」が自動記入される。
【取引先名欄】
　　取引先コードの入力によって、取引先名が自動記入される。
　　取引先コード欄を空欄にした場合、取引先名を手作業で書き込む。
【基準掛率欄】
　　取引先コードの入力によって、数値が自動記入される。
　　取引先コード欄を空欄のまま確定すれば、基準掛率欄は「**」が自動記入される。
【日付欄】
　　エンター・キーで、年月日が自動的に記入される。（手作業で変更可能）
●INPUT PANEL-2
【品番欄】
　　商品データベースに登録されている商品コード（品番）を書き込む。
　　商品コードがない場合、空欄のまま確定することで、「**」が自動記入される。
【品名欄】
　　商品コードの入力によって、商品名が自動記入される。
　　商品コードを空欄のままで確定した場合、商品名を手作業で書き込む。
【定価欄】
　　商品コードの入力によって、定価（メーカー希望小売価格）が自動記入される。
　　商品コードを空欄のままで確定した場合、定価なしで、「**」が自動記入される。
【税率欄】
　　商品コードがあれば、税率が自動的に記入される。
　　商品コードがなければ、税率を手作業で書き込む。
●INPUT PANEL-3
【実効掛率欄】
　　デフォルトで標準掛率が自動記入される。
　　特価販売の場合、特価掛率に書き換える。掛率計算しない場合、空欄「**」にする。
【単価欄】
　　定価と実効掛率とがあれば、単価が自動計算され自動記入される。
　　自動計算できない場合、手作業で単価を書き込む。
【数量欄】
　　数値を手作業で書き込む。
　　数量は、必ず数値を書き込まねばならず、空欄にできない。
【金額欄】
　　自動的に記入される。
　　手作業で修正してはならない。
【税込金額欄】
　　自動的に記入される。
　　手作業で修正してはならない。
●TABLE PANEL
【累積表】
　　INPUT PANEL-2およびPANEL-3の入力で、自動的に累積表に１件追加される。
【合計表】
　　自動的に合計計算されるが、「計算検証」ボタンでも、合計が再計算される。
●TOOL BAR（累積表に対する編集作業のボタンを配置）
【選択解除】
　　累積表で行選択された状態を解除。
【全行削除】
　　累積表の内容全部を削除。ただし、内容が記憶されない。
【一行削除】
　　選択状態にある一行を削除。ただし、内容が記憶される。
【一行記憶】
　　選択状態にある一行を記憶。
【一行挿入】
　　記憶内容を選択行の直上に挿入。選択行がなければ、記憶内容を末尾に追加。
【空行追加】
　　空行を選択行の直上に挿入。選択行がなければ、空行を末尾に追加。
【計算検証】
　　累積表の全行に対し、計算内容を検証する。
●MENU BAR
【ファイル】の【開く】
　　ファイルに保存された内容が呼び出され、「INPUT PANEL-1」および「TABLE PANEL」に展開される。
【ファイル】の【保存】
　　内容がファイルに保存される。
【ファイル】の【印刷】
　　サブ・システムの「PrintCSV1」が呼び出され、表形式に整えられ、印刷準備状態になる。
【消去】の【警告消去】
　　警告欄の内容を消去する。
【ヘルプ】の【使用法】
　　この「帳票作成システム MakeCSV1 の使い方」を別ウィンドウで表示する。
●データベース（専用CSVファイルに記述）
【取引先データベース】
　　「Customer.csv」を別途作成する。
　　その構成は、CSV形式で「取引先コード,取引先名,基準掛率」とする。
【商品データベース】
　　「Item.csv」を別途作成する。
　　その構成は、CSV形式で「商品コード,品名,定価,税率」とする。

◆SystemMessage.java

//■■SystemMessage.java

public interface SystemMessage
{
    public void message(String s);
}

◆StackTraceMonitor.java

//■■StackTraceMonitor.java

import java.awt.*;
import javax.swing.*;
import java.io.*;

//■■キャッチできない例外によるスタックトレース出力監視と出力先切替スレッド
public class StackTraceMonitor extends Thread
{
    //■メンバー変数
    //smの実質は、JFrameとSystemMessageの合流
    private SystemMessage sm;
    //■コンストラクタ
    public StackTraceMonitor(SystemMessage sm)
    {
        this.sm = sm;
    }

    //■標準エラー出力を横取りして、フレーム内の所定場所に表示
    public void run()
    {
        ByteArrayOutputStream baos = new ByteArrayOutputStream(); //バッファ
        try
        { //エラー出力先をバッファに切替え
            System.setErr(new PrintStream(baos));
        }
        catch(Exception e)
        {
            sm.message(e.toString());
            return;
        }
        for(;;) //プログラム終了まで監視を続ける
        {
            try
            {
                sleep(500); //実行時間独占防止の休眠時間
            }
            catch(Exception e)
            {
                sm.message(e.toString());
            }
            if(baos.size() > 0) //エラー出力の蓄積があれば
            {
                synchronized(baos)
                { //baosをロックし、下の文を排他的に実行
                        sm.message(baos.toString()); //エラー表示
                        baos.reset(); //バッファを空にし、次に備える
                }
            }
        }
    }
    //■SystemMessage の切り替え
    public void setFrame(SystemMessage sm)
    {
        this.sm = sm;
    }
}

◆SingleFileFilter.java

//■■SingleFileFilter.java

import java.io.File;
import javax.swing.filechooser.FileFilter;

//■■単一拡張子によるファイルフィルター
public class SingleFileFilter extends FileFilter
{
    private String extention;   //拡張子
    private String description; //説明文
    public SingleFileFilter(String ext)
    {
        extention = ext.toLowerCase(); //小文字に統一
        description = "*." + extention;
    }
    public boolean accept(File f)
    {
        if(f.isDirectory())
            return true;
        return f.getName().toLowerCase().endsWith(
            "." + extention); //ファイル名の末尾判定合否を通知
    }
    public String getDescription()
    {
        return description; //説明文を通知
    }
}

◆Util.java

//■■Util.java

import java.awt.*;
import java.awt.event.*;
import java.awt.im.InputSubset;
import javax.swing.*;
import javax.swing.border.*;
import java.io.*;
import java.util.*;
import java.text.*;

public class Util
{
    static final Font fontM = new Font("MonoSpaced",Font.PLAIN,13);

    //■テキストファイル閲覧サブウィンドウ
    public static void textBrowse(SystemMessage sm, File file)
    {
        String line = null;
        JDialog subWindow = null;
        try
        {
            BufferedReader reader = new BufferedReader(
                new FileReader(file)); //エラーになればsubWindowは生成しない

            subWindow = new JDialog((Frame)sm, file.getName(), true);
            Container pane = subWindow.getContentPane();
            //クローズボタンでサブウィンドウを破棄し、プロセスは継続させる
            //subWindow.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
            JTextArea tArea = new JTextArea(25,80); //行数と１行の文字数
            pane.add(
                new JScrollPane(
                    tArea,
                    JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
                    JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS));
            tArea.setFont(fontM);
            tArea.setBorder(new EmptyBorder(20,20,20,20));
            try
            {
                for (;;)
                {
                    line = reader.readLine();
                    if(line == null)
                        break;
                    tArea.append(line + "\n");
                }
                tArea.select(0, 0); //先頭位置を表示
                tArea.setEditable(false); //編集禁止
            }
            catch(IOException e)
            {
                sm.message("読取エラー>>\n"+e);
            }
            finally
            {
                reader.close();
                subWindow.pack();
                subWindow.setVisible(true);
            }
        }
        catch(FileNotFoundException e)
        {
            sm.message("オープンエラー>>\n"+e);
        }
        catch(IOException e)
        {
            sm.message("クローズエラー>>\n"+e);
        }
    }
    //■テキストファイル閲覧サブウィンドウ（クリップ・ボード機能付き）
    public static void textBrowseCCP(SystemMessage sm, File file)
    {
        String line = null;
        JDialog subWindow = null;
        try
        {
            BufferedReader reader = new BufferedReader(
                new FileReader(file)); //エラーになればsubWindowは生成しない

            subWindow = new JDialog((Frame)sm, file.getName(), true);
            Container pane = subWindow.getContentPane();
            JPanel panel = new JPanel();
            panel.setLayout(new FlowLayout(FlowLayout.LEFT));

            Box vBox = Box.createVerticalBox();
            pane.add(vBox);
            JTextArea tArea = new JTextArea(25,80); //行数と１行の文字数
            tArea.setFont(fontM);
            tArea.setBorder(new EmptyBorder(20,20,20,20));

            JButton cutButton = new JButton("削除");
            cutButton.addActionListener(
            	new ActionListener()
            	{ public void actionPerformed(ActionEvent event)
              	{ tArea.cut(); } }
            );
            JButton copyButton = new JButton("複写");
            copyButton.addActionListener(
            	new ActionListener()
            	{ public void actionPerformed(ActionEvent event)
              	{ tArea.copy(); } }
            );
            JButton pasteButton = new JButton("挿入");
            pasteButton.addActionListener(
            	new ActionListener()
            	{ public void actionPerformed(ActionEvent event)
              	{ tArea.paste(); } }
            );
            panel.add(cutButton);
            panel.add(copyButton);
            panel.add(pasteButton);
            vBox.add(panel);
            vBox.add(
                new JScrollPane(
                    tArea,
                    JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
                    JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS));

            try
            {
                for (;;)
                {
                    line = reader.readLine();
                    if(line == null)
                        break;
                    tArea.append(line + "\n");
                }
                tArea.select(0, 0); //先頭位置を表示
                tArea.setEditable(true); //編集可
            }
            catch(IOException e)
            {
                sm.message("読取エラー>>\n"+e);
            }
            finally
            {
                reader.close();
                subWindow.pack();
                subWindow.setVisible(true);
            }
        }
        catch(FileNotFoundException e)
        {
            sm.message("オープンエラー>>\n"+e);
        }
        catch(IOException e)
        {
            sm.message("クローズエラー>>\n"+e);
        }
    }

    //■改行コードを含む文字列を改行位置で分解し、Stringの配列に変換して返却
    public static String[] splitReturn(String str)
    {
        if(str.charAt(str.length()-1)!='\n')
            str = str + "\n";
        int lines=0;
        for(int i=0; i<str.length(); i++)
        {
            if(str.charAt(i)=='\n')
                lines++;
        }
        String [] stringArray = new String[lines];
        int c1=0;
        int c2=0;
        int lineNum=0;
        int i=0;
        for(; i<str.length(); i++)
        {
            if(str.charAt(i)=='\n')
            {
                c2=i;
                stringArray[lineNum] = str.substring(c1,c2);
                lineNum++;

                c1=c2+1;
            }
        }
        return stringArray;
    }

    //■CSV形式の文字列を想定し、区切り文字（デリミタ）の数を数えて、その数を返却
    public static int countDelimiter(String str, char ch)
    {
      int cnt=0;
      for(int i=0; i<str.length(); i++)
      {
        if(str.charAt(i)==ch)
          cnt++;
      }
      return cnt;
    }

    //■CSV形式の文字列を区切り文字の位置で分解し、Stringの配列に変換して返却
    public static String[] csv2Array(String csv, int cols)
    {
        return csv2Array(csv, cols, ',');
    }
    public static String[] csv2Array(String csv, int cols, char delimiter)
    {
        char [] cArr = {delimiter};
        String delimiterStr = new String(cArr);
        StringTokenizer st = new StringTokenizer(csv, delimiterStr, true);
        int cols2 = 0;
        for(int i=0; i<csv.length(); i++)
        {
            if(csv.charAt(i) == delimiter)
                cols2++;
        }
        cols2++; //カラム数
        if(cols > 0) //カラム数チェック必要
        {
            if(cols != cols2) //カラム数異常
                return null;
        }
        else if(cols == 0) //カラム数チェック不用
            cols = cols2;
        else if(cols < 0) //program error
            ; //次の文で例外発生
        String [] stringArray = new String[cols];
        for(int colCount=0; colCount<cols; colCount++)
        {
            String str;
            if(st.hasMoreTokens()) //トークンがあれば
                str = (st.nextToken()).trim();//区切り文字かデータが得られ
            else                   //トークンがなければ
                str = "";          //最終カラムで、空のカラム

            if(str.equals(delimiterStr))    //トークンが区切り文字か
                str = "";          //最終カラム以外で、空のカラム
            else                   //トークンがデータなら
            {
                if(st.hasMoreTokens()) //最終カラムでないことを確認し
                    st.nextToken();    //次のコンマ除去
            }
            stringArray[colCount] = str; //データか空文字列を格納
        }
        return stringArray;
    }

    //■文字列の先頭と末尾を囲む特定文字を除去。
    //主に引用符や括弧で囲まれた文字列に適用。
    //前後を除去文字に囲まれていなければ、そのまま返却。
    public static String trimAffix(String str, char prefix, char suffix)
    {
        if(str.length()>=2 && str.charAt(0)==prefix && str.charAt(str.length()-1)==suffix)
            return str.substring(1, str.length()-1);
        else
            return str;
    }

    //■JTextField の日本語ＩＭモード切替
    public static void kanjiIn(final JTextField tf, boolean b)
    {
        if(b)
        {
            tf.addFocusListener(new FocusAdapter()
            {
                public void focusGained(FocusEvent event)
                {
                    tf.getInputContext().setCharacterSubsets(
                        new Character.Subset[] {InputSubset.KANJI});
                }
            });
        }
        else
        {
            tf.addFocusListener(new FocusAdapter()
            {
                public void focusGained(FocusEvent event)
                {
                    tf.getInputContext().setCharacterSubsets(null);
                }
            });
        }
    }

    //■数値文字列の整形
    //返却値 String : 整形後の文字列
    //引数 String decimal : 十進実数文字列
    //引数 int fractionDigit : 整形後の小数点桁数
    //引数 boolean comma ：コンマが必要か
    //機能 : 指定の小数点桁数で揃え、必要ならコンマを付加
    public static String decimalStringFormat(String decimal, int fractionDigit, boolean comma)
        throws NumberFormatException
    {
        String s0 = "";
        String s1 = "";
        NumberFormat nf = NumberFormat.getInstance(Locale.JAPANESE);

        if(decimal==null)                  //nullなら
            throw new NumberFormatException("文字列がnull");
        else if(decimal.trim().equals("")) //空文字列なら
            return "";
        else                               //有効文字列なら
        {
            if(decimal.indexOf(".")==-1) //整数表記されていたらコンマ要求とみなし
            {
                s0 = nf.format(Long.parseLong(decimal)); //整数にコンマ付加
                s1 = "";
            }
            else if(comma==true) //実数表記でコンマ要求されていたら
            {
                s0 = decimal.substring(0,decimal.indexOf(".")); //整数部
                if(!s0.equals("-0")) //実数が１未満の場合には符号温存
                    s0 = nf.format(Long.parseLong(s0));   //整数部コンマ付加
                s1 = decimal.substring(decimal.indexOf(".")+1); //小数部
            }
            else //実数表記でコンマ要求なし
            {
                s0 = decimal.substring(0,decimal.indexOf(".")); //整数部
                s1 = decimal.substring(decimal.indexOf(".")+1); //小数部
            }
            if(fractionDigit == 0)     //整数要求
                return s0;
            else if(fractionDigit > 0) //実数要求
            {
                for(int i=0; s1.length()>i; i++)                //小数部チェック
                    if(!Character.isDigit(s1.charAt(i)))
                        throw new NumberFormatException();
                if(fractionDigit < s1.length())
                    s1 = s1.substring(0,fractionDigit);         //末尾切捨
                else if(fractionDigit > s1.length())
                    while(fractionDigit>s1.length())
                        s1 += "0";                              //不足の0を付加
                return (s0 + "." + s1);
            }
            else //小数のマイナス桁は認めない
                throw new NumberFormatException("小数桁がマイナス値");
        }
    }
    public static String decimalStringFormat(String decimal, int fractionDigit)
        throws NumberFormatException
    {
        return decimalStringFormat(decimal, fractionDigit, true);
    }

    //■数値データの変換と照合
    public static int checkInt(String str, int min, int max) throws Exception
    {
        try
        {
            int value = Integer.parseInt(str);
            if(value<min || value>max)
                throw new Exception("数値異常");
            return value;
        }
        catch(NumberFormatException e)
        {
            throw new Exception("数値表記間違");
        }
    }
    public static long checkLong(String str, long min, long max) throws Exception
    {
        try
        {
            long value = Long.parseLong(str);
            if(value<min || value>max)
                throw new Exception("数値異常");
            return value;
        }
        catch(NumberFormatException e)
        {
            throw new Exception("数値表記間違");
        }
    }
    public static double checkDouble(String str, double min, double max) throws Exception
    {
        try
        {
            double value = Double.parseDouble(str);
            if(value<min || value>max)
                throw new Exception("数値異常");
            return value;
        }
        catch(NumberFormatException e)
        {
            throw new Exception("数値表記間違");
        }
    }
    //■CSV簡易データベース
    public static String[] searchCSV(String fileName, String keyWord, int cols, int col)
    //colsは１起算
    //col は０起算
    {
        File file = new File(fileName);
        String ret = null;
        //データ形式は、CSV
        String[] strArr = new TextIO().loadStrArr(file);
        if(strArr==null || strArr.length==0)
        {
            System.err.println("データ・ファイルに問題。"+file.getName());
            return null;
        }
        String[] datArr = null;
        for(int i=0; i<strArr.length; i++)
        {
            datArr = csv2Array(strArr[i], cols);
            if(datArr==null)
                System.err.println("データベースの一部が損壊。"+file.getName());
            else if(datArr[col].equals(keyWord))
                return datArr;
        }
        System.err.println("データベースに該当項目なし。"+file.getName());
        return null;
    }
}

◆TextIO.java

//■■TextIO.java

import java.io.*;
import java.util.LinkedList;
import java.util.List;
import java.nio.file.Files;
import java.nio.file.Path;
import java.awt.*;
import javax.swing.*;

public class TextIO
{

    public static File callFileChooser(String mode, Component parent, String dir, String ext)
    {
        JFileChooser fileChooser = new JFileChooser(dir);
        if(!ext.equals("*"))
        {
          SingleFileFilter filter = new SingleFileFilter(ext);
          fileChooser.setFileFilter(filter);
        }
        int ret;
        if(mode.equals("open"))
            ret = fileChooser.showOpenDialog(parent);
        else if(mode.equals("save"))
            ret = fileChooser.showSaveDialog(parent);
        else
            return null;
        File file = fileChooser.getSelectedFile();
        if(ret != JFileChooser.APPROVE_OPTION || file == null)
            return null;
        else
            return file;
    }

    public static String[] loadStrArr(File file)
    { //デフォルト文字コード
        BufferedReader reader = null;
        Object[] objArr = null;
        String[] strArr = null;
        List<String> list = new LinkedList<String>();
        try
        {
            reader = new BufferedReader(new FileReader(file));
            try
            {
                String line  = "";
                for(int i=0;;i++) //データ読取り
                {
                    line = reader.readLine();
                    if(line == null) //終端超越なら
                        break;
                    list.add(line);
                }
                objArr = list.toArray();
                strArr = new String[objArr.length];
                for(int i=0; i<objArr.length; i++)
                {
                    strArr[i] = (String)objArr[i];
                }
            }
            catch(IOException e)
            {
                System.err.println(file.getName() + ": read error");
                strArr = null;
            }
            reader.close();
        }
        catch(FileNotFoundException e)
        {
            System.err.println(file.getName() + ": open error");
            strArr = null;
        }
        catch(IOException e)
        {
            System.err.println(file.getName() + ": close error");
            strArr = null;
        }
        return strArr;
    }

     public static String loadStr(File file)
    { //デフォルト文字コード
        BufferedReader reader = null;
        String retStr = "";
        try
        {
            reader = new BufferedReader(new FileReader(file));
            try
            {
                String line  = "";
                for(int i=0;;i++) //データ読取り
                {
                    line = reader.readLine();
                    if(line == null) //終端超越なら
                        break;
                    retStr = retStr + line + "\n";
                }
            }
            catch(IOException e)
            {
                System.err.println(file.getName() + ": read error");
                retStr = null;
            }
            reader.close();
        }
        catch(FileNotFoundException e)
        {
            System.err.println(file.getName() + ": open error");
            retStr = null;
        }
        catch(IOException e)
        {
            System.err.println(file.getName() + ": close error");
            retStr = null;
        }
        return retStr;
    }

    public static boolean saveStr(File file, String data)
    { //デフォルト文字コード
        BufferedWriter writer = null;
        boolean b = true;
        try
        {
            writer = new BufferedWriter(new FileWriter(file));
            try
            {
                writer.write(data);
                writer.flush();
            }
            catch(IOException e)
            {
                System.err.println(file.getName() + ": write error");
                b = false;
            }
            writer.close();
        }
        catch(IOException e)
        {
            if(writer == null)
            {
                System.err.println(file.getName() + ": open error");
            }
            else
            {
                System.err.println(file.getName() + ": close error");
            }
            b = false;
        }
        return b;
    }

    public static void saveText(File outputFile, String data, String charCode)
    { //charCode: "UTF-16", "UTF-8", "Shift_JIS", "MS932"
        PrintWriter pw = null;
        try //●書き出しファイルの準備
        {
            pw = new PrintWriter(new OutputStreamWriter(
                new FileOutputStream(outputFile), charCode));
        }
        catch(Exception e)
        {
            System.err.println("Exception1: saveText() " +
                charCode + "> \n" + e.toString());
        }

        try //●ファイルへの書き出し
        {
            if(data==null)
                System.err.println("data==null");
            if(pw==null)
                System.err.println("pw==null");
            if(data!=null && pw!=null)
                pw.print(data);
            pw.close();
        }
        catch(Exception e)
        {
            System.err.println("Exception2: saveText() " +
                charCode + "> \n" + e.toString());
        }
    }

    public static String loadText(File inputFile, String charCode)
    { //charCode: "UTF-16", "UTF-8", "Shift_JIS", "MS932"
        try //●ファイルからの読み取り
        {
            BufferedReader br = new BufferedReader(
                new InputStreamReader(new FileInputStream(inputFile),
                charCode));
            String text1 = "";
            String text2 = "";
            while ((text1 = br.readLine()) != null)
            {
                text2 = text2 + text1 + "\n";
            }
            return text2;
        }
        catch(Exception e)
        {
            System.err.println("Exception2: loadText() " +
                charCode + "> \n" + e.toString());
            return null;
        }
    }
}

◆PrintCSV1.java

//■■PrintCSV1.java

//■■TEST ENVIRONMENT
//CPU:     intel Core i3-1005G1
//MEMORY:  8GB
//STRAGE:  117GB SSD
//DISPLAY: 1920×1080 px (setup 150%: 1280×720 px)
//OS:      Windows11 Home
//JDK:     JDK18
//「タスク バーを自動的に隠す」に設定

//なお、一般的なディスプレイの解像度は、以下の通り。
//8K（7680×4320 dot）
//4K（3840×2160）
//WQHD（2560×1440）
//FHD（1920×1080）
//WXGA++(1600×900）
//HD（1280×720）
//ただし、
//FHD（1920×1080）が最多であり事実上の標準である。

//■■PROJECT
//CSVファイルを罫線入りの表に加工して印刷する。

/*
■■DIRECTORIES & FILES
/user/java/PrintCSV1/src　　ソース・ファイル・ディレクトリー
    PrintCSV1.java
    PrintCSV1MenuLis.java
    PrintCSV1ButtonLis.java
    PrintCSV1AnalyzeEngine.java
    PrintCSV1CanvasEngine.java
    PrintCSV1PrintEngine.java
    PrintCSV1FileIO.java

/user/java/Lib/src　　　　　ライブラリー・ソース・ファイル・ディレクトリー
    SystemMessage.java
    StackTraceMonitor.java
    Util.java
    SingleFileFilter.java
    TextIO.java

/user/java/PrintCSV1/sys　　　システム・ファイル・ディレクトリー
    PrintCSV1Manual.txt

/user/java/PrintCSV1/pcc　　　書式定義文ディレクトリー(拡張子pcc)
/user/java/PrintCSV1/data 　　データ文ディレクトリー(拡張子csv, txt)

/user/class　　　　　　　　　クラス・ファイル・ディレクトリー(拡張子class)
*/

//■■インポート
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.border.*;
import java.util.*;
import java.io.*;
import java.awt.geom.*;
import java.awt.print.*;

//■■フレーム定義
class PrintCSV1 extends JFrame implements SystemMessage
{
    //■メンバー変数

    //●基本的な変数
    PrintCSV1 f;
    static PrintCSV1 sf;
    Container pane;
    PrintCSV1CanvasEngine canvas;  //画像描画制御
    PrintCSV1AnalyzeEngine analyze; //命令文解析
    PrintCSV1PrintEngine printer;  //印刷制御
    PrintCSV1MenuLis menuLis;      //メニュー・リスナー
    PrintCSV1ButtonLis buttonLis;  //ボタン・リスナー
    PrintCSV1FileIO io;            //入出力作業
    JMenuBar menuBar;              //メニューを配置
    JPanel buttonBar1 = new JPanel(); //ボタンを配置
    JPanel buttonBar2 = new JPanel(); //ボタンを配置
    JPanel buttonBar3 = new JPanel(); //ボタンを配置
    JLabel dataFile     = new JLabel("  [NoName]"); //ファイル名
    JLabel controlFile  = new JLabel("  [NoName]"); //ファイル名
    JLabel scaleLabel   = new JLabel("  (scale: )"); //表示スケール
    JLabel pageNumLabel = new JLabel("  (page:  )"); //該当ページ
    JLabel pixelLabel   = new JLabel("  (pixel: )"); //画像ピクセル数
    JLabel mmLabel      = new JLabel("  (mm: )");    //紙サイズ
    JTextArea  dArea, cArea, mArea;
    JScrollPane scroll0,scroll1,scroll2,scroll3;
    final int scroll0Width;
    final int scroll1Width;
    final int scroll2Width;
    final int scroll0Height;
    final int scroll3Height;

    //●画面表示と画像保存とに反映される図形倍率。
    //ミリ単位を見やすいピクセル単位に置き換えるが、
    //印刷には反映されない。
    final double xL6 = 6.0;
    final double xL5 = 5.0;
    final double xL4 = 4.0;
    final double xL3 = 3.0; //1 mm を 3 px（ピクセル）で表示（標準設定）
    final double xL2 = 2.0;
    final double xL1 = 1.0;

    int cdLines;
    int cdCols;
    int mLines;
    int mCols;

    //■エントリーポイント
    public static void main(String[] args)
    {
        //●画面構築部の生成
        new PrintCSV1(null, null);
    }

    //■フレームのコンストラクタ
    PrintCSV1(final SystemMessage sm, final StackTraceMonitor st)
    {
        super("PrintCSV1  Ver.1.0");
        f = this;
        sf = this;

        //●プログラムの開始と終了
        if(st != null) //サブシステムとして実行なら
        {
            addWindowListener(new WindowAdapter()
            {
                public void windowClosing(WindowEvent e) //閉じるときは
                {
                    st.setFrame(sm); //親システムのSystemMessageに戻す
                }
                public void windowActivated(WindowEvent e) //アクティブ化で
                {
                    st.setFrame(f); //このSystemMessageに切替える
                }
            });
            st.setFrame(this); //最初は、このSystemMessageに切替える
            setDefaultCloseOperation(DISPOSE_ON_CLOSE); //サブシステムのみ終了
        }
        else //単独実行なら、
        {
            new StackTraceMonitor(this).start(); //標準エラー出力の監視
            setDefaultCloseOperation(EXIT_ON_CLOSE);  //完全終了
        }

        //●スウィングで表示される文字数や行数や大きさ。
        //それぞれの実行環境に合わせて数値を調整すること。
        //下記の数値設定は、簡易設定。
        if(getToolkit().getScreenSize().width >= 1280 &&
           getToolkit().getScreenSize().height >= 720)
        {
            cdLines = 30;
            cdCols  = 26;
            mLines  =  4;
            mCols   = 100;
        }
        else
        {
            cdLines = 20;
            cdCols  = 17;
            mLines  =  3;
            mCols   = 66;
        }

        //●要求判定部の生成
        menuLis  = new PrintCSV1MenuLis(this);
        buttonLis = new PrintCSV1ButtonLis(this);

        //●主要機能部の生成
        canvas   = new PrintCSV1CanvasEngine(this);
        analyze  = new PrintCSV1AnalyzeEngine(this);
        printer  = new PrintCSV1PrintEngine(this);
        io       = new PrintCSV1FileIO(this);

        //●MENU BAR:メニュー表示部品
        JMenu menu;
        setJMenuBar(menuBar = new JMenuBar());

        addA(menuBar, menu = new JMenu("ファイル"));
        addB(menu, new JMenuItem("データ開く（UTF-8）"),  menuLis, "Lo8");
        addB(menu, new JMenuItem("データ保存（UTF-8）"),  menuLis, "Sa8");
        menu.addSeparator();
        addB(menu, new JMenuItem("書式文開く（UTF-8）"), menuLis,  "LoD");
        addB(menu, new JMenuItem("書式文保存（UTF-8）"), menuLis,  "SaD");
        menu.addSeparator();
        addB(menu, new JMenuItem("画像印刷"),   menuLis,     "Pri");

        addA(menuBar, menu = new JMenu("消去"));
        addB(menu, new JMenuItem("データ文消去"), menuLis,   "Cl1");
        addB(menu, new JMenuItem("書式文消去"), menuLis,     "Cl2");
        addB(menu, new JMenuItem("警告文消去"), menuLis,     "Cl3");
        addB(menu, new JMenuItem("全消去"), menuLis,         "Cl4");

        addA(menuBar, menu = new JMenu("見本"));
        addB(menu, new JMenuItem("CSV 罫線標準用法"), menuLis, "Std");
        addB(menu, new JMenuItem("CSV 罫線特殊用法"), menuLis, "Spe");
        addB(menu, new JMenuItem("NUL 各部名称説明"), menuLis, "Nam");
        addB(menu, new JMenuItem("NUL 印刷試験A4"), menuLis,   "TA4");
        addB(menu, new JMenuItem("NUL カード印刷"), menuLis,   "PCd");
        addB(menu, new JMenuItem("NUL フォント・テスト1"), menuLis, "Pht");
        addB(menu, new JMenuItem("NUL フォント・テスト2"), menuLis, "Ph2");

        addA(menuBar, menu = new JMenu("表示"));
        addB(menu, new JMenuItem("表示:x6"),    menuLis,     "XL6");
        addB(menu, new JMenuItem("表示:x5"),    menuLis,     "XL5");
        addB(menu, new JMenuItem("表示:x4"),    menuLis,     "XL4");
        addB(menu, new JMenuItem("表示:x3 標準設定"), menuLis, "XL3");
        addB(menu, new JMenuItem("表示:x2"),    menuLis,     "XL2");
        addB(menu, new JMenuItem("表示:x1"),    menuLis,     "XL1");

        addA(menuBar, menu = new JMenu("ヘルプ"));
        addB(menu, new JMenuItem("使用法"),     menuLis,     "Usa");

        menuBar.add(new JLabel("　　　"));
        menuBar.add(dataFile);
        menuBar.add(controlFile);
        menuBar.add(new JLabel("　　　"));
        menuBar.add(scaleLabel);
        menuBar.add(pageNumLabel);
        menuBar.add(new JLabel("　　　"));
        menuBar.add(pixelLabel);
        menuBar.add(mmLabel);

        //●BUTTON BAR:ボタン表示部品
        buttonBar1.setLayout(new FlowLayout(FlowLayout.LEFT));
        buttonBar2.setLayout(new FlowLayout(FlowLayout.LEFT));
        buttonBar3.setLayout(new FlowLayout(FlowLayout.LEFT));

        addB(buttonBar1, new JButton("削除１"),
            new ActionListener()
            { public void actionPerformed(ActionEvent event)
                { dArea.cut(); } } ,"");

        addB(buttonBar1, new JButton("複写１"),
            new ActionListener()
            { public void actionPerformed(ActionEvent event)
                { dArea.copy(); } } ,"");

        addB(buttonBar1, new JButton("挿入１"),
            new ActionListener()
            { public void actionPerformed(ActionEvent event)
                { dArea.paste(); } } ,"");

        addB(buttonBar2, new JButton("削除２"),
            new ActionListener()
            { public void actionPerformed(ActionEvent event)
                { cArea.cut(); } } ,"");

        addB(buttonBar2, new JButton("複写２"),
            new ActionListener()
            { public void actionPerformed(ActionEvent event)
                { cArea.copy(); } } ,"");

        addB(buttonBar2, new JButton("挿入２"),
            new ActionListener()
            { public void actionPerformed(ActionEvent event)
                { cArea.paste(); } } ,"");

        addB(buttonBar3, new JButton("描画"), buttonLis, "Dsp");
        addB(buttonBar3, new JButton("初頁"), buttonLis, "Top");
        addB(buttonBar3, new JButton("前頁"), buttonLis, "Prv");
        addB(buttonBar3, new JButton("次頁"), buttonLis, "Nex");

        //●DATA AREA:データ表示部品
        dArea = new JTextArea(cdLines,cdCols);
        dArea.setBackground(new Color(230,230,255));
        dArea.setMargin(new Insets(3, 3, 3, 3)); //余白
        scroll0 = new JScrollPane(
            dArea,
            JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
            JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);

        //●CONTROL AREA:定義文表示部品
        cArea = new JTextArea(cdLines,cdCols);
        cArea.setBackground(new Color(230,230,255));
        cArea.setMargin(new Insets(3, 3, 3, 3)); //余白
        scroll1 = new JScrollPane(
            cArea,
            JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
            JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);

        //●GRAPHIC AREA:図表画像表示部品
        scroll2 = new JScrollPane(
            canvas,
            JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
            JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
        scroll2.setPreferredSize(new Dimension(
            (int)scroll0.getPreferredSize().getWidth() +
            (int)scroll1.getPreferredSize().getWidth(),
            (int)scroll0.getPreferredSize().getHeight()));
        canvas.setPreferredSize(new Dimension(3000,3000));
        canvas.setOpaque(true); //不透明化

        //●メッセージ文表示部品
        mArea = new JTextArea(mLines,mCols);
        mArea.setBackground(new Color(230,230,255));
        mArea.setMargin(new Insets(3, 3, 3, 3)); //余白
        scroll3 = new JScrollPane(
            mArea,
            JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
            JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
        mArea.setAutoscrolls(true);  //自動スクロール
        mArea.setEditable(false);  //編集禁止

        //●全体レイアウト
        //ボックス・レイアウトを利用
        pane = getContentPane();
        Box vBox = Box.createVerticalBox();
        Box vBox1 = Box.createVerticalBox();
        Box vBox2 = Box.createVerticalBox();
        Box vBox3 = Box.createVerticalBox();
        Box hBox = Box.createHorizontalBox();
        pane.add(vBox);
        vBox.add(hBox);
        hBox.add(vBox1);
        hBox.add(vBox2);
        hBox.add(vBox3);
        vBox1.add(buttonBar1);
        vBox2.add(buttonBar2);
        vBox3.add(buttonBar3);
        vBox1.add(scroll0);
        vBox2.add(scroll1);
        vBox3.add(scroll2);
        vBox.add(scroll3);

        //●その他の処理
        scroll0Width =  (int)scroll0.getPreferredSize().getWidth();
        scroll1Width =  (int)scroll1.getPreferredSize().getWidth();
        scroll2Width =  (int)scroll2.getPreferredSize().getWidth();
        scroll0Height = (int)scroll0.getPreferredSize().getHeight();
        scroll3Height = (int)scroll3.getPreferredSize().getHeight();
        pack();
        setVisible(true);

        GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
        int screenW = gd.getDisplayMode().getWidth();
        int screenH = gd.getDisplayMode().getHeight();
        message("hardware screen size = " + screenW + " × " + screenH + " px");
        //上記スクリーン・サイズは、ハードウェアとしての本来の数値。

        screenW = getToolkit().getScreenSize().width;
        screenH = getToolkit().getScreenSize().height;
        message("software screen size = " + screenW + " × " + screenH + " px");
        //上記スクリーン・サイズは、OSでセットアップされたソフトウェアでの数値。
    }

    //■コンテナーに JMenu をレイアウト
    public void addA(Container cont, AbstractButton b)
    {
        cont.add(b);
    }
    //■コンテナーに JButton or JMenuItem をレイアウト
    public void addB(Container cont, AbstractButton b,
        ActionListener lis, String command)
    {
        cont.add(b);
        if(lis!=null)
            b.addActionListener(lis);
        if(command!=null)
            b.setActionCommand(command);
    }

    //■システムメッセージの表示
    public void message(String s)
    {  //●エラーの通知、または状況の報告
        int len = mArea.getText().length();
        mArea.select(len, len); //自動スクロール
        mArea.append(s + "\n");
    }

    //■JFrameの部分再描画要求時には全画面再描画
    public void repaint(int x, int y, int w, int h)
    { //●受け取った値を無視し、全域の値に置き換える
        super.repaint(0,0,getWidth(),getHeight());
    }
}

◆PrintCSV1MenuLis.java

//■■PrintCSV1MenuLis.java

//■■インポート
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.text.*;
import java.io.*;
import java.util.*;

//■■JMenuItemのイベントリスナー
class PrintCSV1MenuLis implements ActionListener
{
    //■メンバー変数
    PrintCSV1 f;

    //■コンストラクタ
    PrintCSV1MenuLis(PrintCSV1 frame)
    {
        f = frame;
    }

    //■JMenuItemのイベントハンドラ
    public void actionPerformed(ActionEvent event)
    {
        String com = event.getActionCommand();

        if(com.equals("Lo8"))
        {  //●データの読み込み（UTF8文字コード）
            f.io.loadUTF(f.dArea, "./data", "*", "UTF-8");
            f.canvas.firstPage();
            //データをセットし、再描画はせず
        }
        else if(com.equals("Lo16"))
        {  //●データの読み込み（UTF16文字コード）
            f.io.loadUTF(f.dArea, "./data", "*", "UTF-16");
            f.canvas.firstPage();
            //データをセットし、再描画はせず
        }
        else if(com.equals("LoSJ"))
        {  //●データの読み込み（Shift_JIS文字コード）
            f.io.loadUTF(f.dArea, "./data", "*", "Shift_JIS");
            f.canvas.firstPage();
            //データをセットし、再描画はせず
        }
        else if(com.equals("Sa8"))
        {  //●データの保存（UTF16文字コード）
            f.io.saveUTF(f.dArea, "./data", "*", "UTF-8");
        }
        else if(com.equals("Sa16"))
        {  //●データの保存（UTF16文字コード）
            f.io.saveUTF(f.dArea, "./data", "*", "UTF-16");
        }
        else if(com.equals("SaSJ"))
        {  //●データの保存（Shift_JIS文字コード）
            f.io.saveUTF(f.dArea, "./data", "*", "Shift_JIS");
        }
        else if(com.equals("LoD"))
        {  //●定義の読み込み
            f.io.load(f.cArea, "./pcc", "pcc");
            f.canvas.firstPage();
            f.canvas.repaint();  //描画実行
        }
        else if(com.equals("SaD"))
        {  //●定義の保存
            f.io.save(f.cArea, "./pcc", "pcc");
        }
        else if(com.equals("Cl1"))
        {  //●データ消去
            f.dArea.setText("");
            f.canvas.firstPage();
            f.canvas.repaint();
            fileNameClear1();
        }
        else if(com.equals("Cl2"))
        {  //●定義文消去
            f.cArea.setText("");
            f.canvas.firstPage();
            f.canvas.repaint();
            fileNameClear2();
        }
        else if(com.equals("Cl3"))
        {  //●警告文消去
            f.mArea.setText("");
        }
        else if(com.equals("Cl4"))
        {  //●全消去
            f.dArea.setText("");
            f.cArea.setText("");
            f.mArea.setText("");
            f.canvas.firstPage();
            f.canvas.repaint();
            fileNameClear();
        }
        else if(com.equals("Std"))
        {  //●CSV標準用法の表組み
            f.cArea.setText("");
            f.cArea.append("//CSV標準用法\n");
            f.cArea.append("DataType(\"CSV\");\n");
            f.cArea.append("Paper(\"B6\");\n");
            f.cArea.append("Antialias(true);\n");
            f.cArea.append("Margin(10,12);\n");
            f.cArea.append("Title(\"明細書\");\n");
            f.cArea.append("Subtitle(\"くだもの\");\n");
            f.cArea.append("PageNumber(true);\n");
            f.cArea.append("FontSize(12);\n");
            f.cArea.append("FontBold(true);\n");
            f.cArea.append("LineWidth(0.3);\n");
            f.cArea.append("PageHeader(12);\n");
            f.cArea.append("TableHeader(8);\n");
            f.cArea.append("Row(7,20);\n");
            f.cArea.append("Column(10,\"R\",\"No.\");\n");
            f.cArea.append("Column(25,\"L\",\"品名\");\n");
            f.cArea.append("Column(20,\"L\",\"規格\");\n");
            f.cArea.append("Column(15,\"N0\",\"単価\");\n");
            f.cArea.append("Column(15,\"N0\",\"数量\");\n");
            f.cArea.append("Column(20,\"N0\",\"金額\");\n");
            f.dArea.setText("");
            f.dArea.append("1,りんご,Ｌ,90,100,9000\n");
            f.dArea.append("2,みかん,Ｌ,50,100,5000\n");
            f.dArea.append("3,なし,Ｍ,80,100,8000\n");
            f.dArea.append(",合計,,,,22000\n");
            f.canvas.firstPage();
            f.canvas.repaint();
            fileNameClear();
        }
        else if(com.equals("Spe"))
        {  //●CSV罫線特殊用法
            f.cArea.setText("");
            f.cArea.append("//CSV罫線特殊用法\n");
            f.cArea.append("DataType(\"CSV\");\n");
            f.cArea.append("Paper(140,170);\n");
            f.cArea.append("Margin(10,12);\n");
            f.cArea.append("Antialias(true);\n");
            f.cArea.append("Title(\"住所録\");\n");
            f.cArea.append("PageNumber(true);\n");
            f.cArea.append("FontSize(12);\n");
            f.cArea.append("FontBold(true);\n");
            f.cArea.append("LineWidth(0.3);\n");
            f.cArea.append("PageHeader(20);\n");
            f.cArea.append("Row(7,18);\n");
            f.cArea.append("Column(10,\"R\",\"\");\n");
            f.cArea.append("Column(45,\"L\",\"\");\n");
            f.cArea.append("Column(40,\"L\",\"\");\n");
            f.cArea.append("Column(20,\"L\",\"\");\n");
            f.cArea.append("DrawMode(2);\n");
            f.cArea.append("RowBlock(2);\n");
            f.dArea.setText("");
            f.dArea.append("1,猫野金太郎,03-500-1234,03-0001\n");
            f.dArea.append(",東京都中央区三角町３丁目３−３,,\n");
            f.dArea.append("2,犬山銀次郎,06-230-2345,06-0012\n");
            f.dArea.append(",大阪府大阪市中央区道頓堀５５１,,\n");
            f.canvas.firstPage();
            f.canvas.repaint();
            fileNameClear();
        }
        else if(com.equals("TA4"))
        {  //●印刷テスト図形Ａ４
            f.cArea.setText("");
            f.cArea.append("//印刷テスト図形Ａ４\n");
            f.cArea.append("DataType(\"NUL\");\n");
            f.cArea.append("Paper(\"A4\");\n");
            f.cArea.append("FontBold(true);\n");
            f.cArea.append("DrawLine(0,0,210,297);\n");
            f.cArea.append("DrawLine(0,297,210,0);\n");
            f.cArea.append("DrawLine(0,148.5,105,0);\n");
            f.cArea.append("DrawLine(105,0,105,297);\n");
            f.cArea.append("DrawLine(210,148.5,0,148.5);\n");
            f.cArea.append("DrawLine(105,0,210,148.5);\n");
            f.cArea.append("DrawLine(210,148.5,105,297);\n");
            f.cArea.append("DrawLine(0,148.5,105,297);\n");
            f.cArea.append(
              "WriteString(30,20,24,\"sansserif\",\"printCSV1 A4\");\n");
            f.cArea.append(
              "WriteString(30,30,18,\"sansserif\",\"210x297mm\");\n");
            f.cArea.append(
              "WriteString(105,148.5,48,\"sansserif\",\"活字48pt\");\n");
            f.cArea.append("DrawLine(52.5,74.25,105,74.25);\n");
            f.cArea.append("DrawLine(52.5,74.25,52.5,148.5);\n");
            f.cArea.append(
              "WriteString(72,80,18,\"sansserif\",\"52.5mm\");\n");
            f.cArea.append(
              "WriteString(54,110,18,\"sansserif\",\"74.25mm\");\n");
            f.canvas.firstPage();
            f.canvas.repaint();
            fileNameClear();
        }

        else if(com.equals("Nam"))
        {  //●各部名称説明
            f.cArea.setText("");
            f.cArea.append("//各部名称説明\n");
            f.cArea.append("DataType(\"NUL\");\n");
            f.cArea.append("Paper(140,140);\n");
            f.cArea.append("Margin(20,37);\n");
            f.cArea.append("Title(\"Title\");\n");
            f.cArea.append("Subtitle(\"Subtitle\");\n");
            f.cArea.append("Number(\"Number\");\n");
            f.cArea.append("PageNumber(true);\n");
            f.cArea.append("FontSize(12);\n");
            f.cArea.append("FontBold(true);\n");
            f.cArea.append("LineWidth(0.3);\n");
            f.cArea.append("PageHeader(20);\n");
            f.cArea.append("TableHeader(10);\n");
            f.cArea.append("Row(7,10);\n");
            f.cArea.append("Column(30,\"L\",\"TableHeader\");\n");
            f.cArea.append("Column(30,\"L\",\"TableHeader\");\n");
            f.cArea.append("Footnote(\"Footnote\");\n");
            f.cArea.append("DrawLine(0,55,37,55);\n");
            f.cArea.append("DrawLine(0,55,2,57);\n");
            f.cArea.append("DrawLine(37,55,35,53);\n");
            f.cArea.append("WriteString(13,53,\"Margin\");\n");
            f.cArea.append("DrawLine(45,22,58,22);\n");
            f.cArea.append("DrawLine(50,22,50,0);\n");
            f.cArea.append("DrawLine(50,22,48,20);\n");
            f.cArea.append("DrawLine(50,0,52,2);\n");
            f.cArea.append("WriteString(52,12,\"Margin\");\n");
            f.cArea.append("DrawLine(75,22,115,22);\n");
            f.cArea.append("DrawLine(100,40,115,40);\n");
            f.cArea.append("DrawLine(110,40,110,22);\n");
            f.cArea.append("DrawLine(110,40,108,38);\n");
            f.cArea.append("DrawLine(110,22,112,24);\n");
            f.cArea.append("WriteString(112,32,\"PageHeader\");\n");
            f.cArea.append("WriteString(100,55,\"Row\");\n");
            f.cArea.append("WriteString(100,62,\"Row\");\n");
            f.cArea.append("WriteString(40,132,\"Column\");\n");
            f.cArea.append("WriteString(70,132,\"Column\");\n");
            f.dArea.setText("");
            f.canvas.firstPage();
            f.canvas.repaint();
            fileNameClear();
        }
        else if(com.equals("PCd"))
        {  //●はがき用紙にカード印刷
            f.cArea.setText("");
            f.cArea.append("//PrintCard.pcc\n");
            f.cArea.append("//はがき用紙にカードを二枚印刷\n");
            f.cArea.append("//はがきサイズ： 100 x 148 mm\n");
            f.cArea.append("//名刺サイズ：    90 x  55 mm\n");
            f.cArea.append("//カード・サイズ：85 x  54 mm\n");
            f.cArea.append("//\n");
            f.cArea.append("DataType(\"NUL\");\n");
            f.cArea.append("Paper(\"PostCard\"); //はがきサイズ\n");
            f.cArea.append("Antialias(true);\n");
            f.cArea.append("//カード・サイズ枠、上段\n");
            f.cArea.append("DrawLine(5,13,90,13);\n");
            f.cArea.append("DrawLine(5,67,90,67);\n");
            f.cArea.append("DrawLine(5,13,5,67);\n");
            f.cArea.append("DrawLine(90,13,90,67);\n");
            f.cArea.append("//カード・サイズ枠、下段\n");
            f.cArea.append("DrawLine(5,80,90,80);\n");
            f.cArea.append("DrawLine(5,134,90,134);\n");
            f.cArea.append("DrawLine(5,80,5,134);\n");
            f.cArea.append("DrawLine(90,80,90,134);\n");
            f.cArea.append("//\n");
            f.cArea.append("WriteString(11,23,13,\"BIZ UDゴシック\",\"『思考方法概論』\");\n");
            f.cArea.append("WriteString(11,30,13,\"BIZ UDゴシック\",\"『Java実例プログラミング』\");\n");
            f.cArea.append("DrawLine(11,34,83,34);\n");
            f.cArea.append("WriteString(11,41,12,\"Consolas\",\"https://rakue.ganriki.net/\");\n");
            f.cArea.append("WriteString(11,48,12,\"Consolas\",\"https://dl.ndl.go.jp/pid/13380270\");\n");
            f.cArea.append("WriteString(11,55,12,\"Consolas\",\"https://dl.ndl.go.jp/pid/13307204\");\n");
            f.cArea.append("WriteString(11,62,12,\"Consolas\",\"https://dl.ndl.go.jp/\");\n");
            f.cArea.append("//\n");
            f.cArea.append("WriteString(11,90,13,\"BIZ UDゴシック\",\"『思考方法概論』\");\n");
            f.cArea.append("WriteString(11,96,13,\"BIZ UDゴシック\",\"『Java実例プログラミング』\");\n");
            f.cArea.append("DrawLine(11,100,83,100);\n");
            f.cArea.append("WriteString(11,108,12,\"Consolas\",\"https://rakue.ganriki.net/\");\n");
            f.cArea.append("WriteString(11,115,12,\"Consolas\",\"https://dl.ndl.go.jp/pid/13380270\");\n");
            f.cArea.append("WriteString(11,122,12,\"Consolas\",\"https://dl.ndl.go.jp/pid/13307204\");\n");
            f.cArea.append("WriteString(11,129,12,\"Consolas\",\"https://dl.ndl.go.jp/\");\n");
            f.dArea.setText("");
            f.canvas.firstPage();
            f.canvas.repaint();
            fileNameClear();
        }
        else if(com.equals("Pht"))
        {  //●フォント表示テスト1
            f.cArea.setText("");
            f.cArea.append("//フォント表示テスト1\n");
            f.cArea.append("DataType(\"NUL\");\n");
            f.cArea.append("Paper(\"A4\");\n");
            f.cArea.append("FontBold(false);\n");
            f.cArea.append("//FontBold(true);\n");
            f.cArea.append("Antialias(true);\n");

            f.cArea.append("WriteString(10,20,18,\"sansserif\","+
                "\"漢字かなカナＡＢａｂABab01.2 [sansserif]\");\n");
            f.cArea.append("WriteString(10,30,18,\"serif\","+
                "\"漢字かなカナＡＢａｂABab01.2 [serif]\");\n");
            f.cArea.append("WriteString(10,40,18,\"monospaced\","+
                "\"漢字かなカナＡＢａｂABab01.2 [monospaced]\");\n");
            f.cArea.append("WriteString(10,50,18,\"dialog\","+
                "\"漢字かなカナＡＢａｂABab01.2 [dialog]\");\n");
            f.cArea.append("WriteString(10,60,18,\"dialoginput\","+
                "\"漢字かなカナＡＢａｂABab01.2 [dialoginput]\");\n");
            f.cArea.append("//以上は、仮想的な論理フォントで、以下は、実体的な物理フォント。\n");
            f.cArea.append("//論理フォントは、二種類の物理フォントの組み合わせである。\n");
            f.cArea.append("//\n");

            f.cArea.append("WriteString(10,70,18,\"Arial\","+
                "\"漢字かなカナＡＢａｂABab01.2 [Arial]\");\n");
            f.cArea.append("WriteString(10,80,18,\"Times New Roman\","+
                "\"漢字かなカナＡＢａｂABab01.2 [Times New Roman]\");\n");
            f.cArea.append("WriteString(10,90,18,\"Courier New\","+
                "\"漢字かなカナＡＢａｂABab01.2 [Courier New]\");\n");
            f.cArea.append("//\n");

            f.cArea.append("WriteString(10,100,18,\"ＭＳ ゴシック\","+
                "\"漢字かなカナＡＢａｂABab01.2 [ＭＳ ゴシック]\");\n");
            f.cArea.append("WriteString(10,110,18,\"ＭＳ 明朝\","+
                "\"漢字かなカナＡＢａｂABab01.2 [ＭＳ 明朝]\");\n");
            f.cArea.append("WriteString(10,120,18,\"ＭＳ Ｐゴシック\","+
                "\"漢字かなカナＡＢａｂABab01.2 [ＭＳ Ｐゴシック]\");\n");
            f.cArea.append("WriteString(10,130,18,\"ＭＳ Ｐ明朝\","+
                "\"漢字かなカナＡＢａｂABab01.2 [ＭＳ Ｐ明朝]\");\n");
            f.cArea.append("WriteString(10,140,18,\"MS UI Gothic\","+
                "\"漢字かなカナＡＢａｂABab01.2 [MS UI Gothic]\");\n");

            f.cArea.append("WriteString(10,150,18,\"游ゴシック\","+
                "\"漢字かなカナＡＢａｂABab01.2 [游ゴシック]\");\n");
            f.cArea.append("WriteString(10,160,18,\"游明朝\","+
                "\"漢字かなカナＡＢａｂABab01.2 [游明朝]\");\n");

            f.cArea.append("WriteString(10,170,18,\"BIZ UDPゴシック\","+
                "\"漢字かなカナＡＢａｂABab01.2 [BIZ UDPゴシック]\");\n");
            f.cArea.append("WriteString(10,180,18,\"BIZ UDP明朝 Medium\","+
                "\"漢字かなカナＡＢａｂABab01.2 [BIZ UDP明朝 Medium]\");\n");
            f.cArea.append("WriteString(10,190,18,\"BIZ UDゴシック\","+
                "\"漢字かなカナＡＢａｂABab01.2 [BIZ UDゴシック]\");\n");
            f.cArea.append("WriteString(10,200,18,\"BIZ UD明朝 Medium\","+
                "\"漢字かなカナＡＢａｂABab01.2 [BIZ UD明朝 Medium]\");\n");

            f.cArea.append("WriteString(10,210,18,\"Meiryo UI\","+
                "\"漢字かなカナＡＢａｂABab01.2 [Meiryo UI]\");\n");
            f.cArea.append("WriteString(10,220,18,\"メイリオ\","+
                "\"漢字かなカナＡＢａｂABab01.2 [メイリオ]\");\n");

            f.cArea.append("WriteString(10,230,18,\"UD デジタル 教科書体 N-R\","+
                "\"漢字かなカナＡＢａｂABab01.2 [UD デジタル 教科書体 N-R]\");\n");
            f.cArea.append("WriteString(10,240,18,\"UD デジタル 教科書体 NK-R\","+
                "\"漢字かなカナＡＢａｂABab01.2 [UD デジタル 教科書体 NK-R]\");\n");
            f.cArea.append("WriteString(10,250,18,\"UD デジタル 教科書体 NP-R\","+
                "\"漢字かなカナＡＢａｂABab01.2 [UD デジタル 教科書体 NP-R]\");\n");
            f.cArea.append("WriteString(10,260,18,\"UD デジタル 教科書体 N-B\","+
                "\"漢字かなカナＡＢａｂABab01.2 [UD デジタル 教科書体 N-B]\");\n");
            f.cArea.append("WriteString(10,270,18,\"UD デジタル 教科書体 NK-B\","+
                "\"漢字かなカナＡＢａｂABab01.2 [UD デジタル 教科書体 NK-B]\");\n");
            f.cArea.append("WriteString(10,280,18,\"UD デジタル 教科書体 NP-B\","+
                "\"漢字かなカナＡＢａｂABab01.2 [UD デジタル 教科書体 NP-B]\");\n");

            f.cArea.append("//フォント名は、空白の個数、全角と半角、これらの違いに注意。\n");
            f.cArea.append("//フォント名を間違うと、「sansserif」での表示になる。\n");
            f.dArea.setText("");
            f.canvas.firstPage();
            f.canvas.repaint();
            fileNameClear();
        }
        else if(com.equals("Ph2"))
        {  //●フォント表示テスト2（詳細拡張版）
           //複数のフェイスを持つものだけを表示
            f.cArea.setText("");
            f.cArea.append("//フォント表示テスト2（詳細拡張版）\n");
            f.cArea.append("DataType(\"NUL\");\n");
            f.cArea.append("Paper(\"A4\");\n");
            f.cArea.append("Antialias(true);\n");
            f.cArea.append("FontBold(false);\n");
            f.cArea.append("//FontBold(true);\n");

            f.cArea.append("WriteString(10,20,18,\"BIZ UDPゴシック Regular\","+
                "\"漢字かなカナＡＢａｂABab01.2 [BIZ UDPゴシック Regular]\");\n");
            f.cArea.append("WriteString(10,30,18,\"BIZ UDPゴシック Bold\","+
                "\"漢字かなカナＡＢａｂABab01.2 [BIZ UDPゴシック Bold]\");\n");

            f.cArea.append("WriteString(10,40,18,\"BIZ UDゴシック Regular\","+
                "\"漢字かなカナＡＢａｂABab01.2 [BIZ UDゴシック Regular]\");\n");
            f.cArea.append("WriteString(10,50,18,\"BIZ UDゴシック Bold\","+
                "\"漢字かなカナＡＢａｂABab01.2 [BIZ UDゴシック Bold]\");\n");

            f.cArea.append("WriteString(10,60,18,\"Meiryo UI Regular\","+
                "\"漢字かなカナＡＢａｂABab01.2 [Meiryo UI Regular]\");\n");
            f.cArea.append("WriteString(10,70,18,\"Meiryo UI Bold\","+
                "\"漢字かなカナＡＢａｂABab01.2 [Meiryo UI Bold]\");\n");
            f.cArea.append("WriteString(10,80,18,\"Meiryo UI Italic\","+
                "\"漢字かなカナＡＢａｂABab01.2 [Meiryo UI Italic]\");\n");
            f.cArea.append("WriteString(10,90,18,\"Meiryo Bold Italic\","+
                "\"漢字かなカナＡＢａｂABab01.2 [Meiryo UI Bold Italic]\");\n");

            f.cArea.append("WriteString(10,100,18,\"メイリオ レギュラー\","+
                "\"漢字かなカナＡＢａｂABab01.2 [メイリオ レギュラー]\");\n");
            f.cArea.append("WriteString(10,110,18,\"メイリオ ボールド\","+
                "\"漢字かなカナＡＢａｂABab01.2 [メイリオ ボールド]\");\n");
            f.cArea.append("WriteString(10,120,18,\"メイリオ イタリック\","+
                "\"漢字かなカナＡＢａｂABab01.2 [メイリオ イタリック]\");\n");
            f.cArea.append("WriteString(10,130,18,\"メイリオ ボールド イタリック\","+
                "\"漢字かなカナＡＢａｂABab01.2 [メイリオ ボールド イタリック]\");\n");

            f.cArea.append("WriteString(10,140,18,\"Yu Gothic UI Light\","+
                "\"漢字かなカナＡＢａｂABab01.2 [Yu Gothic UI Light]\");\n");
            f.cArea.append("WriteString(10,150,18,\"Yu Gothic UI Semilight\","+
                "\"漢字かなカナＡＢａｂABab01.2 [Yu Gothic UI Semilight]\");\n");
            f.cArea.append("WriteString(10,160,18,\"Yu Gothic UI Regular\","+
                "\"漢字かなカナＡＢａｂABab01.2 [Yu Gothic UI Regular]\");\n");
            f.cArea.append("WriteString(10,170,18,\"Yu Gothic UI Semibold\","+
                "\"漢字かなカナＡＢａｂABab01.2 [Yu Gothic UI Semibold]\");\n");
            f.cArea.append("WriteString(10,180,18,\"Yu Gothic UI Bold\","+
                "\"漢字かなカナＡＢａｂABab01.2 [Yu Gothic UI Bold]\");\n");

            f.cArea.append("WriteString(10,190,18,\"游ゴシック Light\","+
                "\"漢字かなカナＡＢａｂABab01.2 [游ゴシック Light]\");\n");
            f.cArea.append("WriteString(10,200,18,\"游ゴシック Regular\","+
                "\"漢字かなカナＡＢａｂABab01.2 [游ゴシック Regular]\");\n");
            f.cArea.append("WriteString(10,210,18,\"游ゴシック Medium\","+
                "\"漢字かなカナＡＢａｂABab01.2 [游ゴシック Medium]\");\n");
            f.cArea.append("WriteString(10,220,18,\"游ゴシック Bold\","+
                "\"漢字かなカナＡＢａｂABab01.2 [游ゴシック Bold]\");\n");

            f.cArea.append("WriteString(10,230,18,\"游明朝 Light\","+
                "\"漢字かなカナＡＢａｂABab01.2 [游明朝 Light]\");\n");
            f.cArea.append("WriteString(10,240,18,\"游明朝 Regular\","+
                "\"漢字かなカナＡＢａｂABab01.2 [游明朝 Regular]\");\n");
            f.cArea.append("WriteString(10,250,18,\"游明朝 Demibold\","+
                "\"漢字かなカナＡＢａｂABab01.2 [游明朝 Demibold]\");\n");

            f.cArea.append("WriteString(10,270,18,\"sansserif\","+
                "\"以下は、特殊用途に利用可能な英文フォント\");\n");
            f.cArea.append("WriteString(10,280,18,\"Consolas\","+
                "\"漢字かなカナＡＢａｂABab01.2 [Consolas]\");\n");

            f.cArea.append("//フォント名は、空白の個数、全角と半角、これらの違いに注意。\n");
            f.cArea.append("//フォント名を間違うと、「sansserif」での表示になる。\n");
            f.dArea.setText("");
            f.canvas.firstPage();
            f.canvas.repaint();
            fileNameClear();
        }
        else if(com.equals("XL6"))
        {  //●拡大率：６倍
            f.canvas.affineScale = f.xL6;
            f.scaleLabel.setText("  (scale:x6)");
            f.canvas.repaint();
        }
        else if(com.equals("XL5"))
        {  //●拡大率：５倍
            f.canvas.affineScale = f.xL5;
            f.scaleLabel.setText("  (scale:x5)");
            f.canvas.repaint();
        }
        else if(com.equals("XL4"))
        {  //●拡大率：４倍
            f.canvas.affineScale = f.xL4;
            f.scaleLabel.setText("  (scale:x4)");
            f.canvas.repaint();
        }
        else if(com.equals("XL3")) //標準設定
        {  //●拡大率：３倍
            f.canvas.affineScale = f.xL3;
            f.scaleLabel.setText("  (scale:x3)");
            f.canvas.repaint();
        }
        else if(com.equals("XL2"))
        {  //●拡大率：２倍
            f.canvas.affineScale = f.xL2;
            f.scaleLabel.setText("  (scale:x2)");
            f.canvas.repaint();
        }
        else if(com.equals("XL1"))
        {  //●拡大率：１倍
            f.canvas.affineScale = f.xL1;
            f.scaleLabel.setText("  (scale:x1)");
            f.canvas.repaint();
        }
        else if(com.equals("Usa"))
        {  //●使用法表示
            Util.textBrowseCCP(f, new File(
                "c:/user/java/PrintCSV1/sys/PrintCSV1Manual.txt")); //絶対パスで指定
        }
        else if(com.equals("Pri"))
        {  //●全ページ印刷
            if(f.canvas.check())
                f.printer.start();
        }
        else
            f.message("メニューに該当なし");
    }
    void fileNameClear()
    {
        f.dataFile.setText("  [NoName]");
        f.controlFile.setText("  [NoName]");
    }
    void fileNameClear1()
    {
        f.dataFile.setText("  [NoName]");
    }
    void fileNameClear2()
    {
        f.controlFile.setText("  [NoName]");
    }
}

◆PrintCSV1ButtonLis.java

//■■PrintCSV1ButtonLis.java

//■■インポート
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.text.*;
import java.io.*;
import java.util.*;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.util.*;
import java.util.List;
import java.text.*;
import java.awt.geom.*;
import java.io.*;
import java.awt.image.*;
import java.util.regex.*;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.awt.Image;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import javax.imageio.IIOImage;
import javax.imageio.ImageWriter;
import javax.imageio.ImageWriteParam;
import javax.imageio.stream.ImageOutputStream;

//■■JButtonのイベントリスナー
class PrintCSV1ButtonLis implements ActionListener
{
    //■メンバー変数
    PrintCSV1 f;

    //■コンストラクタ
    PrintCSV1ButtonLis(PrintCSV1 frame)
    {
        f = frame;
    }

    //■JButtonのイベントハンドラ
    public void actionPerformed(ActionEvent event)
    {
        String com = event.getActionCommand();

        if(com.equals("Dsp"))
        {  //●グラフィックの再描画（描画）
            f.canvas.repaint(); //再描画
        }

        else if(com.equals("Top"))
        {  //●前ページを再描画（前頁）
            f.canvas.firstPage();
            f.canvas.repaint();
        }
        else if(com.equals("Prv"))
        {  //●前ページを再描画（前頁）
            f.canvas.prevPage();
            f.canvas.repaint();
        }
        else if(com.equals("Nex"))
        {  //●次ページを再描画（次頁）
            f.canvas.nextPage();
            f.canvas.repaint();
        }
        else
            f.message("該当なし");
    }
}

◆PrintCSV1AnalyzeEngine.java

//■■PrintCSV1AnalyzeEngine.java

//■■インポート
import java.awt.*;  //必須
import java.util.*;  //必須
import java.io.*;  //必須

//■■描画機能の実現
class PrintCSV1AnalyzeEngine
{
  //■メンバー変数
  PrintCSV1 f; //JFlameの継承インスタンス
  PrintCSV1CanvasEngine canvas;
  Graphics2D g2;

  //■コンストラクタ
  PrintCSV1AnalyzeEngine(PrintCSV1 flame)
  {
    f = flame;
    canvas = f.canvas;
  }

  void commandAnalyze(String commandZ)
  {
    //●命令文の全文解析
    //命令の引数が多いものや紛らわしいものだけ、数のチェックを行なう。
    boolean commentFlag = false;
    StringTokenizer st1 = new StringTokenizer(commandZ,";\n");
      //命令全文に対して、セミコロンと改行コードで切り分ける準備
    String method = ""; //引数を含まない命令本体部分
    String define = ""; //一行分の定義文（引数を含む命令文）

    //●書式定義文の解析
    try
    {
        for(int i=0; st1.hasMoreTokens(); i++)
        { //●コマンドの全文解読
            define = st1.nextToken().trim(); //一行分の定義文
            if(define.equals(""))
                continue;
            if(define.substring(0,2).equals("//")) //◆コメント文
                continue;
            StringTokenizer st2 = new StringTokenizer(define,"(,)");
            //引数区切
            method = st2.nextToken().trim();

            //●コマンドの解析実施
            if(method.equals("Antialias"))
            { //●線（輪郭線を含む）のギザギザ緩和
              //この処理で、細い線の色が薄くなる
              String s1 = st2.nextToken().trim();
              if(s1.equals("true"))
                canvas.antialiasFlag = true;
              else
                canvas.antialiasFlag = false;
            }
            else if(method.equals("Paper"))
            { //●紙の大きさ（ミリで指定）
                //Paper(double w, double h)
                //w:        用紙巾
                //h:        用紙丈
                String s = st2.nextToken().trim();
                if     (s.equals("\"A3\""))
                {  //常用判型は簡易指定可能
                    canvas.paperW = 297;
                    canvas.paperH = 420;
                }
                else if(s.equals("\"B4\""))
                {
                    canvas.paperW = 257;
                    canvas.paperH = 364;
                }
                else if(s.equals("\"A4\""))
                {
                    canvas.paperW = 210;
                    canvas.paperH = 297;
                }
                else if(s.equals("\"B5\""))
                {
                    canvas.paperW = 182;
                    canvas.paperH = 257;
                }
                else if(s.equals("\"A5\""))
                {
                    canvas.paperW = 148;
                    canvas.paperH = 210;
                }
                else if(s.equals("\"B6\""))
                {
                    canvas.paperW = 128;
                    canvas.paperH = 182;
                }
                else if(s.equals("\"A6\""))
                {
                    canvas.paperW = 105;
                    canvas.paperH = 148;
                }
                else if(s.equals("\"PostCard\""))
                {
                    canvas.paperW = 100;
                    canvas.paperH = 148;
                }
                else
                {
                    canvas.paperW = Double.parseDouble(s);
                    canvas.paperH = Double.parseDouble(st2.nextToken().trim());
                }
            }
            else if(method.equals("FreeScale"))
            { //●任意の拡大率の指定（印刷にも反映される）
                //FreeScale(double scale)
                //scale:    表示、画像保存、印刷に有効な拡大倍率
                canvas.freeScale = Double.parseDouble(st2.nextToken().trim());
            }
            else if(method.equals("Margin"))
            { //●余白指定
                //Margin(double top, double left)
                //top:      天余白
                //left:     左余白
                canvas.marginT = Double.parseDouble(st2.nextToken().trim());
                canvas.marginL = Double.parseDouble(st2.nextToken().trim());
            }
            else if(method.equals("Shift"))
            { //●画像をずらす指定
                //Shift(int mode, double x, double y)
                //mode:     0がシフトなし
                //          1が無条件強制シフト
                //          2が偶数ページシフト
                //x:        X軸のシフト量
                //y:        Y軸のシフト量
                canvas.shiftClass = Integer.parseInt(st2.nextToken().trim());
                canvas.shiftX = Double.parseDouble(st2.nextToken().trim());
                canvas.shiftY = Double.parseDouble(st2.nextToken().trim());
            }
            else if(method.equals("PageHeader"))
            { //●ページ見出しの高さ
                //PageHeader(double h)
                //h:        ページ見出しの高さ
                canvas.pHeaderH = Double.parseDouble(st2.nextToken().trim());
            }
            else if(method.equals("TableHeader"))
            { //●表組み枠線内の見出しの高さ
                //TableHeader(double h)
                //h:        表見出しの高さ
                canvas.tHeaderH = Double.parseDouble(st2.nextToken().trim());
            }
            else if(method.equals("Title"))
            { //●表組みのタイトル
                //Title(String title)
                //title:    標題文字列
                canvas.title = Util.trimAffix(st2.nextToken(),'\"','\"');
            }
            else if(method.equals("Subtitle"))
            { //●表組みのサブタイトル
                //Subtitle(String subtitle)
                //subtitle: 副題文字列
                canvas.subtitle = Util.trimAffix(st2.nextToken(),'\"','\"');
            }
            else if(method.equals("Number"))
            { //●書類の任意の発行番号（または発行年月日）
                //Number(String number)
                //number:   発行番号文字列
                canvas.number = Util.trimAffix(st2.nextToken(),'\"','\"');
            }
            else if(method.equals("PageNumber"))
            { //●書類のページ表示
                //PageNumber(bool pn)
                //pn:      ページ番号を表示するか否か
                String value = st2.nextToken().trim();
                if(value.equals("false"))
                    canvas.pageNumFlag = false;
                else
                    canvas.pageNumFlag = true;
            }
            else if(method.equals("FontSize"))
            { //●文字サイズ指定
                //FontSize(int point)
                //point:    文字のポイント数
                canvas.fontSize = Integer.parseInt(st2.nextToken().trim());
            }
            else if(method.equals("FontBold"))
            { //●太字指定
                //FontBold(bool b)
                //b:        太字指定ならtrue
                String value = st2.nextToken().trim();
                if(value.equals("true"))
                {
                    canvas.fontBold = true;
                }
                else if(value.equals("false"))
                {
                    canvas.fontBold = false;
                }
                else
                    throw new Exception(">>" + define);
            }
            else if(method.equals("Column"))
            { //●罫線表の行の指定（各欄の横幅）
                //Column(double w, String constant, String header)
                //w:        列の幅
                //constant: 位置合せ定数 (L/R/Nx)
                //header:   見出し文字列
                canvas.columnList.add(define); //コマンドの分解は後回し
            }
            else if(method.equals("ColumnBlock"))
            { //●罫線表の列のブロック化
                //ColumnBlock(int ... separator)
                //    separator:縦線の点線か実線かの指定
                //0が点線、1が実線で、この組合せで指定する。
                //カラム間の境界線指定のため、個数はカラム数より１少ない。
                StringTokenizer st3 = new StringTokenizer(define,"();");
                st3.nextToken(); //コマンド名は捨てる
                String sep = st3.nextToken().trim(); //引数群を取得
                canvas.colBlockArr = Util.csv2Array(sep, 0);
            }
            else if(method.equals("Row"))
            { //●罫線表の行の指定
                //Row(double h, double rows)
                //h:        行の高さ
                //rows:     １ページの行数
                canvas.cellH = Double.parseDouble(st2.nextToken().trim());
                canvas.rows  = Integer.parseInt(st2.nextToken().trim())-1;
            }
            else if(method.equals("RowBlock"))
            { //●罫線表の行のブロック化指定
                //RowBlock(int b)
                //b:        行のブロック化を指定
                //例えば、3を指定すれば、点線の横罫線が２行のあと、
                //実線の横罫線が１行の繰り返しとなる。
                canvas.rowBlock = Integer.parseInt(st2.nextToken().trim());
            }
            else if(method.equals("LineWidth"))
            { //●直線の太さ指定
                //LineWidth(float width)
                //width:    線の太さ
                canvas.lineWidth = Float.parseFloat(st2.nextToken().trim());
            }
            else if(method.equals("DrawLine"))
            { //●直線の描画指定
                //DrawLine(double x1, double y1, double x2, double y2)
                //x1:       起点座標X
                //y1:       起点座標Y
                //x2:       終点座標X
                //y2:       終点座標Y
                double[] d = new double[4];
                d[0] = Double.parseDouble(st2.nextToken().trim());
                d[1] = Double.parseDouble(st2.nextToken().trim());
                d[2] = Double.parseDouble(st2.nextToken().trim());
                d[3] = Double.parseDouble(st2.nextToken().trim());
                canvas.lineList.add(d);
            }
            else if(method.equals("WriteString") &&
               Util.countDelimiter(define, ',') == 4)
            { //●文字列の描画指定（詳細設定）
                //WriteTitle(double x,double y,int size,
                //          string style, string s)
                //x:        ｘ座標
                //y:        ｙ座標
                //size:     フォント・サイズ
                //style:    字形（"sansserif" / "serif" / "monospaced"）
                //s:        表示する文字列
                double[] d = new double[3];
                String[] s = new String[2];
                d[0] = Double.parseDouble(st2.nextToken().trim());
                d[1] = Double.parseDouble(st2.nextToken().trim());
                d[2] = Double.parseDouble(st2.nextToken().trim());
                s[0] = Util.trimAffix(st2.nextToken().trim(),'\"','\"');
                s[1] = Util.trimAffix(st2.nextToken().trim(),'\"','\"');
                canvas.freeStringListD.add(d);
                canvas.freeStringListS.add(s);
            }
            else if(method.equals("WriteString") &&
               Util.countDelimiter(define, ',') == 2)
            { //●文字列の描画指定（簡易設定）
                //WriteString(double x,double y,string s)
                //x:        ｘ座標
                //y:        ｙ座標
                //s:        表示する文字列
                double[] d = new double[3];
                String[] s = new String[2];
                d[0] = Double.parseDouble(st2.nextToken().trim());
                d[1] = Double.parseDouble(st2.nextToken().trim());
                d[2] = canvas.fontSize;
                s[0] = "sansselif";
                s[1] = Util.trimAffix(st2.nextToken().trim(),'\"','\"');
                canvas.freeStringListD.add(d);
                canvas.freeStringListS.add(s);
            }
            else if(method.equals("Footnote"))
            { //●罫線表の脚注
                //Footnote(String s)
                //s:        脚注文字列
                canvas.footnote = Util.trimAffix(st2.nextToken(),'\"','\"');
            }
            else if(method.equals("Footnote2"))
            { //●罫線表の脚注2行目
                //Footnote2(String s)
                //s:        脚注文字列
                canvas.footnote2 = Util.trimAffix(st2.nextToken(),'\"','\"');
            }
            else if(method.equals("DrawMode"))
            { //●罫線表の罫線の描画指定
                //DrawMode(int mode)
                //   mode:     3なら、罫線の縦横共に描画
                //             2なら、罫線の横だけ描画
                //             1なら、罫線の縦だけ描画
                //             0なら、罫線を描画しない
                String value = st2.nextToken().trim();
                if(value==null)
                    throw new Exception("罫線表モードに問題あり");
                if(value.equals("3"))
                    canvas.drawMode = 3; //罫線を縦横共に描画
                else if(value.equals("2"))
                    canvas.drawMode = 2; //罫線を横だけ描画
                else if(value.equals("1"))
                    canvas.drawMode = 1; //罫線を縦だけ描画
                else if(value.equals("0"))
                    canvas.drawMode = 0; //罫線を描画しない
                else
                    throw new Exception("罫線表モードに問題あり");
            }
            else if(method.equals("LoadData"))
            { //●データ・ファイルの指定（データが決まっている場合）
                //LoadData(String fileName)
                //FileName:     データ・ファイルを指定
                //例えば、"./data/filename.csv"
                canvas.loadData = Util.trimAffix(st2.nextToken(),'\"','\"');
            }
            else if(method.equals("SetData"))
            { //●データの設定（データが決まっている場合）
                //SetData(String str)
                //str:          データ文字列を指定
                //例えば、" " のように空白文字をダミー・データとする
                String str = Util.trimAffix(st2.nextToken(),'\"','\"');
                f.dArea.setText(str);
            }
            else if(method.equals("DataType"))
            { //●データがCSVかTXTかの指定
                //DataType(String s)
                //s:       "CSV"なら、CSVデータ（コンマでの区切り）
                //         "NUL"なら、データなし
                String value = Util.trimAffix(st2.nextToken(),'\"','\"');
                if(value.equals("CSV"))
                {
                    canvas.dataType = "CSV";
                    canvas.dataFlag = true;
                }
                else if(value.equals("NUL"))
                {
                    canvas.dataType = "NUL";
                    canvas.dataFlag = false;
                }
                else
                {
                    canvas.dataInspection = false;
                    throw new Exception("データ・タイプ指定が異常\n");
                }
            }
            else if(method.equals("Rotate"))
            { //●画像の90度回転指定
                //Rotate(bool b)
                //b:        trueの時だけ90度回転
                String value = st2.nextToken().trim();
                if(value.equals("true"))
                    canvas.rotateFlag = true;
                else
                    canvas.rotateFlag = false;
            }
            else
            { //●定義文が異常
                canvas.dataInspection = false;
                throw new Exception("メソッド名が異常：" + define);
        }
        } // for文の終結

        //●Columnコマンド群の分解
        method = "Column";
        Object[] objArr = canvas.columnList.toArray(); //Column(w,aline,header);
        if(objArr!=null)
            canvas.cols = objArr.length-1; //0起算
        else
            canvas.cols = 0;
        canvas.colW = new double[canvas.cols+1];
        canvas.align = new String[canvas.cols+1];
        canvas.header = new String[canvas.cols+1];
        String columnCommands[] = new String[canvas.cols+1];
        for(int i=0; i<canvas.cols+1; i++)
            columnCommands[i] = (String)objArr[i];
        for(int i=0; i<canvas.cols+1; i++)
        {
            define = columnCommands[i];
            StringTokenizer st3 = new StringTokenizer(
                columnCommands[i],"(,)");  //引数区切
            st3.nextToken().trim(); //Columnコマンド名は捨てる
            canvas.colW[i] = Double.parseDouble(st3.nextToken().trim());
            String a = canvas.align[i] = Util.trimAffix(
                st3.nextToken().trim(),'\"','\"');
            canvas.header[i] = Util.trimAffix(st3.nextToken().trim(),'\"','\"');
            if(!(a.equals("L")||
                 a.equals("R")||
                (a.charAt(0)=='N' && a.length()==2 && '0'<=a.charAt(1)
                    && '9'>=a.charAt(1)) ))
                // L:左詰め
                // R:右詰め
                // Nx:数字にコンマ付加、小数点以下x桁に丸め処理
            {
                canvas.dataInspection = false;
                throw new Exception("Columnの引数に異常");
            }
        }
    }
    catch(NumberFormatException e)
    {
        f.message("数値表現エラー");
        f.message(">> "+define);
        canvas.methodErrorMessage(method + "(");
        canvas.dataInspection = false;
    }
    catch(Exception e)
    {
        f.message(e.toString());
        f.message(">> "+define + "コマンド解析エラー");
        canvas.methodErrorMessage(method + "(");
        canvas.dataInspection = false;
    }
  }
}

◆PrintCSV1CanvasEngine.java

//■■PrintCSV1CanvasEngine.java

//■■インポート
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.util.*;
import java.util.List;
import java.text.*;
import java.awt.geom.*;
import java.io.*;
import java.awt.image.*;
import java.util.regex.*;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.awt.Image;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import javax.imageio.IIOImage;
import javax.imageio.ImageWriter;
import javax.imageio.ImageWriteParam;
import javax.imageio.stream.ImageOutputStream;

//■■罫線入りの書式を描画する。
//その他、拡張機能あり
class PrintCSV1CanvasEngine extends JLabel
{
    //■メンバー変数
    PrintCSV1 f;
    private int page = 0; //現在ページを記憶
    Graphics2D g2;

    //●書式定義関連の変数
    double paperW;    //紙巾(mm)
    double paperH;    //紙丈
    double marginT;   //余白 Top
    double marginL;   //同 Left
    double pHeaderH;  //ページ見出し丈
    double tHeaderH;  //表見出し丈
    double cellH;     //セル丈 (mm)
    int    shiftClass;//シフト区分
    double shiftX;    //シフト量X
    double shiftY;    //シフト量Y

    double affineScale  = 1.0; //表示倍率
    double freeScale    = 1.0; //追加倍率
    double affineDegree = 0.0; //90度回転角度
    boolean rotateFlag  = false;//90度回転モード
    boolean antialiasFlag = true;//文字や線の輪郭のギザギザ緩和

    int fontSize;     //文字基準サイズ(point)
    boolean fontBold; //文字の太字指定
    float lineWidth;
    String title;     //標題
    String subtitle;  //副標題
    String number;    //発行番号
    boolean pageNumFlag; //ページ番号の有無
    String footnote;  //脚注
    String footnote2;  //脚注2
    boolean dataFlag; //データを用いるか否か
    String loadData;  //データファイル名
    double colW[];    //カラム巾(mm)
    String align[];   //左詰、右詰、数値整形指定
    String header[];  //表見出し
    int cols;         //１行の総欄数    （0起算）
    int rows;         //１ページの総行数（0起算）
    int rowBlock;     //行のブロック化
    int drawMode;     //表組罫線の描画モード
    double[][] lineArr;     //自由直線
    double[][] stringArrD;  //自由文字列
    String[][] stringArrS;
    String[]   colBlockArr;  //カラム・セパレーター
    Color backgroundColor;
    Color foregroundColor;
    float quality = 1.0f; //高画質保存（デフォルト）
        //画質は、０から１を指定できる（１が最高画質）
    double currentPointX;
    double currentPointY;

    //●CSVデータ関連の変数
    int pages;    //総ページ数      （0起算）
    int lines;    //総行数          （0起算）
    int colCnt;   //欄数カウンター  （0起算）
    int rowCnt;   //行数カウンター  （0起算）
    int pageCnt;  //ページカウンター（0起算）
    int lineCnt;  //総行数カウンター（0起算）
    String dataArray[][][]; //ページ・行・欄
    boolean dataInspection; //データ整合性
    boolean grpSaveMode; //trueなら画像保存モード
    String dataType = "NUL";
    String[] manualArr = null;
    String errMsg;    //エラーメッセージ

    //●リスト関連の変数
    List<double[]> lineList      = new ArrayList<double[]>();
    List<double[]> stringDataListD   = new ArrayList<double[]>();
    List<String[]> stringDataListS   = new ArrayList<String[]>();
    List<double[]> freeStringListD    = new ArrayList<double[]>();
    List<String[]> freeStringListS    = new ArrayList<String[]>();
    List<double[]> freeStringListBD    = new ArrayList<double[]>();
    List<String[]> freeStringListBS    = new ArrayList<String[]>();
    List<String> columnList     = new ArrayList<String>();
    List<String> columnLineList = new ArrayList<String>();

    //■コンストラクタ
    PrintCSV1CanvasEngine(PrintCSV1 frame)
    {
        f = frame;
        affineScale = f.xL3; //拡大率初期設定
        f.scaleLabel.setText("  (scale:x3)"); //表示拡大率設定
        manualArr = TextIO.loadStrArr(
          new File("c:/user/java/PrintCSV1/sys/PrintCSV1Manual.txt"));
    }

    //■書式定義を読取り、各変数をセット
    void setDef(String str)
    { //●外部プログラムから操作
        f.cArea.setText(str);
        setDef();
    }

    void setDef()
    { //●内部プログラムから操作
        //●変数の初期化
        paperW       = 362; //描画範囲初期設定
        paperH       = 512; //描画範囲初期設定
        freeScale    = 1.0; //印刷に反映される拡大倍率
        fontSize     = 10;
        fontBold     = false;
        antialiasFlag = true;
        lineWidth    = 0.2f;
        marginT      = 0;
        marginL      = 0;
        pHeaderH     = 0; //page header
        tHeaderH     = 0; //table header
        cellH        = 0;
        shiftClass   = 0;
        shiftX       = 0;
        shiftY       = 0;
        colCnt       = 0;
        rowBlock     = 1;
        drawMode     = 3; //罫線描画モード
        title        = "";
        subtitle     = "";
        number       = "";
        footnote     = "";
        footnote2     = "";
        errMsg       = "";
        loadData     = "";
        dataType     = "NUL"; //CSV, NUL
        pageNumFlag  = false;
        dataFlag     = true;
        rotateFlag   = false;
        colBlockArr  = null;
        backgroundColor = Color.white;
        foregroundColor = Color.black;

        String define     = ""; //定義文の１行分を格納
        lineList          = new ArrayList<double[]>();
        stringDataListD   = new ArrayList<double[]>();
        stringDataListS   = new ArrayList<String[]>();
        freeStringListD   = new ArrayList<double[]>();
        freeStringListS   = new ArrayList<String[]>();
        freeStringListBD  = new ArrayList<double[]>();
        freeStringListBS  = new ArrayList<String[]>();
        columnList        = new ArrayList<String>();
        columnLineList    = new ArrayList<String>();

        //------------------------------------------------------------
        // 命令文全行の解析
        f.analyze.commandAnalyze(f.cArea.getText());
        //------------------------------------------------------------

        //●直線 数値の取得
        Object[] lineArr0 = lineList.toArray();
        lineArr  = new double[lineArr0.length][4];
        for(int i=0; i<lineArr0.length; i++)
        {
            lineArr[i][0] = ((double[])(lineArr0[i]))[0];
            lineArr[i][1] = ((double[])(lineArr0[i]))[1];
            lineArr[i][2] = ((double[])(lineArr0[i]))[2];
            lineArr[i][3] = ((double[])(lineArr0[i]))[3];
        }

        //●自由文字列）　データの取得
        Object[] stringArrD0 = freeStringListD.toArray();
        Object[] stringArrS0 = freeStringListS.toArray();
        stringArrD = new double[stringArrD0.length][3];
        stringArrS = new String[stringArrS0.length][2];
        for(int i=0; i<stringArrD0.length; i++)
        {
            stringArrD[i][0] = ((double[])(stringArrD0[i]))[0];
            stringArrD[i][1] = ((double[])(stringArrD0[i]))[1];
            stringArrD[i][2] = ((double[])(stringArrD0[i]))[2];
        }
        for(int i=0; i<stringArrD0.length; i++)
        {
            stringArrS[i][0] = ((String[])(stringArrS0[i]))[0];
            stringArrS[i][1] = ((String[])(stringArrS0[i]))[1];
        }

        if(dataFlag==false && loadData.equals(""))
        {  //●データを用いない指定であれば
            setData(" "); //データを消去し、スペースを一つ入れる
        }
        else if(dataFlag==false && !loadData.equals(""))
        {  //●データを用いないのにファイルの指定があれば
            dataInspection = false;  //定義ミス
            errMsg += ("Data と LoadData とに矛盾\n");
            return;
        }
        else if(dataFlag==true && loadData.equals(""))
        {  //●データファイルなしであれば
            ; //現在表示されているデータを用い
        }
        else if(dataFlag==true && !loadData.equals(""))
        {  //●データファイルの指定があれば
            String line = null;
            String line2 = "";
            BufferedReader reader = null;
            try
            {  //データファイルを読み込む
                reader = new BufferedReader(new FileReader(loadData));
                try
                {
                    String [] rowData;
                    for(int i=0;;i++) //データ読取り
                    {
                        line =  reader.readLine();
                        if(line == null) //終端超越なら
                            break;
                        else
                            line2 = line2 + line + "\n";
                    }
                }
                catch(IOException e)
                {
                    f.message("読取エラー>>\n" +e);
                }
                reader.close();
            }
            catch(FileNotFoundException e)
            {
                f.message("オープンエラー>>\n"+e);
            }
            catch(IOException e)
            {
                f.message("クローズエラー>>\n"+e);
            }
            setData(line2);
        }
    } //setDef()

    //■データをデータエリアから読取り、各変数をセット
    void setData(String str) //外部プログラムから操作
    {
        f.dArea.setText(str);
        setData();
    }

    //●内部プログラムから操作
    void setData()
    {
        String data = f.dArea.getText();
        StringTokenizer st1 = new StringTokenizer(data,"\n");
          //空行は副作用で削除される
        //●ページ計算 総行数計算
        String dataP = "";
        for(rowCnt=0,pageCnt=0,lineCnt=0;
            st1.hasMoreTokens(); rowCnt++,lineCnt++)
        {
            String str = st1.nextToken();

            if(rowCnt>=rows)
            { //▼１ページ処理完了
                pageCnt++;
                rowCnt = -1; //頁行カウントを -1 に
            }
            dataP = dataP + str +"\n";
        }
        data = dataP;
        if(rowCnt==0)
            pageCnt--;
        pages = pageCnt;
        lines = --lineCnt; //総行数

        //●データの三次元配列化(ページ・行・列)
        dataArray = new String[pages+1][rows+1][];
        st1 = new StringTokenizer(data,"\n");
        for(pageCnt=0,rowCnt=0,lineCnt=0; lineCnt<=lines;)
        {
            String record = st1.nextToken();
            String c[] = null;
            if(!record.equals("\n"))
            {
                if(dataType.equals("CSV"))
                    c = Util.csv2Array(record.trim(),cols+1);
                else if(dataType.equals("NUL"))
                    c = new String[] {""};
                else
                    c = new String[] {record}; //一応、こうする
                if(c==null)
                {
                    if(cols==0)
                        return;
                    errMsg += ("データ項目数異常："+(rowCnt+1)+"行目\n");
                    dataInspection = false;
                    return;
                }
            }
            else
            {
                errMsg += ("データ空行："+(rowCnt+1)+"行目\n");
                dataInspection = false;
                return;
            }
            dataArray[pageCnt][rowCnt] = c;
            rowCnt++;
            lineCnt++;
            if(rowCnt>rows) //１ページ処理完了
            {
                pageCnt++;  //ページカウント
                rowCnt=0;   //行カウントをリセット
            }
        }
    } //setData()

    //■先頭ページに設定
    void firstPage()
    {
        page = 0; //0起算
    }

    //■前ページへ
    void prevPage()
    {
        if(page > 0)
            page--;
        else
            page = pages;
    }

    //■次ページへ
    void nextPage()
    {
        if(page < pages)
            page++;
        else
            page = 0;
    }

    //■定義とデータのチェック
    boolean check()
    {
        dataInspection = true;
        setDef();
        setData();
        if(dataInspection == false)
        {  //定義やデータに誤りがあれば
            f.message(errMsg);
            return false;
        }
        else
            return true;
    }

    //■描画処理
    public void paintComponent(Graphics g)
    {
        drawPage(g, false, page);
    }

    //■１ページ分の描画
    void drawPage(Graphics g, boolean printMode, int page)
    {
        g2 = (Graphics2D)g;

        //●描画の初期対応
        g2.setColor(new Color(255,127,255)); //画像描画範囲外（淡青緑）
        g2.fill(new Rectangle2D.Double(
            0,0,f.canvas.getPreferredSize().width,
            f.canvas.getPreferredSize().height));
        g2.setColor(new Color(127,255,255)); //画像描画範囲外（淡青緑）
        g2.fill(new Rectangle2D.Double(0,0,3000,3000));

        if(!check())
            return;
        g2.scale(affineScale*freeScale, affineScale*freeScale); //拡大
        if(rotateFlag) //90度回転させる
        {
            g2.translate(paperH, 0); //移動
            affineDegree = 90; //90度回転指定
            g2.rotate(affineDegree*Math.PI/180.0);
            //座標原点を中心に回転（指定順注意）
        }
        if(antialiasFlag == true) //線のギザギザ緩和
          //細い線の色が、周囲との中間色化で、非常に薄くなる
          g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
            RenderingHints.VALUE_ANTIALIAS_ON);
        else
          g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
            RenderingHints.VALUE_ANTIALIAS_OFF);

        //●フォントサイズの調整計算（整数）
        int fSize;
        fSize = (int)(fontSize/3.0); //標準表示倍率が３倍のため、大きさを調整
        Font fontL, fontM;
        if(fontBold == false)
        {
            fontL = new Font(Font.SANS_SERIF,Font.PLAIN,
                (int)(fSize*1.2));
            fontM = new Font(Font.SANS_SERIF,Font.PLAIN,
                fSize);
        }
        else
        {
            fontL = new Font(Font.SANS_SERIF,Font.BOLD,
                (int)(fSize*1.2));
            fontM = new Font(Font.SANS_SERIF,Font.BOLD,
                fSize);
        }

        //●印面原点の計算
        double innerX0 = marginL;
        double innerY0 = marginT;

        //●シフト有効時の印面原点の計算
        if( (shiftClass==1) || //無条件シフト
            (shiftClass==2 && (page%2 ==1)))
        {  //偶数ページシフト;  page:0起算
            innerX0 = marginL + shiftX;
            innerY0 = marginT + shiftY;
        }
        else if(shiftClass==2 && (page%2 ==0))
        {  //偶数ページシフト;  page:0起算
            innerX0 = marginL;
            innerY0 = marginT;
        }

        //●シフト処理の計算
        double sX = 0;
        double sY = 0;
        if( (shiftClass==1) ||
            (shiftClass==2 && (page%2 ==1)) )
        {  //強制シフトまたは条件シフト偶数ページ
            sX = shiftX;
            sY = shiftY;
        }
        else if(
            (shiftClass==2 && (page%2 ==0)) )
        {  //強制シフトまたは条件シフト奇数ページ
            sX = 0;
            sY = 0;
        }

        //●罫線表各部の寸法取得
        double cellW[] = new double[cols+1];
        for(int i=0; i<colW.length; i++)
            cellW[i] = colW[i];
        double innerW = 0;  //印面幅
        for(int i=0; i<cellW.length; i++)
            innerW += cellW[i];

        //●罫線表の表題位置の計算
        FontMetrics fm = g2.getFontMetrics(fontL);
        double titleX = innerX0 + (innerW - fm.stringWidth(title))/2;
        double titleY;
        double titleH = fm.getAscent();
        if(!title.trim().equals(""))
            titleY  = innerY0 + titleH;
        else
            titleY  = innerY0;

        //●発行番号（発行年月日）位置の計算
        fm = g2.getFontMetrics(fontM);
        double numberX  = innerX0 + (innerW - fm.stringWidth(number));
        double numberY;
        if(pageNumFlag==true)
            numberY  = innerY0 + pHeaderH -fm.getAscent()*1.7;
        else
            numberY  = innerY0 + pHeaderH -fm.getAscent()*0.7;

        //●ページ番号位置の計算
        double pageNumberX = innerX0 + (innerW - fm.stringWidth(
            "page # " + (page+1) + "/" + (pages+1)));
        double pageNumberY = innerY0 + pHeaderH -fm.getAscent()*0.5;
        f.pageNumLabel.setText("  (page:" + (page+1) + "/" + (pages+1)+")");

        //●罫線表座標の計算
        double tHeaderY = innerY0+ pHeaderH;
        double gridY = tHeaderY + tHeaderH;
            //gridとは、罫線表の見出し枠を除く格子状のデータ記入欄。
        double gridW = 0.0;
        for(int i=0; i<cellW.length; i++)
            gridW += cellW[i];
        double gridH = cellH*(rows+1);

        //●脚注位置の計算
        fm = g2.getFontMetrics(fontM);
        double footnoteX  = innerX0;
        double footnoteY  = tHeaderY + tHeaderH + gridH + fm.getAscent()*1.5;

        //●脚注2位置の計算
        fm = g2.getFontMetrics(fontM);
        double footnote2X  = innerX0;
        double footnote2Y  = footnoteY + fm.getAscent()*1.5;

        //●線の描画準備
        BasicStroke solidLine = new BasicStroke( //実線の定義
            lineWidth, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER);
        BasicStroke dotLine = new BasicStroke( //点線の定義
            lineWidth, BasicStroke.CAP_SQUARE,
            BasicStroke.JOIN_MITER, 1f, new float[] {1f,1f}, 0f);
        g2.setStroke(solidLine); //実線に設定

        g2.setPaint(backgroundColor); //背景色
        g2.fill(new Rectangle2D.Double(0,0,paperW,paperH));
        g2.setColor(foregroundColor);

        //------------------------------------------------------------

        //●横罫線描画
        if(drawMode==3 || drawMode==2)
        {
            g2.draw(new Line2D.Double(
                innerX0, tHeaderY, innerX0+gridW, tHeaderY));
            for(int i=0; i<=(rows+1); i++)
            {
                if((i)%rowBlock == 0)
                    g2.draw(new Line2D.Double(
                        innerX0, gridY+cellH*i,
                        innerX0+gridW, gridY+cellH*i));
                else
                {
                    float dot[] = {0.4f,0.4f};
                    g2.setStroke(dotLine); //点線
                    g2.draw(new Line2D.Double(
                        innerX0, gridY+cellH*i,
                        innerX0+gridW, gridY+cellH*i));
                    g2.setStroke(solidLine); //実線
                }
            }
        }
        //●縦罫線、ヘッダー文字描画
        g2.setFont(fontM);
        fm = g2.getFontMetrics(fontM);
        double sumW=0;
        g2.setStroke(solidLine); //直線
        for(int i=0; i<(cols+1); i++)
        {
            if(i>=1 && colBlockArr != null) //縦線の区分
            {
                if(colBlockArr[i-1].equals("0"))
                    g2.setStroke(dotLine); //点線
                else
                    g2.setStroke(solidLine); //直線
            }
            if(drawMode==3 || drawMode==1)
                g2.draw(new Line2D.Double(
                    innerX0+sumW, tHeaderY, innerX0+sumW, gridY+gridH));
            if(tHeaderH>0)
            {
                String header2[] = Util.csv2Array(header[i], 0, '/');
                g2.drawString(header2[0],
                    (float)(innerX0+sumW+(cellW[i] -
                        fm.stringWidth(header2[0]))/2),
                    (float)(tHeaderY+fm.getAscent()+cellH*0.2D));
                if(header2.length==2)
                    g2.drawString(header2[1],
                        (float)(innerX0+sumW+(cellW[i] -
                            fm.stringWidth(header2[1]))/2),
                        (float)(tHeaderY+fm.getAscent()*2+cellH*0.2D));
            }
            sumW += cellW[i];
        } //for
        g2.setStroke(solidLine);
        if(drawMode==3 || drawMode==1)  //縦線の最後の一本
            g2.draw(new Line2D.Double(
                innerX0+sumW, tHeaderY,
                innerX0+sumW, gridY+gridH));

        //●データ文字描画（罫線内に書き込むデータ）
        sumW=0;
        for(rowCnt=0; page*(rows+1)+rowCnt<=lines && rowCnt<=rows &&
            dataType.equals("CSV"); rowCnt++)
        {
            for(colCnt=0; colCnt<=cols; colCnt++)
            {
                //▼データ・フィールドの取得
                String field = null;
                try
                {
                    field = dataArray[page][rowCnt][colCnt];
                    if(field == null)
                    {
                        f.message("データの最終行超越");
                        return;
                    }
                }
                catch(IndexOutOfBoundsException e)
                {
                    f.message("データ配列の境界超越");
                    return;
                }
                g2.setColor(Color.black);
                //▼文字列の位置合わせ
                double strMargin = fm.getAscent()*0.5;
                if(align[colCnt].equals("L"))
                { //文字列の左詰め表示
                    g2.drawString(field,
                        (float)(innerX0+sumW+strMargin),
                        (float)(tHeaderY+tHeaderH+cellH*(rowCnt+1)
                            -strMargin));
                }
                else if(align[colCnt].equals("R"))
                { //文字列の右詰め表示
                    g2.drawString(field,
                        (float)(innerX0+sumW+cellW[colCnt]
                            -fm.stringWidth(field)-strMargin),
                        (float)(tHeaderY+tHeaderH+cellH*(rowCnt+1)
                            -strMargin));
                }
                else if(align[colCnt].charAt(0)=='N')
                { //数値にコンマを付加し、小数桁を整形処理
                  //N0,N1,N3,N4などの形式で、小数桁を指定して揃える
                  //整数なら「N0」を指定する
                    try
                    {
                        int fraction =
                            (int)(align[colCnt].charAt(1))-(int)'0';
                        field = Util.decimalStringFormat(field, fraction);
                    }
                    catch(NumberFormatException e)
                    {
                        f.message("数値整形エラー："+field);
                        return;
                    }
                    g2.drawString(field,
                        (float)(innerX0+sumW+cellW[colCnt]
                        -fm.stringWidth(field)-strMargin),
                        (float)(tHeaderY+tHeaderH+cellH*(rowCnt+1)
                        -strMargin));
                }
                sumW += cellW[colCnt];
            }// for
            sumW=0;
        }// for

        g2.setColor(Color.black);
        //●標題、ページ、注記の文字描画
        g2.setFont(fontL);
        g2.drawString(title,(float)titleX,(float)(titleY+1.0));
            //1.0は端数切上げ
        g2.setFont(fontM);
        g2.drawString(subtitle,(float)innerX0,(float)pageNumberY);
        g2.drawString(number,(float)numberX,(float)numberY);
        if(pageNumFlag==true)
            g2.drawString("page # " + Integer.toString(page+1) +
                "/" + (pages+1),
                (float)pageNumberX, (float)pageNumberY);
        g2.setFont(fontM);
        g2.drawString(footnote, (float)footnoteX, (float)footnoteY);
        g2.drawString(footnote2, (float)footnote2X, (float)footnote2Y);

        //-----------------------------------------------------------------

        //●直線の描画
        for(int i=0; i<lineArr.length; i++)
        {
            g2.draw(new Line2D.Double(
                lineArr[i][0]+sX,lineArr[i][1]+sY,
                lineArr[i][2]+sX,lineArr[i][3]+sY));
        }
        //●プレビューの場合だけ描画有効範囲枠線を表示
        if(printMode==false && grpSaveMode==false)
        {
            g2.setColor(Color.red);
            int z = (int)(paperW/20);
            g2.draw(new Line2D.Double(paperW/4, paperH, paperW/2+paperW/4, paperH));
            g2.draw(new Line2D.Double(paperW, paperH/4, paperW, paperH/2+paperH/4));
            g2.draw(new Line2D.Double(paperW, paperH/2, paperW+z, paperH/2-z));
            g2.draw(new Line2D.Double(paperW, paperH/2, paperW+z, paperH/2+z));
            g2.draw(new Line2D.Double(paperW/2, paperH, paperW/2-z, paperH+z));
            g2.draw(new Line2D.Double(paperW/2, paperH, paperW/2+z, paperH+z));
            g2.draw(new Line2D.Double(paperW+5, paperH, paperW*1.5, paperH));
            g2.draw(new Line2D.Double(paperW, paperH+5, paperW, paperH*1.5));
            g2.setColor(Color.black);
        }

        //●自由文字列の描画
        g2.setColor(foregroundColor);
        for(int i=0; i<stringArrD.length; i++)
        {
            //字形は各種（"sansserif" / "serif" / "monospaced" ....）
            Font fontT = new Font(stringArrS[i][0],Font.PLAIN,
            (int)(stringArrD[i][2]/3.0)); //標準表示倍率が３倍のため、サイズ調整
            if(fontBold==true)
            {
              fontT = new Font(stringArrS[i][0],Font.BOLD,
              (int)(stringArrD[i][2]/3.0)); //標準表示倍率が３倍のため、サイズ調整
            }
            g2.setFont(fontT);
            g2.drawString(
                stringArrS[i][1],(float)(stringArrD[i][0]+sX),
                (float)(stringArrD[i][1]+sY));
        }

        //●ピクセル数とミリ数とを、メニュー・バーに表示
        int pw = ((int)(paperW*affineScale*freeScale));
            //pw : pixelを切り出す範囲の巾
        int ph = ((int)(paperH*affineScale*freeScale));
            //ph : pixelを切り出す範囲の丈
        f.pixelLabel.setText("  ("+pw+"x"+ ph+"px)");
        f.mmLabel.setText("  ("+(int)(paperW*freeScale)+"x"+
            (int)(paperH*freeScale)+"mm)");
    } //draw()

    //■画像消去
    void eraseGraph()
    {
        f.canvas.g2.setPaint(new Color(126,126,126));
        f.canvas.g2.fill(new Rectangle2D.Double(
            0,0,f.canvas.getPreferredSize().width,
            f.canvas.getPreferredSize().height));
            f.canvas.g2.setPaint(new Color(255,5,255));
        f.canvas.g2.fillRect(0,0,3500,3500);
        f.canvas.g2.drawString("画像消去", (float)10, (float)10);
        f.message("画像消去");
    }

    //■エラーがあったと思われるメソッドの記述形式を取得して警告する
    void methodErrorMessage(String s)
    {
        for(int i= 0; i<manualArr.length; i++)
        {
            if(manualArr[i].trim().startsWith("★" + s))
                f.message("ヒント："+manualArr[i].trim());
        }
    }
}

◆PrintCSV1PrintEngine.java

//■■PrintCSV1PrintEngine.java

//■■インポート
import java.awt.*;
import java.awt.geom.*;
import java.awt.print.*;
import javax.print.attribute.*;
import javax.print.attribute.standard.*;

//■■印刷実行処理
class PrintCSV1PrintEngine
{
    //■メンバー変数
    PrintCSV1 f;
    double printerMarginX = 3.0; //Canon iP4500 専用の定数(mm単位)
    double printerMarginY = 3.0; //Canon iP4500 専用の定数
    // ただし、機種により、紙押さえのためのプリンター・マージンは異なる。

    //■コンストラク4
    PrintCSV1PrintEngine(PrintCSV1 frame)
    {
        f = frame;
    }

    //■印刷開始要求の受付け
    void start()
    {
        //●ディスプレイ表示倍率の保存
        double scale = f.canvas.affineScale;

        //●ディスプレイ表示用の描画ロジックを印刷描画に転用
        f.canvas.affineScale = 2.8457D; //換算倍率(1mm=2.8457point)
        //f.canvas.affineScale = 1*3*0.9483333D;  でもOK。（３は画面表示倍率）

        //●印刷制御オブジェクト
        PrinterJob pj = PrinterJob.getPrinterJob();

        //●用紙書式設定オブジェクト
        PageFormat fmt = new PageFormat();

        //●用紙情報格納オブジェクト
        Paper paper = new Paper();

        //●印刷制御オブジェクトに要件設定
        PrintOnePage printable = new PrintOnePage();

        if(f.canvas.rotateFlag == false)
        { //●回転なし
            paper.setImageableArea(0,0,
                f.canvas.paperW*2.8457D,f.canvas.paperH*2.8457D);
        }
        else
        { //●回転あり
            paper.setImageableArea(0,0,
                f.canvas.paperH*2.8457D,f.canvas.paperW*2.8457D);
        }
        fmt.setPaper(paper);

        //●余白設定
        PrintRequestAttributeSet reqAtt = new HashPrintRequestAttributeSet();
        float topMgn    = 0.0f;  //余白なし
        float bottomMgn = 0.0f;
        float leftMgn   = 0.0f;
        float rightMgn  = 0.0f;
        if(f.canvas.rotateFlag == false) //●回転なし
        {
            reqAtt.add(new MediaPrintableArea(
           leftMgn, topMgn,
           ((float)f.canvas.paperW - leftMgn - rightMgn),
           ((float)f.canvas.paperH - topMgn - bottomMgn), MediaPrintableArea.MM));
        }
        else //●回転あり
        {
            reqAtt.add(new MediaPrintableArea(
           leftMgn, topMgn,
           ((float)f.canvas.paperH - topMgn - bottomMgn),
           ((float)f.canvas.paperW - leftMgn - rightMgn), MediaPrintableArea.MM));
        }
        reqAtt.add(OrientationRequested.PORTRAIT);
        pj.setPrintable(new PrintOnePage());

        //●印刷開始
        try
        {  //Printable.print(...)にページ毎に描画要求
            if (pj.printDialog()) //印刷開始のダイアログ・ボックス
                pj.print(reqAtt); //印刷開始
        }
        catch(Exception e)
        {
            f.message("印刷実行エラー："+e);
        }

        //●倍率をディスプレイ表示用に戻す
        f.canvas.affineScale = scale;
    }

    //■残ページ判定
    boolean hasMorePages(int page)
    {
        if(f.canvas.pages < page)
            return false;
        else
            return true;
    }

    //■印刷イメージの描画（
    //PrinterJobの管理下で機能するPrintableの実装
    class PrintOnePage implements Printable
    {
        public int print(Graphics g, PageFormat fmt,
            int pageIndex) //指定ページ描画
        { //指定ページ描画
            if(hasMorePages(pageIndex))
            { //未処理ページがあれば
                Graphics2D g2 = (Graphics2D)g;
                g2.translate(fmt.getImageableX()-printerMarginX*2.8457D,
                    fmt.getImageableY()-printerMarginY*2.8457D);
                //描画は外部委託
                f.canvas.drawPage(g,true,pageIndex);
                //該当ページ印刷実行合図返却
                return Printable.PAGE_EXISTS;
            }
            else
            { //印刷完了による停止合図返却
                return Printable.NO_SUCH_PAGE;
            }
        }
    }
}

◆PrintCSV1FileIO.java

//■■PrintCSV1FileIO.java

//■■インポート
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.text.*;
import javax.swing.filechooser.FileFilter;
import java.io.*;
import java.util.*;
import java.util.regex.*;

//■■JMenuItemのイベントリスナー
class PrintCSV1FileIO
{
    //■メンバー変数
    PrintCSV1 f;

    //■コンストラクタ
    PrintCSV1FileIO(PrintCSV1 frame)
    {
        f = frame;
    }

    //■ファイルから読み込む（デフォルト文字コード）
    void load(JTextArea tArea, String dir, String ext)
    {
        JFileChooser fileChooser = new JFileChooser(dir);
        if(!ext.equals("*"))
        {
            SingleFileFilter filter = new SingleFileFilter(ext);
            fileChooser.setFileFilter(filter);
        }
        int ret = fileChooser.showOpenDialog(f.pane);
        File file = fileChooser.getSelectedFile();
        if(ret != JFileChooser.APPROVE_OPTION || file == null)
        {
            return;
        }
        try
        {

            FileReader reader = new FileReader(file);
            try
            {
                tArea.read(reader,new PlainDocument());
            }
            catch(IOException e)
            {
                f.message("リードエラー："+e);
            }
            reader.close();
        }
        catch(FileNotFoundException e)
        {
            f.message("オープンエラー："+e);
        }
        catch(IOException e)
        {
            f.message("クローズエラー："+e);
        }
    }

    //■ファイルに書き込む（デフォルト文字コード）
    void save(JTextArea tArea, String dir, String ext)
    {
        JFileChooser fileChooser = new JFileChooser(dir);
        if(!ext.equals("*"))
        {
            SingleFileFilter filter = new SingleFileFilter(ext);
            fileChooser.setFileFilter(filter);
        }
        int ret = fileChooser.showSaveDialog(f.pane);
        File file = fileChooser.getSelectedFile();
        if(ret != JFileChooser.APPROVE_OPTION || file == null)
        {
            return;
        }
        try
        {
            FileWriter writer = new FileWriter(file);
            try
            {
                String str = tArea.getText();
                if(str.equals(""))
                    f.message("定義またはデータなし");
                else
                    writer.write(str);
            }
            catch(IOException e)
            {
                f.message("ライトエラー："+e);
            }
            writer.close();
            f.message(file.getName()+" を書き込みました。");
        }
        catch(FileNotFoundException e)
        {
            f.message("オープンエラー："+e);
        }
        catch(IOException e)
        {
            f.message("クローズエラー："+e);
        }
    }

    //■ファイルから読み込む（UTF-16/UTF-8文字コード）
    void loadUTF(JTextArea tArea, String dir, String ext, String utf)
    {
        JFileChooser fileChooser = new JFileChooser(dir);
        if(!ext.equals("*"))
        {
            SingleFileFilter filter = new SingleFileFilter(ext);
            fileChooser.setFileFilter(filter);
        }
        int ret = fileChooser.showOpenDialog(f.pane);
        File file = fileChooser.getSelectedFile();
        if(ret != JFileChooser.APPROVE_OPTION || file == null)
        {
            return;
        }
        tArea.setText(TextIO.loadText(file, utf));
        if(file.getName().endsWith(".pcc"))
            f.controlFile.setText("  [" + file.getName() + "]");
        else
            f.dataFile.setText("  [" + file.getName() + "]");
    }

    //■ファイルから読み込む（UTF-16/UTF-8文字コード）
    void saveUTF(JTextArea tArea, String dir, String ext, String utf)
    {
        JFileChooser fileChooser = new JFileChooser(dir);
        if(!ext.equals("*"))
        {
            SingleFileFilter filter = new SingleFileFilter(ext);
            fileChooser.setFileFilter(filter);
        }
        int ret = fileChooser.showSaveDialog(f.pane);
        File file = fileChooser.getSelectedFile();
        if(ret != JFileChooser.APPROVE_OPTION || file == null)
        {
            return;
        }
        TextIO.saveText(file, tArea.getText(), utf);
    }
}

◆PrintCSV1Manual.txt

■■作表印刷ユーティリティ "PrintCSV1" のコマンドの使い方

■■データファイルの準備
最も単純なCSVをサポートし、 引用符を使う形式には対応していません。
CSVファイルの拡張子は、csvでなければなりません。
データ形式で、CSV 以外では、NUL（データなし） が使えます。

■■データ・コマンド一覧

■■CSVデータ例
1,りんご,L,50,10,500
2,りんご,M,40,10,400
3,みかん,L,20,10,200
4,みかん,M,15,10,150
5,みかん,S,10,10,100
6,なし  ,L,20,10,200
7,なし  ,M,15,10,150

■■書式定義（命令書）ファイルの作成
　CSV 形式のデータを罫線付きの一覧表に整形するため、整形の書式を指示す
る必要があります。下記の定義例に従って数値や文字列を定義します。定義内
容はファイルとして任意の名前で保存できます。但し拡張子は、cpp でなけれ
ばなりません。
１行に１メソッドとし、メソッドの末尾にセミコロンが必要です。
行頭が「//」であれば、その行をコメントと見なします。

■■書式定義（命令書）例
Paper(210,297);
DataType("CSV");
Margin(20,20);
Title("明細書");
Subtitle("くだもの");
Number("2000-A001");
PageNumber(true);
FontSize(12);
PageHeader(25);
TableHeader(10)
Row(7,31);
Column(10,"R","No.");
Column(32,"L","品名");
Column(32,"L","規格");
Column(32,"N0","単価");
Column(32,"N0","数量");
Column(32,"N0","金額");
Footnote("消費税は別途");

■■メソッド一覧
（凡例）
    int 　　　　：整数
    double　　　：実数
    String　　　：文字列
    boolean　　 ：ブール (true/false)
    座標位置数値：mm単位
    長さ数値　　：mm単位
    文字サイズ　：ポイント

■用紙
    ★Paper(double w, double h)
    w:        用紙巾
    h:        用紙丈
■余白
    ★Margin(double top, double left)
    top:      天余白
    left:     左余白
■標題
    ★Title(String title)
    title:    標題文字列
■副題
    ★Subtitle(String subtitle)
    subtitle: 副題文字列
■発行番号
    ★Number(String number)
    number:   発行番号文字列
■ページ番号
    ★PageNumber(boolean pn)
    pn:      ページ番号を表示するか否か
■文字サイズ
    ★FontSize(int point)
    point:    文字のポイント数
■太字指定
    ★FontBold(boolean b)
    b:        太字指定ならtrue
■文字列の自由描画
    ★WriteString(double x,double y,int size, String style, String s)
    x:        ｘ座標
    y:        ｙ座標
    size:     フォント・サイズ
    style:    字形（"sansserif" / "serif" / "monospaced" ）
    s:        表示する文字列
    ★WriteString(double x, double y, String s)
    x:        起点座標X
    y:        起点座標Y
    s:        任意の文字列
■直線の太さ指定
    ★LineWidth(double width)
    width:    線の太さ
■直線の自由描線
    ★DrawLine(double x1, double y1, double x2, double y2)
    x1:       起点座標X
    y1:       起点座標Y
    x2:       終点座標X
    y2:       終点座標Y
    ただし、Draw2 は、多角形の輪郭線描画専用とする。
■ページ見出し
    ★PageHeader(double h)
    h:        ページ見出しの高さ
■表見出し
    ★TableHeader(double h)
    h:        表見出しの高さ
■行
    ★Row(double h, double rows)
    h:        行の高さ
    rows:     １ページの行数
■列（欄）
    ★Column(double w, String constant, String header)
    w:        列の幅
    constant: 位置合せ定数 (L/R/Nx)
    header:   見出し文字列
    Columnのみはカラム数に合致した複数行定義しま
    す。L/R は左寄せ、右寄せの指定です。
    なお、右寄せの拡張機能として、数値表現(Nx)も指
    定可能です。この場合、三桁毎にコンマが付加され
    ます。数値"Nx"のxには0から9の数字を1文字書き、
    小数点未満の表示桁数を指定します。
■脚注
    ★Footnote(String s)
    s:        脚注文字列
■脚注2（脚注の二行目）
    ★Footnote2(String s)
    s:        脚注文字列
■シフト
    ★Shift(int mode, double x, double y)
    mode:     0がシフトなし
              1が無条件強制シフト
              2が偶数ページシフト
    x:        X軸のシフト量
    y:        Y軸のシフト量
■行ブロック（反復の行ブロック化）
    ★RowBlock(int b)
    b:        行のブロック化を指定
    例えば、3を指定すれば、点線の横罫線が２行のあと、
    実線の横罫線が１行の繰り返しとなる。
■列ブロック（自由な列ブロック化）
    ★ColumnSeparator(int ... separator)
    separator:縦線の点線か実線かの指定
    0が点線、1が実線で、この組合せで指定する。
    カラム間の境界線指定のため、個数はカラム数より１少ない。
■描線モード（罫線のみ対象）
    ★DrawMode(int mode)
    mode:     3なら、罫線の縦横共に描画
              2なら、罫線の横だけ描画
              1なら、罫線の縦だけ描画
              0なら、罫線を描画しない
■データ種別指定
    ★DataType(String s)
    s:       "CSV"なら、CSVデータ
             "NUL"なら、データなし
■データ・ファイル指定
    ★LoadData(String name)
    name:     データ・ファイルを指定して、データをセットする
    例えば、"./data/filename.csv"
■データを設定
    ★SetData(String s)
    s:        データ文字列を指定して、データをセットする
■回転指定
    ★Rotate(boolean b)
    b:        trueの時だけ90度回転
■自由倍率指定
    ★FreeScale(double scale)
    scale:    表示、画像保存、印刷に有効な拡大倍率