Skip to content
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

Add support for rectangular (box-styled) halo #1407

Merged
merged 5 commits into from
Dec 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions deegree-core/deegree-core-rendering-2d/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<dependency>
<groupId>com.mpobjects.jasperreports.font</groupId>
<artifactId>jasperreports-fonts-liberation</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

</project>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ Occam Labs UG (haftungsbeschränkt)
import static java.awt.BasicStroke.JOIN_ROUND;
import static java.awt.geom.AffineTransform.getTranslateInstance;
import static java.lang.Math.toRadians;
import static org.deegree.commons.utils.math.MathUtils.isZero;
import static org.deegree.commons.utils.math.MathUtils.round;
import static org.slf4j.LoggerFactory.getLogger;

Expand All @@ -55,19 +54,19 @@ Occam Labs UG (haftungsbeschränkt)
import java.awt.font.TextLayout;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Path2D.Double;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.deegree.geometry.Geometry;
import org.deegree.geometry.multi.MultiCurve;
import org.deegree.geometry.multi.MultiGeometry;
import org.deegree.geometry.multi.MultiLineString;
import org.deegree.geometry.multi.MultiPoint;
import org.deegree.geometry.primitive.*;
import org.deegree.rendering.r2d.strokes.OffsetStroke;
import org.deegree.rendering.r2d.strokes.TextStroke;
import org.deegree.geometry.primitive.Curve;
import org.deegree.geometry.primitive.GeometricPrimitive;
import org.deegree.geometry.primitive.Point;
import org.deegree.geometry.primitive.Polygon;
import org.deegree.geometry.primitive.Surface;
import org.deegree.style.styling.TextStyling;
import org.slf4j.Logger;

Expand Down Expand Up @@ -204,13 +203,36 @@ public void render( Label pLabel ) {
if ( pLabel.getStyling().halo != null ) {
context.fillRenderer.applyFill( pLabel.getStyling().halo.fill, pLabel.getStyling().uom );

BasicStroke stroke = new BasicStroke(
round( 2 * context.uomCalculator.considerUOM( pLabel.getStyling().halo.radius,
pLabel.getStyling().uom ) ),
CAP_BUTT, JOIN_ROUND );
renderer.graphics.setStroke( stroke );
renderer.graphics.draw( pLabel.getLayout().getOutline(
getTranslateInstance( pLabel.getDrawPosition().x, pLabel.getDrawPosition().y ) ) );
int haloSize = round( 2 * context.uomCalculator.considerUOM( pLabel.getStyling().halo.radius,
pLabel.getStyling().uom ) );

if ( haloSize < 0 ) {
// render box styled halo (deegree2 like)
int wi = Math.abs( haloSize );
// prevent useless halo of sub-pixel-size
if ( wi < 1 ) {
wi = 1;
}

int w = (int) ( pLabel.getLayout().getBounds().getWidth() + Math.abs(
pLabel.getDrawPosition().x % 1 ) + 0.5d );
int h = (int) ( pLabel.getLayout().getBounds().getHeight() + Math.abs(
pLabel.getDrawPosition().y % 1 ) + 0.5d );
int bx = (int) pLabel.getDrawPosition().x;
int by = (int) pLabel.getDrawPosition().y;

renderer.graphics.fillRect( bx - wi, by - h - wi, w + wi + wi, h + wi + wi );
} else {
// prevent useless halo of sub-pixel-size
if ( haloSize < 1 ) {
haloSize = 1;
}

BasicStroke stroke = new BasicStroke( haloSize, CAP_BUTT, JOIN_ROUND );
renderer.graphics.setStroke( stroke );
renderer.graphics.draw( pLabel.getLayout().getOutline( getTranslateInstance( pLabel.getDrawPosition().x,
pLabel.getDrawPosition().y ) ) );
}
}

//LOG.debug("LabelRender w:" + pLabel.getLayout().getBounds().getWidth() + " h: "+pLabel.getLayout().getBounds().getHeight()+" x: "+pLabel.getDrawPosition().x + " y: "+pLabel.getDrawPosition().y);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,12 +193,32 @@ void render( TextStyling styling, Font font, String text, Point p ) {
if ( styling.halo != null ) {
renderer.rendererContext.fillRenderer.applyFill( styling.halo.fill, styling.uom );

BasicStroke stroke = new BasicStroke(
round( 2 * renderer.rendererContext.uomCalculator.considerUOM( styling.halo.radius,
styling.uom ) ),
CAP_BUTT, JOIN_ROUND );
renderer.graphics.setStroke( stroke );
renderer.graphics.draw( layout.getOutline( getTranslateInstance( px, py ) ) );
int haloSize = round( 2 * renderer.rendererContext.uomCalculator.considerUOM( styling.halo.radius,
styling.uom ) );
if ( haloSize < 0 ) {
// render box styled halo (deegree2 like)
int wi = Math.abs( haloSize );
// prevent useless halo of sub-pixel-size
if ( wi < 1 ) {
wi = 1;
}

int w = (int) ( width + 0.5d );
int h = (int) ( height + 0.5d );
int bx = (int) px;
int by = (int) py;

renderer.graphics.fillRect( bx - wi, by - h - wi, w + wi + wi, h + wi + wi );
} else {
// prevent useless halo of sub-pixel-size
if ( haloSize < 1 ) {
haloSize = 1;
}

BasicStroke stroke = new BasicStroke( haloSize, CAP_BUTT, JOIN_ROUND );
renderer.graphics.setStroke( stroke );
renderer.graphics.draw( layout.getOutline( getTranslateInstance( px, py ) ) );
}
}

renderer.graphics.setStroke( new BasicStroke() );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,14 @@
import static org.slf4j.LoggerFactory.getLogger;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.GraphicsEnvironment;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.util.Iterator;
import java.util.LinkedList;
Expand Down Expand Up @@ -121,6 +124,20 @@ public class Java2DRendererTest extends AbstractSimilarityTest {
} catch ( IOException e ) {
LOG.error( "Unknown error", e );
}

// Load Fonts
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
for ( String fontName : List.of("/com/mpobjects/jasperreports/fonts/liberation/LiberationMono-Regular.ttf",
"/com/mpobjects/jasperreports/fonts/liberation/LiberationSans-Regular.ttf")) {
try (InputStream is = Java2DRendererTest.class.getResourceAsStream( fontName )) {
Font f = Font.createFont( Font.TRUETYPE_FONT, is );
LOG.info("Loaded font with Name {} Font Name: {} Family Name: {}", f.getName(), f.getFontName(), f.getFontName() );
ge.registerFont( f );
} catch ( Exception ex ) {
LOG.error("Failed tor load Font {}: {}", fontName, ex.getMessage());
LOG.trace("Exception loading font", ex);
}
}
}

private void validateImage( RenderedImage img, double time, String testName )
Expand Down Expand Up @@ -584,7 +601,6 @@ public void testTextStyling()
* @throws Exception
*/
@Test
@Ignore
public void testTextStyling2()
throws Exception {
BufferedImage img = new BufferedImage( 1000, 1000, TYPE_INT_ARGB );
Expand Down Expand Up @@ -612,7 +628,9 @@ public void testTextStyling2()
TextStyling styling = new TextStyling();
styling.linePlacement = new LinePlacement();
styling.font.fontSize = 25;
styling.font.fontFamily.add( "Courier" );
//styling.font.fontFamily.add( "Courier" );
// Use layout compatible open source replacement
styling.font.fontFamily.add( "Liberation Mono" );
r2d.render( lineStyle, curves.peek() );
r.render( styling, text, curves.poll() );
styling.linePlacement.repeat = true;
Expand Down Expand Up @@ -684,6 +702,82 @@ public void testTextStyling2()
validateImage( img, time2 - time, "textstyling2" );
}

