diff options
author | Sven Gothel <[email protected]> | 2023-02-10 14:20:35 +0100 |
---|---|---|
committer | Sven Gothel <[email protected]> | 2023-02-10 14:20:35 +0100 |
commit | feb8de27f848b5213423389cf0e19cbd88095682 (patch) | |
tree | ec7051b6fec0a78a4f78b1958a3f7999a063c1d9 /src | |
parent | bc951476c67282d9676f33ee25fb0f697a4dbe45 (diff) |
Font/Graph, {Font, Glyph}/Typecast: Add kerning and expose values in original font-units (FU) to have them scaled later ( fu * pixelScale / unitsPerEM )
Scaling from font-units (funits, or FU) is now performed by the renderer itself,
i.e. applying the scale-factor 'fontPixelSize / font.getMetrics().getUnitsPerEM()'
to the PMV matrix to render the whole graph GLRegion.
This finally provides proper device and resolution independent font utilization.
Further, preliminary kerning has been added.
+++
Also ...
TypecastFont:
- getGlyphID(..) getGlyph(..) enforce symbol mapping to Glyph.ID_SPACE Glyph.ID_CR,
as some fonts ave an erroneous cmap (FreeSerif-Regular)
- add getKerning(..)
TODO: Add binary search
- Set TypecastFont.USE_PRESCALED_ADVANCE := false,
i.e. dropping all prescaled pixel-sized advance values mapped to font pixel-size
as we utilize font-units only for later uniform scaling.
- Drop virtual getPixelSize() and add static FontScale.toPixels(..)
- Add fullString() for debugging purposed, including some font tables
Diffstat (limited to 'src')
22 files changed, 1019 insertions, 354 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 +} diff --git a/src/newt/classes/com/jogamp/newt/opengl/util/NEWTDemoListener.java b/src/newt/classes/com/jogamp/newt/opengl/util/NEWTDemoListener.java index 7507e772f..aaed713a8 100644 --- a/src/newt/classes/com/jogamp/newt/opengl/util/NEWTDemoListener.java +++ b/src/newt/classes/com/jogamp/newt/opengl/util/NEWTDemoListener.java @@ -32,6 +32,7 @@ import java.util.ArrayList; import java.util.List; import com.jogamp.common.util.IOUtil; +import com.jogamp.graph.font.FontScale; import com.jogamp.nativewindow.CapabilitiesImmutable; import com.jogamp.nativewindow.ScalableSurface; import com.jogamp.newt.Window; @@ -126,6 +127,7 @@ public class NEWTDemoListener extends WindowAdapter implements KeyListener, Mous case KeyEvent.VK_SPACE: e.setConsumed(true); glWindow.invokeOnCurrentThread(new Runnable() { + @Override public void run() { final GLAnimatorControl anim = glWindow.getAnimator(); if( null != anim ) { @@ -140,6 +142,7 @@ public class NEWTDemoListener extends WindowAdapter implements KeyListener, Mous case KeyEvent.VK_A: e.setConsumed(true); glWindow.invokeOnNewThread(null, false, new Runnable() { + @Override public void run() { printlnState("[set alwaysontop pre]"); glWindow.setAlwaysOnTop(!glWindow.isAlwaysOnTop()); @@ -149,6 +152,7 @@ public class NEWTDemoListener extends WindowAdapter implements KeyListener, Mous case KeyEvent.VK_B: e.setConsumed(true); glWindow.invokeOnNewThread(null, false, new Runnable() { + @Override public void run() { printlnState("[set alwaysonbottom pre]"); glWindow.setAlwaysOnBottom(!glWindow.isAlwaysOnBottom()); @@ -158,6 +162,7 @@ public class NEWTDemoListener extends WindowAdapter implements KeyListener, Mous case KeyEvent.VK_C: e.setConsumed(true); glWindow.invokeOnNewThread(null, false, new Runnable() { + @Override public void run() { if( null != pointerIcons ) { printlnState("[set pointer-icon pre]"); @@ -177,6 +182,7 @@ public class NEWTDemoListener extends WindowAdapter implements KeyListener, Mous case KeyEvent.VK_D: e.setConsumed(true); glWindow.invokeOnNewThread(null, false, new Runnable() { + @Override public void run() { printlnState("[set undecorated pre]"); glWindow.setUndecorated(!glWindow.isUndecorated()); @@ -187,6 +193,7 @@ public class NEWTDemoListener extends WindowAdapter implements KeyListener, Mous e.setConsumed(true); quitAdapterOff(); glWindow.invokeOnNewThread(null, false, new Runnable() { + @Override public void run() { printlnState("[set fullscreen pre]"); if( glWindow.isFullscreen() ) { @@ -205,6 +212,7 @@ public class NEWTDemoListener extends WindowAdapter implements KeyListener, Mous case KeyEvent.VK_G: e.setConsumed(true); glWindow.invokeOnNewThread(null, false, new Runnable() { + @Override public void run() { final float newGamma = gamma + ( e.isShiftDown() ? -0.1f : 0.1f ); System.err.println("[set gamma]: "+gamma+" -> "+newGamma); @@ -216,6 +224,7 @@ public class NEWTDemoListener extends WindowAdapter implements KeyListener, Mous case KeyEvent.VK_I: e.setConsumed(true); glWindow.invokeOnNewThread(null, false, new Runnable() { + @Override public void run() { printlnState("[set pointer-visible pre]"); glWindow.setPointerVisible(!glWindow.isPointerVisible()); @@ -225,6 +234,7 @@ public class NEWTDemoListener extends WindowAdapter implements KeyListener, Mous case KeyEvent.VK_J: e.setConsumed(true); glWindow.invokeOnNewThread(null, false, new Runnable() { + @Override public void run() { printlnState("[set pointer-confined pre]", "warp-center: "+e.isShiftDown()); final boolean confine = !glWindow.isPointerConfined(); @@ -240,6 +250,7 @@ public class NEWTDemoListener extends WindowAdapter implements KeyListener, Mous case KeyEvent.VK_M: e.setConsumed(true); glWindow.invokeOnNewThread(null, false, new Runnable() { + @Override public void run() { // none: max-v // alt: max-h @@ -271,6 +282,7 @@ public class NEWTDemoListener extends WindowAdapter implements KeyListener, Mous case KeyEvent.VK_P: e.setConsumed(true); glWindow.invokeOnNewThread(null, false, new Runnable() { + @Override public void run() { printlnState("[set position pre]"); glWindow.setPosition(100, 100); @@ -286,6 +298,7 @@ public class NEWTDemoListener extends WindowAdapter implements KeyListener, Mous case KeyEvent.VK_R: e.setConsumed(true); glWindow.invokeOnNewThread(null, false, new Runnable() { + @Override public void run() { printlnState("[set resizable pre]"); glWindow.setResizable(!glWindow.isResizable()); @@ -295,6 +308,7 @@ public class NEWTDemoListener extends WindowAdapter implements KeyListener, Mous case KeyEvent.VK_S: e.setConsumed(true); glWindow.invokeOnNewThread(null, false, new Runnable() { + @Override public void run() { printlnState("[set sticky pre]"); glWindow.setSticky(!glWindow.isSticky()); @@ -331,6 +345,7 @@ public class NEWTDemoListener extends WindowAdapter implements KeyListener, Mous }); } else { glWindow.invokeOnNewThread(null, false, new Runnable() { + @Override public void run() { final boolean wasVisible = glWindow.isVisible(); { @@ -354,6 +369,7 @@ public class NEWTDemoListener extends WindowAdapter implements KeyListener, Mous case KeyEvent.VK_W: e.setConsumed(true); glWindow.invokeOnNewThread(null, false, new Runnable() { + @Override public void run() { printlnState("[set pointer-pos pre]"); glWindow.warpPointer(glWindow.getSurfaceWidth()/2, glWindow.getSurfaceHeight()/2); @@ -441,6 +457,7 @@ public class NEWTDemoListener extends WindowAdapter implements KeyListener, Mous public boolean shouldQuit() { return quitAdapterShouldQuit; } public void doQuit() { quitAdapterShouldQuit=true; } + @Override public void windowDestroyNotify(final WindowEvent e) { if( quitAdapterEnabled && quitAdapterEnabled2 ) { System.err.println("QUIT Window "+Thread.currentThread()); @@ -458,9 +475,7 @@ public class NEWTDemoListener extends WindowAdapter implements KeyListener, Mous final CapabilitiesImmutable reqCaps = win.getRequestedCapabilities(); final CapabilitiesImmutable caps = null != chosenCaps ? chosenCaps : reqCaps; final String capsA = caps.isBackgroundOpaque() ? "opaque" : "transl"; - final float[] sDPI = win.getPixelsPerMM(new float[2]); - sDPI[0] *= 25.4f; - sDPI[1] *= 25.4f; + final float[] sDPI = FontScale.perMMToPerInch( win.getPixelsPerMM(new float[2]) ); final float[] minSurfacePixelScale = win.getMinimumSurfaceScale(new float[2]); final float[] maxSurfacePixelScale = win.getMaximumSurfaceScale(new float[2]); final float[] reqSurfacePixelScale = win.getRequestedSurfaceScale(new float[2]); diff --git a/src/test/com/jogamp/opengl/test/junit/graph/TestFontsNEWT00.java b/src/test/com/jogamp/opengl/test/junit/graph/TestFontsNEWT00.java index e0c6d2ac0..9592a06cf 100644 --- a/src/test/com/jogamp/opengl/test/junit/graph/TestFontsNEWT00.java +++ b/src/test/com/jogamp/opengl/test/junit/graph/TestFontsNEWT00.java @@ -35,7 +35,10 @@ import org.junit.FixMethodOrder; import org.junit.runners.MethodSorters; import com.jogamp.graph.font.Font; +import com.jogamp.graph.font.FontScale; import com.jogamp.graph.font.Font.Glyph; +import com.jogamp.opengl.math.FloatUtil; +import com.jogamp.opengl.math.geom.AABBox; import com.jogamp.opengl.test.junit.util.UITestCase; @@ -64,30 +67,45 @@ public class TestFontsNEWT00 extends UITestCase { final float dpi = 96; for(int i=0; i<fonts.length; i++) { final Font font = fonts[i]; - final float pixelSize = font.getPixelSize(fontSize, dpi); + final float pixelSize = FontScale.toPixels(fontSize, dpi); System.err.println(font.getFullFamilyName(null).toString()+": "+fontSize+"p, "+dpi+"dpi -> "+pixelSize+"px:"); - testFontGlyphAdvancedSize(font, ' ', Glyph.ID_SPACE, fontSize, dpi, pixelSize); - testFontGlyphAdvancedSize(font, 'X', 'X', fontSize, dpi, pixelSize); + testFontGlyphAdvancedSize(font, 'X', pixelSize); + testFontGlyphAdvancedSize(font, 'j', pixelSize); + testFontGlyphAdvancedSize(font, ' ', pixelSize); } } - void testFontGlyphAdvancedSize(final Font font, final char c, final int glyphID, - final float fontSize, final float dpi, final float pixelSize) { - final float glyphScale = font.getGlyph(c).getScale(pixelSize); - final float fontScale = font.getMetrics().getScale(pixelSize); + void testFontGlyphAdvancedSize(final Font font, final char c, final float pixelSize) { + final int glyphID = font.getGlyphID(c); + final int s0 = font.getAdvanceWidthFU(glyphID); + final Font.Glyph glyph = font.getGlyph(c); + final int s1 = glyph.getAdvanceFU(); - // return this.metrics.getAdvance(pixelSize, useFrationalMetrics); - // this.metrics.getAdvance(pixelSize, useFrationalMetrics) - // this.advance * this.font.getMetrics().getScale(pixelSize) - // font.getHmtxTable().getAdvanceWidth(glyphID) * this.font.getMetrics().getScale(pixelSize) - final float spaceAdvanceSizeOfGlyph = font.getGlyph(c).getAdvance(pixelSize, true); + final int unitsPerEM = font.getMetrics().getUnitsPerEM(); - // font.getHmtxTable().getAdvanceWidth(glyphID) * metrics.getScale(pixelSize); - // font.getHmtxTable().getAdvanceWidth(glyphID) * pixelSize * unitsPerEM_Inv; - final float spaceAdvanceWidth = font.getAdvanceWidth(glyphID, pixelSize); - System.err.println(" Char '"+c+"', "+glyphID+":"); - System.err.println(" glyphScale "+glyphScale); - System.err.println(" glyphSize "+spaceAdvanceSizeOfGlyph); - System.err.println(" fontScale "+fontScale); - System.err.println(" fontWidth "+spaceAdvanceWidth); + final float s0_em = font.getAdvanceWidth(glyphID); + final float s1_em = glyph.getAdvance(); + + final float s0_px = font.getAdvanceWidth(glyphID, pixelSize); + final float s1_px = glyph.getAdvance(pixelSize); + + System.err.println(" Char '"+c+"', id "+glyphID+", font-px "+pixelSize+", unitsPerEM "+unitsPerEM+":"); + System.err.println(" "+glyph); + System.err.println(" Advance"); + System.err.println(" funits "+s0+", "+s1); + System.err.println(" em "+s0_em+", "+s1_em); + System.err.println(" px "+s0_px+", "+s1_px); + System.err.println(" AABBox"); + System.err.println(" funits "+glyph.getBBoxFU()); + System.err.println(" px "+glyph.getBBox(new AABBox(), pixelSize, new float[3])); + + Assert.assertEquals(s0, s1); + + Assert.assertEquals((float)s0/(float)unitsPerEM, s0_em, FloatUtil.EPSILON); + Assert.assertEquals((float)s1/(float)unitsPerEM, s1_em, FloatUtil.EPSILON); + Assert.assertEquals(s0_em, s1_em, FloatUtil.EPSILON); + + Assert.assertEquals(s0_em*pixelSize, s0_px, FloatUtil.EPSILON); + Assert.assertEquals(s1_em*pixelSize, s1_px, FloatUtil.EPSILON); + Assert.assertEquals(s0_px, s1_px, FloatUtil.EPSILON); } } diff --git a/src/test/com/jogamp/opengl/test/junit/graph/TestTextRendererNEWT00.java b/src/test/com/jogamp/opengl/test/junit/graph/TestTextRendererNEWT00.java index 66785b2ab..9cb29365c 100644 --- a/src/test/com/jogamp/opengl/test/junit/graph/TestTextRendererNEWT00.java +++ b/src/test/com/jogamp/opengl/test/junit/graph/TestTextRendererNEWT00.java @@ -54,6 +54,7 @@ import com.jogamp.graph.curve.opengl.RegionRenderer; import com.jogamp.graph.curve.opengl.RenderState; import com.jogamp.graph.font.Font; import com.jogamp.graph.font.FontFactory; +import com.jogamp.graph.font.FontScale; import com.jogamp.graph.geom.SVertex; import com.jogamp.junit.util.JunitTracer; import com.jogamp.newt.Window; @@ -338,10 +339,10 @@ public class TestTextRendererNEWT00 extends UITestCase { final Window win = (Window)drawable.getUpstreamWidget(); final float[] pixelsPerMM = win.getPixelsPerMM(new float[2]); - final float[] dotsPerInch = new float[] { pixelsPerMM[0]*25.4f, pixelsPerMM[1]*25.4f }; + final float[] dotsPerInch = FontScale.ppmmToPPI(pixelsPerMM, new float[2]); dpiH = dotsPerInch[1]; System.err.println(getFontInfo()); - System.err.println("fontSize "+fontSizeFixed+", dotsPerMM "+pixelsPerMM[0]+"x"+pixelsPerMM[1]+", dpi "+dotsPerInch[0]+"x"+dotsPerInch[1]+", pixelSize "+font.getPixelSize(fontSizeFixed, dotsPerInch[1] /* dpi display */)); + System.err.println("fontSize "+fontSizeFixed+", dotsPerMM "+pixelsPerMM[0]+"x"+pixelsPerMM[1]+", dpi "+dotsPerInch[0]+"x"+dotsPerInch[1]+", pixelSize "+FontScale.toPixels(fontSizeFixed, dotsPerInch[1] /* dpi display */)); } @Override @@ -395,8 +396,8 @@ public class TestTextRendererNEWT00 extends UITestCase { fontSizeDelta *= -1f; } - final float pixelSize = font.getPixelSize(fontSizeFixed, dpiH); - final float pixelSizeAnim = font.getPixelSize(fontSizeAnim, dpiH); + final float pixelSize = FontScale.toPixels(fontSizeFixed, dpiH); + final float pixelSizeAnim = FontScale.toPixels(fontSizeAnim, dpiH); final String modeS = Region.getRenderModeString(renderModes); diff --git a/src/test/com/jogamp/opengl/test/junit/graph/TestTextRendererNEWT10.java b/src/test/com/jogamp/opengl/test/junit/graph/TestTextRendererNEWT10.java index b452e091d..cdd2bc750 100644 --- a/src/test/com/jogamp/opengl/test/junit/graph/TestTextRendererNEWT10.java +++ b/src/test/com/jogamp/opengl/test/junit/graph/TestTextRendererNEWT10.java @@ -231,7 +231,11 @@ public class TestTextRendererNEWT10 extends UITestCase { pmv.glMatrixMode(GLMatrixFunc.GL_MODELVIEW); pmv.glLoadIdentity(); pmv.glTranslatef(dx, dy, z0); - textRenderUtil.drawString3D(gl, renderer, font, fontSize, text, null, sampleCount); + { + final float sxy = fontSize / font.getMetrics().getUnitsPerEM(); + pmv.glScalef(sxy, sxy, 1.0f); + } + textRenderUtil.drawString3D(gl, renderer, font, text, null, sampleCount); lastRow = row; } diff --git a/src/test/com/jogamp/opengl/test/junit/graph/TestTextRendererNEWTBugXXXX.java b/src/test/com/jogamp/opengl/test/junit/graph/TestTextRendererNEWTBugXXXX.java index e6ae83911..7d285fe43 100644 --- a/src/test/com/jogamp/opengl/test/junit/graph/TestTextRendererNEWTBugXXXX.java +++ b/src/test/com/jogamp/opengl/test/junit/graph/TestTextRendererNEWTBugXXXX.java @@ -201,7 +201,11 @@ public class TestTextRendererNEWTBugXXXX extends UITestCase { pmv.glMatrixMode(GLMatrixFunc.GL_MODELVIEW); pmv.glLoadIdentity(); pmv.glTranslatef(dx, dy, z0); - textRenderUtil.drawString3D(gl, renderer, font, fontSize, text, null, sampleCount); + { + final float sxy = fontSize / font.getMetrics().getUnitsPerEM(); + pmv.glScalef(sxy, sxy, 1.0f); + } + textRenderUtil.drawString3D(gl, renderer, font, text, null, sampleCount); lastRow = row; } diff --git a/src/test/com/jogamp/opengl/test/junit/graph/TextRendererGLELBase.java b/src/test/com/jogamp/opengl/test/junit/graph/TextRendererGLELBase.java index 1ee8a464d..866a522ac 100644 --- a/src/test/com/jogamp/opengl/test/junit/graph/TextRendererGLELBase.java +++ b/src/test/com/jogamp/opengl/test/junit/graph/TextRendererGLELBase.java @@ -40,6 +40,7 @@ import com.jogamp.graph.curve.opengl.RegionRenderer; import com.jogamp.graph.curve.opengl.TextRegionUtil; import com.jogamp.graph.font.Font; import com.jogamp.graph.font.FontFactory; +import com.jogamp.graph.font.FontScale; import com.jogamp.graph.font.FontSet; import com.jogamp.graph.geom.SVertex; import com.jogamp.newt.Window; @@ -145,8 +146,8 @@ public abstract class TextRendererGLELBase implements GLEventListener { final Object upObj = drawable.getUpstreamWidget(); if( upObj instanceof Window ) { - final float[] pixelsPerMM = ((Window)upObj).getPixelsPerMM(new float[2]); - dpiH = pixelsPerMM[1]*25.4f; + final float[] dpi = FontScale.perMMToPerInch( ((Window)upObj).getPixelsPerMM(new float[2]) ); + dpiH = dpi[1]; } } @@ -183,7 +184,7 @@ public abstract class TextRendererGLELBase implements GLEventListener { * * @param drawable * @param font - * @param pixelSize Use {@link Font#getPixelSize(float, float)} for resolution correct pixel-size. + * @param pixelSize Use {@link Font#toPixels(float, float)} for resolution correct pixel-size. * @param text * @param column * @param tx @@ -209,7 +210,7 @@ public abstract class TextRendererGLELBase implements GLEventListener { * * @param drawable * @param font - * @param pixelSize Use {@link Font#getPixelSize(float, float)} for resolution correct pixel-size. + * @param pixelSize Use {@link Font#toPixels(float, float)} for resolution correct pixel-size. * @param text * @param column * @param row @@ -261,19 +262,20 @@ public abstract class TextRendererGLELBase implements GLEventListener { pmvMatrix.glLoadIdentity(); } pmvMatrix.glTranslatef(dx, dy, tz); + final float sxy = ( pixelScale * pixelSize ) / font.getMetrics().getUnitsPerEM(); if( flipVerticalInGLOrientation && drawable.isGLOriented() ) { - pmvMatrix.glScalef(pixelScale, -1f*pixelScale, 1f); - } else if( 1f != pixelScale ) { - pmvMatrix.glScalef(pixelScale, pixelScale, 1f); + pmvMatrix.glScalef(sxy, -1f*sxy, 1.0f); + } else { + pmvMatrix.glScalef(sxy, sxy, 1.0f); } renderer.enable(gl, true); if( cacheRegion ) { - textRenderUtil.drawString3D(gl, renderer, font, pixelSize, text, null, vbaaSampleCount); + textRenderUtil.drawString3D(gl, renderer, font, text, null, vbaaSampleCount); } else if( null != region ) { - TextRegionUtil.drawString3D(gl, region, renderer, font, pixelSize, text, null, vbaaSampleCount, + TextRegionUtil.drawString3D(gl, region, renderer, font, text, null, vbaaSampleCount, textRenderUtil.tempT1, textRenderUtil.tempT2); } else { - TextRegionUtil.drawString3D(gl, renderModes, renderer, font, pixelSize, text, null, vbaaSampleCount, + TextRegionUtil.drawString3D(gl, renderModes, renderer, font, text, null, vbaaSampleCount, textRenderUtil.tempT1, textRenderUtil.tempT2); } renderer.enable(gl, false); diff --git a/src/test/com/jogamp/opengl/test/junit/graph/demos/GPURendererListenerBase01.java b/src/test/com/jogamp/opengl/test/junit/graph/demos/GPURendererListenerBase01.java index 2d3b0664e..311abbcc3 100644 --- a/src/test/com/jogamp/opengl/test/junit/graph/demos/GPURendererListenerBase01.java +++ b/src/test/com/jogamp/opengl/test/junit/graph/demos/GPURendererListenerBase01.java @@ -47,6 +47,7 @@ import com.jogamp.graph.curve.Region; import com.jogamp.graph.curve.opengl.GLRegion; import com.jogamp.graph.curve.opengl.RegionRenderer; import com.jogamp.graph.curve.opengl.RenderState; +import com.jogamp.graph.font.FontScale; import com.jogamp.newt.Window; import com.jogamp.newt.event.KeyEvent; import com.jogamp.newt.event.KeyListener; @@ -125,9 +126,7 @@ public abstract class GPURendererListenerBase01 implements GLEventListener { final Object upObj = drawable.getUpstreamWidget(); if( upObj instanceof Window ) { final Window window = (Window) upObj; - final float[] sDPI = window.getPixelsPerMM(new float[2]); - sDPI[0] *= 25.4f; - sDPI[1] *= 25.4f; + final float[] sDPI = FontScale.perMMToPerInch( window.getPixelsPerMM(new float[2]) ); System.err.println("DPI "+sDPI[0]+" x "+sDPI[1]); final float[] hasSurfacePixelScale1 = window.getCurrentSurfaceScale(new float[2]); diff --git a/src/test/com/jogamp/opengl/test/junit/graph/demos/GPUTextNewtDemo.java b/src/test/com/jogamp/opengl/test/junit/graph/demos/GPUTextNewtDemo.java index fff3bc291..c3b1d1473 100644 --- a/src/test/com/jogamp/opengl/test/junit/graph/demos/GPUTextNewtDemo.java +++ b/src/test/com/jogamp/opengl/test/junit/graph/demos/GPUTextNewtDemo.java @@ -29,9 +29,12 @@ package com.jogamp.opengl.test.junit.graph.demos; import com.jogamp.opengl.GLCapabilities; import com.jogamp.opengl.GLProfile; - +import com.jogamp.opengl.math.geom.AABBox; +import com.jogamp.common.util.InterruptSource; import com.jogamp.graph.curve.Region; import com.jogamp.graph.curve.opengl.RenderState; +import com.jogamp.graph.font.Font; +import com.jogamp.graph.font.FontScale; import com.jogamp.graph.geom.SVertex; import com.jogamp.newt.event.KeyAdapter; import com.jogamp.newt.event.KeyEvent; @@ -130,19 +133,38 @@ public class GPUTextNewtDemo { // ((TextRenderer)textGLListener.getRenderer()).setCacheLimit(32); window.addGLEventListener(textGLListener); window.setVisible(true); + + { + final Font font = textGLListener.getFont(); + final float[] sDPI = FontScale.perMMToPerInch( window.getPixelsPerMM(new float[2]) ); + final float font_ptpi = 12f; + final float font_ppi = FontScale.toPixels(font_ptpi, sDPI[1]); + final AABBox fontNameBox = font.getMetricBounds(GPUTextRendererListenerBase01.textX2, font_ppi); + System.err.println("GPU Text Newt Demo: "+font.fullString()); + System.err.println("GPU Text Newt Demo: screen-dpi: "+sDPI[0]+"x"+sDPI[1]+", font "+font_ptpi+" pt, "+font_ppi+" pixel"); + System.err.println("GPU Text Newt Demo: textX2: "+fontNameBox+" pixel"); + window.setSurfaceSize((int)(fontNameBox.getWidth()*1.1f), (int)(fontNameBox.getHeight()*2f)); + } + // FPSAnimator animator = new FPSAnimator(60); final Animator animator = new Animator(); animator.setUpdateFPSFrames(60, System.err); animator.add(window); window.addKeyListener(new KeyAdapter() { + @Override public void keyPressed(final KeyEvent arg0) { if(arg0.getKeyCode() == KeyEvent.VK_F4) { - window.destroy(); + new InterruptSource.Thread() { + @Override + public void run() { + window.destroy(); + } }.start(); } } }); window.addWindowListener(new WindowAdapter() { + @Override public void windowDestroyed(final WindowEvent e) { animator.stop(); } diff --git a/src/test/com/jogamp/opengl/test/junit/graph/demos/GPUTextRendererListenerBase01.java b/src/test/com/jogamp/opengl/test/junit/graph/demos/GPUTextRendererListenerBase01.java index 85d1b5290..e0097bd74 100644 --- a/src/test/com/jogamp/opengl/test/junit/graph/demos/GPUTextRendererListenerBase01.java +++ b/src/test/com/jogamp/opengl/test/junit/graph/demos/GPUTextRendererListenerBase01.java @@ -43,6 +43,9 @@ import com.jogamp.graph.curve.opengl.RenderState; import com.jogamp.graph.curve.opengl.TextRegionUtil; import com.jogamp.graph.font.Font; import com.jogamp.graph.font.FontFactory; +import com.jogamp.graph.font.FontScale; +import com.jogamp.graph.font.FontSet; +import com.jogamp.graph.font.Font.Glyph; import com.jogamp.newt.Window; import com.jogamp.newt.event.KeyEvent; import com.jogamp.newt.event.KeyListener; @@ -68,13 +71,13 @@ import com.jogamp.opengl.util.PMVMatrix; */ public abstract class GPUTextRendererListenerBase01 extends GPURendererListenerBase01 { public final TextRegionUtil textRegionUtil; - private final GLRegion regionFPS, regionBottom; + private final GLRegion regionFPS, regionHead, regionBottom; int fontSet = FontFactory.UBUNTU; Font font; int headType = 0; boolean drawFPS = true; - final float fontSizeFName = 8f; + final float fontSizeFName = 10f; final float fontSizeFPS = 10f; final int[] sampleCountFPS = new int[] { 8 }; float fontSizeHead = 12f; @@ -88,28 +91,54 @@ public abstract class GPUTextRendererListenerBase01 extends GPURendererListenerB static final String text1 = "abcdefghijklmnopqrstuvwxyz\nABCDEFGHIJKLMNOPQRSTUVWXYZ\n0123456789.:,;(*!?/\\\")$%^&-+@~#<>{}[]"; static final String text2 = "The quick brown fox jumps over the lazy dog"; - static final String textX = + public static final String textX = "JOGAMP graph demo using Resolution Independent NURBS\n"+ "JOGAMP JOGL - OpenGL ES2 profile\n"+ "Press 1/2 to zoom in/out the below text\n"+ "Press 3/4 to incr/decs font size (alt: head, w/o bottom)\n"+ "Press 6/7 to edit texture size if using VBAA\n"+ "Press 0/9 to rotate the below string\n"+ + "Press s to screenshot\n"+ "Press v to toggle vsync\n"+ "Press i for live input text input (CR ends it, backspace supported)\n"+ "Press f to toggle fps. H for different text, space for font type\n"; - static final String textX2 = + public static final String textX2 = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec nec sapien tellus. \n"+ "Ut purus odio, rhoncus sit amet commodo eget, ullamcorper vel urna. Mauris ultricies \n"+ "quam iaculis urna cursus ornare. Nullam ut felis a ante ultrices ultricies nec a elit. \n"+ - "In hac habitasse platea dictumst. Vivamus et mi a quam lacinia pharetra at venenatis est.\n"+ - "Morbi quis bibendum nibh. Donec lectus orci, sagittis in consequat nec, volutpat nec nisi.\n"+ + "In hac habitasse platea dictumst. Vivamus et mi a quam lacinia pharetra at venenatis est. \n"+ + "Morbi quis bibendum nibh. Donec lectus orci, sagittis in consequat nec, volutpat nec nisi. \n"+ "Donec ut dolor et nulla tristique varius. In nulla magna, fermentum id tempus quis, semper \n"+ - "in lorem. Maecenas in ipsum ac justo scelerisque sollicitudin. Quisque sit amet neque lorem,\n" + + "in lorem. Maecenas in ipsum ac justo scelerisque sollicitudin. Quisque sit amet neque lorem, \n" + "-------Press H to change text---------"; - StringBuilder userString = new StringBuilder(); + public static final String textX3 = + "I “Ask Jeff” or ‘Ask Jeff’. Take the chef d’œuvre! Two of [of] (of) ‘of’ “of” of? of! of*.\n"+ + "Les Woëvres, the Fôret de Wœvres, the Voire and Vauvise. Yves is in heaven; D’Amboise is in jail.\n"+ + "Lyford’s in Texas & L’Anse-aux-Griffons in Québec; the Łyna in Poland. Yriarte, Yciar and Ysaÿe are at Yale.\n"+ + "Kyoto and Ryotsu are both in Japan, Kwikpak on the Yukon delta, Kvæven in Norway, Kyulu in Kenya, not in Rwanda.…\n"+ + "Miłosz and Wū Wŭ all in the library? 1510–1620, 11:00 pm, and the 1980s are over.\n"+ + "Ut purus odio, rhoncus sit amet commodo eget, ullamcorper vel urna. Mauris ultricies \n"+ + "-------Press H to change text---------"; + + public static final String textX30 = + "I “Ask Jeff” or ‘Ask Jeff’. Take the chef d’œuvre! Two of [of] (of) ‘of’ “of” of? of! of*.\n"+ + "Two of [of] (of) ‘of’ “of” of? of! of*. Ydes, Yffignac and Ygrande are in France: so are Ypres,\n"+ + "Les Woëvres, the Fôret de Wœvres, the Voire and Vauvise. Yves is in heaven; D’Amboise is in jail.\n"+ + "Lyford’s in Texas & L’Anse-aux-Griffons in Québec; the Łyna in Poland. Yriarte, Yciar and Ysaÿe are at Yale.\n"+ + "Kyoto and Ryotsu are both in Japan, Kwikpak on the Yukon delta, Kvæven in Norway, Kyulu in Kenya, not in Rwanda.…\n"+ + "Walton’s in West Virginia, but «Wren» is in Oregon. Tlálpan is near Xochimilco in México.\n"+ + "The Zygos & Xylophagou are in Cyprus, Zwettl in Austria, Fænø in Denmark, the Vøringsfossen and Værøy in Norway.\n"+ + "Tchula is in Mississippi, the Tittabawassee in Michigan. Twodot is here in Montana, Ywamun in Burma.\n"+ + "Yggdrasil and Ymir, Yngvi and Vóden, Vídrið and Skeggjöld and Týr are all in the Eddas.\n"+ + "Tørberget and Våg, of course, are in Norway, Ktipas and Tmolos in Greece, but Vázquez is in Argentina, Vreden in Germany,\n"+ + "Von-Vincke-Straße in Münster, Vdovino in Russia, Ytterbium in the periodic table. Are Toussaint L’Ouverture, Wölfflin, Wolfe,\n"+ + "Miłosz and Wū Wŭ all in the library? 1510–1620, 11:00 pm, and the 1980s are over.\n"+ + "Ut purus odio, rhoncus sit amet commodo eget, ullamcorper vel urna. Mauris ultricies \n"+ + "-------Press H to change text---------"; + + StringBuilder userString = new StringBuilder(textX2); boolean userInput = false; public GPUTextRendererListenerBase01(final RenderState rs, final int renderModes, final int sampleCount, final boolean blending, final boolean debug, final boolean trace) { // NOTE_ALPHA_BLENDING: We use alpha-blending @@ -119,9 +148,11 @@ public abstract class GPUTextRendererListenerBase01 extends GPURendererListenerB rs.setHintMask(RenderState.BITHINT_GLOBAL_DEPTH_TEST_ENABLED); this.textRegionUtil = new TextRegionUtil(renderModes); this.regionFPS = GLRegion.create(renderModes, null); + this.regionHead = GLRegion.create(renderModes, null); this.regionBottom = GLRegion.create(renderModes, null); try { - this.font = FontFactory.get(fontSet).getDefault(); + // this.font = FontFactory.get(fontSet).getDefault(); + this.font = FontFactory.get(fontSet).get(FontSet.FAMILY_LIGHT, FontSet.STYLE_NONE); dumpFontNames(); this.fontName = font.toString(); @@ -139,7 +170,7 @@ public abstract class GPUTextRendererListenerBase01 extends GPURendererListenerB } void switchHeadBox() { - headType = ( headType + 1 ) % 4 ; + headType = ( headType + 1 ) % 5 ; switch(headType) { case 0: headtext = null; @@ -151,12 +182,15 @@ public abstract class GPUTextRendererListenerBase01 extends GPURendererListenerB case 2: headtext= textX; break; + case 3: + headtext= textX3; + break; default: headtext = text1; } if(null != headtext) { - headbox = font.getMetricBounds(headtext, font.getPixelSize(fontSizeHead, dpiH)); + headbox = font.getMetricBounds(headtext, FontScale.toPixels(fontSizeHead, dpiH)); } } @@ -166,15 +200,13 @@ public abstract class GPUTextRendererListenerBase01 extends GPURendererListenerB final Object upObj = drawable.getUpstreamWidget(); if( upObj instanceof Window ) { final Window window = (Window) upObj; - final float[] sDPI = window.getPixelsPerMM(new float[2]); - sDPI[0] *= 25.4f; - sDPI[1] *= 25.4f; + final float[] sDPI = FontScale.perMMToPerInch( window.getPixelsPerMM(new float[2]) ); dpiH = sDPI[1]; System.err.println("Using screen DPI of "+dpiH); } else { System.err.println("Using default DPI of "+dpiH); } - fontNameBox = font.getMetricBounds(fontName, font.getPixelSize(fontSizeFName, dpiH)); + fontNameBox = font.getMetricBounds(fontName, FontScale.toPixels(fontSizeFName, dpiH)); switchHeadBox(); } @@ -198,6 +230,7 @@ public abstract class GPUTextRendererListenerBase01 extends GPURendererListenerB @Override public void dispose(final GLAutoDrawable drawable) { regionFPS.destroy(drawable.getGL().getGL2ES2()); + regionHead.destroy(drawable.getGL().getGL2ES2()); regionBottom.destroy(drawable.getGL().getGL2ES2()); super.dispose(drawable); } @@ -223,14 +256,14 @@ public abstract class GPUTextRendererListenerBase01 extends GPURendererListenerB pmv.glMatrixMode(GLMatrixFunc.GL_MODELVIEW); pmv.glLoadIdentity(); rs.setColorStatic(0.1f, 0.1f, 0.1f, 1.0f); - final float pixelSizeFName = font.getPixelSize(fontSizeFName, dpiH); - final float pixelSizeHead = font.getPixelSize(fontSizeHead, dpiH); - final float pixelSizeBottom = font.getPixelSize(fontSizeBottom, dpiH); + final float pixelSizeFName = FontScale.toPixels(fontSizeFName, dpiH); + final float pixelSizeHead = FontScale.toPixels(fontSizeHead, dpiH); + final float pixelSizeBottom = FontScale.toPixels(fontSizeBottom, dpiH); renderer.enable(gl, true); if( drawFPS ) { - final float pixelSizeFPS = font.getPixelSize(fontSizeFPS, dpiH); + final float pixelSizeFPS = FontScale.toPixels(fontSizeFPS, dpiH); final float lfps, tfps, td; final GLAnimatorControl animator = drawable.getAnimator(); if( null != animator ) { @@ -250,20 +283,27 @@ public abstract class GPUTextRendererListenerBase01 extends GPURendererListenerB // bottom, half line up pmv.glTranslatef(nearPlaneX0, nearPlaneY0+(nearPlaneS * pixelSizeFPS / 2f), nearPlaneZ0); - + { + final float sxy = ( nearPlaneS * pixelSizeFPS ) / font.getMetrics().getUnitsPerEM(); + pmv.glScalef(sxy, sxy, 1.0f); + } // No cache, keep region alive! - TextRegionUtil.drawString3D(gl, regionFPS, renderer, font, nearPlaneS * pixelSizeFPS, text, null, sampleCountFPS, - textRegionUtil.tempT1, textRegionUtil.tempT2); + TextRegionUtil.drawString3D(gl, regionFPS, renderer, font, text, null, sampleCountFPS, textRegionUtil.tempT1, + textRegionUtil.tempT2); } - float dx = width-fontNameBox.getWidth()-2f; - float dy = height - 10f; + float dx = width-fontNameBox.getWidth()-font.getAdvanceWidth(Glyph.ID_SPACE, pixelSizeFName); + float dy = height-fontNameBox.getHeight(); pmv.glMatrixMode(GLMatrixFunc.GL_MODELVIEW); pmv.glLoadIdentity(); pmv.glTranslatef(nearPlaneX0+(dx*nearPlaneSx), nearPlaneY0+(dy*nearPlaneSy), nearPlaneZ0); + { + final float sxy = ( nearPlaneS * pixelSizeFName ) / font.getMetrics().getUnitsPerEM(); + pmv.glScalef(sxy, sxy, 1.0f); + } // System.err.printf("FontN: [%f %f] -> [%f %f]%n", dx, dy, nearPlaneX0+(dx*nearPlaneSx), nearPlaneY0+(dy*nearPlaneSy)); - textRegionUtil.drawString3D(gl, renderer, font, nearPlaneS * pixelSizeFName, fontName, null, getSampleCount()); + textRegionUtil.drawString3D(gl, renderer, font, fontName, null, getSampleCount()); dx = 10f; dy += -fontNameBox.getHeight() - 10f; @@ -273,8 +313,12 @@ public abstract class GPUTextRendererListenerBase01 extends GPURendererListenerB pmv.glLoadIdentity(); // System.err.printf("Head: [%f %f] -> [%f %f]%n", dx, dy, nearPlaneX0+(dx*nearPlaneSx), nearPlaneY0+(dy*nearPlaneSy)); pmv.glTranslatef(nearPlaneX0+(dx*nearPlaneSx), nearPlaneY0+(dy*nearPlaneSy), nearPlaneZ0); + { + final float sxy = ( nearPlaneS * pixelSizeHead ) / font.getMetrics().getUnitsPerEM(); + pmv.glScalef(sxy, sxy, 1.0f); + } // pmv.glTranslatef(x0, y1, z0); - textRegionUtil.drawString3D(gl, renderer, font, nearPlaneS * pixelSizeHead, headtext, null, getSampleCount()); + textRegionUtil.drawString3D(gl, renderer, font, headtext, null, getSampleCount()); } dy += -headbox.getHeight() - font.getLineHeight(pixelSizeBottom); @@ -285,6 +329,10 @@ public abstract class GPUTextRendererListenerBase01 extends GPURendererListenerB // System.err.printf("Bottom: [%f %f] -> [%f %f]%n", dx, dy, nearPlaneX0+(dx*nearPlaneSx), nearPlaneY0+(dy*nearPlaneSy)); pmv.glTranslatef(getXTran(), getYTran(), getZTran()); pmv.glRotatef(getAngle(), 0, 1, 0); + { + final float sxy = ( nearPlaneS * pixelSizeBottom ) / font.getMetrics().getUnitsPerEM(); + pmv.glScalef(sxy, sxy, 1.0f); + } rs.setColorStatic(0.9f, 0.0f, 0.0f, 1.0f); if( bottomTextUseFrustum ) { @@ -292,23 +340,26 @@ public abstract class GPUTextRendererListenerBase01 extends GPURendererListenerB } if(!userInput) { if( bottomTextUseFrustum ) { - TextRegionUtil.drawString3D(gl, regionBottom, renderer, font, nearPlaneS * pixelSizeBottom, text2, null, getSampleCount(), - textRegionUtil.tempT1, textRegionUtil.tempT2); + TextRegionUtil.drawString3D(gl, regionBottom, renderer, font, text2, null, getSampleCount(), textRegionUtil.tempT1, + textRegionUtil.tempT2); } else { - textRegionUtil.drawString3D(gl, renderer, font, nearPlaneS * pixelSizeBottom, text2, null, getSampleCount()); + textRegionUtil.drawString3D(gl, renderer, font, text2, null, getSampleCount()); } } else { if( bottomTextUseFrustum ) { - TextRegionUtil.drawString3D(gl, regionBottom, renderer, font, nearPlaneS * pixelSizeBottom, userString.toString(), null, getSampleCount(), - textRegionUtil.tempT1, textRegionUtil.tempT2); + TextRegionUtil.drawString3D(gl, regionBottom, renderer, font, userString.toString(), null, getSampleCount(), textRegionUtil.tempT1, + textRegionUtil.tempT2); } else { - textRegionUtil.drawString3D(gl, renderer, font, nearPlaneS * pixelSizeBottom, userString.toString(), null, getSampleCount()); + textRegionUtil.drawString3D(gl, renderer, font, userString.toString(), null, getSampleCount()); } } renderer.enable(gl, false); } final boolean bottomTextUseFrustum = true; + public Font getFont() { return font; } + public float getFontSizeHead() { return fontSizeHead; } + public void fontBottomIncr(final int v) { fontSizeBottom = Math.abs((fontSizeBottom + v) % fontSizeModulo) ; dumpMatrix(true); @@ -317,7 +368,7 @@ public abstract class GPUTextRendererListenerBase01 extends GPURendererListenerB public void fontHeadIncr(final int v) { fontSizeHead = Math.abs((fontSizeHead + v) % fontSizeModulo) ; if(null != headtext) { - headbox = font.getMetricBounds(headtext, font.getPixelSize(fontSizeHead, dpiH)); + headbox = font.getMetricBounds(headtext, FontScale.toPixels(fontSizeHead, dpiH)); } } @@ -329,7 +380,7 @@ public abstract class GPUTextRendererListenerBase01 extends GPURendererListenerB fontSet = set; font = _font; fontName = font.getFullFamilyName(null).toString(); - fontNameBox = font.getMetricBounds(fontName, font.getPixelSize(fontSizeFName, dpiH)); + fontNameBox = font.getMetricBounds(fontName, FontScale.toPixels(fontSizeFName, dpiH)); dumpFontNames(); return true; } @@ -346,7 +397,7 @@ public abstract class GPUTextRendererListenerBase01 extends GPURendererListenerB fontSet = set; font = _font; fontName = font.getFullFamilyName(null).toString(); - fontNameBox = font.getMetricBounds(fontName, font.getPixelSize(fontSizeFName, dpiH)); + fontNameBox = font.getMetricBounds(fontName, FontScale.toPixels(fontSizeFName, dpiH)); dumpFontNames(); return true; } @@ -361,7 +412,7 @@ public abstract class GPUTextRendererListenerBase01 extends GPURendererListenerB void dumpMatrix(final boolean bbox) { System.err.println("Matrix: " + getXTran() + "/" + getYTran() + " x"+getZTran() + " @"+getAngle() +" fontSize "+fontSizeBottom); if(bbox) { - System.err.println("bbox: "+font.getMetricBounds(text2, nearPlaneS * font.getPixelSize(fontSizeBottom, dpiH))); + System.err.println("bbox: "+font.getMetricBounds(text2, nearPlaneS * FontScale.toPixels(fontSizeBottom, dpiH))); } } diff --git a/src/test/com/jogamp/opengl/test/junit/graph/demos/GPUUISceneGLListener0A.java b/src/test/com/jogamp/opengl/test/junit/graph/demos/GPUUISceneGLListener0A.java index a55ed26c0..e92f18995 100644 --- a/src/test/com/jogamp/opengl/test/junit/graph/demos/GPUUISceneGLListener0A.java +++ b/src/test/com/jogamp/opengl/test/junit/graph/demos/GPUUISceneGLListener0A.java @@ -23,6 +23,7 @@ import com.jogamp.graph.curve.opengl.RegionRenderer; import com.jogamp.graph.curve.opengl.RenderState; import com.jogamp.graph.font.Font; import com.jogamp.graph.font.FontFactory; +import com.jogamp.graph.font.FontScale; import com.jogamp.graph.geom.SVertex; import com.jogamp.newt.MonitorDevice; import com.jogamp.newt.Window; @@ -357,6 +358,7 @@ public class GPUUISceneGLListener0A implements GLEventListener { @Override public void mouseClicked(final MouseEvent e) { new InterruptSource.Thread() { + @Override public void run() { if( null != cDrawable ) { final GLAnimatorControl actrl = cDrawable.getAnimator(); @@ -668,12 +670,8 @@ public class GPUUISceneGLListener0A implements GLEventListener { if( upObj instanceof Window ) { final Window upWin = (Window)upObj; final MonitorDevice mm = upWin.getMainMonitor(); - final float[] monitorDPI = mm.getPixelsPerMM(new float[2]); - monitorDPI[0] *= 25.4f; - monitorDPI[1] *= 25.4f; - final float[] sDPI = upWin.getPixelsPerMM(new float[2]); - sDPI[0] *= 25.4f; - sDPI[1] *= 25.4f; + final float[] monitorDPI = FontScale.perMMToPerInch( mm.getPixelsPerMM(new float[2]) ); + final float[] sDPI = FontScale.perMMToPerInch( upWin.getPixelsPerMM(new float[2]) ); dpiH = sDPI[1]; System.err.println("Monitor detected: "+mm); System.err.println("Monitor dpi: "+monitorDPI[0]+" x "+monitorDPI[1]); @@ -731,7 +729,7 @@ public class GPUUISceneGLListener0A implements GLEventListener { sceneUIController.addShape(jogampLabel); jogampLabel.setEnabled(enableOthers); - final float pixelSize10Pt = font.getPixelSize(fontSizePt, dpiH); + final float pixelSize10Pt = FontScale.toPixels(fontSizePt, dpiH); System.err.println("10Pt PixelSize: Display "+dpiH+" dpi, fontSize "+fontSizePt+" ppi -> "+pixelSize10Pt+" pixel-size"); truePtSizeLabel = new Label(renderer.getRenderState().getVertexFactory(), renderModes, font, pixelSize10Pt, truePtSize); sceneUIController.addShape(truePtSizeLabel); diff --git a/src/test/com/jogamp/opengl/test/junit/graph/demos/ui/LabelButton.java b/src/test/com/jogamp/opengl/test/junit/graph/demos/ui/LabelButton.java index a6b2e03bd..39eab26e2 100644 --- a/src/test/com/jogamp/opengl/test/junit/graph/demos/ui/LabelButton.java +++ b/src/test/com/jogamp/opengl/test/junit/graph/demos/ui/LabelButton.java @@ -107,7 +107,7 @@ public class LabelButton extends RoundButton { } // Setting pixelSize based on actual text-box size - final AABBox lbox1 = label.font.getPointsBounds(null, label.text, lPixelSize1, tempT1, tempT2); + final AABBox lbox1 = label.font.getPointsBounds(null, label.text, lPixelSize1); // Center text .. (share same center w/ button) final float[] lctr = lbox1.getCenter(); final float[] ctr = box.getCenter(); diff --git a/src/test/com/jogamp/opengl/test/junit/jogl/acore/GLReadBuffer00Base.java b/src/test/com/jogamp/opengl/test/junit/jogl/acore/GLReadBuffer00Base.java index 163f5736d..66655dadd 100644 --- a/src/test/com/jogamp/opengl/test/junit/jogl/acore/GLReadBuffer00Base.java +++ b/src/test/com/jogamp/opengl/test/junit/jogl/acore/GLReadBuffer00Base.java @@ -44,6 +44,7 @@ import com.jogamp.graph.curve.Region; import com.jogamp.graph.curve.opengl.GLRegion; import com.jogamp.graph.curve.opengl.RegionRenderer; import com.jogamp.graph.font.Font; +import com.jogamp.graph.font.FontScale; import com.jogamp.opengl.test.junit.graph.TextRendererGLELBase; import com.jogamp.opengl.test.junit.util.UITestCase; @@ -93,7 +94,7 @@ public abstract class GLReadBuffer00Base extends UITestCase { final String text = String.format("Frame %04d (%03d): %04dx%04d", frameNo, userCounter, drawable.getSurfaceWidth(), drawable.getSurfaceHeight()); System.err.println("TextRendererGLEL.display: "+text); if( null != renderer ) { - final float pixelSize = font.getPixelSize(14f, dpiH); + final float pixelSize = FontScale.toPixels(14f, dpiH); drawable.getGL().glClearColor(1f, 1f, 1f, 0f); renderString(drawable, font, pixelSize, text, 0 /* col */, 0 /* row */, 0, 0, -1, regionFPS); } else { diff --git a/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/av/MovieCube.java b/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/av/MovieCube.java index a8dede526..134503520 100644 --- a/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/av/MovieCube.java +++ b/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/av/MovieCube.java @@ -47,6 +47,7 @@ import com.jogamp.graph.curve.Region; import com.jogamp.graph.curve.opengl.GLRegion; import com.jogamp.graph.curve.opengl.RegionRenderer; import com.jogamp.graph.font.Font; +import com.jogamp.graph.font.FontScale; import com.jogamp.junit.util.JunitTracer; import com.jogamp.newt.Window; import com.jogamp.newt.event.KeyAdapter; @@ -118,6 +119,7 @@ public class MovieCube implements GLEventListener { } if( 0 != ( GLMediaEventListener.EVENT_CHANGE_EOS & event_mask ) ) { new InterruptSource.Thread() { + @Override public void run() { // loop for-ever .. mPlayer.seek(0); @@ -196,8 +198,8 @@ public class MovieCube implements GLEventListener { this.setSharedPMVMatrix(cube.pmvMatrix); super.init(drawable); - pixelSize1 = font.getPixelSize(fontSize1, dpiH); - pixelSize2 = font.getPixelSize(fontSize2, dpiH); + pixelSize1 = FontScale.toPixels(fontSize1, dpiH); + pixelSize2 = FontScale.toPixels(fontSize2, dpiH); pixelScale = 1.0f / ( pixelSize1 * 20f ); // underlineSize: 'underline' amount of pixel below 0/0 (Note: lineGap is negative) final Font.Metrics metrics = font.getMetrics(); @@ -270,6 +272,7 @@ public class MovieCube implements GLEventListener { private boolean displayOSD = true; private final KeyListener keyAction = new KeyAdapter() { + @Override public void keyReleased(final KeyEvent e) { if( e.isAutoRepeat() ) { return; @@ -562,6 +565,7 @@ public class MovieCube implements GLEventListener { final GLWindow window = GLWindow.create(caps); final Animator anim = new Animator(window); window.addWindowListener(new WindowAdapter() { + @Override public void windowDestroyed(final WindowEvent e) { anim.stop(); } @@ -601,6 +605,7 @@ public class MovieCube implements GLEventListener { se.printStackTrace(); } new InterruptSource.Thread() { + @Override public void run() { window.destroy(); } }.start(); diff --git a/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/av/MovieSBSStereo.java b/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/av/MovieSBSStereo.java index 9b9073721..af0aaa775 100644 --- a/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/av/MovieSBSStereo.java +++ b/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/av/MovieSBSStereo.java @@ -47,6 +47,7 @@ import com.jogamp.graph.curve.Region; import com.jogamp.graph.curve.opengl.GLRegion; import com.jogamp.graph.curve.opengl.RegionRenderer; import com.jogamp.graph.font.Font; +import com.jogamp.graph.font.FontScale; import com.jogamp.junit.util.JunitTracer; import com.jogamp.newt.Window; import com.jogamp.newt.event.KeyAdapter; @@ -194,7 +195,7 @@ public class MovieSBSStereo implements StereoGLEventListener { final String text4 = mPlayer.getUri().path.decode(); if( displayOSD && null != renderer ) { // We share ClearColor w/ MovieSimple's init ! - final float pixelSize = font.getPixelSize(fontSize, dpiH); + final float pixelSize = FontScale.toPixels(fontSize, dpiH); if( null != regionFPS ) { renderString(drawable, font, pixelSize, text1, 1 /* col */, 1 /* row */, 0, 0, -1, regionFPS); // no-cache } else { @@ -210,6 +211,7 @@ public class MovieSBSStereo implements StereoGLEventListener { private boolean displayOSD = false; private final MouseListener mouseAction = new MouseAdapter() { + @Override public void mousePressed(final MouseEvent e) { if(e.getY()<=surfHeight/2 && null!=mPlayer && 1 == e.getClickCount()) { if(GLMediaPlayer.State.Playing == mPlayer.getState()) { @@ -219,6 +221,7 @@ public class MovieSBSStereo implements StereoGLEventListener { } } } + @Override public void mouseReleased(final MouseEvent e) { if(e.getY()<=surfHeight/2) { rotate = -1; @@ -226,10 +229,12 @@ public class MovieSBSStereo implements StereoGLEventListener { System.err.println("zoom: "+zoom); } } + @Override public void mouseMoved(final MouseEvent e) { prevMouseX = e.getX(); // prevMouseY = e.getY(); } + @Override public void mouseDragged(final MouseEvent e) { final int x = e.getX(); final int y = e.getY(); @@ -247,6 +252,7 @@ public class MovieSBSStereo implements StereoGLEventListener { prevMouseX = x; // prevMouseY = y; } + @Override public void mouseWheelMoved(final MouseEvent e) { if( !e.isShiftDown() ) { zoom += e.getRotation()[1]/10f; // vertical: wheel @@ -255,6 +261,7 @@ public class MovieSBSStereo implements StereoGLEventListener { } }; private final KeyListener keyAction = new KeyAdapter() { + @Override public void keyReleased(final KeyEvent e) { if( e.isAutoRepeat() ) { return; @@ -802,6 +809,7 @@ public class MovieSBSStereo implements StereoGLEventListener { static class StereoGLMediaEventListener implements GLMediaEventListener { void destroyWindow(final Window window) { new InterruptSource.Thread() { + @Override public void run() { window.destroy(); } }.start(); @@ -848,6 +856,7 @@ public class MovieSBSStereo implements StereoGLEventListener { } else { System.err.println("MovieSimple State: EOS"); new InterruptSource.Thread() { + @Override public void run() { mp.setPlaySpeed(1f); mp.seek(0); diff --git a/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/av/MovieSimple.java b/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/av/MovieSimple.java index 25ce93597..4ba3372eb 100644 --- a/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/av/MovieSimple.java +++ b/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/av/MovieSimple.java @@ -52,6 +52,7 @@ import com.jogamp.graph.curve.Region; import com.jogamp.graph.curve.opengl.GLRegion; import com.jogamp.graph.curve.opengl.RegionRenderer; import com.jogamp.graph.font.Font; +import com.jogamp.graph.font.FontScale; import com.jogamp.junit.util.JunitTracer; import com.jogamp.newt.Window; import com.jogamp.newt.event.KeyAdapter; @@ -197,7 +198,7 @@ public class MovieSimple implements GLEventListener { final String text4 = mPlayer.getUri().path.decode(); if( displayOSD && null != renderer ) { // We share ClearColor w/ MovieSimple's init ! - final float pixelSize = font.getPixelSize(fontSize, dpiH); + final float pixelSize = FontScale.toPixels(fontSize, dpiH); if( null != regionFPS ) { renderString(drawable, font, pixelSize, text1, 1 /* col */, 1 /* row */, 0, 0, -1, regionFPS); // no-cache } else { @@ -212,6 +213,7 @@ public class MovieSimple implements GLEventListener { private boolean displayOSD = true; private final MouseListener mouseAction = new MouseAdapter() { + @Override public void mousePressed(final MouseEvent e) { if(e.getY()<=surfHeight/2 && null!=mPlayer && 1 == e.getClickCount()) { if(GLMediaPlayer.State.Playing == mPlayer.getState()) { @@ -221,6 +223,7 @@ public class MovieSimple implements GLEventListener { } } } + @Override public void mouseReleased(final MouseEvent e) { if(e.getY()<=surfHeight/2) { rotate = -1; @@ -228,10 +231,12 @@ public class MovieSimple implements GLEventListener { System.err.println("zoom: "+zoom); } } + @Override public void mouseMoved(final MouseEvent e) { prevMouseX = e.getX(); // prevMouseY = e.getY(); } + @Override public void mouseDragged(final MouseEvent e) { final int x = e.getX(); final int y = e.getY(); @@ -249,6 +254,7 @@ public class MovieSimple implements GLEventListener { prevMouseX = x; // prevMouseY = y; } + @Override public void mouseWheelMoved(final MouseEvent e) { if( !e.isShiftDown() ) { zoom += e.getRotation()[1]/10f; // vertical: wheel @@ -257,6 +263,7 @@ public class MovieSimple implements GLEventListener { } }; private final KeyListener keyAction = new KeyAdapter() { + @Override public void keyReleased(final KeyEvent e) { if( e.isAutoRepeat() ) { return; @@ -357,6 +364,7 @@ public class MovieSimple implements GLEventListener { } if( 0 != ( GLMediaEventListener.EVENT_CHANGE_EOS & event_mask ) ) { new InterruptSource.Thread() { + @Override public void run() { // loop for-ever .. mPlayer.seek(0); @@ -809,6 +817,7 @@ public class MovieSimple implements GLEventListener { static class MyGLMediaEventListener implements GLMediaEventListener { void destroyWindow(final Window window) { new InterruptSource.Thread() { + @Override public void run() { window.destroy(); } }.start(); @@ -875,6 +884,7 @@ public class MovieSimple implements GLEventListener { System.err.println("MovieSimple State: EOS"); if( loopEOS ) { new InterruptSource.Thread() { + @Override public void run() { mp.setPlaySpeed(1f); mp.seek(0); @@ -1038,6 +1048,7 @@ public class MovieSimple implements GLEventListener { anim.start(); windows[i] = GLWindow.create(caps); windows[i].addWindowListener(new WindowAdapter() { + @Override public void windowDestroyed(final WindowEvent e) { anim.stop(); } diff --git a/src/test/com/jogamp/opengl/test/junit/newt/parenting/NewtReparentingKeyAdapter.java b/src/test/com/jogamp/opengl/test/junit/newt/parenting/NewtReparentingKeyAdapter.java index cd4edddb1..5d1ceba36 100644 --- a/src/test/com/jogamp/opengl/test/junit/newt/parenting/NewtReparentingKeyAdapter.java +++ b/src/test/com/jogamp/opengl/test/junit/newt/parenting/NewtReparentingKeyAdapter.java @@ -27,6 +27,7 @@ */ package com.jogamp.opengl.test.junit.newt.parenting; +import com.jogamp.graph.font.FontScale; import com.jogamp.nativewindow.CapabilitiesImmutable; import com.jogamp.nativewindow.NativeWindow; import com.jogamp.nativewindow.NativeWindowHolder; @@ -69,6 +70,7 @@ public class NewtReparentingKeyAdapter extends NEWTDemoListener { e.setConsumed(true); quitAdapterOff(); glWindow.invokeOnNewThread(null, false, new Runnable() { + @Override public void run() { final java.lang.Thread t = glWindow.setExclusiveContextThread(null); if(glWindow.getParent()==null) { @@ -103,9 +105,7 @@ public class NewtReparentingKeyAdapter extends NEWTDemoListener { final CapabilitiesImmutable reqCaps = win.getRequestedCapabilities(); final CapabilitiesImmutable caps = null != chosenCaps ? chosenCaps : reqCaps; final String capsA = caps.isBackgroundOpaque() ? "opaque" : "transl"; - final float[] sDPI = win.getPixelsPerMM(new float[2]); - sDPI[0] *= 25.4f; - sDPI[1] *= 25.4f; + final float[] sDPI = FontScale.perMMToPerInch( win.getPixelsPerMM(new float[2]) ); win.setTitle("GLWindow["+capsA+"], win: "+win.getBounds()+", pix: "+win.getSurfaceWidth()+"x"+win.getSurfaceHeight()+", sDPI "+sDPI[0]+" x "+sDPI[1]); } } |