com.googlecode.blaisemath.graphics.swing.WrappedTextRenderer Maven / Gradle / Ivy
/*
* WrappedTextRenderer.java
* Created on Jan 2, 2013
*/
package com.googlecode.blaisemath.graphics.swing;
/*
* #%L
* BlaiseGraphics
* --
* Copyright (C) 2009 - 2015 Elisha Peterson
* --
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import com.google.common.base.Strings;
import com.googlecode.blaisemath.style.Anchor;
import static com.googlecode.blaisemath.style.Anchor.EAST;
import static com.googlecode.blaisemath.style.Anchor.NORTH;
import static com.googlecode.blaisemath.style.Anchor.NORTHEAST;
import static com.googlecode.blaisemath.style.Anchor.NORTHWEST;
import static com.googlecode.blaisemath.style.Anchor.SOUTH;
import static com.googlecode.blaisemath.style.Anchor.SOUTHEAST;
import static com.googlecode.blaisemath.style.Anchor.SOUTHWEST;
import static com.googlecode.blaisemath.style.Anchor.WEST;
import com.googlecode.blaisemath.style.AttributeSet;
import com.googlecode.blaisemath.style.Styles;
import com.googlecode.blaisemath.util.AnchoredText;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.font.FontRenderContext;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RectangularShape;
import java.util.ArrayList;
import java.util.List;
/**
* Draws a string within the boundaries of a given clip.
*
* @author petereb1
*/
public class WrappedTextRenderer extends TextRenderer {
/** Provides clip boundaries */
protected RectangularShape clipPath;
/** Insets used for text anchoring. */
private Insets insets = new Insets(2,2,2,2);
public WrappedTextRenderer() {
}
@Override
public String toString() {
return String.format("TextStyleWrapped[clip=%s]", clipPath);
}
//
/**
* Sets clip and returns pointer to object
* @param clip the clip to use
* @return this
*/
public WrappedTextRenderer clipPath(RectangularShape clip) {
setTextBounds(clip);
return this;
}
/**
* Sets insets and returns pointer to object
* @param insets the insets to use
* @return this
*/
public WrappedTextRenderer insets(Insets insets) {
this.insets = insets;
return this;
}
//
//
//
// PROPERTY PATTERNS
//
public RectangularShape getTextBounds() {
return clipPath;
}
public void setTextBounds(RectangularShape clip) {
this.clipPath = clip;
}
public Insets getInsets() {
return insets;
}
public void setInsets(Insets insets) {
this.insets = insets;
}
//
@Override
public void render(AnchoredText text, AttributeSet style, Graphics2D canvas) {
if (Strings.isNullOrEmpty(text.getText())) {
return;
}
canvas.setFont(Styles.getFont(style));
canvas.setColor(style.getColor(Styles.FILL));
Rectangle2D clip = clipPath.getBounds2D();
if (clipPath instanceof Ellipse2D) {
Ellipse2D textClip = new Ellipse2D.Double(
clip.getMinX()+insets.left, clip.getMinY()+insets.top,
clip.getWidth()-insets.left-insets.right, clip.getHeight()-insets.top-insets.bottom);
drawInEllipse(text.getText(), style, textClip, canvas);
} else {
Rectangle2D textClip = new Rectangle2D.Double(
clip.getMinX()+insets.left, clip.getMinY()+insets.top,
clip.getWidth()-insets.left-insets.right, clip.getHeight()-insets.top-insets.bottom);
drawInRectangle(text.getText(), style, textClip, canvas);
}
}
private void drawInEllipse(String text, AttributeSet style, Ellipse2D ell, Graphics2D canvas) {
Rectangle2D bounds = canvas.getFontMetrics().getStringBounds(text, canvas);
if (bounds.getWidth() < ell.getWidth() - 8 || ell.getWidth()*.6 < 3 * canvas.getFont().getSize2D()) {
// entire string fits in box... draw centered
AttributeSet centeredStyle = new AttributeSet(style).and(Styles.TEXT_ANCHOR, Anchor.CENTER);
super.render(new AnchoredText(ell.getCenterX(), ell.getCenterY(), text), centeredStyle, canvas);
} else {
// need to wrap string
drawInRectangle(text, style,
new Rectangle2D.Double(
ell.getX()+ell.getWidth()*.15, ell.getY()+ell.getHeight()*.15,
ell.getWidth()*.7, ell.getHeight()*.7),
canvas
);
}
}
private void drawInRectangle(String string, AttributeSet style, Rectangle2D rect, Graphics2D canvas) {
// make font smaller if lots of words
Font font = Styles.getFont(style);
if (rect.getWidth() * rect.getHeight() < (font.getSize() * font.getSize() / 1.5) * string.length()
|| rect.getWidth() < font.getSize() * 5) {
font = font.deriveFont(font.getSize2D()-2);
}
canvas.setFont(font);
float sz = canvas.getFont().getSize2D();
List lines = computeLineBreaks(string, font, rect.getWidth(), rect.getHeight());
double y0;
Anchor textAnchor = Styles.getAnchor(style, Anchor.CENTER);
switch (textAnchor) {
case NORTH:
// fall through
case NORTHWEST:
// fall through
case NORTHEAST:
y0 = rect.getY()+sz;
break;
case SOUTH:
// fall through
case SOUTHWEST:
// fall through
case SOUTHEAST:
y0 = rect.getMaxY()-(lines.size()-1)*(sz+2);
break;
default:
// y-centered
y0 = rect.getCenterY()-(lines.size()/2.0)*(sz+2)+sz;
break;
}
for (String s : lines) {
double wid = canvas.getFontMetrics().getStringBounds(s, canvas).getWidth();
switch (textAnchor) {
case WEST:
// fall through
case SOUTHWEST:
// fall through
case NORTHWEST:
canvas.drawString(s, (float) rect.getX(), (float) y0);
break;
case EAST:
// fall through
case SOUTHEAST:
// fall through
case NORTHEAST:
canvas.drawString(s, (float)(rect.getMaxX()-wid), (float) y0);
break;
default:
// x-centered
canvas.drawString(s, (float)(rect.getCenterX()-wid/2.0), (float) y0);
break;
}
y0 += sz+2;
}
}
/**
* Create set of lines representing the word-wrapped version of the string. Words are
* wrapped at spaces if possible, and always wrapped at line breaks. Lines are constrained to be within given width and height.
* If the string is too long to fit in the given space, it is truncated and "..." appended.
* Assumes lines are separated by current font size + 2.
* @param string initial string
* @param font the font to be drawn in
* @param width width of bounding box
* @param height height of bounding box
* @return lines
*/
public static List computeLineBreaks(String string, Font font, double width, double height) {
FontRenderContext frc = new FontRenderContext(null, true, true);
Rectangle2D sBounds = font.getStringBounds(string, frc);
List lines = new ArrayList();
if (string.length() == 0) {
// do nothing
} else if (width < 3*font.getSize()) {
// if really small, show only first character
lines.add(string.substring(0,1)+"...");
} else if (sBounds.getWidth() <= width-4 && !string.contains("\n")) {
// enough to fit the entire string
lines.add(string);
} else {
// need to wrap string
double totHt = font.getSize()+2;
int pos0 = 0;
int pos1 = 1;
while (pos1 < string.length()) {
while (pos1 <= string.length() && string.charAt(pos1-1) != '\n'
&& font.getStringBounds(string.substring(pos0,pos1), frc).getWidth() < width-4) {
pos1++;
}
if (pos1 >= string.length()) {
pos1 = string.length()+1;
} else if (string.charAt(pos1-1)=='\n') {
// wrap at the line break
} else {
// wrap at the previous space
int idx = string.lastIndexOf(' ', pos1 - 1);
if (idx > pos0) {
pos1 = idx + 2;
}
}
String s = string.substring(pos0, pos1 - 1);
totHt += font.getSize()+2;
if (totHt >= height-2) {
// will be the last line, may need to truncate
if (pos1-1 < string.length()) {
s += "...";
}
while (s.length() >= 4
&& font.getStringBounds(s, frc).getWidth() > width-4) {
s = s.substring(0, s.length() - 4) + "...";
}
lines.add(s);
break;
} else {
lines.add(s);
}
pos0 = pos1 - 1;
if (pos0 < string.length() && string.charAt(pos0)=='\n') {
pos0++;
}
pos1 = pos0 + 1;
}
}
return lines;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy