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 - 2023 Cloud Software Group, 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.JRPropertiesUtil;
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 boolean ignoreMissingFonts;
private final Map, FamilyFonts> familyFonts =
new ConcurrentHashMap<>();
/**
*
*/
private JRStyledTextUtil(JasperReportsContext jasperReportsContext)
{
//this.jasperReportsContext = jasperReportsContext;
this.allSelector = JRStyledTextAttributeSelector.getAllSelector(jasperReportsContext);
fontUtil = FontUtil.getInstance(jasperReportsContext);
//FIXME read from report/element
ignoreMissingFonts = JRPropertiesUtil.getInstance(jasperReportsContext).getBooleanProperty(
JRStyledText.PROPERTY_AWT_IGNORE_MISSING_FONT);
}
/**
*
*/
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());
}
else
{
truncatedText = originalText.substring(0, printText.getTextTruncateIndex());
}
}
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, ignoreMissingFonts);
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);
}
}
public static String getIndentedBulletText(StyledTextWriteContext context)
{
String bulletIndent = null;
if (context.isListItemChange())
{
if (
!context.isFirstRun()
&& !context.prevListItemEndedWithNewLine()
&& ((!context.listItemStartsWithNewLine() && context.isListItemStart())
|| context.isListItemEnd())// || context.isListStart() || context.isListEnd()))
)
{
bulletIndent = "\n";
}
if (context.getDepth() > 0)
{
bulletIndent = (bulletIndent == null ? "" : bulletIndent) + new String(new char[context.getDepth() * 4]).replace('\0', ' ');
}
}
String bulletText = JRStyledTextUtil.getBulletText(context);
return bulletIndent == null ? null : (bulletIndent + (bulletText == null ? "" : (bulletText + " ")));
}
public static String getBulletText(StyledTextWriteContext context)
{
String bulletText = null;
if (
context.isListItemStart()
&& !context.getListItem().noBullet()
)
{
bulletText = getBulletText(context.getList(), context.getListItem());
}
return bulletText;
}
public static String getBulletText(StyledTextListInfo list, StyledTextListItemInfo listItem)
{
String bulletText = null;
if (list == null || !list.ordered())
{
bulletText = "\u2022";
}
else
{
int itemNumber = list.getStart() + listItem.getItemIndex();
if (list.getType() == null)
{
bulletText = String.valueOf(itemNumber);
}
else
{
switch (list.getType())
{
case "A":
{
bulletText = JRStringUtil.getLetterNumeral(itemNumber, true);
break;
}
case "a":
{
bulletText = JRStringUtil.getLetterNumeral(itemNumber, false);
break;
}
case "I":
{
bulletText = JRStringUtil.getRomanNumeral(itemNumber, true);
break;
}
case "i":
{
bulletText = JRStringUtil.getRomanNumeral(itemNumber, false);
break;
}
case "1":
default:
{
bulletText = String.valueOf(itemNumber);
break;
}
}
}
bulletText += ".";
}
return bulletText;
}
public static JRStyledText getBulletedText(JRStyledText styledText)
{
if (styledText != null)
{
StyledTextWriteContext context = new StyledTextWriteContext();
StringBuilder sb = new StringBuilder();
AttributedCharacterIterator allParagraphs = styledText.getAttributedString().getIterator();
String allText = styledText.getText();
int runLimit = 0;
while (runLimit < allParagraphs.getEndIndex() && (runLimit = allParagraphs.getRunLimit(JRTextAttribute.HTML_LIST_ATTRIBUTES)) <= allParagraphs.getEndIndex())
{
Map attributes = allParagraphs.getAttributes();
String runText = allText.substring(allParagraphs.getIndex(), runLimit);
context.next(attributes, runText);
//if (context.listItemStartsWithNewLine() && !context.isListItemStart() && (context.isListItemEnd() || context.isListStart() || context.isListEnd()))
//{
// runText = runText.substring(1);
//}
if (runText.length() > 0)
{
String bulletText = JRStyledTextUtil.getIndentedBulletText(context);
sb.append((bulletText == null ? "" : bulletText) + runText);
}
allParagraphs.setIndex(runLimit);
}
styledText = new JRStyledText(styledText.getLocale(), sb.toString(), styledText.getGlobalAttributes());
}
return styledText;
}
public static JRStyledText getBulletedStyledText(JRStyledText styledText)
{
if (styledText != null)
{
StyledTextWriteContext context = new StyledTextWriteContext();
StringBuilder sb = new StringBuilder();
AttributedCharacterIterator allParagraphs = styledText.getAttributedString().getIterator();
String allText = styledText.getText();
int resizeOffset = 0;
int runLimit = 0;
while (runLimit < allParagraphs.getEndIndex() && (runLimit = allParagraphs.getRunLimit(JRTextAttribute.HTML_LIST_ATTRIBUTES)) <= allParagraphs.getEndIndex())
{
Map attributes = allParagraphs.getAttributes();
String runText = allText.substring(allParagraphs.getIndex(), runLimit);
context.next(attributes, runText);
int initRunTextLength = runText.length();
int initBufferSize = sb.length();
//if (context.listItemStartsWithNewLine() && !context.isListItemStart() && (context.isListItemEnd() || context.isListStart() || context.isListEnd()))
//{
// runText = runText.substring(1);
//}
if (runText.length() > 0)
{
String bulletText = JRStyledTextUtil.getIndentedBulletText(context);
if (bulletText != null)
{
sb.append(bulletText);
}
sb.append(runText);
}
int resizeAmount = (sb.length() - initBufferSize) - initRunTextLength;
resizeRuns(styledText.getRuns(), allParagraphs.getIndex() + resizeOffset, resizeAmount);
resizeOffset += resizeAmount;
allParagraphs.setIndex(runLimit);
}
styledText = new JRStyledText(styledText.getLocale(), sb.toString(), styledText.getGlobalAttributes(), styledText.getRuns());
}
return styledText;
}
public static void resizeRuns(List runs, int startIndex, int count)
{
for (int j = 0; j < runs.size(); j++)
{
JRStyledText.Run run = runs.get(j);
if (startIndex < run.startIndex)
{
run.startIndex += count;
}
if (startIndex < run.endIndex)
{
run.endIndex += count;
}
}
}
}