/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2015, Open Source Geospatial Foundation (OSGeo) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General License for more details. */ package org.geotools.renderer.label; import java.awt.font.FontRenderContext; import java.awt.font.GlyphVector; import java.awt.font.LineMetrics; import java.awt.font.TextLayout; import java.awt.geom.Rectangle2D; import java.util.ArrayList; import java.util.List; /** * Core information needed to draw out a line of text */ class LineInfo { /** * Part of a line that can be rendered with a uniform font * * @author Andrea Aime - GeoSolutions */ static class LineComponent { // the coordinates at which the label should be drawn within the global // label bounds (so these are relative coordinates) private double x; // the text to be drawn private String text; // the text represented as a glyph vector private GlyphVector gv; // the text layout private TextLayout layout; Rectangle2D visualBounds; LineComponent(String text, GlyphVector gv, TextLayout layout) { this.text = text; this.gv = gv; this.layout = layout; } Rectangle2D getVisualBounds() { if (visualBounds == null) { visualBounds = gv.getVisualBounds(); } return visualBounds; } double getX() { return x; } void setX(double x) { this.x = x; } String getText() { return text; } GlyphVector getGlyphVector() { return gv; } TextLayout getLayout() { return layout; } /** * Computes some metrics for this part of the line taking in account the * provided rendering context. This methods will always recompute the * metrics even if the same font rendering context is provided. */ LineMetrics computeLineMetrics(FontRenderContext fontRenderContext) { return gv.getFont().getLineMetrics(text, fontRenderContext); } } // the coordinates at which the label should be drawn within the global // label bounds (so these are relative coordinates) private double y; /** * The components of the line */ private List<LineComponent> components; LineInfo() { components = new ArrayList<>(); } LineInfo(LineComponent component) { this(); components.add(component); } void add(LineComponent component) { components.add(component); } double getWidth() { double width = 0; for (LineComponent lineComponent : components) { width += lineComponent.getGlyphVector().getLogicalBounds().getWidth(); } return width; } Rectangle2D getBounds() { Rectangle2D vb = null; for (LineComponent lineComponent : components) { Rectangle2D componentVisualBounds = lineComponent.getGlyphVector().getVisualBounds(); Rectangle2D componentLogicalBounds = lineComponent.getGlyphVector().getLogicalBounds(); // the logical bounds include the spaces, we want them in the horizontal direction // in order to compose the element in the row, but we need the visual bounds for // vertical alignment Rectangle2D componentBounds = new Rectangle2D.Double(componentLogicalBounds.getX(), componentVisualBounds.getY(), componentLogicalBounds.getWidth(), componentVisualBounds.getHeight()); if (vb == null) { vb = componentBounds; } else { Rectangle2D other = new Rectangle2D.Double(vb.getMaxX(), vb.getMinY(), componentBounds.getWidth(), componentBounds.getHeight()); vb = vb.createUnion(other); } } return vb; } void setMinX(double minX) { double x = minX; for (LineComponent component : components) { component.setX(x); // use the logical bounds to have spaces taken into account x += component.getGlyphVector().getLogicalBounds().getWidth(); } } float getLineOffset() { float offset = Float.NEGATIVE_INFINITY; for (LineComponent component : components) { float co = component.getLayout().getAscent() + component.getLayout().getDescent() + component.getLayout().getLeading(); if (co > offset) { offset = co; } } return offset; } double getLineHeight() { double height = Float.NEGATIVE_INFINITY; for (LineComponent component : components) { double ch = component.getGlyphVector().getVisualBounds().getHeight() - component.getLayout().getDescent(); if (ch > height) { height = ch; } } return height; } float getAscent() { float ascent = Float.NEGATIVE_INFINITY; for (LineComponent component : components) { float ca = component.getLayout().getAscent(); if (ca > ascent) { return ascent; } } return ascent; } List<LineComponent> getComponents() { return components; } double getY() { return y; } void setY(double y) { this.y = y; } }