Created
April 10, 2018 14:47
-
-
Save tmorgner/194a2ca3d03d82922193486cad88ee59 to your computer and use it in GitHub Desktop.
PRD-5998 fix
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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) 2006 - 2017 Hitachi Vantara and Contributors. All rights reserved. | |
*/ | |
package org.pentaho.reporting.libraries.fonts.awt; | |
import org.apache.commons.logging.Log; | |
import org.apache.commons.logging.LogFactory; | |
import org.pentaho.reporting.libraries.base.util.DebugLog; | |
import org.pentaho.reporting.libraries.fonts.LibFontsDefaults; | |
import org.pentaho.reporting.libraries.fonts.encoding.CodePointUtilities; | |
import org.pentaho.reporting.libraries.fonts.registry.BaselineInfo; | |
import org.pentaho.reporting.libraries.fonts.registry.FontContext; | |
import org.pentaho.reporting.libraries.fonts.registry.FontMetrics; | |
import org.pentaho.reporting.libraries.fonts.registry.FontNativeContext; | |
import org.pentaho.reporting.libraries.fonts.tools.FontStrictGeomUtility; | |
import java.awt.*; | |
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; | |
/** | |
* Creation-Date: 16.12.2005, 21:09:39 | |
* | |
* @author Thomas Morgner | |
*/ | |
public class AWTFontMetrics implements FontMetrics { | |
private static final Log logger = LogFactory.getLog(AWTFontMetrics.class); | |
private static final Graphics2D[] graphics = new Graphics2D[ 4 ]; | |
static { | |
graphics[ 0 ] = produce( false, false ); | |
graphics[ 1 ] = produce( true, false ); | |
graphics[ 2 ] = produce( false, true ); | |
graphics[ 3 ] = produce( true, true ); | |
} | |
private static Graphics2D produce( boolean antiAlias, boolean fractMetrics ) { | |
final BufferedImage image = new BufferedImage | |
( 1, 1, BufferedImage.TYPE_INT_ARGB ); | |
final Graphics2D g2 = image.createGraphics(); | |
if ( antiAlias ) { | |
g2.setRenderingHint | |
( RenderingHints.KEY_TEXT_ANTIALIASING, | |
RenderingHints.VALUE_TEXT_ANTIALIAS_ON ); | |
} else { | |
g2.setRenderingHint | |
( RenderingHints.KEY_TEXT_ANTIALIASING, | |
RenderingHints.VALUE_TEXT_ANTIALIAS_OFF ); | |
} | |
if ( fractMetrics ) { | |
g2.setRenderingHint( RenderingHints.KEY_FRACTIONALMETRICS, | |
RenderingHints.VALUE_FRACTIONALMETRICS_ON ); | |
} else { | |
g2.setRenderingHint( RenderingHints.KEY_FRACTIONALMETRICS, | |
RenderingHints.VALUE_FRACTIONALMETRICS_OFF ); | |
} | |
return g2; | |
} | |
private FontNativeContext record; | |
private Font font; | |
private long maxCharAdvance; | |
private char[] cpBuffer; | |
private FontRenderContext frc; | |
private long xheight; | |
private long ascent; | |
private long descent; | |
private long[] cachedWidths; | |
private BaselineInfo[] cachedBaselines; | |
private long leading; | |
private long maxAscent; | |
private long maxDescent; | |
private boolean uniformLineMetrics; | |
public AWTFontMetrics( final FontNativeContext record, final Font font, final FontContext context ) { | |
this.record = record; | |
this.font = font; | |
this.frc = new FontRenderContext( null, context.isAntiAliased(), context.isFractionalMetrics() ); | |
if ("MS UI Gothic".equals(font.getFamily())){ | |
DebugLog.logHere(); | |
} | |
final Graphics2D graphics = createGraphics( context ); | |
// This is some sort of evil hack and does only play well as long as we deal with scalable fonts | |
// It will happily cause troubles with bitmap fonts. | |
// Windows seems to have an arbitrary limit on large font sizes so anything larger than 16000 is | |
// not safe to use (to give margin of error as I assume they use 16bit signed int values there | |
final float size2D = font.getSize2D(); | |
if (size2D * 1000 < 16000) { | |
final Font derivedFont = font.deriveFont(size2D * 1000); | |
java.awt.FontMetrics fontMetrics = graphics.getFontMetrics(derivedFont); | |
this.leading = fontMetrics.getLeading(); | |
this.maxAscent = fontMetrics.getMaxAscent(); | |
this.maxDescent = fontMetrics.getMaxDescent(); | |
this.uniformLineMetrics = fontMetrics.hasUniformLineMetrics(); | |
} else if (size2D * 100 < 16000) { | |
final Font derivedFont = font.deriveFont(size2D * 100); | |
java.awt.FontMetrics fontMetrics = graphics.getFontMetrics(derivedFont); | |
this.leading = fontMetrics.getLeading() * 10; | |
this.maxAscent = fontMetrics.getMaxAscent() * 10; | |
this.maxDescent = fontMetrics.getMaxDescent() * 10; | |
this.uniformLineMetrics = fontMetrics.hasUniformLineMetrics(); | |
} else if (size2D * 10 < 16000) { | |
final Font derivedFont = font.deriveFont(size2D * 100); | |
java.awt.FontMetrics fontMetrics = graphics.getFontMetrics(derivedFont); | |
this.leading = fontMetrics.getLeading() * 100; | |
this.maxAscent = fontMetrics.getMaxAscent() * 100; | |
this.maxDescent = fontMetrics.getMaxDescent() * 100; | |
this.uniformLineMetrics = fontMetrics.hasUniformLineMetrics(); | |
} else { | |
final Font derivedFont = font.deriveFont(size2D); | |
java.awt.FontMetrics fontMetrics = graphics.getFontMetrics(derivedFont); | |
this.leading = fontMetrics.getLeading() * 1000; | |
this.maxAscent = fontMetrics.getMaxAscent() * 1000; | |
this.maxDescent = fontMetrics.getMaxDescent() * 1000; | |
} | |
final Rectangle2D rect = this.font.getMaxCharBounds( frc ); | |
this.maxCharAdvance = FontStrictGeomUtility.toInternalValue( rect.getWidth() ); | |
this.ascent = FontStrictGeomUtility.toInternalValue( -rect.getY() ); | |
this.descent = FontStrictGeomUtility.toInternalValue( rect.getHeight() + rect.getY()); | |
if (this.maxAscent < ascent) { | |
logger.debug("Max Ascent wrong for font " + font.getFamily() + "."); | |
this.maxAscent = ascent; | |
} | |
if (this.maxDescent < descent) { | |
logger.debug("Max Descent wrong for font " + font.getFamily() + "."); | |
this.maxDescent = descent; | |
} | |
final GlyphVector gv = font.createGlyphVector( frc, "x" ); | |
final Rectangle2D bounds = gv.getVisualBounds(); | |
this.xheight = FontStrictGeomUtility.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; | |
} | |
return graphics[ idx ]; | |
} | |
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 leading; //FontStrictGeomUtility.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 maxAscent; //FontStrictGeomUtility.toInternalValue(fontMetrics.getMaxAscent()); | |
} | |
public long getMaxDescent() { | |
return maxDescent; //FontStrictGeomUtility.toInternalValue(fontMetrics.getMaxDescent()); | |
} | |
public long getMaxHeight() { | |
return getMaxAscent() + getMaxDescent() + getLeading(); | |
} | |
public long getMaxCharAdvance() { | |
return maxCharAdvance; | |
} | |
public 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 = FontStrictGeomUtility.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 FontStrictGeomUtility.toInternalValue( lm.getWidth() ); | |
} else { | |
return 0; | |
} | |
} | |
/** | |
* This method is <b>EXPENSIVE</b>. | |
* | |
* @param previous | |
* @param character | |
* @return | |
*/ | |
public 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 = FontStrictGeomUtility.toInternalValue( gv.getGlyphPosition( limit ).getX() ); | |
final long renderedWidth = FontStrictGeomUtility.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). | |
* <p/> | |
* 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 = FontStrictGeomUtility.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 + FontStrictGeomUtility.toInternalValue( bls[ Font.ROMAN_BASELINE ] ) ); | |
info.setBaseline( BaselineInfo.HANGING, | |
base + FontStrictGeomUtility.toInternalValue( bls[ Font.HANGING_BASELINE ] ) ); | |
info.setDominantBaseline( BaselineInfo.CENTRAL ); | |
break; | |
} | |
case Font.HANGING_BASELINE: { | |
info.setBaseline( BaselineInfo.CENTRAL, | |
base + FontStrictGeomUtility.toInternalValue( bls[ Font.CENTER_BASELINE ] ) ); | |
info.setBaseline( BaselineInfo.ALPHABETIC, | |
base + FontStrictGeomUtility.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 + FontStrictGeomUtility.toInternalValue( bls[ Font.CENTER_BASELINE ] ) ); | |
info.setBaseline( BaselineInfo.HANGING, | |
base + FontStrictGeomUtility.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; | |
} | |
/** | |
* Is it guaranteed that the font always returns the same baseline info objct? | |
* | |
* @return true, if the baseline info in question is always the same, false otherwise. | |
*/ | |
public boolean isUniformFontMetrics() { | |
return uniformLineMetrics; | |
} | |
/** | |
* 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; | |
} | |
public FontNativeContext getNativeContext() { | |
return record; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment