diff options
author | Sven Gothel <[email protected]> | 2023-09-24 16:50:49 +0200 |
---|---|---|
committer | Sven Gothel <[email protected]> | 2023-09-24 16:50:49 +0200 |
commit | a8de1673ca83475227fcc914fd94a9a0be1cba79 (patch) | |
tree | f180bafeb037fd66eed565236a054322fe5e229e /src/jogl/classes/jogamp/graph/font | |
parent | 0c8700589abffe13e42f18d3c755541268d44a34 (diff) |
Bug 1462 - Graph Font: Add name + codepoint to ID and Glyph mapping plus traversing through all Glyphs
See UISceneDemo03
new Button(options.renderModes, fontSymbols, " "+fontSymbols.getUTF16String("pause")+" ", buttonWidth, buttonHeight); // pause
Unicode codepoint symbol is also contained in FontGlyph
Diffstat (limited to 'src/jogl/classes/jogamp/graph/font')
-rw-r--r-- | src/jogl/classes/jogamp/graph/font/typecast/TypecastFont.java | 299 | ||||
-rw-r--r-- | src/jogl/classes/jogamp/graph/font/typecast/TypecastGlyph.java | 32 |
2 files changed, 215 insertions, 116 deletions
diff --git a/src/jogl/classes/jogamp/graph/font/typecast/TypecastFont.java b/src/jogl/classes/jogamp/graph/font/typecast/TypecastFont.java index a8651389c..e8c6a4ed8 100644 --- a/src/jogl/classes/jogamp/graph/font/typecast/TypecastFont.java +++ b/src/jogl/classes/jogamp/graph/font/typecast/TypecastFont.java @@ -27,6 +27,9 @@ */ package jogamp.graph.font.typecast; +import java.util.HashMap; +import java.util.Map; + import com.jogamp.common.util.IntObjectHashMap; import com.jogamp.graph.curve.OutlineShape; import com.jogamp.graph.font.Font; @@ -52,14 +55,24 @@ import jogamp.opengl.Debug; class TypecastFont implements Font { private static final boolean DEBUG = Debug.debug("graph.font.Font"); + private static final char UNDEF_SYMBOL = 0xffff; + static class SymAndID { + public final char codepoint; + public final int id; + public final String name; + public TypecastGlyph glyph; + SymAndID(final char codepoint, final int id, final String name) { this.codepoint = codepoint; this.id = id; this.name = name; this.glyph = null; } + } + private static final SymAndID UNDEF_VALUE = new SymAndID(UNDEF_SYMBOL, Glyph.ID_UNKNOWN, TypecastGlyph.dot_undef_NAME); // private final OTFontCollection fontset; /* pp */ final TTFont font; private final CmapFormat cmapFormat; private final int cmapentries; private final IntObjectHashMap idToGlyph; + private final IntObjectHashMap cpToGlyph; + private final Map<String, SymAndID> nameToGlyph; private final TypecastHMetrics metrics; - // FIXME: Add cache size to limit memory usage ?? private static final boolean forceAscii = false; // FIXME ??? (ASCII/Macintosh cmap format) @@ -92,27 +105,58 @@ class TypecastFont implements Font { throw new RuntimeException("Cannot find a suitable cmap table"); } cmapFormat = cmapFmt; - if(DEBUG) { - System.err.println("Selected CmapFormat: platform " + platform + ", encoding "+encoding + ": "+cmapFormat); - } - { int _cmapentries = 0; - for (int i = 0; i < cmapFormat.getRangeCount(); ++i) { + for(int i=0; i<cmapFormat.getRangeCount(); ++i) { final CmapFormat.Range range = cmapFormat.getRange(i); _cmapentries += range.getEndCode() - range.getStartCode() + 1; // end included } cmapentries = _cmapentries; + idToGlyph = new IntObjectHashMap(cmapentries + cmapentries/4); + idToGlyph.setKeyNotFoundValue(UNDEF_VALUE); + cpToGlyph = new IntObjectHashMap(cmapentries + cmapentries/4); + cpToGlyph.setKeyNotFoundValue(UNDEF_VALUE); + nameToGlyph = new HashMap<String, SymAndID>(cmapentries + cmapentries/4); + final PostTable post = font.getPostTable(); + for(int i=0; i<cmapFormat.getRangeCount(); ++i) { + final CmapFormat.Range range = cmapFormat.getRange(i); + for(int codepoint = range.getStartCode(); codepoint <= range.getEndCode(); ++codepoint) { + final int id = cmapFormat.mapCharCode(codepoint); + if( 0 < id ) { + boolean mapped = false; + if( null != post ) { + final String name = post.getGlyphName(id); + if( null != name && name.length() > 0 ) { + final SymAndID value = new SymAndID((char)codepoint, id, name); + nameToGlyph.put(name, value); + idToGlyph.put(id, value); + cpToGlyph.put(codepoint, value); + mapped = true; + } + } + if( !mapped ) { + final SymAndID value = new SymAndID((char)codepoint, id, ""); + idToGlyph.put(id, value); + cpToGlyph.put(codepoint, value); + } + } + } + } + if( DEBUG ) { + System.err.println("Selected CmapFormat: platform " + platform + ", encoding "+encoding + ": "+cmapFormat.getClass().getSimpleName()); + System.err.println("Map Result"); + System.err.println("Map: idToGlyph: "+idToGlyph.size()); + System.err.println("Map: nameToGlyph: "+nameToGlyph.size()); + System.err.println("CMap entries: "+cmapentries); + System.err.println("Font glyph-count "+this.getGlyphCount()+", num-glyphs "+font.getNumGlyphs()); + } } - idToGlyph = new IntObjectHashMap(cmapentries + cmapentries/4); metrics = new TypecastHMetrics(this); + getGlyphImpl(UNDEF_VALUE); if(DEBUG) { final int max_id = 36; // "A" System.err.println("font direction hint: "+font.getHeadTable().getFontDirectionHint()); - System.err.println("num glyphs: "+font.getNumGlyphs()); - System.err.println("num cmap entries: "+cmapentries); - System.err.println("num cmap ranges: "+cmapFormat.getRangeCount()); for (int i = 0; i < cmapFormat.getRangeCount(); ++i) { final CmapFormat.Range range = cmapFormat.getRange(i); @@ -177,12 +221,31 @@ class TypecastFont implements Font { } @Override - public int getGlyphID(final char symbol) { - final int glyphID = cmapFormat.mapCharCode(symbol); - if( 0 < glyphID ) { - return glyphID; + public int getGlyphCount() { return font.getGlyphCount(); } + + @Override + public char getGlyphCodepoint(final String name) { + final SymAndID value = nameToGlyph.get(name); + if( null != value ) { + return value.codepoint; + } else { + return UNDEF_VALUE.codepoint; + } + } + + @Override + public String getUTF16String(final String name) { + return Font.getUTF16String( getGlyphCodepoint( name ) ); + } + + @Override + public int getGlyphID(final char codepoint) { + final SymAndID value = (SymAndID) cpToGlyph.get(codepoint); + if( null != value ) { + return value.id; + } else { + return UNDEF_VALUE.id; } - return Glyph.ID_UNKNOWN; } /** pp **/ PostTable getPostTable() { @@ -190,73 +253,112 @@ class TypecastFont implements Font { } @Override - public int getGlyphCount() { return font.getGlyphCount(); } + public synchronized Glyph getGlyph(final String name) { + final SymAndID value = nameToGlyph.get(name); + if( null != value ) { + return getGlyphImpl(value); + } else { + return UNDEF_VALUE.glyph; + } + } + + @Override + public synchronized Glyph getGlyph(final char codepoint) { + final SymAndID value = (SymAndID) cpToGlyph.get(codepoint); + if( null != value ) { + return getGlyphImpl(value); + } else { + return UNDEF_VALUE.glyph; + } + } @Override - public Glyph getGlyph(final int glyph_id) { - TypecastGlyph result = (TypecastGlyph) idToGlyph.get(glyph_id); - if (null == result) { - final jogamp.graph.font.typecast.ot.Glyph glyph = font.getGlyph(glyph_id); - final String glyph_name; - { - final PostTable post = font.getPostTable(); - glyph_name = null != post ? post.getGlyphName(glyph_id) : ""; + public synchronized Glyph getGlyph(final int glyph_id) { + final SymAndID value = (SymAndID) idToGlyph.get(glyph_id); + if( null != value ) { + return getGlyphImpl(value); + } else { + return UNDEF_VALUE.glyph; + } + } + + @Override + public void forAllCodepoints(final Font.CodepointIDVisitor visitor) { + for(int i=0; i<cmapFormat.getRangeCount(); ++i) { + final CmapFormat.Range range = cmapFormat.getRange(i); + for(int codepoint = range.getStartCode(); codepoint <= range.getEndCode(); ++codepoint) { + visitor.visit( (char)codepoint, getGlyphID((char)codepoint) ); } - final boolean isUndefined = Glyph.ID_UNKNOWN == glyph_id || TypecastGlyph.isUndefName(glyph_name); - final int glyph_height = metrics.getAscentFU() - metrics.getDescentFU(); - final int glyph_advance; - final int glyph_leftsidebearings; - final boolean isWhitespace; - final AABBox glyph_bbox; - final OutlineShape shape; - final int mode; - if( null != glyph ) { - glyph_advance = glyph.getAdvanceWidth(); - glyph_leftsidebearings = glyph.getLeftSideBearing(); - final AABBox sb = glyph.getBBox(); - final OutlineShape os = TypecastRenderer.buildShape(metrics.getUnitsPerEM(), glyph); - if( 0 < os.getVertexCount() ) { - // Case 1: Either valid contour glyph, undefined or a whitespace (Case 2 with zero-area shape) - isWhitespace = isUndefined ? false : os.getBounds().hasZero2DArea(); - glyph_bbox = sb; - shape = ( !isWhitespace && !isUndefined ) || Glyph.ID_UNKNOWN == glyph_id ? os : null; - mode = 1; - } else { - // Case 2: Non-contour glyph -> whitespace or undefined - isWhitespace = !isUndefined; - glyph_bbox = new AABBox(0f,0f,0f, glyph_advance, glyph_height, 0f); - shape = Glyph.ID_UNKNOWN == glyph_id ? TypecastRenderer.buildEmptyShape(metrics.getUnitsPerEM(), glyph_bbox) : null; - mode = 2; - } + } + } + @Override + public void forAllGlyphs(final Font.GlyphVisitor2 visitor) { + for(int i=0; i<cmapFormat.getRangeCount(); ++i) { + final CmapFormat.Range range = cmapFormat.getRange(i); + for(int codepoint = range.getStartCode(); codepoint <= range.getEndCode(); ++codepoint) { + visitor.visit( getGlyph( (char)codepoint ) ); + } + } + } + + private Glyph getGlyphImpl(final SymAndID key) { + if( null != key.glyph ) { + return key.glyph; + } + final jogamp.graph.font.typecast.ot.Glyph glyph = font.getGlyph(key.id); + final boolean isUndefined = Glyph.ID_UNKNOWN == key.id || TypecastGlyph.isUndefName(key.name); + final int glyph_height = metrics.getAscentFU() - metrics.getDescentFU(); + final int glyph_advance; + final int glyph_leftsidebearings; + final boolean isWhitespace; + final AABBox glyph_bbox; + final OutlineShape shape; + final int mode; + if( null != glyph ) { + glyph_advance = glyph.getAdvanceWidth(); + glyph_leftsidebearings = glyph.getLeftSideBearing(); + final AABBox sb = glyph.getBBox(); + final OutlineShape os = TypecastRenderer.buildShape(metrics.getUnitsPerEM(), glyph); + if( 0 < os.getVertexCount() ) { + // Case 1: Either valid contour glyph, undefined or a whitespace (Case 2 with zero-area shape) + isWhitespace = isUndefined ? false : os.getBounds().hasZero2DArea(); + glyph_bbox = sb; + shape = ( !isWhitespace && !isUndefined ) || Glyph.ID_UNKNOWN == key.id ? os : null; + mode = 1; } else { - // Case 3: Non-contour glyph -> whitespace or undefined - glyph_advance = getAdvanceWidthFU(glyph_id); - glyph_leftsidebearings = 0; + // Case 2: Non-contour glyph -> whitespace or undefined isWhitespace = !isUndefined; glyph_bbox = new AABBox(0f,0f,0f, glyph_advance, glyph_height, 0f); - shape = Glyph.ID_UNKNOWN == glyph_id ? TypecastRenderer.buildEmptyShape(metrics.getUnitsPerEM(), glyph_bbox) : null; - mode = 3; - } - KernSubtable kernSub = null; - { - final KernTable kern = font.getKernTable(); - if (kern != null ) { - kernSub = kern.getSubtable0(); - } + shape = Glyph.ID_UNKNOWN == key.id ? TypecastRenderer.buildEmptyShape(metrics.getUnitsPerEM(), glyph_bbox) : null; + mode = 2; } - result = new TypecastGlyph(this, glyph_id, glyph_name, glyph_bbox, glyph_advance, glyph_leftsidebearings, kernSub, shape, - isUndefined, isWhitespace); - if( DEBUG || TypecastRenderer.DEBUG ) { - System.err.println("New glyph: " + glyph_id + "/'"+glyph_name+"', shape " + (null != shape)+", mode "+mode); - System.err.println(" tc_glyph "+glyph); - System.err.println(" glyph "+result); - } - if( null != glyph ) { - glyph.clearPointData(); + } else { + // Case 3: Non-contour glyph -> whitespace or undefined + glyph_advance = getAdvanceWidthFU(key.id); + glyph_leftsidebearings = 0; + isWhitespace = !isUndefined; + glyph_bbox = new AABBox(0f,0f,0f, glyph_advance, glyph_height, 0f); + shape = Glyph.ID_UNKNOWN == key.id ? TypecastRenderer.buildEmptyShape(metrics.getUnitsPerEM(), glyph_bbox) : null; + mode = 3; + } + KernSubtable kernSub = null; + { + final KernTable kern = font.getKernTable(); + if (kern != null ) { + kernSub = kern.getSubtable0(); } - - idToGlyph.put(glyph_id, result); } + final TypecastGlyph result = new TypecastGlyph(this, key.codepoint, key.id, key.name, glyph_bbox, glyph_advance, glyph_leftsidebearings, kernSub, + shape, isUndefined, isWhitespace); + if( DEBUG || TypecastRenderer.DEBUG ) { + System.err.println("New glyph: id 0x" + Integer.toHexString(key.id) + "/'"+key.name+"', sym 0x"+Integer.toHexString(key.codepoint)+", shape " + (null != shape)+", mode "+mode); + System.err.println(" tc_glyph "+glyph); + System.err.println(" glyph "+result); + } + if( null != glyph ) { + glyph.clearPointData(); + } + key.glyph = result; return result; } @@ -336,34 +438,33 @@ class TypecastFont implements Font { final AABBox temp_box = new AABBox(); for(int i=0; i< charCount; i++) { - final char character = string.charAt(i); - if( '\n' == character ) { + final char codepoint = string.charAt(i); + if( '\n' == codepoint ) { y -= lineHeight; advanceTotal = 0; left_glyph = null; } else { // reset transform temp1.setToIdentity(); - final int glyph_id = getGlyphID(character); - final Font.Glyph glyph = getGlyph(glyph_id); + final Font.Glyph glyph = getGlyph( codepoint ); if( glyph.isUndefined() ) { // break kerning, drop undefined - advanceTotal += glyph.getAdvanceFU(); + advanceTotal += glyph.getAdvanceWidthFU(); left_glyph = null; } else if( glyph.isWhitespace() ) { // break kerning, but include its bounding box space left_glyph = null; temp1.translate(advanceTotal, y, temp2); res.resize(temp1.transform(glyph.getBoundsFU(), temp_box)); - advanceTotal += glyph.getAdvanceFU(); + advanceTotal += glyph.getAdvanceWidthFU(); } else { // regular contour if( null != left_glyph ) { - advanceTotal += left_glyph.getKerningFU(glyph_id); + advanceTotal += left_glyph.getKerningFU( glyph.getID() ); } temp1.translate(advanceTotal, y, temp2); res.resize(temp1.transform(glyph.getBoundsFU(), temp_box)); - advanceTotal += glyph.getAdvanceFU(); + advanceTotal += glyph.getAdvanceWidthFU(); left_glyph = glyph; } } @@ -383,7 +484,7 @@ class TypecastFont implements Font { } final Font.GlyphVisitor visitor = new Font.GlyphVisitor() { @Override - public final void visit(final char symbol, final Font.Glyph shape, final AffineTransform t) { + public final void visit(final Font.Glyph shape, final AffineTransform t) { // nop } }; return processString(visitor, transform, string, temp1, temp2); @@ -414,8 +515,8 @@ class TypecastFont implements Font { final AABBox temp_box = new AABBox(); for(int i=0; i< charCount; i++) { - final char character = string.charAt(i); - if( '\n' == character ) { + final char codepoint = string.charAt(i); + if( '\n' == codepoint ) { y -= lineHeight; advanceTotal = 0; left_glyph = null; @@ -426,29 +527,27 @@ class TypecastFont implements Font { } else { temp1.setToIdentity(); } - final int glyph_id = getGlyphID(character); - - final Font.Glyph glyph = getGlyph(glyph_id); + final Font.Glyph glyph = getGlyph( codepoint ); if( glyph.isUndefined() ) { // break kerning, drop undefined - advanceTotal += glyph.getAdvance(); + advanceTotal += glyph.getAdvanceWidth(); left_glyph = null; } else if( glyph.isWhitespace() ) { // break kerning, but include its bounding box space and visit the visitor left_glyph = null; temp1.translate(advanceTotal, y, temp2); res.resize(temp1.transform(glyph.getBounds(), temp_box)); - visitor.visit(character, glyph, temp1); - advanceTotal += glyph.getAdvance(); + visitor.visit(glyph, temp1); + advanceTotal += glyph.getAdvanceWidth(); } else { // regular contour if( null != left_glyph ) { - advanceTotal += left_glyph.getKerning(glyph_id); + advanceTotal += left_glyph.getKerning( glyph.getID() ); } temp1.translate(advanceTotal, y, temp2); res.resize(temp1.transform(glyph.getShape().getBounds(), temp_box)); - visitor.visit(character, glyph, temp1); - advanceTotal += glyph.getAdvance(); + visitor.visit(glyph, temp1); + advanceTotal += glyph.getAdvanceWidth(); left_glyph = glyph; } } @@ -464,22 +563,14 @@ class TypecastFont implements Font { final int charCount = string.length(); for(int i=0; i< charCount; i++) { - final char character = string.charAt(i); - if( '\n' != character ) { - final Glyph glyph = getGlyph(getGlyphID(character)); - if( null != glyph.getShape() ) { // also covers 'space' and all non-contour symbols - visitor.visit(character, glyph); - } + final char codepoint = string.charAt(i); + if( '\n' != codepoint ) { + visitor.visit( getGlyph( codepoint ) ); } } } @Override - final public int getNumGlyphs() { - return font.getNumGlyphs(); - } - - @Override public boolean isPrintableChar( final char c ) { return FontFactory.isPrintableChar(c); } diff --git a/src/jogl/classes/jogamp/graph/font/typecast/TypecastGlyph.java b/src/jogl/classes/jogamp/graph/font/typecast/TypecastGlyph.java index fa8271088..d49fb7393 100644 --- a/src/jogl/classes/jogamp/graph/font/typecast/TypecastGlyph.java +++ b/src/jogl/classes/jogamp/graph/font/typecast/TypecastGlyph.java @@ -39,7 +39,7 @@ public final class TypecastGlyph implements Font.Glyph { public static final short INVALID_ID = (short)((1 << 16) - 1); public static final short MAX_ID = (short)((1 << 16) - 2); - private static final String dot_undef_NAME = ".notdef"; + /* pp */ static final String dot_undef_NAME = ".notdef"; private static final String NULL_NAME = "NULL"; private static final String null_NAME = "null"; private static final String dot_null_NAME = ".null"; @@ -83,6 +83,7 @@ public final class TypecastGlyph implements Font.Glyph { return dst; } + private final char codepoint; private final int id; private final String name; private final boolean isUndefined; @@ -101,17 +102,19 @@ public final class TypecastGlyph implements Font.Glyph { /** * * @param font - * @param name from `post` table + * @param codepoint TODO * @param id + * @param name from `post` table * @param bbox in font-units * @param advance from hmtx in font-units * @param leftSideBearings from hmtx in font-units * @param shape */ - protected TypecastGlyph(final TypecastFont font, final int id, final String name, - final AABBox bbox, final int advance, final int leftSideBearings, - final KernSubtable kernSub, final OutlineShape shape, - final boolean isUndefined, final boolean isWhiteSpace) { + protected TypecastGlyph(final TypecastFont font, final char codepoint, final int id, + final String name, final AABBox bbox, final int advance, + final int leftSideBearings, final KernSubtable kernSub, + final OutlineShape shape, final boolean isUndefined, final boolean isWhiteSpace) { + this.codepoint = codepoint; this.id = id; this.name = name; this.isUndefined = isUndefined; @@ -155,6 +158,9 @@ public final class TypecastGlyph implements Font.Glyph { } @Override + public char getCodepoint() { return codepoint; } + + @Override public final int getID() { return id; } @Override @@ -187,10 +193,10 @@ public final class TypecastGlyph implements Font.Glyph { } @Override - public final int getAdvanceFU() { return advance; } + public final int getAdvanceWidthFU() { return advance; } @Override - public float getAdvance() { return font.getMetrics().getScale( advance ); } + public float getAdvanceWidth() { return font.getMetrics().getScale( advance ); } @Override public final int getLeftSideBearingsFU() { return leftSideBearings; } @@ -267,9 +273,10 @@ public final class TypecastGlyph implements Font.Glyph { } final String name_s = null != name ? name : ""; final String shape_s = null != shape ? "shape "+shape.getVertexCount()+"v" : "shape null"; - sb.append("Glyph[id ").append(id).append(" '").append(name_s).append("', ").append(contour_s) + sb.append("Glyph[id 0x").append(Integer.toHexString(id)).append(", cp 0x").append(Integer.toHexString(codepoint)) + .append(", name '").append(name_s).append("', ").append(contour_s) .append(", ").append(shape_s) - .append(", advance ").append(getAdvanceFU()) + .append(", advance ").append(getAdvanceWidthFU()) .append(", leftSideBearings ").append(getLeftSideBearingsFU()) .append(", kerning[size ").append(kerning.length).append(", horiz ").append(this.isKerningHorizontal()).append(", cross ").append(this.isKerningCrossstream()).append("]") .append("]"); @@ -290,9 +297,10 @@ public final class TypecastGlyph implements Font.Glyph { } final String name_s = null != name ? name : ""; final String shape_s = null != shape ? "shape "+shape.getVertexCount()+"v" : "shape null"; - sb.append("Glyph id ").append(id).append(" '").append(name_s).append("', ").append(contour_s) + sb.append("Glyph[id 0x").append(Integer.toHexString(id)).append(", cp 0x").append(Integer.toHexString(codepoint)) + .append(" name '").append(name_s).append("', ").append(contour_s) .append(", shape ").append(shape_s) - .append(", advance ").append(getAdvanceFU()) + .append(", advance ").append(getAdvanceWidthFU()) .append(", leftSideBearings ").append(getLeftSideBearingsFU()) .append(", ").append(getBoundsFU()); |