@Test
public void testTextStylingHalo()
throws
Exception {
BufferedImage img = new BufferedImage( 200, 200, TYPE_INT_ARGB );

long time = currentTimeMillis();
Graphics2D g = img.createGraphics();
GeometryFactory geomFac = new GeometryFactory();
Java2DRenderer r2d = new Java2DRenderer( g, img.getWidth(), img.getHeight(),
geomFac.createEnvelope( new double[] { 0, 0 },
new double[] { 200d, 200d }, mapcs ) );
Java2DTextRenderer r = new Java2DTextRenderer( r2d );

LinkedList<Point> points = new LinkedList<Point>();
points.add( geomFac.createPoint( null, new double[] { 100 ,50}, mapcs ) );
points.add( geomFac.createPoint( null, new double[] { 100 ,150}, mapcs ) );

String text = "A b C - X Y Z";
TextStyling styling = new TextStyling();
styling.font.fontSize = 20;
styling.font.fontFamily.clear();
styling.font.fontFamily.add( "Liberation Sans" );
styling.halo = new Halo();
styling.halo.radius = 10;
styling.halo.fill = new Fill();
styling.halo.fill.color = Color.RED;
r.render( styling, text, points.poll() );
styling.halo.radius = -10;
r.render( styling, text, points.poll() );

g.dispose();
long time2 = currentTimeMillis();
validateImage( img, time2 - time, "textstylinghalo" );
}

@Test
public void testTextStylingLabelHalo()
throws
Exception {
BufferedImage img = new BufferedImage( 200, 200, TYPE_INT_ARGB );

long time = currentTimeMillis();
Graphics2D g = img.createGraphics();
GeometryFactory geomFac = new GeometryFactory();
Java2DRenderer r2d = new Java2DRenderer( g, img.getWidth(), img.getHeight(),
geomFac.createEnvelope( new double[] { 0, 0 },
new double[] { 200d, 200d }, mapcs ) );
Java2DTextRenderer tr = new Java2DTextRenderer( r2d );
Java2DLabelRenderer r = new Java2DLabelRenderer( r2d, tr );

LinkedList<Point> points = new LinkedList<Point>();
points.add( geomFac.createPoint( null, new double[] { 100 ,50}, mapcs ) );
points.add( geomFac.createPoint( null, new double[] { 100 ,150}, mapcs ) );

String text = "A b C - X Y Z";
TextStyling styling = new TextStyling();
styling.font.fontSize = 20;
styling.font.fontFamily.clear();
styling.font.fontFamily.add( "Liberation Sans" );
styling.halo = new Halo();
styling.halo.radius = 10;
styling.halo.fill = new Fill();
styling.halo.fill.color = Color.RED;

r.createLabel( styling, text, points.poll() );
styling = styling.copy();
styling.halo.radius = -10;
r.createLabel( styling, text, points.poll() );
r.render(r.getLabels());

g.dispose();
long time2 = currentTimeMillis();
validateImage( img, time2 - time, "textstylinghalo" );
}

@Test(timeout = 2500)
public void testPolygonStylingSmallClipping()
throws Exception {
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -1010,6 +1010,33 @@ support ogc:Expressions as child elements. Example:
</ExternalGraphic>
----

===== Text with rectangular Halo

For the cartographic design of text, it is possible to place a rectangular box behind the text instead of a halo effect.
To enable the rectangular box behind a text use an negative value for the `Radius` of `Halo` in the `TextSymbolizer`.

.Example of regular `Halo` (`Radius` of `3.0`) on the left and rectangular `Halo` (`Radius` of `-3.0`) on the right.
image::renderstyles_halo_regular_and_boxed.png.png[]

.Symbolizer used in previous example
[source,xml]
----
<TextSymbolizer>
<!-- Label omitted -->
<Font>
<SvgParameter name="font-family">Sans-Serif</SvgParameter>
<SvgParameter name="font-size">30</SvgParameter>
</Font>
<Halo>
<Radius>-3.0</Radius>
<Fill>
<SvgParameter name="fill">#FF0000</SvgParameter>
<SvgParameter name="fill-opacity">0.4</SvgParameter>
</Fill>
</Halo>
</TextSymbolizer>
----

===== GraphicStroke extensions

By default, a _GraphicStroke_ is drawn repeatedly, but it can also be
Expand Down
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,12 @@
<version>${jsonpath.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.mpobjects.jasperreports.font</groupId>
<artifactId>jasperreports-fonts-liberation</artifactId>
<version>2.1.2</version>
<scope>test</scope>
</dependency>
<!-- apache commons -->
<dependency>
<groupId>commons-io</groupId>
Expand Down