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.fill;
25
26 import java.awt.Color;
27 import java.awt.font.TextAttribute;
28 import java.text.AttributedCharacterIterator.Attribute;
29 import java.util.HashMap;
30 import java.util.List;
31 import java.util.Map;
32
33 import net.sf.jasperreports.annotations.properties.Property;
34 import net.sf.jasperreports.annotations.properties.PropertyScope;
35 import net.sf.jasperreports.engine.JRCommonText;
36 import net.sf.jasperreports.engine.JRException;
37 import net.sf.jasperreports.engine.JRFont;
38 import net.sf.jasperreports.engine.JRLineBox;
39 import net.sf.jasperreports.engine.JRParagraph;
40 import net.sf.jasperreports.engine.JRPrintText;
41 import net.sf.jasperreports.engine.JRPropertiesUtil;
42 import net.sf.jasperreports.engine.JRRuntimeException;
43 import net.sf.jasperreports.engine.JRStyle;
44 import net.sf.jasperreports.engine.JRTextElement;
45 import net.sf.jasperreports.engine.base.JRBaseStyle;
46 import net.sf.jasperreports.engine.fonts.FontUtil;
47 import net.sf.jasperreports.engine.type.HorizontalTextAlignEnum;
48 import net.sf.jasperreports.engine.type.ModeEnum;
49 import net.sf.jasperreports.engine.type.RotationEnum;
50 import net.sf.jasperreports.engine.type.RunDirectionEnum;
51 import net.sf.jasperreports.engine.type.VerticalTextAlignEnum;
52 import net.sf.jasperreports.engine.util.JRSingletonCache;
53 import net.sf.jasperreports.engine.util.JRStringUtil;
54 import net.sf.jasperreports.engine.util.JRStyledText;
55 import net.sf.jasperreports.engine.util.JRStyledText.Run;
56 import net.sf.jasperreports.engine.util.JRTextAttribute;
57 import net.sf.jasperreports.engine.util.JRTextMeasurerUtil;
58 import net.sf.jasperreports.engine.util.MarkupProcessor;
59 import net.sf.jasperreports.engine.util.MarkupProcessorFactory;
60 import net.sf.jasperreports.engine.util.StyleUtil;
61 import net.sf.jasperreports.properties.PropertyConstants;
62
63
64 /**
65  * @author Teodor Danciu (teodord@users.sourceforge.net)
66  */

