1
24 package net.sf.jasperreports.engine.util;
25
26 import java.awt.Font;
27 import java.awt.font.TextAttribute;
28 import java.awt.geom.AffineTransform;
29 import java.text.AttributedCharacterIterator;
30 import java.text.AttributedCharacterIterator.Attribute;
31 import java.text.AttributedString;
32 import java.util.ArrayList;
33 import java.util.Collections;
34 import java.util.HashMap;
35 import java.util.HashSet;
36 import java.util.Iterator;
37 import java.util.List;
38 import java.util.Locale;
39 import java.util.Map;
40 import java.util.Set;
41
42 import net.sf.jasperreports.annotations.properties.Property;
43 import net.sf.jasperreports.annotations.properties.PropertyScope;
44 import net.sf.jasperreports.engine.DefaultJasperReportsContext;
45 import net.sf.jasperreports.engine.JRPropertiesUtil;
46 import net.sf.jasperreports.engine.JRRuntimeException;
47 import net.sf.jasperreports.engine.JasperReportsContext;
48 import net.sf.jasperreports.engine.fonts.AwtFontAttribute;
49 import net.sf.jasperreports.engine.fonts.FontUtil;
50 import net.sf.jasperreports.properties.PropertyConstants;
51
52
53
56 public class JRStyledText implements Cloneable
57 {
58 public static final String EXCEPTION_MESSAGE_KEY_CANNOT_COPY_CHARACTERS = "util.styled.text.cannot.copy.characters";
59
62 @Property(
63 valueType = Boolean.class,
64 defaultValue = PropertyConstants.BOOLEAN_FALSE,
65 scopes = {PropertyScope.CONTEXT, PropertyScope.REPORT},
66 sinceVersion = PropertyConstants.VERSION_3_6_1
67 )
68 public static final String PROPERTY_AWT_IGNORE_MISSING_FONT = JRPropertiesUtil.PROPERTY_PREFIX + "awt.ignore.missing.font";
69
70 @Property(
71 valueType = Boolean.class,
72 scopes = {PropertyScope.GLOBAL},
73 sinceVersion = PropertyConstants.VERSION_3_1_3
74 )
75 public static final String PROPERTY_AWT_SUPERSCRIPT_FIX_ENABLED = JRPropertiesUtil.PROPERTY_PREFIX + "awt.superscript.fix.enabled";
76
77 private static final boolean AWT_SUPERSCRIPT_FIX_ENABLED =
78 JRPropertiesUtil.getInstance(DefaultJasperReportsContext.getInstance()).getBooleanProperty(PROPERTY_AWT_SUPERSCRIPT_FIX_ENABLED);
79
80 private static final Set<Attribute> FONT_ATTRS = new HashSet<Attribute>();
81 static
82 {
83 FONT_ATTRS.add(TextAttribute.FAMILY);
84 FONT_ATTRS.add(JRTextAttribute.FONT_INFO);
85 FONT_ATTRS.add(TextAttribute.WEIGHT);
86 FONT_ATTRS.add(TextAttribute.POSTURE);
87 FONT_ATTRS.add(TextAttribute.SIZE);
88 FONT_ATTRS.add(TextAttribute.SUPERSCRIPT);
89 }
90
91
94 private StringBuilder sbuffer;
95 private String text;
96
97 private List<Run> runs = Collections.emptyList();
98 private AttributedString attributedString;
99 private AttributedString awtAttributedString;
100 private Map<Attribute,Object> globalAttributes;
101 private Locale locale;
102
103
104
107 public JRStyledText()
108 {
109 this(null);
110 }
111
112
113
116 public JRStyledText(Locale locale)
117 {
118 this.locale = locale;
119 }
120
121 public JRStyledText(Locale locale, String text)
122 {
123 this.locale = locale;
124 this.text = text;
125 }
126
127 public JRStyledText(Locale locale, String text, Map<Attribute,Object> globalAttributes)
128 {
129 this.locale = locale;
130 this.text = text;
131 this.globalAttributes = globalAttributes;
132 this.runs = Collections.singletonList(new Run(globalAttributes, 0, text.length()));
133 }
134
135 private void ensureBuffer()
136 {
137 if (sbuffer == null)
138 {
139 sbuffer = text == null ? new StringBuilder() : new StringBuilder(text);
140 }
141 text = null;
142 }
143
144 private void ensureText()
145 {
146 if (text == null)
147 {
148 text = sbuffer == null ? "" : sbuffer.toString();
149 }
150 sbuffer = null;
151 }
152
153
154
157 public void append(String text)
158 {
159 ensureBuffer();
160 sbuffer.append(text);
161 attributedString = null;
162 awtAttributedString = null;
163 }
164
165
168 public void addRun(Run run)
169 {
170 int currentSize = runs.size();
171 if (currentSize == 0)
172 {
173 runs = Collections.singletonList(run);
174 }
175 else
176 {
177 if (currentSize == 1 && !(runs instanceof ArrayList))
178 {
179 List<Run> newRuns = new ArrayList<Run>();
180 newRuns.add(runs.get(0));
181 runs = newRuns;
182 }
183
184 runs.add(run);
185 }
186
187 attributedString = null;
188 awtAttributedString = null;
189 }
190
191
194 public int length()
195 {
196 return text == null ? (sbuffer == null ? 0 : sbuffer.length()) : text.length();
197 }
198
199
202 public String getText()
203 {
204 ensureText();
205 return text;
206 }
207
208
211 public Locale getLocale()
212 {
213 return locale;
214 }
215
216
219 public AttributedString getAttributedString()
220 {
221 if (attributedString == null)
222 {
223 ensureText();
224 attributedString = new AttributedString(text);
225
226 for(int i = runs.size() - 1; i >= 0; i--)
227 {
228 Run run = runs.get(i);
229 if (run.startIndex != run.endIndex && run.attributes != null)
230 {
231 attributedString.addAttributes(run.attributes, run.startIndex, run.endIndex);
232 }
233 }
234 }
235
236 return attributedString;
237 }
238
239
242 public AttributedString getAwtAttributedString(JasperReportsContext jasperReportsContext, boolean ignoreMissingFont)
243 {
244 if (awtAttributedString == null)
245 {
246 ensureText();
247 awtAttributedString = new AttributedString(text);
248
249 for(int i = runs.size() - 1; i >= 0; i--)
250 {
251 Run run = runs.get(i);
252 if (run.startIndex != run.endIndex && run.attributes != null)
253 {
254 awtAttributedString.addAttributes(run.attributes, run.startIndex, run.endIndex);
255 }
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274 }
275
276 AttributedCharacterIterator iterator = awtAttributedString.getIterator();
277
278 int runLimit = 0;
279 AffineTransform atrans = null;
280
281 while(runLimit < iterator.getEndIndex() && (runLimit = iterator.getRunLimit(FONT_ATTRS)) <= iterator.getEndIndex())
282 {
283 Map<Attribute,Object> attrs = iterator.getAttributes();
284
285 AwtFontAttribute fontAttribute = AwtFontAttribute.fromAttributes(attrs);
286
287 FontUtil fontUtil = FontUtil.getInstance(jasperReportsContext);
288 Font awtFont = fontUtil.getAwtFontFromBundles(
289 fontAttribute,
290 ((TextAttribute.WEIGHT_BOLD.equals(attrs.get(TextAttribute.WEIGHT))?Font.BOLD:Font.PLAIN)
291 |(TextAttribute.POSTURE_OBLIQUE.equals(attrs.get(TextAttribute.POSTURE))?Font.ITALIC:Font.PLAIN)),
292 (Float)attrs.get(TextAttribute.SIZE),
293 locale,
294 ignoreMissingFont
295 );
296 if (awtFont == null)
297 {
298
299
300 fontUtil.checkAwtFont(fontAttribute.getFamily(), ignoreMissingFont);
301 }
302 else
303 {
304 if (AWT_SUPERSCRIPT_FIX_ENABLED && atrans != null)
305 {
306 double y = atrans.getTranslateY();
307 atrans = new AffineTransform();
308 atrans.translate(0, - y);
309 awtFont = awtFont.deriveFont(atrans);
310 atrans = null;
311 }
312 Integer superscript = (Integer)attrs.get(TextAttribute.SUPERSCRIPT);
313 if (TextAttribute.SUPERSCRIPT_SUPER.equals(superscript))
314 {
315 atrans = new AffineTransform();
316 atrans.scale(2 / 3d, 2 / 3d);
317 atrans.translate(0, - awtFont.getSize() / 2f);
318 awtFont = awtFont.deriveFont(atrans);
319 }
320 else if (TextAttribute.SUPERSCRIPT_SUB.equals(superscript))
321 {
322 atrans = new AffineTransform();
323 atrans.scale(2 / 3d, 2 / 3d);
324 atrans.translate(0, awtFont.getSize() / 2f);
325 awtFont = awtFont.deriveFont(atrans);
326 }
327 awtAttributedString.addAttribute(TextAttribute.FONT, awtFont, iterator.getIndex(), runLimit);
328 }
329
330 iterator.setIndex(runLimit);
331 }
332
333 }
334
335 return awtAttributedString;
336 }
337
338
339
342 public List<Run> getRuns()
343 {
344 return runs;
345 }
346
347
350 public static class Run implements Cloneable
351 {
352
355 public Map<Attribute,Object> attributes;
356 public int startIndex;
357 public int endIndex;
358
359
362 public Run(Map<Attribute,Object> attributes, int startIndex, int endIndex)
363 {
364 this.attributes = attributes;
365 this.startIndex = startIndex;
366 this.endIndex = endIndex;
367 }
368
369 @Override
370 protected Object clone()
371 {
372 return cloneRun();
373 }
374
375
380 public Run cloneRun()
381 {
382 try
383 {
384 Run clone = (Run) super.clone();
385 clone.attributes = cloneAttributesMap(attributes);
386 return clone;
387 }
388 catch (CloneNotSupportedException e)
389 {
390
391 throw new JRRuntimeException(e);
392 }
393 }
394 }
395
396 public void setGlobalAttributes(Map<Attribute,Object> attributes)
397 {
398 this.globalAttributes = attributes;
399 addRun(new Run(attributes, 0, length()));
400 }
401
402
403 public Map<Attribute,Object> getGlobalAttributes()
404 {
405 return globalAttributes;
406 }
407
408 @Override
409 protected Object clone() throws CloneNotSupportedException
410 {
411
412 return super.clone();
413 }
414
415 protected static Map<Attribute,Object> cloneAttributesMap(Map<Attribute,Object> attributes)
416 {
417 return attributes == null ? null : new HashMap<Attribute,Object>(attributes);
418 }
419
420
421
426 public JRStyledText cloneText()
427 {
428 try
429 {
430 JRStyledText clone = (JRStyledText) super.clone();
431 clone.globalAttributes = cloneAttributesMap(globalAttributes);
432
433 int runsCount = runs.size();
434 if (runsCount == 0)
435 {
436 clone.runs = Collections.emptyList();
437 }
438 else if (runsCount == 1)
439 {
440 clone.runs = Collections.singletonList(runs.get(0).cloneRun());
441 }
442 else
443 {
444 clone.runs = new ArrayList<Run>(runsCount);
445 for (Iterator<Run> it = runs.iterator(); it.hasNext();)
446 {
447 Run run = it.next();
448 Run runClone = run.cloneRun();
449 clone.runs.add(runClone);
450 }
451 }
452
453 return clone;
454 }
455 catch (CloneNotSupportedException e)
456 {
457
458 throw new JRRuntimeException(e);
459 }
460 }
461
462
475 public void insert(String str, short[] offsets)
476 {
477 int insertLength = str.length();
478
479 int currentLength = length();
480
481 StringBuilder newText = new StringBuilder(currentLength + insertLength * offsets.length);
482 char[] buffer = null;
483 int offset = 0;
484 for (int i = 0; i < offsets.length; i++)
485 {
486 int charCount = offsets[i];
487 int prevOffset = offset;
488 offset += offsets[i];
489
490
491 if (buffer == null || buffer.length < charCount)
492 {
493 buffer = new char[charCount];
494 }
495 getChars(prevOffset, offset, buffer, 0);
496 newText.append(buffer, 0, charCount);
497
498
499 newText.append(str);
500
501
502
503 for (Iterator<Run> it = runs.iterator(); it.hasNext();)
504 {
505 Run run = it.next();
506 if (run.startIndex >= offset)
507 {
508
509 run.startIndex += insertLength;
510 run.endIndex += insertLength;
511 }
512 else if (run.endIndex >= offset)
513 {
514
515
516 run.endIndex += insertLength;
517 }
518 }
519 }
520
521
522 int charCount = currentLength - offset;
523 if (buffer == null || buffer.length < charCount)
524 {
525 buffer = new char[charCount];
526 }
527 getChars(offset, currentLength, buffer, 0);
528 newText.append(buffer, 0, charCount);
529
530
531 sbuffer = newText;
532 text = null;
533
534 attributedString = null;
535 awtAttributedString = null;
536 }
537
538 private void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin)
539 {
540 if (text != null)
541 {
542 text.getChars(srcBegin, srcEnd, dst, dstBegin);
543 }
544 else if (sbuffer != null)
545 {
546 sbuffer.getChars(srcBegin, srcEnd, dst, dstBegin);
547 }
548 else if (srcBegin < srcEnd)
549 {
550
551 throw
552 new JRRuntimeException(
553 EXCEPTION_MESSAGE_KEY_CANNOT_COPY_CHARACTERS,
554 new Object[]{srcBegin, srcEnd});
555 }
556 }
557 }
558