summaryrefslogtreecommitdiffstats
path: root/src/jogl
diff options
context:
space:
mode:
authorSven Gothel <[email protected]>2023-02-12 07:17:16 +0100
committerSven Gothel <[email protected]>2023-02-12 07:17:16 +0100
commit93c51380f34c3eb203f46df52fed49a8a967510e (patch)
tree52d8745dffc4f7581efdc118f7ec0792b7b75315 /src/jogl
parent87060fe41b559418ea7b383e162d3d80fb515e0b (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')
-rw-r--r--src/jogl/classes/com/jogamp/graph/curve/opengl/TextRegionUtil.java35
-rw-r--r--src/jogl/classes/com/jogamp/graph/font/Font.java56
-rw-r--r--src/jogl/classes/jogamp/graph/font/typecast/TypecastFont.java61
-rw-r--r--src/jogl/classes/jogamp/graph/font/typecast/TypecastFontConstructor.java4
-rw-r--r--src/jogl/classes/jogamp/graph/font/typecast/TypecastGlyph.java133
-rw-r--r--src/jogl/classes/jogamp/graph/font/typecast/TypecastRenderer.java5
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;