locationtech / locationtech/jts
Unexpected output from FontGlyphReader for truetype fonts
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 2.2k
- Forks
- 475
- Avg merge
- 14d 10h
- Merged PRs (30d)
- 1
Description
This test illustrates the issue:
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.awt.Font;
import java.awt.FontFormatException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import org.junit.jupiter.api.Test;
import org.locationtech.jts.awt.FontGlyphReader;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.MultiPolygon;
import org.locationtech.jts.geom.Polygon;
/**
* Illustrating unexpected output from {@link FontGlyphReader}
*/
@SuppressWarnings("static-method")
class FontGlyphReaderTest {
private static final GeometryFactory GF = new GeometryFactory();
private static final Font SERIF = Font.decode( FontGlyphReader.FONT_SERIF );
private static final Font KRYPTON;
static {
try( InputStream is = new URI(
"https://github.com/githubnext/monaspace/raw/main/fonts/otf/MonaspaceKrypton-Regular.otf" )
.toURL().openStream() ) {
KRYPTON = Font.createFont( Font.TRUETYPE_FONT, is );
}
catch( FontFormatException | IOException | URISyntaxException e ) {
throw new IllegalStateException( e );
}
}
/**
* For simple characters (completely connected, no holes), both fonts produce
* {@link Polygon} geometry with no interior rings, as we'd expect.
*/
@Test
void simple() {
assertEquals( "Polygon with 0 interior rings",
details( FontGlyphReader.read( "z", SERIF, GF ) ) );
assertEquals( "Polygon with 0 interior rings",
details( FontGlyphReader.read( "z", KRYPTON, GF ) ) );
}
/**
* For characters with two disconnected elements the built-in font produces the
* multipolygon you'd expect, while the truetype font produces a single polygon
* with a hole
*/
@Test
void disconnected() {
assertEquals( "MultiPolygon with 2 constituents",
details( FontGlyphReader.read( "=", SERIF, GF ) ) );
assertEquals( "Polygon with 1 interior rings",
details( FontGlyphReader.read( "=", KRYPTON, GF ) ) );
}
/**
* For characters with holes the truetype font now swings the <i>other</i> way
*/
@Test
void holes() {
assertEquals( "Polygon with 1 interior rings",
details( FontGlyphReader.read( "o", SERIF, GF ) ) );
assertEquals( "MultiPolygon with 2 constituents",
details( FontGlyphReader.read( "o", KRYPTON, GF ) ) );
}
private static String details( Geometry g ) {
StringBuilder sb = new StringBuilder();
sb.append( g.getClass().getSimpleName() );
if( g instanceof Polygon p ) {
sb.append( " with " ).append( p.getNumInteriorRing() ).append( " interior rings" );
}
else if( g instanceof MultiPolygon mp ) {
sb.append( " with " ).append( mp.getNumGeometries() ).append( " constituents" );
}
return sb.toString();
}
}
I suspect that the winding order of the font is messing things up - drawing the geometries reveals that the vertices are in the opposite order in the truetype font when compared against the standard font.
For this font the holes are at least specified in the opposite winding order to the shell, but this SA question suggests that there is no standard for the winding order of the vertices in a font, or even any assurance that the polygon shell is specified before the holes.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with FontGlyphReader and reproduce the supplied FontGlyphReaderTest cases using the standard and Monaspace Krypton fonts. Inspect how glyph contours and winding order are converted into Geometry, then verify that disconnected components and holes retain the expected Polygon or MultiPolygon structure. Done means the three assertions pass for both fonts.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java
- Domain
- computer-graphics
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 28/100