/**
* ===========================================
* LibFonts : a free Java font reading library
* ===========================================
*
* Project Info: http://reporting.pentaho.org/libfonts/
*
* (C) Copyright 2006-2007, by Pentaho Corporation and Contributors.
*
* This library 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 2.1 of the License, or (at your option) any later version.
*
* This library 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 this
* library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307, USA.
*
* [Java is a trademark or registered trademark of Sun Microsystems, Inc.
* in the United States and other countries.]
*
* ------------
* $Id: AWTFontMetrics.java 3523 2007-10-16 11:03:09Z tmorgner $
* ------------
* (C) Copyright 2006-2007, by Pentaho Corporation.
*/
package org.jfree.fonts.awt;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.font.LineMetrics;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.util.Arrays;
import org.jfree.fonts.encoding.CodePointUtilities;
import org.jfree.fonts.registry.BaselineInfo;
import org.jfree.fonts.registry.FontContext;
import org.jfree.fonts.registry.FontMetrics;
import org.jfree.fonts.LibFontsDefaults;
import org.jfree.fonts.tools.StrictGeomUtility;
/**
* Creation-Date: 16.12.2005, 21:09:39
*
* @author Thomas Morgner
*/
public class AWTFontMetrics implements FontMetrics
{
private static final Graphics2D[] graphics = new Graphics2D[4];
private Font font;
//private FontContext context;
private java.awt.FontMetrics fontMetrics;
private long maxCharAdvance;
private char[] cpBuffer;
private FontRenderContext frc;
private long xheight;
private long ascent;
private long descent;
private long[] cachedWidths;
private BaselineInfo[] cachedBaselines;
public AWTFontMetrics(final Font font, final FontContext context)
{
this.font = font;
this.frc = new FontRenderContext(null, context.isAntiAliased(), context.isFractionalMetrics());
final Graphics2D graphics = createGraphics(context);
this.fontMetrics = graphics.getFontMetrics(font);
final Rectangle2D rect = this.font.getMaxCharBounds(frc);
this.maxCharAdvance = StrictGeomUtility.toInternalValue(rect.getWidth());
this.ascent = StrictGeomUtility.toInternalValue(-rect.getY());
this.descent = StrictGeomUtility.toInternalValue(rect.getHeight() + rect.getY());
final GlyphVector gv = font.createGlyphVector(frc, "x");
final Rectangle2D bounds = gv.getVisualBounds();
this.xheight = StrictGeomUtility.toInternalValue(bounds.getHeight());
this.cpBuffer = new char[4];
this.cachedBaselines = new BaselineInfo[256- 32];
this.cachedWidths = new long[256- 32];
Arrays.fill(cachedWidths, -1);
}
protected Graphics2D createGraphics(final FontContext context)
{
int idx = 0;
if (context.isAntiAliased())
{
idx += 1;
}
if (context.isFractionalMetrics())
{
idx += 2;
}
synchronized(graphics)
{
final Graphics2D retval = graphics[idx];
if (retval == null)
{
final BufferedImage image = new BufferedImage
(1, 1, BufferedImage.TYPE_INT_ARGB);
final Graphics2D g2 = image.createGraphics();
if (context.isAntiAliased())
{
g2.setRenderingHint
(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
}
else
{
g2.setRenderingHint
(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
}
if (context.isFractionalMetrics())
{
g2.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
RenderingHints.VALUE_FRACTIONALMETRICS_ON);
}
else
{
g2.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
RenderingHints.VALUE_FRACTIONALMETRICS_OFF);
}
graphics[idx] = g2;
return g2;
}
return retval;
}
}
public Font getFont()
{
return font;
}
/**
* From the baseline to the
*
* @return
*/
public long getAscent()
{
return ascent;
}
public long getDescent()
{
return descent;
}
public long getLeading()
{
return StrictGeomUtility.toInternalValue(fontMetrics.getLeading());
}
/**
* The height of the lowercase 'x'. This is used as hint, which size the
* lowercase characters will have.
*
* @return
*/
public long getXHeight()
{
return xheight;
}
public long getOverlinePosition()
{
return getLeading() - Math.max (1000, getMaxHeight() / 20);
}
public long getUnderlinePosition()
{
return getLeading() + getMaxAscent() + Math.max (1000, getMaxHeight() / 20);
}
public long getStrikeThroughPosition()
{
return getMaxAscent() - (long) (LibFontsDefaults.DEFAULT_STRIKETHROUGH_POSITION * getXHeight());
}
public long getMaxAscent()
{
return StrictGeomUtility.toInternalValue(fontMetrics.getMaxAscent());
}
public long getMaxDescent()
{
return StrictGeomUtility.toInternalValue(fontMetrics.getMaxDescent());
}
public long getMaxHeight()
{
return getMaxAscent() + getMaxDescent() + getLeading();
}
public long getMaxCharAdvance()
{
return maxCharAdvance;
}
public synchronized long getCharWidth(final int character)
{
if (character >=32 && character < 256)
{
// can be cached ..
final int index = character - 32;
final long cachedWidth = cachedWidths[index];
if (cachedWidth >= 0)
{
return cachedWidth;
}
final int retval = CodePointUtilities.toChars(character, cpBuffer, 0);
if (retval > 0)
{
final Rectangle2D lm = font.getStringBounds(cpBuffer, 0, retval, frc);
final long width = StrictGeomUtility.toInternalValue(lm.getWidth());
cachedWidths[index] = width;
return width;
}
else
{
cachedWidths[index] = 0;
return 0;
}
}
final int retval = CodePointUtilities.toChars(character, cpBuffer, 0);
if (retval > 0)
{
final Rectangle2D lm = font.getStringBounds(cpBuffer, 0, retval, frc);
return StrictGeomUtility.toInternalValue(lm.getWidth());
}
else
{
return 0;
}
}
/**
* This method is <b>EXPENSIVE</b>.
* @param previous
* @param character
* @return
*/
public synchronized long getKerning(final int previous, final int character)
{
final int retvalC1 = CodePointUtilities.toChars(previous, cpBuffer, 0);
if (retvalC1 <= 0)
{
return 0;
}
final int retvalC2 = CodePointUtilities.toChars(character, cpBuffer, retvalC1);
if (retvalC2 > 0)
{
final int limit = (retvalC1 + retvalC2);
final GlyphVector gv = font.createGlyphVector(frc, new String (cpBuffer, 0, limit));
final long totalSize = StrictGeomUtility.toInternalValue(gv.getGlyphPosition(limit).getX());
final long renderedWidth = StrictGeomUtility.toInternalValue(gv.getOutline().getBounds2D().getWidth());
return totalSize - renderedWidth;
}
else
{
return 0;
}
}
/**
* Baselines are defined for scripts, not glyphs. A glyph carries script
* information most of the time (unless it is a neutral characters or just
* weird).
*
* The baseline info does not take any leading into account.
*
* @param c the character that is used to select the script type.
* @return
*/
public BaselineInfo getBaselines(final int c, BaselineInfo info)
{
final boolean cacheable = (c >=32 && c < 256);
if (cacheable)
{
final BaselineInfo fromCache = cachedBaselines[c - 32];
if (fromCache != null)
{
if (info == null)
{
info = new BaselineInfo();
}
info.update(fromCache);
return info;
}
}
cpBuffer[0] = (char) (c & 0xFFFF);
final LineMetrics lm = font.getLineMetrics(cpBuffer, 0, 1, frc);
final float[] bls = lm.getBaselineOffsets();
final int idx = lm.getBaselineIndex();
if (info == null)
{
info = new BaselineInfo();
}
// The ascent is local - but we need the global baseline, relative to the
// MaxAscent.
final long maxAscent = getMaxAscent();
final long ascent = StrictGeomUtility.toInternalValue(lm.getAscent());
final long delta = maxAscent - ascent;
info.setBaseline(BaselineInfo.MATHEMATICAL, delta + maxAscent - getXHeight());
info.setBaseline(BaselineInfo.IDEOGRAPHIC, getMaxHeight());
info.setBaseline(BaselineInfo.MIDDLE, maxAscent / 2);
final long base = delta + ascent;
switch (idx)
{
case Font.CENTER_BASELINE:
{
info.setBaseline(BaselineInfo.CENTRAL, base);
info.setBaseline(BaselineInfo.ALPHABETIC, base + StrictGeomUtility.toInternalValue(bls[Font.ROMAN_BASELINE]));
info.setBaseline(BaselineInfo.HANGING, base + StrictGeomUtility.toInternalValue(bls[Font.HANGING_BASELINE]));
info.setDominantBaseline(BaselineInfo.CENTRAL);
break;
}
case Font.HANGING_BASELINE:
{
info.setBaseline(BaselineInfo.CENTRAL, base + StrictGeomUtility.toInternalValue(bls[Font.CENTER_BASELINE]));
info.setBaseline(BaselineInfo.ALPHABETIC, base + StrictGeomUtility.toInternalValue(bls[Font.ROMAN_BASELINE]));
info.setBaseline(BaselineInfo.HANGING, base);
info.setDominantBaseline(BaselineInfo.HANGING);
break;
}
default: // ROMAN Base-line
{
info.setBaseline(BaselineInfo.ALPHABETIC, base);
info.setBaseline(BaselineInfo.CENTRAL, base + StrictGeomUtility.toInternalValue(bls[Font.CENTER_BASELINE]));
info.setBaseline(BaselineInfo.HANGING, base + StrictGeomUtility.toInternalValue(bls[Font.HANGING_BASELINE]));
info.setDominantBaseline(BaselineInfo.ALPHABETIC);
break;
}
}
if (cacheable)
{
final BaselineInfo cached = new BaselineInfo();
cached.update(info);
cachedBaselines[c - 32] = cached;
}
return info;
}
/**
* Returns zero, as the AWT renderer will take care of the italic rendering already. We do not have
* to apply any special transformations to the font to make it look italic.
*
* @return always zero.
*/
public long getItalicAngle()
{
return 0;
}
}