]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.document.linking.ui/src/org/simantics/document/linking/report/pdf/PDFTable.java
Externalize org.simantics.document.linking.ui
[simantics/platform.git] / bundles / org.simantics.document.linking.ui / src / org / simantics / document / linking / report / pdf / PDFTable.java
1 package org.simantics.document.linking.report.pdf;
2
3 import java.awt.Color;
4 import java.awt.Font;
5 import java.awt.Graphics2D;
6 import java.awt.Shape;
7 import java.awt.font.LineBreakMeasurer;
8 import java.awt.font.TextAttribute;
9 import java.awt.font.TextLayout;
10 import java.net.URL;
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;
17
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;
25
26 import com.lowagie.text.pdf.PdfAction;
27
28
29 public class PDFTable implements Table, PDFElement {
30         PDFDocument writer;
31         PDFPageStream stream;
32         PDFPage startPage;
33         
34         int currentLine = 0;
35         
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>();
40         
41         TextItem title = null;
42         
43         boolean headerVisible = true;
44         private boolean linesVisible = true;
45         private boolean linesPrevVisible = true;
46         boolean clipText = false;
47         
48         int textOffsetX = 2;
49         int textOffsetY = 2;
50         
51         public PDFTable(PDFDocument writer, PDFPageStream stream) {
52                 this.writer = writer;
53                 this.stream = stream;
54                 this.startPage = stream.getCurrentPage();
55                 
56         }
57         
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);
64                 
65                 updateColumnPositions();
66         }
67         
68         
69         @Override
70         public PDFPage getPage() {
71                 return startPage;
72         }
73         
74         /* (non-Javadoc)
75          * @see org.simantics.document.linking.report.Table#addColumn(java.lang.String, double)
76          */
77         @Override
78         public TableColumn addColumn(String name, double width) {
79                 TableColumn tc = new TableColumn(name, width);
80                 columns.add(tc);
81                 columnNames.add(name);
82                 
83                 updateColumnPositions();
84                 return tc;
85         }
86         
87         private void updateColumnPositions() {
88                 int pos = 0;
89                 columnSizes.clear();
90                 columnPositions.clear();
91                 for (TableColumn c : columns) {
92                         int size = (int)(c.getWidth()*stream.getContentWidth());
93                         columnSizes.add(size);
94                         columnPositions.add(pos);
95                         pos+=size;
96                 }
97         }
98         
99         @Override
100         public List<TableColumn> getColumns() {
101                 return columns;
102         }
103         
104         @Override
105         public boolean isLinesVisible() {
106                 return linesVisible;
107         }
108         
109         @Override
110         public void setLinesVisible(boolean b) {
111                 if (this.linesVisible == b)
112                         return;
113                 this.linesPrevVisible = linesVisible;
114                 this.linesVisible = b;
115         }
116         
117         @Override
118         public boolean isHeaderVisible() {
119                 return headerVisible;
120         }
121         
122         @Override
123         public void setHeaderVisible(boolean b) {
124                 this.headerVisible = b;
125         }
126         
127         private boolean isFirstLine() {
128                 return currentLine == 0 || stream.getCurrentPage().currentLine == 1;
129         }
130         
131         @Override
132         public void setTitle(String title) {
133                 try {
134                         this.title = writer.newItem(TextItem.class);
135                         this.title.setText(title);
136                 } catch (Exception e) {
137                         
138                 }
139         }
140         
141         @Override
142         public void setTitle(TextItem title){
143                 this.title = title;
144         }
145         
146         /* (non-Javadoc)
147          * @see org.simantics.document.linking.report.Table#writeRow(java.lang.String[])
148          */
149         @Override
150         public TableRow writeRow(String... line) throws Exception{
151                 List<String> list = new ArrayList<String>(line.length);
152                 for (String s : line)
153                         list.add(s);
154                 return writeRow(list);
155         }
156         
157         /* (non-Javadoc)
158          * @see org.simantics.document.linking.report.Table#writeRow(java.util.List)
159          */
160         @Override
161         public TableRow writeRow(List<String> line) throws Exception{
162                 if (isFirstLine())
163                         writeHeader();
164                 return _writeRow(line);
165         }
166         
167         @Override
168         public TableRow writeRowItem(TextItem... line) throws Exception {
169                 List<TextItem> list = new ArrayList<TextItem>(line.length);
170                 for (TextItem s : line)
171                         list.add(s);
172                 return writeRowItem(list);
173         }
174         
175         @Override
176         public TableRow writeRowItem(List<TextItem> line) throws Exception {
177                 if (isFirstLine())
178                         writeHeader();
179                 return _writeRow2(line);
180         }
181         
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();
189                 
190                 if (clipText) {
191                         for (int i = 0; i < line.size(); i++) {
192                                 if (line.get(i) == null)
193                                         continue;
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);
196                         }
197                         g2d.setClip(clip);
198                         if (linesVisible) {
199                                 for (int i = 0; i < line.size(); i++) {
200                                         g2d.drawLine(columnPositions.get(i), ht, columnPositions.get(i), hb);
201                                 }
202                                 if (isFirstLine() || !linesPrevVisible) {
203                                         g2d.drawLine(0, ht, stream.contentWidth, ht);
204                                         linesPrevVisible = true;
205                                 }
206                                 g2d.drawLine(stream.contentWidth, ht, stream.contentWidth, hb);
207                                 g2d.drawLine(0, hb, stream.contentWidth, hb);
208                         }
209                         currentLine++;
210                         page.currentLine++;
211                         page.availableLines--;
212                         page.currentPixel += getLineHeight();
213                 } else {
214                         PositionedRow row = _getRow(line);
215                         if (stream.contentHeight-page.currentPixel < row.reservedSpace) {
216                                 stream.nextPage();
217                                 page = getCurrentPage();
218                                 g2d = page.g2d;
219                                 writeHeader();
220                                 row = _getRow(line);
221                         }
222                         row.render(g2d);
223                         currentLine+= row.realLines;
224                         page.currentLine+= row.realLines;
225                         page.currentPixel += row.reservedSpace;
226                         page.estimateAvailableLines();
227                 }
228                 
229                 stream.checkNextPage();
230                 return new PDFTableRow();
231         }
232         
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();
240                 
241                 if (clipText) {
242                         for (int i = 0; i < line.size(); i++) {
243                                 TextItem text = line.get(i);
244                                 if (text == null)
245                                         continue;
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();
250                                         if (url != null) {
251                                                 addLink(url, columnPositions.get(i),ht,columnSizes.get(i),hb-ht);
252                                         }
253                                 }
254                         }
255                         g2d.setClip(clip);
256                         if (linesVisible) {
257                                 for (int i = 0; i < line.size(); i++) {
258                                         g2d.drawLine(columnPositions.get(i), ht, columnPositions.get(i), hb);
259                                 }
260                                 if (isFirstLine() || !linesPrevVisible) {
261                                         g2d.drawLine(0, ht, stream.contentWidth, ht);
262                                         linesPrevVisible = true;
263                                 }
264                                 g2d.drawLine(stream.contentWidth, ht, stream.contentWidth, hb);
265                                 g2d.drawLine(0, hb, stream.contentWidth, hb);
266                         }
267                         currentLine++;
268                         page.currentLine++;
269                         page.availableLines--;
270                         page.currentPixel += getLineHeight();
271                 } else {
272                         PositionedRow row = _getRow2(line);
273                         if (stream.contentHeight-page.currentPixel < row.reservedSpace) {
274                                 stream.nextPage();
275                                 page = getCurrentPage();
276                                 g2d = page.g2d;
277                                 writeHeader();
278                                 row = _getRow2(line);
279                         }
280                         row.render(g2d);
281                         currentLine+= row.realLines;
282                         page.currentLine+= row.realLines;
283                         page.currentPixel += row.reservedSpace;
284                         page.estimateAvailableLines();
285                 }
286                 
287                 stream.checkNextPage();
288                 return new PDFTableRow();
289         }
290         
291         void writeLine(String line) throws Exception{
292                 writeLine(line, 0);
293         }
294         
295         void writeLine(TextItem line) throws Exception{
296                 writeLine(line, 0);
297         }
298         
299         private void writeHeader() throws Exception{
300                 if (headerVisible) {
301                         TextSize s = currentTextSize;
302                         setTextSize(TextSize.MEDIUM);
303                         if (title != null) {
304                                 boolean b = linesVisible;
305                                 setLinesVisible(false);
306                                 writeLine(title);
307                                 setLinesVisible(b);
308                         }
309                         _writeRow(columnNames);
310                         setTextSize(s);
311                 }
312         }
313         
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);
321                 if (linesVisible) {
322                         if (isFirstLine() || !linesPrevVisible) {
323                                 g2d.drawLine(0, ht, stream.contentWidth, ht);
324                                 linesPrevVisible = true;
325                         }
326                         g2d.drawLine(0, ht, 0, hb);
327                         g2d.drawLine(stream.contentWidth, ht, stream.contentWidth, hb);
328                         g2d.drawLine(0, hb, stream.contentWidth, hb);
329                 }
330                 currentLine++;
331                 page.currentLine++;
332                 page.availableLines--;
333                 page.currentPixel += getLineHeight();
334                 stream.checkNextPage();
335         }
336         
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);
344                 if (linesVisible) {
345                         if (isFirstLine() || !linesPrevVisible) {
346                                 g2d.drawLine(0, ht, stream.contentWidth, ht);
347                                 linesPrevVisible = true;
348                         }
349                         g2d.drawLine(0, ht, 0, hb);
350                         g2d.drawLine(stream.contentWidth, ht, stream.contentWidth, hb);
351                         g2d.drawLine(0, hb, stream.contentWidth, hb);
352                 }
353                 if (line instanceof URLItem) {
354                         URL url = ((URLItem)line).getURL();
355                         if (url != null) {
356                                 addLink(url, 0,ht,stream.contentWidth,hb-ht);
357                         }
358                 }
359                 currentLine++;
360                 page.currentLine++;
361                 page.availableLines--;
362                 page.currentPixel += getLineHeight();
363                 stream.checkNextPage();
364         }
365         
366         int getTopHeight() {
367                 return getTopHeight(currentLine);
368         }
369         
370         int getTopHeight(int line) {
371                 return (line-currentLine)*getLineHeight()+getCurrentPage().currentPixel;
372         }
373         
374         int getTextHeight() {
375                 PDFPage page = getCurrentPage();
376                 return page.currentPixel+getLineHeight()-page.fm.getDescent()-textOffsetY;
377         }
378         
379         PDFPage getCurrentPage() {
380                 return stream.getCurrentPage();
381         }
382         
383         int getBottomHeight() {
384                 return getBottomHeight(currentLine);
385         }
386         
387         protected int getLineHeight() {
388                 return getCurrentPage().fm.getHeight()+textOffsetY;
389         }
390         
391         private int getBottomHeight(int line) {
392                 return (line-currentLine+1)*getLineHeight()+getCurrentPage().currentPixel;
393         }
394         
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());
400         }
401         
402         private TextSize currentTextSize = TextSize.SMALL;
403         
404         @Override
405         public void setTextSize(TextSize size) {
406                 stream.getCurrentPage().setFont(writer.fonts.get(size));
407                 currentTextSize = size;
408         }
409         
410         @Override
411         public TextSize getTextSize() {
412                 return currentTextSize;
413         }
414         
415         private PositionedRow _getRow(List<String> line) {
416                 PositionedRow row = new PositionedRow();
417                 int h = getTextHeight();
418                 int realLines = 1;
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());
427                                         cells.add(pt);
428                                         reservedSpace = Math.max(reservedSpace, getResevedSpace(pt)+getLineHeight());
429                                         realLines = Math.max(realLines, getLineSpace(pt));
430                                 } else {
431                                         cells.add(Collections.<PositionedText> emptyList());
432                                         reservedSpace = Math.max(reservedSpace, getLineHeight());
433                                 }
434                         }
435                 } else {
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());
440                                 cells.add(pt);
441                                 reservedSpace = Math.max(reservedSpace, getResevedSpace(pt)+getLineHeight());
442                                 realLines = Math.max(realLines, getLineSpace(pt));
443                         } else {
444                                 cells.add(Collections.<PositionedText> emptyList());
445                                 reservedSpace = Math.max(reservedSpace, getLineHeight());
446                         }
447                 }
448                 row.reservedSpace = reservedSpace;
449                 row.startLine = currentLine;
450                 row.realLines = realLines;
451                 row.cells = cells;
452                 return row;
453         }
454         
455         private PositionedRow _getRow2(List<TextItem> line) {
456                 PositionedRow row = new PositionedRow();
457                 int h = getTextHeight();
458                 int realLines = 1;
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); 
465                                 
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());
470                                         row.cells.add(pt);
471                                         reservedSpace = Math.max(reservedSpace, getResevedSpace(pt)+getLineHeight());
472                                         realLines = Math.max(realLines, getLineSpace(pt));
473                                 } else {
474                                         row.cells.add(Collections.<PositionedText> emptyList());
475                                         reservedSpace = Math.max(reservedSpace, getLineHeight());
476                                 }
477                                 if (item instanceof URLItem) {
478                                         row.urls.add(((URLItem)item).getURL());
479                                 } else {
480                                         row.urls.add(null);
481                                 }
482                         }
483                 
484                 } else {
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());
489                                 row.cells.add(pt);
490                                 reservedSpace = Math.max(reservedSpace, getResevedSpace(pt)+getLineHeight());
491                                 realLines = Math.max(realLines, getLineSpace(pt));
492                         } else {
493                                 row.cells.add(Collections.<PositionedText> emptyList());
494                                 reservedSpace = Math.max(reservedSpace, getLineHeight());
495                         }
496                 }
497                 row.reservedSpace = reservedSpace;
498                 row.startLine = currentLine;
499                 row.realLines = realLines;
500                 return row;
501         }
502         
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);
507         }
508         /**
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. 
510          * @param pt
511          * @return
512          */
513         private int getLineSpace(List<PositionedText> pt) {
514                 if (pt.size() < 2)
515                         return 1;
516                 
517                 return (int)(getResevedSpace(pt)/getLineHeight())+1;
518         }
519         
520         private class PositionedRow {
521                 int startLine;
522                 int realLines;
523                 int reservedSpace;
524                 List<List<PositionedText>> cells;
525                 List<URL> urls;
526                 
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)
534                                                 pt.render(g);
535                                         if (urls != null) {
536                                                 URL url = urls.get(i);
537                                                 if (url != null) {
538                                                         addLink(url, columnPositions.get(i),ht,columnSizes.get(i),hb-ht);
539                                                 }
540                                         }
541                                 }
542                         } 
543                         if (linesVisible) {
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);
547                                         }
548                                 } else {
549                                         g.drawLine(columnPositions.get(0), ht, columnPositions.get(0), hb);
550                                 }
551                                 if (isFirstLine() || !linesPrevVisible) {
552                                         g.drawLine(0, ht, stream.contentWidth, ht);
553                                         linesPrevVisible = true;
554                                 }
555                                 g.drawLine(stream.contentWidth, ht, stream.contentWidth, hb);
556                                 g.drawLine(0, hb, stream.contentWidth, hb);
557                                 
558                         }
559                 }
560         }
561         
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);
567         }
568         
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);
576                         if (v != null)
577                                 map.put(a, v);
578                 }
579 //              map.putAll(font.getAttributes());
580                 map.put(TextAttribute.FOREGROUND, Color.black);
581
582                 AttributedString attributedText = new AttributedString( text, map);
583
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);
589                 
590                 // Get lines until the entire paragraph has been displayed.
591         int next, limit, charat, position = 0;
592         int drawPosY = y;
593         while ((position = lineMeasurer.getPosition()) < paragraphEnd) {
594
595             // Find possible line break and set it as a limit to the next layout
596             next = lineMeasurer.nextOffset(cellWidth);
597             limit = next;
598             charat = text.indexOf(System.getProperty("line.separator"),position+1); //$NON-NLS-1$
599             if(charat < next && charat != -1){
600                limit = charat;
601             }
602
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);
606
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.
611                 float drawPosX = 0;
612                 switch (alignment) {
613                         case LEFT: 
614                                 drawPosX = layout.isLeftToRight() ? 0 : cellWidth - layout.getAdvance();
615                                 break;
616                         case CENTER:
617                                 drawPosX = (cellWidth - layout.getAdvance()) / 2;
618                                 break;
619                         case RIGHT:
620                                 drawPosX = layout.isLeftToRight() ? cellWidth - layout.getAdvance() : 0;
621                                 break;
622                 }
623                 
624                 drawPosX += x;
625                 
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;
632 //              }
633
634                 // Stop drawing if the text won't fit
635 //              if (breakHeight < drawPosY + layout.getDescent() + layout.getLeading()) {
636 //                  break;
637 //              }
638
639 //              drawPosY += layout.getAscent();
640
641                 // Add TextLayout at (drawPosX, drawPosY).
642                 result.add(new PositionedText(drawPosX, drawPosY, layout));
643
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();
648             }
649         return result;
650         }
651         
652         class PositionedText {
653         float drawPosX;
654         float drawPosY;
655         TextLayout layout;      
656         
657         public PositionedText(float drawPosX, float drawPosY, TextLayout layout) {
658             this.drawPosX = drawPosX;
659             this.drawPosY = drawPosY;
660             this.layout = layout;
661         }
662         
663         public void render(Graphics2D g) {
664             layout.draw(g, drawPosX, drawPosY);
665         }
666     }
667 }