Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
net.sf.jasperreports.engine.util.JRStyledTextUtil Maven / Gradle / Ivy
/*
* JasperReports - Free Java Reporting Library.
* Copyright (C) 2001 - 2014 TIBCO Software Inc. All rights reserved.
* http://www.jaspersoft.com
*
* Unless you have purchased a commercial license agreement from Jaspersoft,
* the following license terms apply:
*
* This program is part of JasperReports.
*
* JasperReports is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* JasperReports is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with JasperReports. If not, see .
*/
package net.sf.jasperreports.engine.util;
import java.awt.Font;
import java.awt.font.TextAttribute;
import java.text.AttributedCharacterIterator;
import java.text.AttributedCharacterIterator.Attribute;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import net.sf.jasperreports.engine.JRCommonText;
import net.sf.jasperreports.engine.JRPrintText;
import net.sf.jasperreports.engine.JRStyledTextAttributeSelector;
import net.sf.jasperreports.engine.JasperReportsContext;
import net.sf.jasperreports.engine.fonts.FontFace;
import net.sf.jasperreports.engine.fonts.FontFamily;
import net.sf.jasperreports.engine.fonts.FontInfo;
import net.sf.jasperreports.engine.fonts.FontSetFamilyInfo;
import net.sf.jasperreports.engine.fonts.FontSetInfo;
import net.sf.jasperreports.engine.fonts.FontUtil;
import net.sf.jasperreports.engine.util.CharPredicateCache.Result;
import net.sf.jasperreports.engine.util.JRStyledText.Run;
/**
* @author Teodor Danciu ([email protected] )
*/
public class JRStyledTextUtil
{
//private final JasperReportsContext jasperReportsContext;
private final JRStyledTextAttributeSelector allSelector;
private final FontUtil fontUtil;
private final Map, FamilyFonts> familyFonts =
new ConcurrentHashMap, FamilyFonts>();
/**
*
*/
private JRStyledTextUtil(JasperReportsContext jasperReportsContext)
{
//this.jasperReportsContext = jasperReportsContext;
this.allSelector = JRStyledTextAttributeSelector.getAllSelector(jasperReportsContext);
fontUtil = FontUtil.getInstance(jasperReportsContext);
}
/**
*
*/
public static JRStyledTextUtil getInstance(JasperReportsContext jasperReportsContext)
{
return new JRStyledTextUtil(jasperReportsContext);
}
/**
*
*/
public String getTruncatedText(JRPrintText printText)
{
String truncatedText = null;
String originalText = printText.getOriginalText();
if (originalText != null)
{
if (printText.getTextTruncateIndex() == null)
{
truncatedText = originalText;
}
else
{
if (!JRCommonText.MARKUP_NONE.equals(printText.getMarkup()))
{
truncatedText = JRStyledTextParser.getInstance().write(
printText.getFullStyledText(allSelector),
0, printText.getTextTruncateIndex().intValue());
}
else
{
truncatedText = originalText.substring(0, printText.getTextTruncateIndex().intValue());
}
}
String textTruncateSuffix = printText.getTextTruncateSuffix();
if (textTruncateSuffix != null)
{
truncatedText += textTruncateSuffix;
}
}
return truncatedText;
}
/**
*
*/
public JRStyledText getStyledText(JRPrintText printText, JRStyledTextAttributeSelector attributeSelector)
{
String truncatedText = getTruncatedText(printText);
if (truncatedText == null)
{
return null;
}
Locale locale = JRStyledTextAttributeSelector.getTextLocale(printText);
JRStyledText styledText = getStyledText(printText, truncatedText, attributeSelector, locale);
return styledText;
}
protected JRStyledText getStyledText(JRPrintText printText, String text,
JRStyledTextAttributeSelector attributeSelector, Locale locale)
{
return JRStyledTextParser.getInstance().getStyledText(
attributeSelector.getStyledTextAttributes(printText),
text,
!JRCommonText.MARKUP_NONE.equals(printText.getMarkup()),
locale
);
}
public JRStyledText getProcessedStyledText(JRPrintText printText, JRStyledTextAttributeSelector attributeSelector,
String exporterKey)
{
String truncatedText = getTruncatedText(printText);
if (truncatedText == null)
{
return null;
}
Locale locale = JRStyledTextAttributeSelector.getTextLocale(printText);
JRStyledText styledText = getStyledText(printText, truncatedText, attributeSelector, locale);
JRStyledText processedStyledText = resolveFonts(styledText, locale, exporterKey);
return processedStyledText;
}
public JRStyledText resolveFonts(JRStyledText styledText, Locale locale)
{
return resolveFonts(styledText, locale, null);
}
protected JRStyledText resolveFonts(JRStyledText styledText, Locale locale, String exporterKey)
{
if (styledText == null || styledText.length() == 0)
{
return styledText;
}
//TODO introduce an option to modify the existing object
//TODO lucianc trace logging
String text = styledText.getText();
List runs = styledText.getRuns();
List newRuns = null;
if (runs.size() == 1)
{
//treating separately to avoid styledText.getAttributedString() because it's slow
Map attributes = runs.get(0).attributes;
FamilyFonts families = getFamilyFonts(attributes, locale);
if (families.needsToResolveFonts(exporterKey))//TODO lucianc check for single family
{
newRuns = new ArrayList(runs.size() + 2);
matchFonts(text, 0, styledText.length(), attributes, families, newRuns);
}
}
else
{
//quick test to avoid styledText.getAttributedString() when not needed
boolean needsFontMatching = false;
for (Run run : runs)
{
FamilyFonts families = getFamilyFonts(run.attributes, locale);
if (families.needsToResolveFonts(exporterKey))
{
needsFontMatching = true;
break;
}
}
if (needsFontMatching)
{
newRuns = new ArrayList(runs.size() + 2);
AttributedCharacterIterator attributesIt = styledText.getAttributedString().getIterator();
int index = 0;
while (index < styledText.length())
{
int runEndIndex = attributesIt.getRunLimit();
Map runAttributes = attributesIt.getAttributes();
FamilyFonts familyFonts = getFamilyFonts(runAttributes, locale);
if (familyFonts.needsToResolveFonts(exporterKey))
{
matchFonts(text, index, runEndIndex, runAttributes, familyFonts, newRuns);
}
else
{
//not a font set, copying the run
copyRun(newRuns, runAttributes, index, runEndIndex);
}
index = runEndIndex;
attributesIt.setIndex(index);
}
}
}
if (newRuns == null)
{
//no changes
return styledText;
}
JRStyledText processedText = createProcessedStyledText(styledText, text, newRuns);
return processedText;
}
protected JRStyledText createProcessedStyledText(JRStyledText styledText, String text, List newRuns)
{
Map globalAttributes = null;
JRStyledText processedText = new JRStyledText(styledText.getLocale(), text);
for (Run newRun : newRuns)
{
if (newRun.startIndex == 0 && newRun.endIndex == text.length() && globalAttributes == null)
{
globalAttributes = newRun.attributes;
}
else
{
processedText.addRun(newRun);
}
}
processedText.setGlobalAttributes(globalAttributes == null ? styledText.getGlobalAttributes()
: globalAttributes);
return processedText;
}
protected void matchFonts(String text, int startIndex, int endIndex,
Map attributes, FamilyFonts familyFonts,
List newRuns)
{
Number posture = (Number) attributes.get(TextAttribute.POSTURE);
boolean italic = posture != null && !TextAttribute.POSTURE_REGULAR.equals(posture);
Number weight = (Number) attributes.get(TextAttribute.WEIGHT);
boolean bold = weight != null && !TextAttribute.WEIGHT_REGULAR.equals(weight);
boolean hadUnmatched = false;
int index = startIndex;
do
{
FontMatch fontMatch = null;
if (bold && italic)
{
fontMatch = fontMatchRun(text, index, endIndex, familyFonts.boldItalicFonts);
}
if (bold && (fontMatch == null || fontMatch.fontInfo == null))
{
fontMatch = fontMatchRun(text, index, endIndex, familyFonts.boldFonts);
}
if (italic && (fontMatch == null || fontMatch.fontInfo == null))
{
fontMatch = fontMatchRun(text, index, endIndex, familyFonts.italicFonts);
}
if (fontMatch == null || fontMatch.fontInfo == null)
{
fontMatch = fontMatchRun(text, index, endIndex, familyFonts.normalFonts);
}
if (fontMatch.fontInfo != null)
{
//we have a font that matched a part of the text
addFontRun(newRuns, attributes, index, fontMatch.endIndex, fontMatch.fontInfo);
}
else
{
//we stopped at the first character
hadUnmatched = true;
}
index = fontMatch.endIndex;
}
while(index < endIndex);
if (hadUnmatched)
{
//we have unmatched characters, adding a run with the primary font for the entire chunk.
//we're relying on the JRStyledText to apply the runs in the reverse order.
addFallbackRun(newRuns, attributes, startIndex, endIndex, familyFonts);
}
}
protected void copyRun(List newRuns, Map attributes,
int startIndex, int endIndex)
{
Map newAttributes = Collections.unmodifiableMap(attributes);
Run newRun = new Run(newAttributes, startIndex, endIndex);
newRuns.add(newRun);
}
protected void addFallbackRun(List newRuns, Map attributes,
int startIndex, int endIndex, FamilyFonts familyFonts)
{
Map newAttributes;
if (familyFonts.fontSet.getPrimaryFamily() != null)
{
//using the primary font as fallback for characters that are not found in any fonts
//TODO lucianc enhance AdditionalEntryMap to support overwriting an entry
newAttributes = new HashMap(attributes);
String primaryFamilyName = familyFonts.fontSet.getPrimaryFamily().getFontFamily().getName();
newAttributes.put(TextAttribute.FAMILY, primaryFamilyName);
}
else
{
//not a normal case, leaving the font family as is
newAttributes = Collections.unmodifiableMap(attributes);
}
Run newRun = new Run(newAttributes, startIndex, endIndex);
newRuns.add(newRun);
}
protected void addFontRun(List newRuns, Map attributes,
int startIndex, int endIndex, FontInfo fontInfo)
{
//directly putting the FontInfo as an attribute
Map newAttributes = new AdditionalEntryMap(
attributes, JRTextAttribute.FONT_INFO, fontInfo);
Run newRun = new Run(newAttributes, startIndex, endIndex);
newRuns.add(newRun);
}
protected static class FontMatch
{
FontInfo fontInfo;
int endIndex;
}
protected FontMatch fontMatchRun(String text, int startIndex, int endIndex, List fonts)
{
LinkedList validFonts = new LinkedList(fonts);
Face lastValid = null;
int charIndex = startIndex;
int nextCharIndex = charIndex;
while (charIndex < endIndex)
{
char textChar = text.charAt(charIndex);
nextCharIndex = charIndex + 1;
int codePoint;
if (Character.isHighSurrogate(textChar))
{
if (charIndex + 1 >= endIndex)
{
//isolated high surrogate, not attempting to match fonts
break;
}
char nextChar = text.charAt(charIndex + 1);
if (!Character.isLowSurrogate(nextChar))
{
//unpaired high surrogate, not attempting to match fonts
break;
}
codePoint = Character.toCodePoint(textChar, nextChar);
++nextCharIndex;
}
else
{
codePoint = textChar;
}
for (ListIterator fontIt = validFonts.listIterator(); fontIt.hasNext();)
{
Face face = fontIt.next();
if (!face.supports(codePoint))
{
fontIt.remove();
}
}
if (validFonts.isEmpty())
{
break;
}
lastValid = validFonts.getFirst();
charIndex = nextCharIndex;
}
FontMatch fontMatch = new FontMatch();
fontMatch.endIndex = lastValid == null ? nextCharIndex : charIndex;
fontMatch.fontInfo = lastValid == null ? null : lastValid.fontInfo;
return fontMatch;
}
private FamilyFonts getFamilyFonts(Map attributes, Locale locale)
{
String family = (String) attributes.get(TextAttribute.FAMILY);
return getFamilyFonts(family, locale);
}
protected FamilyFonts getFamilyFonts(String name, Locale locale)
{
Pair key = new Pair(name, locale);
FamilyFonts fonts = familyFonts.get(key);
if (fonts == null)
{
fonts = loadFamilyFonts(name, locale);
familyFonts.put(key, fonts);
}
return fonts;
}
protected FamilyFonts loadFamilyFonts(String name, Locale locale)
{
if (name == null)
{
return NULL_FAMILY_FONTS;
}
FontInfo fontInfo = fontUtil.getFontInfo(name, locale);
if (fontInfo != null)
{
//we found a font, not looking for font sets
return NULL_FAMILY_FONTS;
}
FontSetInfo fontSetInfo = fontUtil.getFontSetInfo(name, locale);
if (fontSetInfo == null)
{
return NULL_FAMILY_FONTS;
}
return new FamilyFonts(fontSetInfo);
}
private static FamilyFonts NULL_FAMILY_FONTS = new FamilyFonts(null);
private static class FamilyFonts
{
FontSetInfo fontSet;
List normalFonts;
List boldFonts;
List italicFonts;
List boldItalicFonts;
public FamilyFonts(FontSetInfo fontSet)
{
this.fontSet = fontSet;
init();
}
private void init()
{
if (fontSet == null)
{
return;
}
List families = fontSet.getFamilies();
this.normalFonts = new ArrayList(families.size());
this.boldFonts = new ArrayList(families.size());
this.italicFonts = new ArrayList(families.size());
this.boldItalicFonts = new ArrayList(families.size());
for (FontSetFamilyInfo fontSetFamily : families)
{
Family family = new Family(fontSetFamily);
FontFamily fontFamily = fontSetFamily.getFontFamily();
if (fontFamily.getNormalFace() != null && fontFamily.getNormalFace().getFont() != null)
{
normalFonts.add(new Face(family, fontFamily.getNormalFace(), Font.PLAIN));
}
if (fontFamily.getBoldFace() != null && fontFamily.getBoldFace().getFont() != null)
{
boldFonts.add(new Face(family, fontFamily.getBoldFace(), Font.BOLD));
}
if (fontFamily.getItalicFace() != null && fontFamily.getItalicFace().getFont() != null)
{
italicFonts.add(new Face(family, fontFamily.getItalicFace(), Font.ITALIC));
}
if (fontFamily.getBoldItalicFace() != null && fontFamily.getBoldItalicFace().getFont() != null)
{
boldItalicFonts.add(new Face(family, fontFamily.getBoldItalicFace(), Font.BOLD | Font.ITALIC));
}
}
}
public boolean needsToResolveFonts(String exporterKey)
{
return fontSet != null && (exporterKey == null
|| fontSet.getFontSet().getExportFont(exporterKey) == null);
}
}
private static class Family
{
final FontSetFamilyInfo fontFamily;
CharScriptsSet scriptsSet;
public Family(FontSetFamilyInfo fontSetFamily)
{
this.fontFamily = fontSetFamily;
initScripts();
}
private void initScripts()
{
List includedScripts = fontFamily.getFontSetFamily().getIncludedScripts();
List excludedScripts = fontFamily.getFontSetFamily().getExcludedScripts();
if ((includedScripts != null && !includedScripts.isEmpty())
|| (excludedScripts != null && !excludedScripts.isEmpty()))
{
scriptsSet = new CharScriptsSet(includedScripts, excludedScripts);
}
}
public boolean includesCharacter(int codePoint)
{
return scriptsSet == null || scriptsSet.includesCharacter(codePoint);
}
}
private static class Face
{
final Family family;
final FontInfo fontInfo;
//TODO share caches across fills/exports
final CharPredicateCache cache;
public Face(Family family, FontFace fontFace, int style)
{
this.family = family;
this.fontInfo = new FontInfo(family.fontFamily.getFontFamily(), fontFace, style);
this.cache = new CharPredicateCache();
}
public boolean supports(int code)
{
Result cacheResult = cache.getCached(code);
boolean supports;
switch (cacheResult)
{
case TRUE:
supports = true;
break;
case FALSE:
supports = false;
break;
case NOT_FOUND:
supports = supported(code);
cache.set(code, supports);
break;
case NOT_CACHEABLE:
default:
supports = supported(code);
break;
}
return supports;
}
protected boolean supported(int code)
{
return family.includesCharacter(code)
&& fontInfo.getFontFace().getFont().canDisplay(code);
}
}
}