From 93c51380f34c3eb203f46df52fed49a8a967510e Mon Sep 17 00:00:00 2001 From: Sven Gothel Date: Sun, 12 Feb 2023 07:17:16 +0100 Subject: 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) --- .../jogamp/graph/font/typecast/TypecastFont.java | 61 +++------- .../font/typecast/TypecastFontConstructor.java | 4 +- .../jogamp/graph/font/typecast/TypecastGlyph.java | 133 +++++++++++++++++++-- .../graph/font/typecast/TypecastRenderer.java | 5 +- 4 files changed, 147 insertions(+), 56 deletions(-) (limited to 'src/jogl/classes/jogamp/graph/font') 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 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; @@ -152,23 +153,17 @@ class TypecastFont implements Font { metrics = new TypecastHMetrics(this); } - @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 @@ -189,34 +184,6 @@ class TypecastFont implements Font { return metrics; } - @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) @@ -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); } @@ -247,6 +303,39 @@ public final class TypecastGlyph implements Font.Glyph { return metrics.getAdvance(pixelSize); } + @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 vertexFactory) { + public static OutlineShape buildShape(final char symbol, final jogamp.graph.font.typecast.ot.Glyph glyph, final Factory 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; -- cgit v1.2.3