67 public abstract class JRFillTextElement extends JRFillElement implements JRTextElement
68 {
69     
70     public static final String EXCEPTION_MESSAGE_KEY_MISSING_MARKUP_PROCESSOR_FACTORY = "fill.text.element.missing.markup.processor.factory";
71     public static final String EXCEPTION_MESSAGE_KEY_INVALID_START_INDEX = "fill.text.element.invalid.start.index";
72
73     @Property(
74             category = PropertyConstants.CATEGORY_FILL,
75             defaultValue = PropertyConstants.BOOLEAN_TRUE,
76             scopes = {PropertyScope.CONTEXT, PropertyScope.REPORT, PropertyScope.TEXT_ELEMENT},
77             sinceVersion = PropertyConstants.VERSION_6_3_1,
78             valueType = Boolean.class
79             )
80     public static final String PROPERTY_CONSUME_SPACE_ON_OVERFLOW = 
81             JRPropertiesUtil.PROPERTY_PREFIX + "consume.space.on.overflow";
82
83     @Property(
84             category = PropertyConstants.CATEGORY_FILL,
85             defaultValue = "0.5",
86             scopes = {PropertyScope.CONTEXT, PropertyScope.REPORT, PropertyScope.TEXT_ELEMENT},
87             sinceVersion = PropertyConstants.VERSION_6_11_0,
88             valueType = Float.class
89             )
90     public static final String PROPERTY_SCALE_FONT_STEP_LIMIT = 
91             JRPropertiesUtil.PROPERTY_PREFIX + "scale.font.step.limit";
92
93     /**
94      *
95      */

96     private static final JRSingletonCache<MarkupProcessorFactory> markupProcessorFactoryCache = 
97             new JRSingletonCache<MarkupProcessorFactory>(MarkupProcessorFactory.class);
98     private static final Map<String,MarkupProcessor> markupProcessors = new HashMap<String,MarkupProcessor>();
99
100     /**
101      *
102      */

103     private boolean isLeftToRight = true;
104     private JRTextMeasurer textMeasurer;
105     private float lineSpacingFactor;
106     private float leadingOffset;
107     private float textWidth;
108     private float textHeight;
109     //private int elementStretchHeightDelta;
110     private int textStart;
111     private int textEnd;
112     private boolean isCutParagraphOverflow;
113     private boolean isCutParagraphToContinueInOverflow;
114     private short[] lineBreakOffsets;
115     private String textTruncateSuffix;
116     private String oldRawText;
117     private String rawText;
118     private JRStyledText styledText;
119     private JRStyledText processedStyledText;
120     private Map<JRStyle,Map<Attribute,Object>> styledTextAttributesMap = new HashMap<JRStyle,Map<Attribute,Object>>();
121     
122     protected final JRLineBox initLineBox;
123     protected final JRParagraph initParagraph;
124     protected JRLineBox lineBox;
125     protected JRParagraph paragraph;
126
127     private JRStyle currentFillStyle;
128     private FillStyleObjects fillStyleObjects;
129     private Map<JRStyle, FillStyleObjects> fillStyleObjectsMap;
130     
131     private Boolean defaultConsumeSpaceOnOverflow;
132     private boolean dynamicConsumeSpaceOnOverflow;
133     private Boolean defaultKeepFullText;
134     private boolean dynamicKeepFullText;
135     private Float defaultScaleFontStepLimit;
136     private boolean dynamicScaleFontStepLimit;
137
138     /**
139      *
140      */

141     protected JRFillTextElement(
142         JRBaseFiller filler,
143         JRTextElement textElement, 
144         JRFillObjectFactory factory
145         )
146     {
147         super(filler, textElement, factory);
148
149         initLineBox = textElement.getLineBox().clone(this);
150         initParagraph = textElement.getParagraph().clone(this);
151
152         this.dynamicConsumeSpaceOnOverflow = hasDynamicProperty(PROPERTY_CONSUME_SPACE_ON_OVERFLOW);
153         this.dynamicKeepFullText = hasDynamicProperty(JRTextElement.PROPERTY_PRINT_KEEP_FULL_TEXT);
154         this.dynamicScaleFontStepLimit = hasDynamicProperty(PROPERTY_SCALE_FONT_STEP_LIMIT);
155         
156         this.fillStyleObjectsMap = new HashMap<JRStyle, JRFillTextElement.FillStyleObjects>();
157     }
158     
159
160     protected JRFillTextElement(JRFillTextElement textElement, JRFillCloneFactory factory)
161     {
162         super(textElement, factory);
163         
164         initLineBox = textElement.getLineBox().clone(this);
165         initParagraph = textElement.getParagraph().clone(this);
166
167         this.defaultConsumeSpaceOnOverflow = textElement.defaultConsumeSpaceOnOverflow;
168         this.dynamicConsumeSpaceOnOverflow = textElement.dynamicConsumeSpaceOnOverflow;
169         this.defaultKeepFullText = textElement.defaultKeepFullText;
170         this.dynamicKeepFullText = textElement.dynamicKeepFullText;
171         this.defaultScaleFontStepLimit = textElement.defaultScaleFontStepLimit;
172         this.dynamicScaleFontStepLimit = textElement.dynamicScaleFontStepLimit;
173
174         this.fillStyleObjectsMap = textElement.fillStyleObjectsMap;
175     }
176
177
178     private void createTextMeasurer()
179     {
180         textMeasurer = JRTextMeasurerUtil.getInstance(filler.getJasperReportsContext()).createTextMeasurer(this);
181     }
182
183     protected void ensureTextMeasurer()
184     {
185         if (textMeasurer == null)
186         {
187             createTextMeasurer();
188         }
189     }
190
191
192     @Override
193     protected void evaluateStyle(
194         byte evaluation
195         ) throws JRException
196     {
197         super.evaluateStyle(evaluation);
198
199         if (providerStyle == null)
200         {
201             lineBox = null;
202             paragraph = null;
203             
204             setFillStyleObjects();
205         }
206         else
207         {
208             lineBox = initLineBox.clone(this);
209             paragraph = initParagraph.clone(this);
210             StyleUtil.appendBox(lineBox, providerStyle.getLineBox());
211             StyleUtil.appendParagraph(paragraph, providerStyle.getParagraph());
212             
213             fillStyleObjects = null;
214         }
215     }
216
217     private void setFillStyleObjects()
218     {
219         JRStyle evaluatedStyle = getStyle();
220         // quick check for fast exit (avoid map lookup) when the style has not changed
221         //FIXME keep two previous styles to catch common alternating row styles?
222         if (fillStyleObjects != null && currentFillStyle == evaluatedStyle)
223         {
224             return;
225         }
226         
227         // update current style
228         currentFillStyle = evaluatedStyle;
229         
230         // search cached per style
231         fillStyleObjects = fillStyleObjectsMap.get(evaluatedStyle);
232         if (fillStyleObjects == null)
233         {
234             // create fill style objects
235             CachingLineBox cachedLineBox = new CachingLineBox(initLineBox);
236             CachingParagraph cachedParagraph = new CachingParagraph(initParagraph);
237             fillStyleObjects = new FillStyleObjects(cachedLineBox, cachedParagraph);
238             
239             fillStyleObjectsMap.put(evaluatedStyle, fillStyleObjects);
240         }
241     }
242
243
244     @Override
245     public ModeEnum getModeValue()
246     {
247         return getStyleResolver().getMode(this, ModeEnum.TRANSPARENT);
248     }
249
250     @Override
251     public HorizontalTextAlignEnum getHorizontalTextAlign()
252     {
253         return getStyleResolver().getHorizontalTextAlign(this);
254     }
255         
256     @Override
257     public HorizontalTextAlignEnum getOwnHorizontalTextAlign()
258     {
259         return providerStyle == null || providerStyle.getOwnHorizontalTextAlign() == null ? ((JRTextElement)this.parent).getOwnHorizontalTextAlign() : providerStyle.getOwnHorizontalTextAlign();
260     }
261
262     @Override
263     public void setHorizontalTextAlign(HorizontalTextAlignEnum horizontalAlignment)
264     {
265         throw new UnsupportedOperationException();
266     }
267         
268     @Override
269     public VerticalTextAlignEnum getVerticalTextAlign()
270     {
271         return getStyleResolver().getVerticalTextAlign(this);
272     }
273         
274     @Override
275     public VerticalTextAlignEnum getOwnVerticalTextAlign()
276     {
277         return providerStyle == null || providerStyle.getOwnVerticalTextAlign() == null ? ((JRTextElement)this.parent).getOwnVerticalTextAlign() : providerStyle.getOwnVerticalTextAlign();
278     }
279
280     @Override
281     public void setVerticalTextAlign(VerticalTextAlignEnum verticalAlignment)
282     {
283         throw new UnsupportedOperationException();
284     }
285         
286     @Override
287     public RotationEnum getRotationValue()
288     {
289         return getStyleResolver().getRotationValue(this);
290     }
291         
292     @Override
293     public RotationEnum getOwnRotationValue()
294     {
295         return providerStyle == null || providerStyle.getOwnRotationValue() == null ? ((JRTextElement)this.parent).getOwnRotationValue() : providerStyle.getOwnRotationValue();
296     }
297
298     @Override
299     public void setRotation(RotationEnum rotation)
300     {
301         throw new UnsupportedOperationException();
302     }
303
304     @Override
305     public String getMarkup()
306     {
307         return getStyleResolver().getMarkup(this);
308     }
309         
310     @Override
311     public String getOwnMarkup()
312     {
313         return providerStyle == null || providerStyle.getOwnMarkup() == null ? ((JRTextElement)parent).getOwnMarkup() : providerStyle.getOwnMarkup();
314     }
315
316     @Override
317     public void setMarkup(String markup)
318     {
319         throw new UnsupportedOperationException();
320     }
321
322     protected JRLineBox getPrintLineBox()
323     {
324         return lineBox == null ? initLineBox : lineBox;
325     }
326     
327     @Override
328     public JRLineBox getLineBox()
329     {
330         return lineBox == null 
331                 ? (fillStyleObjects == null ? initLineBox : fillStyleObjects.lineBox) 
332                 : lineBox;
333     }
334
335     protected JRParagraph getPrintParagraph()
336     {
337         return paragraph == null ? initParagraph : paragraph;
338     }
339
340     @Override
341     public JRParagraph getParagraph()
342     {
343         return paragraph == null 
344                 ? (fillStyleObjects == null ? initParagraph : fillStyleObjects.paragraph) 
345                 : paragraph;
346     }
347
348     /**
349      * @deprecated
350      */

351     public JRFont getFont()
352     {
353         return this;
354     }
355
356     
357     /**
358      *
359      */

360     protected Map<Attribute,Object> getStyledTextAttributes()
361     {
362         JRStyle style = getStyle();
363         Map<Attribute,Object> styledTextAttributes = styledTextAttributesMap.get(style);
364         if (styledTextAttributes == null)
365         {
366             styledTextAttributes = new HashMap<Attribute,Object>(); 
367             //JRFontUtil.getAttributes(styledTextAttributes, this, filler.getLocale());
368             FontUtil.getInstance(filler.getJasperReportsContext()).getAttributesWithoutAwtFont(styledTextAttributes, this);
369             styledTextAttributes.put(TextAttribute.FOREGROUND, getForecolor());
370             if (getModeValue() == ModeEnum.OPAQUE)
371             {
372                 styledTextAttributes.put(TextAttribute.BACKGROUND, getBackcolor());
373             }
374             styledTextAttributesMap.put(style, styledTextAttributes);
375         }
376         
377         return styledTextAttributes;
378     }
379
380     /**
381      *
382      */

383     protected float getLineSpacingFactor()
384     {
385         return lineSpacingFactor;
386     }
387         
388     /**
389      *
390      */

391     protected void setLineSpacingFactor(float lineSpacingFactor)
392     {
393         this.lineSpacingFactor = lineSpacingFactor;
394     }
395
396     /**
397      *
398      */

399     protected float getLeadingOffset()
400     {
401         return leadingOffset;
402     }
403         
404     /**
405      *
406      */

407     protected void setLeadingOffset(float leadingOffset)
408     {
409         this.leadingOffset = leadingOffset;
410     }
411
412     /**
413      *
414      */

415     public RunDirectionEnum getRunDirectionValue()
416     {
417         return isLeftToRight ? RunDirectionEnum.LTR : RunDirectionEnum.RTL;
418     }
419         
420     /**
421      *
422      */

423     public float getTextWidth()
424     {
425         return textWidth;
426     }
427         
428     /**
429      *
430      */

431     protected void setTextWidth(float textWidth)
432     {
433         this.textWidth = textWidth;
434     }
435
436     /**
437      *
438      */

439     protected float getTextHeight()
440     {
441         return textHeight;
442     }
443         
444     /**
445      *
446      */

447     protected void setTextHeight(float textHeight)
448     {
449         this.textHeight = textHeight;
450     }
451
452     /**
453      *
454      */

455     protected int getTextStart()
456     {
457         return textStart;
458     }
459         
460     /**
461      *
462      */

463     protected void setTextStart(int textStart)
464     {
465         this.textStart = textStart;
466     }
467
468     /**
469      *
470      */

471     protected int getTextEnd()
472     {
473         return textEnd;
474     }
475         
476     /**
477      *
478      */

479     protected void setTextEnd(int textEnd)
480     {
481         this.textEnd = textEnd;
482     }
483
484     /**
485      *
486      */

487     protected boolean isCutParagraphToContinueInOverflow()
488     {
489         return isCutParagraphToContinueInOverflow;
490     }
491         
492     /**
493      *
494      */

495     protected void setCutParagraphToContinueInOverflow(boolean isCutParagraphToContinueInOverflow)
496     {
497         this.isCutParagraphToContinueInOverflow = isCutParagraphToContinueInOverflow;
498     }
499     
500     protected short[] getLineBreakOffsets()
501     {
502         return lineBreakOffsets;
503     }
504
505     protected void setLineBreakOffsets(short[] lineBreakOffsets)
506     {
507         this.lineBreakOffsets = lineBreakOffsets;
508     }
509
510     protected void resetTextChunk()
511     {
512         textStart = 0;
513         textEnd = 0;
514         textTruncateSuffix = null;
515         lineBreakOffsets = null;
516         //elementStretchHeightDelta = 0;
517     }
518     
519     /**
520      *
521      */

522     protected String getRawText()
523     {
524         return rawText;
525     }
526
527     /**
528      *
529      */

530     protected void setRawText(String rawText)
531     {
532         this.oldRawText = this.rawText;
533         this.rawText = rawText;
534         styledText = null;
535         processedStyledText = null;
536     }
537
538
539     @Override
540     public void reset()
541     {
542         super.reset();
543         
544         isLeftToRight = true;
545         lineSpacingFactor = 0;
546         leadingOffset = 0;
547         textHeight = 0;
548         //elementStretchHeightDelta = 0;
549     }
550
551
552     @Override
553     public void rewind()
554     {
555         @SuppressWarnings("deprecation")
556         boolean isLegacyBandEvaluationEnabled = filler.getFillContext().isLegacyBandEvaluationEnabled(); 
557         if (!isLegacyBandEvaluationEnabled)
558         {
559             this.rawText = this.oldRawText;
560         }
561         resetTextChunk();
562     }
563
564
565     /**
566      *
567      */

568     protected JRStyledText getStyledText()
569     {
570         if (styledText == null)
571         {
572             String text = getRawText();
573             if (text != null)
574             {
575                 styledText = 
576                     filler.getStyledTextParser().getStyledText(
577                         getStyledTextAttributes(), 
578                         text, 
579                         !JRCommonText.MARKUP_NONE.equals(getMarkup()),
580                         filler.getLocale()
581                         );
582             }
583             processedStyledText = null;
584         }
585         
586         return styledText;
587     }
588     
589     protected JRStyledText getProcessedStyledText()
590     {
591         if (processedStyledText == null)
592         {
593             JRStyledText text = getStyledText();
594             if (text != null)
595             {
596                 processedStyledText = filler.getStyledTextUtil().resolveFonts(text, filler.getLocale());
597             }
598         }
599         return processedStyledText;
600     }
601
602     /**
603      *
604      */

605     public String getTextString()
606     {
607         JRStyledText tmpStyledText = getStyledText();
608
609         if (tmpStyledText == null)
610         {
611             return null;
612         }
613
614         return tmpStyledText.getText();
615     }
616     
617
618     @Override
619     protected boolean prepare(
620         int availableHeight,
621         boolean isOverflow
622         ) throws JRException
623     {
624         isCutParagraphOverflow = isCutParagraphToContinueInOverflow;
625         
626         return super.prepare(availableHeight, isOverflow);
627     }
628
629
630     /**
631      *
632      */

633     protected void chopTextElement(
634         int availableStretchHeight
635         )
636     {
637         ensureTextMeasurer();
638         
639         JRStyledText tmpStyledText = getStyledText();
640
641         if (tmpStyledText == null)
642         {
643             return;
644         }
645
646         int fullTextLength = tmpStyledText.getText().length();
647         if (getTextEnd() == fullTextLength)
648         {
649             return;
650         }
651
652         boolean canOverflow = canOverflow();
653         JRStyledText processedText = getProcessedStyledText();
654         JRMeasuredText measuredText = textMeasurer.measure(
655             processedText,
656             getTextEnd(),
657             availableStretchHeight,
658             !isCutParagraphOverflow, // indentFirstLine
659             canOverflow
660             );
661         
662         if (
663             scaleFontToFit()
664             && measuredText.getTextOffset() < fullTextLength
665             )
666         {
667             // work with a clone of processed styled text so that we don't
668             // damage the cached global style attributes that are going to be reused
669             // by subsequent print text elements issued from this fill element
670             JRStyledText tmpProcessedText = processedText.cloneText();
671             
672             JRMeasuredText tmpMeasuredText = measuredText;
673             JRMeasuredText lastGoodMeasuredText = measuredText;
674             JRMeasuredText lastBadMeasuredText = measuredText;
675
676             float factor = 1f;
677             float lastGoodFactor = 1f;
678             float lastBadFactor = 1f;
679             float delta = 1f;
680             int deltaSign = -1;
681             int oldDeltaSign = -1;
682             float oldFontSizeMaxDiff = 0;
683             float scaleFontStepLimit = scaleFontStepLimit();
684
685             boolean keepMeasuring = true;
686
687             while (keepMeasuring)
688             {
689                 delta = 0.5f * delta;
690                 
691                 if (tmpMeasuredText.getTextOffset() < fullTextLength)
692                 {
693                     lastBadMeasuredText = tmpMeasuredText;
694                     lastBadFactor = factor;
695
696                     deltaSign = -1;
697                     factor = factor - delta;
698                 }
699                 else
700                 {
701                     lastGoodMeasuredText = tmpMeasuredText;
702                     lastGoodFactor = factor;
703
704                     deltaSign = 1;
705                     factor = factor + delta;
706                 }
707
708                 float newFontSizeMaxDiff = alterFontSizes(tmpProcessedText, factor, scaleFontStepLimit);
709                 keepMeasuring = 
710                     newFontSizeMaxDiff != 0
711                     && (newFontSizeMaxDiff != scaleFontStepLimit || deltaSign * newFontSizeMaxDiff != - oldDeltaSign * oldFontSizeMaxDiff);
712                 if (keepMeasuring)
713                 {
714                     tmpMeasuredText = textMeasurer.measure(
715                         tmpProcessedText,
716                         getTextEnd(),
717                         availableStretchHeight,
718                         !isCutParagraphOverflow, // indentFirstLine
719                         canOverflow
720                         );
721                 }
722                 
723                 oldDeltaSign = deltaSign;
724                 oldFontSizeMaxDiff = newFontSizeMaxDiff;
725             }
726
727             if (lastGoodFactor == 1f)
728             {
729                 lastGoodFactor = lastBadFactor; // fit as much text as we can, if cannot fit all
730                 measuredText = lastBadMeasuredText;
731             }
732             else
733             {
734                 measuredText = lastGoodMeasuredText;
735             }
736             
737             if (!JRCommonText.MARKUP_NONE.equals(getMarkup()))
738             {
739                 // scale font sizes in styled text according to calculated factor, but on a clone of the styled text;
740                 // not doing it on existing styled text instance as it would damage the global style text attributes used
741                 // by the last run, which are cached and reused for print text elements issued from this fill element
742                 styledText = styledText.cloneText();
743                 alterFontSizes(styledText, lastGoodFactor, scaleFontStepLimit);
744             }
745
746             if (providerStyle == null)
747             {
748                 providerStyle = new JRBaseStyle();
749             }
750             providerStyle.setFontSize(scaleFontSize(getFontsize(), lastGoodFactor, scaleFontStepLimit));
751         }
752         
753         isLeftToRight = measuredText.isLeftToRight();
754         setTextWidth(measuredText.getTextWidth());
755         setTextHeight(measuredText.getTextHeight());
756         
757         //elementStretchHeightDelta = 0;
758         if (getRotationValue().equals(RotationEnum.NONE))
759         {
760             //FIXME truncating to int here seems wrong as the text measurer compares 
761             // the exact text height against the available height
762             int elementTextHeight = (int) getTextHeight() + getLineBox().getTopPadding() + getLineBox().getBottomPadding();
763             if (
764                 measuredText.getTextOffset() >= fullTextLength //text ended 
765                 || !canOverflow 
766                 || !isConsumeSpaceOnOverflow()
767                 )
768             {
769                 setPrepareHeight(elementTextHeight);
770             }
771             else
772             {
773                 // occupy all remaining space so that no other element renders there
774                 setPrepareHeight(getHeight() + availableStretchHeight);
775                 
776                 // store the difference between the consumed stretch height and the text stretch height.
777                 // this will be used in fill() to set the print element height, 
778                 // which doesn't take into account the consumed empty space;
779                 
780                 // gave up on storing this delta because it was not consistent with other elements stretching to container height
781                 //int textStretchHeight = elementTextHeight > getHeight() ? elementTextHeight : getHeight();
782                 //elementStretchHeightDelta = getStretchHeight() - textStretchHeight;
783             }
784         }
785         else
786         {
787             setPrepareHeight(getHeight());
788         }
789         
790         setTextStart(getTextEnd());
791         setTextEnd(measuredText.getTextOffset());
792         setCutParagraphToContinueInOverflow(canOverflow && measuredText.isParagraphCut());
793         setLineBreakOffsets(measuredText.getLineBreakOffsets());
794         setTextTruncateSuffix(measuredText.getTextSuffix());
795         setLineSpacingFactor(measuredText.getLineSpacingFactor());
796         setLeadingOffset(measuredText.getLeadingOffset());
797     }
798     
799     private static float scaleFontSize(float fontSize, float factor, float scaleFontStepLimit)
800     {
801         float newFontSize = (int)(100 * (Math.round(factor * fontSize / scaleFontStepLimit) * scaleFontStepLimit)) / 100f; // we round to step limit and round to 2 decimals
802         newFontSize = newFontSize < scaleFontStepLimit ? scaleFontStepLimit : newFontSize;
803         return newFontSize;
804     }
805     
806     private static float alterFontSizes(JRStyledText styledText, float factor, float scaleFontStepLimit)
807     {
808         float fontSizeMaxDiff = 0;
809         if (styledText != null && styledText.length() != 0)
810         {
811             fontSizeMaxDiff = alterFontSize(styledText.getGlobalAttributes(), factor, scaleFontStepLimit, fontSizeMaxDiff);
812             
813             List<Run> runs = styledText.getRuns();
814             for (Run run : runs)
815             {
816                 fontSizeMaxDiff = alterFontSize(run.attributes, factor, scaleFontStepLimit, fontSizeMaxDiff);
817             }
818             styledText.append(""); // just to reset internal caches
819         }
820         return fontSizeMaxDiff;
821     }
822
823     private static float alterFontSize(Map<Attribute, Object> attributes, float factor, float scaleFontStepLimit, float fontSizeMaxDiff)
824     {
825         Float originalFontSize = (Float)attributes.get(JRTextAttribute.FONT_SIZE);
826         if (originalFontSize == null)
827         {
828             originalFontSize = (Float)attributes.get(TextAttribute.SIZE);
829             if (originalFontSize != null)
830             {
831                 //keep the original font size, if present
832                 attributes.put(JRTextAttribute.FONT_SIZE, originalFontSize);
833             }
834         }
835         if (originalFontSize != null)
836         {
837             Float newFontSize = scaleFontSize(originalFontSize, factor, scaleFontStepLimit);
838             Float oldFontSize = (Float)attributes.get(TextAttribute.SIZE);
839             fontSizeMaxDiff = Math.max(Math.abs(newFontSize - oldFontSize), fontSizeMaxDiff);
840             attributes.put(TextAttribute.SIZE, newFontSize);
841         }
842         return fontSizeMaxDiff;
843     }
844
845     protected boolean isConsumeSpaceOnOverflow()
846     {
847         if (defaultConsumeSpaceOnOverflow == null)
848         {
849             defaultConsumeSpaceOnOverflow = filler.getPropertiesUtil().getBooleanProperty( 
850                     PROPERTY_CONSUME_SPACE_ON_OVERFLOW, true,
851                     // manually falling back to report properties as getParentProperties() is null for textElement
852                     parent, filler.getMainDataset());//TODO
853         }
854         
855         boolean consumeSpaceOnOverflow = defaultConsumeSpaceOnOverflow;
856         if (dynamicConsumeSpaceOnOverflow)
857         {
858             String consumeSpaceOnOverflowProp = getDynamicProperties().getProperty(PROPERTY_CONSUME_SPACE_ON_OVERFLOW);
859             if (consumeSpaceOnOverflowProp != null)
860             {
861                 consumeSpaceOnOverflow = JRPropertiesUtil.asBoolean(consumeSpaceOnOverflowProp);
862             }
863         }
864         return consumeSpaceOnOverflow;
865     }
866     
867 //    public int getPrintElementHeight()
868 //    {
869 //        return getStretchHeight() - elementStretchHeightDelta;
870 //    }
871     
872     protected abstract boolean canOverflow();
873
874     protected abstract boolean scaleFontToFit();
875
876
877     @Override
878     public String getFontName()
879     {
880         return getStyleResolver().getFontName(this);
881     }
882
883     @Override
884     public String getOwnFontName()
885     {
886         return providerStyle == null || providerStyle.getOwnFontName() == null ? ((JRFont)parent).getOwnFontName() : providerStyle.getOwnFontName();
887     }
888
889     @Override
890     public void setFontName(String fontName)
891     {
892         throw new UnsupportedOperationException();
893     }
894
895
896     @Override
897     public boolean isBold()
898     {
899         return getStyleResolver().isBold(this);
900     }
901
902     @Override
903     public Boolean isOwnBold()
904     {
905         return providerStyle == null || providerStyle.isOwnBold() == null ? ((JRFont)parent).isOwnBold() : providerStyle.isOwnBold();
906     }
907
908     /**
909      * @deprecated Replaced by {@link #setBold(Boolean)}.
910      */

911     @Override
912     public void setBold(boolean isBold)
913     {
914         throw new UnsupportedOperationException();
915     }
916
917     /**
918      * Alternative setBold method which allows also to reset
919      * the "own" isBold property.
920      */

921     @Override
922     public void setBold(Boolean isBold)
923     {
924         throw new UnsupportedOperationException();
925     }
926
927
928     @Override
929     public boolean isItalic()
930     {
931         return getStyleResolver().isItalic(this);
932     }
933
934     @Override
935     public Boolean isOwnItalic()
936     {
937         return providerStyle == null || providerStyle.isOwnItalic() == null ? ((JRFont)parent).isOwnItalic() : providerStyle.isOwnItalic();
938     }
939
940     /**
941      * @deprecated Replaced by {@link #setItalic(Boolean)}.
942      */

943     @Override
944     public void setItalic(boolean isItalic)
945     {
946         throw new UnsupportedOperationException();
947     }
948
949     /**
950      * Alternative setItalic method which allows also to reset
951      * the "own" isItalic property.
952      */

953     @Override
954     public void setItalic(Boolean isItalic)
955     {
956         throw new UnsupportedOperationException();
957     }
958
959     @Override
960     public boolean isUnderline()
961     {
962         return getStyleResolver().isUnderline(this);
963     }
964
965     @Override
966     public Boolean isOwnUnderline()
967     {
968         return providerStyle == null || providerStyle.isOwnUnderline() == null ? ((JRFont)parent).isOwnUnderline() : providerStyle.isOwnUnderline();
969     }
970
971     /**
972      * @deprecated Replaced by {@link #setUnderline(Boolean)}.
973      */

974     @Override
975     public void setUnderline(boolean isUnderline)
976     {
977         throw new UnsupportedOperationException();
978     }
979
980     /**
981      * Alternative setUnderline method which allows also to reset
982      * the "own" isUnderline property.
983      */

984     @Override
985     public void setUnderline(Boolean isUnderline)
986     {
987         throw new UnsupportedOperationException();
988     }
989
990     @Override
991     public boolean isStrikeThrough()
992     {
993         return getStyleResolver().isStrikeThrough(this);
994     }
995
996     @Override
997     public Boolean isOwnStrikeThrough()
998     {
999         return providerStyle == null || providerStyle.isOwnStrikeThrough() == null ? ((JRFont)parent).isOwnStrikeThrough() : providerStyle.isOwnStrikeThrough();
1000     }
1001
1002     /**
1003      * @deprecated Replaced by {@link #setStrikeThrough(Boolean)}.
1004      */

1005     @Override
1006     public void setStrikeThrough(boolean isStrikeThrough)
1007     {
1008         throw new UnsupportedOperationException();
1009     }
1010
1011     /**
1012      * Alternative setStrikeThrough method which allows also to reset
1013      * the "own" isStrikeThrough property.
1014      */

1015     @Override
1016     public void setStrikeThrough(Boolean isStrikeThrough)
1017     {
1018         throw new UnsupportedOperationException();
1019     }
1020
1021     @Override
1022     public float getFontsize()
1023     {
1024         return getStyleResolver().getFontsize(this);
1025     }
1026
1027     @Override
1028     public Float getOwnFontsize()
1029     {
1030         return providerStyle == null || providerStyle.getOwnFontsize() == null ? ((JRFont)parent).getOwnFontsize() : providerStyle.getOwnFontsize();
1031     }
1032
1033     @Override
1034     public void setFontSize(Float size)
1035     {
1036         throw new UnsupportedOperationException();
1037     }
1038
1039     @Override
1040     public String getPdfFontName()
1041     {
1042         return getStyleResolver().getPdfFontName(this);
1043     }
1044
1045     @Override
1046     public String getOwnPdfFontName()
1047     {
1048         return providerStyle == null || providerStyle.getOwnPdfFontName() == null ? ((JRFont)parent).getOwnPdfFontName() : providerStyle.getOwnPdfFontName();
1049     }
1050
1051     @Override
1052     public void setPdfFontName(String pdfFontName)
1053     {
1054         throw new UnsupportedOperationException();
1055     }
1056
1057
1058     @Override
1059     public String getPdfEncoding()
1060     {
1061         return getStyleResolver().getPdfEncoding(this);
1062     }
1063
1064     @Override
1065     public String getOwnPdfEncoding()
1066     {
1067         return providerStyle == null || providerStyle.getOwnPdfEncoding() == null ? ((JRFont)parent).getOwnPdfEncoding() : providerStyle.getOwnPdfEncoding();
1068     }
1069
1070     @Override
1071     public void setPdfEncoding(String pdfEncoding)
1072     {
1073         throw new UnsupportedOperationException();
1074     }
1075
1076
1077     @Override
1078     public boolean isPdfEmbedded()
1079     {
1080         return getStyleResolver().isPdfEmbedded(this);
1081     }
1082
1083     @Override
1084     public Boolean isOwnPdfEmbedded()
1085     {
1086         return providerStyle == null || providerStyle.isOwnPdfEmbedded() == null ? ((JRFont)parent).isOwnPdfEmbedded() : providerStyle.isOwnPdfEmbedded();
1087     }
1088
1089     /**
1090      * @deprecated Replaced by {@link #setPdfEmbedded(Boolean)}.
1091      */

1092     @Override
1093     public void setPdfEmbedded(boolean isPdfEmbedded)
1094     {
1095         throw new UnsupportedOperationException();
1096     }
1097
1098     /**
1099      * Alternative setPdfEmbedded method which allows also to reset
1100      * the "own" isPdfEmbedded property.
1101      */

1102     @Override
1103     public void setPdfEmbedded(Boolean isPdfEmbedded)
1104     {
1105         throw new UnsupportedOperationException();
1106     }
1107
1108     
1109     @Override
1110     public Color getDefaultLineColor() 
1111     {
1112         return getForecolor();
1113     }
1114
1115     
1116     @Override
1117     public void setHeight(int height)
1118     {
1119         super.setHeight(height);
1120         
1121         createTextMeasurer();
1122     }
1123
1124
1125     @Override
1126     public void setWidth(int width)
1127     {
1128         super.setWidth(width);
1129         
1130         createTextMeasurer();
1131     }
1132
1133     protected String processMarkupText(String text)
1134     {
1135         text = JRStringUtil.replaceCRwithLF(text);
1136         
1137         if (text != null)
1138         {
1139             String markup = getMarkup();
1140             if (
1141                 !JRCommonText.MARKUP_NONE.equals(markup)
1142                 && !JRCommonText.MARKUP_STYLED_TEXT.equals(markup)
1143                 )
1144             {
1145                 text = getMarkupProcessor(markup).convert(text);
1146             }
1147         }
1148         
1149         return text;
1150     }
1151
1152     protected MarkupProcessor getMarkupProcessor(String markup)
1153     {
1154         MarkupProcessor markupProcessor = markupProcessors.get(markup);
1155         
1156         if (markupProcessor == null)
1157         {
1158             String factoryClass = filler.getPropertiesUtil().getProperty(MarkupProcessorFactory.PROPERTY_MARKUP_PROCESSOR_FACTORY_PREFIX + markup);
1159             if (factoryClass == null)
1160             {
1161                 throw 
1162                     new JRRuntimeException(
1163                         EXCEPTION_MESSAGE_KEY_MISSING_MARKUP_PROCESSOR_FACTORY,  
1164                         new Object[]{markup} 
1165                         );
1166             }
1167
1168             MarkupProcessorFactory factory = null;
1169             try
1170             {
1171                 factory = markupProcessorFactoryCache.getCachedInstance(factoryClass);
1172             }
1173             catch (JRException e)
1174             {
1175                 throw new JRRuntimeException(e);
1176             }
1177             
1178             markupProcessor = factory.createMarkupProcessor();
1179             markupProcessors.put(markup, markupProcessor);
1180         }
1181         
1182         return markupProcessor;
1183     }
1184
1185     protected void setPrintText(JRPrintText printText)
1186     {
1187         int startIndex = getTextStart();
1188         int endIndex = getTextEnd();
1189         JRStyledText fullStyledText = getStyledText();
1190         String fullText = fullStyledText.getText();
1191         
1192         boolean keepAllText = !canOverflow() && keepFullText();
1193         if (keepAllText)
1194         {
1195             //assert getTextStart() == 0
1196             if (startIndex != 0)
1197             {
1198                 throw 
1199                 new JRRuntimeException(
1200                     EXCEPTION_MESSAGE_KEY_INVALID_START_INDEX,  
1201                     (Object[])null 
1202                     );
1203             }
1204             
1205             if (!JRCommonText.MARKUP_NONE.equals(getMarkup()))
1206             {
1207                 //rewrite as styled text
1208                 String styledText = filler.getStyledTextParser().write(
1209                         fullStyledText);
1210                 setPrintText(printText, styledText);
1211             }
1212             else
1213             {
1214                 setPrintText(printText, fullText);
1215             }
1216             
1217             if (endIndex < fullText.length())
1218             {
1219                 printText.setTextTruncateIndex(endIndex);
1220             }
1221         }
1222         else
1223         {
1224             String printedText;
1225             if (!JRCommonText.MARKUP_NONE.equals(getMarkup()))
1226             {
1227                 printedText = filler.getStyledTextParser().write(
1228                         fullStyledText, 
1229                         startIndex, endIndex);
1230             }
1231             else
1232             {
1233                 // relying on substring to return the same String object when whole substring
1234                 printedText = fullText.substring(startIndex, endIndex);
1235             }
1236             
1237             setPrintText(printText, printedText);
1238         }
1239         
1240         printText.setTextTruncateSuffix(getTextTruncateSuffix());
1241         printText.setLineBreakOffsets(getLineBreakOffsets());
1242         
1243         if (
1244             isCutParagraphOverflow
1245             && getParagraph().getFirstLineIndent() != 0
1246             )
1247         {
1248             printText.getPropertiesMap().setProperty(JRPrintText.PROPERTY_AWT_INDENT_FIRST_LINE, Boolean.FALSE.toString());
1249         }
1250         
1251         if (
1252             fullText != null
1253             && endIndex < fullText.length()
1254             && HorizontalTextAlignEnum.JUSTIFIED == getHorizontalTextAlign()
1255             )
1256         {
1257             printText.getPropertiesMap().setProperty(JRPrintText.PROPERTY_AWT_JUSTIFY_LAST_LINE, Boolean.TRUE.toString());
1258         }
1259     }
1260     
1261     protected boolean keepFullText()
1262     {
1263         if (defaultKeepFullText == null)
1264         {
1265             defaultKeepFullText = filler.getPropertiesUtil().getBooleanProperty( 
1266                     JRTextElement.PROPERTY_PRINT_KEEP_FULL_TEXT, false,
1267                     // manually falling back to report properties as getParentProperties() is null for textElement
1268                     parent, filler.getMainDataset());//TODO
1269         }
1270         
1271         boolean keepFullText = defaultKeepFullText;
1272         if (dynamicKeepFullText)
1273         {
1274             String keepFullTextProp = getDynamicProperties().getProperty(
1275                     JRTextElement.PROPERTY_PRINT_KEEP_FULL_TEXT);
1276             if (keepFullTextProp != null)
1277             {
1278                 keepFullText = JRPropertiesUtil.asBoolean(keepFullTextProp);
1279             }
1280         }
1281         return keepFullText;
1282     }
1283     
1284     protected float scaleFontStepLimit()
1285     {
1286         if (defaultScaleFontStepLimit == null)
1287         {
1288             defaultScaleFontStepLimit = filler.getPropertiesUtil().getFloatProperty(
1289                     PROPERTY_SCALE_FONT_STEP_LIMIT, 0.5f,
1290                     // manually falling back to report properties as getParentProperties() is null for textElement
1291                     parent, filler.getMainDataset());//TODO
1292         }
1293         
1294         float scaleFontStepLimit = defaultScaleFontStepLimit;
1295         if (dynamicScaleFontStepLimit)
1296         {
1297             String scaleFontStepLimitProp = getDynamicProperties().getProperty(
1298                     PROPERTY_SCALE_FONT_STEP_LIMIT);
1299             if (scaleFontStepLimitProp != null)
1300             {
1301                 scaleFontStepLimit = JRPropertiesUtil.asFloat(scaleFontStepLimitProp);
1302             }
1303         }
1304         return scaleFontStepLimit;
1305     }
1306     
1307     protected void setPrintText(JRPrintText printText, String text)
1308     {
1309         printText.setText(text);
1310     }
1311
1312     protected String getTextTruncateSuffix()
1313     {
1314         return textTruncateSuffix;
1315     }
1316
1317     protected void setTextTruncateSuffix(String textTruncateSuffix)
1318     {
1319         this.textTruncateSuffix = textTruncateSuffix;
1320     }
1321
1322     private static class FillStyleObjects
1323     {
1324         private final JRLineBox lineBox;
1325         private final JRParagraph paragraph;
1326         
1327         public FillStyleObjects(JRLineBox lineBox, JRParagraph paragraph)
1328         {
1329             this.lineBox = lineBox;
1330             this.paragraph = paragraph;
1331         }
1332     }
1333 }
1334