1 package org.simantics.document.linking.report.pdf;
\r
3 import java.awt.Color;
\r
4 import java.awt.Font;
\r
5 import java.awt.Graphics2D;
\r
6 import java.awt.Shape;
\r
7 import java.awt.font.LineBreakMeasurer;
\r
8 import java.awt.font.TextAttribute;
\r
9 import java.awt.font.TextLayout;
\r
10 import java.net.URL;
\r
11 import java.text.AttributedCharacterIterator;
\r
12 import java.text.AttributedString;
\r
13 import java.util.ArrayList;
\r
14 import java.util.Collections;
\r
15 import java.util.Hashtable;
\r
16 import java.util.List;
\r
18 import org.simantics.document.linking.report.Document.TextSize;
\r
19 import org.simantics.document.linking.report.Table;
\r
20 import org.simantics.document.linking.report.TableColumn;
\r
21 import org.simantics.document.linking.report.TableColumn.Alignment;
\r
22 import org.simantics.document.linking.report.TableRow;
\r
23 import org.simantics.document.linking.report.TextItem;
\r
24 import org.simantics.document.linking.report.URLItem;
\r
26 import com.lowagie.text.pdf.PdfAction;
\r
29 public class PDFTable implements Table, PDFElement {
\r
31 PDFPageStream stream;
\r
34 int currentLine = 0;
\r
36 List<TableColumn> columns = new ArrayList<TableColumn>();
\r
37 List<String> columnNames = new ArrayList<String>();
\r
38 List<Integer> columnSizes = new ArrayList<Integer>();
\r
39 List<Integer> columnPositions = new ArrayList<Integer>();
\r
41 TextItem title = null;
\r
43 boolean headerVisible = true;
\r
44 private boolean linesVisible = true;
\r
45 private boolean linesPrevVisible = true;
\r
46 boolean clipText = false;
\r
48 int textOffsetX = 2;
\r
49 int textOffsetY = 2;
\r
51 public PDFTable(PDFDocument writer, PDFPageStream stream) {
\r
52 this.writer = writer;
\r
53 this.stream = stream;
\r
54 this.startPage = stream.getCurrentPage();
\r
58 public PDFTable(PDFTable table) {
\r
59 this.writer = table.writer;
\r
60 this.stream = table.stream;
\r
61 this.startPage = stream.getCurrentPage();
\r
62 this.columns.addAll(table.columns);
\r
63 this.columnNames.addAll(table.columnNames);
\r
65 updateColumnPositions();
\r
70 public PDFPage getPage() {
\r
75 * @see org.simantics.document.linking.report.Table#addColumn(java.lang.String, double)
\r
78 public TableColumn addColumn(String name, double width) {
\r
79 TableColumn tc = new TableColumn(name, width);
\r
81 columnNames.add(name);
\r
83 updateColumnPositions();
\r
87 private void updateColumnPositions() {
\r
89 columnSizes.clear();
\r
90 columnPositions.clear();
\r
91 for (TableColumn c : columns) {
\r
92 int size = (int)(c.getWidth()*stream.getContentWidth());
\r
93 columnSizes.add(size);
\r
94 columnPositions.add(pos);
\r
100 public List<TableColumn> getColumns() {
\r
105 public boolean isLinesVisible() {
\r
106 return linesVisible;
\r
110 public void setLinesVisible(boolean b) {
\r
111 if (this.linesVisible == b)
\r
113 this.linesPrevVisible = linesVisible;
\r
114 this.linesVisible = b;
\r
118 public boolean isHeaderVisible() {
\r
119 return headerVisible;
\r
123 public void setHeaderVisible(boolean b) {
\r
124 this.headerVisible = b;
\r
127 private boolean isFirstLine() {
\r
128 return currentLine == 0 || stream.getCurrentPage().currentLine == 1;
\r
132 public void setTitle(String title) {
\r
134 this.title = writer.newItem(TextItem.class);
\r
135 this.title.setText(title);
\r
136 } catch (Exception e) {
\r
142 public void setTitle(TextItem title){
\r
143 this.title = title;
\r
147 * @see org.simantics.document.linking.report.Table#writeRow(java.lang.String[])
\r
150 public TableRow writeRow(String... line) throws Exception{
\r
151 List<String> list = new ArrayList<String>(line.length);
\r
152 for (String s : line)
\r
154 return writeRow(list);
\r
158 * @see org.simantics.document.linking.report.Table#writeRow(java.util.List)
\r
161 public TableRow writeRow(List<String> line) throws Exception{
\r
164 return _writeRow(line);
\r
168 public TableRow writeRowItem(TextItem... line) throws Exception {
\r
169 List<TextItem> list = new ArrayList<TextItem>(line.length);
\r
170 for (TextItem s : line)
\r
172 return writeRowItem(list);
\r
176 public TableRow writeRowItem(List<TextItem> line) throws Exception {
\r
179 return _writeRow2(line);
\r
182 private TableRow _writeRow(List<String> line) throws Exception {
\r
183 int h = getTextHeight();
\r
184 int ht = getTopHeight();
\r
185 int hb = getBottomHeight();
\r
186 PDFPage page = getCurrentPage();
\r
187 Graphics2D g2d = page.g2d;
\r
188 Shape clip = g2d.getClip();
\r
191 for (int i = 0; i < line.size(); i++) {
\r
192 if (line.get(i) == null)
\r
194 g2d.setClip(columnPositions.get(i),ht-1,columnSizes.get(i),hb-ht+2);
\r
195 g2d.drawString(line.get(i), columnPositions.get(i)+textOffsetX, h);
\r
198 if (linesVisible) {
\r
199 for (int i = 0; i < line.size(); i++) {
\r
200 g2d.drawLine(columnPositions.get(i), ht, columnPositions.get(i), hb);
\r
202 if (isFirstLine() || !linesPrevVisible) {
\r
203 g2d.drawLine(0, ht, stream.contentWidth, ht);
\r
204 linesPrevVisible = true;
\r
206 g2d.drawLine(stream.contentWidth, ht, stream.contentWidth, hb);
\r
207 g2d.drawLine(0, hb, stream.contentWidth, hb);
\r
210 page.currentLine++;
\r
211 page.availableLines--;
\r
212 page.currentPixel += getLineHeight();
\r
214 PositionedRow row = _getRow(line);
\r
215 if (stream.contentHeight-page.currentPixel < row.reservedSpace) {
\r
217 page = getCurrentPage();
\r
220 row = _getRow(line);
\r
223 currentLine+= row.realLines;
\r
224 page.currentLine+= row.realLines;
\r
225 page.currentPixel += row.reservedSpace;
\r
226 page.estimateAvailableLines();
\r
229 stream.checkNextPage();
\r
230 return new PDFTableRow();
\r
233 private TableRow _writeRow2(List<TextItem> line) throws Exception {
\r
234 int h = getTextHeight();
\r
235 int ht = getTopHeight();
\r
236 int hb = getBottomHeight();
\r
237 PDFPage page = getCurrentPage();
\r
238 Graphics2D g2d = page.g2d;
\r
239 Shape clip = g2d.getClip();
\r
242 for (int i = 0; i < line.size(); i++) {
\r
243 TextItem text = line.get(i);
\r
246 g2d.setClip(columnPositions.get(i),ht,columnSizes.get(i),hb-ht);
\r
247 g2d.drawString(text.getText(), columnPositions.get(i)+textOffsetX, h);
\r
248 if (text instanceof URLItem) {
\r
249 URL url = ((URLItem)text).getURL();
\r
251 addLink(url, columnPositions.get(i),ht,columnSizes.get(i),hb-ht);
\r
256 if (linesVisible) {
\r
257 for (int i = 0; i < line.size(); i++) {
\r
258 g2d.drawLine(columnPositions.get(i), ht, columnPositions.get(i), hb);
\r
260 if (isFirstLine() || !linesPrevVisible) {
\r
261 g2d.drawLine(0, ht, stream.contentWidth, ht);
\r
262 linesPrevVisible = true;
\r
264 g2d.drawLine(stream.contentWidth, ht, stream.contentWidth, hb);
\r
265 g2d.drawLine(0, hb, stream.contentWidth, hb);
\r
268 page.currentLine++;
\r
269 page.availableLines--;
\r
270 page.currentPixel += getLineHeight();
\r
272 PositionedRow row = _getRow2(line);
\r
273 if (stream.contentHeight-page.currentPixel < row.reservedSpace) {
\r
275 page = getCurrentPage();
\r
278 row = _getRow2(line);
\r
281 currentLine+= row.realLines;
\r
282 page.currentLine+= row.realLines;
\r
283 page.currentPixel += row.reservedSpace;
\r
284 page.estimateAvailableLines();
\r
287 stream.checkNextPage();
\r
288 return new PDFTableRow();
\r
291 void writeLine(String line) throws Exception{
\r
292 writeLine(line, 0);
\r
295 void writeLine(TextItem line) throws Exception{
\r
296 writeLine(line, 0);
\r
299 private void writeHeader() throws Exception{
\r
300 if (headerVisible) {
\r
301 TextSize s = currentTextSize;
\r
302 setTextSize(TextSize.MEDIUM);
\r
303 if (title != null) {
\r
304 boolean b = linesVisible;
\r
305 setLinesVisible(false);
\r
307 setLinesVisible(b);
\r
309 _writeRow(columnNames);
\r
314 void writeLine(String line, int x) throws Exception{
\r
315 int h = getTextHeight();
\r
316 int ht = getTopHeight();
\r
317 int hb = getBottomHeight();
\r
318 PDFPage page = getCurrentPage();
\r
319 Graphics2D g2d = page.g2d;
\r
320 g2d.drawString(line, x+textOffsetX, h);
\r
321 if (linesVisible) {
\r
322 if (isFirstLine() || !linesPrevVisible) {
\r
323 g2d.drawLine(0, ht, stream.contentWidth, ht);
\r
324 linesPrevVisible = true;
\r
326 g2d.drawLine(0, ht, 0, hb);
\r
327 g2d.drawLine(stream.contentWidth, ht, stream.contentWidth, hb);
\r
328 g2d.drawLine(0, hb, stream.contentWidth, hb);
\r
331 page.currentLine++;
\r
332 page.availableLines--;
\r
333 page.currentPixel += getLineHeight();
\r
334 stream.checkNextPage();
\r
337 void writeLine(TextItem line, int x) throws Exception{
\r
338 int h = getTextHeight();
\r
339 int ht = getTopHeight();
\r
340 int hb = getBottomHeight();
\r
341 PDFPage page = getCurrentPage();
\r
342 Graphics2D g2d = page.g2d;
\r
343 g2d.drawString(line.getText(), x+textOffsetX, h);
\r
344 if (linesVisible) {
\r
345 if (isFirstLine() || !linesPrevVisible) {
\r
346 g2d.drawLine(0, ht, stream.contentWidth, ht);
\r
347 linesPrevVisible = true;
\r
349 g2d.drawLine(0, ht, 0, hb);
\r
350 g2d.drawLine(stream.contentWidth, ht, stream.contentWidth, hb);
\r
351 g2d.drawLine(0, hb, stream.contentWidth, hb);
\r
353 if (line instanceof URLItem) {
\r
354 URL url = ((URLItem)line).getURL();
\r
356 addLink(url, 0,ht,stream.contentWidth,hb-ht);
\r
360 page.currentLine++;
\r
361 page.availableLines--;
\r
362 page.currentPixel += getLineHeight();
\r
363 stream.checkNextPage();
\r
366 int getTopHeight() {
\r
367 return getTopHeight(currentLine);
\r
370 int getTopHeight(int line) {
\r
371 return (line-currentLine)*getLineHeight()+getCurrentPage().currentPixel;
\r
374 int getTextHeight() {
\r
375 PDFPage page = getCurrentPage();
\r
376 return page.currentPixel+getLineHeight()-page.fm.getDescent()-textOffsetY;
\r
379 PDFPage getCurrentPage() {
\r
380 return stream.getCurrentPage();
\r
383 int getBottomHeight() {
\r
384 return getBottomHeight(currentLine);
\r
387 protected int getLineHeight() {
\r
388 return getCurrentPage().fm.getHeight()+textOffsetY;
\r
391 private int getBottomHeight(int line) {
\r
392 return (line-currentLine+1)*getLineHeight()+getCurrentPage().currentPixel;
\r
395 public int getAvailableLines() {
\r
396 PDFPage page = getCurrentPage();
\r
397 int contentHeight = stream.contentHeight;
\r
398 int pixelY = page.currentPixel;
\r
399 return (int)Math.floor((contentHeight-pixelY)/getLineHeight());
\r
402 private TextSize currentTextSize = TextSize.SMALL;
\r
405 public void setTextSize(TextSize size) {
\r
406 stream.getCurrentPage().setFont(writer.fonts.get(size));
\r
407 currentTextSize = size;
\r
411 public TextSize getTextSize() {
\r
412 return currentTextSize;
\r
415 private PositionedRow _getRow(List<String> line) {
\r
416 PositionedRow row = new PositionedRow();
\r
417 int h = getTextHeight();
\r
419 int reservedSpace = 0;
\r
420 List<List<PositionedText>> cells = new ArrayList<List<PositionedText>>(line.size());
\r
421 if (line.size() > 1) {
\r
422 for (int i = 0; i < line.size(); i++) {
\r
423 String text = line.get(i);
\r
424 int availableSize = columnSizes.get(i)-textOffsetX;
\r
425 if (text != null && text.length() > 0) {
\r
426 List<PositionedText> pt = getText(text, columnPositions.get(i)+textOffsetX, h,availableSize, columns.get(i).getAlignment());
\r
428 reservedSpace = Math.max(reservedSpace, getResevedSpace(pt)+getLineHeight());
\r
429 realLines = Math.max(realLines, getLineSpace(pt));
\r
431 cells.add(Collections.<PositionedText> emptyList());
\r
432 reservedSpace = Math.max(reservedSpace, getLineHeight());
\r
436 String text = line.get(0);
\r
437 int availableSize = stream.contentWidth;
\r
438 if (text != null && text.length() > 0) {
\r
439 List<PositionedText> pt = getText(text, textOffsetX, h,availableSize, columns.get(0).getAlignment());
\r
441 reservedSpace = Math.max(reservedSpace, getResevedSpace(pt)+getLineHeight());
\r
442 realLines = Math.max(realLines, getLineSpace(pt));
\r
444 cells.add(Collections.<PositionedText> emptyList());
\r
445 reservedSpace = Math.max(reservedSpace, getLineHeight());
\r
448 row.reservedSpace = reservedSpace;
\r
449 row.startLine = currentLine;
\r
450 row.realLines = realLines;
\r
455 private PositionedRow _getRow2(List<TextItem> line) {
\r
456 PositionedRow row = new PositionedRow();
\r
457 int h = getTextHeight();
\r
459 int reservedSpace = 0;
\r
460 row.cells = new ArrayList<List<PositionedText>>(line.size());
\r
461 row.urls = new ArrayList<URL>();
\r
462 if (line.size() > 1) {
\r
463 for (int i = 0; i < line.size(); i++) {
\r
464 TextItem item =line.get(i);
\r
466 int availableSize = columnSizes.get(i)-textOffsetX;
\r
467 if (item != null && item.getText().length() > 0) {
\r
468 String text = item.getText();
\r
469 List<PositionedText> pt = getText(text, columnPositions.get(i)+textOffsetX, h,availableSize, columns.get(i).getAlignment());
\r
471 reservedSpace = Math.max(reservedSpace, getResevedSpace(pt)+getLineHeight());
\r
472 realLines = Math.max(realLines, getLineSpace(pt));
\r
474 row.cells.add(Collections.<PositionedText> emptyList());
\r
475 reservedSpace = Math.max(reservedSpace, getLineHeight());
\r
477 if (item instanceof URLItem) {
\r
478 row.urls.add(((URLItem)item).getURL());
\r
480 row.urls.add(null);
\r
485 String text = line.get(0).getText();
\r
486 int availableSize = stream.contentWidth;
\r
487 if (text != null && text.length() > 0) {
\r
488 List<PositionedText> pt = getText(text, textOffsetX, h,availableSize, columns.get(0).getAlignment());
\r
490 reservedSpace = Math.max(reservedSpace, getResevedSpace(pt)+getLineHeight());
\r
491 realLines = Math.max(realLines, getLineSpace(pt));
\r
493 row.cells.add(Collections.<PositionedText> emptyList());
\r
494 reservedSpace = Math.max(reservedSpace, getLineHeight());
\r
497 row.reservedSpace = reservedSpace;
\r
498 row.startLine = currentLine;
\r
499 row.realLines = realLines;
\r
503 private int getResevedSpace(List<PositionedText> pt) {
\r
504 float sy = pt.get(0).drawPosY;
\r
505 float ey = pt.get(pt.size()-1).drawPosY;
\r
506 return (int)Math.ceil(ey-sy);
\r
509 * Usually lines of multi-line cells consume less space than the maximum line height (FontMetrics). This method calculates the exact amount of lines required by a cell.
\r
513 private int getLineSpace(List<PositionedText> pt) {
\r
517 return (int)(getResevedSpace(pt)/getLineHeight())+1;
\r
520 private class PositionedRow {
\r
524 List<List<PositionedText>> cells;
\r
527 void render(Graphics2D g) {
\r
528 int ht = getTopHeight(startLine);
\r
529 int hb = ht +reservedSpace;
\r
530 if (cells.size() > 0) {
\r
531 for (int i = 0; i < cells.size(); i++) {
\r
532 List<PositionedText> ptl = cells.get(i);
\r
533 for (PositionedText pt : ptl)
\r
535 if (urls != null) {
\r
536 URL url = urls.get(i);
\r
538 addLink(url, columnPositions.get(i),ht,columnSizes.get(i),hb-ht);
\r
543 if (linesVisible) {
\r
544 if (cells.size() > 0) {
\r
545 for (int i = 0; i < cells.size(); i++) {
\r
546 g.drawLine(columnPositions.get(i), ht, columnPositions.get(i), hb);
\r
549 g.drawLine(columnPositions.get(0), ht, columnPositions.get(0), hb);
\r
551 if (isFirstLine() || !linesPrevVisible) {
\r
552 g.drawLine(0, ht, stream.contentWidth, ht);
\r
553 linesPrevVisible = true;
\r
555 g.drawLine(stream.contentWidth, ht, stream.contentWidth, hb);
\r
556 g.drawLine(0, hb, stream.contentWidth, hb);
\r
562 private void addLink(URL url, int x, int y, int w, int h) {
\r
563 PDFPage page = getCurrentPage();
\r
564 float fx = +page.stream.marginLeft + x;
\r
565 float fy = -page.stream.marginTop + page.template.getHeight() - y;
\r
566 page.template.setAction(new PdfAction(url), fx, fy-h, fx+w, fy);
\r
569 private List<PositionedText> getText(String text, int x, int y, int cellWidth, Alignment alignment) {
\r
570 List<PositionedText> result = new ArrayList<PositionedText>();
\r
571 Hashtable<TextAttribute, Object> map = new Hashtable<TextAttribute, Object>();
\r
572 PDFPage page = getCurrentPage();
\r
573 Font font = page.getFont();
\r
574 for (TextAttribute a : font.getAttributes().keySet()) {
\r
575 Object v = font.getAttributes().get(a);
\r
579 // map.putAll(font.getAttributes());
\r
580 map.put(TextAttribute.FOREGROUND, Color.black);
\r
582 AttributedString attributedText = new AttributedString( text, map);
\r
584 AttributedCharacterIterator paragraph = attributedText.getIterator();
\r
585 int paragraphStart = paragraph.getBeginIndex();
\r
586 int paragraphEnd = paragraph.getEndIndex();
\r
587 LineBreakMeasurer lineMeasurer = new LineBreakMeasurer(paragraph, page.frc);
\r
588 lineMeasurer.setPosition(paragraphStart);
\r
590 // Get lines until the entire paragraph has been displayed.
\r
591 int next, limit, charat, position = 0;
\r
593 while ((position = lineMeasurer.getPosition()) < paragraphEnd) {
\r
595 // Find possible line break and set it as a limit to the next layout
\r
596 next = lineMeasurer.nextOffset(cellWidth);
\r
598 charat = text.indexOf(System.getProperty("line.separator"),position+1);
\r
599 if(charat < next && charat != -1){
\r
603 // Retrieve next layout. A cleverer program would also cache
\r
604 // these layouts until the component is re-sized.
\r
605 TextLayout layout = lineMeasurer.nextLayout(cellWidth, limit, false);
\r
607 // Compute pen x position. If the paragraph is right-to-left we
\r
608 // will align the TextLayouts to the right edge of the panel.
\r
609 // Note: this won't occur for the English text in this sample.
\r
610 // Note: drawPosX is always where the LEFT of the text is placed.
\r
611 float drawPosX = 0;
\r
612 switch (alignment) {
\r
614 drawPosX = layout.isLeftToRight() ? 0 : cellWidth - layout.getAdvance();
\r
617 drawPosX = (cellWidth - layout.getAdvance()) / 2;
\r
620 drawPosX = layout.isLeftToRight() ? cellWidth - layout.getAdvance() : 0;
\r
626 // If text has been forced to vertical, align it to center
\r
627 // if(breakWidth < textAreaWidth) {
\r
628 // float centerCorrection = layout.isLeftToRight() ?
\r
629 // (float) (layout.getAdvance() / 2) :
\r
630 // -1 * (float) (layout.getAdvance() / 2);
\r
631 // drawPosX = textAreaWidth / 2 - centerCorrection;
\r
634 // Stop drawing if the text won't fit
\r
635 // if (breakHeight < drawPosY + layout.getDescent() + layout.getLeading()) {
\r
639 // drawPosY += layout.getAscent();
\r
641 // Add TextLayout at (drawPosX, drawPosY).
\r
642 result.add(new PositionedText(drawPosX, drawPosY, layout));
\r
644 // Move y-coordinate in preparation for next layout.
\r
645 //drawPosY += layout.getDescent() + layout.getLeading();
\r
646 drawPosY += layout.getDescent() + layout.getLeading() + layout.getAscent();
\r
647 // drawPosY += getLineHeight();
\r
652 class PositionedText {
\r
655 TextLayout layout;
\r
657 public PositionedText(float drawPosX, float drawPosY, TextLayout layout) {
\r
658 this.drawPosX = drawPosX;
\r
659 this.drawPosY = drawPosY;
\r
660 this.layout = layout;
\r
663 public void render(Graphics2D g) {
\r
664 layout.draw(g, drawPosX, drawPosY);
\r