/*! * 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; } }