diff options
Diffstat (limited to 'src/jogl')
6 files changed, 783 insertions, 258 deletions
diff --git a/src/jogl/classes/com/jogamp/graph/curve/opengl/TextRegionUtil.java b/src/jogl/classes/com/jogamp/graph/curve/opengl/TextRegionUtil.java index 344e1e51f..5aa2be258 100644 --- a/src/jogl/classes/com/jogamp/graph/curve/opengl/TextRegionUtil.java +++ b/src/jogl/classes/com/jogamp/graph/curve/opengl/TextRegionUtil.java @@ -74,41 +74,41 @@ public class TextRegionUtil { /** * Visit each {@link Font.Glyph}'s {@link OutlineShape} with the given {@link ShapeVisitor} - * additionally passing the progressed {@link AffineTransform}. - * The latter reflects the given font metric, pixelSize and hence character position. + * additionally passing the progressed {@link AffineTransform} in font-units. + * The latter reflects the given font metric in font-units and hence character position. * @param visitor * @param transform optional given transform * @param font the target {@link Font} - * @param pixelSize Use {@link Font#getPixelSize(float, float)} for resolution correct pixel-size. * @param str string text * @param temp1 temporary AffineTransform storage, mandatory, will be passed to {@link ShapeVisitor#visit(OutlineShape, AffineTransform)} and can be modified. * @param temp2 temporary AffineTransform storage, mandatory, can be re-used in {@link ShapeVisitor#visit(OutlineShape, AffineTransform)} by user code. */ public static void processString(final ShapeVisitor visitor, final AffineTransform transform, - final Font font, final float pixelSize, final CharSequence str, + final Font font, final CharSequence str, final AffineTransform temp1, final AffineTransform temp2) { final int charCount = str.length(); // region.setFlipped(true); final Font.Metrics metrics = font.getMetrics(); - final float lineHeight = font.getLineHeight(pixelSize); + final int lineHeight = font.getLineHeightFU(); - final float scale = metrics.getScale(pixelSize); - - float y = 0; - float advanceTotal = 0; + int y = 0; + int advanceTotal = 0; + char left_character = 0; + int left_glyphid = 0; for(int i=0; i< charCount; i++) { final char character = str.charAt(i); if( '\n' == character ) { y -= lineHeight; advanceTotal = 0; + left_glyphid = 0; + left_character = 0; } else if (character == ' ') { - advanceTotal += font.getAdvanceWidth(Glyph.ID_SPACE, pixelSize); + advanceTotal += font.getAdvanceWidthFU(Glyph.ID_SPACE); + left_glyphid = 0; + left_character = character; } else { - if(Region.DEBUG_INSTANCE) { - System.err.println("XXXXXXXXXXXXXXx char: "+character+", scale: "+scale+"; translate: "+advanceTotal+", "+y); - } // reset transform if( null != transform ) { temp1.setTransform(transform); @@ -116,51 +116,65 @@ public class TextRegionUtil { temp1.setToIdentity(); } temp1.translate(advanceTotal, y, temp2); - temp1.scale(scale, scale, temp2); final Font.Glyph glyph = font.getGlyph(character); final OutlineShape glyphShape = glyph.getShape(); if( null == glyphShape ) { + left_glyphid = 0; continue; } visitor.visit(glyphShape, temp1); - - advanceTotal += glyph.getAdvance(pixelSize, true); + final int right_glyphid = glyph.getID(); + final int kern = font.getKerningFU(left_glyphid, right_glyphid); + final int advance = glyph.getAdvanceFU(); + advanceTotal += advance + kern; + if( Region.DEBUG_INSTANCE && 0 != left_character && kern > 0f ) { + System.err.println(": '"+left_character+"'/"+left_glyphid+" -> '"+character+"'/"+right_glyphid+ + ": a "+advance+"px + k ["+kern+"em, "+kern+"px = "+(advance+kern)+"px -> "+advanceTotal+"px, y "+y); + } + // advanceTotal += glyph.getAdvance(pixelSize, true) + // + font.getKerning(left_glyphid, right_glyphid); + left_glyphid = right_glyphid; + left_character = character; } } } /** - * Add the string in 3D space w.r.t. the font and pixelSize at the end of the {@link GLRegion}. + * Add the string in 3D space w.r.t. the font using font-units at the end of the {@link GLRegion}. + * <p> + * The resulting GLRegion should be scaled by the chosen pixelSize of the font divided by the font's unitsPerEM. + * </p> * @param region the {@link GLRegion} sink * @param vertexFactory vertex impl factory {@link Factory} * @param font the target {@link Font} - * @param pixelSize Use {@link Font#getPixelSize(float, float)} for resolution correct pixel-size. * @param str string text * @param rgbaColor if {@link Region#hasColorChannel()} RGBA color must be passed, otherwise value is ignored. * @param temp1 temporary AffineTransform storage, mandatory * @param temp2 temporary AffineTransform storage, mandatory */ public static void addStringToRegion(final GLRegion region, final Factory<? extends Vertex> vertexFactory, - final Font font, final float pixelSize, final CharSequence str, final float[] rgbaColor, + final Font font, final CharSequence str, final float[] rgbaColor, final AffineTransform temp1, final AffineTransform temp2) { final ShapeVisitor visitor = new ShapeVisitor() { + @Override public final void visit(final OutlineShape shape, final AffineTransform t) { region.addOutlineShape(shape, t, region.hasColorChannel() ? rgbaColor : null); } }; - processString(visitor, null, font, pixelSize, str, temp1, temp2); + processString(visitor, null, font, str, temp1, temp2); } /** - * Render the string in 3D space w.r.t. the font and pixelSize - * using a cached {@link GLRegion} for reuse. + * Render the string in 3D space w.r.t. the font using font-units at the end of an internally cached {@link GLRegion}. + * <p> + * The resulting GLRegion should be scaled by the chosen pixelSize of the font divided by the font's unitsPerEM. + * </p> * <p> * Cached {@link GLRegion}s will be destroyed w/ {@link #clear(GL2ES2)} or to free memory. * </p> * @param gl the current GL state * @param renderer TODO * @param font {@link Font} to be used - * @param pixelSize Use {@link Font#getPixelSize(float, float)} for resolution correct pixel-size. * @param str text to be rendered * @param rgbaColor if {@link Region#hasColorChannel()} RGBA color must be passed, otherwise value is ignored. * @param sampleCount desired multisampling sample count for msaa-rendering. @@ -168,34 +182,35 @@ public class TextRegionUtil { * @throws Exception if TextRenderer not initialized */ public void drawString3D(final GL2ES2 gl, - final RegionRenderer renderer, final Font font, final float pixelSize, - final CharSequence str, final float[] rgbaColor, final int[/*1*/] sampleCount) { + final RegionRenderer renderer, final Font font, final CharSequence str, + final float[] rgbaColor, final int[/*1*/] sampleCount) { if( !renderer.isInitialized() ) { throw new GLException("TextRendererImpl01: not initialized!"); } final int special = 0; - GLRegion region = getCachedRegion(font, str, pixelSize, special); + GLRegion region = getCachedRegion(font, str, special); if(null == region) { region = GLRegion.create(renderModes, null); - addStringToRegion(region, renderer.getRenderState().getVertexFactory(), font, pixelSize, str, rgbaColor, tempT1, tempT2); - addCachedRegion(gl, font, str, pixelSize, special, region); + addStringToRegion(region, renderer.getRenderState().getVertexFactory(), font, str, rgbaColor, tempT1, tempT2); + addCachedRegion(gl, font, str, special, region); } region.draw(gl, renderer, sampleCount); } /** - * Render the string in 3D space w.r.t. the font and pixelSize - * using a temporary {@link GLRegion}, which will be destroyed afterwards. + * Render the string in 3D space w.r.t. the font using font-units at the end of an internally temporary {@link GLRegion}. + * <p> + * The resulting GLRegion should be scaled by the chosen pixelSize of the font divided by the font's unitsPerEM. + * </p> * <p> * In case of a multisampling region renderer, i.e. {@link Region#VBAA_RENDERING_BIT}, recreating the {@link GLRegion} * is a huge performance impact. - * In such case better use {@link #drawString3D(GL2ES2, GLRegion, RegionRenderer, Font, float, CharSequence, float[], int[], AffineTransform, AffineTransform)} + * In such case better use {@link #drawString3D(GL2ES2, GLRegion, RegionRenderer, Font, CharSequence, float[], int[], AffineTransform, AffineTransform)} * instead. * </p> * @param gl the current GL state * @param renderModes TODO * @param font {@link Font} to be used - * @param pixelSize Use {@link Font#getPixelSize(float, float)} for resolution correct pixel-size. * @param str text to be rendered * @param rgbaColor if {@link Region#hasColorChannel()} RGBA color must be passed, otherwise value is ignored. * @param sampleCount desired multisampling sample count for msaa-rendering. @@ -205,24 +220,26 @@ public class TextRegionUtil { * @throws Exception if TextRenderer not initialized */ public static void drawString3D(final GL2ES2 gl, final int renderModes, - final RegionRenderer renderer, final Font font, final float pixelSize, - final CharSequence str, final float[] rgbaColor, final int[/*1*/] sampleCount, - final AffineTransform temp1, final AffineTransform temp2) { + final RegionRenderer renderer, final Font font, final CharSequence str, + final float[] rgbaColor, final int[/*1*/] sampleCount, final AffineTransform temp1, + final AffineTransform temp2) { if(!renderer.isInitialized()){ throw new GLException("TextRendererImpl01: not initialized!"); } final GLRegion region = GLRegion.create(renderModes, null); - addStringToRegion(region, renderer.getRenderState().getVertexFactory(), font, pixelSize, str, rgbaColor, temp1, temp2); + addStringToRegion(region, renderer.getRenderState().getVertexFactory(), font, str, rgbaColor, temp1, temp2); region.draw(gl, renderer, sampleCount); region.destroy(gl); } /** - * Render the string in 3D space w.r.t. the font and pixelSize - * using the given {@link GLRegion}, which will {@link GLRegion#clear(GL2ES2) cleared} beforehand. + * Render the string in 3D space w.r.t. the font using font-units at the end of the given {@link GLRegion}, + * which will {@link GLRegion#clear(GL2ES2) cleared} beforehand. + * <p> + * The resulting GLRegion should be scaled by the chosen pixelSize of the font divided by the font's unitsPerEM. + * </p> * @param gl the current GL state * @param font {@link Font} to be used - * @param pixelSize Use {@link Font#getPixelSize(float, float)} for resolution correct pixel-size. * @param str text to be rendered * @param rgbaColor if {@link Region#hasColorChannel()} RGBA color must be passed, otherwise value is ignored. * @param sampleCount desired multisampling sample count for msaa-rendering. @@ -232,14 +249,14 @@ public class TextRegionUtil { * @throws Exception if TextRenderer not initialized */ public static void drawString3D(final GL2ES2 gl, final GLRegion region, final RegionRenderer renderer, - final Font font, final float pixelSize, final CharSequence str, - final float[] rgbaColor, final int[/*1*/] sampleCount, - final AffineTransform temp1, final AffineTransform temp2) { + final Font font, final CharSequence str, final float[] rgbaColor, + final int[/*1*/] sampleCount, final AffineTransform temp1, + final AffineTransform temp2) { if(!renderer.isInitialized()){ throw new GLException("TextRendererImpl01: not initialized!"); } region.clear(gl); - addStringToRegion(region, renderer.getRenderState().getVertexFactory(), font, pixelSize, str, rgbaColor, temp1, temp2); + addStringToRegion(region, renderer.getRenderState().getVertexFactory(), font, str, rgbaColor, temp1, temp2); region.draw(gl, renderer, sampleCount); } @@ -297,13 +314,13 @@ public class TextRegionUtil { } } - protected final GLRegion getCachedRegion(final Font font, final CharSequence str, final float pixelSize, final int special) { - return stringCacheMap.get(getKey(font, str, pixelSize, special)); + protected final GLRegion getCachedRegion(final Font font, final CharSequence str, final int special) { + return stringCacheMap.get(getKey(font, str, special)); } - protected final void addCachedRegion(final GL2ES2 gl, final Font font, final CharSequence str, final float pixelSize, final int special, final GLRegion glyphString) { + protected final void addCachedRegion(final GL2ES2 gl, final Font font, final CharSequence str, final int special, final GLRegion glyphString) { if ( 0 != getCacheLimit() ) { - final String key = getKey(font, str, pixelSize, special); + final String key = getKey(font, str, special); final GLRegion oldRegion = stringCacheMap.put(key, glyphString); if ( null == oldRegion ) { // new entry .. @@ -313,8 +330,8 @@ public class TextRegionUtil { } } - protected final void removeCachedRegion(final GL2ES2 gl, final Font font, final CharSequence str, final int pixelSize, final int special) { - final String key = getKey(font, str, pixelSize, special); + protected final void removeCachedRegion(final GL2ES2 gl, final Font font, final CharSequence str, final int special) { + final String key = getKey(font, str, special); final GLRegion region = stringCacheMap.remove(key); if(null != region) { region.destroy(gl); @@ -332,10 +349,10 @@ public class TextRegionUtil { } } - protected final String getKey(final Font font, final CharSequence str, final float pixelSize, final int special) { + protected final String getKey(final Font font, final CharSequence str, final int special) { final StringBuilder sb = new StringBuilder(); return font.getName(sb, Font.NAME_UNIQUNAME) - .append(".").append(str.hashCode()).append(".").append(Float.floatToIntBits(pixelSize)).append(special).toString(); + .append(".").append(str.hashCode()).append(".").append(special).toString(); } /** Default cache limit, see {@link #setCacheLimit(int)} */ diff --git a/src/jogl/classes/com/jogamp/graph/font/Font.java b/src/jogl/classes/com/jogamp/graph/font/Font.java index 597a7e76f..9f25b5481 100644 --- a/src/jogl/classes/com/jogamp/graph/font/Font.java +++ b/src/jogl/classes/com/jogamp/graph/font/Font.java @@ -37,7 +37,7 @@ import com.jogamp.opengl.math.geom.AABBox; * TrueType Font Specification: * <ul> * <li>http://www.freetype.org/freetype2/documentation.html</li> - * <li>http://developer.apple.com/fonts/ttrefman/rm06/Chap6.html</li> + * <li>https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6.html</li> * <li>http://www.microsoft.com/typography/SpecificationsOverview.mspx</li> * <li>http://www.microsoft.com/typography/otspec/</li> * </ul> @@ -76,20 +76,54 @@ public interface Font { * Depending on the font's direction, horizontal or vertical, * the following tables shall be used: * - * Vertical http://developer.apple.com/fonts/TTRefMan/RM06/Chap6vhea.html - * Horizontal http://developer.apple.com/fonts/TTRefMan/RM06/Chap6hhea.html + * Vertical https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6vhea.html + * Horizontal https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6hhea.html */ public interface Metrics { - float getAscent(final float pixelSize); - float getDescent(final float pixelSize); - float getLineGap(final float pixelSize); - float getMaxExtend(final float pixelSize); - float getScale(final float pixelSize); + /** + * @return ascent in font-units to be divided by {@link #getUnitsPerEM()} + */ + int getAscentFU(); + + /** + * @return descent in font-units to be divided by {@link #getUnitsPerEM()} + */ + int getDescentFU(); + + /** + * @return line-gap in font-units to be divided by {@link #getUnitsPerEM()} + */ + int getLineGapFU(); + + /** + * @return max-extend in font-units to be divided by {@link #getUnitsPerEM()} + */ + int getMaxExtendFU(); + + /** Returns the font's units per EM from the 'head' table. One em square covers one glyph. */ + int getUnitsPerEM(); + + /** + * Return fractional font em-size [0..1], i.e. funits divided by {@link #getUnitsPerEM()}, i.e. + * <pre> + * return funits / head.unitsPerEM; + * </pre> + * @param funits smallest font unit, where {@link #getUnitsPerEM()} square covers whole glyph + * @return fractional font em-size [0..1] + */ + float getScale(final int funits); + + /** + * @param dest AABBox instance set to this metrics boundary in font-units + * @return the given and set AABBox 'dest' in font units + */ + AABBox getBBox(final AABBox dest); + /** * @param dest AABBox instance set to this metrics boundary w/ given pixelSize - * @param pixelSize Use <code>pointSize * resolution</code> for resolution correct pixel-size, see {@link Font#getPixelSize(float, float)} + * @param pixelSize pixel-size of font, for resolution correct pixel-size use {@link FontScale#toPixels(float, float)} * @param tmpV3 caller provided temporary 3-component vector - * @return the given and set AABBox 'dest' + * @return the given and set AABBox 'dest' in pixel size */ AABBox getBBox(final AABBox dest, final float pixelSize, final float[] tmpV3); } @@ -108,118 +142,192 @@ public interface Font { public static final int ID_CR = 2; public static final int ID_SPACE = 3; - public Font getFont(); - public char getSymbol(); - public short getID(); - public AABBox getBBox(); + Font getFont(); + char getSymbol(); + int getID(); + /** - * - * @param pixelSize Use <code>pointSize * resolution</code> for resolution correct pixel-size, see {@link Font#getPixelSize(float, float)} - * @return + * Return fractional font em-size [0..1], i.e. funits divided by {@link #getUnitsPerEM()}, i.e. + * <pre> + * return funits / head.unitsPerEM; + * </pre> + * @param funits smallest font unit, where {@link #getUnitsPerEM()} square covers whole glyph + * @return fractional font em-size [0..1] */ - public float getScale(final float pixelSize); + float getScale(final int funits); + /** * @param dest AABBox instance set to this metrics boundary w/ given pixelSize - * @param pixelSize Use <code>pointSize * resolution</code> for resolution correct pixel-size, see {@link Font#getPixelSize(float, float)} + * @param pixelSize pixel-size of font, for resolution correct pixel-size use {@link FontScale#toPixels(float, float)} * @param tmpV3 caller provided temporary 3-component vector - * @return the given and set AABBox 'dest' + * @return the given and set AABBox 'dest' in pixel size + */ + AABBox getBBox(final AABBox dest, final float pixelSize, float[] tmpV3); + + /** + * Return the AABBox in font-units to be divided by unitsPerEM + * @param dest AABBox instance set to this metrics boundary in font-units + * @return the given and set AABBox 'dest' in font-units + */ + AABBox getBBoxFU(final AABBox dest); + + /** + * Return the AABBox in font-units to be divided by unitsPerEM */ - public AABBox getBBox(final AABBox dest, final float pixelSize, float[] tmpV3); + AABBox getBBoxFU(); + + /** Return advance in font units to be divided by unitsPerEM */ + int getAdvanceFU(); + + /** Return advance in font em-size [0..1] */ + float getAdvance(); + /** - * - * @param pixelSize Use <code>pointSize * resolution</code> for resolution correct pixel-size, see {@link Font#getPixelSize(float, float)} - * @param useFrationalMetrics - * @return + * @param pixelSize pixel-size of font, for resolution correct pixel-size use {@link FontScale#toPixels(float, float)} + * @return pixel size of advance */ - public float getAdvance(final float pixelSize, boolean useFrationalMetrics); - public OutlineShape getShape(); - public int hashCode(); + float getAdvance(final float pixelSize); + + OutlineShape getShape(); + + @Override + int hashCode(); } - public String getName(final int nameIndex); - public StringBuilder getName(final StringBuilder string, final int nameIndex); + String getName(final int nameIndex); + StringBuilder getName(final StringBuilder string, final int nameIndex); /** Shall return the family and subfamily name, separated a dash. * <p>{@link #getName(StringBuilder, int)} w/ {@link #NAME_FAMILY} and {@link #NAME_SUBFAMILY}</p> * <p>Example: "{@code Ubuntu-Regular}"</p> */ - public StringBuilder getFullFamilyName(final StringBuilder buffer); + StringBuilder getFullFamilyName(final StringBuilder buffer); + + StringBuilder getAllNames(final StringBuilder string, final String separator); - public StringBuilder getAllNames(final StringBuilder string, final String separator); + /** + * + * @param glyphID + * @param pixelSize pixel-size of font, for resolution correct pixel-size use {@link FontScale#toPixels(float, float)} + * @return pixel size of advance width + */ + float getAdvanceWidth(final int glyphID, final float pixelSize); /** - * <pre> - Font Scale Formula: - inch: 25.4 mm - pointSize: [point] = [1/72 inch] - - [1] Scale := pointSize * resolution / ( 72 points per inch * units_per_em ) - [2] PixelSize := pointSize * resolution / ( 72 points per inch ) - [3] Scale := PixelSize / units_per_em - * </pre> - * @param fontSize in point-per-inch - * @param resolution display resolution in dots-per-inch - * @return pixel-per-inch, pixelSize scale factor for font operations. + * Return advance-width of given glyphID in font em-size [0..1] + * @param glyphID */ - public float getPixelSize(final float fontSize /* points per inch */, final float resolution); + float getAdvanceWidth(final int glyphID); /** - * + * Return advance-width of given glyphID in font-units to be divided by unitsPerEM * @param glyphID - * @param pixelSize Use <code>pointSize * resolution</code> for resolution correct pixel-size, see {@link #getPixelSize(float, float)} - * @return */ - public float getAdvanceWidth(final int glyphID, final float pixelSize); - public Metrics getMetrics(); - public Glyph getGlyph(final char symbol); - public int getNumGlyphs(); + int getAdvanceWidthFU(final int glyphID); /** + * Returns the optional kerning inter-glyph distance within words in fractional font em-size [0..1]. * - * @param pixelSize Use <code>pointSize * resolution</code> for resolution correct pixel-size, see {@link #getPixelSize(float, float)} - * @return + * @param left_glyphid left glyph code id + * @param right_glyphid right glyph code id + * @return fractional font em-size distance [0..1] */ - public float getLineHeight(final float pixelSize); + float getKerning(final int left_glyphid, final int right_glyphid); + + /** + * Returns the optional kerning inter-glyph distance within words in fractional font-units to be divided by unitsPerEM + * + * @param left_glyphid left glyph code id + * @param right_glyphid right glyph code id + * @return font-units to be divided by unitsPerEM + */ + int getKerningFU(final int left_glyphid, final int right_glyphid); + + Metrics getMetrics(); + int getGlyphID(final char symbol); + Glyph getGlyph(final char symbol); + int getNumGlyphs(); + + /** + * + * @param pixelSize pixel-size of font, for resolution correct pixel-size use {@link FontScale#toPixels(float, float)} + * @return pixel size of line height + */ + float getLineHeight(final float pixelSize); + + /** + * Return line height in font em-size [0..1] + */ + float getLineHeight(); + + /** + * Return line height in font-units to be divided by unitsPerEM + */ + int getLineHeightFU(); + /** * * @param string - * @param pixelSize Use <code>pointSize * resolution</code> for resolution correct pixel-size, see {@link #getPixelSize(float, float)} - * @return + * @param pixelSize pixel-size of font, for resolution correct pixel-size use {@link FontScale#toPixels(float, float)} + * @return pixel size of metric width */ - public float getMetricWidth(final CharSequence string, final float pixelSize); + float getMetricWidth(final CharSequence string, final float pixelSize); + + /** Return metric-width in font em-size */ + float getMetricWidth(final CharSequence string); + + /** Return metric-width in font-units */ + int getMetricWidthFU(final CharSequence string); + /** * * @param string - * @param pixelSize Use <code>pointSize * resolution</code> for resolution correct pixel-size, see {@link #getPixelSize(float, float)} - * @param tmp - * @return + * @param pixelSize pixel-size of font, for resolution correct pixel-size use {@link FontScale#toPixels(float, float)} + * @return pixel size of metric height */ - public float getMetricHeight(final CharSequence string, final float pixelSize, final AABBox tmp); + float getMetricHeight(final CharSequence string, final float pixelSize); + + /** Return metric-height in font em-size */ + float getMetricHeight(final CharSequence string); + + /** Return metric-height in font-units */ + int getMetricHeightFU(final CharSequence string); + /** * Return the <i>layout</i> bounding box as computed by each glyph's metrics. - * The result is not pixel correct, bit reflects layout specific metrics. + * The result is not pixel correct, but reflects layout specific metrics. * <p> * See {@link #getPointsBounds(AffineTransform, CharSequence, float, AffineTransform, AffineTransform)} for pixel correct results. * </p> * @param string string text - * @param pixelSize Use <code>pointSize * resolution</code> for resolution correct pixel-size, see {@link #getPixelSize(float, float)} + * @param pixelSize pixel-size of font, for resolution correct pixel-size use {@link FontScale#toPixels(float, float)} */ - public AABBox getMetricBounds(final CharSequence string, final float pixelSize); + AABBox getMetricBounds(final CharSequence string, final float pixelSize); + + /** Return layout metric-bounds in font em-size, see {@link #getMetricBounds(CharSequence, float)} */ + AABBox getMetricBounds(final CharSequence string); + + /** Return layout metric-bounds in font-units, see {@link #getMetricBounds(CharSequence, float)} */ + AABBox getMetricBoundsFU(final CharSequence string); /** * Return the bounding box by taking each glyph's point-based bounding box into account. * @param transform optional given transform * @param string string text - * @param pixelSize Use <code>pointSize * resolution</code> for resolution correct pixel-size, see {@link #getPixelSize(float, float)} - * @param temp1 temporary AffineTransform storage, mandatory - * @param temp2 temporary AffineTransform storage, mandatory + * @param pixelSize pixel-size of font, for resolution correct pixel-size use {@link FontScale#toPixels(float, float)} */ - public AABBox getPointsBounds(final AffineTransform transform, final CharSequence string, final float pixelSize, - final AffineTransform temp1, final AffineTransform temp2); + AABBox getPointsBounds(final AffineTransform transform, final CharSequence string, final float pixelSize); + + AABBox getPointsBounds(final AffineTransform transform, final CharSequence string); - public boolean isPrintableChar(final char c); + AABBox getPointsBoundsFU(final AffineTransform transform, final CharSequence string); + + boolean isPrintableChar(final char c); /** Shall return {@link #getFullFamilyName()} */ @Override public String toString(); + + /** Return all font details as string. */ + String fullString(); } diff --git a/src/jogl/classes/com/jogamp/graph/font/FontScale.java b/src/jogl/classes/com/jogamp/graph/font/FontScale.java new file mode 100644 index 000000000..a0f2ef4e0 --- /dev/null +++ b/src/jogl/classes/com/jogamp/graph/font/FontScale.java @@ -0,0 +1,70 @@ +package com.jogamp.graph.font; + +/** + * Simple static font scale methods for unit conversion. + */ +public class FontScale { + /** + * Converts typical font size in points (per-inch) and screen resolution in dpi to font size in pixels (per-inch), + * which can be used for pixel-size font scaling operations. + * <p> + * Note that 1em == size of the selected font.<br/> + * In case the pixel per em size is required (advance etc), + * the resulting pixel-size (per-inch) of this method shall be used if rendering directly into the screen resolution! + * </p> + * <pre> + Font Scale Formula: + 1 inch = 25.4 mm + + 1 inch = 72 points + pointSize: [point] = [1/72 inch] + + [1] Scale := pointSize * resolution / ( 72 points per inch * units_per_em ) + [2] PixelSize := pointSize * resolution / ( 72 points per inch ) + [3] Scale := PixelSize / units_per_em + * </pre> + * @param font_sz_pt in points (per-inch) + * @param res_dpi display resolution in dots-per-inch + * @return pixel-per-inch, pixelSize scale factor for font operations. + * @see #toPixels2(float, float) + */ + public static float toPixels(final float font_sz_pt /* points per inch */, final float res_dpi /* dots per inch */) { + return ( font_sz_pt / 72f /* points per inch */ ) * res_dpi; + } + + /** + * Converts typical font size in points-per-inch and screen resolution in points-per-mm to font size in pixels (per-inch), + * which can be used for pixel-size font scaling operations. + * + * @param font_sz_pt in points (per-inch) + * @param res_ppmm display resolution in dots-per-mm + * @return pixel-per-inch, pixelSize scale factor for font operations. + * @see #toPixels(float, float) + */ + public static float toPixels2(final float font_sz_pt /* points per inch */, final float res_ppmm /* pixels per mm */) { + return ( font_sz_pt / 72f /* points per inch */ ) * ( res_ppmm * 25.4f /* mm per inch */ ) ; + } + + /** + * Converts [1/mm] to [1/inch] in place + * @param ppmm float[2] [1/mm] value + * @return return [1/inch] value + */ + public static float[/*2*/] perMMToPerInch(final float[/*2*/] ppmm) { + ppmm[0] *= 25.4f; + ppmm[1] *= 25.4f; + return ppmm; + } + + /** + * Converts [1/mm] to [1/inch] into res storage + * @param ppmm float[2] [1/mm] value + * @param res the float[2] result storage + * @return return [1/inch] value, i.e. the given res storage + */ + public static float[/*2*/] ppmmToPPI(final float[/*2*/] ppmm, final float[/*2*/] res) { + res[0] = ppmm[0] * 25.4f; + res[1] = ppmm[1] * 25.4f; + return res; + } +} diff --git a/src/jogl/classes/jogamp/graph/font/typecast/TypecastFont.java b/src/jogl/classes/jogamp/graph/font/typecast/TypecastFont.java index b3c1885d4..885261bf9 100644 --- a/src/jogl/classes/jogamp/graph/font/typecast/TypecastFont.java +++ b/src/jogl/classes/jogamp/graph/font/typecast/TypecastFont.java @@ -34,6 +34,10 @@ import jogamp.graph.font.typecast.ot.table.CmapIndexEntry; import jogamp.graph.font.typecast.ot.table.CmapTable; import jogamp.graph.font.typecast.ot.table.HdmxTable; import jogamp.graph.font.typecast.ot.table.ID; +import jogamp.graph.font.typecast.ot.table.KernSubtableFormat0; +import jogamp.graph.font.typecast.ot.table.KernTable; +import jogamp.graph.font.typecast.ot.table.KerningPair; +import jogamp.graph.font.typecast.ot.table.PostTable; import com.jogamp.common.util.IntObjectHashMap; import com.jogamp.graph.curve.OutlineShape; @@ -45,6 +49,7 @@ import com.jogamp.graph.geom.plane.AffineTransform; import com.jogamp.opengl.math.geom.AABBox; class TypecastFont implements Font { + static final boolean USE_PRESCALED_ADVANCE = false; static final boolean DEBUG = false; private static final Vertex.Factory<SVertex> vertexFactory = SVertex.factory(); @@ -54,7 +59,6 @@ class TypecastFont implements Font { private final int cmapentries; private final IntObjectHashMap char2Glyph; private final TypecastHMetrics metrics; - private final float[] tmpV3 = new float[3]; // FIXME: Add cache size to limit memory usage ?? public TypecastFont(final OTFontCollection fontset) { @@ -169,7 +173,15 @@ class TypecastFont implements Font { @Override public float getAdvanceWidth(final int glyphID, final float pixelSize) { - return font.getHmtxTable().getAdvanceWidth(glyphID) * metrics.getScale(pixelSize); + return pixelSize * metrics.getScale( font.getHmtxTable().getAdvanceWidth(glyphID) ); + } + @Override + public float getAdvanceWidth(final int glyphID) { + return metrics.getScale( font.getHmtxTable().getAdvanceWidth(glyphID) ); + } + @Override + public int getAdvanceWidthFU(final int glyphID) { + return font.getHmtxTable().getAdvanceWidth(glyphID); } @Override @@ -178,46 +190,104 @@ class TypecastFont implements Font { } @Override + public final float getKerning(final int left_glyphid, final int right_glyphid) { + return metrics.getScale( getKerningFU(left_glyphid, right_glyphid) ); + } + + @Override + public int getKerningFU(final int left_glyphid, final int right_glyphid) { + if( 0 == left_glyphid || 0 == right_glyphid ) { + return 0; + } + final KernTable kern = font.getKernTable(); + if (kern != null) { + final int kernSubtableCount = kern.getSubtableCount(); + if( 0 < kernSubtableCount ) { + final KernSubtableFormat0 kst0 = kern.getSubtable0(); + if( null != kst0 && kst0.areKerningValues() && kst0.isHorizontal() && !kst0.isCrossstream() ) { + for (int i = 0; i < kst0.getKerningPairCount(); i++) { + final KerningPair kpair = kst0.getKerningPair(i); + if( kpair.getLeft() == left_glyphid && kpair.getRight() == right_glyphid ) { + return kpair.getValue(); + } + } + } + } + } + return 0; + } + + @Override + public int getGlyphID(final char symbol) { + // enforce mapping as some fonts have an erroneous cmap (FreeSerif-Regular) + switch(symbol) { + case ' ': return Glyph.ID_SPACE; + case '\n': return Glyph.ID_CR; + } + final int glyphID = cmapFormat.mapCharCode(symbol); + if( 0 != glyphID ) { + return glyphID; + } + return Glyph.ID_UNKNOWN; + } + + /** pp **/ PostTable getPostTable() { + return font.getPostTable(); + } + + @Override public Glyph getGlyph(final char symbol) { TypecastGlyph result = (TypecastGlyph) char2Glyph.get(symbol); if (null == result) { - // final short code = (short) char2Code.get(symbol); - short code = (short) cmapFormat.mapCharCode(symbol); - if(0 == code && 0 != symbol) { - // reserved special glyph IDs by convention - switch(symbol) { - case ' ': code = Glyph.ID_SPACE; break; - case '\n': code = Glyph.ID_CR; break; - default: code = Glyph.ID_UNKNOWN; - } - } + final int glyph_id = getGlyphID( symbol ); - jogamp.graph.font.typecast.ot.OTGlyph glyph = font.getGlyph(code); + jogamp.graph.font.typecast.ot.OTGlyph glyph = font.getGlyph(glyph_id); + final int glyph_advance; + final AABBox glyph_bbox; if(null == glyph) { + // fallback glyph = font.getGlyph(Glyph.ID_UNKNOWN); } + switch( glyph_id ) { + case Glyph.ID_SPACE: + /** fallthrough */ + case Glyph.ID_CR: + glyph_advance = getAdvanceWidthFU(glyph_id); + glyph_bbox = new AABBox(0, 0, 0, glyph_advance, getLineHeightFU(), 0); + break; + default: + glyph_advance = glyph.getAdvanceWidth(); + glyph_bbox = glyph.getBBox(); + break; + } if(null == glyph) { - throw new RuntimeException("Could not retrieve glyph for symbol: <"+symbol+"> "+(int)symbol+" -> glyph id "+code); + throw new RuntimeException("Could not retrieve glyph for symbol: <"+symbol+"> "+(int)symbol+" -> glyph id "+glyph_id); } final OutlineShape shape = TypecastRenderer.buildShape(symbol, glyph, vertexFactory); - result = new TypecastGlyph(this, symbol, code, glyph.getBBox(), glyph.getAdvanceWidth(), shape); + result = new TypecastGlyph(this, symbol, glyph_id, glyph_bbox, glyph_advance, shape); if(DEBUG) { - System.err.println("New glyph: " + (int)symbol + " ( " + symbol +" ) -> " + code + ", contours " + glyph.getPointCount() + ": " + shape); + final PostTable post = font.getPostTable(); + final String glyph_name = null != post ? post.getGlyphName(glyph_id) : "n/a"; + System.err.println("New glyph: " + (int)symbol + " ( " + symbol +" ) -> " + glyph_id + "/'"+glyph_name+"', contours " + glyph.getPointCount() + ": " + shape); + System.err.println(" "+glyph); + System.err.println(" "+result); } glyph.clearPointData(); - final HdmxTable hdmx = font.getHdmxTable(); - if (null!= result && null != hdmx) { - /*if(DEBUG) { - System.err.println("hdmx "+hdmx); - }*/ - for (int i=0; i<hdmx.getNumberOfRecords(); i++) - { - final HdmxTable.DeviceRecord dr = hdmx.getRecord(i); - result.addAdvance(dr.getWidth(code), dr.getPixelSize()); - /* if(DEBUG) { - System.err.println("hdmx advance : pixelsize = "+dr.getWidth(code)+" : "+ dr.getPixelSize()); - } */ + if( TypecastFont.USE_PRESCALED_ADVANCE ) { + final HdmxTable hdmx = font.getHdmxTable(); + if (null!= result && null != hdmx) { + /*if(DEBUG) { + System.err.println("hdmx "+hdmx); + }*/ + for (int i=0; i<hdmx.getNumberOfRecords(); i++) + { + final HdmxTable.DeviceRecord dr = hdmx.getRecord(i); + if(DEBUG) { + System.err.println("hdmx advance : pixelsize "+dr.getPixelSize()+" -> advance "+dr.getWidth(glyph_id)); + } + result.addAdvance(dr.getPixelSize(), dr.getWidth(glyph_id)); + } } } char2Glyph.put(symbol, result); @@ -226,23 +296,53 @@ class TypecastFont implements Font { } @Override - public final float getPixelSize(final float fontSize /* points per inch */, final float resolution) { - return fontSize * resolution / ( 72f /* points per inch */ ); + public float getLineHeight(final float pixelSize) { + return pixelSize * metrics.getScale( getLineHeightFU() ); } @Override - public float getLineHeight(final float pixelSize) { + public float getLineHeight() { + return metrics.getScale( getLineHeightFU() ); + } + + @Override + public int getLineHeightFU() { final Metrics metrics = getMetrics(); - final float lineGap = metrics.getLineGap(pixelSize) ; // negative value! - final float ascent = metrics.getAscent(pixelSize) ; // negative value! - final float descent = metrics.getDescent(pixelSize) ; // positive value! - final float advanceY = lineGap - descent + ascent; // negative value! + final int lineGap = metrics.getLineGapFU() ; // negative value! + final int ascent = metrics.getAscentFU() ; // negative value! + final int descent = metrics.getDescentFU() ; // positive value! + final int advanceY = lineGap - descent + ascent; // negative value! return -advanceY; } @Override public float getMetricWidth(final CharSequence string, final float pixelSize) { - float width = 0; + if( !TypecastFont.USE_PRESCALED_ADVANCE ) { + return pixelSize * getMetricWidth(string); + } else { + float width = 0; + final int len = string.length(); + for (int i=0; i< len; i++) { + final char character = string.charAt(i); + if (character == '\n') { + width = 0; + } else { + final Glyph glyph = getGlyph(character); + width += glyph.getAdvance(pixelSize); // uses pixelSize mapping, different than glyph.getAdvanceFU() + } + } + return width; + } + } + + @Override + public float getMetricWidth(final CharSequence string) { + return metrics.getScale( getMetricWidthFU(string) ); + } + + @Override + public int getMetricWidthFU(final CharSequence string) { + int width = 0; final int len = string.length(); for (int i=0; i< len; i++) { final char character = string.charAt(i); @@ -250,22 +350,31 @@ class TypecastFont implements Font { width = 0; } else { final Glyph glyph = getGlyph(character); - width += glyph.getAdvance(pixelSize, false); + width += glyph.getAdvanceFU(); } } - return (int)(width + 0.5f); + return width; + } + + @Override + public float getMetricHeight(final CharSequence string, final float pixelSize) { + return pixelSize * getMetricHeight(string); + } + + @Override + public float getMetricHeight(final CharSequence string) { + return metrics.getScale( getMetricHeightFU(string) ); } @Override - public float getMetricHeight(final CharSequence string, final float pixelSize, final AABBox tmp) { + public int getMetricHeightFU(final CharSequence string) { int height = 0; for (int i=0; i<string.length(); i++) { final char character = string.charAt(i); if (character != ' ') { final Glyph glyph = getGlyph(character); - final AABBox bbox = glyph.getBBox(tmp, pixelSize, tmpV3); - height = (int)Math.ceil(Math.max(bbox.getHeight(), height)); + height = (int)Math.ceil(Math.max(glyph.getBBoxFU().getHeight(), height)); } } return height; @@ -273,14 +382,51 @@ class TypecastFont implements Font { @Override public AABBox getMetricBounds(final CharSequence string, final float pixelSize) { + if( !TypecastFont.USE_PRESCALED_ADVANCE ) { + return getMetricBoundsFU(string).scale(pixelSize/metrics.getUnitsPerEM(), new float[3]); + } else { + if (string == null) { + return new AABBox(); + } + final int charCount = string.length(); + final float lineHeight = getLineHeight(pixelSize); + float totalHeight = 0; + float totalWidth = 0; + float curLineWidth = 0; + for (int i=0; i<charCount; i++) { + final char character = string.charAt(i); + if (character == '\n') { + totalWidth = Math.max(curLineWidth, totalWidth); + curLineWidth = 0; + totalHeight += lineHeight; + continue; + } + final Glyph glyph = getGlyph(character); + curLineWidth += glyph.getAdvance(pixelSize); // uses pixelSize mapping, different than glyph.getAdvanceFU() + } + if (curLineWidth > 0) { + totalHeight += lineHeight; + totalWidth = Math.max(curLineWidth, totalWidth); + } + return new AABBox(0, 0, 0, totalWidth, totalHeight,0); + } + } + + @Override + public AABBox getMetricBounds(final CharSequence string) { + return getMetricBoundsFU(string).scale(1.0f/metrics.getUnitsPerEM(), new float[3]); + } + + @Override + public AABBox getMetricBoundsFU(final CharSequence string) { if (string == null) { return new AABBox(); } final int charCount = string.length(); - final float lineHeight = getLineHeight(pixelSize); - float totalHeight = 0; - float totalWidth = 0; - float curLineWidth = 0; + final int lineHeight = getLineHeightFU(); + int totalHeight = 0; + int totalWidth = 0; + int curLineWidth = 0; for (int i=0; i<charCount; i++) { final char character = string.charAt(i); if (character == '\n') { @@ -289,8 +435,7 @@ class TypecastFont implements Font { totalHeight += lineHeight; continue; } - final Glyph glyph = getGlyph(character); - curLineWidth += glyph.getAdvance(pixelSize, true); + curLineWidth += getAdvanceWidthFU( getGlyphID( character ) ); } if (curLineWidth > 0) { totalHeight += lineHeight; @@ -298,15 +443,72 @@ class TypecastFont implements Font { } return new AABBox(0, 0, 0, totalWidth, totalHeight,0); } + + @Override + public AABBox getPointsBounds(final AffineTransform transform, final CharSequence string, final float pixelSize) { + if( !TypecastFont.USE_PRESCALED_ADVANCE ) { + return getPointsBoundsFU(transform, string).scale(pixelSize/metrics.getUnitsPerEM(), new float[3]); + } else { + if (string == null) { + return new AABBox(); + } + final AffineTransform temp1 = new AffineTransform(); + final AffineTransform temp2 = new AffineTransform(); + final float pixelSize2 = pixelSize / metrics.getUnitsPerEM(); + final int charCount = string.length(); + final float lineHeight = getLineHeight(pixelSize); + final AABBox tbox = new AABBox(); + final AABBox res = new AABBox(); + + float y = 0; + float advanceTotal = 0; + + for(int i=0; i< charCount; i++) { + final char character = string.charAt(i); + if( '\n' == character ) { + y -= lineHeight; + advanceTotal = 0; + } else if (character == ' ') { + advanceTotal += getAdvanceWidth(Glyph.ID_SPACE, pixelSize); + } else { + // reset transform + if( null != transform ) { + temp1.setTransform(transform); + } else { + temp1.setToIdentity(); + } + temp1.translate(advanceTotal, y, temp2); + temp1.scale(pixelSize2, pixelSize2, temp2); + tbox.reset(); + + final Font.Glyph glyph = getGlyph(character); + res.resize(temp1.transform(glyph.getBBoxFU(), tbox)); + + final OutlineShape glyphShape = glyph.getShape(); + if( null == glyphShape ) { + continue; + } + advanceTotal += glyph.getAdvance(pixelSize); + } + } + return res; + } + } + @Override - public AABBox getPointsBounds(final AffineTransform transform, final CharSequence string, final float pixelSize, - final AffineTransform temp1, final AffineTransform temp2) { + public AABBox getPointsBounds(final AffineTransform transform, final CharSequence string) { + return getPointsBoundsFU(transform, string).scale(1.0f/metrics.getUnitsPerEM(), new float[3]); + } + + @Override + public AABBox getPointsBoundsFU(final AffineTransform transform, final CharSequence string) { if (string == null) { return new AABBox(); } + final AffineTransform temp1 = new AffineTransform(); + final AffineTransform temp2 = new AffineTransform(); final int charCount = string.length(); - final float lineHeight = getLineHeight(pixelSize); - final float scale = getMetrics().getScale(pixelSize); + final int lineHeight = getLineHeightFU(); final AABBox tbox = new AABBox(); final AABBox res = new AABBox(); @@ -319,7 +521,7 @@ class TypecastFont implements Font { y -= lineHeight; advanceTotal = 0; } else if (character == ' ') { - advanceTotal += getAdvanceWidth(Glyph.ID_SPACE, pixelSize); + advanceTotal += getAdvanceWidthFU(Glyph.ID_SPACE); } else { // reset transform if( null != transform ) { @@ -328,17 +530,16 @@ class TypecastFont implements Font { temp1.setToIdentity(); } temp1.translate(advanceTotal, y, temp2); - temp1.scale(scale, scale, temp2); tbox.reset(); final Font.Glyph glyph = getGlyph(character); - res.resize(temp1.transform(glyph.getBBox(), tbox)); + res.resize(temp1.transform(glyph.getBBoxFU(), tbox)); final OutlineShape glyphShape = glyph.getShape(); if( null == glyphShape ) { continue; } - advanceTotal += glyph.getAdvance(pixelSize, true); + advanceTotal += glyph.getAdvanceFU(); } } return res; @@ -358,4 +559,55 @@ class TypecastFont implements Font { public String toString() { return getFullFamilyName(null).toString(); } + + @Override + public String fullString() { + final StringBuilder sb = new StringBuilder(); + sb.append(toString()).append("[ ").append(font.toString()); + sb.append("\n").append(font.getHeadTable()); + sb.append("\n\n").append(font.getHheaTable()); + if( null != font.getVheaTable() ) { + sb.append("\n\n").append(font.getVheaTable()); + } + if( null != font.getKernTable() ) { + final PostTable post = font.getPostTable(); + final KernTable kern = font.getKernTable(); + sb.append("\n\n").append(kern); + final KernSubtableFormat0 ks0 = kern.getSubtable0(); + if( null != ks0 ) { + final int sz = ks0.getKerningPairCount(); + for(int i=0; i<sz; ++i) { + final KerningPair kp = ks0.getKerningPair(i); + final int left = kp.getLeft(); + final int right = kp.getRight(); + final String leftS; + final String rightS; + if( null == post ) { + leftS = String.valueOf(left); + rightS = String.valueOf(left); + } else { + leftS = post.getGlyphName(left)+"/"+String.valueOf(left); + rightS = post.getGlyphName(right)+"/"+String.valueOf(right); + } + sb.append("\n kp[").append(i).append("]: ").append(leftS).append(" -> ").append(rightS).append(" = ").append(kp.getValue()); + } + } + } + + sb.append("\n\n").append(font.getCmapTable()); + /* if( null != font.getHdmxTable() ) { + sb.append("\n").append(font.getHdmxTable()); // too too long + } */ + // glyf + // sb.append("\n").append(font.getHmtxTable()); // too long + /* if( null != font.getLocaTable() ) { + sb.append("\n").append(font.getLocaTable()); // too long + } */ + sb.append("\n").append(font.getMaxpTable()); + // sb.append("\n\n").append(font.getNameTable()); // no toString() + sb.append("\n\n").append(font.getOS2Table()); + // sb.append("\n\n").append(font.getPostTable()); // too long + sb.append("\n]"); + return sb.toString(); + } } diff --git a/src/jogl/classes/jogamp/graph/font/typecast/TypecastGlyph.java b/src/jogl/classes/jogamp/graph/font/typecast/TypecastGlyph.java index 97570d605..b36196ee5 100644 --- a/src/jogl/classes/jogamp/graph/font/typecast/TypecastGlyph.java +++ b/src/jogl/classes/jogamp/graph/font/typecast/TypecastGlyph.java @@ -32,52 +32,63 @@ import com.jogamp.graph.curve.OutlineShape; import com.jogamp.graph.font.Font; import com.jogamp.opengl.math.geom.AABBox; +import jogamp.graph.font.typecast.ot.table.PostTable; + public final class TypecastGlyph implements Font.Glyph { + + /** Scaled hmtx value */ public static final class Advance { private final Font font; - private final float advance; - private final IntIntHashMap size2advanceI = new IntIntHashMap(); + private final int advance; // in font-units + private final IntIntHashMap size2advanceI; - public Advance(final Font font, final float advance) + public Advance(final Font font, final int advance) { this.font = font; this.advance = advance; - size2advanceI.setKeyNotFoundValue(0); + if( TypecastFont.USE_PRESCALED_ADVANCE ) { + size2advanceI = new IntIntHashMap(); + size2advanceI.setKeyNotFoundValue(0); + } else { + size2advanceI = null; + } } public final void reset() { - size2advanceI.clear(); + if( TypecastFont.USE_PRESCALED_ADVANCE ) { + size2advanceI.clear(); + } } public final Font getFont() { return font; } - public final float getScale(final float pixelSize) + public final int getUnitsPerEM() { return this.font.getMetrics().getUnitsPerEM(); } + + public final float getScale(final int funits) { - return this.font.getMetrics().getScale(pixelSize); + return this.font.getMetrics().getScale(funits); } - public final void add(final float advance, final float size) + public final void add(final float pixelSize, final float advance) { - size2advanceI.put(Float.floatToIntBits(size), Float.floatToIntBits(advance)); + if( TypecastFont.USE_PRESCALED_ADVANCE ) { + size2advanceI.put(Float.floatToIntBits(pixelSize), Float.floatToIntBits(advance)); + } } - public final float get(final float pixelSize, final boolean useFrationalMetrics) + public final float get(final float pixelSize) { - final int sI = Float.floatToIntBits(pixelSize); - final int aI = size2advanceI.get(sI); - if( 0 != aI ) { - return Float.intBitsToFloat(aI); - } - final float a; - if ( useFrationalMetrics ) { - a = this.advance * getScale(pixelSize); + if( !TypecastFont.USE_PRESCALED_ADVANCE ) { + return pixelSize * font.getMetrics().getScale( advance ); } else { - // a = Math.ceil(this.advance * getScale(pixelSize)); - a = Math.round(this.advance * getScale(pixelSize)); // TODO: check whether ceil should be used instead? + final int sI = Float.floatToIntBits( (float) Math.ceil( pixelSize ) ); + final int aI = size2advanceI.get(sI); + if( 0 != aI ) { + return Float.intBitsToFloat(aI); + } + return pixelSize * font.getMetrics().getScale( advance ); } - size2advanceI.put(sI, Float.floatToIntBits(a)); - return a; } @Override @@ -91,39 +102,59 @@ public final class TypecastGlyph implements Font.Glyph { public static final class Metrics { - private final AABBox bbox; - private final Advance advance; - - public Metrics(final Font font, final AABBox bbox, final float advance) + private final TypecastFont font; + private final AABBox bbox; // in font-units + private final int advance; // in font-units + private final Advance advance2; + + /** + * + * @param font + * @param bbox in font-units + * @param advance hmtx value in font-units + */ + public Metrics(final TypecastFont font, final AABBox bbox, final int advance) { + this.font = font; this.bbox = bbox; - this.advance = new Advance(font, advance); + this.advance = advance; + if( TypecastFont.USE_PRESCALED_ADVANCE ) { + this.advance2 = new Advance(font, advance); + } else { + this.advance2 = null; + } } public final void reset() { - advance.reset(); + if( TypecastFont.USE_PRESCALED_ADVANCE ) { + advance2.reset(); + } } - public final Font getFont() { return advance.getFont(); } + public final TypecastFont getFont() { return font; } - public final float getScale(final float pixelSize) - { - return this.advance.getScale(pixelSize); - } + public final int getUnitsPerEM() { return font.getMetrics().getUnitsPerEM(); } - public final AABBox getBBox() - { - return this.bbox; - } + public final float getScale(final int funits) { return font.getMetrics().getScale(funits); } - public final void addAdvance(final float advance, final float size) - { - this.advance.add(advance, size); + /** in font-units */ + public final AABBox getBBoxFU() { return this.bbox; } + + /** Return advance in font units to be divided by unitsPerEM */ + public final int getAdvanceFU() { return this.advance; } + + public final void addAdvance(final float pixelSize, final float advance) { + if( TypecastFont.USE_PRESCALED_ADVANCE ) { + this.advance2.add(pixelSize, advance); + } } - public final float getAdvance(final float pixelSize, final boolean useFrationalMetrics) - { - return this.advance.get(pixelSize, useFrationalMetrics); + public final float getAdvance(final float pixelSize) { + if( TypecastFont.USE_PRESCALED_ADVANCE ) { + return this.advance2.get(pixelSize); + } else { + return pixelSize * font.getMetrics().getScale( advance ); + } } @Override @@ -131,7 +162,8 @@ public final class TypecastGlyph implements Font.Glyph { { return "\nMetrics:"+ "\n bbox: "+this.bbox+ - this.advance; + "\n advance: "+this.advance+ + "\n advance2: "+this.advance2; } } @@ -140,10 +172,19 @@ public final class TypecastGlyph implements Font.Glyph { private final char symbol; private final OutlineShape shape; // in EM units - private final short id; + private final int id; private final Metrics metrics; - protected TypecastGlyph(final Font font, final char symbol, final short id, final AABBox bbox, final int advance, final OutlineShape shape) { + /** + * + * @param font + * @param symbol + * @param id + * @param bbox in font-units + * @param advance from hmtx in font-units + * @param shape + */ + protected TypecastGlyph(final TypecastFont font, final char symbol, final int id, final AABBox bbox, final int advance, final OutlineShape shape) { this.symbol = symbol; this.shape = shape; this.id = id; @@ -160,41 +201,50 @@ public final class TypecastGlyph implements Font.Glyph { return this.symbol; } - final AABBox getBBoxUnsized() { - return this.metrics.getBBox(); + public final Metrics getMetrics() { + return this.metrics; } @Override - public final AABBox getBBox() { - return this.metrics.getBBox(); + public final int getID() { + return this.id; } - public final Metrics getMetrics() { - return this.metrics; + @Override + public final float getScale(final int funits) { + return this.metrics.getScale(funits); } @Override - public final short getID() { - return this.id; + public final AABBox getBBox(final AABBox dest, final float pixelSize, final float[] tmpV3) { + return dest.copy(metrics.getBBoxFU()).scale(pixelSize/metrics.getUnitsPerEM(), tmpV3); } @Override - public final float getScale(final float pixelSize) { - return this.metrics.getScale(pixelSize); + public final AABBox getBBoxFU(final AABBox dest) { + return dest.copy(metrics.getBBoxFU()); } @Override - public final AABBox getBBox(final AABBox dest, final float pixelSize, final float[] tmpV3) { - return dest.copy(getBBox()).scale(getScale(pixelSize), tmpV3); + public final AABBox getBBoxFU() { + return metrics.getBBoxFU(); } - protected final void addAdvance(final float advance, final float size) { - this.metrics.addAdvance(advance, size); + @Override + public final int getAdvanceFU() { return metrics.getAdvanceFU(); } + + @Override + public float getAdvance() { return getScale( getAdvanceFU() ); } + + protected final void addAdvance(final float pixelSize, final float advance) { + if( TypecastFont.USE_PRESCALED_ADVANCE ) { + this.metrics.addAdvance(pixelSize, advance); + } } @Override - public final float getAdvance(final float pixelSize, final boolean useFrationalMetrics) { - return this.metrics.getAdvance(pixelSize, useFrationalMetrics); + public final float getAdvance(final float pixelSize) { + return metrics.getAdvance(pixelSize); } @Override @@ -208,4 +258,15 @@ public final class TypecastGlyph implements Font.Glyph { final int hash = 31 + getFont().getName(Font.NAME_UNIQUNAME).hashCode(); return ((hash << 5) - hash) + id; } + + @Override + public String toString() { + final PostTable post = metrics.getFont().getPostTable(); + final String glyph_name = null != post ? post.getGlyphName(id) : "n/a"; + return new StringBuilder() + .append("Glyph id ").append(id).append(" '").append(glyph_name).append("'") + .append(", advance ").append(getAdvanceFU()) + .append(", ").append(getBBoxFU()) + .toString(); + } } diff --git a/src/jogl/classes/jogamp/graph/font/typecast/TypecastHMetrics.java b/src/jogl/classes/jogamp/graph/font/typecast/TypecastHMetrics.java index d5e30a500..705246822 100644 --- a/src/jogl/classes/jogamp/graph/font/typecast/TypecastHMetrics.java +++ b/src/jogl/classes/jogamp/graph/font/typecast/TypecastHMetrics.java @@ -33,12 +33,13 @@ import jogamp.graph.font.typecast.ot.table.HheaTable; import com.jogamp.graph.font.Font.Metrics; import com.jogamp.opengl.math.geom.AABBox; -class TypecastHMetrics implements Metrics { +final class TypecastHMetrics implements Metrics { private final TypecastFont fontImpl; // HeadTable private final HeadTable headTable; - private final float unitsPerEM_Inv; + private final int unitsPerEM; + private final float unitsPerEM_inv; private final AABBox bbox; // HheaTable private final HheaTable hheaTable; @@ -50,39 +51,55 @@ class TypecastHMetrics implements Metrics { headTable = this.fontImpl.font.getHeadTable(); hheaTable = this.fontImpl.font.getHheaTable(); // vheaTable = this.fontImpl.font.getVheaTable(); - unitsPerEM_Inv = 1.0f / ( headTable.getUnitsPerEm() ); + unitsPerEM = headTable.getUnitsPerEm(); + unitsPerEM_inv = 1.0f / unitsPerEM; final int maxWidth = headTable.getXMax() - headTable.getXMin(); final int maxHeight = headTable.getYMax() - headTable.getYMin(); - final float lowx= headTable.getXMin(); - final float lowy = -(headTable.getYMin()+maxHeight); - final float highx = lowx + maxWidth; - final float highy = lowy + maxHeight; + final int lowx= headTable.getXMin(); + final int lowy = -(headTable.getYMin()+maxHeight); + final int highx = lowx + maxWidth; + final int highy = lowy + maxHeight; bbox = new AABBox(lowx, lowy, 0, highx, highy, 0); // invert } @Override - public final float getAscent(final float pixelSize) { - return getScale(pixelSize) * -hheaTable.getAscender(); // invert + public int getAscentFU() { + return -hheaTable.getAscender(); // inverted } + @Override - public final float getDescent(final float pixelSize) { - return getScale(pixelSize) * -hheaTable.getDescender(); // invert + public int getDescentFU() { + return -hheaTable.getDescender(); // inverted } + @Override - public final float getLineGap(final float pixelSize) { - return getScale(pixelSize) * -hheaTable.getLineGap(); // invert + public int getLineGapFU() { + return -hheaTable.getLineGap(); // inverted } + @Override - public final float getMaxExtend(final float pixelSize) { - return getScale(pixelSize) * hheaTable.getXMaxExtent(); + public int getMaxExtendFU() { + return hheaTable.getXMaxExtent(); } + @Override - public final float getScale(final float pixelSize) { - return pixelSize * unitsPerEM_Inv; + public final int getUnitsPerEM() { + return unitsPerEM; } + + @Override + public final float getScale(final int funits) { + return funits * unitsPerEM_inv; + } + + @Override + public final AABBox getBBox(final AABBox dest) { + return dest.copy(bbox); + } + @Override public final AABBox getBBox(final AABBox dest, final float pixelSize, final float[] tmpV3) { - return dest.setSize(bbox.getLow(), bbox.getHigh()).scale(getScale(pixelSize), tmpV3); + return dest.setSize(bbox.getLow(), bbox.getHigh()).scale(pixelSize*unitsPerEM_inv, tmpV3); } -}
\ No newline at end of file +} |