-
Notifications
You must be signed in to change notification settings - Fork 443
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Unexpected output from FontGlyphReader for truetype fonts #1075
Comments
I'm currently avoiding the effects of this issue by: |
That workaround quickly proved to be touchingly naive - it failed on more complex glyphs like As always, stepping away from the problem provoked the solution into revealing itself: stop using
Extracting loops char[] chars = { character };
GlyphVector gv = font.createGlyphVector( FRC, chars );
List<LinearRing> rings = new ArrayList<>();
for( int i = 0; i < gv.getNumGlyphs(); i++ ) {
@SuppressWarnings("unchecked")
List<Coordinate[]> loops = ShapeReader.toCoordinates(
gv.getGlyphOutline( i ).getPathIterator(
AffineTransform.getScaleInstance( 1, -1 ),
font.getSize() / 400.0f ) );
for( Coordinate[] loop : loops ) {
// remove repeated vertices - they mess up triangulation
CoordinateList cl = new CoordinateList( loop, false );
rings.add( GEO_FACT.createLinearRing( cl.toCoordinateArray() ) );
}
}
MultiPolygon geometry = EvenOddPolygonBuilder.build(
GEO_FACT, rings.toArray( LinearRing[]::new ) ); Combining loops into polygons: import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.LinearRing;
import org.locationtech.jts.geom.MultiPolygon;
import org.locationtech.jts.geom.Polygon;
/**
* Combines multiple non-intersecting {@link LinearRing}s into a
* {@link MultiPolygon} where the interior is defined by the even-odd winding
* rule
*/
public class EvenOddPolygonBuilder {
/**
* Applies the even-odd winding rule to a set of line loops
*
* @param geoFact utility methods
* @param rings A set of simple line loops with no mutual intersections
* @return the geometry of applying the even-odd rule to the rings
*/
public static MultiPolygon build( GeometryFactory geoFact, LinearRing... rings ) {
for( int i = 0; i < rings.length; i++ ) {
for( int j = i + 1; j < rings.length; j++ ) {
if( rings[i].intersects( rings[j] ) ) {
throw new IllegalArgumentException( rings[i] + " intersects with " + rings[j] );
}
}
}
ContainmentNode root = new ContainmentNode( null );
for( LinearRing ring : rings ) {
root.add( geoFact.createPolygon( ring ) );
}
return geoFact.createMultiPolygon(
root.buildPolygons( geoFact, false, new ArrayList<>() )
.toArray( Polygon[]::new ) );
}
private static class ContainmentNode {
public Polygon area;
public final Collection<ContainmentNode> contents = new ArrayList<>();
ContainmentNode( Polygon area ) {
this.area = area;
}
boolean add( Polygon candidate ) {
if( area == null || area.contains( candidate ) ) {
// does it belong to any of the children?
for( ContainmentNode content : contents ) {
if( content.add( candidate ) ) {
return true;
}
}
// it's not contained by any of the children, so it must be ours
ContainmentNode node = new ContainmentNode( candidate );
// check if any of our current children _actually_ belong to our new child
Iterator<ContainmentNode> children = contents.iterator();
while( children.hasNext() ) {
ContainmentNode child = children.next();
if( node.area.contains( child.area ) ) {
children.remove();
node.contents.add( child );
}
}
contents.add( node );
return true;
}
return false;
}
List<Polygon> buildPolygons( GeometryFactory geoFact, boolean isPolygon,
List<Polygon> polygons ) {
if( isPolygon ) {
// this level of the tree represents polygons - our direct children are the
// holes in our shell
Polygon polygon = geoFact.createPolygon(
area.getExteriorRing(),
contents.stream()
.map( child -> child.area.getExteriorRing() )
.toArray( LinearRing[]::new ) );
polygon.normalize();
polygons.add( polygon );
}
// recurse to the next level, flipping the polygon/hole flag as we go
for( ContainmentNode child : contents ) {
child.buildPolygons( geoFact, !isPolygon, polygons );
}
return polygons;
}
}
} |
This test illustrates the issue:
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.
The text was updated successfully, but these errors were encountered: