-package org.simantics.document.linking.report.pdf;\r
-\r
-import java.awt.Color;\r
-import java.awt.Font;\r
-import java.awt.Graphics2D;\r
-import java.awt.Shape;\r
-import java.awt.font.LineBreakMeasurer;\r
-import java.awt.font.TextAttribute;\r
-import java.awt.font.TextLayout;\r
-import java.net.URL;\r
-import java.text.AttributedCharacterIterator;\r
-import java.text.AttributedString;\r
-import java.util.ArrayList;\r
-import java.util.Collections;\r
-import java.util.Hashtable;\r
-import java.util.List;\r
-\r
-import org.simantics.document.linking.report.Document.TextSize;\r
-import org.simantics.document.linking.report.Table;\r
-import org.simantics.document.linking.report.TableColumn;\r
-import org.simantics.document.linking.report.TableColumn.Alignment;\r
-import org.simantics.document.linking.report.TableRow;\r
-import org.simantics.document.linking.report.TextItem;\r
-import org.simantics.document.linking.report.URLItem;\r
-\r
-import com.lowagie.text.pdf.PdfAction;\r
-\r
-\r
-public class PDFTable implements Table, PDFElement {\r
- PDFDocument writer;\r
- PDFPageStream stream;\r
- PDFPage startPage;\r
- \r
- int currentLine = 0;\r
- \r
- List<TableColumn> columns = new ArrayList<TableColumn>();\r
- List<String> columnNames = new ArrayList<String>();\r
- List<Integer> columnSizes = new ArrayList<Integer>();\r
- List<Integer> columnPositions = new ArrayList<Integer>();\r
- \r
- TextItem title = null;\r
- \r
- boolean headerVisible = true;\r
- private boolean linesVisible = true;\r
- private boolean linesPrevVisible = true;\r
- boolean clipText = false;\r
- \r
- int textOffsetX = 2;\r
- int textOffsetY = 2;\r
- \r
- public PDFTable(PDFDocument writer, PDFPageStream stream) {\r
- this.writer = writer;\r
- this.stream = stream;\r
- this.startPage = stream.getCurrentPage();\r
- \r
- }\r
- \r
- public PDFTable(PDFTable table) {\r
- this.writer = table.writer;\r
- this.stream = table.stream;\r
- this.startPage = stream.getCurrentPage();\r
- this.columns.addAll(table.columns);\r
- this.columnNames.addAll(table.columnNames);\r
- \r
- updateColumnPositions();\r
- }\r
- \r
- \r
- @Override\r
- public PDFPage getPage() {\r
- return startPage;\r
- }\r
- \r
- /* (non-Javadoc)\r
- * @see org.simantics.document.linking.report.Table#addColumn(java.lang.String, double)\r
- */\r
- @Override\r
- public TableColumn addColumn(String name, double width) {\r
- TableColumn tc = new TableColumn(name, width);\r
- columns.add(tc);\r
- columnNames.add(name);\r
- \r
- updateColumnPositions();\r
- return tc;\r
- }\r
- \r
- private void updateColumnPositions() {\r
- int pos = 0;\r
- columnSizes.clear();\r
- columnPositions.clear();\r
- for (TableColumn c : columns) {\r
- int size = (int)(c.getWidth()*stream.getContentWidth());\r
- columnSizes.add(size);\r
- columnPositions.add(pos);\r
- pos+=size;\r
- }\r
- }\r
- \r
- @Override\r
- public List<TableColumn> getColumns() {\r
- return columns;\r
- }\r
- \r
- @Override\r
- public boolean isLinesVisible() {\r
- return linesVisible;\r
- }\r
- \r
- @Override\r
- public void setLinesVisible(boolean b) {\r
- if (this.linesVisible == b)\r
- return;\r
- this.linesPrevVisible = linesVisible;\r
- this.linesVisible = b;\r
- }\r
- \r
- @Override\r
- public boolean isHeaderVisible() {\r
- return headerVisible;\r
- }\r
- \r
- @Override\r
- public void setHeaderVisible(boolean b) {\r
- this.headerVisible = b;\r
- }\r
- \r
- private boolean isFirstLine() {\r
- return currentLine == 0 || stream.getCurrentPage().currentLine == 1;\r
- }\r
- \r
- @Override\r
- public void setTitle(String title) {\r
- try {\r
- this.title = writer.newItem(TextItem.class);\r
- this.title.setText(title);\r
- } catch (Exception e) {\r
- \r
- }\r
- }\r
- \r
- @Override\r
- public void setTitle(TextItem title){\r
- this.title = title;\r
- }\r
- \r
- /* (non-Javadoc)\r
- * @see org.simantics.document.linking.report.Table#writeRow(java.lang.String[])\r
- */\r
- @Override\r
- public TableRow writeRow(String... line) throws Exception{\r
- List<String> list = new ArrayList<String>(line.length);\r
- for (String s : line)\r
- list.add(s);\r
- return writeRow(list);\r
- }\r
- \r
- /* (non-Javadoc)\r
- * @see org.simantics.document.linking.report.Table#writeRow(java.util.List)\r
- */\r
- @Override\r
- public TableRow writeRow(List<String> line) throws Exception{\r
- if (isFirstLine())\r
- writeHeader();\r
- return _writeRow(line);\r
- }\r
- \r
- @Override\r
- public TableRow writeRowItem(TextItem... line) throws Exception {\r
- List<TextItem> list = new ArrayList<TextItem>(line.length);\r
- for (TextItem s : line)\r
- list.add(s);\r
- return writeRowItem(list);\r
- }\r
- \r
- @Override\r
- public TableRow writeRowItem(List<TextItem> line) throws Exception {\r
- if (isFirstLine())\r
- writeHeader();\r
- return _writeRow2(line);\r
- }\r
- \r
- private TableRow _writeRow(List<String> line) throws Exception {\r
- int h = getTextHeight();\r
- int ht = getTopHeight();\r
- int hb = getBottomHeight();\r
- PDFPage page = getCurrentPage();\r
- Graphics2D g2d = page.g2d;\r
- Shape clip = g2d.getClip();\r
- \r
- if (clipText) {\r
- for (int i = 0; i < line.size(); i++) {\r
- if (line.get(i) == null)\r
- continue;\r
- g2d.setClip(columnPositions.get(i),ht-1,columnSizes.get(i),hb-ht+2);\r
- g2d.drawString(line.get(i), columnPositions.get(i)+textOffsetX, h);\r
- }\r
- g2d.setClip(clip);\r
- if (linesVisible) {\r
- for (int i = 0; i < line.size(); i++) {\r
- g2d.drawLine(columnPositions.get(i), ht, columnPositions.get(i), hb);\r
- }\r
- if (isFirstLine() || !linesPrevVisible) {\r
- g2d.drawLine(0, ht, stream.contentWidth, ht);\r
- linesPrevVisible = true;\r
- }\r
- g2d.drawLine(stream.contentWidth, ht, stream.contentWidth, hb);\r
- g2d.drawLine(0, hb, stream.contentWidth, hb);\r
- }\r
- currentLine++;\r
- page.currentLine++;\r
- page.availableLines--;\r
- page.currentPixel += getLineHeight();\r
- } else {\r
- PositionedRow row = _getRow(line);\r
- if (stream.contentHeight-page.currentPixel < row.reservedSpace) {\r
- stream.nextPage();\r
- page = getCurrentPage();\r
- g2d = page.g2d;\r
- writeHeader();\r
- row = _getRow(line);\r
- }\r
- row.render(g2d);\r
- currentLine+= row.realLines;\r
- page.currentLine+= row.realLines;\r
- page.currentPixel += row.reservedSpace;\r
- page.estimateAvailableLines();\r
- }\r
- \r
- stream.checkNextPage();\r
- return new PDFTableRow();\r
- }\r
- \r
- private TableRow _writeRow2(List<TextItem> line) throws Exception {\r
- int h = getTextHeight();\r
- int ht = getTopHeight();\r
- int hb = getBottomHeight();\r
- PDFPage page = getCurrentPage();\r
- Graphics2D g2d = page.g2d;\r
- Shape clip = g2d.getClip();\r
- \r
- if (clipText) {\r
- for (int i = 0; i < line.size(); i++) {\r
- TextItem text = line.get(i);\r
- if (text == null)\r
- continue;\r
- g2d.setClip(columnPositions.get(i),ht,columnSizes.get(i),hb-ht);\r
- g2d.drawString(text.getText(), columnPositions.get(i)+textOffsetX, h);\r
- if (text instanceof URLItem) {\r
- URL url = ((URLItem)text).getURL();\r
- if (url != null) {\r
- addLink(url, columnPositions.get(i),ht,columnSizes.get(i),hb-ht);\r
- }\r
- }\r
- }\r
- g2d.setClip(clip);\r
- if (linesVisible) {\r
- for (int i = 0; i < line.size(); i++) {\r
- g2d.drawLine(columnPositions.get(i), ht, columnPositions.get(i), hb);\r
- }\r
- if (isFirstLine() || !linesPrevVisible) {\r
- g2d.drawLine(0, ht, stream.contentWidth, ht);\r
- linesPrevVisible = true;\r
- }\r
- g2d.drawLine(stream.contentWidth, ht, stream.contentWidth, hb);\r
- g2d.drawLine(0, hb, stream.contentWidth, hb);\r
- }\r
- currentLine++;\r
- page.currentLine++;\r
- page.availableLines--;\r
- page.currentPixel += getLineHeight();\r
- } else {\r
- PositionedRow row = _getRow2(line);\r
- if (stream.contentHeight-page.currentPixel < row.reservedSpace) {\r
- stream.nextPage();\r
- page = getCurrentPage();\r
- g2d = page.g2d;\r
- writeHeader();\r
- row = _getRow2(line);\r
- }\r
- row.render(g2d);\r
- currentLine+= row.realLines;\r
- page.currentLine+= row.realLines;\r
- page.currentPixel += row.reservedSpace;\r
- page.estimateAvailableLines();\r
- }\r
- \r
- stream.checkNextPage();\r
- return new PDFTableRow();\r
- }\r
- \r
- void writeLine(String line) throws Exception{\r
- writeLine(line, 0);\r
- }\r
- \r
- void writeLine(TextItem line) throws Exception{\r
- writeLine(line, 0);\r
- }\r
- \r
- private void writeHeader() throws Exception{\r
- if (headerVisible) {\r
- TextSize s = currentTextSize;\r
- setTextSize(TextSize.MEDIUM);\r
- if (title != null) {\r
- boolean b = linesVisible;\r
- setLinesVisible(false);\r
- writeLine(title);\r
- setLinesVisible(b);\r
- }\r
- _writeRow(columnNames);\r
- setTextSize(s);\r
- }\r
- }\r
- \r
- void writeLine(String line, int x) throws Exception{\r
- int h = getTextHeight();\r
- int ht = getTopHeight();\r
- int hb = getBottomHeight();\r
- PDFPage page = getCurrentPage();\r
- Graphics2D g2d = page.g2d;\r
- g2d.drawString(line, x+textOffsetX, h);\r
- if (linesVisible) {\r
- if (isFirstLine() || !linesPrevVisible) {\r
- g2d.drawLine(0, ht, stream.contentWidth, ht);\r
- linesPrevVisible = true;\r
- }\r
- g2d.drawLine(0, ht, 0, hb);\r
- g2d.drawLine(stream.contentWidth, ht, stream.contentWidth, hb);\r
- g2d.drawLine(0, hb, stream.contentWidth, hb);\r
- }\r
- currentLine++;\r
- page.currentLine++;\r
- page.availableLines--;\r
- page.currentPixel += getLineHeight();\r
- stream.checkNextPage();\r
- }\r
- \r
- void writeLine(TextItem line, int x) throws Exception{\r
- int h = getTextHeight();\r
- int ht = getTopHeight();\r
- int hb = getBottomHeight();\r
- PDFPage page = getCurrentPage();\r
- Graphics2D g2d = page.g2d;\r
- g2d.drawString(line.getText(), x+textOffsetX, h);\r
- if (linesVisible) {\r
- if (isFirstLine() || !linesPrevVisible) {\r
- g2d.drawLine(0, ht, stream.contentWidth, ht);\r
- linesPrevVisible = true;\r
- }\r
- g2d.drawLine(0, ht, 0, hb);\r
- g2d.drawLine(stream.contentWidth, ht, stream.contentWidth, hb);\r
- g2d.drawLine(0, hb, stream.contentWidth, hb);\r
- }\r
- if (line instanceof URLItem) {\r
- URL url = ((URLItem)line).getURL();\r
- if (url != null) {\r
- addLink(url, 0,ht,stream.contentWidth,hb-ht);\r
- }\r
- }\r
- currentLine++;\r
- page.currentLine++;\r
- page.availableLines--;\r
- page.currentPixel += getLineHeight();\r
- stream.checkNextPage();\r
- }\r
- \r
- int getTopHeight() {\r
- return getTopHeight(currentLine);\r
- }\r
- \r
- int getTopHeight(int line) {\r
- return (line-currentLine)*getLineHeight()+getCurrentPage().currentPixel;\r
- }\r
- \r
- int getTextHeight() {\r
- PDFPage page = getCurrentPage();\r
- return page.currentPixel+getLineHeight()-page.fm.getDescent()-textOffsetY;\r
- }\r
- \r
- PDFPage getCurrentPage() {\r
- return stream.getCurrentPage();\r
- }\r
- \r
- int getBottomHeight() {\r
- return getBottomHeight(currentLine);\r
- }\r
- \r
- protected int getLineHeight() {\r
- return getCurrentPage().fm.getHeight()+textOffsetY;\r
- }\r
- \r
- private int getBottomHeight(int line) {\r
- return (line-currentLine+1)*getLineHeight()+getCurrentPage().currentPixel;\r
- }\r
- \r
- public int getAvailableLines() {\r
- PDFPage page = getCurrentPage();\r
- int contentHeight = stream.contentHeight;\r
- int pixelY = page.currentPixel;\r
- return (int)Math.floor((contentHeight-pixelY)/getLineHeight());\r
- }\r
- \r
- private TextSize currentTextSize = TextSize.SMALL;\r
- \r
- @Override\r
- public void setTextSize(TextSize size) {\r
- stream.getCurrentPage().setFont(writer.fonts.get(size));\r
- currentTextSize = size;\r
- }\r
- \r
- @Override\r
- public TextSize getTextSize() {\r
- return currentTextSize;\r
- }\r
- \r
- private PositionedRow _getRow(List<String> line) {\r
- PositionedRow row = new PositionedRow();\r
- int h = getTextHeight();\r
- int realLines = 1;\r
- int reservedSpace = 0;\r
- List<List<PositionedText>> cells = new ArrayList<List<PositionedText>>(line.size());\r
- if (line.size() > 1) {\r
- for (int i = 0; i < line.size(); i++) {\r
- String text = line.get(i);\r
- int availableSize = columnSizes.get(i)-textOffsetX;\r
- if (text != null && text.length() > 0) {\r
- List<PositionedText> pt = getText(text, columnPositions.get(i)+textOffsetX, h,availableSize, columns.get(i).getAlignment());\r
- cells.add(pt);\r
- reservedSpace = Math.max(reservedSpace, getResevedSpace(pt)+getLineHeight());\r
- realLines = Math.max(realLines, getLineSpace(pt));\r
- } else {\r
- cells.add(Collections.<PositionedText> emptyList());\r
- reservedSpace = Math.max(reservedSpace, getLineHeight());\r
- }\r
- }\r
- } else {\r
- String text = line.get(0);\r
- int availableSize = stream.contentWidth;\r
- if (text != null && text.length() > 0) {\r
- List<PositionedText> pt = getText(text, textOffsetX, h,availableSize, columns.get(0).getAlignment());\r
- cells.add(pt);\r
- reservedSpace = Math.max(reservedSpace, getResevedSpace(pt)+getLineHeight());\r
- realLines = Math.max(realLines, getLineSpace(pt));\r
- } else {\r
- cells.add(Collections.<PositionedText> emptyList());\r
- reservedSpace = Math.max(reservedSpace, getLineHeight());\r
- }\r
- }\r
- row.reservedSpace = reservedSpace;\r
- row.startLine = currentLine;\r
- row.realLines = realLines;\r
- row.cells = cells;\r
- return row;\r
- }\r
- \r
- private PositionedRow _getRow2(List<TextItem> line) {\r
- PositionedRow row = new PositionedRow();\r
- int h = getTextHeight();\r
- int realLines = 1;\r
- int reservedSpace = 0;\r
- row.cells = new ArrayList<List<PositionedText>>(line.size());\r
- row.urls = new ArrayList<URL>();\r
- if (line.size() > 1) {\r
- for (int i = 0; i < line.size(); i++) {\r
- TextItem item =line.get(i); \r
- \r
- int availableSize = columnSizes.get(i)-textOffsetX;\r
- if (item != null && item.getText().length() > 0) {\r
- String text = item.getText();\r
- List<PositionedText> pt = getText(text, columnPositions.get(i)+textOffsetX, h,availableSize, columns.get(i).getAlignment());\r
- row.cells.add(pt);\r
- reservedSpace = Math.max(reservedSpace, getResevedSpace(pt)+getLineHeight());\r
- realLines = Math.max(realLines, getLineSpace(pt));\r
- } else {\r
- row.cells.add(Collections.<PositionedText> emptyList());\r
- reservedSpace = Math.max(reservedSpace, getLineHeight());\r
- }\r
- if (item instanceof URLItem) {\r
- row.urls.add(((URLItem)item).getURL());\r
- } else {\r
- row.urls.add(null);\r
- }\r
- }\r
- \r
- } else {\r
- String text = line.get(0).getText();\r
- int availableSize = stream.contentWidth;\r
- if (text != null && text.length() > 0) {\r
- List<PositionedText> pt = getText(text, textOffsetX, h,availableSize, columns.get(0).getAlignment());\r
- row.cells.add(pt);\r
- reservedSpace = Math.max(reservedSpace, getResevedSpace(pt)+getLineHeight());\r
- realLines = Math.max(realLines, getLineSpace(pt));\r
- } else {\r
- row.cells.add(Collections.<PositionedText> emptyList());\r
- reservedSpace = Math.max(reservedSpace, getLineHeight());\r
- }\r
- }\r
- row.reservedSpace = reservedSpace;\r
- row.startLine = currentLine;\r
- row.realLines = realLines;\r
- return row;\r
- }\r
- \r
- private int getResevedSpace(List<PositionedText> pt) {\r
- float sy = pt.get(0).drawPosY;\r
- float ey = pt.get(pt.size()-1).drawPosY;\r
- return (int)Math.ceil(ey-sy);\r
- }\r
- /**\r
- * 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
- * @param pt\r
- * @return\r
- */\r
- private int getLineSpace(List<PositionedText> pt) {\r
- if (pt.size() < 2)\r
- return 1;\r
- \r
- return (int)(getResevedSpace(pt)/getLineHeight())+1;\r
- }\r
- \r
- private class PositionedRow {\r
- int startLine;\r
- int realLines;\r
- int reservedSpace;\r
- List<List<PositionedText>> cells;\r
- List<URL> urls;\r
- \r
- void render(Graphics2D g) {\r
- int ht = getTopHeight(startLine);\r
- int hb = ht +reservedSpace;\r
- if (cells.size() > 0) {\r
- for (int i = 0; i < cells.size(); i++) {\r
- List<PositionedText> ptl = cells.get(i);\r
- for (PositionedText pt : ptl)\r
- pt.render(g);\r
- if (urls != null) {\r
- URL url = urls.get(i);\r
- if (url != null) {\r
- addLink(url, columnPositions.get(i),ht,columnSizes.get(i),hb-ht);\r
- }\r
- }\r
- }\r
- } \r
- if (linesVisible) {\r
- if (cells.size() > 0) {\r
- for (int i = 0; i < cells.size(); i++) {\r
- g.drawLine(columnPositions.get(i), ht, columnPositions.get(i), hb);\r
- }\r
- } else {\r
- g.drawLine(columnPositions.get(0), ht, columnPositions.get(0), hb);\r
- }\r
- if (isFirstLine() || !linesPrevVisible) {\r
- g.drawLine(0, ht, stream.contentWidth, ht);\r
- linesPrevVisible = true;\r
- }\r
- g.drawLine(stream.contentWidth, ht, stream.contentWidth, hb);\r
- g.drawLine(0, hb, stream.contentWidth, hb);\r
- \r
- }\r
- }\r
- }\r
- \r
- private void addLink(URL url, int x, int y, int w, int h) {\r
- PDFPage page = getCurrentPage();\r
- float fx = +page.stream.marginLeft + x;\r
- float fy = -page.stream.marginTop + page.template.getHeight() - y;\r
- page.template.setAction(new PdfAction(url), fx, fy-h, fx+w, fy);\r
- }\r
- \r
- private List<PositionedText> getText(String text, int x, int y, int cellWidth, Alignment alignment) {\r
- List<PositionedText> result = new ArrayList<PositionedText>();\r
- Hashtable<TextAttribute, Object> map = new Hashtable<TextAttribute, Object>();\r
- PDFPage page = getCurrentPage();\r
- Font font = page.getFont();\r
- for (TextAttribute a : font.getAttributes().keySet()) {\r
- Object v = font.getAttributes().get(a);\r
- if (v != null)\r
- map.put(a, v);\r
- }\r
-// map.putAll(font.getAttributes());\r
- map.put(TextAttribute.FOREGROUND, Color.black);\r
-\r
- AttributedString attributedText = new AttributedString( text, map);\r
-\r
- AttributedCharacterIterator paragraph = attributedText.getIterator();\r
- int paragraphStart = paragraph.getBeginIndex();\r
- int paragraphEnd = paragraph.getEndIndex();\r
- LineBreakMeasurer lineMeasurer = new LineBreakMeasurer(paragraph, page.frc);\r
- lineMeasurer.setPosition(paragraphStart);\r
- \r
- // Get lines until the entire paragraph has been displayed.\r
- int next, limit, charat, position = 0;\r
- int drawPosY = y;\r
- while ((position = lineMeasurer.getPosition()) < paragraphEnd) {\r
-\r
- // Find possible line break and set it as a limit to the next layout\r
- next = lineMeasurer.nextOffset(cellWidth);\r
- limit = next;\r
- charat = text.indexOf(System.getProperty("line.separator"),position+1);\r
- if(charat < next && charat != -1){\r
- limit = charat;\r
- }\r
-\r
- // Retrieve next layout. A cleverer program would also cache\r
- // these layouts until the component is re-sized.\r
- TextLayout layout = lineMeasurer.nextLayout(cellWidth, limit, false);\r
-\r
- // Compute pen x position. If the paragraph is right-to-left we\r
- // will align the TextLayouts to the right edge of the panel.\r
- // Note: this won't occur for the English text in this sample.\r
- // Note: drawPosX is always where the LEFT of the text is placed.\r
- float drawPosX = 0;\r
- switch (alignment) {\r
- case LEFT: \r
- drawPosX = layout.isLeftToRight() ? 0 : cellWidth - layout.getAdvance();\r
- break;\r
- case CENTER:\r
- drawPosX = (cellWidth - layout.getAdvance()) / 2;\r
- break;\r
- case RIGHT:\r
- drawPosX = layout.isLeftToRight() ? cellWidth - layout.getAdvance() : 0;\r
- break;\r
- }\r
- \r
- drawPosX += x;\r
- \r
- // If text has been forced to vertical, align it to center\r
-// if(breakWidth < textAreaWidth) {\r
-// float centerCorrection = layout.isLeftToRight() ? \r
-// (float) (layout.getAdvance() / 2) : \r
-// -1 * (float) (layout.getAdvance() / 2);\r
-// drawPosX = textAreaWidth / 2 - centerCorrection;\r
-// }\r
-\r
- // Stop drawing if the text won't fit\r
-// if (breakHeight < drawPosY + layout.getDescent() + layout.getLeading()) {\r
-// break;\r
-// }\r
-\r
-// drawPosY += layout.getAscent();\r
-\r
- // Add TextLayout at (drawPosX, drawPosY).\r
- result.add(new PositionedText(drawPosX, drawPosY, layout));\r
-\r
- // Move y-coordinate in preparation for next layout.\r
- //drawPosY += layout.getDescent() + layout.getLeading();\r
- drawPosY += layout.getDescent() + layout.getLeading() + layout.getAscent();\r
-// drawPosY += getLineHeight();\r
- }\r
- return result;\r
- }\r
- \r
- class PositionedText {\r
- float drawPosX;\r
- float drawPosY;\r
- TextLayout layout; \r
- \r
- public PositionedText(float drawPosX, float drawPosY, TextLayout layout) {\r
- this.drawPosX = drawPosX;\r
- this.drawPosY = drawPosY;\r
- this.layout = layout;\r
- }\r
- \r
- public void render(Graphics2D g) {\r
- layout.draw(g, drawPosX, drawPosY);\r
- }\r
- }\r
-}\r
+package org.simantics.document.linking.report.pdf;
+
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.Graphics2D;
+import java.awt.Shape;
+import java.awt.font.LineBreakMeasurer;
+import java.awt.font.TextAttribute;
+import java.awt.font.TextLayout;
+import java.net.URL;
+import java.text.AttributedCharacterIterator;
+import java.text.AttributedString;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Hashtable;
+import java.util.List;
+
+import org.simantics.document.linking.report.Document.TextSize;
+import org.simantics.document.linking.report.Table;
+import org.simantics.document.linking.report.TableColumn;
+import org.simantics.document.linking.report.TableColumn.Alignment;
+import org.simantics.document.linking.report.TableRow;
+import org.simantics.document.linking.report.TextItem;
+import org.simantics.document.linking.report.URLItem;
+
+import com.lowagie.text.pdf.PdfAction;
+
+
+public class PDFTable implements Table, PDFElement {
+ PDFDocument writer;
+ PDFPageStream stream;
+ PDFPage startPage;
+
+ int currentLine = 0;
+
+ List<TableColumn> columns = new ArrayList<TableColumn>();
+ List<String> columnNames = new ArrayList<String>();
+ List<Integer> columnSizes = new ArrayList<Integer>();
+ List<Integer> columnPositions = new ArrayList<Integer>();
+
+ TextItem title = null;
+
+ boolean headerVisible = true;
+ private boolean linesVisible = true;
+ private boolean linesPrevVisible = true;
+ boolean clipText = false;
+
+ int textOffsetX = 2;
+ int textOffsetY = 2;
+
+ public PDFTable(PDFDocument writer, PDFPageStream stream) {
+ this.writer = writer;
+ this.stream = stream;
+ this.startPage = stream.getCurrentPage();
+
+ }
+
+ public PDFTable(PDFTable table) {
+ this.writer = table.writer;
+ this.stream = table.stream;
+ this.startPage = stream.getCurrentPage();
+ this.columns.addAll(table.columns);
+ this.columnNames.addAll(table.columnNames);
+
+ updateColumnPositions();
+ }
+
+
+ @Override
+ public PDFPage getPage() {
+ return startPage;
+ }
+
+ /* (non-Javadoc)
+ * @see org.simantics.document.linking.report.Table#addColumn(java.lang.String, double)
+ */
+ @Override
+ public TableColumn addColumn(String name, double width) {
+ TableColumn tc = new TableColumn(name, width);
+ columns.add(tc);
+ columnNames.add(name);
+
+ updateColumnPositions();
+ return tc;
+ }
+
+ private void updateColumnPositions() {
+ int pos = 0;
+ columnSizes.clear();
+ columnPositions.clear();
+ for (TableColumn c : columns) {
+ int size = (int)(c.getWidth()*stream.getContentWidth());
+ columnSizes.add(size);
+ columnPositions.add(pos);
+ pos+=size;
+ }
+ }
+
+ @Override
+ public List<TableColumn> getColumns() {
+ return columns;
+ }
+
+ @Override
+ public boolean isLinesVisible() {
+ return linesVisible;
+ }
+
+ @Override
+ public void setLinesVisible(boolean b) {
+ if (this.linesVisible == b)
+ return;
+ this.linesPrevVisible = linesVisible;
+ this.linesVisible = b;
+ }
+
+ @Override
+ public boolean isHeaderVisible() {
+ return headerVisible;
+ }
+
+ @Override
+ public void setHeaderVisible(boolean b) {
+ this.headerVisible = b;
+ }
+
+ private boolean isFirstLine() {
+ return currentLine == 0 || stream.getCurrentPage().currentLine == 1;
+ }
+
+ @Override
+ public void setTitle(String title) {
+ try {
+ this.title = writer.newItem(TextItem.class);
+ this.title.setText(title);
+ } catch (Exception e) {
+
+ }
+ }
+
+ @Override
+ public void setTitle(TextItem title){
+ this.title = title;
+ }
+
+ /* (non-Javadoc)
+ * @see org.simantics.document.linking.report.Table#writeRow(java.lang.String[])
+ */
+ @Override
+ public TableRow writeRow(String... line) throws Exception{
+ List<String> list = new ArrayList<String>(line.length);
+ for (String s : line)
+ list.add(s);
+ return writeRow(list);
+ }
+
+ /* (non-Javadoc)
+ * @see org.simantics.document.linking.report.Table#writeRow(java.util.List)
+ */
+ @Override
+ public TableRow writeRow(List<String> line) throws Exception{
+ if (isFirstLine())
+ writeHeader();
+ return _writeRow(line);
+ }
+
+ @Override
+ public TableRow writeRowItem(TextItem... line) throws Exception {
+ List<TextItem> list = new ArrayList<TextItem>(line.length);
+ for (TextItem s : line)
+ list.add(s);
+ return writeRowItem(list);
+ }
+
+ @Override
+ public TableRow writeRowItem(List<TextItem> line) throws Exception {
+ if (isFirstLine())
+ writeHeader();
+ return _writeRow2(line);
+ }
+
+ private TableRow _writeRow(List<String> line) throws Exception {
+ int h = getTextHeight();
+ int ht = getTopHeight();
+ int hb = getBottomHeight();
+ PDFPage page = getCurrentPage();
+ Graphics2D g2d = page.g2d;
+ Shape clip = g2d.getClip();
+
+ if (clipText) {
+ for (int i = 0; i < line.size(); i++) {
+ if (line.get(i) == null)
+ continue;
+ g2d.setClip(columnPositions.get(i),ht-1,columnSizes.get(i),hb-ht+2);
+ g2d.drawString(line.get(i), columnPositions.get(i)+textOffsetX, h);
+ }
+ g2d.setClip(clip);
+ if (linesVisible) {
+ for (int i = 0; i < line.size(); i++) {
+ g2d.drawLine(columnPositions.get(i), ht, columnPositions.get(i), hb);
+ }
+ if (isFirstLine() || !linesPrevVisible) {
+ g2d.drawLine(0, ht, stream.contentWidth, ht);
+ linesPrevVisible = true;
+ }
+ g2d.drawLine(stream.contentWidth, ht, stream.contentWidth, hb);
+ g2d.drawLine(0, hb, stream.contentWidth, hb);
+ }
+ currentLine++;
+ page.currentLine++;
+ page.availableLines--;
+ page.currentPixel += getLineHeight();
+ } else {
+ PositionedRow row = _getRow(line);
+ if (stream.contentHeight-page.currentPixel < row.reservedSpace) {
+ stream.nextPage();
+ page = getCurrentPage();
+ g2d = page.g2d;
+ writeHeader();
+ row = _getRow(line);
+ }
+ row.render(g2d);
+ currentLine+= row.realLines;
+ page.currentLine+= row.realLines;
+ page.currentPixel += row.reservedSpace;
+ page.estimateAvailableLines();
+ }
+
+ stream.checkNextPage();
+ return new PDFTableRow();
+ }
+
+ private TableRow _writeRow2(List<TextItem> line) throws Exception {
+ int h = getTextHeight();
+ int ht = getTopHeight();
+ int hb = getBottomHeight();
+ PDFPage page = getCurrentPage();
+ Graphics2D g2d = page.g2d;
+ Shape clip = g2d.getClip();
+
+ if (clipText) {
+ for (int i = 0; i < line.size(); i++) {
+ TextItem text = line.get(i);
+ if (text == null)
+ continue;
+ g2d.setClip(columnPositions.get(i),ht,columnSizes.get(i),hb-ht);
+ g2d.drawString(text.getText(), columnPositions.get(i)+textOffsetX, h);
+ if (text instanceof URLItem) {
+ URL url = ((URLItem)text).getURL();
+ if (url != null) {
+ addLink(url, columnPositions.get(i),ht,columnSizes.get(i),hb-ht);
+ }
+ }
+ }
+ g2d.setClip(clip);
+ if (linesVisible) {
+ for (int i = 0; i < line.size(); i++) {
+ g2d.drawLine(columnPositions.get(i), ht, columnPositions.get(i), hb);
+ }
+ if (isFirstLine() || !linesPrevVisible) {
+ g2d.drawLine(0, ht, stream.contentWidth, ht);
+ linesPrevVisible = true;
+ }
+ g2d.drawLine(stream.contentWidth, ht, stream.contentWidth, hb);
+ g2d.drawLine(0, hb, stream.contentWidth, hb);
+ }
+ currentLine++;
+ page.currentLine++;
+ page.availableLines--;
+ page.currentPixel += getLineHeight();
+ } else {
+ PositionedRow row = _getRow2(line);
+ if (stream.contentHeight-page.currentPixel < row.reservedSpace) {
+ stream.nextPage();
+ page = getCurrentPage();
+ g2d = page.g2d;
+ writeHeader();
+ row = _getRow2(line);
+ }
+ row.render(g2d);
+ currentLine+= row.realLines;
+ page.currentLine+= row.realLines;
+ page.currentPixel += row.reservedSpace;
+ page.estimateAvailableLines();
+ }
+
+ stream.checkNextPage();
+ return new PDFTableRow();
+ }
+
+ void writeLine(String line) throws Exception{
+ writeLine(line, 0);
+ }
+
+ void writeLine(TextItem line) throws Exception{
+ writeLine(line, 0);
+ }
+
+ private void writeHeader() throws Exception{
+ if (headerVisible) {
+ TextSize s = currentTextSize;
+ setTextSize(TextSize.MEDIUM);
+ if (title != null) {
+ boolean b = linesVisible;
+ setLinesVisible(false);
+ writeLine(title);
+ setLinesVisible(b);
+ }
+ _writeRow(columnNames);
+ setTextSize(s);
+ }
+ }
+
+ void writeLine(String line, int x) throws Exception{
+ int h = getTextHeight();
+ int ht = getTopHeight();
+ int hb = getBottomHeight();
+ PDFPage page = getCurrentPage();
+ Graphics2D g2d = page.g2d;
+ g2d.drawString(line, x+textOffsetX, h);
+ if (linesVisible) {
+ if (isFirstLine() || !linesPrevVisible) {
+ g2d.drawLine(0, ht, stream.contentWidth, ht);
+ linesPrevVisible = true;
+ }
+ g2d.drawLine(0, ht, 0, hb);
+ g2d.drawLine(stream.contentWidth, ht, stream.contentWidth, hb);
+ g2d.drawLine(0, hb, stream.contentWidth, hb);
+ }
+ currentLine++;
+ page.currentLine++;
+ page.availableLines--;
+ page.currentPixel += getLineHeight();
+ stream.checkNextPage();
+ }
+
+ void writeLine(TextItem line, int x) throws Exception{
+ int h = getTextHeight();
+ int ht = getTopHeight();
+ int hb = getBottomHeight();
+ PDFPage page = getCurrentPage();
+ Graphics2D g2d = page.g2d;
+ g2d.drawString(line.getText(), x+textOffsetX, h);
+ if (linesVisible) {
+ if (isFirstLine() || !linesPrevVisible) {
+ g2d.drawLine(0, ht, stream.contentWidth, ht);
+ linesPrevVisible = true;
+ }
+ g2d.drawLine(0, ht, 0, hb);
+ g2d.drawLine(stream.contentWidth, ht, stream.contentWidth, hb);
+ g2d.drawLine(0, hb, stream.contentWidth, hb);
+ }
+ if (line instanceof URLItem) {
+ URL url = ((URLItem)line).getURL();
+ if (url != null) {
+ addLink(url, 0,ht,stream.contentWidth,hb-ht);
+ }
+ }
+ currentLine++;
+ page.currentLine++;
+ page.availableLines--;
+ page.currentPixel += getLineHeight();
+ stream.checkNextPage();
+ }
+
+ int getTopHeight() {
+ return getTopHeight(currentLine);
+ }
+
+ int getTopHeight(int line) {
+ return (line-currentLine)*getLineHeight()+getCurrentPage().currentPixel;
+ }
+
+ int getTextHeight() {
+ PDFPage page = getCurrentPage();
+ return page.currentPixel+getLineHeight()-page.fm.getDescent()-textOffsetY;
+ }
+
+ PDFPage getCurrentPage() {
+ return stream.getCurrentPage();
+ }
+
+ int getBottomHeight() {
+ return getBottomHeight(currentLine);
+ }
+
+ protected int getLineHeight() {
+ return getCurrentPage().fm.getHeight()+textOffsetY;
+ }
+
+ private int getBottomHeight(int line) {
+ return (line-currentLine+1)*getLineHeight()+getCurrentPage().currentPixel;
+ }
+
+ public int getAvailableLines() {
+ PDFPage page = getCurrentPage();
+ int contentHeight = stream.contentHeight;
+ int pixelY = page.currentPixel;
+ return (int)Math.floor((contentHeight-pixelY)/getLineHeight());
+ }
+
+ private TextSize currentTextSize = TextSize.SMALL;
+
+ @Override
+ public void setTextSize(TextSize size) {
+ stream.getCurrentPage().setFont(writer.fonts.get(size));
+ currentTextSize = size;
+ }
+
+ @Override
+ public TextSize getTextSize() {
+ return currentTextSize;
+ }
+
+ private PositionedRow _getRow(List<String> line) {
+ PositionedRow row = new PositionedRow();
+ int h = getTextHeight();
+ int realLines = 1;
+ int reservedSpace = 0;
+ List<List<PositionedText>> cells = new ArrayList<List<PositionedText>>(line.size());
+ if (line.size() > 1) {
+ for (int i = 0; i < line.size(); i++) {
+ String text = line.get(i);
+ int availableSize = columnSizes.get(i)-textOffsetX;
+ if (text != null && text.length() > 0) {
+ List<PositionedText> pt = getText(text, columnPositions.get(i)+textOffsetX, h,availableSize, columns.get(i).getAlignment());
+ cells.add(pt);
+ reservedSpace = Math.max(reservedSpace, getResevedSpace(pt)+getLineHeight());
+ realLines = Math.max(realLines, getLineSpace(pt));
+ } else {
+ cells.add(Collections.<PositionedText> emptyList());
+ reservedSpace = Math.max(reservedSpace, getLineHeight());
+ }
+ }
+ } else {
+ String text = line.get(0);
+ int availableSize = stream.contentWidth;
+ if (text != null && text.length() > 0) {
+ List<PositionedText> pt = getText(text, textOffsetX, h,availableSize, columns.get(0).getAlignment());
+ cells.add(pt);
+ reservedSpace = Math.max(reservedSpace, getResevedSpace(pt)+getLineHeight());
+ realLines = Math.max(realLines, getLineSpace(pt));
+ } else {
+ cells.add(Collections.<PositionedText> emptyList());
+ reservedSpace = Math.max(reservedSpace, getLineHeight());
+ }
+ }
+ row.reservedSpace = reservedSpace;
+ row.startLine = currentLine;
+ row.realLines = realLines;
+ row.cells = cells;
+ return row;
+ }
+
+ private PositionedRow _getRow2(List<TextItem> line) {
+ PositionedRow row = new PositionedRow();
+ int h = getTextHeight();
+ int realLines = 1;
+ int reservedSpace = 0;
+ row.cells = new ArrayList<List<PositionedText>>(line.size());
+ row.urls = new ArrayList<URL>();
+ if (line.size() > 1) {
+ for (int i = 0; i < line.size(); i++) {
+ TextItem item =line.get(i);
+
+ int availableSize = columnSizes.get(i)-textOffsetX;
+ if (item != null && item.getText().length() > 0) {
+ String text = item.getText();
+ List<PositionedText> pt = getText(text, columnPositions.get(i)+textOffsetX, h,availableSize, columns.get(i).getAlignment());
+ row.cells.add(pt);
+ reservedSpace = Math.max(reservedSpace, getResevedSpace(pt)+getLineHeight());
+ realLines = Math.max(realLines, getLineSpace(pt));
+ } else {
+ row.cells.add(Collections.<PositionedText> emptyList());
+ reservedSpace = Math.max(reservedSpace, getLineHeight());
+ }
+ if (item instanceof URLItem) {
+ row.urls.add(((URLItem)item).getURL());
+ } else {
+ row.urls.add(null);
+ }
+ }
+
+ } else {
+ String text = line.get(0).getText();
+ int availableSize = stream.contentWidth;
+ if (text != null && text.length() > 0) {
+ List<PositionedText> pt = getText(text, textOffsetX, h,availableSize, columns.get(0).getAlignment());
+ row.cells.add(pt);
+ reservedSpace = Math.max(reservedSpace, getResevedSpace(pt)+getLineHeight());
+ realLines = Math.max(realLines, getLineSpace(pt));
+ } else {
+ row.cells.add(Collections.<PositionedText> emptyList());
+ reservedSpace = Math.max(reservedSpace, getLineHeight());
+ }
+ }
+ row.reservedSpace = reservedSpace;
+ row.startLine = currentLine;
+ row.realLines = realLines;
+ return row;
+ }
+
+ private int getResevedSpace(List<PositionedText> pt) {
+ float sy = pt.get(0).drawPosY;
+ float ey = pt.get(pt.size()-1).drawPosY;
+ return (int)Math.ceil(ey-sy);
+ }
+ /**
+ * 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.
+ * @param pt
+ * @return
+ */
+ private int getLineSpace(List<PositionedText> pt) {
+ if (pt.size() < 2)
+ return 1;
+
+ return (int)(getResevedSpace(pt)/getLineHeight())+1;
+ }
+
+ private class PositionedRow {
+ int startLine;
+ int realLines;
+ int reservedSpace;
+ List<List<PositionedText>> cells;
+ List<URL> urls;
+
+ void render(Graphics2D g) {
+ int ht = getTopHeight(startLine);
+ int hb = ht +reservedSpace;
+ if (cells.size() > 0) {
+ for (int i = 0; i < cells.size(); i++) {
+ List<PositionedText> ptl = cells.get(i);
+ for (PositionedText pt : ptl)
+ pt.render(g);
+ if (urls != null) {
+ URL url = urls.get(i);
+ if (url != null) {
+ addLink(url, columnPositions.get(i),ht,columnSizes.get(i),hb-ht);
+ }
+ }
+ }
+ }
+ if (linesVisible) {
+ if (cells.size() > 0) {
+ for (int i = 0; i < cells.size(); i++) {
+ g.drawLine(columnPositions.get(i), ht, columnPositions.get(i), hb);
+ }
+ } else {
+ g.drawLine(columnPositions.get(0), ht, columnPositions.get(0), hb);
+ }
+ if (isFirstLine() || !linesPrevVisible) {
+ g.drawLine(0, ht, stream.contentWidth, ht);
+ linesPrevVisible = true;
+ }
+ g.drawLine(stream.contentWidth, ht, stream.contentWidth, hb);
+ g.drawLine(0, hb, stream.contentWidth, hb);
+
+ }
+ }
+ }
+
+ private void addLink(URL url, int x, int y, int w, int h) {
+ PDFPage page = getCurrentPage();
+ float fx = +page.stream.marginLeft + x;
+ float fy = -page.stream.marginTop + page.template.getHeight() - y;
+ page.template.setAction(new PdfAction(url), fx, fy-h, fx+w, fy);
+ }
+
+ private List<PositionedText> getText(String text, int x, int y, int cellWidth, Alignment alignment) {
+ List<PositionedText> result = new ArrayList<PositionedText>();
+ Hashtable<TextAttribute, Object> map = new Hashtable<TextAttribute, Object>();
+ PDFPage page = getCurrentPage();
+ Font font = page.getFont();
+ for (TextAttribute a : font.getAttributes().keySet()) {
+ Object v = font.getAttributes().get(a);
+ if (v != null)
+ map.put(a, v);
+ }
+// map.putAll(font.getAttributes());
+ map.put(TextAttribute.FOREGROUND, Color.black);
+
+ AttributedString attributedText = new AttributedString( text, map);
+
+ AttributedCharacterIterator paragraph = attributedText.getIterator();
+ int paragraphStart = paragraph.getBeginIndex();
+ int paragraphEnd = paragraph.getEndIndex();
+ LineBreakMeasurer lineMeasurer = new LineBreakMeasurer(paragraph, page.frc);
+ lineMeasurer.setPosition(paragraphStart);
+
+ // Get lines until the entire paragraph has been displayed.
+ int next, limit, charat, position = 0;
+ int drawPosY = y;
+ while ((position = lineMeasurer.getPosition()) < paragraphEnd) {
+
+ // Find possible line break and set it as a limit to the next layout
+ next = lineMeasurer.nextOffset(cellWidth);
+ limit = next;
+ charat = text.indexOf(System.getProperty("line.separator"),position+1);
+ if(charat < next && charat != -1){
+ limit = charat;
+ }
+
+ // Retrieve next layout. A cleverer program would also cache
+ // these layouts until the component is re-sized.
+ TextLayout layout = lineMeasurer.nextLayout(cellWidth, limit, false);
+
+ // Compute pen x position. If the paragraph is right-to-left we
+ // will align the TextLayouts to the right edge of the panel.
+ // Note: this won't occur for the English text in this sample.
+ // Note: drawPosX is always where the LEFT of the text is placed.
+ float drawPosX = 0;
+ switch (alignment) {
+ case LEFT:
+ drawPosX = layout.isLeftToRight() ? 0 : cellWidth - layout.getAdvance();
+ break;
+ case CENTER:
+ drawPosX = (cellWidth - layout.getAdvance()) / 2;
+ break;
+ case RIGHT:
+ drawPosX = layout.isLeftToRight() ? cellWidth - layout.getAdvance() : 0;
+ break;
+ }
+
+ drawPosX += x;
+
+ // If text has been forced to vertical, align it to center
+// if(breakWidth < textAreaWidth) {
+// float centerCorrection = layout.isLeftToRight() ?
+// (float) (layout.getAdvance() / 2) :
+// -1 * (float) (layout.getAdvance() / 2);
+// drawPosX = textAreaWidth / 2 - centerCorrection;
+// }
+
+ // Stop drawing if the text won't fit
+// if (breakHeight < drawPosY + layout.getDescent() + layout.getLeading()) {
+// break;
+// }
+
+// drawPosY += layout.getAscent();
+
+ // Add TextLayout at (drawPosX, drawPosY).
+ result.add(new PositionedText(drawPosX, drawPosY, layout));
+
+ // Move y-coordinate in preparation for next layout.
+ //drawPosY += layout.getDescent() + layout.getLeading();
+ drawPosY += layout.getDescent() + layout.getLeading() + layout.getAscent();
+// drawPosY += getLineHeight();
+ }
+ return result;
+ }
+
+ class PositionedText {
+ float drawPosX;
+ float drawPosY;
+ TextLayout layout;
+
+ public PositionedText(float drawPosX, float drawPosY, TextLayout layout) {
+ this.drawPosX = drawPosX;
+ this.drawPosY = drawPosY;
+ this.layout = layout;
+ }
+
+ public void render(Graphics2D g) {
+ layout.draw(g, drawPosX, drawPosY);
+ }
+ }
+}