1 /*
2  * JasperReports - Free Java Reporting Library.
3  * Copyright (C) 2001 - 2019 TIBCO Software Inc. All rights reserved.
4  * http://www.jaspersoft.com
5  *
6  * Unless you have purchased a commercial license agreement from Jaspersoft,
7  * the following license terms apply:
8  *
9  * This program is part of JasperReports.
10  *
11  * JasperReports is free software: you can redistribute it and/or modify
12  * it under the terms of the GNU Lesser General Public License as published by
13  * the Free Software Foundation, either version 3 of the License, or
14  * (at your option) any later version.
15  *
16  * JasperReports is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19  * GNU Lesser General Public License for more details.
20  *
21  * You should have received a copy of the GNU Lesser General Public License
22  * along with JasperReports. If not, see <http://www.gnu.org/licenses/>.
23  */

24 package net.sf.jasperreports.engine.export;
25
26 import java.awt.font.FontRenderContext;
27 import java.awt.font.LineBreakMeasurer;
28 import java.awt.font.TextLayout;
29 import java.text.AttributedCharacterIterator;
30 import java.text.AttributedString;
31 import java.util.ArrayList;
32 import java.util.List;
33 import java.util.StringTokenizer;
34
35 import net.sf.jasperreports.engine.JRParagraph;
36 import net.sf.jasperreports.engine.JRPrintText;
37 import net.sf.jasperreports.engine.JRPropertiesUtil;
38 import net.sf.jasperreports.engine.JasperReportsContext;
39 import net.sf.jasperreports.engine.TabStop;
40 import net.sf.jasperreports.engine.type.HorizontalTextAlignEnum;
41 import net.sf.jasperreports.engine.util.JRStringUtil;
42 import net.sf.jasperreports.engine.util.JRStyledText;
43 import net.sf.jasperreports.engine.util.ParagraphUtil;
44
45
46 /**
47  * @author Teodor Danciu (teodord@users.sourceforge.net)
48  */

