diff options
author | Sven Gothel <[email protected]> | 2023-02-12 07:17:16 +0100 |
---|---|---|
committer | Sven Gothel <[email protected]> | 2023-02-12 07:17:16 +0100 |
commit | 93c51380f34c3eb203f46df52fed49a8a967510e (patch) | |
tree | 52d8745dffc4f7581efdc118f7ec0792b7b75315 /src/jogl | |
parent | 87060fe41b559418ea7b383e162d3d80fb515e0b (diff) |
Graph font/typecast: Adopt to our Typecast updates (see below); Fix kerning; Use TestTextRendererNEWT01 to produce validation snaps
- Move kerning handling from Font to Font.Glyph using binary-search for right-glyph-id on kerning subset from this instance (left)
- TextRegionUtil: Kerning must be added before translation as it applies before the current right-glyph.
- TestTextRendererNEWT01 produces validation snapshots against LibreOffice print-preview snapshots
- GPUTextRendererListenerBase01 added another text for kerning validation, show more font-size details (pt, px, mm)
Diffstat (limited to 'src/jogl')
6 files changed, 195 insertions, 99 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 5aa2be258..a4ba4bf52 100644 --- a/src/jogl/classes/com/jogamp/graph/curve/opengl/TextRegionUtil.java +++ b/src/jogl/classes/com/jogamp/graph/curve/opengl/TextRegionUtil.java @@ -89,25 +89,21 @@ public class TextRegionUtil { final int charCount = str.length(); // region.setFlipped(true); - final Font.Metrics metrics = font.getMetrics(); final int lineHeight = font.getLineHeightFU(); int y = 0; int advanceTotal = 0; - char left_character = 0; - int left_glyphid = 0; + Font.Glyph left_glyph = null; 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; + left_glyph = null; } else if (character == ' ') { advanceTotal += font.getAdvanceWidthFU(Glyph.ID_SPACE); - left_glyphid = 0; - left_character = character; + left_glyph = null; } else { // reset transform if( null != transform ) { @@ -115,27 +111,20 @@ public class TextRegionUtil { } else { temp1.setToIdentity(); } - temp1.translate(advanceTotal, y, temp2); - final Font.Glyph glyph = font.getGlyph(character); final OutlineShape glyphShape = glyph.getShape(); if( null == glyphShape ) { - left_glyphid = 0; + left_glyph = null; + temp1.translate(advanceTotal, y, temp2); continue; } - visitor.visit(glyphShape, temp1); - 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); + if( null != left_glyph ) { + advanceTotal += left_glyph.getKerningFU(glyph.getID()); } - // advanceTotal += glyph.getAdvance(pixelSize, true) - // + font.getKerning(left_glyphid, right_glyphid); - left_glyphid = right_glyphid; - left_character = character; + temp1.translate(advanceTotal, y, temp2); + visitor.visit(glyphShape, temp1); + advanceTotal += glyph.getAdvanceFU(); + left_glyph = glyph; } } } @@ -351,7 +340,7 @@ public class TextRegionUtil { 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) + return sb.append( font.getName(Font.NAME_UNIQUNAME) ) .append(".").append(str.hashCode()).append(".").append(special).toString(); } diff --git a/src/jogl/classes/com/jogamp/graph/font/Font.java b/src/jogl/classes/com/jogamp/graph/font/Font.java index 9f25b5481..5c63227b3 100644 --- a/src/jogl/classes/com/jogamp/graph/font/Font.java +++ b/src/jogl/classes/com/jogamp/graph/font/Font.java @@ -144,6 +144,8 @@ public interface Font { Font getFont(); char getSymbol(); + + /** Return this glyph's ID */ int getID(); /** @@ -188,20 +190,49 @@ public interface Font { */ float getAdvance(final float pixelSize); + /** True if kerning values are horizontal, otherwise vertical */ + boolean isKerningHorizontal(); + /** True if kerning values are perpendicular to text flow, otherwise along with flow */ + boolean isKerningCrossstream(); + + /** Return the number of kerning values stored for this glyph, associated to a right hand glyph. */ + int getKerningPairCount(); + + /** + * Returns the optional kerning inter-glyph distance within words between this glyph and the given right glyph_id in font-units to be divided by unitsPerEM + * + * @param right_glyphid right glyph code id + * @return font-units to be divided by unitsPerEM + */ + int getKerningFU(final int right_glyphid); + + /** + * Returns the optional kerning inter-glyph distance within words between this glyph and the given right glyph_id in fractional font em-size [0..1]. + * + * @param right_glyphid right glyph code id + * @return fractional font em-size distance [0..1] + */ + float getKerning(final int right_glyphid); + OutlineShape getShape(); @Override int hashCode(); + + @Override + String toString(); + + /** Return all glyph details as string. */ + String fullString(); } 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> */ - StringBuilder getFullFamilyName(final StringBuilder buffer); + String getFullFamilyName(); StringBuilder getAllNames(final StringBuilder string, final String separator); @@ -225,27 +256,12 @@ public interface Font { */ int getAdvanceWidthFU(final int glyphID); - /** - * Returns the optional kerning inter-glyph distance within words in fractional font em-size [0..1]. - * - * @param left_glyphid left glyph code id - * @param right_glyphid right glyph code id - * @return fractional font em-size distance [0..1] - */ - 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(); /** diff --git a/src/jogl/classes/jogamp/graph/font/typecast/TypecastFont.java b/src/jogl/classes/jogamp/graph/font/typecast/TypecastFont.java index 885261bf9..146bc0380 100644 --- a/src/jogl/classes/jogamp/graph/font/typecast/TypecastFont.java +++ b/src/jogl/classes/jogamp/graph/font/typecast/TypecastFont.java @@ -27,13 +27,14 @@ */ package jogamp.graph.font.typecast; -import jogamp.graph.font.typecast.ot.OTFont; import jogamp.graph.font.typecast.ot.OTFontCollection; +import jogamp.graph.font.typecast.ot.TTFont; import jogamp.graph.font.typecast.ot.table.CmapFormat; 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.KernSubtable; import jogamp.graph.font.typecast.ot.table.KernSubtableFormat0; import jogamp.graph.font.typecast.ot.table.KernTable; import jogamp.graph.font.typecast.ot.table.KerningPair; @@ -54,7 +55,7 @@ class TypecastFont implements Font { private static final Vertex.Factory<SVertex> vertexFactory = SVertex.factory(); // private final OTFontCollection fontset; - /* pp */ final OTFont font; + /* pp */ final TTFont font; private final CmapFormat cmapFormat; private final int cmapentries; private final IntObjectHashMap char2Glyph; @@ -153,22 +154,16 @@ class TypecastFont implements Font { } @Override - public StringBuilder getName(final StringBuilder sb, final int nameIndex) { - return font.getName(nameIndex, sb); - } - @Override public String getName(final int nameIndex) { - return getName(null, nameIndex).toString(); + return font.getName(nameIndex); } @Override public StringBuilder getAllNames(final StringBuilder sb, final String separator) { return font.getAllNames(sb, separator); } @Override - public StringBuilder getFullFamilyName(StringBuilder sb) { - sb = getName(sb, Font.NAME_FAMILY).append("-"); - getName(sb, Font.NAME_SUBFAMILY); - return sb; + public String getFullFamilyName() { + return getName(Font.NAME_FAMILY) + "-" + getName(Font.NAME_SUBFAMILY); } @Override @@ -190,34 +185,6 @@ 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) { @@ -241,7 +208,7 @@ class TypecastFont implements Font { if (null == result) { final int glyph_id = getGlyphID( symbol ); - jogamp.graph.font.typecast.ot.OTGlyph glyph = font.getGlyph(glyph_id); + jogamp.graph.font.typecast.ot.Glyph glyph = font.getGlyph(glyph_id); final int glyph_advance; final AABBox glyph_bbox; if(null == glyph) { @@ -264,7 +231,14 @@ class TypecastFont implements Font { 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, glyph_id, glyph_bbox, glyph_advance, shape); + KernSubtable kernSub = null; + { + final KernTable kern = font.getKernTable(); + if (kern != null ) { + kernSub = kern.getSubtable0(); + } + } + result = new TypecastGlyph(this, symbol, glyph_id, glyph_bbox, glyph_advance, kernSub, shape); if(DEBUG) { final PostTable post = font.getPostTable(); final String glyph_name = null != post ? post.getGlyphName(glyph_id) : "n/a"; @@ -557,9 +531,10 @@ class TypecastFont implements Font { @Override public String toString() { - return getFullFamilyName(null).toString(); + return getFullFamilyName(); } + @SuppressWarnings("unused") @Override public String fullString() { final StringBuilder sb = new StringBuilder(); @@ -569,7 +544,7 @@ class TypecastFont implements Font { if( null != font.getVheaTable() ) { sb.append("\n\n").append(font.getVheaTable()); } - if( null != font.getKernTable() ) { + if( false && null != font.getKernTable() ) { // too long final PostTable post = font.getPostTable(); final KernTable kern = font.getKernTable(); sb.append("\n\n").append(kern); diff --git a/src/jogl/classes/jogamp/graph/font/typecast/TypecastFontConstructor.java b/src/jogl/classes/jogamp/graph/font/typecast/TypecastFontConstructor.java index ef7f38e64..72dd95da2 100644 --- a/src/jogl/classes/jogamp/graph/font/typecast/TypecastFontConstructor.java +++ b/src/jogl/classes/jogamp/graph/font/typecast/TypecastFontConstructor.java @@ -40,11 +40,11 @@ public class TypecastFontConstructor implements FontConstructor { @Override public Font create(final File ffile) throws IOException { - return new TypecastFont( OTFontCollection.create(ffile) ); + return new TypecastFont( new OTFontCollection(ffile) ); } @Override public Font create(final InputStream istream, final int streamLen) throws IOException { - return new TypecastFont( OTFontCollection.create(istream, streamLen) ); + return new TypecastFont( new OTFontCollection(istream, streamLen) ); } } diff --git a/src/jogl/classes/jogamp/graph/font/typecast/TypecastGlyph.java b/src/jogl/classes/jogamp/graph/font/typecast/TypecastGlyph.java index b36196ee5..b5876758f 100644 --- a/src/jogl/classes/jogamp/graph/font/typecast/TypecastGlyph.java +++ b/src/jogl/classes/jogamp/graph/font/typecast/TypecastGlyph.java @@ -32,6 +32,8 @@ 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.KernSubtable; +import jogamp.graph.font.typecast.ot.table.KerningPair; import jogamp.graph.font.typecast.ot.table.PostTable; public final class TypecastGlyph implements Font.Glyph { @@ -170,9 +172,36 @@ 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 int[][] growPairArray(final int[][] src) { + final int length = src.length; + final int new_length = length * 2; + final int[/*right_glyphid*/][/*value*/] dst = new int[new_length][2]; + for (int i = 0; i < length; i++) { + dst[i][0] = src[i][0]; + dst[i][1] = src[i][1]; + } + return dst; + } + + private static int[][] trimPairArray(final int[][] src, final int new_length) { + final int length = src.length; + if( new_length >= length ) { + return src; + } + final int[/*right_glyphid*/][/*value*/] dst = new int[new_length][2]; + for (int i = 0; i < new_length; i++) { + dst[i][0] = src[i][0]; + dst[i][1] = src[i][1]; + } + return dst; + } + private final char symbol; - private final OutlineShape shape; // in EM units private final int id; + private final int[/*right_glyphid*/][/*value*/] kerning; + private final boolean kerning_horizontal; + private final boolean kerning_crossstream; + private final OutlineShape shape; // in EM units private final Metrics metrics; /** @@ -184,10 +213,37 @@ public final class TypecastGlyph implements Font.Glyph { * @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) { + protected TypecastGlyph(final TypecastFont font, final char symbol, final int id, final AABBox bbox, final int advance, + final KernSubtable kernSub, final OutlineShape shape) { this.symbol = symbol; - this.shape = shape; this.id = id; + if( null != kernSub && kernSub.areKerningValues() ) { + int pair_sz = 64; + int pair_idx = 0; + int[/*right_glyphid*/][/*value*/] pairs = new int[pair_sz][2]; + for (int i = 0; i < kernSub.getKerningPairCount(); i++) { + final KerningPair kpair = kernSub.getKerningPair(i); + if( kpair.getLeft() == id ) { + if( pair_idx == pair_sz ) { + pairs = growPairArray(pairs); + pair_sz = pairs.length; + } + pairs[pair_idx][0] = kpair.getRight(); + pairs[pair_idx][1] = kpair.getValue(); + ++pair_idx; + } else if( kpair.getLeft() > id ) { + break; // early out + } + } + this.kerning = trimPairArray(pairs, pair_idx); + this.kerning_horizontal = kernSub.isHorizontal(); + this.kerning_crossstream = kernSub.isCrossstream(); + } else { + this.kerning = new int[0][0]; + this.kerning_horizontal = true; + this.kerning_crossstream = true; + } + this.shape = shape; this.metrics = new Metrics(font, bbox, advance); } @@ -248,6 +304,39 @@ public final class TypecastGlyph implements Font.Glyph { } @Override + public final boolean isKerningHorizontal() { return kerning_horizontal; } + + @Override + public final boolean isKerningCrossstream() { return kerning_crossstream; } + + @Override + public final int getKerningPairCount() { return kerning.length; } + + @Override + public final int getKerningFU(final int right_glyphid) { + // binary search in ordered kerning table + int l = 0; + int h = kerning.length-1; + while( l <= h ) { + final int i = ( l + h ) / 2; + final int k_right = kerning[i][0]; + if ( k_right < right_glyphid ) { + l = i + 1; + } else if ( k_right > right_glyphid ) { + h = i - 1; + } else { + return kerning[i][1]; + } + } + return 0; + } + + @Override + public final float getKerning(final int right_glyphid) { + return getScale( getKerningFU(right_glyphid) ); + } + + @Override public final OutlineShape getShape() { return this.shape; } @@ -263,10 +352,38 @@ public final class TypecastGlyph implements Font.Glyph { 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(); + final StringBuilder sb = new StringBuilder(); + sb.append("Glyph id ").append(id).append(" '").append(glyph_name).append("'") + .append(", advance ").append(getAdvanceFU()) + .append(", kerning[size ").append(kerning.length).append(", horiz ").append(this.isKerningHorizontal()).append(", cross ").append(this.isKerningCrossstream()).append("]"); + return sb.toString(); + } + + @Override + public String fullString() { + final PostTable post = metrics.getFont().getPostTable(); + final String glyph_name = null != post ? post.getGlyphName(id) : "n/a"; + final StringBuilder sb = new StringBuilder(); + sb.append("Glyph id ").append(id).append(" '").append(glyph_name).append("'") + .append(", advance ").append(getAdvanceFU()) + .append(", ").append(getBBoxFU()); + + sb.append("\n Kerning: size ").append(kerning.length).append(", horiz ").append(this.isKerningHorizontal()).append(", cross ").append(this.isKerningCrossstream()); + final int left = getID(); + for (int i = 0; i < kerning.length; i++) { + final int right = kerning[i][0]; + final int value = kerning[i][1]; + 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(value); + } + return sb.toString(); } } diff --git a/src/jogl/classes/jogamp/graph/font/typecast/TypecastRenderer.java b/src/jogl/classes/jogamp/graph/font/typecast/TypecastRenderer.java index 09a63d845..472e3e58e 100644 --- a/src/jogl/classes/jogamp/graph/font/typecast/TypecastRenderer.java +++ b/src/jogl/classes/jogamp/graph/font/typecast/TypecastRenderer.java @@ -27,7 +27,6 @@ */ package jogamp.graph.font.typecast; -import jogamp.graph.font.typecast.ot.OTGlyph; import jogamp.graph.font.typecast.ot.Point; import jogamp.opengl.Debug; @@ -76,7 +75,7 @@ public class TypecastRenderer { shape.addVertex(0, p3.x, p3.y, p3.onCurve); } */ - public static OutlineShape buildShape(final char symbol, final OTGlyph glyph, final Factory<? extends Vertex> vertexFactory) { + public static OutlineShape buildShape(final char symbol, final jogamp.graph.font.typecast.ot.Glyph glyph, final Factory<? extends Vertex> vertexFactory) { // // See Typecast: GlyphPathFactory.addContourToPath(..) // @@ -111,7 +110,7 @@ public class TypecastRenderer { } } */ - private static void buildShapeImpl(final OutlineShape shape, final char symbol, final OTGlyph glyph) { + private static void buildShapeImpl(final OutlineShape shape, final char symbol, final jogamp.graph.font.typecast.ot.Glyph glyph) { // Iterate through all of the points in the glyph. Each time we find a // contour end point, add the point range to the path. int startIndex = 0; |