/*!
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
* or from the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* This program 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.
*
* Copyright (c) 2002-2013 Pentaho Corporation.. All rights reserved.
*/
package org.pentaho.reporting.libraries.css.dom;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.pentaho.reporting.libraries.base.config.Configuration;
import org.pentaho.reporting.libraries.base.config.ExtendedConfiguration;
import org.pentaho.reporting.libraries.base.config.ExtendedConfigurationWrapper;
import org.pentaho.reporting.libraries.css.StyleSheetUtility;
import org.pentaho.reporting.libraries.css.keys.font.FontFamilyValues;
import org.pentaho.reporting.libraries.css.keys.font.FontSmooth;
import org.pentaho.reporting.libraries.css.keys.font.FontStyle;
import org.pentaho.reporting.libraries.css.keys.font.FontStyleKeys;
import org.pentaho.reporting.libraries.css.keys.page.PageSize;
import org.pentaho.reporting.libraries.css.values.CSSNumericType;
import org.pentaho.reporting.libraries.css.values.CSSNumericValue;
import org.pentaho.reporting.libraries.css.values.CSSStringType;
import org.pentaho.reporting.libraries.css.values.CSSStringValue;
import org.pentaho.reporting.libraries.css.values.CSSValue;
import org.pentaho.reporting.libraries.fonts.awt.AWTFontRegistry;
import org.pentaho.reporting.libraries.fonts.registry.DefaultFontStorage;
import org.pentaho.reporting.libraries.fonts.registry.FontContext;
import org.pentaho.reporting.libraries.fonts.registry.FontFamily;
import org.pentaho.reporting.libraries.fonts.registry.FontMetrics;
import org.pentaho.reporting.libraries.fonts.registry.FontRecord;
import org.pentaho.reporting.libraries.fonts.registry.FontRegistry;
import org.pentaho.reporting.libraries.fonts.registry.FontStorage;
import java.util.HashMap;
import java.util.HashSet;
public abstract class AbstractOutputMetaData implements LayoutOutputMetaData {
private static final Log logger = LogFactory.getLog( AbstractOutputMetaData.class );
private static class FontMetricsKey {
private transient Integer hashCode;
private String fontFamily;
private double fontSize;
private boolean antiAliased;
private boolean embedded;
private String encoding;
private boolean italics;
private boolean bold;
protected FontMetricsKey() {
}
protected FontMetricsKey( final FontMetricsKey derived ) {
this.fontFamily = derived.fontFamily;
this.fontSize = derived.fontSize;
this.antiAliased = derived.antiAliased;
this.embedded = derived.embedded;
this.encoding = derived.encoding;
this.italics = derived.italics;
this.bold = derived.bold;
}
public String getFontFamily() {
return fontFamily;
}
public void setFontFamily( final String fontFamily ) {
this.fontFamily = fontFamily;
this.hashCode = null;
}
public double getFontSize() {
return fontSize;
}
public void setFontSize( final double fontSize ) {
this.fontSize = fontSize;
this.hashCode = null;
}
public boolean isAntiAliased() {
return antiAliased;
}
public void setAntiAliased( final boolean antiAliased ) {
this.antiAliased = antiAliased;
this.hashCode = null;
}
public boolean isEmbedded() {
return embedded;
}
public void setEmbedded( final boolean embedded ) {
this.embedded = embedded;
this.hashCode = null;
}
public String getEncoding() {
return encoding;
}
public void setEncoding( final String encoding ) {
this.encoding = encoding;
this.hashCode = null;
}
public boolean isItalics() {
return italics;
}
public void setItalics( final boolean italics ) {
this.italics = italics;
this.hashCode = null;
}
public boolean isBold() {
return bold;
}
public void setBold( final boolean bold ) {
this.bold = bold;
this.hashCode = null;
}
public boolean equals( final Object o ) {
if ( this == o ) {
return true;
}
if ( o == null || getClass() != o.getClass() ) {
return false;
}
final FontMetricsKey that = (FontMetricsKey) o;
if ( hashCode() != that.hashCode() ) {
return false;
}
if ( antiAliased != that.antiAliased ) {
return false;
}
if ( embedded != that.embedded ) {
return false;
}
if ( bold != that.bold ) {
return false;
}
if ( italics != that.italics ) {
return false;
}
if ( that.fontSize != fontSize ) {
return false;
}
if ( encoding != null ? !encoding.equals( that.encoding ) : that.encoding != null ) {
return false;
}
if ( !fontFamily.equals( that.fontFamily ) ) {
return false;
}
return true;
}
public int hashCode() {
final Integer hashCode = this.hashCode;
if ( hashCode == null ) {
int result = fontFamily.hashCode();
final long temp = fontSize != +0.0d ? Double.doubleToLongBits( fontSize ) : 0L;
result = 29 * result + (int) ( temp ^ ( temp >>> 32 ) );
result = 29 * result + ( antiAliased ? 1 : 0 );
result = 29 * result + ( embedded ? 1 : 0 );
result = 29 * result + ( italics ? 1 : 0 );
result = 29 * result + ( bold ? 1 : 0 );
result = 29 * result + ( encoding != null ? encoding.hashCode() : 0 );
this.hashCode = new Integer( result );
return result;
}
return hashCode.intValue();
}
}
protected static class ReusableFontContext implements FontContext {
private boolean antiAliased;
private double fontSize;
private boolean embedded;
private String encoding;
protected ReusableFontContext() {
}
public boolean isEmbedded() {
return embedded;
}
public void setEmbedded( final boolean embedded ) {
this.embedded = embedded;
}
public String getEncoding() {
return encoding;
}
public void setEncoding( final String encoding ) {
this.encoding = encoding;
}
public void setAntiAliased( final boolean antiAliased ) {
this.antiAliased = antiAliased;
}
public void setFontSize( final double fontSize ) {
this.fontSize = fontSize;
}
/**
* This is controlled by the output target and the stylesheet. If the output target does not support aliasing, it
* makes no sense to enable it and all such requests are ignored.
*
* @return
*/
public boolean isAntiAliased() {
return antiAliased;
}
/**
* This is defined by the output target. This is not controlled by the stylesheet.
*
* @return
*/
public boolean isFractionalMetrics() {
return true;
}
/**
* The requested font size. A font may have a fractional font size (ie. 8.5 point). The font size may be influenced
* by the output target.
*
* @return the font size.
*/
public double getFontSize() {
return fontSize;
}
}
private FontStorage fontStorage;
private FontRegistry fontRegistry;
private HashMap numericFeatures;
private HashMap fontFamilyMapping;
private HashSet booleanFeatures;
private Configuration configuration;
private ReusableFontContext reusableFontContext;
private HashMap fontMetricsCache;
private FontMetricsKey lookupKey;
protected AbstractOutputMetaData( final Configuration configuration ) {
this( configuration, new DefaultFontStorage( new AWTFontRegistry() ) );
}
protected AbstractOutputMetaData( final Configuration configuration,
final FontStorage fontStorage ) {
if ( configuration == null ) {
throw new NullPointerException();
}
if ( fontStorage == null ) {
throw new NullPointerException();
}
this.configuration = configuration;
this.fontRegistry = fontStorage.getFontRegistry();
this.fontStorage = fontStorage;
this.booleanFeatures = new HashSet();
this.numericFeatures = new HashMap();
this.reusableFontContext = new ReusableFontContext();
this.fontMetricsCache = new HashMap();
this.lookupKey = new FontMetricsKey();
final ExtendedConfiguration extendedConfig = new ExtendedConfigurationWrapper( configuration );
final double defaultFontSize =
extendedConfig.getIntProperty( "org.pentaho.reporting.libraries.css.defaults.FontSize", 12 );
fontFamilyMapping = new HashMap();
setNumericFeatureValue( OutputProcessorFeature.DEFAULT_FONT_SIZE, defaultFontSize );
final double fontSmoothThreshold =
extendedConfig.getIntProperty( "org.pentaho.reporting.libraries.css.defaults.FontSmoothThreshold", 8 );
setNumericFeatureValue( OutputProcessorFeature.FONT_SMOOTH_THRESHOLD, fontSmoothThreshold );
final double deviceResolution =
extendedConfig.getIntProperty( "org.pentaho.reporting.libraries.css.defaults.DeviceResolution", 72 );
if ( deviceResolution > 0 ) {
setNumericFeatureValue( OutputProcessorFeature.DEVICE_RESOLUTION, deviceResolution );
} else {
setNumericFeatureValue( OutputProcessorFeature.DEVICE_RESOLUTION, 72 );
}
setFamilyMapping( FontFamilyValues.SANS_SERIF, new CSSStringValue( CSSStringType.STRING, "SansSerif" ) );
setFamilyMapping( FontFamilyValues.SERIF, new CSSStringValue( CSSStringType.STRING, "Serif" ) );
setFamilyMapping( FontFamilyValues.NONE, FontFamilyValues.NONE );
setFamilyMapping( FontFamilyValues.MONOSPACE, new CSSStringValue( CSSStringType.STRING, "Monospaced" ) );
setFamilyMapping( FontFamilyValues.FANTASY, new CSSStringValue( CSSStringType.STRING, "Serif" ) );
setFamilyMapping( FontFamilyValues.CURSIVE, new CSSStringValue( CSSStringType.STRING, "SansSerif" ) );
setFamilyMapping( null, new CSSStringValue( CSSStringType.STRING, "SansSerif" ) );
}
public Configuration getConfiguration() {
return configuration;
}
protected void setFamilyMapping( final CSSValue family, final CSSValue name ) {
if ( name == null ) {
throw new NullPointerException();
}
fontFamilyMapping.put( family, name );
}
protected void addFeature( final OutputProcessorFeature.BooleanOutputProcessorFeature feature ) {
if ( feature == null ) {
throw new NullPointerException();
}
this.booleanFeatures.add( feature );
}
protected void removeFeature( final OutputProcessorFeature.BooleanOutputProcessorFeature feature ) {
if ( feature == null ) {
throw new NullPointerException();
}
this.booleanFeatures.remove( feature );
}
public boolean isFeatureSupported( final OutputProcessorFeature.BooleanOutputProcessorFeature feature ) {
if ( feature == null ) {
throw new NullPointerException();
}
return this.booleanFeatures.contains( feature );
}
protected void setNumericFeatureValue( final OutputProcessorFeature.NumericOutputProcessorFeature feature,
final double value ) {
if ( feature == null ) {
throw new NullPointerException();
}
numericFeatures.put( feature, new Double( value ) );
}
public double getNumericFeatureValue( final OutputProcessorFeature.NumericOutputProcessorFeature feature ) {
if ( feature == null ) {
throw new NullPointerException();
}
final Double d = (Double) numericFeatures.get( feature );
if ( d == null ) {
return 0;
}
return d.doubleValue();
}
public boolean isContentSupported( final Object content ) {
return content != null;
}
protected FontRegistry getFontRegistry() {
return fontRegistry;
}
protected FontStorage getFontStorage() {
return fontStorage;
}
/**
* Computes the font-metrics using the given properties.
* <p/>
* This method is a implementation detail. Use it in an output target, but be aware that it may change between
* releases.
*
* @param fontFamily the font family.
* @param fontSize the font size.
* @param bold a flag indicating whether the font should be displayed in bold.
* @param italics a flag indicating whether the font should be displayed in italics.
* @param encoding a valid font encoding, can be null to use the default.
* @param embedded a flag indicating whether the font is intended for embedded use.
* @param antiAliasing a flag indicating whether the font should be rendered in aliased mode.
* @return the font metrics, never null.
* @throws IllegalArgumentException if the font family was invalid and no default family could be located.
*/
public FontMetrics getFontMetrics( final String fontFamily,
final double fontSize,
final boolean bold,
final boolean italics,
final String encoding,
final boolean embedded,
final boolean antiAliasing ) throws IllegalArgumentException {
if ( fontFamily == null ) {
throw new NullPointerException();
}
lookupKey.setAntiAliased( antiAliasing );
lookupKey.setEncoding( encoding );
lookupKey.setEmbedded( embedded );
lookupKey.setFontFamily( fontFamily );
lookupKey.setFontSize( fontSize );
lookupKey.setBold( bold );
lookupKey.setItalics( italics );
final FontMetrics cached = (FontMetrics) fontMetricsCache.get( lookupKey );
if ( cached != null ) {
return cached;
}
final FontRegistry registry = getFontRegistry();
FontFamily family = registry.getFontFamily( fontFamily );
if ( family == null ) {
AbstractOutputMetaData.logger.warn( "Unable to lookup the font family: " + fontFamily );
// Get the default font name
final CSSValue fallBack = getDefaultFontFamily();
if ( fallBack == null ) {
// If this case happens, the output-processor meta-data does not provide a sensible
// fall-back value. As we cannot continue without a font, we fail here instead of
// waiting for a NullPointer or other weird error later.
throw new IllegalArgumentException( "No default family defined, aborting." );
}
if ( fallBack instanceof CSSStringValue ) {
final CSSStringValue svalue = (CSSStringValue) fallBack;
family = registry.getFontFamily( svalue.getValue() );
} else {
family = registry.getFontFamily( fallBack.getCSSText() );
}
if ( family == null ) {
// If this case happens, the output-processor meta-data does not provide a sensible
// fall-back value. As we cannot continue without a font, we fail here instead of
// waiting for a NullPointer or other weird error later.
throw new IllegalArgumentException( "Default family is invalid. Aborting." );
}
}
reusableFontContext.setAntiAliased( antiAliasing );
reusableFontContext.setFontSize( fontSize );
reusableFontContext.setEncoding( encoding );
reusableFontContext.setEmbedded( embedded );
final FontRecord record = family.getFontRecord( bold, italics );
final FontMetrics fm = getFontStorage().getFontMetrics( record.getIdentifier(), reusableFontContext );
if ( fm == null ) {
// If this case happens, then the previous steps of mapping the font name into sensible
// defaults failed. The font-system's font-registry is not in sync with the actual font-metrics
// provider (which indicates that the LibFonts font-system implementation is invalid).
throw new NullPointerException( "FontMetrics returned from factory is null." );
}
fontMetricsCache.put( new FontMetricsKey( lookupKey ), fm );
return fm;
}
/**
* Returns the font metrics for the font specified in the style sheet.
* <p/>
* <B>NOTE: This method will throw an <code>IllegalArgumentException</code> if the specified font family can not be
* found and the default font family can not be found</B>
*
* @param styleSheet ths style sheet from which the font information will be extracted
* @return FontMetrics for the specified font. If the font family can not be found, the FontMetrics for the default
* font family will be returned
* @throws IllegalArgumentException indicated the font metrics could not be determined (this is thrown since methods
* depending upon this method can not handle a <code>null</code> return).
*/
public FontMetrics getFontMetrics( final LayoutStyle styleSheet ) throws IllegalArgumentException {
final CSSValue fontFamily = getNormalizedFontFamilyName( styleSheet.getValue( FontStyleKeys.FONT_FAMILY ) );
if ( fontFamily == null ) {
// If this case happens, the stylesheet is not implemented correctly. At that point,
// we have to assume that the whole engine is no longer behaving valid and therefore we
// abort early.
throw new IllegalArgumentException( "No valid font family specified." );
}
final String fontName;
if ( fontFamily instanceof CSSStringValue ) {
final CSSStringValue svalue = (CSSStringValue) fontFamily;
fontName = svalue.getValue();
} else {
fontName = fontFamily.getCSSText();
}
final CSSValue value = styleSheet.getValue( FontStyleKeys.FONT_SIZE );
final int resolution = (int) getNumericFeatureValue( OutputProcessorFeature.DEVICE_RESOLUTION );
final double fontSize = StyleSheetUtility.convertLengthToDouble( value, resolution );
final boolean antiAliasing = FontSmooth.ALWAYS.equals( styleSheet.getValue( FontStyleKeys.FONT_SMOOTH ) );
final CSSValue boldVal = styleSheet.getValue( FontStyleKeys.FONT_WEIGHT );
final CSSValue italicsVal = styleSheet.getValue( FontStyleKeys.FONT_STYLE );
return getFontMetrics( fontName, fontSize, computeBold( boldVal ), computeItalics( italicsVal ), "UTF-8", false,
antiAliasing );
}
private boolean computeItalics( final CSSValue value ) {
if ( FontStyle.NORMAL.equals( value ) ) {
return false;
}
return true;
}
private boolean computeBold( final CSSValue value ) {
if ( CSSNumericType.NUMBER.equals( value.getType() ) == false ) {
return false;
}
CSSNumericValue nvalue = (CSSNumericValue) value;
return nvalue.getValue() >= 700;
}
public void commit() {
fontStorage.commit();
}
public PageSize getDefaultPageSize() {
return PageSize.A4;
}
/**
* Resolve one of the built-in fonts.
*
* @param name
* @return
*/
public CSSValue getNormalizedFontFamilyName( final CSSValue name ) {
final CSSValue retval = (CSSValue) fontFamilyMapping.get( name );
if ( retval != null ) {
return retval;
}
if ( name != null ) {
return name;
}
return getDefaultFontFamily();
}
public CSSValue getDefaultFontFamily() {
final CSSValue retval = (CSSValue) fontFamilyMapping.get( null );
if ( retval == null ) {
throw new IllegalStateException();
}
return retval;
}
}