序文
このシリーズの最初の章で、特定のページサイズ、特定のページマージン(明示的または暗黙的に定義)を作成Document
し、Document
オブジェクトにParagraph
sやなどの基本的な描画ブロックを追加したことを覚えていますか。List
■、iTextは、コンテンツがページ内で適切に整理されることを保証します。同時に、Table
CSVファイルの内容を表示するオブジェクトも作成しましたが、結果はすでに非常によく表示されています。しかし、上記のすべてが効率的に実行されない場合はどうなりますか?Webページのコンテンツのレイアウトをより適切に制御したい場合はどうなりますか?Tableクラスによって描画された長方形の境界線に満足できない場合はどうなりますか?作成されたページ数に関係なく、各ページの特定の場所にコンテンツを追加するとどうなりますか?
第2章で述べた絶対位置にすべてを描く方法でこの問題を解決できますか?第2章のスターウォーズの冒頭のテキストを描く例を通して、これが非常に複雑なコードにつながる可能性があることに気づきました(コードを維持するのは難しいです)。もちろん、基本コンポーネントの高レベルAPIと低レベルAPIを組み合わせて、レイアウトをさらに制御できるようにする方法が必要です。これは、この章の第3章で説明する内容です。
ドキュメントレンダラーの紹介
Document
テキストと画像 を1つに追加したいが、テキストがドキュメントの幅全体を占めることは望ましくないとします。代わりに、図1に示すように、コンテンツを3つの列に編成します。
この例では、次のコードを使用できます(次のコードはすべてNewYorkTimes
クラスの一部です)。
PdfDocument pdf = new PdfDocument(new PdfWriter(dest));
PageSize ps = PageSize.A5;
Document document = new Document(pdf, ps);
//Set column parameters
float offSet = 36;
float columnWidth = (ps.getWidth() - offSet * 2 + 10) / 3;
float columnHeight = ps.getHeight() - offSet * 2;
//Define column areas
Rectangle[] columns = {
new Rectangle(offSet - 5, offSet, columnWidth, columnHeight),
new Rectangle(offSet + columnWidth, offSet, columnWidth, columnHeight),
new Rectangle(
offSet + columnWidth * 2 + 5, offSet, columnWidth, columnHeight)};
document.setRenderer(new ColumnDocumentRenderer(document, columns));
// adding content
Image inst = new Image(ImageDataFactory.getImage(INST_IMG)).setWidth(columnWidth);
String articleInstagram = new String(
Files.readAllBytes(Paths.get(INST_TXT)), StandardCharsets.UTF_8);
NewYorkTimes.addArticle(document,
"Instagram May Change Your Feed, Personalizing It With an Algorithm",
"By MIKE ISAAC MARCH 15, 2016", inst, articleInstagram);
doc.close();
誰もが最初の3行に精通している必要があります。これらは第1章で紹介されました。5、6、および7行目は、一連のパラメーターを定義しています。
offset
変数。この変数を使用して、上下左右の境界線の幅を定義します。- 各列の幅
columnWidth
。これは、計算可能なページを3つの部分に分割して計算されます(目標は3列です)。計算可能なページのサイズは、ページ全体の幅-2 *offset
+10であり、+ 10の関数は次のことを保証します。各列の間にギャップがあります columnHeight
サイズは単にページ全体の高さ-2 *offset
columns
この配列変数 を使用して、3つのRectangle
オブジェクトを格納します(確認する前に、デフォルトの座標系が左下隅にあり、x軸が右、y軸が右にあることに注意してください)。
- 最初のもの
Rectangle
:左下隅の座標は(offset-5
、offset
)、幅はcolumnWidth
、、高さはcolumnHeight
- 2つ目
Rectangle
:左下隅の座標は(offset+columnWidth
、offset
)、幅はcolumnWidth
、、高さはcolumnHeight
- 3番目
Rectangle
:左下隅の座標は(offset+2*columnWidth+5
、offset
)、幅はcolumnWidth
、、高さはcolumnHeight
私たちは、その後、使用columns
作成するには、このオブジェクトをColumnDocumentRenderer
、一度この宣言ColumnDocumentRenderer
のように私たちがして、我々はすべてのコンテンツが追加されます定義する3つに従うレイアウト表示。Document
DocumentRenderer
Document
Rectangle
16行目でImage
オブジェクトを作成し、長方形の幅に合わせて画像を比例的に拡大縮小しました。17行目と18行目では、テキストファイルを読み取って保存String
します。次にaddArticle()
示すように、これらの変数をパラメーターとして使用します。
public static void addArticle(
Document doc, String title, String author, Image img, String text)
throws IOException {
Paragraph p1 = new Paragraph(title)
.setFont(timesNewRomanBold)
.setFontSize(14);
doc.add(p1);
doc.add(img);
Paragraph p2 = new Paragraph()
.setFont(timesNewRoman)
.setFontSize(7)
.setFontColor(Color.GRAY)
.add(author);
doc.add(p2);
Paragraph p3 = new Paragraph()
.setFont(timesNewRoman)
.setFontSize(10)
.add(text);
doc.add(p3);
}
timesNewRoman
また、timesNewRomanBold
オブジェクトはNewYorkTimes
クラスの静的メンバー変数であり、タイプはPdfFont
です。一般に、この例は前の章の例よりも単純です。次に、もう少し複雑な例を見てみましょう。
ブロックレンダラーを使用する
最初の章では、米国の各大陸の情報のcsvファイルの内容をPDFに表示するときに、一連のCell
オブジェクトを作成してからオブジェクトに追加しましたTable
。オブジェクトCell
の背景色と境界線のサイズは定義しませんでした。デフォルト値を使用しています。
デフォルト設定:Cellオブジェクトには背景色がなく、境界線のサイズは0.5ユーザー単位です。
次にTable
、次の図2に示すように、別のデータソースを使用してそのデータソースに配置します。
ここで、コードの記述方法を説明します。最初のコードは前のコードと似ていますが、注目に値するのは次のコードだけです(クラス全体がクラスですPremierLeague
)。
PageSize ps = new PageSize(842, 680);
以前は、PageSize.A4
サイズなどの標準的な紙を使用していました。この例では、自分で定義した用紙サイズを使用します:842x680ユーザーユニット(1インチは72ユーザーユニット、つまり11.7x9.4インチに相当します)。PremierLeague
クラスのメインコードは次のとおりです。
PdfFont font = PdfFontFactory.createFont(FontConstants.HELVETICA);
PdfFont bold = PdfFontFactory.createFont(FontConstants.HELVETICA_BOLD);
Table table = new Table(new float[]{1.5f, 7, 2, 2, 2, 2, 3, 4, 4, 2});
table.setWidthPercent(100)
.setTextAlignment(Property.TextAlignment.CENTER)
.setHorizontalAlignment(Property.HorizontalAlignment.CENTER);
BufferedReader br = new BufferedReader(new FileReader(DATA));
String line = br.readLine();
process(table, line, bold, true);
while ((line = br.readLine()) != null) {
process(table, line, font, false);
}
br.close();
document.add(table);
米国の状態を示す第1章の前の例との違いはわずかです。この例では、使用方法setTextAlignment
とsetHorizontalAlignment
方法を設定して、コンテンツをテーブルの中央に配置し、テーブル自体を中央に配置します(これは、テーブルが使用可能な幅の100%を占めることとは関係ありません)。 。次に、process()
このより興味深い関数を見てみましょう。
public void process(Table table, String line, PdfFont font, boolean isHeader) {
StringTokenizer tokenizer = new StringTokenizer(line, ";");
int columnNumber = 0;
while (tokenizer.hasMoreTokens()) {
if (isHeader) {
Cell cell = new Cell().add(new Paragraph(tokenizer.nextToken()));
cell.setNextRenderer(new RoundedCornersCellRenderer(cell));
cell.setPadding(5).setBorder(null);
table.addHeaderCell(cell);
} else {
columnNumber++;
Cell cell = new Cell().add(new Paragraph(tokenizer.nextToken()));
cell.setFont(font).setBorder(new SolidBorder(Color.BLACK, 0.5f));
switch (columnNumber) {
case 4:
cell.setBackgroundColor(greenColor);
break;
case 5:
cell.setBackgroundColor(yellowColor);
break;
case 6:
cell.setBackgroundColor(redColor);
break;
default:
cell.setBackgroundColor(blueColor);
break;
}
table.addCell(cell);
}
}
}
まず普通の文章を見てみましょう。行16、19、22、および25では、列番号に応じて背景色を変更します。13行目ではCell
、フォントを設定し、setBorder()
関数を使用してデフォルトの境界線をオーバーライドします。境界線を、幅0.5ユーザー単位の黒い実線の境界線として定義します。
SolidBorder
継承されBorder
たクラス、それは、次のような多くの同様の兄弟、持っているDashedBorder
、DottedBorder
とDoubleBorder
など iTextで選択した境界線が提供されない場合は、Border
クラスを拡張したり、既存の実装を使用してインスピレーションを得たり、独自の実装を作成したりできますCellRenderer
。
7行目と8行目でカスタムを使用しRoundedCornersCellRenderer()
、パディングサイズを指定して、境界線をに設定しましたnul
。そうでsetBorder(null)
ない場合は、2つの境界線が描画されます。1つはiText自体によって描画され、もう1つは作成するコンテンツレンダラーによって描画される境界線です。定義したコンテンツレンダラーを見てみましょう。
private class RoundedCornersCellRenderer extends CellRenderer {
public RoundedCornersCellRenderer(Cell modelElement) {
super(modelElement);
}
@Override
public void drawBorder(DrawContext drawContext) {
Rectangle rectangle = getOccupiedAreaBBox();
float llx = rectangle.getX() + 1;
float lly = rectangle.getY() + 1;
float urx = rectangle.getX() + getOccupiedAreaBBox().getWidth() - 1;
float ury = rectangle.getY() + getOccupiedAreaBBox().getHeight() - 1;
PdfCanvas canvas = drawContext.getCanvas();
float r = 4;
float b = 0.4477f;
canvas.moveTo(llx, lly).lineTo(urx, lly).lineTo(urx, ury - r)
.curveTo(urx, ury - r * b, urx - r * b, ury, urx - r, ury)
.lineTo(llx + r, ury)
.curveTo(llx + r * b, ury, llx, ury - r * b, llx, ury - r)
.lineTo(llx, lly).stroke();
super.drawBorder(drawContext);
}
}
CellRenderer
クラスはBlockRenderer
、クラスの特別な実装です。
BlockRenderer
やリストBlockElements
などのクラスを使用できますParagraph
。これらのレンダラークラスを使用するとdraw()
、メソッドをオーバーライドしてカスタム関数を作成できます。例:Paragraph
カスタム背景を作成できます。方法CellRenderer
もありdrawBorder()
ます。
drawBorder()
メソッド をオーバーライドして、上部に角が丸い長方形を描画します(6〜21行目)。getOccupiedAreaBBox()
このメソッドはRectangle
オブジェクトを返します。これを使用しBlockElement
て境界ボックスを見つけることができます(8行目)。我々が使用しgetX()
、getY()
、getWidth()
およびgetHeight()
方法は、細胞の左下隅と右上隅の座標(ライン9~12)を定義します。
drawContext
このパラメーターを使用すると、PdfCanvas
インスタンスにアクセスできます(13行目)。一連の線と曲線(線14〜20)の形で境界線を描画します。この例は、高レベルのapi(でCell
構成されるTable
)と低レベルのapi(ニーズを満たす境界線を描画するためにほぼ手動でPDF構文を作成する)を緊密に統合して使用する方法を示しています。
曲線を描くためのコードには数学の知識が必要ですが、ロケット科学ほど難しくはありません。最も一般的なタイプの境界はiTextにあるため、iTextエンジンで数式がどのように計算されるかを心配する必要はありません。
BlockRenderer
その実装クラス についてはまだ多くの知識があります。別のチュートリアルで詳しく説明します。最後に、この章の最後に例を示し、作成された各ページに背景、ヘッダー(またはフッター)、透かし、ページ番号を自動的に追加する方法を示します。
イベントの処理(イベントの処理、背景、ページフッター、ウォーターマークの追加)
多くの行があるドキュメントに追加するとTable
、このテーブルはさまざまなページに分散される可能性があります。以下の図3に、に格納されてufo.csv
いるUFOディレクトリのリストを示します。奇数ページの背景は緑と黄色、偶数ページの背景は青です。各ページにはヘッダー“THE TRUTH IS OUT THERE”
があり、ページの実際のコンテンツに透かしがあり、“CONFIDENTIAL”
各ページの下部が中央にあり、ページ番号があります。
以下は、UFOディレクトリテーブルを生成するためのコードです。これは、第1章のテーブル表示コードと非常によく似ています。
PdfDocument pdf = new PdfDocument(new PdfWriter(dest));
pdf.addEventHandler(PdfDocumentEvent.END_PAGE, new MyEventHandler());
Document document = new Document(pdf);
Paragraph p = new Paragraph("List of reported UFO sightings in 20th century")
.setTextAlignment(Property.TextAlignment.CENTER)
.setFont(helveticaBold).setFontSize(14);
document.add(p);
Table table = new Table(new float[]{3, 5, 7, 4});
table.setWidthPercent(100);
BufferedReader br = new BufferedReader(new FileReader(DATA));
String line = br.readLine();
process(table, line, helveticaBold, true);
while ((line = br.readLine()) != null) {
process(table, line, helvetica, false);
}
br.close();
document.add(table);
document.close();
コードでは、次のProperty.TextAlignment.CENTER
ように、テキスト配置プロパティをに設定してcenteredを追加し、Paragraph
ufo.csvをループしてコンテンツを表示します。
public void process(Table table, String line, PdfFont font, boolean isHeader) {
StringTokenizer tokenizer = new StringTokenizer(line, ";");
while (tokenizer.hasMoreTokens()) {
if (isHeader) {
table.addHeaderCell(new Cell().add(new Paragraph(tokenizer.nextToken()).setFont(font)).setFontSize(9).setBorder(new SolidBorder(Color.BLACK, 0.5f)));
} else {
table.addCell(new Cell().add(new Paragraph(tokenizer.nextToken()).setFont(font)).setFontSize(9).setBorder(new SolidBorder(Color.BLACK, 0.5f)));
}
}
}
pdf.addEventHandler(PdfDocumentEvent.END_PAGE, new MyEventHandler());
、見たことがありませんか?ここでPdfDocument
は、イベントハンドラーを追加しますMyEventHandler
。このMyEventHandler
実現(実装)は、IEventHandler
一方向のみのインターフェイスですhandleEvent()
。このPdfDocumentEvent.END_PAGE
タイプのイベントが発生するたびに、このタイプのイベントがトリガーされます。このタイプのイベントとは、iTextがページへのコンテンツの追加を終了したとき、新しいページが作成されたためか、最後のページが到着して完了したためかを示します。
次にIEventHandler
、実装を見てみましょう。
protected class MyEventHandler implements IEventHandler {
public void handleEvent(Event event) {
PdfDocumentEvent docEvent = (PdfDocumentEvent) event;
PdfDocument pdfDoc = docEvent.getDocument();
PdfPage page = docEvent.getPage();
int pageNumber = pdfDoc.getPageNumber(page);
Rectangle pageSize = page.getPageSize();
PdfCanvas pdfCanvas = new PdfCanvas(
page.newContentStreamBefore(), page.getResources(), pdfDoc);
//Set background
Color limeColor = new DeviceCmyk(0.208f, 0, 0.584f, 0);
Color blueColor = new DeviceCmyk(0.445f, 0.0546f, 0, 0.0667f);
pdfCanvas.saveState()
.setFillColor(pageNumber % 2 == 1 ? limeColor : blueColor)
.rectangle(pageSize.getLeft(), pageSize.getBottom(),
pageSize.getWidth(), pageSize.getHeight())
.fill().restoreState();
//Add header and footer
pdfCanvas.beginText()
.setFontAndSize(helvetica, 9)
.moveText(pageSize.getWidth() / 2 - 60, pageSize.getTop() - 20)
.showText("THE TRUTH IS OUT THERE")
.moveText(60, -pageSize.getTop() + 30)
.showText(String.valueOf(pageNumber))
.endText();
//Add watermark
Canvas canvas = new Canvas(pdfCanvas, pdfDoc, page.getPageSize());
canvas.setProperty(Property.FONT_COLOR, Color.WHITE);
canvas.setProperty(Property.FONT_SIZE, 60);
canvas.setProperty(Property.FONT, helveticaBold);
canvas.showTextAligned(new Paragraph("CONFIDENTIAL"),
298, 421, pdfDoc.getPageNumber(page),
TextAlignment.CENTER, VerticalAlignment.MIDDLE, 45);
pdfCanvas.release();
}
}
3行目では、最初event
にこの関数パラメーターをに変換しPdfDocumentEvent
、次にそれgetDocument()
を取得するために呼び出しPdfDocument
ます。これらの変数を介して現在のページのページ番号を取得しPdfCanvas
、ページサイズのインスタンスがあります。
異なるパスまたはシェイプがオーバーラップする可能性があります。最初に描画されたパスまたはシェイプ(コンテンツストリームに保存されます)が最初にキャンバスに描画され、後で描画されるグラフィックが前のコンテンツをカバーします(オーバーラップする部分がある場合)。ページのコンテンツが完全にレンダリングされるたびに、背景、つまり各
PdfPage
トラッキングコンテンツフローの配列を追加する必要があります。インデックスをパラメータとして使用して、getContentStream()
個々のコンテンツストリームを取得できます。最初と最後のコンテンツストリームを使用getFirstContentStream()
してgetLastContentStream()
取得できます。newContentStreamBefore()
andnewContentStreamAfter()
メソッドを使用して、新しいコンテンツストリームを作成することもできます。
8行目では、次の3つのパラメーターを使用して1つを作成しますPdfCanvas
。
page.newContentStreamBefore()
:ページのレンダリング後に不透明な長方形を描画すると、その長方形は既存のすべてのコンテンツをカバーします。背景と透かしがテーブルのコンテンツを覆わないように、ページコンテンツの前に追加されるコンテンツストリームにアクセスする必要があります。page.getResources()
:すべてのコンテンツストリームには、フォントや画像などの外部リソースが必要です。ページに新しいコンテンツを追加する場合、iTextがページのリソースディレクトリにアクセスできることが重要です。pdfDoc
:PdfDocument
新しく追加したコンテンツストリームをオブジェクトに追加できるように、オブジェクトを取得できる必要がありますPdfDocument
。
次に、canvas
オブジェクトに追加したもの:
- 11〜18行目:定義
limeColor
とblueColor
2色。最初に現在の画像の状態を保存し(詳細は第2章の詳細な説明を参照)、次に奇数ページと偶数ページの数に応じて塗りつぶしペンの色を設定し、奇数ページを緑と黄色、偶数ページを青で、ページ全体のサイズの長方形を作成します。最後に、後でコンテンツの色に影響を与えることなく、前の画像の状態を復元します。 - 20〜26行目:テキストの書き込みを開始し、フォントスタイルとフォントサイズを設定してから、ページの上部中央に移動し、書き込みを開始して
"THE TRUTH IS OUT THERE"
から、カーソルを下部に移動し、ページ番号を入力します。ヘッダーとフッターはOKです。 - 28〜31行目:ここで
Canvas
は、タイプのインスタンスを作成しますcanvas
。高レベルが同じことを示すのと同じように、Canvas
これはPdfCanvas
高レベルの表現です。ここでは、pdf構文(第2章の構文)を使用して、フォント、フォントサイズ、フォントの色、およびその他の属性を変更しません。メソッドを使用します。同様に、デフォルトのフォントを変更するなど、ここでメソッドを使用できます。これは、オブジェクトのような、同じ目的のために使用することができるです、、。Document
PdfDocument
setProperty()
Document
setProperty()
Paragraph
List
Table
- 32〜34行目:
showTextAligned()
メソッドを使用してParagraph
、中央に表示されたディスプレイを1つ追加します。座標は(298,421)で、45度傾斜しています。
背景、ヘッダー、フッター、ウォーターマークを追加したら、PdfCanvas
オブジェクトを解放します。
この例では、2つの異なる方法を使用して、絶対位置にテキストを追加します。ヘッダーとフッターの描画に関しては、前の章で出会った低レベルのAPI(テキストの状態を含む)を使用しました。同様の方法を使用して、透かしを追加できます。ただし、テキストを回転してページの中央に配置する必要があるため、多くの数学的な計算が必要になります。テキストを必要な座標に配置する変換行列の計算を回避するために、便利な方法を使用しますshowTextAligned()
。iTextを使用すると、ここで多くの操作を節約できます。
総括する
この章の例を組み合わせると、前の章で説明した低レベルAPIの重要性を理解できます。この機能を基本的な構成要素と組み合わせて、カスタム機能を作成できます。これにより、セルオブジェクトのカスタム境界線が作成され、ページに背景色、ヘッダー、フッターが追加されます。最後に、透かしを追加すると、ここで使用できるPDF構文のすべての入力と出力を知る必要がないことがわかります。テキストを回転および中央揃えするための変換マトリックスの定義を処理する便利な方法。
次の章では、コンテンツのさまざまな形式である注釈について学習します。インタラクティブな形式を作成できる特定の種類の注釈に焦点を当てます。今後ともよろしくお願いいたします。
PS:iText7シリーズには全部で7つの章があります。最初の数章の内容は比較的基本的で退屈です。次の数章でより実用的になります!これらの7つの章を書いた後、私たちは公式ウェブサイトでサンプルを更新し続けます!
PS2:これらの記事を翻訳するのは難しいので、特に学生のパーティーのために、それらが正しいかどうかを確認するために練習する必要があります。それが良いと思うなら、賞賛してください!私へのあなたのサポートは、更新を続ける私の動機です!他のタイプミスを理解していただければ幸いです。