49 public abstract class AbstractTextRenderer
50 {
51     public static final FontRenderContext LINE_BREAK_FONT_RENDER_CONTEXT = new FontRenderContext(nulltruetrue);
52
53     protected final JasperReportsContext jasperReportsContext;
54     protected final JRPropertiesUtil propUtil;
55     protected JRPrintText text;
56     protected JRStyledText styledText;
57     protected String allText;
58     protected int x;
59     protected int y;
60     protected int width;
61     protected int height;
62     protected int topPadding;
63     protected int leftPadding;
64     protected int bottomPadding;
65     protected int rightPadding;
66     
67     //protected float formatWidth;
68     protected float verticalAlignOffset;
69     protected float drawPosY;
70     protected float drawPosX;
71     protected float lineHeight;
72     protected boolean isMaxHeightReached;
73     protected boolean isFirstParagraph;
74     protected boolean isLastParagraph;
75     protected List<TabSegment> segments;
76     protected int segmentIndex;
77     protected boolean indentFirstLine;
78     protected boolean justifyLastLine;
79     
80     /**
81      * 
82      *
83     private MaxFontSizeFinder maxFontSizeFinder;
84     
85     /**
86      * 
87      */

88     private final boolean isMinimizePrinterJobSize;
89     protected final boolean ignoreMissingFont;
90     private final boolean defaultIndentFirstLine;
91     private final boolean defaultJustifyLastLine;
92
93     
94     /**
95      * @deprecated Replaced by {@link #AbstractTextRenderer(JasperReportsContext, booleanbooleanbooleanboolean)}.
96      */

97     public AbstractTextRenderer(
98         JasperReportsContext jasperReportsContext,
99         boolean isMinimizePrinterJobSize,
100         boolean ignoreMissingFont
101         )
102     {
103         this(
104             jasperReportsContext,
105             isMinimizePrinterJobSize,
106             ignoreMissingFont,
107             true,
108             false
109             );
110     }
111     
112     
113     /**
114      * 
115      */

116     public AbstractTextRenderer(
117         JasperReportsContext jasperReportsContext,
118         boolean isMinimizePrinterJobSize,
119         boolean ignoreMissingFont,
120         boolean defaultIndentFirstLine,
121         boolean defaultJustifyLastLine
122         )
123     {
124         this.jasperReportsContext = jasperReportsContext;
125         this.propUtil = JRPropertiesUtil.getInstance(jasperReportsContext);
126         this.isMinimizePrinterJobSize = isMinimizePrinterJobSize;
127         this.ignoreMissingFont = ignoreMissingFont;
128         this.defaultIndentFirstLine = defaultIndentFirstLine;
129         this.defaultJustifyLastLine = defaultJustifyLastLine;
130     }
131     
132     
133     /**
134      *
135      */

136     public int getX()
137     {
138         return x;
139     }
140     
141     
142     /**
143      *
144      */

145     public int getY()
146     {
147         return y;
148     }
149     
150     
151     /**
152      *
153      */

154     public int getWidth()
155     {
156         return width;
157     }
158     
159     
160     /**
161      *
162      */

163     public int getHeight()
164     {
165         return height;
166     }
167     
168     
169     /**
170      *
171      */

172     public int getTopPadding()
173     {
174         return topPadding;
175     }
176     
177     
178     /**
179      *
180      */

181     public int getLeftPadding()
182     {
183         return leftPadding;
184     }
185     
186     
187     /**
188      *
189      */

190     public int getBottomPadding()
191     {
192         return bottomPadding;
193     }
194     
195     
196     /**
197      *
198      */

199     public int getRightPadding()
200     {
201         return rightPadding;
202     }
203     
204     
205     /**
206      *
207      */

208     public JRStyledText getStyledText()
209     {
210         return styledText;
211     }
212     
213     
214     /**
215      *
216      */

217     public String getPlainText()
218     {
219         return allText;
220     }
221     
222     
223     /**
224      * 
225      */

226     public void initialize(JRPrintText text, JRStyledText styledText, int offsetX, int offsetY)
227     {
228         this.styledText = styledText;
229         allText = styledText.getText();
230         
231         x = text.getX() + offsetX;
232         y = text.getY() + offsetY;
233         width = text.getWidth();
234         height = text.getHeight();
235         topPadding = text.getLineBox().getTopPadding();
236         leftPadding = text.getLineBox().getLeftPadding();
237         bottomPadding = text.getLineBox().getBottomPadding();
238         rightPadding = text.getLineBox().getRightPadding();
239         
240         switch (text.getRotationValue())
241         {
242             case LEFT :
243             {
244                 y = text.getY() + offsetY + text.getHeight();
245                 width = text.getHeight();
246                 height = text.getWidth();
247                 int tmpPadding = topPadding;
248                 topPadding = leftPadding;
249                 leftPadding = bottomPadding;
250                 bottomPadding = rightPadding;
251                 rightPadding = tmpPadding;
252                 break;
253             }
254             case RIGHT :
255             {
256                 x = text.getX() + offsetX + text.getWidth();
257                 width = text.getHeight();
258                 height = text.getWidth();
259                 int tmpPadding = topPadding;
260                 topPadding = rightPadding;
261                 rightPadding = bottomPadding;
262                 bottomPadding = leftPadding;
263                 leftPadding = tmpPadding;
264                 break;
265             }
266             case UPSIDE_DOWN :
267             {
268                 int tmpPadding = topPadding;
269                 x = text.getX() + offsetX + text.getWidth();
270                 y = text.getY() + offsetY + text.getHeight();
271                 topPadding = bottomPadding;
272                 bottomPadding = tmpPadding;
273                 tmpPadding = leftPadding;
274                 leftPadding = rightPadding;
275                 rightPadding = tmpPadding;
276                 break;
277             }
278             case NONE :
279             default :
280             {
281             }
282         }
283         
284         this.text = text;
285
286         verticalAlignOffset = 0f;
287         switch (text.getVerticalTextAlign())
288         {
289             case BOTTOM :
290             {
291                 verticalAlignOffset = height - topPadding - bottomPadding - text.getTextHeight();
292                 break;
293             }
294             case MIDDLE :
295             {
296                 verticalAlignOffset = (height - topPadding - bottomPadding - text.getTextHeight()) / 2f;
297                 break;
298             }
299             case TOP :
300             case JUSTIFIED :
301             default :
302             {
303                 verticalAlignOffset = 0f;
304             }
305         }
306         
307         indentFirstLine = defaultIndentFirstLine;
308         if (text.getPropertiesMap().containsProperty(JRPrintText.PROPERTY_AWT_INDENT_FIRST_LINE))
309         {
310             indentFirstLine = propUtil.getBooleanProperty(text, JRPrintText.PROPERTY_AWT_INDENT_FIRST_LINE, defaultIndentFirstLine);
311         }
312
313         justifyLastLine = defaultJustifyLastLine;
314         if (text.getPropertiesMap().containsProperty(JRPrintText.PROPERTY_AWT_JUSTIFY_LAST_LINE))
315         {
316             justifyLastLine = propUtil.getBooleanProperty(text, JRPrintText.PROPERTY_AWT_JUSTIFY_LAST_LINE, defaultJustifyLastLine);
317         }
318
319 //        formatWidth = width - leftPadding - rightPadding;
320 //        formatWidth = formatWidth < 0 ? 0 : formatWidth;
321
322         drawPosY = 0;
323         drawPosX = 0;
324     
325         isMaxHeightReached = false;
326         isLastParagraph = false;
327         
328         //maxFontSizeFinder = MaxFontSizeFinder.getInstance(!JRCommonText.MARKUP_NONE.equals(text.getMarkup()));
329     }
330     
331
332     /**
333      * 
334      */

335     public void render()
336     {
337         AttributedCharacterIterator allParagraphs = getAttributedString().getIterator(); 
338
339         int tokenPosition = 0;
340         int prevParagraphStart = 0;
341         String prevParagraphText = null;
342
343         isFirstParagraph = true;
344         
345         StringTokenizer tkzer = new StringTokenizer(allText, "\n"true);
346
347         // text is split into paragraphs, using the newline character as delimiter
348         while(tkzer.hasMoreTokens() && !isMaxHeightReached) 
349         {
350             String token = tkzer.nextToken();
351
352             if ("\n".equals(token))
353             {
354                 renderParagraph(allParagraphs, prevParagraphStart, prevParagraphText);
355
356                 isFirstParagraph = false;
357                 isLastParagraph = !tkzer.hasMoreTokens();
358                 prevParagraphStart = tokenPosition + (tkzer.hasMoreTokens() || tokenPosition == 0 ? 1 : 0);
359                 prevParagraphText = null;
360             }
361             else
362             {
363                 prevParagraphStart = tokenPosition;
364                 prevParagraphText = token;
365             }
366
367             tokenPosition += token.length();
368         }
369
370         if (!isMaxHeightReached && prevParagraphStart < allText.length())
371         {
372             isLastParagraph = true;
373             renderParagraph(allParagraphs, prevParagraphStart, prevParagraphText);
374         }
375     }
376
377
378     /**
379      * 
380      */

381     protected void renderParagraph(
382         AttributedCharacterIterator allParagraphs,
383         int paragraphStart,
384         String paragraphText
385         )
386     {
387         AttributedCharacterIterator paragraph = null;
388         
389         if (paragraphText == null)
390         {
391             paragraphText = " ";
392             paragraph = 
393                 new AttributedString(
394                     paragraphText,
395                     new AttributedString(
396                         allParagraphs, 
397                         paragraphStart, 
398                         paragraphStart + paragraphText.length()
399                         ).getIterator().getAttributes()
400                     ).getIterator();
401         }
402         else
403         {
404             paragraph = 
405                 new AttributedString(
406                     allParagraphs, 
407                     paragraphStart, 
408                     paragraphStart + paragraphText.length()
409                     ).getIterator();
410         }
411
412         List<Integer> tabIndexes = JRStringUtil.getTabIndexes(paragraphText);
413         
414         int currentTab = 0;
415         int lines = 0;
416         float endX = 0;
417         
418         TabStop nextTabStop = null;
419         boolean requireNextWord = false;
420     
421         LineBreakMeasurer lineMeasurer = new LineBreakMeasurer(paragraph, getFontRenderContext());//grx.getFontRenderContext()
422
423         // the paragraph is rendered one line at a time
424         while (lineMeasurer.getPosition() < paragraph.getEndIndex() && !isMaxHeightReached)
425         {
426             boolean lineComplete = false;
427
428             float maxAscent = 0;
429             float maxDescent = 0;
430             float maxLeading = 0;
431             
432             // each line is split into segments, using the tab character as delimiter
433             segments = new ArrayList<TabSegment>(1);
434
435             TabSegment oldSegment = null;
436             TabSegment crtSegment = null;
437
438             // splitting the current line into tab segments
439             while (!lineComplete)
440             {
441                 // the current segment limit is either the next tab character or the paragraph end 
442                 int tabIndexOrEndIndex = (tabIndexes == null || currentTab >= tabIndexes.size() ? paragraph.getEndIndex() : tabIndexes.get(currentTab) + 1); // this +1 here means
443                 // that each segment would contain its terminal tab character, except the last segment which ends where the paragraph ends;
444                 // the tab character at the end of the segment, although it is not actually rendered, it still causes the layout.getAdvance() to equal layout.getVisibleAdvance()
445                 // meaning that any white spaces before the tab are not considered trailing spaces, so they contribute to segment width and thus impact segment text alignment
446                 
447                 int firstLineIndent = lineMeasurer.getPosition() == 0 ? text.getParagraph().getFirstLineIndent() : 0;
448                 
449                 if (
450                     firstLineIndent != 0
451                     && (isFirstParagraph && !indentFirstLine)
452                     )
453                 {
454                     firstLineIndent = 0;
455                 }
456                 
457                 float startX = firstLineIndent + leftPadding;
458                 endX = width - text.getParagraph().getRightIndent() - rightPadding;
459                 endX = endX < startX ? startX : endX;
460                 //formatWidth = endX - startX;
461                 //formatWidth = endX;
462                 
463                 int startIndex = lineMeasurer.getPosition();
464
465                 float rightX = 0;
466
467                 if (segments.size() == 0)
468                 {
469                     rightX = startX;
470                     //nextTabStop = nextTabStop;
471                 }
472                 else
473                 {
474                     rightX = oldSegment.rightX;
475                     nextTabStop = ParagraphUtil.getNextTabStop(text.getParagraph(), endX, rightX);
476                 }
477
478                 //float availableWidth = formatWidth - ParagraphUtil.getSegmentOffset(nextTabStop, rightX); // nextTabStop can be null here; and that's OK
479                 float availableWidth = endX - text.getParagraph().getLeftIndent() - ParagraphUtil.getSegmentOffset(nextTabStop, rightX); // nextTabStop can be null here; and that's OK
480                 
481                 // creating a text layout object for each tab segment 
482                 TextLayout layout = 
483                     lineMeasurer.nextLayout(
484                         availableWidth,
485                         tabIndexOrEndIndex,
486                         requireNextWord
487                         );
488                 
489                 if (layout != null)
490                 {
491                      AttributedString tmpText = 
492                         new AttributedString(
493                             paragraph, 
494                             startIndex, 
495                             startIndex + layout.getCharacterCount()
496                             );
497                      
498                     if (isMinimizePrinterJobSize)
499                     {
500                         //eugene fix - start
501                         layout = new TextLayout(tmpText.getIterator(), getFontRenderContext());
502                         //eugene fix - end
503                     }
504         
505                     if (
506                         text.getHorizontalTextAlign() == HorizontalTextAlignEnum.JUSTIFIED
507                         && (lineMeasurer.getPosition() < paragraph.getEndIndex() || (isLastParagraph && justifyLastLine))
508                         )
509                     {
510                         layout = layout.getJustifiedLayout(availableWidth);
511                     }
512                     
513                     maxAscent = Math.max(maxAscent, layout.getAscent());
514                     maxDescent = Math.max(maxDescent, layout.getDescent());
515                     maxLeading = Math.max(maxLeading, layout.getLeading());
516
517                     //creating the current segment
518                     crtSegment = new TabSegment();
519                     crtSegment.layout = layout;
520                     crtSegment.as = tmpText;
521                     crtSegment.text = paragraphText.substring(startIndex, startIndex + layout.getCharacterCount());
522                     crtSegment.isLastLine = lineMeasurer.getPosition() == paragraph.getEndIndex();
523
524                     // using layout.getVisibleAdvance() here means trailing white space characters at the end of the line do not contribute to line width,
525                     // which is important when aligning the line of text, to match how text alignment works in PDF, DOCX and other formats;
526                     // unlike entire lines of text which might end up with white space characters and are thus considered trailing spaces, 
527                     // segments separated by tab character contain the tab character as last character and any white space character preceding the tab are not 
528                     // considered trailing spaces; they contribute to the segment width and impact segment alignment because layout.getAvance() equals layout.getVisibleAdvance()
529                     // in their case
530                     
531                     float advance = layout.getVisibleAdvance();
532                     //float advance = layout.getAdvance();
533                     float leftX = ParagraphUtil.getLeftX(nextTabStop, advance); // nextTabStop can be null here; and that's OK
534                     if (rightX > leftX)
535                     {
536                         crtSegment.leftX = rightX;
537                         crtSegment.rightX = rightX + advance;
538                     }
539                     else
540                     {
541                         crtSegment.leftX = leftX;
542                         // we need this special tab stop based utility call because adding the advance to leftX causes rounding issues
543                         crtSegment.rightX = ParagraphUtil.getRightX(nextTabStop, advance); // nextTabStop can be null here; and that's OK
544                     }
545
546                     segments.add(crtSegment);
547                 }
548                 
549                 requireNextWord = true;
550
551                 if (lineMeasurer.getPosition() == tabIndexOrEndIndex)
552                 {
553                     // the segment limit was a tab; going to the next tab
554                     currentTab++;
555                 }
556
557                 if (lineMeasurer.getPosition() == paragraph.getEndIndex())
558                 {
559                     // the segment limit was the paragraph end; line completed and next line should start at normal zero x offset
560                     lineComplete = true;
561                     nextTabStop = null;
562                 }
563                 else
564                 {
565                     // there is paragraph text remaining 
566                     if (lineMeasurer.getPosition() == tabIndexOrEndIndex)
567                     {
568                         // the segment limit was a tab
569                         if (crtSegment.rightX >= ParagraphUtil.getLastTabStop(text.getParagraph(), endX).getPosition())
570                         {
571                             // current segment stretches out beyond the last tab stop; line complete
572                             lineComplete = true;
573                             // next line should should start at first tab stop indent
574                             nextTabStop = ParagraphUtil.getFirstTabStop(text.getParagraph(), endX);
575                         }
576 //                        else
577 //                        {
578 //                            //nothing; this leaves lineComplete=false
579 //                        }
580                     }
581                     else
582                     {
583                         // the segment did not fit entirely
584                         lineComplete = true;
585                         if (layout == null)
586                         {
587                             // nothing fitted; next line should start at first tab stop indent
588                             if (nextTabStop.getPosition() == ParagraphUtil.getFirstTabStop(text.getParagraph(), endX).getPosition())//FIXMETAB check based on segments.size()
589                             {
590                                 // at second attempt we give up to avoid infinite loop
591                                 nextTabStop = null;
592                                 requireNextWord = false;
593                                 
594                                 //provide dummy maxFontSize because it is used for the line height of this empty line when attempting drawing below
595                                  AttributedString tmpText = 
596                                     new AttributedString(
597                                         paragraph, 
598                                         startIndex, 
599                                         startIndex + 1
600                                         );
601                                  LineBreakMeasurer lbm = new LineBreakMeasurer(tmpText.getIterator(), getFontRenderContext());
602                                  TextLayout tlyt = lbm.nextLayout(100);
603                                 maxAscent = tlyt.getAscent();
604                                 maxDescent = tlyt.getDescent();
605                                 maxLeading = tlyt.getLeading();
606                             }
607                             else
608                             {
609                                 nextTabStop = ParagraphUtil.getFirstTabStop(text.getParagraph(), endX);
610                             }
611                         }
612                         else
613                         {
614                             // something fitted
615                             nextTabStop = null;
616                             requireNextWord = false;
617                         }
618                     }
619                 }
620
621                 oldSegment = crtSegment;
622             }
623
624             lineHeight = getLineHeight(paragraphStart == 0 && lines == 0, text.getParagraph(), maxLeading, maxAscent);// + maxDescent;
625             
626             if (paragraphStart == 0 && lines == 0)
627             //if (lines == 0) //FIXMEPARA
628             {
629                 lineHeight +=  text.getParagraph().getSpacingBefore();
630             }
631
632             if (drawPosY + lineHeight <= text.getTextHeight())
633             {
634                 lines++;
635                 
636                 drawPosY += lineHeight;
637                 
638                 float lastRightX = (segments == null || segments.size() == 0 ? 0 : segments.get(segments.size() - 1).rightX);
639                 
640                 // now iterate through segments and draw their layouts
641                 for (segmentIndex = 0; segmentIndex < segments.size(); segmentIndex++)
642                 {
643                     TabSegment segment = segments.get(segmentIndex);
644                     TextLayout layout = segment.layout;
645
646                     switch (text.getHorizontalTextAlign())
647                     {
648                         case JUSTIFIED :
649                         {
650                             if (layout.isLeftToRight())
651                             {
652                                 drawPosX = text.getParagraph().getLeftIndent() + segment.leftX;
653                             }
654                             else
655                             {
656                                 drawPosX = (endX - lastRightX + segment.leftX);
657                             }
658     
659                             break;
660                         }
661                         case RIGHT ://FIXMETAB RTL writings
662                         {
663                             drawPosX = (endX - lastRightX + segment.leftX);
664                             break;
665                         }
666                         case CENTER :
667                         {
668                             drawPosX = ((endX - lastRightX) / 2) + segment.leftX; 
669                             break;
670                         }
671                         case LEFT :
672                         default :
673                         {
674                             drawPosX = text.getParagraph().getLeftIndent() + segment.leftX;
675                         }
676                     }
677
678                     /*   */
679                     draw();
680                 }
681                 
682                 drawPosY += maxDescent;
683                 
684 //                if (lineMeasurer.getPosition() == paragraph.getEndIndex()) //FIXMEPARA
685 //                {
686 //                    drawPosY += text.getParagraph().getSpacingAfter();
687 //                }
688             }
689             else
690             {
691                 isMaxHeightReached = true;
692             }
693         }
694     }
695     
696     /**
697      * 
698      */

699     protected AttributedString getAttributedString()
700     {
701         return styledText.getAwtAttributedString(jasperReportsContext, ignoreMissingFont);
702     }
703
704     /**
705      * 
706      */

707     public abstract void draw();
708
709     /**
710      * 
711      */

712     public static float getLineHeight(boolean isFirstLine, JRParagraph paragraph, float maxLeading, float maxAscent)
713     {
714         float lineHeight = 0;
715
716         switch(paragraph.getLineSpacing())
717         {
718             case SINGLE:
719             default :
720             {
721                 lineHeight = maxLeading + 1f * maxAscent;
722                 break;
723             }
724             case ONE_AND_HALF:
725             {
726                 if (isFirstLine)
727                 {
728                     lineHeight = maxLeading + 1f * maxAscent;
729                 }
730                 else
731                 {
732                     lineHeight = maxLeading + 1.5f * maxAscent;
733                 }
734                 break;
735             }
736             case DOUBLE:
737             {
738                 if (isFirstLine)
739                 {
740                     lineHeight = maxLeading + 1f * maxAscent;
741                 }
742                 else
743                 {
744                     lineHeight = maxLeading + 2f * maxAscent;
745                 }
746                 break;
747             }
748             case PROPORTIONAL:
749             {
750                 if (isFirstLine)
751                 {
752                     lineHeight = maxLeading + 1f * maxAscent;
753                 }
754                 else
755                 {
756                     lineHeight = maxLeading + paragraph.getLineSpacingSize() * maxAscent;
757                 }
758                 break;
759             }
760             case AT_LEAST:
761             {
762                 if (isFirstLine)
763                 {
764                     lineHeight = maxLeading + 1f * maxAscent;
765                 }
766                 else
767                 {
768                     lineHeight = Math.max(maxLeading + 1f * maxAscent, paragraph.getLineSpacingSize());
769                 }
770                 break;
771             }
772             case FIXED:
773             {
774                 if (isFirstLine)
775                 {
776                     lineHeight = maxLeading + 1f * maxAscent;
777                 }
778                 else
779                 {
780                     lineHeight = paragraph.getLineSpacingSize();
781                 }
782                 break;
783             }
784         }
785         
786         return lineHeight;
787     }
788
789     /**
790      * 
791      *
792     public static float getLineHeight(JRParagraph paragraph, float lineSpacingFactor, int maxFontSize)
793     {
794         float lineHeight = 0;
795
796         switch(paragraph.getLineSpacing())
797         {
798             case SINGLE:
799             case ONE_AND_HALF:
800             case DOUBLE:
801             case PROPORTIONAL:
802             {
803                 lineHeight = lineSpacingFactor * maxFontSize;
804                 break;
805             }
806             case AT_LEAST:
807             {
808                 lineHeight = Math.max(lineSpacingFactor * maxFontSize, paragraph.getLineSpacingSize());
809                 break;
810             }
811             case FIXED:
812             {
813                 lineHeight = paragraph.getLineSpacingSize();
814                 break;
815             }
816             default :
817             {
818                 throw new JRRuntimeException("Invalid line space type: " + paragraph.getLineSpacing());
819             }
820         }
821         
822         return lineHeight;
823     }
824
825
826     /**
827      * 
828      */

829     public FontRenderContext getFontRenderContext()
830     {
831         return LINE_BREAK_FONT_RENDER_CONTEXT;
832     }
833
834
835     /**
836      * 
837      */

838     public static class TabSegment
839     {
840         public TextLayout layout;
841         public AttributedString as;//FIXMETAB rename these
842         public String text;
843         public float leftX;
844         public float rightX;
845         public boolean isLastLine;
846     }
847 }
848