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